├── .gitignore
├── LICENSE
├── README.md
├── dist
├── javascript
│ └── main.js
├── js_helper
│ ├── bigRender.js
│ ├── lazy.js
│ └── pageEmulator.js
└── plugin
│ ├── block.script.php
│ ├── compiler.body.php
│ ├── compiler.head.php
│ ├── compiler.html.php
│ ├── compiler.pagelet.php
│ ├── compiler.title.php
│ ├── function.require.php
│ ├── function.widget.php
│ └── lib
│ ├── BigPipe.class.php
│ ├── BigPipeResource.class.php
│ ├── FirstController.class.php
│ ├── NoscriptController.class.php
│ ├── PageController.class.php
│ ├── PageletContext.class.php
│ ├── PageletEvent.class.php
│ └── SmartController.class.php
├── gulpfile.js
├── package.json
└── src
├── javascript
├── BigPipe.js
├── CSSLoader.js
├── Controller.js
├── EventEmitter.js
├── JSLoader.js
├── Pagelet.js
├── Requestor.js
├── Resource.js
├── main.js
├── runtimeAMD.js
└── util
│ ├── JSON.js
│ ├── ajax.js
│ ├── amd.js
│ ├── appendToHead.js
│ ├── bind.js
│ ├── copyProperties.js
│ ├── counter.js
│ ├── derive.js
│ ├── each.js
│ ├── hasOwnProperty.js
│ ├── inArray.js
│ ├── isArray.js
│ ├── isEmpty.js
│ ├── isFunction.js
│ ├── nextTick.js
│ ├── queueCall.js
│ ├── slice.js
│ ├── toArray.js
│ ├── type.js
│ └── util.js
├── js_helper
├── bigRender.js
├── lazy.js
└── pageEmulator.js
└── plugin
├── block.script.php
├── compiler.body.php
├── compiler.head.php
├── compiler.html.php
├── compiler.pagelet.php
├── compiler.title.php
├── function.require.php
├── function.widget.php
└── lib
├── BigPipe.class.php
├── BigPipeResource.class.php
├── FirstController.class.php
├── NoscriptController.class.php
├── PageController.class.php
├── PageletContext.class.php
├── PageletEvent.class.php
└── SmartController.class.php
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .idea/
3 | npm-debug.log
4 | .DS_Store
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # her-runtime
2 | her的运行时,包括后端smarty插件和前端js框架
3 |
4 | ##系统架构
5 |
6 | 首先,我们来看一下her系统架构图
7 |
8 | 
9 |
10 |
11 | her主要包括2个部分:编译系统、运行时
12 |
13 | ###编译系统
14 |
15 | 编译系统主要工作是对模板(tpl)、JS、文件编译,编译包含了资源分析、模板编译、资源打包等过程,最终会生成一个map文件、供运行时的资源管理用。
16 |
17 | 编译工具分别是基于FIS的扩展和基于GULP的扩展,可以根据自己团队的需求选择一种合适的编译工具。
18 |
19 | ###运行时
20 | 运行时包括2个部分,前端和后端:
21 |
22 | 后端运行时是通过PHP+Smarty插件实现的,包括控制器的选择、页面输出控制,通过编译工具输出的map文件做资源管理。
23 |
24 | 前端运行时是JS实现的,包括对Pagelet的渲染控制、AMD模块管理,静态资源加载等功能。
25 |
26 | ## 设计思想
27 |
28 | ###传统页面
29 |
30 | 
31 |
32 | 代码如下:
33 |
34 | ```
35 |
36 |
37 |
38 | HTML内容
39 | HTML内容
40 | HTML内容
41 |
42 |
43 |
44 | ```
45 | 传统页面的输出方式大家都很熟悉,无需赘述
46 |
47 | ###使用Pagelet标记区块
48 |
49 | 
50 |
51 | 代码如下:
52 |
53 | ```
54 |
55 |
56 |
57 | {pagelet}
58 | HTML内容
59 |
62 | {/pagelet}
63 | {pagelet}HTML内容{/pagelet}
64 | {pagelet}HTML内容{/pagelet}
65 |
66 |
67 | ```
68 | 我们用{pagelet}标签去替代div标签,它实际上是一个smarty插件,我们可以看看它最终输出成html的结果:
69 |
70 | ```
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
88 |
89 |
90 | ```
91 | 页面会首先把{Pagelet}区块输出成一个空的div占位,同时会输出code标签,内容就是对应Pagelet的html片段,接下来再通过Bigpipe.onPageletArrive的方式输出这个Pagelet。
92 |
93 | ### 优势
94 |
95 | 自动分析Pagelet依赖的资源(CSS、JS),细粒度的控制页面输出, 配合上Bigrender,极大的优化页面的首屏时间和资源总下载时间
96 |
97 |
--------------------------------------------------------------------------------
/dist/js_helper/bigRender.js:
--------------------------------------------------------------------------------
1 | /*
2 | * File: bigRender.js
3 | * Path: commmon/js/bigRender.js
4 | * Author: HuangYi
5 | * Modifier: HuangYi
6 | * Modified: 2013-7-16
7 | * Description: 延迟渲染js
8 | */
9 | var lazy = require("./lazy.js");
10 |
11 | function add(pagelet) {
12 |
13 | var lazyKey, ele, cssText;
14 | try {
15 | // if(pagelet.parent){
16 | // pagelet.parent.on("load",function(){
17 | // bindBigRender(pagelet);
18 | // });
19 | // }else{
20 | bindBigRender(pagelet);
21 | //}
22 | return true;
23 | } catch (e) {
24 | setTimeout(function() {
25 | throw e;
26 | }, 0);
27 | return false;
28 | }
29 | }
30 |
31 | function bindBigRender(pagelet) {
32 | var ele = document.getElementById(pagelet.id);
33 | addClass(ele, "g-bigrender");
34 |
35 | var lazyKey = lazy.add(ele, function() {
36 |
37 | pagelet.on("load", function() {
38 | removeClass(ele, "g-bigrender");
39 | });
40 | pagelet.load();
41 |
42 | });
43 |
44 | pagelet.on("unload", function() {
45 | lazy.remove(lazyKey);
46 | });
47 | }
48 |
49 | function addClass(element, className) {
50 | if (!element)
51 | return;
52 | var elementClassName = element.className;
53 | if (elementClassName.length == 0) {
54 | element.className = className;
55 | return;
56 | }
57 | if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
58 | return;
59 | element.className = elementClassName + " " + className;
60 | }
61 |
62 | function removeClass(element, className) {
63 | if (!element)
64 | return;
65 | var elementClassName = element.className;
66 | if (elementClassName.length == 0)
67 | return;
68 | if (elementClassName == className) {
69 | element.className = "";
70 | return;
71 | }
72 | if (elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
73 | element.className = elementClassName.replace((new RegExp("(^|\\s)" + className + "(\\s|$)")), " ");
74 | }
75 |
76 |
77 | module.exports = {
78 | add: add
79 | };
80 |
--------------------------------------------------------------------------------
/dist/js_helper/lazy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 一个简单的lazy模块实现,基于getBoundingClientRect
3 | * 提供add和remove方法
4 | */
5 |
6 | function getViewportSize() {
7 | if (window.innerHeight != null) {
8 | return {
9 | width: window.innerWidth,
10 | height: window.innerHeight
11 | };
12 | }
13 | if (document.compatMode == 'CSS1Compat') { //标准模式
14 | return {
15 | width: document.documentElement.clientWidth,
16 | height: document.documentElement.clientHeight
17 | };
18 | }
19 | return {
20 | width: document.body.clientWidth,
21 | height: document.body.clientHeight
22 | };
23 | }
24 |
25 | function getElementOffset(element) {
26 | return {
27 | left: element.getBoundingClientRect().left,
28 | top: element.getBoundingClientRect().top
29 | };
30 | }
31 |
32 | function addHandler(element, type, handler) {
33 | if (element.addEventListener) {
34 | element.addEventListener(type, handler, false);
35 | } else if (element.attachEvent) {
36 | element.attachEvent("on" + type, handler);
37 | } else {
38 | element["on" + type] = handler;
39 | }
40 | }
41 |
42 | var data = {};
43 | var uniqueId = 0;
44 | var offsetBuffer = 50;
45 |
46 | function bind() {
47 | addHandler(window, 'resize', excute);
48 | addHandler(window, 'scroll', excute);
49 | }
50 |
51 | function excute() {
52 | var viewport = getViewportSize();
53 |
54 | for (var i in data) {
55 | var item = data[i];
56 | var ele = item[0];
57 | var callback = item[1];
58 | if (!ele || !ele.getBoundingClientRect) {
59 | return;
60 | }
61 |
62 | if (getElementOffset(ele).top < viewport.height + offsetBuffer) {
63 | callback && callback();
64 | remove(i);
65 | }
66 | }
67 | }
68 |
69 | function add(element, callback) {
70 | data[uniqueId++] = [element, callback];
71 | }
72 |
73 | function remove(id) {
74 | try {
75 | delete data[id];
76 | return true;
77 | } catch (e) {
78 | //return false;
79 | throw new Error('Remove unknow lazy element by id:' + id);
80 | }
81 | }
82 |
83 | bind();
84 |
85 | module.exports = {
86 | add: add,
87 | remove: remove
88 | };
89 |
--------------------------------------------------------------------------------
/dist/js_helper/pageEmulator.js:
--------------------------------------------------------------------------------
1 | var cacheMaxTime = 0, // 缓存时间
2 | appOptions = {}, // app页面管理的options
3 | layer; // 事件代理层
4 |
5 | var EventUtil = {
6 | addHandler: function(element, type, handler, flag) {
7 | if (element.addEventListener) {
8 | element.addEventListener(type, handler, flag || false);
9 | } else if (element.attachEvent) {
10 | element.attachEvent("on" + type, handler);
11 | } else {
12 | element["on" + type] = handler;
13 | }
14 | },
15 | getEvent: function(event) {
16 | return event ? event : window.event;
17 | },
18 | getTarget: function(event) {
19 | //return event.tagert || event.srcElement;
20 | return event.target || event.srcElement;
21 | },
22 | preventDefault: function(event) {
23 | if (event.preventDefault) {
24 | event.preventDefault();
25 | } else {
26 | event.returnValue = false;
27 | }
28 | },
29 | stopPropagation: function(event) {
30 | if (event.stopPropagation) {
31 | event.stopPropagation();
32 | } else {
33 | event.cancelBubble = true;
34 | }
35 | },
36 | removeHandler: function(element, type, handler) {
37 | if (element.removeEventListener) {
38 | element.removeEventListener(type, handler, false);
39 | } else if (element.detachEvent) {
40 | element.detachEvent("on" + type, handler);
41 | } else {
42 | element["on" + type] = null;
43 | }
44 | }
45 | };
46 |
47 |
48 | /**
49 | * 启动页面管理
50 | * @param {Object} options 初始化参数
51 | * @param {String} options["selector"] 全局代理元素的选择器匹配,写法同 document.querySeletor 函数
52 | * @param {Number} options["cacheMaxTime"] 页面缓存时间
53 | * @param {Function|RegExp} options["validate"] url验证方法,
54 | * @return {void}
55 | */
56 |
57 | function start(options) {
58 |
59 | /**
60 | * 默认参数 {
61 | * selector : // 代理元素的选择器规则
62 | * cacheMaxTime: //缓存存活时间,默认5min
63 | * }
64 | */
65 | var defaultOptions = {
66 | cacheMaxTime: 5 * 60 * 1000,
67 | layer: document
68 | };
69 |
70 | appOptions = merge(defaultOptions, options);
71 | cacheMaxTime = appOptions.cacheMaxTime;
72 | layer = getLayer(appOptions.layer);
73 | bindEvent();
74 | }
75 |
76 | /**
77 | * 事件绑定
78 | * @return {void}
79 | */
80 |
81 | function bindEvent() {
82 | EventUtil.addHandler(layer, 'click', proxy, true);
83 | }
84 |
85 | function getLayer(ele) {
86 | if (typeof ele === "string") {
87 | return document.querySelector(ele);
88 | } else if (ele && ele.nodeType) {
89 | return ele;
90 | } else {
91 | return document.body
92 | }
93 | }
94 |
95 | /**
96 | * 简单merge两个对象
97 | * @param {object} _old
98 | * @param {object} _new
99 | * @returns {*}
100 | */
101 |
102 | function merge(_old, _new) {
103 | for (var i in _new) {
104 | if (_new.hasOwnProperty(i) && _new[i] !== null) {
105 | _old[i] = _new[i];
106 | }
107 | }
108 | return _old;
109 | }
110 |
111 | /**
112 | * 事件代理
113 | * @param {MouseEvent} 点击事件对象
114 | */
115 |
116 | function proxy(e) {
117 | var element = EventUtil.getTarget(e),
118 | parent = element,
119 | selector = appOptions.selector;
120 |
121 | var url, urlAttr, pagelets;
122 |
123 | while (parent !== layer) {
124 |
125 | urlAttr = parent.tagName.toLowerCase() === "a" ? "href" : "data-href";
126 | url = parent.getAttribute(urlAttr);
127 | pagelets = parent.getAttribute("data-pagelets");
128 |
129 | if (url && pagelets) {
130 | pagelets = pagelets.split(',');
131 |
132 | if (pagelets.length) {
133 |
134 | EventUtil.stopPropagation(e);
135 | EventUtil.preventDefault(e);
136 |
137 | BigPipe.fetch(pagelets, url);
138 | }
139 | break;
140 | } else {
141 | parent = parent.parentNode;
142 | }
143 | }
144 | }
145 |
146 | module.exports = {
147 | start: start
148 | };
149 |
--------------------------------------------------------------------------------
/dist/plugin/block.script.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * @param array $params
8 | * @param string $content
9 | * @param Smarty $smarty
10 | * @param bool $repeat
11 | * @see BigPipe::currentContext
12 | * @see PageletContext->addRequire
13 | * @see PageletContext->addRequireAsync
14 | * @see PageletContext->addHook
15 | */
16 | function smarty_block_script($params, $content, $smarty, &$repeat)
17 | {
18 | if (!$repeat && isset($content)) {
19 | $eventType = isset($params['on']) ? $params['on'] : "load";
20 | $strict = (isset($params['strict']) && $params['strict'] == false) ? false : true;
21 | $context = BigPipe::currentContext();
22 |
23 | if (isset($params["sync"])) {
24 | foreach ($params["sync"] as $resource) {
25 | BigPipeResource::registModule($resource);
26 | }
27 | $context->addRequire($eventType, $params["sync"]);
28 | }
29 |
30 | if (isset($params["async"])) {
31 | foreach ($params["async"] as $resource) {
32 | BigPipeResource::registModule($resource);
33 | }
34 | $context->addRequireAsync($eventType, $params["async"]);
35 | }
36 |
37 | $context->addHook($eventType, $content, $strict);
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/dist/plugin/compiler.body.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | */
13 | function smarty_compiler_body($params, $smarty){
14 | return
15 | '';
19 | }
20 |
21 | /**
22 | * smarty 编译插件 bodyclose
23 | *
24 | * 处理 {/body} 标签
25 | *
26 | * @param array $params
27 | * @param Smarty $smarty
28 | * @access public
29 | * @return string 编译后的php代码
30 | */
31 | function smarty_compiler_bodyclose($params, $smarty){
32 | return
33 | '';
37 | }
38 |
--------------------------------------------------------------------------------
/dist/plugin/compiler.head.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | */
13 | function smarty_compiler_head($params, $smarty){
14 | return
15 | '';
19 | }
20 |
21 | /**
22 | * smarty 编译插件 headclose
23 | *
24 | * 处理 {/head} 标签
25 | *
26 | * @param array $params
27 | * @param Smarty $smarty
28 | * @access public
29 | * @return string 编译后的php代码
30 | */
31 | function smarty_compiler_headclose($params, $smarty){
32 | return
33 | '';
37 | }
38 |
--------------------------------------------------------------------------------
/dist/plugin/compiler.html.php:
--------------------------------------------------------------------------------
1 | smarty)){'.
23 | 'do{'.
24 | 'if(' . BigPipe::compileOpenTag(BigPipe::TAG_HTML, $params) . '){'.
25 | '?>';
26 | }
27 |
28 | /**
29 | * smarty 编译插件 htmlclose
30 | *
31 | * 处理 {/html} 标签
32 | *
33 | * @param array $params
34 | * @param Smarty $smarty
35 | * @access public
36 | * @return string 编译后的php代码
37 | */
38 | function smarty_compiler_htmlclose($params, $smarty){
39 | return
40 | '';
46 | }
47 |
--------------------------------------------------------------------------------
/dist/plugin/compiler.pagelet.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | * @see BigPipe::compileOpenTag
13 | */
14 | function smarty_compiler_pagelet($params, $smarty){
15 | return
16 | '';
20 | }
21 |
22 | /**
23 | * smarty 编译插件 pageletclose
24 | *
25 | * 处理 {/pagelet} 标签
26 | *
27 | * @param array $params
28 | * @param Smarty $smarty
29 | * @access public
30 | * @return string 编译后的php代码
31 | * @see BigPipe::compileCloseTag
32 | */
33 | function smarty_compiler_pageletclose($params, $smarty){
34 | return
35 | '';
39 | }
40 |
--------------------------------------------------------------------------------
/dist/plugin/compiler.title.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | * @see BigPipe::compileOpenTag
13 | */
14 | function smarty_compiler_title($params, $smarty){
15 | return
16 | '';
20 | }
21 |
22 | /**
23 | * smarty 编译插件 titleclose
24 | *
25 | * 处理 {/title} 标签
26 | *
27 | * @param array $params
28 | * @param Smarty $smarty
29 | * @access public
30 | * @return string 编译后的php代码
31 | * @see BigPipe::compileCloseTag
32 | */
33 | function smarty_compiler_titleclose($params, $smarty){
34 | return
35 | '';
39 | }
40 |
--------------------------------------------------------------------------------
/dist/plugin/function.require.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * @param array $params
8 | * @param Smarty $smarty
9 | * @return void
10 | * @see BigPipeResource::registModule
11 | * @see BigPipe::currentContext
12 | * @see PageletContext->addRequire
13 | */
14 | function smarty_function_require($params, $smarty){
15 | $link = $params['name'];
16 | unset($params['name']);
17 |
18 | BigPipeResource::registModule($link);
19 |
20 | $context = BigPipe::currentContext();
21 | $resource = BigPipeResource::getResourceByPath($link);
22 |
23 | switch ($resource["type"]) {
24 | case 'css':
25 | $on = isset($params['on']) ? $params['on'] : 'beforedisplay';
26 | break;
27 | case 'js':
28 | $on = isset($params['on']) ? $params['on'] : 'load';
29 | break;
30 | }
31 |
32 | $context->addRequire($on, $link);
33 | }
34 |
--------------------------------------------------------------------------------
/dist/plugin/function.widget.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * @param array $params
8 | * @param Smarty $template
9 | * @return Smarty template funtion call
10 | * @see BigPipeResource::registModule
11 | * @see BigPipeResource::getTplByPath
12 | * @see BigPipe::currentContext
13 | * @see PageletContext->addRequire
14 | */
15 |
16 | function smarty_function_widget($params, $template)
17 | {
18 | $name = $params['name'];
19 | $method = $params['method'];
20 | unset($params['name']);
21 | unset($params['method']);
22 |
23 | if(isset($params['call'])){
24 | $call = $params['call'];
25 | unset($params['call']);
26 | }
27 |
28 | BigPipeResource::registModule($name);
29 |
30 | $tpl = BigPipeResource::getTplByPath($name);
31 | // Auto add widget css and less deps (no js) to currentContext.
32 | if(!empty($tpl["deps"])){
33 |
34 | $deps = $tpl["deps"];
35 | $context = BigPipe::currentContext();
36 |
37 | foreach($deps as $dep){
38 | BigPipeResource::registModule($dep);
39 | $depResource = BigPipeResource::getResourceByPath($dep);
40 |
41 | if($depResource["type"] === "css"){
42 | $on = 'beforedisplay';
43 | $context->addRequire($on, $dep);
44 | }
45 | }
46 | }
47 |
48 | $smarty=$template->smarty;
49 | $tplpath = $tpl["uri"];
50 |
51 | // First try to call the mothed passed via the $call param,
52 | // in order to made it compatible for fisp.
53 | if(isset($call)){
54 | $call = 'smarty_template_function_' . $call;
55 | if(!function_exists($call)) {
56 | try {
57 | $smarty->fetch($tplpath);
58 | } catch (Exception $e) {
59 | throw new Exception("\nNo tpl here, via call \n\"$name\" \n@ \"$tplpath\"");
60 | }
61 | }
62 | if(function_exists($call)) {
63 | return $call($template, $params);
64 | }
65 | }
66 |
67 | // If there is no method named $call,
68 | // try to call mothed passed via the $method param
69 | $fn='smarty_template_function_' . $method;
70 | if(!function_exists($fn)) {
71 | try {
72 | $smarty->fetch($tplpath);
73 | } catch (Exception $e) {
74 | throw new Exception("\nNo tpl here,via method \n\"$name\" \n@ \"$tplpath\"");
75 | }
76 | }
77 |
78 | if(function_exists($fn)) {
79 | return $fn($template, $params);
80 | }
81 |
82 | // If still no method named $method,
83 | // try to construct a method name with the tpl path, via md5().
84 | // This is in order to support call method through dynamic tpl path.
85 | else
86 | {
87 | $methodName = preg_replace('/^_[a-fA-F0-9]{32}_/','',$method);
88 |
89 | if($method !== $methodName){
90 | $method = '_' . md5($name) . '_' . $methodName;
91 |
92 | $fn='smarty_template_function_' . $method;
93 |
94 | if(function_exists($fn)){
95 | return $fn($template, $params);
96 | }
97 | }
98 | throw new Exception("\nUndefined function \"$method\" \nin \"$name\" \n@ \"$tplpath\"");
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/dist/plugin/lib/BigPipe.class.php:
--------------------------------------------------------------------------------
1 |
7 | * zhangwentao
8 | */
9 |
10 | if (!defined('BIGPIPE_BASE_DIR')) {
11 | define('BIGPIPE_BASE_DIR', dirname(__FILE__));
12 | }
13 |
14 | define("BIGPIPE_DEBUG", 0);
15 |
16 | class BigPipe
17 | {
18 | // 标签类型常量
19 | /**
20 | * NONE 标签类型,用于标识默认根节点
21 | * @see init
22 | */
23 | const TAG_NONE = 0;
24 | /**
25 | * HTML 标签类型,供 Smarty {html} 插件使用
26 | */
27 | const TAG_HTML = 1;
28 | /**
29 | * HEAD 标签类型,供 Smarty {head} 插件使用
30 | */
31 | const TAG_HEAD = 2;
32 | /**
33 | * TITLE 标签类型,供 Smarty {title} 插件使用
34 | */
35 | const TAG_TITLE = 3;
36 | /**
37 | * BODY 标签类型,供 Smarty {body} 插件使用
38 | */
39 | const TAG_BODY = 4;
40 | /**
41 | * PAGELET 标签类型,供 Smarty {pagelet} 插件使用
42 | */
43 | const TAG_PAGELET = 5;
44 |
45 | // 状态常量
46 | /**
47 | * 未初始化的状态
48 | */
49 | const STAT_UNINIT = 0;
50 | /**
51 | * 初始化后第一次执行
52 | */
53 | const STAT_FIRST = 1;
54 | /**
55 | * 循环执行
56 | */
57 | const STAT_LOOP = 2;
58 | /**
59 | * 准备结束
60 | */
61 | const STAT_END = 3;
62 |
63 | /**
64 | * 页面输出控制器
65 | */
66 | private static $controller = null;
67 | /**
68 | * 当前上下文
69 | */
70 | private static $context = null;
71 | /**
72 | * bodyContext
73 | */
74 | private static $bodyContext = null;
75 | /**
76 | * 当前状态
77 | */
78 | private static $state = self::STAT_UNINIT;
79 |
80 | /**
81 | * 框架使用参数的前缀
82 | */
83 | const ATTR_PREFIX = 'her-';
84 | /**
85 | * 渲染模式属性名
86 | */
87 | const RENDER_MODE_KEY = 'her-renderMode';
88 | /**
89 | * 渲染模式属性名
90 | */
91 | const CONFIG_KEY = 'her-config';
92 | /**
93 | * 渲染模式 server, 直接输出html到页面, beforedisplay依赖加入root context
94 | */
95 | const RENDER_MODE_SERVER = 'server';
96 | /**
97 | * 渲染模式 lazy, 只输出占位标签
98 | */
99 | const RENDER_MODE_LAZY = 'lazy';
100 | /**
101 | * 渲染模式 none, 不输出 直接跳过
102 | */
103 | const RENDER_MODE_NONE = 'none';
104 | /**
105 | * 渲染模式 default, 输出html到注释 用js渲染
106 | */
107 | const RENDER_MODE_DEFAULT = 'default';
108 | /**
109 | * 渲染模式 default, 输出html到注释 用js渲染
110 | */
111 | const RENDER_MODE_QUICKLING = 'quickling';
112 | /**
113 | * 前端不支持 Js 时的 fallback 请求参数
114 | */
115 | const NO_JS = '__noscript__-';
116 | /**
117 | * Quickling 请求时的特征参数
118 | */
119 | protected static $quicklingKey = '__quickling__';
120 | /**
121 | * Quickling 请求时的会话参数,用于保持前后端状态
122 | */
123 | protected static $sessionKey = '__session__';
124 | /**
125 | * 前端不支持 Js 时的 fallback 请求参数
126 | */
127 | // protected static $nojsKey = '__noscript__';
128 | /**
129 | * 前端 js 框架地址
130 | */
131 | static $jsLib = null;
132 | static $herConf = null;
133 | /**
134 | * 前端 js 入口对象名
135 | */
136 | protected static $globalVar = 'BigPipe';
137 | /**
138 | * Quickling 请求的区块名分隔符
139 | */
140 | protected static $separator = ',';
141 | /**
142 | * 是否 debug 状态
143 | */
144 | protected static $debug = false;
145 | /**
146 | * 保存的断言配置
147 | */
148 | private static $savedAssertOptions = null;
149 |
150 | /**
151 | * 得到当前上下文
152 | *
153 | * @static
154 | * @access public
155 | * @return PageletContext 当前的上下文
156 | * @see PageletContext
157 | * @example
158 | * $context = BigPipe::currentContext();
159 | * $context->addDepend("her:css/layout.css", "beforedisplay");
160 | * $context->addDepend("her:js/jquery.js", "load");
161 | */
162 | public static function currentContext()
163 | {
164 | return self::$context;
165 | }
166 |
167 | /**
168 | * 得到 bodyContext
169 | *
170 | * @static
171 | * @access public
172 | * @return PageletContext 当前的上下文
173 | * @see PageletContext
174 | * @example
175 | * $context = BigPipe::bodyContext();
176 | * $context->addRequire("beforedisplay", "common:css/layout.css");
177 | * $context->addRequire("load", array("common:js/jquery.js"));
178 | */
179 | public static function bodyContext()
180 | {
181 | return self::$bodyContext;
182 | }
183 |
184 | /**
185 | * 加载类
186 | *
187 | * @param mixed $className 类名
188 | * @static
189 | * @access public
190 | * @return bool 是否加载成功
191 | */
192 | public static function loadClass($className)
193 | {
194 | if (!class_exists($className, false)) {
195 | $fileName = BIGPIPE_BASE_DIR . DIRECTORY_SEPARATOR . $className . ".class.php";
196 | if (file_exists($fileName)) {
197 | require($fileName);
198 | }
199 | }
200 | return class_exists($className, false);
201 | }
202 |
203 | /**
204 | * 设置框架的 debug 模式
205 | *
206 | * @param bool $isDebug 是否开启debug模式
207 | * @static
208 | * @access public
209 | * @return void
210 | */
211 | public static function setDebug($isDebug)
212 | {
213 | self::$debug = !!$isDebug;
214 | }
215 |
216 | /**
217 | * 根据请求得到控制器
218 | * BigPipe 框架有三种控制器
219 | * 1、页面刷新时的控制器:将会输出页面框架并采用Js渲染内容
220 | * 2、页面局部刷新的控制器:将会输出局部刷新的内容,并且使用Js渲染
221 | * 3、浏览器不支持Js或者判定为网络爬虫时,使用正常的流式输出
222 | *
223 | * @static
224 | * @access private
225 | * @return PageController 适合当前环境的控制器
226 | * @see init
227 | */
228 | private static function getController()
229 | {
230 | $get = self::getSuperGlobal(self::SUPER_TYPE_GET);
231 | $cookie = self::getSuperGlobal(self::SUPER_TYPE_COOKIE);
232 |
233 | /**
234 | * 先判断是否为 Noscript 请求
235 | */
236 | if (isset($get[self::NO_JS]) || isset($cookie[self::NO_JS])) {
237 | setcookie(self::NO_JS, 1);
238 | self::loadClass("NoScriptController");
239 | return new NoScriptController();
240 | }
241 |
242 |
243 | /**
244 | * 判断是否为 Quickling 请求
245 | */
246 | if ( isset($get[self::$quicklingKey]) ){
247 | $ids = $get[self::$quicklingKey];
248 | $sessions = array();
249 |
250 | if (empty($ids)) {
251 | $ids = array();
252 | } else {
253 | $ids = explode(self::$separator, $ids);
254 | foreach($ids as $id){
255 | $id = explode(".", $id);
256 | if( isset($id[0]) && isset($id[1]) ) {
257 | $sessions[$id[0]] = intval($id[1]);
258 | }
259 | }
260 | $ids = array_keys($sessions);
261 | }
262 | self::loadClass("SmartController");
263 | return new SmartController($sessions, $ids);
264 | }
265 | /**
266 | * 默认走 Fist 请求
267 | */
268 | self::loadClass("FirstController");
269 | return new FirstController();
270 | }
271 |
272 | /**
273 | * 模板调用函数。用于初始化控制器,
274 | * 在 Smarty {html} 标签处调用
275 | *
276 | * @static
277 | * @final
278 | * @access public
279 | * @return bool 是否初始化成功,固定为 true
280 | * @see smarty_compiler_html
281 | */
282 | public static final function init($smarty)
283 | {
284 | self::saveAssertOptions();
285 | self::setAssertOptions();
286 | self::loadClass("PageletContext");
287 |
288 | assert('self::$state === self::STAT_UNINIT');
289 |
290 | self::$controller = self::getController();
291 | self::$context = new PageletContext(self::TAG_NONE);
292 | self::$state = self::STAT_FIRST;
293 |
294 | // BigPipeResource运行依赖her-map.json, 需要在这里设置config目录的路径
295 | if (!defined('BIGPIPE_CONF_DIR')) {
296 | $confDir = $smarty->getConfigDir();
297 | define('BIGPIPE_CONF_DIR', $confDir[0]);
298 | }
299 |
300 | return true;
301 | }
302 |
303 | /**
304 | * 模板调用函数。在标签打开时调用,用于控制标签内部是否执行。
305 | *
306 | * @param int $type 标签类型
307 | * @param string $uniqid 节点ID
308 | * @param array $params 标签参数
309 | * @static
310 | * @final
311 | * @access public
312 | * @return bool 是否需要执行标签内部内容
313 | * @see compileOpenTag
314 | * @see PageController::openTag
315 | */
316 | public static final function open($type, $uniqid, $params = null) // {{{
317 | {
318 |
319 | assert('self::$state === self::STAT_FIRST || self::$state === self::STAT_LOOP');
320 |
321 | if (self::has($uniqid)) {
322 | assert('isset(self::$context->children[$uniqid])');
323 | $context = self::$context->children[$uniqid];
324 | assert('$context->type === $type');
325 | } else {
326 | if (!isset($params)) {
327 | $params = array();
328 | }
329 | if($type == self::TAG_HTML && isset($params['her'])){
330 |
331 | BigPipeResource::registModule($params['her']);
332 | self::$jsLib = $params['her'];
333 | unset($params['her']);
334 |
335 | if(isset($params[self::CONFIG_KEY])){
336 | self::$herConf = $params[self::CONFIG_KEY];
337 | unset($params[self::CONFIG_KEY]);
338 | }
339 | }
340 |
341 | $context = new PageletContext($type, $params);
342 |
343 | if($type === self::TAG_BODY) {
344 | self::$bodyContext = $context;
345 | }
346 |
347 | if($type === self::TAG_PAGELET) {
348 | // get context renderMode
349 | $renderModeKey = self::RENDER_MODE_KEY;
350 | if(isset($params[$renderModeKey])){
351 | $context->renderMode = $params[$renderModeKey];
352 | unset($params[$renderModeKey]);
353 | }
354 |
355 | if(!in_array($context->renderMode, array(
356 | self::RENDER_MODE_SERVER,
357 | self::RENDER_MODE_LAZY,
358 | self::RENDER_MODE_NONE
359 | ))) {
360 | $context->renderMode = self::RENDER_MODE_DEFAULT;
361 | }
362 |
363 | // if parent context renderMode is RENDER_MODE_NONE, pass to child
364 | if( self::$context->renderMode === self::RENDER_MODE_NONE ){
365 | $context->renderMode = self::RENDER_MODE_NONE;
366 | }
367 |
368 | // if parent context renderMode is RENDER_MODE_LAZY
369 | // and renderMode is not RENDER_MODE_LAZY or RENDER_MODE_NONE
370 | // then set renderMode to RENDER_MODE_LAZY
371 | if( self::$context->renderMode === self::RENDER_MODE_LAZY ){
372 | if( !in_array($context->renderMode, array(
373 | self::RENDER_MODE_LAZY,
374 | self::RENDER_MODE_NONE
375 | )
376 | )) {
377 | $context->renderMode = self::RENDER_MODE_LAZY;
378 | }
379 | }
380 |
381 | if( $context->renderMode === self::RENDER_MODE_SERVER
382 | && self::$context->renderMode !== self::RENDER_MODE_SERVER
383 | && self::$context !== self::$bodyContext
384 | ) {
385 | $context->renderMode = self::RENDER_MODE_DEFAULT;
386 | }
387 | }
388 | $context->parent = self::$context;
389 | // 如果 $context 是 RENDER_MODE_NONE, 则不保存到 parent 的 children 里面
390 | if($context->renderMode !== self::RENDER_MODE_NONE) {
391 | self::$context->children[$uniqid] = $context;
392 | }
393 | }
394 |
395 | self::$context = $context;
396 |
397 | return $context->opened = self::$controller->openTag($context);
398 | } // }}}
399 |
400 | /**
401 | * 模板调用函数。在标签关闭时调用,将会返回标签父级节点的执行状态。
402 | *
403 | * @param int $type 标签类型
404 | * @static
405 | * @final
406 | * @access public
407 | * @return bool 父级节点是否需要执行
408 | * @see compileCloseTag
409 | * @see PageController::closeTag
410 | */
411 | public static final function close($type, $params = null) // {{{
412 | {
413 | assert('self::$state === self::STAT_FIRST || self::$state === self::STAT_LOOP');
414 | $context = self::$context;
415 | assert('$context->type === $type');
416 |
417 | self::$controller->closeTag($context);
418 | $context->opened = false;
419 |
420 | $context = $context->parent;
421 | self::$context = $context;
422 | return $context->opened;
423 | } // }}}
424 |
425 | /**
426 | * 模板调用函数。用于判断当前节点是否已经存在,用于避免参数的多次执行
427 | *
428 | * @param string $uniqid 当前节点的ID
429 | * @static
430 | * @final
431 | * @access public
432 | * @return bool 当前标签是否存在
433 | * @see compileOpenTag
434 | */
435 | public static final function has($uniqid)
436 | {
437 | return isset(self::$context->children[$uniqid]);
438 | }
439 |
440 | /**
441 | * 模板调用函数。用于控制页面是否需要再次执行。
442 | * 在 Smarty {/html} 标签处调用
443 | *
444 | * @static
445 | * @final
446 | * @access public
447 | * @return bool 页面是否需要再次执行。
448 | * @see smarty_compiler_htmlclose
449 | * @see PageController::hasMore
450 | */
451 | public static final function more()
452 | {
453 | assert('self::$state === self::STAT_FIRST || self::$state === self::STAT_LOOP');
454 |
455 | if (self::$controller->hasMore()) {
456 | self::$state = self::STAT_LOOP;
457 | return true;
458 | } else {
459 | self::$state = self::STAT_END;
460 | return false;
461 | }
462 | }
463 |
464 | /**
465 | * Smarty 编译辅助函数,用于编译一个打开标签
466 | *
467 | * @param int $type 标签类型
468 | * @param array $param 标签的参数
469 | * @static
470 | * @final
471 | * @access public
472 | * @return string 编译后的PHP代码
473 | * @see open
474 | * @see has
475 | */
476 | public static final function compileOpenTag($type, $params)
477 | {
478 | $uniqid = self::compileUniqid();
479 | $phpCode = "BigPipe::open(" . $type . "," . $uniqid;
480 | /**
481 | * 如果没有参数,则不传递第三个参数
482 | * @see open
483 | */
484 | if (!empty($params)) {
485 | /**
486 | * 如果已经执行过该节点 BigPipe::has 返回true ,$params 数组不会使用到
487 | * @see has
488 | */
489 | $phpCode .= ',BigPipe::has(' . $uniqid . ')?null:' . self::compileParamsArray($params);
490 | }
491 | $phpCode .= ")";
492 | return $phpCode;
493 | }
494 |
495 | /**
496 | * Smarty 编译辅助函数,用于编译一个闭合标签
497 | *
498 | * @param int $type 标签类型
499 | * @param array $param 标签的参数
500 | * @static
501 | * @final
502 | * @access public
503 | * @return string 编译后的PHP代码
504 | */
505 | public static final function compileCloseTag($type, $params)
506 | {
507 | return 'BigPipe::close(' . $type . ')';
508 | }
509 |
510 | /**
511 | * Smarty编译辅助函数,用于将参数数组编译为PHP代码
512 | *
513 | * @param array $params
514 | * @static
515 | * @final
516 | * @access public
517 | * @return string 编译后的 PHP 代码
518 | *
519 | */
520 | public static final function compileParamsArray($params)
521 | {
522 | $items = array();
523 | $code = 'array(';
524 | foreach ($params as $key => $value) {
525 | $items[] = "\"$key\"=>$value";
526 | //$code.="\"$key\"=>$value,";
527 | }
528 | $code .= join($items, ",");
529 | $code .= ")";
530 |
531 | return $code;
532 | }
533 |
534 | /**
535 | * Smarty编译辅助函数,用于生成一个唯一ID
536 | *
537 | * @static
538 | * @final
539 | * @access public
540 | * @return string 用于PHP的唯一字符串字面量
541 | */
542 | public static final function compileUniqid()
543 | {
544 | return var_export(uniqid(), true);
545 | }
546 |
547 | /**
548 | * 超全局变量 GET 类型
549 | */
550 | const SUPER_TYPE_GET = "get";
551 | /**
552 | * 超全局变量 COOKIE 类型
553 | */
554 | const SUPER_TYPE_COOKIE = "cookie";
555 | /**
556 | * 单元测试辅助变量,用于设置超全局变量
557 | */
558 | public static $superGlobals = array();
559 | /**
560 | * 单元测试辅助函数,得到超全局变量
561 | *
562 | * @param int $type 变量类型
563 | * @static
564 | * @access protected
565 | * @return mixed
566 | */
567 | private static function getSuperGlobal($type)
568 | {
569 |
570 | if (isset(self::$superGlobals[$type])) {
571 | return self::$superGlobals[$type];
572 | } else {
573 | switch ($type) {
574 | case self::SUPER_TYPE_GET:
575 | return $_GET;
576 | case self::SUPER_TYPE_COOKIE:
577 | return $_COOKIE;
578 | }
579 | }
580 | return null;
581 | }
582 |
583 | /**
584 | * 保存断言配置
585 | *
586 | * @static
587 | * @access private
588 | * @return void
589 | */
590 | private static function saveAssertOptions()
591 | {
592 | self::$savedAssertOptions = array();
593 | foreach (array(
594 | ASSERT_ACTIVE,
595 | ASSERT_WARNING,
596 | ASSERT_BAIL,
597 | ASSERT_QUIET_EVAL,
598 | ASSERT_CALLBACK
599 | ) as $key) {
600 | self::$savedAssertOptions[] = array(
601 | 'key' => $key,
602 | 'val' => assert_options($key)
603 | );
604 | }
605 | }
606 |
607 | /**
608 | * 断言回调
609 | *
610 | * @param mixed $file
611 | * @param mixed $line
612 | * @param mixed $code
613 | * @static
614 | * @access private
615 | * @return void
616 | */
617 | private static function assertCallback($file, $line, $code)
618 | {
619 | echo "
Assertion Failed:
File '$file'
Line '$line'
Code '$code'
";
620 | }
621 |
622 | /**
623 | * 设置断言配置
624 | *
625 | * @static
626 | * @access private
627 | * @return void
628 | */
629 | private static function setAssertOptions()
630 | {
631 | if (defined("BIGPIPE_DEBUG") && BIGPIPE_DEBUG) {
632 | assert_options(ASSERT_ACTIVE, 1);
633 | assert_options(ASSERT_WARNING, 1);
634 | assert_options(ASSERT_BAIL, 1);
635 | assert_options(ASSERT_QUIET_EVAL, 0);
636 | assert_options(ASSERT_CALLBACK, array(
637 | "self",
638 | "assertCallback"
639 | ));
640 | } else {
641 | assert_options(ASSERT_ACTIVE, 0);
642 | }
643 | }
644 |
645 | public static function array_merge($arr1, $arr2){
646 | foreach ($arr2 as $key => $value) {
647 | $arr1[$key] = $value;
648 | }
649 | return $arr1;
650 | }
651 | }
652 |
--------------------------------------------------------------------------------
/dist/plugin/lib/BigPipeResource.class.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 | class BigPipeResource
8 | {
9 | /**
10 | * 存储资源map
11 | */
12 | private static $map = array(
13 | "res"=>array(),
14 | "her"=>array()
15 | );
16 | /**
17 | * 存储已经注册的module
18 | */
19 | private static $registedMoudle = array();
20 | /**
21 | * 存储已经处理过返回给前端的Resources
22 | */
23 | public static $knownResources = array();
24 | /**
25 | * 初始化资源map
26 | *
27 | * @param array $map 类名
28 | * @static
29 | * @access public
30 | * @return void
31 | */
32 | public static function setupMap($map)
33 | {
34 | self::$map["res"] = self::$map["res"] + $map["res"];
35 | self::$map["her"] = self::$map["her"] + $map["her"];
36 | }
37 |
38 | /**
39 | * 注册一个module 将module的map存在$registedMoudle
40 | *
41 | * @param string $name 标准化资源路径
42 | * @static
43 | * @access public
44 | * @return void
45 | */
46 | public static function registModule($name)
47 | {
48 | $intPos = strpos($name, ':');
49 |
50 | if($intPos === false){
51 | return;
52 | } else {
53 | $femodule = substr($name, 0, $intPos);
54 | }
55 |
56 | $configPath = BIGPIPE_CONF_DIR;
57 |
58 | if(!in_array($femodule, self::$registedMoudle)){
59 |
60 | if(defined("FE_HTTPS") && FE_HTTPS){
61 | $femodulefix = $femodule . '-https';
62 | }else{
63 | $femodulefix = $femodule;
64 | }
65 | $mapPath = $configPath . '/' . $femodulefix . '-map.json';
66 | $map = json_decode(file_get_contents($mapPath), true);
67 | BigPipeResource::setupMap($map);
68 | self::$registedMoudle[] = $femodule;
69 | }
70 | }
71 |
72 | /**
73 | * 通过标准化路径获取tpl资源
74 | *
75 | * @param string $path 标准化资源路径
76 | * @static
77 | * @access public
78 | * @return resource
79 | */
80 | public static function getTplByPath($path)
81 | {
82 | return self::$map["res"][$path];
83 | }
84 |
85 | /**
86 | * 通过标准化路径获取资源
87 | *
88 | * @param string $path 标准化资源路径
89 | * @static
90 | * @access public
91 | * @return resource
92 | */
93 | public static function getResourceByPath($path, $type = null){
94 |
95 | $map = self::$map["her"];
96 | $resource = self::getResource($map, $path, $type);
97 | if($resource)
98 | return $resource;
99 | return false;
100 | }
101 |
102 | public static function getResource($map, $path, $type){
103 | foreach ($map as $id => $resource) {
104 | if( (!isset($type) || $type == $resource['type'])
105 | && in_array($path, $resource['defines'])){
106 | $resource['id'] = $id;
107 | if(!isset($resource['requires'])) $resource['requires'] = array();
108 | if(!isset($resource['requireAsyncs'])) $resource['requireAsyncs'] = array();
109 | return $resource;
110 | }
111 | }
112 | return false;
113 | }
114 |
115 | /**
116 | * 通过路径数组获取资源数组
117 | *
118 | * @param string $pathArr 标准化资源路径数组
119 | * @static
120 | * @access public
121 | * @return resources 资源数组
122 | */
123 | public static function pathToResource($pathArr, $type = null){
124 | $resources = array();
125 |
126 | foreach ($pathArr as $path) {
127 | $resource = self::getResourceByPath($path, $type);
128 | if($resource){
129 | $resources[$resource['id']] = $resource;
130 | }
131 | }
132 | return $resources;
133 | }
134 |
135 | /**
136 | * 通过资源数组获取依赖资源数组
137 | *
138 | * @param array $resources 资源数组
139 | * @param bool $asyncs 是否需要获取async依赖
140 | * @static
141 | * @access public
142 | * @return resources 依赖资源数组
143 | */
144 | public static function getDependResource($resources, $asyncs = true){
145 | $dependResources = array();
146 |
147 | $depends = $resources;
148 |
149 | while(!empty($depends)){
150 |
151 | $last = end($depends);
152 | array_pop($depends);
153 |
154 | $id = $last['id'];
155 |
156 | if(isset($dependResources[$id])){
157 | continue;
158 | }
159 | $dependResources[$id] = $last;
160 |
161 | $lastDepends = self::getDepend($last, $asyncs);
162 | if(!empty($lastDepends)){
163 | $depends = BigPipe::array_merge($depends, $lastDepends);
164 | }
165 | }
166 |
167 | return array_reverse($dependResources, true);
168 | }
169 |
170 | /**
171 | * 获取一个资源的依赖
172 | *
173 | * @param mixed $resource 资源数组
174 | * @param bool $asyncs 是否需要获取async依赖
175 | * @static
176 | * @access public
177 | * @return resources 依赖资源数组
178 | */
179 | private static function getDepend($resource, $asyncs){
180 | $requires = $resource['requires'];
181 |
182 | if($asyncs){
183 | $requires = array_merge($requires, $resource['requireAsyncs']);
184 | }
185 |
186 | if(count($requires) > 0 ){
187 | return $dependResources = self::pathToResource($requires);
188 | }
189 | return array();
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/dist/plugin/lib/NoscriptController.class.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 |
8 | BigPipe::loadClass("PageController");
9 | BigPipe::loadClass("BigPipeResource");
10 |
11 | class NoScriptController extends PageController
12 | {
13 | const STAT_COLLECT = 1;
14 | const STAT_OUTPUT = 2;
15 |
16 | private $state = self::STAT_COLLECT;
17 | private $bodyHTML = null;
18 | private $bodyStyleLinks = array();
19 |
20 | public function __construct()
21 | {
22 | $this->actionChain = array(
23 | 'default' => false,
24 | // 收集
25 | 'collect_html_open' => array(
26 | 'outputOpenTag',
27 | true
28 | ),
29 | 'collect_body_open' => array(
30 | 'startCollect',
31 | true
32 | ),
33 | 'collect_block_open' => array(
34 | 'outputOpenTag',
35 | true
36 | ),
37 | 'collect_block_close' => array(
38 | 'outputCloseTag'
39 | ),
40 | 'collect_body_close' => array(
41 | 'collectBody'
42 | ),
43 | 'collect_more' => array(
44 | 'changeState',
45 | true
46 | ),
47 | // 输出
48 | 'output_head_open' => array(
49 | 'outputOpenTag',
50 | 'outputScriptReload',
51 | true
52 | ),
53 | 'output_title_open' => array(
54 | 'outputOpenTag',
55 | true
56 | ),
57 | 'output_title_close' => array(
58 | 'outputCloseTag'
59 | ),
60 | 'output_head_close' => array(
61 | 'outputStyle',
62 | 'outputCloseTag'
63 | ),
64 | 'output_body_open' => array(
65 | 'outputOpenTag',
66 | 'outputBody',
67 | false
68 | ),
69 | 'output_body_close' => array(
70 | 'outputCloseTag'
71 | ),
72 | 'output_html_close' => array(
73 | 'outputCloseTag'
74 | ),
75 | 'output_more' => false,
76 | );
77 | }
78 |
79 | protected function collectBody($context){
80 | $this->bodyHTML = ob_get_clean();
81 | }
82 |
83 | protected function outputStyle($context){
84 | $event = $context->parent->getEvent('beforedisplay');
85 |
86 | if($event != false){
87 | $styleLinks = $event->requires;
88 |
89 | $styleResources = BigPipeResource::pathToResource($styleLinks, 'css');
90 | $styleResources = BigPipeResource::getDependResource($styleResources);
91 | }
92 | foreach($styleResources as $resource){
93 | echo "";
94 | }
95 | }
96 |
97 | protected function outputScriptReload($context){
98 | echo '';
104 | }
105 |
106 | protected function outputBody($context){
107 | echo $this->bodyHTML;
108 | }
109 |
110 | protected function changeState(){
111 | switch($this->state){
112 | case self::STAT_COLLECT:
113 | $this->state=self::STAT_OUTPUT;
114 | break;
115 | case self::STAT_OUTPUT:
116 | break;
117 | default:
118 | break;
119 | }
120 | }
121 |
122 | /**
123 | * getActionKey 得到需要执行的动作索引
124 | *
125 | * @param mixed $context
126 | * @param mixed $action
127 | * @access protected
128 | * @return void
129 | */
130 | protected function getActionKey($type, $action)
131 | {
132 | $keys=array();
133 | switch($this->state) {
134 | case self::STAT_COLLECT:
135 | $keys[]="collect";
136 | break;
137 | case self::STAT_OUTPUT:
138 | $keys[]="output";
139 | break;
140 | default:
141 | }
142 |
143 | switch($type) {
144 | case BigPipe::TAG_HTML:
145 | $keys[] = "html";
146 | break;
147 | case BigPipe::TAG_HEAD:
148 | $keys[] = "head";
149 | break;
150 | case BigPipe::TAG_TITLE:
151 | $keys[] = "title";
152 | break;
153 | case BigPipe::TAG_BODY:
154 | $keys[] = "body";
155 | break;
156 | case BigPipe::TAG_PAGELET:
157 | $keys[] = "block";
158 | break;
159 | default:
160 | }
161 |
162 | switch($action) {
163 | case PageController::ACTION_OPEN:
164 | $keys[]="open";
165 | break;
166 | case PageController::ACTION_CLOSE:
167 | $keys[]="close";
168 | break;
169 | case PageController::ACTION_MORE:
170 | $keys[]="more";
171 | break;
172 | default:
173 | }
174 |
175 | $key=join("_", $keys);
176 | if(!isset($this->actionChain[$key])) {
177 | $key='default';
178 | }
179 | return $key;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/dist/plugin/lib/PageController.class.php:
--------------------------------------------------------------------------------
1 |
7 | * zhangwentao
8 | */
9 |
10 | abstract class PageController
11 | {
12 | /**
13 | * 标签打开的动作类型
14 | * @see getActionKey
15 | */
16 | const ACTION_OPEN = 1;
17 | /**
18 | * 标签关闭的动作类型
19 | * @see getActionKey
20 | */
21 | const ACTION_CLOSE = 2;
22 | /**
23 | * 判断页面是否需要再次执行的动作类型
24 | * @see getActionKey
25 | */
26 | const ACTION_MORE = 3;
27 |
28 | /**
29 | * 执行的动作链,子类应该设置该属性,以便 doAction 调用
30 | *
31 | * @var array
32 | * @access protected
33 | */
34 | protected $actionChain = null;
35 |
36 | /**
37 | * 得到用于执行的动作链 key, 子类应该实现,以便从 actionChain 中查找用于执行的动作链
38 | *
39 | * @param PageletContext $context 当前 Pagelet 上下文
40 | * @param int $action 当前的动作类型
41 | * @abstract
42 | * @access protected
43 | * @return string
44 | */
45 | abstract protected function getActionKey($context, $action);
46 |
47 | /**
48 | * 执行某个函数链
49 | *
50 | * @param string $key 函数链名
51 | * @param PageletContext $context 当前 Pagelet 上下文
52 | * @access private
53 | * @return bool 函数链的执行结果
54 | */
55 | private function doAction($key, $context)
56 | {
57 | $ret = null;
58 | $actions = null;
59 |
60 | if (isset($this->actionChain[$key])) {
61 | $actions = $this->actionChain[$key];
62 | if (is_string($actions)) {
63 | // $actions = array(
64 | // $actions
65 | // );
66 | $actions = call_user_func(array(
67 | $this,
68 | $actions
69 | ), $context);
70 | }
71 | if (is_array($actions)) {
72 | foreach ($actions as $method) {
73 | if (is_string($method)) {
74 | $ret = call_user_func(array(
75 | $this,
76 | $method
77 | ), $context);
78 | } else {
79 | $ret = $method;
80 | }
81 | // 如果返回 false 直接退出返回
82 | if($ret === false){
83 | break;
84 | }
85 | }
86 | } else {
87 | $ret = $actions;
88 | }
89 | }
90 | return $ret;
91 | }
92 |
93 | /**
94 | * 标签打开时调用,控制标签的执行
95 | *
96 | * @param PageletContext $context 当前 Pagelet 上下文
97 | * @final
98 | * @access public
99 | * @return bool 当前 Pagelet 是否需要执行
100 | */
101 | public final function openTag($context)
102 | {
103 | return $this->doAction($this->getActionKey($context->type, self::ACTION_OPEN), $context);
104 | }
105 |
106 | /**
107 | * 标签关闭时调用
108 | *
109 | * @param PageletContext $context
110 | * @final
111 | * @access public
112 | * @return void
113 | */
114 | public final function closeTag($context)
115 | {
116 | $this->doAction($this->getActionKey($context->type, self::ACTION_CLOSE), $context);
117 | }
118 |
119 | /**
120 | * 页面完成一次执行后调用,用于控制页面是否重复执行
121 | *
122 | * @final
123 | * @access public
124 | * @return bool 页面是否重复执行
125 | */
126 | public final function hasMore()
127 | {
128 | return $this->doAction($this->getActionKey(BigPipe::TAG_NONE, self::ACTION_MORE), null);
129 | }
130 |
131 | /**
132 | * 输出打开标签
133 | *
134 | * @param PageletContext $context 当前 Pagelet 上下文
135 | * @access protected
136 | * @return void
137 | */
138 | protected function outputOpenTag($context)
139 | {
140 | echo $context->getOpenHTML();
141 | }
142 |
143 | /**
144 | * 输出闭合标签
145 | *
146 | * @param PageletContext $context 当前 Pagelet 上下文
147 | * @access protected
148 | * @return void
149 | */
150 | protected function outputCloseTag($context)
151 | {
152 | echo $context->getCloseHTML();
153 | }
154 |
155 | /**
156 | * 开始收集内容
157 | *
158 | * @param PageletContext $context 当前 Pagelet 上下文
159 | * @access protected
160 | * @return void
161 | */
162 | protected function startCollect($context)
163 | {
164 | ob_start();
165 | }
166 |
167 | /**
168 | * 将收集到的内容作为 pagelet 的 HTML 内容
169 | *
170 | * @param PageletContext $context 当前 Pagelet 上下文
171 | * @access protected
172 | * @return void
173 | */
174 | protected function collectHTML($context)
175 | {
176 | $context->html = ob_get_clean();
177 | }
178 |
179 | /**
180 | * collectScript 收集脚本
181 | *
182 | * @param PageletContext $context 当前 Pagelet 上下文
183 | * @access protected
184 | * @return void
185 | */
186 | protected function collectScript($context)
187 | {
188 | $context->parent->addScript(
189 | ob_get_clean(),
190 | $context->getBigPipeConfig("on", "load"),
191 | $context->getBigPipeConfig("deps"),
192 | $context->getBigPipeConfig("asyncs")
193 | );
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/dist/plugin/lib/PageletContext.class.php:
--------------------------------------------------------------------------------
1 |
8 | * zhangwentao
9 | */
10 |
11 | BigPipe::loadClass("PageletEvent");
12 |
13 | class PageletContext
14 | {
15 |
16 | /**
17 | * 空标志
18 | */
19 | const FLG_NONE = 0;
20 |
21 | /**
22 | * 自动添加事件的标志
23 | * @see getEvent
24 | */
25 | const FLG_AUTO_ADD_EVENT = 1;
26 |
27 |
28 | /**
29 | * 如果指定参数没有值则添加参数
30 | * @see getParam
31 | */
32 | const FLG_APPEND_PARAM = 2;
33 |
34 | /**
35 | * 标签类型
36 | *
37 | * @var int
38 | * @access public
39 | */
40 | public $type = null;
41 |
42 | /**
43 | * 标签参数
44 | *
45 | * @var array
46 | * @access public
47 | */
48 | public $params = null;
49 |
50 | /**
51 | * 标签 HTML 内容
52 | *
53 | * @var string
54 | * @access public
55 | */
56 | public $html = null;
57 |
58 | /**
59 | * 父级Pagelet
60 | *
61 | * @var PageletContext
62 | * @access public
63 | */
64 | public $parent = null;
65 |
66 | /**
67 | * 子级 Pagelet 列表
68 | *
69 | * @var array
70 | * @access public
71 | */
72 | public $children = null;
73 |
74 | /**
75 | * 当前标签是否执行( open 时返回值是否为 true )
76 | *
77 | * @var bool
78 | * @access public
79 | */
80 | public $opened = false;
81 |
82 | /**
83 | * Pagelet 事件
84 | *
85 | * @var array
86 | * @access public
87 | */
88 | public $events = null;
89 |
90 | //FIXME delete
91 | private static $priority_list = array();
92 | private static $max_priority = 0;
93 | private $vars = null;
94 | public $priority = null; // 输出优先级
95 | public $priorityArray = null; //输出数组
96 | public $scripts = null; // script内容
97 | public $scriptLinks = null; // js 链接
98 | public $styles = null; // style内容
99 | public $styleLinks = null; // css 链接
100 | public $shouldShow = false;
101 |
102 | /**
103 | * 创建一个新的 Pagelet 节点上下文
104 | *
105 | * @param int $type 节点类型
106 | * @param array $params 节点参数
107 | * @access public
108 | * @return void
109 | */
110 | public function __construct($type, $params = null)
111 | {
112 | $this->type = $type;
113 | $this->params = $params;
114 | $this->children = array();
115 | $this->events = array();
116 |
117 | $this->scripts = array();
118 | $this->scriptLinks = array();
119 | $this->styles = array();
120 | $this->styleLinks = array();
121 |
122 | $this->renderMode = BigPipe::RENDER_MODE_DEFAULT;
123 | $this->vars = array();
124 |
125 | if($type == BigPipe::TAG_HEAD || $type == BigPipe::TAG_BODY){
126 | $this->popTarget = true;
127 | }
128 |
129 | }
130 |
131 | /**
132 | * 添加依赖资源
133 | *
134 | * @param string $eventType 事件类型
135 | * @param string|array $resourceName 资源名或者资源列表
136 | * @access public
137 | * @return void
138 | *
139 | * @example
140 | * $context = BigPipe::currentContext();
141 | * $context->addRequire("beforedisplay", "common:css/layout.css");
142 | * $context->addRequire("load", array("common:js/jquery.js"));
143 | */
144 | public function addRequire($eventType, $resourceName)
145 | {
146 | //var_dump($resourceName);
147 | if(isset($this->popTarget) && $this->popTarget == true){
148 | $target = $this->parent;
149 | }else{
150 | $target = $this;
151 | }
152 |
153 | $event = $target->getEvent($eventType, self::FLG_AUTO_ADD_EVENT);
154 | if (is_array($resourceName)) {
155 | foreach ($resourceName as $name) {
156 | $event->addRequire($name);
157 | }
158 | } else {
159 | $event->addRequire($resourceName);
160 | }
161 | }
162 |
163 | /**
164 | * 添加异步依赖资源
165 | *
166 | * @param string $eventType 事件类型
167 | * @param string|array $resourceName 资源名或者资源列表
168 | * @access public
169 | * @return void
170 | *
171 | * @see addRequire
172 | */
173 | public function addRequireAsync($eventType, $resourceName)
174 | {
175 | if(isset($this->popTarget) && $this->popTarget == true){
176 | $target = $this->parent;
177 | }else{
178 | $target = $this;
179 | }
180 | $event = $target->getEvent($eventType, self::FLG_AUTO_ADD_EVENT);
181 | if (is_array($resourceName)) {
182 | foreach ($resourceName as $name) {
183 | $event->addRequireAsync($name);
184 | }
185 | } else {
186 | $event->addRequireAsync($resourceName);
187 | }
188 | }
189 |
190 | /**
191 | * 添加 hook 函数
192 | *
193 | * @param string $eventType 要添加到的事件类型
194 | * @param string $scriptCode 添加的代码
195 | * @access public
196 | * @return void
197 | *
198 | * @example
199 | * $context = BigPipe::currentContext();
200 | * $context->addHook("load", "console.log(\"pagelet loaded!\")");
201 | *
202 | * @see addRequire
203 | * @see addRequireAsync
204 | */
205 | public function addHook($eventType, $scriptCode, $strict)
206 | {
207 | if(isset($this->popTarget) && $this->popTarget == true){
208 | $target = $this->parent;
209 | }else{
210 | $target = $this;
211 | }
212 | $event = $target->getEvent($eventType, self::FLG_AUTO_ADD_EVENT);
213 | $event->addHook($scriptCode, $strict);
214 | }
215 |
216 | /**
217 | * 得到事件对象
218 | *
219 | * @param string $eventType 事件类型
220 | * @param int $flag 事件添加标志,可选值:FLG_AUTO_ADD_EVENT
221 | * @access private
222 | * @return PageletEvent 指定的事件对象
223 | */
224 | public function getEvent($eventType, $flag = self::FLG_NONE)
225 | {
226 | if (isset($this->events[$eventType])) {
227 | return $this->events[$eventType];
228 | } else if ($flag & self::FLG_AUTO_ADD_EVENT) {
229 | $this->events[$eventType] = new PageletEvent();
230 | return $this->events[$eventType];
231 | } else {
232 | return false;
233 | }
234 | }
235 |
236 | /**
237 | * 得到参数,如果不存在此参数,则返回默认值
238 | *
239 | * @param string $name 参数名
240 | * @param mixed $default 默认值
241 | * @access public
242 | * @return mixed 参数值
243 | */
244 | public function getParam($name, $default = null, $flag = self::FLG_NONE)
245 | {
246 | if (isset($this->params[$name])) {
247 | return $this->params[$name];
248 | } else if ($flag & self::FLG_APPEND_PARAM) {
249 | $this->params[$name] = $default;
250 | }
251 | return $default;
252 | }
253 |
254 | /**
255 | * 得到标签名
256 | *
257 | * @access private
258 | * @return string 标签名
259 | */
260 | private function getTagName()
261 | {
262 | switch ($this->type) {
263 | case BigPipe::TAG_HTML:
264 | return 'html';
265 | case BigPipe::TAG_HEAD:
266 | return 'head';
267 | case BigPipe::TAG_TITLE:
268 | return 'title';
269 | case BigPipe::TAG_BODY:
270 | return 'body';
271 | case BigPipe::TAG_PAGELET:
272 | return $this->getParam("html-tag", "div");
273 | default:
274 | }
275 | }
276 |
277 | /**
278 | * 得到打开标签的 HTML
279 | *
280 | * @param array $params 标签参数,如果为false,则不显示参数
281 | * @access public
282 | * @return string 标签打开的 HTML
283 | */
284 | public function getOpenHTML($params = null)
285 | {
286 |
287 | $text = "";
288 | if( $this->type == BigPipe::TAG_HTML ){
289 | $text .= "";
290 | }
291 | $text .= '<' . $this->getTagName();
292 |
293 | if ($params !== false) {
294 | if (!isset($params)) {
295 | $params = $this->params;
296 | }
297 |
298 | // Parse _attributes param for Hao123-sub
299 | if($this->type == BigPipe::TAG_BODY && isset($params["_attributes"])){
300 | $attrs = $params["_attributes"];
301 | unset($params["_attributes"]);
302 |
303 | foreach ($attrs as $key => $value) {
304 | $text .= " $key=\"" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8', true) . "\"";
305 | }
306 | }
307 |
308 | foreach ($params as $key => $value) {
309 | if(!isset($value)) continue;
310 | if (strpos($key, BigPipe::ATTR_PREFIX) !== 0) {
311 | $text .= " $key=\"" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8', true) . "\"";
312 | }
313 | }
314 |
315 | if(!empty($this->renderMode) && $this->type === BigPipe::TAG_PAGELET) {
316 | $text .= " data-rm=\"$this->renderMode\"";
317 | }
318 | }
319 | $text .= '>';
320 | return $text;
321 | }
322 |
323 | /**
324 | * 得到闭合标签的HTML
325 | *
326 | * @access public
327 | * @return string 标签闭合的 HTML
328 | */
329 | public function getCloseHTML()
330 | {
331 | return '' . $this->getTagName() . '>';
332 | }
333 |
334 | private static function getPriorityString($arr)
335 | {
336 | $str = array();
337 | foreach ($arr as $pri) {
338 | $str[] = str_pad($pri, self::$max_priority, '0', STR_PAD_LEFT);
339 | }
340 | $str = implode('/', $str) . ".";
341 | return $str;
342 | }
343 |
344 | public static function uniquePriority()
345 | {
346 | $list = array();
347 | foreach (self::$priority_list as $arr) {
348 | $list[] = self::getPriorityString($arr);
349 | }
350 | $list = array_unique($list, SORT_STRING);
351 | rsort($list, SORT_STRING);
352 | return $list;
353 | }
354 |
355 | public function setPriority($priority)
356 | {
357 | if ($this->parent !== null && $this->parent->priorityArray !== null) {
358 | $priorityArray = $this->parent->priorityArray;
359 | } else {
360 | $priorityArray = array();
361 | }
362 | $priorityArray[] = $priority;
363 | $this->priorityArray = $priorityArray;
364 | self::$priority_list[] = $this->priorityArray;
365 | self::$max_priority = max(self::$max_priority, strlen($priority));
366 | }
367 |
368 | public function getPriority()
369 | {
370 | if ($this->priority === null) {
371 | $this->priority = self::getPriorityString($this->priorityArray);
372 | }
373 | return $this->priority;
374 | }
375 |
376 | public function get($key, $default = null)
377 | {
378 | if (isset($this->vars[$key])) {
379 | return $this->vars[$key];
380 | }
381 | return $default;
382 | }
383 |
384 | public function set($key, $value = null)
385 | {
386 | if (isset($value)) {
387 | $this->vars[$key] = $value;
388 | } elseif (isset($this->vars[$key])) {
389 | unset($this->vars[$key]);
390 | }
391 | return $value;
392 | }
393 |
394 | }
--------------------------------------------------------------------------------
/dist/plugin/lib/PageletEvent.class.php:
--------------------------------------------------------------------------------
1 |
7 | * zhangwentao
8 | */
9 |
10 | class PageletEvent
11 | {
12 | /**
13 | * 事件的依赖资源
14 | *
15 | * @var mixed
16 | * @access public
17 | */
18 | public $requires = null;
19 |
20 | /**
21 | * 事件的异步依赖资源
22 | *
23 | * @var mixed
24 | * @access public
25 | */
26 | public $requireAsyncs = null;
27 |
28 | /**
29 | * 事件的钩子函数
30 | *
31 | * @var mixed
32 | * @access public
33 | */
34 | public $hooks = null;
35 |
36 | /**
37 | * 构造新的 PageletEvent 对象
38 | *
39 | * @access public
40 | * @return void
41 | */
42 | public function __construct()
43 | {
44 | $this->requires = array();
45 | $this->requireAsyncs = array();
46 | $this->hooks = array();
47 | }
48 |
49 | /**
50 | * 添加依赖资源
51 | *
52 | * @param string $resourceName 依赖资源名
53 | * @access public
54 | * @return void
55 | */
56 | public function addRequire($resourceName)
57 | {
58 | if (!in_array($resourceName, $this->requires)) {
59 | $this->requires[] = $resourceName;
60 | }
61 | }
62 |
63 | /**
64 | * 添加异步依赖资源
65 | *
66 | * @param string $resourceName 异步依赖资源名
67 | * @access public
68 | * @return void
69 | */
70 | public function addRequireAsync($resourceName)
71 | {
72 | if (!in_array($resourceName, $this->requireAsyncs)) {
73 | $this->requireAsyncs[] = $resourceName;
74 | }
75 | }
76 |
77 | /**
78 | * 添加钩子函数
79 | *
80 | * @param string $scriptCode 钩子函数代码
81 | * @access public
82 | * @return void
83 | */
84 | public function addHook($scriptCode, $strict)
85 | {
86 | if($strict){
87 | $scriptCode = "'use strict';\n" . $scriptCode;
88 | }
89 | $this->hooks[] = $scriptCode;
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/dist/plugin/lib/SmartController.class.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | BigPipe::loadClass("FirstController");
10 | BigPipe::loadClass("BigPipeResource");
11 |
12 | class SmartController extends FirstController
13 | {
14 | const STAT_COLLECT = 1; // 收集阶段
15 | const STAT_OUTPUT = 2; // 输出阶段
16 |
17 | private $state = self::STAT_COLLECT; //初始状态
18 | private $headInnerHTML = null;
19 | private $bodyInnerHTML = null;
20 |
21 | private $loadedResource = array();
22 |
23 | protected $sessionId = 0; // 此次会话ID,用于自动生成不重复id,第一次默认为0
24 | protected $uniqIds = array(); // 不重复id种子
25 |
26 | /**
27 | * 构造函数
28 | *
29 | * @access public
30 | * @return void
31 | */
32 | public function __construct($sessions, $ids)
33 | {
34 | $this->ids = $ids;
35 | $this->sessions = $sessions;
36 |
37 | $this->pagelets = array();
38 | $this->cids = array();
39 | $this->oids = $ids;
40 |
41 | $this->actionChain = array(
42 | //收集阶段
43 | 'collect_pagelet_open' => 'collect_pagelet_open',
44 | 'collect_pagelet_close' => 'collect_pagelet_close',
45 | // 'collect_pagelet_open' => array(
46 | // //TODO 'outputPageletOpenTag',
47 | // 'addPagelet',
48 | // 'outputSmartOpenTag',
49 | // 'startCollect',
50 | // true
51 | // ),
52 | // 'collect_pagelet_close' => array(
53 | // 'collectHTML',
54 | // // 'setupBigrender',
55 | // 'outputSmartCloseTag'
56 | // ),
57 | 'collect_more' => array(
58 | 'changeState',
59 | true
60 | ),
61 | //输出阶段
62 | 'output_body_close' => array(
63 | 'outputPagelets',
64 | ),
65 | 'output_more' => false,
66 | 'default' => false
67 | );
68 | }
69 |
70 | /**
71 | * collect_pagelet_open 时的 actionChain
72 | *
73 | * @param PageletContext $context
74 | * @return mixed: actionChain
75 | */
76 | protected function collect_pagelet_open($context) {
77 | if($context->renderMode === BigPipe::RENDER_MODE_NONE) {
78 | return false;
79 | }
80 |
81 | $context->renderMode = BigPipe::RENDER_MODE_QUICKLING;
82 |
83 | $id = $context->getParam(
84 | "id",
85 | $this->sessionUniqId("__elm_"),
86 | PageletContext::FLG_APPEND_PARAM
87 | );
88 |
89 | if(isset($context->parent)){
90 | $parentId = $context->parent->getParam("id");
91 | if(!empty($parentId) && in_array($parentId, $this->ids)){
92 | $this->ids = array_merge($this->ids, array($id));
93 | $this->cids[] = $id;
94 | }
95 | }
96 | if(in_array($id, $this->ids)){
97 | $this->pagelets[] = $context;
98 | // var_dump($context->getParam("id"), $this->cids );
99 | if( in_array($context->getParam("id"), $this->cids ) ){
100 | return array(
101 | 'outputOpenTag',
102 | 'addPagelet',
103 | 'startCollect',
104 | true
105 | );
106 | }else{
107 | return array(
108 | // 'outputOpenTag',
109 | 'addPagelet',
110 | 'startCollect',
111 | true
112 | );
113 | }
114 | }else{
115 | // $context->renderMode = BigPipe::RENDER_MODE_NONE;
116 | // $context->opened = false;
117 | return false;
118 | }
119 | }
120 |
121 | /**
122 | * collect_pagelet_close 时的 actionChain
123 | *
124 | * @param PageletContext $context
125 | * @return mixed: actionChain
126 | */
127 | protected function collect_pagelet_close($context) {
128 |
129 | // if($context->renderMode === BigPipe::RENDER_MODE_NONE) {
130 | if(!$context->opened) {
131 | return false;
132 | }
133 |
134 | if( in_array($context->getParam("id"), $this->cids ) ){
135 | return array(
136 | 'collectHTML',
137 | 'outputCloseTag'
138 | );
139 | }
140 |
141 | return array(
142 | 'collectHTML',
143 | );
144 | }
145 |
146 | /**
147 | * 输出Quickling请求的pagelets
148 | *
149 | * @param PageletContext $context
150 | */
151 | protected function outputPagelets($context)
152 | {
153 | $pagelets = array();
154 | foreach ($this->pagelets as $pagelet) {
155 | $id = $pagelet->getParam("id");
156 | if( in_array($id, $this->ids) ){
157 | $config = $this->outputPagelet($pagelet);
158 |
159 | if( isset($this->sessions[$id]) ){
160 | $config["session"] = $this->sessions[$id];
161 | }
162 | $pagelets[] = $config;
163 | }
164 | }
165 |
166 | // 输出之前 设置 Content-Type: application/json
167 | header('Content-Type: application/json;charset=UTF-8');
168 | echo json_encode($pagelets);
169 | }
170 | /**
171 | * 按Quickling模式输出一个pagelet
172 | *
173 | * @param PageletContext $context
174 | */
175 | protected function outputPagelet($pagelet)
176 | {
177 | $resourceMap = array();
178 | $hooks = array();
179 | $config = $this->getPageletConfig($pagelet, $html, $resourceMap, $hooks, true);
180 | $config['quickling'] = true;
181 | $outputMap = array();
182 | //设置资源表
183 | if (!empty($resourceMap)) {
184 | $resourceMap = BigPipeResource::pathToResource($resourceMap);
185 | $resourceMap = BigPipeResource::getDependResource($resourceMap);
186 |
187 | $resourceMap = BigPipe::array_merge($resourceMap, $this->loadedResource);
188 |
189 | foreach ($resourceMap as $id => $resource) {
190 |
191 | if(isset(BigPipeResource::$knownResources[$id])){
192 | continue;
193 | }
194 |
195 | $requires = $resource['requires'];
196 | unset($resource['requires']);
197 | unset($resource['requireAsyncs']);
198 |
199 | $requireIds = array();
200 | if(!empty($requires)){
201 | $requires = BigPipeResource::pathToResource($requires);
202 | $requireIds = array_keys($requires);
203 | }
204 | $resource['deps'] = $requireIds;
205 | $resource['mods'] = $resource['defines'];
206 |
207 | unset($resource['defines']);
208 | unset($resource['id']);
209 | $outputMap[$id] = $resource;
210 | BigPipeResource::$knownResources[$id] = $resource;
211 | }
212 | }
213 |
214 | $config["resourceMap"] = $outputMap;
215 |
216 | return $config;
217 | }
218 | }
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var del = require('del');
3 | var inline = require('gulp-inline-js');
4 |
5 | var path = {
6 | libjs: {
7 | src: 'src/javascript/main.js',
8 | release: 'dist/javascript'
9 | },
10 | libplugin: {
11 | src: 'src/plugin/**/*',
12 | release: 'dist/plugin'
13 | },
14 | jshelper: {
15 | src: 'src/js_helper/**/*',
16 | release: 'dist/js_helper'
17 | }
18 | };
19 |
20 | //compile libjs
21 | gulp.task('libjs:compile', function () {
22 | return gulp.src(path.libjs.src)
23 | .pipe(inline())
24 | .pipe(gulp.dest(path.libjs.release));
25 | });
26 |
27 | //copy runtime
28 | gulp.task('libplugin:copy', function () {
29 | gulp.src(path.libplugin.src)
30 | .pipe(gulp.dest(path.libplugin.release));
31 | });
32 |
33 | //copy jshelper
34 | gulp.task('js_helper:copy', function () {
35 | return gulp.src(path.jshelper.src)
36 | .pipe(gulp.dest(path.jshelper.release));
37 | });
38 |
39 | gulp.task('clean', del.bind(null, 'dist/**'));
40 | gulp.task('compile', ['libjs:compile', 'libplugin:copy', 'js_helper:copy']);
41 |
42 | gulp.task('default', ['clean'], function(){
43 | gulp.start('compile');
44 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "her-runtime",
3 | "version": "0.0.2",
4 | "description": "her的运行时,包括后端smarty插件和前端js框架",
5 | "keywords": [
6 | "herJS",
7 | "fis",
8 | "bigpipe",
9 | "quickling",
10 | "bigrender",
11 | "lazyrender",
12 | "pagelet",
13 | "high-performence rendering"
14 | ],
15 | "homepage": "https://hao123-fe.github.io/her",
16 | "bugs": {
17 | "url": "https://github.com/hao123-fe/her/issues"
18 | },
19 | "author": {
20 | "name": "hao123-fe",
21 | "email": "zhangwentao@baidu.com"
22 | },
23 | "engines": {
24 | "node": ">=0.9"
25 | },
26 | "licenses": "MIT",
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/hao123-fe/her-runtime.git"
30 | },
31 | "dependencies": {
32 | "del": "^0.1.3",
33 | "gulp": "^3.6.0",
34 | "gulp-inline-js": "latest"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/javascript/BigPipe.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Bigpipe提供获取pagelet 处理pagelet和资源的接口
3 | * @author zhangwentao(zhangwentao@baidu.com)
4 | */
5 |
6 | __d("BigPipe", ["Resource", "Requestor", "Controller"], function(global, require, module, exports) {
7 |
8 | var Resource = require("Resource");
9 | var Requestor = require("Requestor");
10 | var Controller = require("Controller");
11 |
12 | /**
13 | * @global
14 | * @namespace
15 | */
16 | var BigPipe = {
17 | init: function() {},
18 | /**
19 | * 页面调用函数,用于传输数据
20 | *
21 | * @param {Object} conf pagelet数据
22 | * @example
23 | * BigPipe.onPageletArrive({
24 | * "id" : "", //pagelet 的 ID
25 | * "container": "__cnt_0_1", //pagelet HTML的容器ID
26 | * "children" : [ //子pagelet的 ID 列表
27 | * "header",
28 | * "__elm_0_1",
29 | * "__elm_0_2"
30 | * ].
31 | * "deps":{ //事件的依赖资源
32 | * "beforeload": [ //"beforeload" 事件的依赖资源
33 | * "XbsgfuDYNN"
34 | * ],
35 | * "load": [ //"load" 事件的依赖资源
36 | * "qwlFYEkDet"
37 | * ]
38 | * },
39 | * "hooks":{ //事件回调
40 | * "beforeload": [ //"beforeload" 的事件回调
41 | * "__cb_0_1",
42 | * "__cb_0_2"
43 | * ],
44 | * "load":[ //"load" 的事件回调
45 | * "__cb_0_3",
46 | * "__cb_0_4"
47 | * ]
48 | * }
49 | * });
50 | */
51 | onPageletArrive: function(conf) {
52 | Controller.pageletArrive(conf);
53 | },
54 | //TODO
55 | /**
56 | * 页面调用函数,用于设置资源列表
57 | *
58 | * @param {Object} map 资源列表
59 | * @example
60 | * BigPipe.setResourceMap({
61 | * "XbsgfuDYNN" : { //资源ID
62 | * "type" : "css", //资源类型
63 | * "src" : "http://s0.hao123img.com/v5/xx/XX/XX.Bigpipe", //资源地址
64 | * "deps" : [ //依赖资源 ID 列表
65 | * "rIffdDBQKC",
66 | * "qwlFYEkDet"
67 | * ],
68 | * "mods" : [ //资源所包含的模块列表
69 | * "common:Bigpipe/jquery.Bigpipe",
70 | * "common:Bigpipe/event.Bigpipe"
71 | * ]
72 | * },
73 | * "rIffdDBQKC" : {
74 | * // 同上
75 | * }
76 | * });
77 | */
78 | setResourceMap : Controller.setResourceMap, //function(map) {
79 | //TODO
80 | //Resource.setResourceMap(map);
81 | //},
82 | loadedResource : Resource.loadedResource, //function(resources){
83 | //Resource.loadedResource(resources);
84 | //},
85 | getResource : Resource.getResource, //function(id) {
86 | //TODO
87 | //return Resource.getResource(id);
88 | //},
89 | getResourceByName : Resource.getResourceByName, //function(name){
90 | //return Resource.getResourceByName(name);
91 | //},
92 | hooks : Controller.hooks,
93 | fetch : function(pagelets, url){
94 | Requestor.fetch(pagelets, url);
95 | }
96 | };
97 |
98 | module.exports = BigPipe;
99 |
100 | });
101 |
--------------------------------------------------------------------------------
/src/javascript/CSSLoader.js:
--------------------------------------------------------------------------------
1 | __d("CSSLoader", ["EventEmitter"], function(global, require, module, exports) {
2 | var EventEmitter = require("EventEmitter");
3 | /**
4 | * 声明全局变量
5 | * @param {Number} interval 检测拉取css的时间间隔
6 | * @param {Number} WAITTIME 等待超时时间
7 | * @param {Boolen} dusSupport 是否支持data url scheme
8 | * @param {Boolen} checked 是否已检测过data url scheme
9 | * @param {Object} loadedMap 已加载css的Map
10 | * @param {Array} styleSheetSet 加载styleSheet的集合
11 | * @param {Number} timeout 超时时间
12 | * @param {Object} pullMap 拉取css的Map
13 | */
14 |
15 | var interval = 20,
16 | WAITTIME = 5000,
17 | dusSupport,
18 | checked,
19 | loadedMap = {},
20 | styleSheetSet = [],
21 | timeout,
22 | pullMap = {};
23 |
24 | function checkDusSupport() {
25 | var link;
26 | if (checked)
27 | return;
28 | checked = true;
29 | link = document.createElement('link');
30 | link.onload = function () {
31 | dusSupport = true;
32 | link.parentNode.removeChild(link);
33 | };
34 | link.rel = 'stylesheet';
35 | link.href = 'data:text/css;base64,';
36 | appendToHead(link);
37 | }
38 |
39 | function checkCssLoaded() {
40 | var id,
41 | callbacks = [],
42 | contexts = [],
43 | signals = [],
44 | signal,
45 | style;
46 | if (+new Date >= timeout) { //超时
47 | for (id in pullMap) {
48 | signals.push(pullMap[id].signal);
49 | callbacks.push(pullMap[id].error);
50 | contexts.push(pullMap[id].context);
51 | }
52 | pullMap = {};
53 | } else {
54 | for (id in pullMap) {
55 | signal = pullMap[id].signal;
56 | style = window.getComputedStyle ? getComputedStyle(signal, null) : signal.currentStyle;
57 | if (style && parseInt(style.height, 10) > 1) {
58 | callbacks.push(pullMap[id].load);
59 | contexts.push(pullMap[id].context);
60 | signals.push(signal);
61 | delete pullMap[id];
62 | }
63 | }
64 | }
65 | //清理
66 | for (var i = 0; i < signals.length; i++)
67 | signals[i].parentNode.removeChild(signals[i]);
68 | if (!isEmpty(callbacks)) {
69 | for (i = 0; i < callbacks.length; i++) {
70 | callbacks[i].call(contexts[i]);
71 | }
72 | timeout = +new Date + WAITTIME;
73 | }
74 | return isEmpty(pullMap);
75 | }
76 |
77 | function pullCss(id, onload, onTimeout, context) {
78 | var meta, empty, timer;
79 | meta = document.createElement('meta');
80 | meta.className = 'css_' + id;
81 | appendToHead(meta);
82 |
83 | empty = !isEmpty(pullMap);
84 | timeout = +new Date + WAITTIME;
85 | pullMap[id] = {
86 | signal: meta,
87 | load: onload,
88 | error: onTimeout,
89 | context: context
90 | };
91 | if (!empty) {
92 | timer = setInterval(function () {
93 | if (checkCssLoaded()) {
94 | clearInterval(timer);
95 | }
96 | }, interval);
97 | }
98 | }
99 |
100 | function onload() {
101 | this.done("load");
102 | }
103 |
104 | function onTimeout() {
105 | this.done("timeout");
106 | }
107 |
108 | var EVENT_TYPES = [
109 | "load", // CSS加载完成时派发的事件
110 | "timeout" // CSS 加载超时事件
111 | //TODO 错误处理?
112 | ];
113 |
114 | var CSSLoader = derive(EventEmitter,
115 | /**
116 | * 构造一个CSS加载对象,
117 | *
118 | * @constructor
119 | * @extends EventEmitter
120 | * @alias CSSLoader
121 | *
122 | * @param {String} id CSS资源ID
123 | * @param {Object} config CSS资源信息
124 | */
125 | function (__super, id, config) {
126 | __super(EVENT_TYPES);
127 | /**
128 | * CSS资源ID
129 | * @member {String}
130 | */
131 | this.id = id;
132 | /**
133 | * CSS资源URL
134 | * @member {String}
135 | */
136 | this.uri = config.src;
137 |
138 | },
139 | /**
140 | * @alias CSSLoader.prototype
141 | */
142 | {
143 | /**
144 | * 开始加载资源
145 | * @fires CSSLoader#load
146 | */
147 | load: function () {
148 | var me = this,
149 | id = this.id,
150 | uri = this.uri,
151 | index, link;
152 | if (loadedMap[id])
153 | throw new Error('CSS component ' + id + ' has already been requested.');
154 | if (document.createStyleSheet) {
155 | for (var i = 0; i < styleSheetSet.length; i++)
156 | if (styleSheetSet[i].imports.length < 31) {
157 | index = i;
158 | break;
159 | }
160 | if (index === undefined) {
161 | styleSheetSet.push(document.createStyleSheet());
162 | index = styleSheetSet.length - 1;
163 | }
164 | styleSheetSet[index].addImport(uri);
165 | loadedMap[id] = {
166 | styleSheet: styleSheetSet[index],
167 | uri: uri
168 | };
169 | pullCss(id, onload, onTimeout, this);
170 | return;
171 | }
172 | link = document.createElement('link');
173 | link.rel = 'stylesheet';
174 | link.type = 'text/css';
175 | link.href = uri;
176 | loadedMap[id] = {
177 | link: link
178 | };
179 | if (dusSupport) {
180 | link.onload = function () {
181 | link.onload = link.onerror = null;
182 | //link.parentNode.removeChild(link);
183 | onload.call(me);
184 | };
185 | link.onerror = function () {
186 | link.onload = link.onerror = null;
187 | //link.parentNode.removeChild(link);
188 | onTimeout.call(me);
189 | };
190 | } else {
191 | pullCss(id, onload, onTimeout, this);
192 | if (dusSupport === undefined)
193 | checkDusSupport();
194 | }
195 | appendToHead(link);
196 | },
197 | unload: function () {
198 | // TODO 资源卸载?一期先不用
199 | }
200 | });
201 |
202 | module.exports = CSSLoader;
203 | });
204 |
--------------------------------------------------------------------------------
/src/javascript/Controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Controller 处理pageletArrive
3 | * @author zhangwentao(zhangwentao@baidu.com)
4 | */
5 |
6 | __d("Controller", ["Pagelet", "Resource"], function (global, require, module, exports) {
7 |
8 | var Pagelet = require("Pagelet");
9 | var Resource = require("Resource");
10 |
11 | var Controller = {
12 | pageletsArrive : function(pagelets){
13 | each(pagelets, function(pagelet){
14 | this.pageletArrive(pagelet);
15 | }, this);
16 | },
17 | pageletArrive : function(conf){
18 | var pagelet;
19 |
20 | if(Pagelet.hasPlagelet(conf.id)){
21 | Pagelet.getPlagelet(conf.id).unload();
22 | }
23 |
24 | pagelet = Pagelet.getPlagelet(conf.id);
25 |
26 | if(conf.quickling){
27 | Resource.setResourceMap(conf.resourceMap);
28 | }else if(conf.html){
29 | conf.html = document.getElementById(conf.html.container).firstChild.data;
30 | }
31 |
32 | var hooks = conf.hooks;
33 | if(hooks){
34 | for(var i in hooks){
35 | var type = hooks[i];
36 | for(var j = 0; j < type.length; j++){
37 | conf.quickling ?
38 | pagelet.on(i, new Function(type[j])) :
39 | pagelet.on(i, this.hooks[type[j]]);
40 | }
41 | }
42 | }
43 |
44 | pagelet.arrive(conf);
45 | },
46 | hooks:{},
47 | setResourceMap : Resource.setResourceMap
48 | };
49 |
50 | module.exports = Controller;
51 | });
52 | /* @cmd false */
53 |
--------------------------------------------------------------------------------
/src/javascript/EventEmitter.js:
--------------------------------------------------------------------------------
1 | __d("EventEmitter", [], function(global, require, module, exports) {
2 | /**
3 | * 事件机制实现
4 | *
5 | * @class
6 | * @param {Object} [bindObj] 使用借用方式调用时的目标对象
7 | * @param {Array} types 初始的事件列表
8 | * @access public
9 | *
10 | * @example
11 | * // 直接使用
12 | * var emitter = new EventEmitter(["load"]);
13 | *
14 | * emitter.on("load", function(data){
15 | * console.log("load!!!");
16 | * });
17 | *
18 | * emitter.emit("load", data);
19 | *
20 | *
21 | * @example
22 | * // 继承使用
23 | * var SubClass = derive(EventEmitter, function(__super){
24 | * __super(["someevent"]);
25 | * }, {
26 | * doSomething:function() {
27 | * this.emit("someevent");
28 | * }
29 | * });
30 | *
31 | * var emitter = new SubClass();
32 | *
33 | * emitter.on("someevent", function(){
34 | * console.log("some event!!");
35 | * });
36 | *
37 | * emitter.doSomething(); // some event!!
38 | *
39 | * @example
40 | * // 借用方式使用
41 | * var obj = {};
42 | * EventEmitter(obj, ["load"]);
43 | *
44 | * obj.on("load", function(data){
45 | * console.log("load!!");
46 | * });
47 | * obj.emit("load", data);
48 | */
49 | function EventEmitter(bindObj, types) {
50 | if (this instanceof EventEmitter) {
51 | //使用 new EventEmitter(types)方式调用
52 | types = bindObj;
53 | bindObj = this;
54 | } else if (bindObj !== undefined) {
55 | //借用方式调用
56 | copyProperties(bindObj, EventEmitter.prototype);
57 | }
58 | bindObj.initEventTypes(types);
59 | }
60 |
61 | /**
62 | * 成员函数表
63 | * @alias EventEmitter.prototype
64 | */
65 | var eventEmitterMembers = {
66 |
67 | /**
68 | * 初始化监听事件类型
69 | *
70 | * @param {Array} types 监听的事件类型
71 | * @access public
72 | * @return void
73 | */
74 | initEventTypes: function(types) {
75 | var listenerMap = {};
76 | each(types, function(type) {
77 | listenerMap[type] = {
78 | callbacks: [],
79 | eventData: undefined
80 | };
81 | });
82 | /**
83 | * _listenerMap
84 | *
85 | * 监听函数列表
86 | * {
87 | * "load":{ //事件类型
88 | * "callbacks": [ //回调函数列表
89 | * {
90 | * callback:{Function} //回调函数
91 | * thisObj:{Object} //执行回调函数时的 this
92 | * once:false //是否执行一次
93 | * }
94 | * ],
95 | * "eventData": undefined //事件数据,调用 done 时会保存事件数据
96 | * }
97 | * }
98 | */
99 | this._listenerMap = listenerMap;
100 | },
101 |
102 | /**
103 | * 添加事件监听
104 | *
105 | * @param {String} type 事件类型
106 | * @param {Function} callback 事件监听函数
107 | * @param {Object} [thisObj = this] 执行监听函数时的 this 对象
108 | * @param {Boolean} [once = false] 是否只触发一次
109 | * @return {Boolean} 是否添加成功
110 | */
111 | addEventListener: function(type, callback, thisObj, once) {
112 | var listenerMap = this._listenerMap,
113 | eventConfig,
114 | eventData,
115 | callbacks,
116 | immediately;
117 |
118 | if (!hasOwnProperty(listenerMap, type)) {
119 | // 不知道的事件类型
120 | // TODO 报错
121 | return false;
122 | }
123 |
124 | eventConfig = listenerMap[type];
125 | eventData = eventConfig.eventData;
126 | callbacks = eventConfig.callbacks;
127 | // eventData 不为undefined,该事件已经 done 过
128 | // 需要直接调用回调函数
129 | immediately = eventData !== undefined;
130 | thisObj = thisObj || this;
131 | once = !!once;
132 |
133 |
134 | if (!(immediately && once)) {
135 | //eventConfig.eventData = data;
136 | //如果不是直接执行并且只执行一次,则添加到回调列表中
137 | callbacks.push({
138 | callback: callback,
139 | thisObj: thisObj,
140 | once: once
141 | });
142 | }
143 | if (immediately) {
144 | // TODO 错误处理?
145 | queueCall(bind(callback, thisObj, eventData));
146 | }
147 |
148 | return true;
149 | },
150 |
151 | /**
152 | * 删除事件监听
153 | *
154 | * @param {String} type 需要删除的监听类型
155 | * @param {Function} [callback] 要删除的监听函数,如果不传这个参数则删除该类型下的所有监听函数
156 | * @return {Boolean} 是否删除成功
157 | */
158 | removeEventListener: function(type, callback) {
159 | var listenerMap = this._listenerMap,
160 | eventConfig,
161 | callbacks,
162 | count;
163 |
164 | if (!hasOwnProperty(listenerMap, type)) {
165 | // 不知道的事件类型
166 | // TODO 报错
167 | return false;
168 | }
169 |
170 | eventConfig = listenerMap[type];
171 |
172 | if (arguments.length > 1) {
173 | //这里用来判断是否传入了 callback 参数
174 | //不用 callback === undefined 是为了避免
175 | //不小心传入了一个为 undefined 值的参数
176 | //导致所有的监听函数都被 remove
177 |
178 | callbacks = eventConfig.callbacks;
179 | count = callbacks.length;
180 |
181 | while (count--) { //从后往前遍历,方便 splice
182 | if (callbacks[count].callback === callback) {
183 | callbacks.splice(count, 1);
184 | }
185 | }
186 |
187 | } else {
188 | //没有传入第二个参数,直接重置 callbacks 数组
189 | eventConfig.callbacks = [];
190 | }
191 |
192 | return true;
193 | },
194 |
195 | /**
196 | * 派发事件
197 | *
198 | * @param {String} type 派发的事件类型
199 | * @param {Object} [data=null] 事件参数
200 | * @return {Boolean} 如果至少有一个监听函数返回 false 则返回 false,否则返回 true
201 | */
202 | emit: function(type, data) {
203 | var listenerMap = this._listenerMap,
204 | eventConfig,
205 | callbacks,
206 | count,
207 | index,
208 | item,
209 | ret;
210 |
211 | if (!hasOwnProperty(listenerMap, type)) {
212 | // 不知道的事件类型
213 | throw new Error("unknow event type\"" + type + "\"");
214 | }
215 |
216 | eventConfig = listenerMap[type];
217 | callbacks = eventConfig.callbacks;
218 | count = callbacks.length;
219 | /**
220 | * 将 undefined 值转化为 null
221 | */
222 | data = data === undefined ? null : data;
223 | /**
224 | * 返回值,只要有一个为falas,则返回值为false
225 | */
226 | ret = true;
227 |
228 | for (index = 0; index < count; index++) {
229 | item = callbacks[index];
230 | if (item.once) {
231 | callbacks.splice(index, 1);
232 | index--;
233 | count--;
234 | }
235 | ret = item.callback.call(item.thisObj, data) !== false && ret;
236 | }
237 | return ret;
238 | },
239 |
240 | /**
241 | * 派发事件并且保存事件参数,
242 | * 当有新的监听添加时,直接调用.
243 | *
244 | * @param {String} type 事件类型
245 | * @param {Object} [data] 事件参数
246 | * @return {Boolean} 是否派发成功
247 | * @see emit
248 | *
249 | * @example
250 | * var emitter = new EventEmitter(["load"]);
251 | * emitter.done("load", data);
252 | *
253 | * emitter.on("load", function(data){
254 | * console.log("这里会立即执行");
255 | * });
256 | */
257 | done: function(type, data) {
258 | var listenerMap = this._listenerMap,
259 | eventConfig,
260 | ret;
261 |
262 | /**
263 | * 将 undefined 值转化为 null
264 | */
265 | data = data === undefined ? null : data;
266 | eventConfig = listenerMap[type];
267 | eventConfig.eventData = data;
268 |
269 | //emit其实是一个很复杂的过程,为了确保done的事件被监听时能立即执行,把emit放在后面
270 | ret = this.emit(type, data);
271 |
272 | return ret;
273 | },
274 |
275 | /**
276 | * 删除由 done 保存的数据
277 | * @param {String} type 事件类型
278 | * @return {Boolean} 是否删除成功
279 | */
280 | undo: function(type) {
281 | var listenerMap = this._listenerMap,
282 | eventConfig;
283 |
284 | if (!hasOwnProperty(listenerMap, type)) {
285 | // 不知道的事件类型
286 | throw new Error("unknow event type\"" + type + "\"");
287 | }
288 |
289 | eventConfig = listenerMap[type];
290 | eventConfig.eventData = undefined;
291 | return true;
292 | }
293 | };
294 |
295 |
296 | /**
297 | * addEventListener 的快捷方法
298 | * @function
299 | * @param {String} type 事件类型
300 | * @param {Function} callback 事件监听函数
301 | * @param {Object} [thisObj = this] 执行监听函数时的 this 对象
302 | * @param {Boolean} [once = false] 是否只触发一次
303 | * @return {Boolean} 是否添加成功
304 | * @see addEventListener
305 | */
306 | eventEmitterMembers.on = eventEmitterMembers.addEventListener;
307 |
308 | /**
309 | * removeEventListener 的快捷方法
310 | * @function
311 | * @param {String} type 需要删除的监听类型
312 | * @param {Function} [callback] 要删除的监听函数,如果不传这个参数则删除该类型下的所有监听函数
313 | * @return {Boolean} 是否删除成功
314 | * @see removeEventListener
315 | */
316 | eventEmitterMembers.off = eventEmitterMembers.removeEventListener;
317 | /**
318 | * addEventListener 的快捷方法,用于添加只触发一次的监听
319 | * @function
320 | * @param {String} type 事件类型
321 | * @param {Function} callback 事件监听函数
322 | * @param {Object} [thisObj = this] 执行监听函数时的 this 对象
323 | * @return {Boolean} 是否添加成功
324 | * @see addEventListener
325 | */
326 | eventEmitterMembers.once = function(type, callback, thisObj) {
327 | return this.addEventListener(type, callback, thisObj, true);
328 | };
329 |
330 | copyProperties(EventEmitter.prototype, eventEmitterMembers);
331 |
332 | module.exports = EventEmitter;
333 | });
334 |
--------------------------------------------------------------------------------
/src/javascript/JSLoader.js:
--------------------------------------------------------------------------------
1 | __d("JSLoader", ["EventEmitter"], function(global, require, module, exports) {
2 | var EventEmitter = require("EventEmitter");
3 | var STAT_INITIALIZED = 1,
4 | STAT_LOADING = 2,
5 | STAT_LOADED = 3,
6 | STAT_ERROR = 4;
7 |
8 | var EVENT_TYPES = [
9 | "load" // JS加载完成时派发的事件
10 | //TODO 错误处理?
11 | ];
12 |
13 | var JSLoader = derive(EventEmitter,
14 | /**
15 | * 构造一个Js加载对象,
16 | *
17 | * @constructor
18 | * @extends EventEmitter
19 | * @alias JSLoader
20 | *
21 | * @param {String} id JS资源ID
22 | * @param {Object} config JS资源信息
23 | */
24 | function (__super, id, config) {
25 | __super(EVENT_TYPES);
26 | /**
27 | * JS资源ID
28 | * @member {String}
29 | */
30 | this.id = id;
31 | /**
32 | * JS资源URL
33 | * @member {String}
34 | */
35 | this.url = config.src;
36 | /**
37 | * loader加载状态
38 | * @member {Number}
39 | */
40 | this.state = STAT_INITIALIZED;
41 | },
42 | /**
43 | * @alias JSLoader.prototype
44 | */
45 | {
46 | /**
47 | * 开始加载资源
48 | * @fires JSLoader#load
49 | */
50 | load: function () {
51 | //TODO 实现资源加载
52 | var me = this,
53 | script,
54 | onload;
55 | if (this.state >= STAT_LOADING)
56 | return;
57 | this.state = STAT_LOADING;
58 |
59 | script = document.createElement('script');
60 | script.type = 'text/javascript';
61 | script.src = me.url;
62 | script.async = true;
63 |
64 | script.onload = function () {
65 | callback(true);
66 | };
67 |
68 | script.onerror = function () {
69 | callback(false);
70 | };
71 |
72 | script.onreadystatechange = function () {
73 | if (this.readyState in {
74 | loaded: 1,
75 | complete: 1
76 | }) {
77 | callback(true);
78 | }
79 | };
80 |
81 | appendToHead(script);
82 | /**
83 | * 资源加载完成回调
84 | *
85 | * @event JSLoader#load
86 | */
87 | function callback(success) {
88 | if (me.state >= STAT_LOADED) {
89 | return;
90 | }
91 | me.state = success ? STAT_LOADED : STAT_ERROR;
92 |
93 | me.emit("load");
94 |
95 | nextTick(function () {
96 |
97 | script.onload = script.onerror = script.onreadystatechange = null;
98 | script.parentNode && script.parentNode.removeChild(script);
99 | script = null;
100 | });
101 | }
102 | },
103 |
104 | unload: function () {
105 | // TODO 资源卸载?一期先不用
106 | }
107 | });
108 |
109 | module.exports = JSLoader;
110 | });
111 |
--------------------------------------------------------------------------------
/src/javascript/Pagelet.js:
--------------------------------------------------------------------------------
1 | __d("Pagelet", ["EventEmitter", "Resource"], function (global, require, module, exports) {
2 |
3 | var EventEmitter = require("EventEmitter"),
4 | Resource = require("Resource");
5 |
6 | var EVENT_TYPES = [
7 | "beforeload" // Pagelet 开始加载前派发,return false 可以阻止加载
8 | , "beforedisplay" // Pagelet 加载完成CSS资源和html资源,准备渲染时派发
9 | , "display" //Pagelet dom渲染后派发
10 | , "load" // Pagelet 加载并渲染完成后派发
11 | , "unload" // Pagelet 卸载完成后派发
12 | ];
13 |
14 | var STAT_UNINITIALIZED = 0,
15 | STAT_INITIALIZED = 1,
16 | STAT_LOADING = 2,
17 | STAT_DISPLAYED = 3,
18 | STAT_UNLOADING = 4;
19 |
20 | var pageletSet = {};
21 |
22 | var Pagelet = derive(EventEmitter,
23 | /**
24 | * 构造一个 Pagelet 对象,
25 | *
26 | * @constructor
27 | * @extends EventEmitter
28 | * @alias Pagelet
29 | *
30 | * @param {String} id Pagelet id
31 | */
32 | function (__super, id) {
33 | __super(EVENT_TYPES);
34 |
35 | /**
36 | * Pagelet id
37 | * @member {String}
38 | */
39 | this.id = id;
40 |
41 | /**
42 | * Pagelet root
43 | * @member {Boolen}
44 | */
45 | this.root = this.id === null;
46 | /**
47 | * Pagelet state
48 | * @member {Number}
49 | */
50 | this.state = STAT_UNINITIALIZED;
51 |
52 | /**
53 | * 绑定会被作为参数传递的成员函数
54 | */
55 | this.load = bind(this.load, this);
56 | this.display = bind(this.display, this);
57 | },
58 | /**
59 | * @alias Pagelet.prototype
60 | */
61 | {
62 | /**
63 | * 加载依赖资源组
64 | *
65 | * @param {String} name 要加载的依赖资源组key
66 | * @param {Function} [callback] 依赖资源加载完成后的回调函数
67 | */
68 | loadDepends: function (key, callback) {
69 | var deps, count, timeout, onload, onresoved;
70 |
71 | /**
72 | * 依赖资源 ID 的数组
73 | */
74 | deps = this.deps[key] || [];
75 |
76 | /**
77 | * 依赖资源总数
78 | */
79 | count = deps.length;
80 |
81 | /**
82 | * 超时时间 ???
83 | */
84 | //timeout = 0;
85 |
86 | if (!callback) {
87 | callback = function () {
88 | }; //noop function
89 | }
90 |
91 | /**
92 | * 单个资源加载后的回调函数
93 | * 使用计数器生成
94 | */
95 | onload = counter(count, callback /*, timeout TODO 需要超时么??? */);
96 |
97 | /**
98 | * 加载所有依赖资源
99 | */
100 | each(deps, function (id) {
101 | var res = Resource.getResource(id);
102 | res.on("resolve", onload);
103 | res.load();
104 | });
105 | },
106 | /**
107 | * 调用事件
108 | *
109 | * @param {String} key 要调用的事件名
110 | * @param {callback} 事件调用后的处理函数
111 | */
112 | callHooks: function (key, callback) {
113 | var thisObj = this;
114 | this.loadDepends(key, function () {
115 | if (thisObj.emit(key)) {
116 | callback && callback();
117 | }
118 | });
119 | },
120 | /**
121 | * Pagelet 到达前端
122 | * @param {String} config Pagelet config
123 | * @fires Pagelet#beforeload
124 | */
125 | arrive: function (config) {
126 | copyProperties(this, {
127 | /**
128 | * HTML 内容
129 | * @member {String|Function}
130 | */
131 | html: config.html || null,
132 | /**
133 | * 事件依赖的资源ID
134 | * @member {Object}
135 | */
136 | deps: config.deps || {},
137 | /**
138 | * 父节点
139 | * @member {Paget}
140 | */
141 | parent: config.parent ? Pagelet.getPlagelet(config.parent) : null,
142 | /**
143 | * 子节点的ID
144 | * @member {Array}
145 | */
146 | children: config.children || [],
147 | renderMode: config.renderMode || null,
148 | /**
149 | * Pagelet state
150 | * @member {Number}
151 | */
152 | state: STAT_INITIALIZED
153 | })
154 | /**
155 | * 触发 beforeload 事件,如果没有被阻止,则执行加载
156 | */
157 | this.callHooks("beforeload", this.load);
158 | },
159 |
160 | /**
161 | * 加载 Pagelet
162 | */
163 | load: function () {
164 | /**
165 | * 加载渲染前依赖的资源(CSS等),派发 beforeload 事件
166 | * 如果没有被阻止,则开始渲染
167 | * 这里提前加载JS,可能会有坑,如果有依赖的js是没有包装define并且会操作DOM的话,在innerHTML之前加载可能导致DOM不存在
168 | */
169 | var me = this;
170 | if (me.state >= STAT_LOADING)
171 | return false;
172 | me.state = STAT_LOADING;
173 | this.callHooks("beforedisplay", function () {
174 | me.parent ? me.parent.on("display", me.display) : me.display();
175 | });
176 |
177 | this.loadDepends("load");
178 | },
179 |
180 | /**
181 | * 渲染节点
182 | */
183 | display: function () {
184 | /**
185 | * 获取html内容
186 | * 用innerHTML将html添加到文档中,最外层不需要
187 | */
188 | if (this.state >= STAT_DISPLAYED)
189 | return false;
190 | this.state = STAT_DISPLAYED;
191 | if (!this.root) {
192 | var dom = document.getElementById(this.id);
193 | // console.log(this);
194 | if(dom && this.html !== null) {
195 | dom.innerHTML = this.html;
196 | }
197 | if(dom && this.renderMode && this.renderMode !== dom.getAttribute('data-rm')) {
198 | dom.setAttribute('data-rm', this.renderMode);
199 | }
200 | this.done("display");
201 | }
202 | this.callHooks("load");
203 | },
204 |
205 | /**
206 | * 卸载pagelet
207 | */
208 | unload: function () {
209 |
210 | if (this.state == STAT_UNLOADING || this.state == STAT_UNINITIALIZED) return;
211 | // 如果是 STAT_INITIALIZED 或 STAT_LOADING
212 | if (this.state <= STAT_LOADING) {
213 | this.drop();
214 | }
215 |
216 | var children = this.children;
217 | var count = children.length;
218 | var onunload;
219 | var pagelet = this;
220 |
221 | /**
222 | * 每个子pagelet卸载之后的回调函数
223 | * 使用计数器生成
224 | */
225 | onunload = counter(count, callback);
226 | /**
227 | * 卸载所有子pagelet
228 | */
229 | each(children, function (child) {
230 | //var res = Resource.getResource(id);
231 | child = Pagelet.getPlagelet(child);
232 | child.on("unload", onunload);
233 | child.unload();
234 | });
235 |
236 | /**
237 | * 所有子pagelet卸载完成之后的回调函数
238 | */
239 | function callback() {
240 | pagelet.remove();
241 | //delete pageletSet[pagelet.id];
242 | pagelet.state = STAT_UNINITIALIZED;
243 | pagelet.callHooks("unload");
244 | }
245 | },
246 |
247 | /**
248 | * 对于STAT_INITIALIZED 或 STAT_LOADING 状态,放弃加载
249 | */
250 | drop: function () {
251 | },
252 | /**
253 | * 从pageletSet删除pagelet
254 | */
255 | remove: function () {
256 | if (pageletSet[this.id]) {
257 | delete pageletSet[this.id];
258 | }
259 | }
260 | });
261 |
262 | Pagelet.getPlagelet = function (id) {
263 | var pagelet;
264 | if (id === null) {
265 | return new Pagelet(id);
266 | }
267 | pagelet = pageletSet[id];
268 | if (!pagelet) {
269 | pagelet = new Pagelet(id);
270 | pageletSet[id] = pagelet;
271 | }
272 | return pagelet;
273 | };
274 |
275 | Pagelet.hasPlagelet = function (id) {
276 | return !!pageletSet[id];
277 | };
278 |
279 | module.exports = Pagelet;
280 |
281 | });
282 |
--------------------------------------------------------------------------------
/src/javascript/Requestor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Requestor 发起quickling请求,管理sessionid和pagelet缓存
3 | * @author zhangwentao(zhangwentao@baidu.com)
4 | */
5 |
6 | __d("Requestor", ["Controller"], function (global, require, module, exports) {
7 |
8 | var Controller = require('Controller');
9 |
10 | var Requestor = {
11 | sessions: {},
12 | /*
13 | cacheData : {
14 | url1 : {
15 | id1 : {}, // {child : id2}
16 | id2 : {}
17 | },
18 | url2 : {
19 | id1 : {}, // {child : id2}
20 | id2 : {}
21 | }
22 | }
23 | */
24 | cacheData: {},
25 | fetch: function (pagelets, url) {
26 | var cache,
27 | cached = [],
28 | nonCached = [],
29 | pagelet,
30 | i,
31 | length = pagelets.length,
32 | j,
33 | child;
34 |
35 | if (cache = this.cacheData[url]) {
36 | for (i = 0; i < length; i++) {
37 | pagelet = pagelets[i];
38 | if (!cache[pagelet]) {
39 | nonCached.push(pagelet);
40 | } else {
41 | //cached.push(cache[pagelet]);
42 | findChild(cache[pagelet]);
43 | }
44 | }
45 | } else {
46 | nonCached = pagelets;
47 | }
48 |
49 | this._fetch(nonCached, url, Controller.pageletArrive, cached);
50 |
51 | function findChild(pagelet) {
52 | var count;
53 | var childObj;
54 | count = pagelet.children && pagelet.children.length || 0;
55 |
56 | cached.push(pagelet);
57 |
58 | if (count) {
59 | for (j = 0; j < count; j++) {
60 | child = pagelet.children[j];
61 | childObj = cache[child];
62 |
63 | if (!childObj) {
64 | nonCached.push(child);
65 | } else {
66 | findChild(childObj);
67 | }
68 | }
69 | }
70 | }
71 | },
72 | _fetch: function (pagelets, url, callback, cached) {
73 | //var url;
74 | var cache;
75 |
76 | if (!this.cacheData[url]) {
77 | this.cacheData[url] = {};
78 | }
79 | cache = this.cacheData[url];
80 |
81 | if (!pagelets.length && cached.length) {
82 | parseData(cached);
83 | return;
84 | }
85 |
86 | for (var i = 0; i < pagelets.length; i++) {
87 | if (this.sessions[pagelets[i]] === undefined) {
88 | this.sessions[pagelets[i]] = 0;
89 | } else {
90 | this.sessions[pagelets[i]]++;
91 | }
92 | pagelets[i] += '.' + this.sessions[pagelets[i]];
93 | }
94 | url = url || '';
95 |
96 | if (url.indexOf('?') > -1) {
97 | url += '&__quickling__=' + pagelets.join(',');
98 | }
99 | else {
100 | url += '?__quickling__=' + pagelets.join(',');
101 | }
102 |
103 | ajax(url, function (text) {
104 | var data = JSON.parse(text);
105 |
106 | if (cached && cached.length) {
107 | data = data.concat(cached);
108 | }
109 | //console.log(data);
110 | parseData(data);
111 | });
112 |
113 | function parseData(data) {
114 | for (var i = 0; i < data.length; i++) {
115 | //BigPipe.onPageletArrive(data[i]);
116 | var conf = data[i];
117 | if (conf.html.html) {
118 | conf.html = conf.html.html;
119 | }
120 | //conf.html = conf.html.html;
121 | //if(!this.cacheData[url]) this.cacheData[url] = {};
122 | if (!cache[conf.id]) {
123 | cache[conf.id] = conf;
124 | }
125 | //if(conf.session >= me.sessions[conf.id]){
126 | callback && callback(data[i]);
127 | //}
128 | }
129 | }
130 | }
131 | };
132 |
133 | module.exports = Requestor;
134 | });
135 | /* __wrapped__ */
136 | /* @cmd false */
137 |
--------------------------------------------------------------------------------
/src/javascript/Resource.js:
--------------------------------------------------------------------------------
1 | __d("Resource", ["EventEmitter", "CSSLoader", "JSLoader"], function (global, require, module, exports) {
2 |
3 | var EventEmitter = require("EventEmitter");
4 | var CSSLoader = require('CSSLoader');
5 | var JSLoader = require('JSLoader');
6 |
7 | var _nameMap = {}, // mods到 资源id的映射
8 | _resourceMap = {}, // 页面资源原始数据
9 | _loadedResource = [],
10 | STAT_INITIALIZED = 1,
11 | STAT_LOADING = 2,
12 | STAT_LOADED = 3,
13 | STAT_RESOLVED = 4;
14 |
15 | var loader = {
16 | css: function (id, config) {
17 | return new CSSLoader(id, config);
18 | },
19 | js: function (id, config) {
20 | return new JSLoader(id, config);
21 | }
22 | };
23 |
24 |
25 | var EVENT_TYPES = [
26 | "load", // 资源加载完成时派发的事件
27 | "resolve" // 资源本身以及依赖资源全部加载完成时派发的事件
28 | //TODO 错误处理?
29 | ];
30 |
31 | /**
32 | * 加载资源
33 | */
34 | function loadResource() {
35 | var me = this,
36 | loader = Resource.getResourceLoader(me.id);
37 | loader.on('load', function () {
38 | me.loaded = true;
39 | me.state = STAT_LOADED;
40 | me.done('load');
41 | });
42 | loader.load();
43 |
44 | }
45 |
46 | /*
47 | // 解析依赖数组
48 | function parseArr(id, arr) {
49 | var dep, subDeps;
50 | if (hasOwnProperty(_resourceMap[id], 'deps') && _resourceMap[id].deps.length > 0) {
51 | subDeps = _resourceMap[id].deps;
52 | while (subDeps.length) {
53 | dep = subDeps.shift();
54 | if (inArray(arr, dep) > -1) {
55 | arr.splice(inArray(arr, dep), 1).push(dep);
56 | }
57 | arguments.callee(dep, arr);
58 | }
59 | }
60 | }
61 | */
62 |
63 | var Resource = derive(EventEmitter,
64 | /**
65 | * 构造一个资源对象,
66 | * 不能使用 new Resource() 方式调用,
67 | * 如果需要得到资源,请使用 Resource.getResource(id) 方法
68 | *
69 | * @constructor
70 | * @extends EventEmitter
71 | * @alias Resource
72 | *
73 | * @param {String} id 资源ID
74 | * @param {Array} [deps=[]] 依赖的资源ID
75 | * @param {Boolean} [parallel=true] 是否并行加载
76 | * @param {Array} [mods=[]] 资源包含的mod
77 | */
78 | function (__super, id, deps, parallel, mods) {
79 | __super(EVENT_TYPES);
80 | /**
81 | * 资源ID
82 | * @member {String}
83 | */
84 | this.id = id;
85 | /**
86 | * 依赖的资源ID
87 | * @member {Array}
88 | */
89 | this.deps = deps || [];
90 | /**
91 | * 资源包含的mod
92 | * @member {Array}
93 | */
94 | this.mods = mods || [];
95 | /**
96 | * 是否并行加载
97 | * 并行加载时资源本身和它的依赖资源同时加载
98 | * 串行加载是先加载资源的依赖,依赖reslove后再加载资源本身
99 | * @member {Boolean}
100 | */
101 | this.parallel = parallel === undefined ? true : !!parallel;
102 | /**
103 | * Resource状态
104 | * @member {Number}
105 | */
106 | this.state = STAT_INITIALIZED;
107 | },
108 | /**
109 | * @alias Resource.prototype
110 | */
111 | {
112 | /**
113 | * 开始加载资源
114 | * @fires Resource#load
115 | * @fires Resource#resolve
116 | */
117 | load: function () {
118 | // 实现资源加载
119 | var me = this,
120 | depRes,
121 | depId,
122 | onload,
123 | depsLen = me.deps.length;
124 | // 标记当前资源状态
125 | if (me.state >= STAT_LOADING) {
126 | return;
127 | }
128 | me.state = STAT_LOADING;
129 |
130 | if (me.parallel) {
131 | //并行
132 | onload = counter((me.mods.length === 0 ? depsLen : depsLen + 1), function () {
133 | me.state = STAT_RESOLVED;
134 | me.done('resolve');
135 | });
136 | } else {
137 | // 串行
138 | onload = counter(depsLen, function () {
139 | if (me.mods.length === 0 || me.loaded) {
140 | me.state = STAT_RESOLVED;
141 | me.done('resolve');
142 | return;
143 | }
144 | me.on("load", function () {
145 | me.state = STAT_RESOLVED;
146 | me.done('resolve');
147 | });
148 | loadResource.call(me);
149 | });
150 | }
151 |
152 | //加载依赖资源
153 | while (depsLen--) {
154 | depId = me.deps[depsLen];
155 | depRes = Resource.getResource(depId, me.parallel);
156 | depRes.on('resolve', onload);
157 | depRes.load();
158 | }
159 |
160 | //加载资源本身
161 | if (me.parallel && me.mods.length > 0) {
162 |
163 | if (me.loaded) {
164 | onload();
165 | } else {
166 | me.on("load", function () {
167 | onload();
168 | });
169 | loadResource.call(me);
170 | }
171 | }
172 | },
173 |
174 | unload: function () {
175 | // TODO 资源卸载?一期先不用
176 | }
177 | });
178 |
179 |
180 | /**
181 | * 设置资源映射表
182 | * 把资源拷贝到局部变量中???
183 | * @param {Object} map 资源映射表
184 | */
185 | Resource.setResourceMap = function (map) {
186 | if (type(map) === 'object') {
187 | //copyProperties(_resourceMap, map);
188 | for (var id in map) {
189 | if (!hasOwnProperty(_resourceMap, id)) {
190 | _resourceMap[id] = map[id];
191 | }
192 | }
193 | each(_resourceMap, function (value, key, obj) {
194 | each(value, function (v, k, o) {
195 | if (k === 'mods' && v.length > 0) {
196 | each(v, function (item, index, v) {
197 | _nameMap[item] = key;
198 | })
199 | }
200 | })
201 | })
202 | }
203 | };
204 |
205 | /**
206 | * 根据id得到资源
207 | *
208 | * @param {String} id 资源ID
209 | * @return {Resource} 资源对象
210 | * @throws 如果资源表中没有注册该ID则会报错
211 | *
212 | * @example
213 | * var res = Resource.getResource("XXX");
214 | * res.on("load", function(){
215 | * console.log("resource XXX loaded!");
216 | * });
217 | * res.load();
218 | */
219 | Resource.getResource = function (id, flag) {
220 | var res,
221 | item;
222 | if (hasOwnProperty(_resourceMap, id)) {
223 | item = _resourceMap[id];
224 | if (!(res = item._handler)) {
225 | res = item._handler = new Resource(id, _resourceMap[id].deps || [], flag, _resourceMap[id].mods || []);
226 | if (inArray(_loadedResource, id) > -1) {
227 | res.loaded = true;
228 | }
229 | }
230 | return res;
231 | } else {
232 | throw new Error("resource \"" + id + "\" unknow.");
233 | }
234 | };
235 |
236 | /**
237 | * 通过模块名得到资源实例
238 | *
239 | * @param {String} name 模块名
240 | * @return {Resource} 包含该模块的资源对象
241 | * @throws 如果资源表中没有注册该 name 则会报错
242 | */
243 | Resource.getResourceByName = function (name) {
244 | var resId;
245 | if (hasOwnProperty(_nameMap, name)) {
246 | resId = _nameMap[name];
247 | return Resource.getResource(resId);
248 | } else {
249 | throw new Error("resource \"" + name + "\" unknow.");
250 | }
251 | };
252 |
253 | /**
254 | * 通过ID得到资源加载器
255 | *
256 | * @param {String} id 资源id
257 | * @return {ResourceLoader} 资源加载器
258 | * @throws 如果资源id不存在,或者不能实例化对象则会报错
259 | */
260 | Resource.getResourceLoader = function (id) {
261 | var conf = _resourceMap[id];
262 | if (hasOwnProperty(_resourceMap[id], 'type')) {
263 | return loader[conf.type](id, conf);
264 | } else {
265 | throw new Error("resource \"" + id + "\" unknow.");
266 | }
267 | };
268 |
269 | /**
270 | * 添加已加载的资源
271 | *
272 | * @param {String | array} id 资源id
273 | */
274 | Resource.loadedResource = function (id) {
275 | if (isArray(id)) {
276 | each(id, function (item, index, arr) {
277 | if (inArray(_loadedResource, item) < 0) {
278 | _loadedResource.push(item);
279 | }
280 | })
281 | } else {
282 | if (typeof id === 'string' && inArray(_loadedResource, id) < 0) {
283 | _loadedResource.push(id);
284 | }
285 | }
286 | };
287 |
288 | module.exports = Resource;
289 |
290 | });
291 |
--------------------------------------------------------------------------------
/src/javascript/main.js:
--------------------------------------------------------------------------------
1 | __inline('runtimeAMD.js');
2 |
3 | (function(global, window, document, undefined) {
4 | __inline('util/util.js');
5 | __inline('BigPipe.js');
6 | __inline('Controller.js');
7 | __inline('CSSLoader.js');
8 | __inline('EventEmitter.js');
9 | __inline('JSLoader.js');
10 | __inline('Pagelet.js');
11 | __inline('Requestor.js');
12 | __inline('Resource.js');
13 |
14 | var BigPipe = require("BigPipe");
15 | if (hasOwnProperty(global, "BigPipe")) {
16 | BigPipe.origBigPipe = global.BigPipe;
17 | }
18 | global.BigPipe = BigPipe;
19 |
20 | })(this, window, document);
--------------------------------------------------------------------------------
/src/javascript/runtimeAMD.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 运行时AMD,提供define require require.defer require.async接口
3 | * @author zhangwentao(zhangwentao@baidu.com)
4 | */
5 |
6 | (function(global){
7 |
8 | var isReady = false,
9 | DOMContentLoaded,
10 | _lazy_modules = [];
11 |
12 | var _module_map = {};
13 |
14 | //function define(id, dependencies, factory) {
15 | //var _module_map = {};
16 |
17 | /**
18 | * 运行时 AMD define 实现
19 | *
20 | * @param {String} id 模块名
21 | * @param {Array} dependencies 依赖模块
22 | * @param {factory} factory 模块函数
23 | * @access public
24 | * @return void
25 | * @see require
26 | */
27 | var define = function(id, dependencies, factory) {
28 | _module_map[id] = {
29 | factory: factory,
30 | dependencies: dependencies
31 | };
32 | };
33 |
34 |
35 | define.amd = {
36 | jQuery : true
37 | };
38 | /**
39 | * 运行时 AMD require 实现
40 | *
41 | * @param {String} id 需要 require 的模块 id
42 | * @access public
43 | * @return {Object} 模块的exports
44 | * @see define
45 | */
46 | var require = function(id) {
47 | var module, exports, args, ret;
48 |
49 | if (!_module_map.hasOwnProperty(id)) {
50 | throw new Error('Requiring unknown module "' + id + '"');
51 | }
52 |
53 | module = _module_map[id];
54 | if (module.hasOwnProperty("exports")) {
55 | return module.exports;
56 | }
57 |
58 | module.exports = exports = {};
59 |
60 | args = buildArguments(module.dependencies, require, module, exports);
61 | ret = module.factory.apply(undefined, args);
62 |
63 | if(ret !== undefined){
64 | module.exports = ret;
65 | }
66 | return module.exports;
67 | };
68 |
69 | require.async = function(names, callback){
70 | var resource, modules;
71 |
72 | resource = getFakeResource(names);
73 |
74 | resource.on('resolve', function(){
75 | modules = getModules(names);
76 |
77 | callback && callback.apply(null, modules);
78 | });
79 |
80 | resource.load();
81 | };
82 |
83 | function getModules(names){
84 | var i,
85 | count = names.length,
86 | name,
87 | modules = [];
88 |
89 | for(i = 0; i < count; i++){
90 | name = names[i];
91 | modules.push(require(name));
92 | }
93 |
94 | return modules;
95 | }
96 |
97 | var __fakeId = 0;
98 | function getFakeResource(names){
99 | var count,
100 | i,
101 | name,
102 | deps = [],
103 | resource,
104 | map = {},
105 | id;
106 |
107 | count = names.length;
108 |
109 | for(i = 0; i < count; i++){
110 | name = names[i];
111 | deps.push(BigPipe.getResourceByName(name).id);
112 | }
113 |
114 | id = 'fake_res_' + __fakeId ++;
115 | map[id] = {
116 | deps : deps,
117 | type : 'js',
118 | mods : []
119 | };
120 |
121 | BigPipe.setResourceMap(map);
122 | resource = BigPipe.getResource(id);
123 |
124 | return resource;
125 | }
126 |
127 | require.defer = function(names, callback){
128 | if(isReady){
129 | require.async(names, callback);
130 | }else{
131 | _lazy_modules.push([names, callback]);
132 | }
133 | };
134 | /**
135 | * 根据 id 数组生成模块数组
136 | * 实现AMD规范
137 | *
138 | * @param {Array} deps 依赖的模块名列表
139 | * @param {Function} require require函数
140 | * @param {Object} module 模块
141 | * @param {Object} exports 模块的 exports 对象
142 | * @access public
143 | * @return {Array} 执行 require 后的模块数组
144 | */
145 | function buildArguments(deps, require, module, exports) {
146 | var index, count, did, args;
147 |
148 | args = [];
149 | count = deps.length;
150 | for (index = 0; index < count; index++) {
151 | did = deps[index];
152 | if (did === "global") {
153 | args.push(window);
154 | }else if (did === "require") {
155 | args.push(require);
156 | } else if (did === "module") {
157 | args.push(module);
158 | } else if (did === "exports") {
159 | args.push(exports);
160 | } else {
161 | args.push(require(did));
162 | }
163 | }
164 | return args;
165 | }
166 |
167 | function onready(){
168 | var i,
169 | len = _lazy_modules.length,
170 | mod,
171 | names,
172 | callback;
173 |
174 | if(len > 0){
175 | for(i = 0; i < len; i++){
176 | mod = _lazy_modules[i];
177 | names = mod[0];
178 | callback = mod[1];
179 | require.async(names, callback);
180 | }
181 | _lazy_modules = [];
182 | }
183 | }
184 |
185 | function ready() {
186 | if (!isReady) {
187 | isReady = true;
188 | onready();
189 | //requireLazy.onready && requireLazy.onready();
190 | //loadAllLazyModules();
191 | }
192 | }
193 |
194 | // Cleanup functions for the document ready method
195 | if (document.addEventListener) {
196 | DOMContentLoaded = function () {
197 | document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
198 | ready();
199 | };
200 |
201 | } else if (document.attachEvent) {
202 | DOMContentLoaded = function () {
203 | // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
204 | if (document.readyState === "complete") {
205 | document.detachEvent("onreadystatechange", DOMContentLoaded);
206 | ready();
207 | }
208 | };
209 | }
210 |
211 | function bindReady() {
212 | // Mozilla, Opera and webkit nightlies currently support this event
213 | if (document.addEventListener) {
214 | // Use the handy event callback
215 | document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
216 |
217 | // A fallback to window.onload, that will always work
218 | window.addEventListener("load", ready, false);
219 |
220 | // If IE event model is used
221 | } else if (document.attachEvent) {
222 | // ensure firing before onload,
223 | // maybe late but safe also for iframes
224 | document.attachEvent("onreadystatechange", DOMContentLoaded);
225 |
226 | // A fallback to window.onload, that will always work
227 | window.attachEvent("onload", ready);
228 | }
229 | }
230 |
231 | global.require = require;
232 | global.define = define;
233 |
234 | bindReady();
235 | //global.require = require;
236 | })(window);
237 |
238 |
--------------------------------------------------------------------------------
/src/javascript/util/JSON.js:
--------------------------------------------------------------------------------
1 | /**
2 | * File: JSON.js
3 | * Path: src/common/js
4 | * Author: zhangyuanwei
5 | * Modifier: zhangyuanwei
6 | * Modified: 2013-06-20 13:04:58
7 | * Description: JSON跨浏览器实现
8 | */
9 | /*
10 | json2.Bigpipe
11 | 2013-05-26
12 |
13 | Public Domain.
14 |
15 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
16 |
17 | See http://www.JSON.org/Bigpipe.html
18 |
19 |
20 | This code should be minified before deployment.
21 | See http://javascript.crockford.com/jsmin.html
22 |
23 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
24 | NOT CONTROL.
25 |
26 |
27 | This file creates a global JSON object containing two methods: stringify
28 | and parse.
29 |
30 | JSON.stringify(value, replacer, space)
31 | value any JavaScript value, usually an object or array.
32 |
33 | replacer an optional parameter that determines how object
34 | values are stringified for objects. It can be a
35 | function or an array of strings.
36 |
37 | space an optional parameter that specifies the indentation
38 | of nested structures. If it is omitted, the text will
39 | be packed without extra whitespace. If it is a number,
40 | it will specify the number of spaces to indent at each
41 | level. If it is a string (such as '\t' or ' '),
42 | it contains the characters used to indent at each level.
43 |
44 | This method produces a JSON text from a JavaScript value.
45 |
46 | When an object value is found, if the object contains a toJSON
47 | method, its toJSON method will be called and the result will be
48 | stringified. A toJSON method does not serialize: it returns the
49 | value represented by the name/value pair that should be serialized,
50 | or undefined if nothing should be serialized. The toJSON method
51 | will be passed the key associated with the value, and this will be
52 | bound to the value
53 |
54 | For example, this would serialize Dates as ISO strings.
55 |
56 | Date.prototype.toJSON = function (key) {
57 | function f(n) {
58 | // Format integers to have at least two digits.
59 | return n < 10 ? '0' + n : n;
60 | }
61 |
62 | return this.getUTCFullYear() + '-' +
63 | f(this.getUTCMonth() + 1) + '-' +
64 | f(this.getUTCDate()) + 'T' +
65 | f(this.getUTCHours()) + ':' +
66 | f(this.getUTCMinutes()) + ':' +
67 | f(this.getUTCSeconds()) + 'Z';
68 | };
69 |
70 | You can provide an optional replacer method. It will be passed the
71 | key and value of each member, with this bound to the containing
72 | object. The value that is returned from your method will be
73 | serialized. If your method returns undefined, then the member will
74 | be excluded from the serialization.
75 |
76 | If the replacer parameter is an array of strings, then it will be
77 | used to select the members to be serialized. It filters the results
78 | such that only members with keys listed in the replacer array are
79 | stringified.
80 |
81 | Values that do not have JSON representations, such as undefined or
82 | functions, will not be serialized. Such values in objects will be
83 | dropped; in arrays they will be replaced with null. You can use
84 | a replacer function to replace those with JSON values.
85 | JSON.stringify(undefined) returns undefined.
86 |
87 | The optional space parameter produces a stringification of the
88 | value that is filled with line breaks and indentation to make it
89 | easier to read.
90 |
91 | If the space parameter is a non-empty string, then that string will
92 | be used for indentation. If the space parameter is a number, then
93 | the indentation will be that many spaces.
94 |
95 | Example:
96 |
97 | text = JSON.stringify(['e', {pluribus: 'unum'}]);
98 | // text is '["e",{"pluribus":"unum"}]'
99 |
100 |
101 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
102 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
103 |
104 | text = JSON.stringify([new Date()], function (key, value) {
105 | return this[key] instanceof Date ?
106 | 'Date(' + this[key] + ')' : value;
107 | });
108 | // text is '["Date(---current time---)"]'
109 |
110 |
111 | JSON.parse(text, reviver)
112 | This method parses a JSON text to produce an object or array.
113 | It can throw a SyntaxError exception.
114 |
115 | The optional reviver parameter is a function that can filter and
116 | transform the results. It receives each of the keys and values,
117 | and its return value is used instead of the original value.
118 | If it returns what it received, then the structure is not modified.
119 | If it returns undefined then the member is deleted.
120 |
121 | Example:
122 |
123 | // Parse the text. Values that look like ISO date strings will
124 | // be converted to Date objects.
125 |
126 | myData = JSON.parse(text, function (key, value) {
127 | var a;
128 | if (typeof value === 'string') {
129 | a =
130 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
131 | if (a) {
132 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
133 | +a[5], +a[6]));
134 | }
135 | }
136 | return value;
137 | });
138 |
139 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
140 | var d;
141 | if (typeof value === 'string' &&
142 | value.slice(0, 5) === 'Date(' &&
143 | value.slice(-1) === ')') {
144 | d = new Date(value.slice(5, -1));
145 | if (d) {
146 | return d;
147 | }
148 | }
149 | return value;
150 | });
151 |
152 |
153 | This is a reference implementation. You are free to copy, modify, or
154 | redistribute.
155 | */
156 |
157 | /*jslint evil: true, regexp: true */
158 |
159 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
160 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
161 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
162 | lastIndex, length, parse, prototype, push, replace, slice, stringify,
163 | test, toJSON, toString, valueOf
164 | */
165 |
166 |
167 | // Create a JSON object only if one does not already exist. We create the
168 | // methods in a closure to avoid creating global variables.
169 |
170 | if (typeof JSON !== 'object') {
171 | JSON = {};
172 | }
173 |
174 | (function () {
175 | 'use strict';
176 |
177 | function f(n) {
178 | // Format integers to have at least two digits.
179 | return n < 10 ? '0' + n : n;
180 | }
181 |
182 | if (typeof Date.prototype.toJSON !== 'function') {
183 |
184 | Date.prototype.toJSON = function () {
185 |
186 | return isFinite(this.valueOf())
187 | ? this.getUTCFullYear() + '-' +
188 | f(this.getUTCMonth() + 1) + '-' +
189 | f(this.getUTCDate()) + 'T' +
190 | f(this.getUTCHours()) + ':' +
191 | f(this.getUTCMinutes()) + ':' +
192 | f(this.getUTCSeconds()) + 'Z'
193 | : null;
194 | };
195 |
196 | String.prototype.toJSON =
197 | Number.prototype.toJSON =
198 | Boolean.prototype.toJSON = function () {
199 | return this.valueOf();
200 | };
201 | }
202 |
203 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
204 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
205 | gap,
206 | indent,
207 | meta = { // table of character substitutions
208 | '\b': '\\b',
209 | '\t': '\\t',
210 | '\n': '\\n',
211 | '\f': '\\f',
212 | '\r': '\\r',
213 | '"': '\\"',
214 | '\\': '\\\\'
215 | },
216 | rep;
217 |
218 |
219 | function quote(string) {
220 |
221 | // If the string contains no control characters, no quote characters, and no
222 | // backslash characters, then we can safely slap some quotes around it.
223 | // Otherwise we must also replace the offending characters with safe escape
224 | // sequences.
225 |
226 | escapable.lastIndex = 0;
227 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
228 | var c = meta[a];
229 | return typeof c === 'string'
230 | ? c
231 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
232 | }) + '"' : '"' + string + '"';
233 | }
234 |
235 |
236 | function str(key, holder) {
237 |
238 | // Produce a string from holder[key].
239 |
240 | var i, // The loop counter.
241 | k, // The member key.
242 | v, // The member value.
243 | length,
244 | mind = gap,
245 | partial,
246 | value = holder[key];
247 |
248 | // If the value has a toJSON method, call it to obtain a replacement value.
249 |
250 | if (value && typeof value === 'object' &&
251 | typeof value.toJSON === 'function') {
252 | value = value.toJSON(key);
253 | }
254 |
255 | // If we were called with a replacer function, then call the replacer to
256 | // obtain a replacement value.
257 |
258 | if (typeof rep === 'function') {
259 | value = rep.call(holder, key, value);
260 | }
261 |
262 | // What happens next depends on the value's type.
263 |
264 | switch (typeof value) {
265 | case 'string':
266 | return quote(value);
267 |
268 | case 'number':
269 |
270 | // JSON numbers must be finite. Encode non-finite numbers as null.
271 |
272 | return isFinite(value) ? String(value) : 'null';
273 |
274 | case 'boolean':
275 | case 'null':
276 |
277 | // If the value is a boolean or null, convert it to a string. Note:
278 | // typeof null does not produce 'null'. The case is included here in
279 | // the remote chance that this gets fixed someday.
280 |
281 | return String(value);
282 |
283 | // If the type is 'object', we might be dealing with an object or an array or
284 | // null.
285 |
286 | case 'object':
287 |
288 | // Due to a specification blunder in ECMAScript, typeof null is 'object',
289 | // so watch out for that case.
290 |
291 | if (!value) {
292 | return 'null';
293 | }
294 |
295 | // Make an array to hold the partial results of stringifying this object value.
296 |
297 | gap += indent;
298 | partial = [];
299 |
300 | // Is the value an array?
301 |
302 | if (Object.prototype.toString.apply(value) === '[object Array]') {
303 |
304 | // The value is an array. Stringify every element. Use null as a placeholder
305 | // for non-JSON values.
306 |
307 | length = value.length;
308 | for (i = 0; i < length; i += 1) {
309 | partial[i] = str(i, value) || 'null';
310 | }
311 |
312 | // Join all of the elements together, separated with commas, and wrap them in
313 | // brackets.
314 |
315 | v = partial.length === 0
316 | ? '[]'
317 | : gap
318 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
319 | : '[' + partial.join(',') + ']';
320 | gap = mind;
321 | return v;
322 | }
323 |
324 | // If the replacer is an array, use it to select the members to be stringified.
325 |
326 | if (rep && typeof rep === 'object') {
327 | length = rep.length;
328 | for (i = 0; i < length; i += 1) {
329 | if (typeof rep[i] === 'string') {
330 | k = rep[i];
331 | v = str(k, value);
332 | if (v) {
333 | partial.push(quote(k) + (gap ? ': ' : ':') + v);
334 | }
335 | }
336 | }
337 | } else {
338 |
339 | // Otherwise, iterate through all of the keys in the object.
340 |
341 | for (k in value) {
342 | if (Object.prototype.hasOwnProperty.call(value, k)) {
343 | v = str(k, value);
344 | if (v) {
345 | partial.push(quote(k) + (gap ? ': ' : ':') + v);
346 | }
347 | }
348 | }
349 | }
350 |
351 | // Join all of the member texts together, separated with commas,
352 | // and wrap them in braces.
353 |
354 | v = partial.length === 0
355 | ? '{}'
356 | : gap
357 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
358 | : '{' + partial.join(',') + '}';
359 | gap = mind;
360 | return v;
361 | }
362 | }
363 |
364 | // If the JSON object does not yet have a stringify method, give it one.
365 |
366 | if (typeof JSON.stringify !== 'function') {
367 | JSON.stringify = function (value, replacer, space) {
368 |
369 | // The stringify method takes a value and an optional replacer, and an optional
370 | // space parameter, and returns a JSON text. The replacer can be a function
371 | // that can replace values, or an array of strings that will select the keys.
372 | // A default replacer method can be provided. Use of the space parameter can
373 | // produce text that is more easily readable.
374 |
375 | var i;
376 | gap = '';
377 | indent = '';
378 |
379 | // If the space parameter is a number, make an indent string containing that
380 | // many spaces.
381 |
382 | if (typeof space === 'number') {
383 | for (i = 0; i < space; i += 1) {
384 | indent += ' ';
385 | }
386 |
387 | // If the space parameter is a string, it will be used as the indent string.
388 |
389 | } else if (typeof space === 'string') {
390 | indent = space;
391 | }
392 |
393 | // If there is a replacer, it must be a function or an array.
394 | // Otherwise, throw an error.
395 |
396 | rep = replacer;
397 | if (replacer && typeof replacer !== 'function' &&
398 | (typeof replacer !== 'object' ||
399 | typeof replacer.length !== 'number')) {
400 | throw new Error('JSON.stringify');
401 | }
402 |
403 | // Make a fake root object containing our value under the key of ''.
404 | // Return the result of stringifying the value.
405 |
406 | return str('', {'': value});
407 | };
408 | }
409 |
410 |
411 | // If the JSON object does not yet have a parse method, give it one.
412 |
413 | if (typeof JSON.parse !== 'function') {
414 | JSON.parse = function (text, reviver) {
415 |
416 | // The parse method takes a text and an optional reviver function, and returns
417 | // a JavaScript value if the text is a valid JSON text.
418 |
419 | var j;
420 |
421 | function walk(holder, key) {
422 |
423 | // The walk method is used to recursively walk the resulting structure so
424 | // that modifications can be made.
425 |
426 | var k, v, value = holder[key];
427 | if (value && typeof value === 'object') {
428 | for (k in value) {
429 | if (Object.prototype.hasOwnProperty.call(value, k)) {
430 | v = walk(value, k);
431 | if (v !== undefined) {
432 | value[k] = v;
433 | } else {
434 | delete value[k];
435 | }
436 | }
437 | }
438 | }
439 | return reviver.call(holder, key, value);
440 | }
441 |
442 |
443 | // Parsing happens in four stages. In the first stage, we replace certain
444 | // Unicode characters with escape sequences. JavaScript handles many characters
445 | // incorrectly, either silently deleting them, or treating them as line endings.
446 |
447 | text = String(text);
448 | cx.lastIndex = 0;
449 | if (cx.test(text)) {
450 | text = text.replace(cx, function (a) {
451 | return '\\u' +
452 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
453 | });
454 | }
455 |
456 | // In the second stage, we run the text against regular expressions that look
457 | // for non-JSON patterns. We are especially concerned with '()' and 'new'
458 | // because they can cause invocation, and '=' because it can cause mutation.
459 | // But just to be safe, we want to reject all unexpected forms.
460 |
461 | // We split the second stage into 4 regexp operations in order to work around
462 | // crippling inefficiencies in IE's and Safari's regexp engines. First we
463 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
464 | // replace all simple value tokens with ']' characters. Third, we delete all
465 | // open brackets that follow a colon or comma or that begin the text. Finally,
466 | // we look to see that the remaining characters are only whitespace or ']' or
467 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
468 |
469 | if (/^[\],:{}\s]*$/
470 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
471 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
472 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
473 |
474 | // In the third stage we use the eval function to compile the text into a
475 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
476 | // in JavaScript: it can begin a block or an object literal. We wrap the text
477 | // in parens to eliminate the ambiguity.
478 |
479 | j = eval('(' + text + ')');
480 |
481 | // In the optional fourth stage, we recursively walk the new structure, passing
482 | // each name/value pair to a reviver function for possible transformation.
483 |
484 | return typeof reviver === 'function'
485 | ? walk({'': j}, '')
486 | : j;
487 | }
488 |
489 | // If the text is not JSON parseable, then a SyntaxError is thrown.
490 |
491 | throw new SyntaxError('JSON.parse');
492 | };
493 | }
494 | }());
495 |
--------------------------------------------------------------------------------
/src/javascript/util/ajax.js:
--------------------------------------------------------------------------------
1 | function ajax(url, cb, data) {
2 | var xhr = new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP");
3 |
4 | xhr.onreadystatechange = function () {
5 | if (xhr.readyState == 4) {
6 | cb(xhr.responseText);
7 | }
8 | };
9 | xhr.open(data ? 'POST' : 'GET', url + '&t=' + ~~(Math.random() * 1e6), true);
10 |
11 | if (data) {
12 | xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
13 | }
14 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
15 | xhr.send(data);
16 | }
17 |
--------------------------------------------------------------------------------
/src/javascript/util/amd.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 一个简单的 AMD define 实现
3 | *
4 | * @param {String} name 模块名
5 | * @param {Array} dependencies 依赖模块
6 | * @param {factory} factory 模块函数
7 | * @access public
8 | * @return void
9 | * @see require
10 | */
11 |
12 | var _module_map = {};
13 |
14 | function define(id, dependencies, factory) {
15 | _module_map[id] = {
16 | factory: factory,
17 | deps: dependencies
18 | };
19 | }
20 |
21 | function require(id) {
22 | var module = _module_map[id];
23 | if (!hasOwnProperty(_module_map, id))
24 | throw new Error('Requiring unknown module "' + id + '"');
25 | if (hasOwnProperty(module, "exports"))
26 | return module.exports;
27 |
28 | var exports;
29 | module.exports = exports = {};
30 |
31 | var args = buildArguments(module.deps, require, module, exports);
32 |
33 | module.factory.apply(undefined, args);
34 |
35 | return module.exports;
36 | }
37 |
38 | /**
39 | * 根据 id 数组生成模块数组
40 | * 实现AMD规范
41 | *
42 | * @param {Array} deps 依赖的模块名列表
43 | * @param {Function} require require函数
44 | * @param {Object} module 模块
45 | * @param {Object} exports 模块的 exports 对象
46 | * @access public
47 | * @return {Array} 执行 require 后的模块数组
48 | */
49 | function buildArguments(deps, require, module, exports) {
50 | var index, count, did, args;
51 |
52 | args = [];
53 | count = deps.length;
54 | for (index = 0; index < count; index++) {
55 | did = deps[index];
56 | if (did === "require") {
57 | args.push(require);
58 | } else if (did === "module") {
59 | args.push(module);
60 | } else if (did === "exports") {
61 | args.push(exports);
62 | } else {
63 | args.push(require(did));
64 | }
65 | }
66 | return args;
67 | }
68 |
69 | function __b(id, exports) {
70 | _module_map[id] = {
71 | exports: exports
72 | };
73 | }
74 |
75 | function __d(id, dependencies, factory) {
76 | return define(id, ['global', 'require', 'module', 'exports'].concat(dependencies), factory);
77 | }
78 |
79 | __b("global", global);
80 |
--------------------------------------------------------------------------------
/src/javascript/util/appendToHead.js:
--------------------------------------------------------------------------------
1 | function appendToHead(element) {
2 | var hardpoint,
3 | heads = document.getElementsByTagName('head');
4 | hardpoint = heads.length && heads[0] || document.body;
5 |
6 | appendToHead = function (element) {
7 | hardpoint.appendChild(element);
8 | };
9 | return appendToHead(element);
10 | }
11 |
--------------------------------------------------------------------------------
/src/javascript/util/bind.js:
--------------------------------------------------------------------------------
1 | /**
2 | * bind 方法会创建一个新函数,称为绑定函数.
3 | * 当调用这个绑定函数时,绑定函数会以创建它时传入bind方法的第二个参数作为this,
4 | * 传入bind方法的第三个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数.
5 | *
6 | * @param {Function} fn 摇绑定的函数
7 | * @param {Object} thisObj 调用函数时的 this 对象
8 | * @param {...*} [args] 执行函数时的参数
9 | * @access public
10 | * @return {Function} 绑定后的函数
11 | *
12 | * @example
13 | *
14 | * var obj = {
15 | * name: "it's me!"
16 | * };
17 | *
18 | * var fn = bind(function(){
19 | * console.log(this.name);
20 | * console.log(arguments);
21 | * }, obj, 1, 2, 3);
22 | *
23 | * fn(4, 5, 6);
24 | * // it's me
25 | * // [ 1, 2, 3, 4, 5, 6]
26 | */
27 | function bind(fn, thisObj /*, args... */) {
28 |
29 | function _bind(fn, thisObj /*, args... */) {
30 | var savedArgs, //保存绑定的参数
31 | savedArgLen, //绑定参数的个数
32 | ret; //返回函数
33 |
34 | // 判断是否有绑定的参数
35 | savedArgLen = arguments.length - 2;
36 | if (savedArgLen > 0) {
37 | //有绑定参数,需要拼接调用参数
38 | savedArgs = slice(arguments, 2);
39 | ret = function () {
40 | var args = toArray(arguments),
41 | index = savedArgLen;
42 | //循环将保存的参数移入调用参数
43 | //这里不使用 Array.prototype.concat 也是为了避免内存浪费
44 | while (index--) {
45 | args.unshift(savedArgs[index]);
46 | }
47 | return fn.apply(thisObj, args);
48 | };
49 | } else {
50 | // 没有绑定参数,直接调用,减少内存消耗
51 | ret = function () {
52 | return fn.apply(thisObj, arguments);
53 | };
54 | }
55 |
56 | return ret;
57 | }
58 |
59 | //保存原生的 bind 函数
60 | var native_bind = Function.prototype.bind;
61 | //修改 bind 函数指针
62 | if (native_bind) {
63 | //如果原生支持 Function.prototype.bind 则使用原生函数来实现
64 | bind = function (fn, thisObj /*, args... */) {
65 | return native_bind.apply(fn, slice(arguments, 1));
66 | };
67 | } else {
68 | bind = _bind;
69 | }
70 |
71 | return bind.apply(this, arguments);
72 | }
73 |
--------------------------------------------------------------------------------
/src/javascript/util/copyProperties.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 复制属性到对象
3 | *
4 | * @param {Object} to 目标对象
5 | * @param {...Object} from 多个参数
6 | * @access public
7 | * @return {Object} 目标对象
8 | *
9 | * @example
10 | * var objA = {
11 | * a : 1
12 | * },
13 | * objB = {
14 | * b : 2
15 | * };
16 | *
17 | * copyProperties(objA, objB, {
18 | * c : 3
19 | * });
20 | * console.log(objA);
21 | * // {
22 | * // a : 1,
23 | * // b : 2,
24 | * // c : 3
25 | * // }
26 | */
27 | function copyProperties(to /*, ...*/) {
28 | var index, count, item, key;
29 |
30 | to = to || {};
31 | count = arguments.length;
32 |
33 | //遍历参数列表
34 | for (index = 1; index < count; index++) {
35 | item = arguments[index];
36 | for (key in item) {
37 | //只复制自有属性
38 | if (hasOwnProperty(item, key)) {
39 | to[key] = item[key];
40 | }
41 | }
42 | }
43 |
44 | return to;
45 | }
46 |
--------------------------------------------------------------------------------
/src/javascript/util/counter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 计数器,返回一个用于计数调用的函数,每调用该函数一次,内部的计数器加一,直到达到 total 所指定次数,则调用 callback 函数。如果传递了 timeout 参数,则在 timeout 毫秒后如果还未达到指定次数,则调用 callback
3 | *
4 | * @param {Number} total 计数次数
5 | * @param {Function} callback 计数完成或超时后的回调函数
6 | * @param {Number} timeout 超时时间
7 | * @access public
8 | * @return {Function} 计数函数,每调用一次,内部的计数器就会加一
9 | *
10 | *
11 | * @example
12 | * var add = counter(4, function(){
13 | * console.log("add 4 times!");
14 | * });
15 | *
16 | * add();
17 | * add();
18 | * add();
19 | * add(); // add 4 times!
20 | */
21 |
22 | function counter(total, callback, timeout) {
23 | var running = true, //是否正在计数
24 | count = 0, //当前计数值
25 | timeoutId = null; //超时标记
26 |
27 | // 计数完成或者超时后的回调函数
28 | function done() {
29 | if (!running) {
30 | return;
31 | }
32 |
33 | running = false;
34 | if (timeoutId !== null) {
35 | clearTimeout(timeoutId);
36 | }
37 |
38 | //TODO 参数?错误处理?
39 | callback();
40 | }
41 |
42 | //将 total 值转换为整数
43 | total = total | 0;
44 |
45 | //如果目标计数值小于0,则直接调用done
46 | if (total <= 0) {
47 | done();
48 | } else if (timeout !== undefined) {
49 | timeoutId = setTimeout(done, timeout);
50 | }
51 |
52 | //返回计数器触发函数
53 | //该函数每执行一次,计数器加一
54 | //直到到达设定的 total 值或者超时
55 | return function () {
56 | if (running && ++count >= total) {
57 | done();
58 | }
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/src/javascript/util/derive.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Js 派生实现
3 | *
4 | * @param {Function} parent 父类
5 | * @param {Function} [constructor] 子类构造函数
6 | * @param {Object} [proto] 子类原型
7 | * @access public
8 | * @return {Function} 新的类
9 | *
10 | * @example
11 | *
12 | * var ClassA = derive(Object, function(__super){
13 | * console.log("I'm an instance of ClassA:", this instanceof ClassA);
14 | * });
15 | *
16 | * var ClassB = derive(ClassA, function(__super){
17 | * console.log("I'm an instance of ClassB:", this instanceof ClassB);
18 | * __super();
19 | * }, {
20 | * test:function(){
21 | * console.log("test method!");
22 | * }
23 | * });
24 | *
25 | * var b = new ClassB();
26 | * //I'm an instance of ClassA: true
27 | * //I'm an instance of ClassA: true
28 | * b.test();
29 | * //test method!
30 | */
31 | function derive(parent, constructor, proto) {
32 |
33 | //如果没有传 constructor 参数
34 | if (typeof constructor === 'object') {
35 | proto = constructor;
36 | constructor = proto.constructor || function () {
37 | };
38 | delete proto.constructor;
39 | }
40 |
41 | var tmp = function () {
42 | },
43 | //子类构造函数
44 | subClass = function () {
45 | //有可能子类和父类初始化参数定义不同,所以将初始化延迟到子类构造函数中执行
46 | //构造一个 __super 函数,用于子类中调用父类构造函数
47 | var __super = bind(parent, this),
48 | args = slice(arguments);
49 |
50 | //将 __super 函数作为 constructor 的第一个参数
51 | args.unshift(__super);
52 | constructor.apply(this, args);
53 |
54 | //parent.apply(this, arguments);
55 | //constructor.apply(this, arguments);
56 | },
57 | subClassPrototype;
58 |
59 | //原型链桥接
60 | tmp.prototype = parent.prototype;
61 | subClassPrototype = new tmp();
62 |
63 | //复制属性到子类的原型链上
64 | copyProperties(
65 | subClassPrototype,
66 | constructor.prototype,
67 | proto || {});
68 |
69 | subClassPrototype.constructor = constructor.prototype.constructor;
70 | subClass.prototype = subClassPrototype;
71 | return subClass;
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/src/javascript/util/each.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 遍历数组或对象
3 | *
4 | * @param {Object|Array} object 数组或对象
5 | * @param {Function} callback 回调函数
6 | * @param {Object} [thisObj=undefined] 回调函数的this对象
7 | * @access public
8 | * @return {Object|Array} 被遍历的对象
9 | *
10 | * @example
11 | *
12 | * var arr = [1, 2, 3, 4];
13 | * each(arr, function(item, index, arr){
14 | * console.log(index + ":" + item);
15 | * });
16 | * // 0:1
17 | * // 1:2
18 | * // 2:3
19 | * // 3:4
20 | *
21 | * @example
22 | *
23 | * var arr = [1, 2, 3, 4];
24 | * each(arr, function(item, index, arr){
25 | * console.log(index + ":" + item);
26 | * if(item > 2){
27 | * return false;
28 | * }
29 | * });
30 | * // 0:1
31 | * // 1:2
32 | */
33 | function each(object, callback, thisObj) {
34 | var name, i = 0,
35 | length = object.length,
36 | isObj = length === undefined || isFunction(object);
37 |
38 | if (isObj) {
39 | for (name in object) {
40 | if (callback.call(thisObj, object[name], name, object) === false) {
41 | break;
42 | }
43 | }
44 | } else {
45 | for (i = 0; i < length; i++) {
46 | if (callback.call(thisObj, object[i], i, object) === false) {
47 | break;
48 | }
49 | }
50 | }
51 | return object;
52 | }
53 |
--------------------------------------------------------------------------------
/src/javascript/util/hasOwnProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * hasOwnProperty
3 | *
4 | * @param obj $obj
5 | * @param key $key
6 | * @access public
7 | * @return void
8 | */
9 | function hasOwnProperty(obj, key) {
10 |
11 | var native_hasOwnProperty = Object.prototype.hasOwnProperty;
12 |
13 | hasOwnProperty = function(obj, key) {
14 | return native_hasOwnProperty.call(obj, key);
15 | };
16 |
17 | return hasOwnProperty(obj, key);
18 | }
19 |
--------------------------------------------------------------------------------
/src/javascript/util/inArray.js:
--------------------------------------------------------------------------------
1 | /**
2 | * inArray 判断对象是否为数组
3 | *
4 | * @param {array} arr 数据源
5 | * @param {item} item 需要判断的数据项
6 | * @access public
7 | * @return {index} 该数据项在目标数组中的索引
8 | */
9 | function inArray(arr, item) {
10 | //如果支持 Array.prototype.indexOf 方法则直接使用,
11 | var index = undefined;
12 | if (Array.prototype.indexOf) {
13 | return arr.indexOf(item) > -1 ? arr.indexOf(item) : -1;
14 | } else {
15 | each(arr, function (v, i, arr) {
16 | if (v === item) {
17 | index = i;
18 | return false;
19 | }
20 | });
21 | }
22 | return index === undefined ? -1 : index;
23 | }
24 |
--------------------------------------------------------------------------------
/src/javascript/util/isArray.js:
--------------------------------------------------------------------------------
1 | /**
2 | * isArray 判断对象是否为数组
3 | *
4 | * @param {Object} obj 需要被判断的对象
5 | * @access public
6 | * @return {Boolean} 该对象是否为 Array
7 | */
8 | function isArray(obj) {
9 | //重设 isArray 函数指针,方便下次调用
10 | //如果支持 Array.isArray 方法则直接使用,
11 | //否则用 type 函数来判断
12 | isArray = Array.isArray || function (obj) {
13 | return type(obj) === 'array';
14 | };
15 | return isArray(obj);
16 | }
17 |
--------------------------------------------------------------------------------
/src/javascript/util/isEmpty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * isEmpty 判断是否是空对象,数组判断长度,对象判断自定义属性,其它取非
3 | *
4 | * @param {Object} obj 需要被判断的对象
5 | * @access public
6 | * @return {Boolean} 该对象是否为为空
7 | */
8 | function isEmpty(obj) {
9 | if (isArray(obj)) {
10 | return obj.length === 0;
11 | } else if (typeof obj === 'object') {
12 | for (var i in obj) return false;
13 | return true;
14 | } else return !obj;
15 | }
16 |
--------------------------------------------------------------------------------
/src/javascript/util/isFunction.js:
--------------------------------------------------------------------------------
1 | /**
2 | * isFunction 判断一个对象是否为一个函数
3 | *
4 | * @param {Object} obj 需要被鉴定的对象
5 | * @access public
6 | * @return {Boolean} 该对象是否为一个函数
7 | */
8 | function isFunction(obj) {
9 | return type(obj) === 'function';
10 | }
11 |
--------------------------------------------------------------------------------
/src/javascript/util/nextTick.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 将函数延迟执行
3 | *
4 | * @param {Function} fn 希望被延迟执行的函数
5 | * @access public
6 | * @return {Number} 等待执行的任务数
7 | *
8 | * @example
9 | *
10 | * var fn = function(){
11 | * console.log(2);
12 | * };
13 | *
14 | * nextTick(fn);
15 | * console.log(1);
16 | *
17 | * // 1
18 | * // 2
19 | */
20 | function nextTick(fn) {
21 |
22 | var callbacks = [], //等待调用的函数栈
23 | running = false; //当前是否正在运行中
24 |
25 | //调用所有在函数栈中的函数
26 | //如果在执行某函数时又有新的函数被添加进来,
27 | //该函数也会在本次调用的最后被执行
28 | function callAllCallbacks() {
29 | var count, index;
30 |
31 | count = callbacks.length;
32 | for (index = 0; index < count; index++) {
33 | //TODO 错误处理
34 | callbacks[index]();
35 | }
36 | //删除已经调用过的函数
37 | callbacks.splice(0, count);
38 |
39 | //判断是否还有函数需要执行
40 | //函数可能在 callAllCallbacks 调用的过程中被添加到 callbacks 数组
41 | //所以需要再次判断
42 | if (callbacks.length) {
43 | setTimeout(callAllCallbacks, 1);
44 | } else {
45 | running = false;
46 | }
47 | }
48 |
49 | //修改 nextTick 函数指针,方便下次调用
50 | nextTick = function (fn) {
51 | //将函数存放到待调用栈中
52 | callbacks.push(fn);
53 |
54 | //判断定时器是否启动
55 | //如果没有启动,则启动计时器
56 | //如果已经启动,则不需要做什么
57 | //本次添加的函数会在 callAllCallbacks 时被调用
58 | if (!running) {
59 | running = true;
60 | setTimeout(callAllCallbacks, 1);
61 | }
62 | return callbacks.length;
63 | };
64 |
65 | return nextTick.apply(this, arguments);
66 | }
67 |
--------------------------------------------------------------------------------
/src/javascript/util/queueCall.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 按照顺序调用函数,避免调用堆栈过长
3 | *
4 | * @param {Function} fn 需要调用的函数
5 | * @access public
6 | * @return {Number} 当前的队列大小,返回0表示函数已经被立即执行
7 | *
8 | * @example
9 | *
10 | * function fn1(){
11 | * console.log("fn1 start");
12 | * queueCall(fn2);
13 | * console.log("fn1 end");
14 | * }
15 | *
16 | * function fn2(){
17 | * console.log("fn2 start");
18 | * queueCall(fn3);
19 | * console.log("fn2 end");
20 | * }
21 | * function fn3(){
22 | * console.log("fn3 start");
23 | * console.log("fn3 end");
24 | * }
25 | *
26 | * queueCall(fn1);
27 | * //fn1 start
28 | * //fn1 end
29 | * //fn2 start
30 | * //fn2 end
31 | * //fn3 start
32 | * //fn3 end
33 | */
34 | function queueCall(fn) {
35 | var running = false,
36 | list = [];
37 |
38 | queueCall = function (fn) {
39 | var count, index;
40 | list.push(fn);
41 | if (!running) {
42 | running = true;
43 | while (true) {
44 | count = list.length;
45 | if (count <= 0) {
46 | break;
47 | }
48 | for (index = 0; index < count; index++) {
49 | //TODO 错误处理
50 | list[index]();
51 | }
52 | list.splice(0, count);
53 | }
54 | running = false;
55 | }
56 |
57 | return list.length;
58 | };
59 |
60 | return queueCall(fn);
61 | }
62 |
--------------------------------------------------------------------------------
/src/javascript/util/slice.js:
--------------------------------------------------------------------------------
1 | /**
2 | * slice 把数组中一部分的浅复制存入一个新的数组对象中,并返回这个新的数组。
3 | *
4 | * @param {Array} array 数组
5 | * @param {Number} start 开始索引
6 | * @param {Number} end 结束索引
7 | * @access public
8 | * @return {Array} 被截取后的数组
9 | */
10 | function slice(array, start, end) {
11 | var _slice = Array.prototype.slice;
12 | //重写 slice 函数指针,便于下次调用.
13 | slice = function (array, start, end) {
14 | switch (arguments.length) {
15 | case 0:
16 | //TODO throw Error???
17 | return [];
18 | case 1:
19 | return _slice.call(array);
20 | case 2:
21 | return _slice.call(array, start);
22 | case 3:
23 | default:
24 | return _slice.call(array, start, end);
25 | }
26 | };
27 | return slice.apply(this, arguments);
28 | }
29 |
--------------------------------------------------------------------------------
/src/javascript/util/toArray.js:
--------------------------------------------------------------------------------
1 | /**
2 | * toArray 将类数组对象转化为数组
3 | *
4 | * @param {Object} obj 需要被转化的类数组对象
5 | * @access public
6 | * @return {Array} 数组对象
7 | */
8 | function toArray(obj) {
9 | return slice(obj);
10 | }
11 |
--------------------------------------------------------------------------------
/src/javascript/util/type.js:
--------------------------------------------------------------------------------
1 | /**
2 | * type 判断对象类型函数
3 | * 从 jquery 中拷贝来的
4 | *
5 | * @param {Object} obj 被鉴定的对象
6 | * @access public
7 | * @return {String} 对象类型字符串
8 | */
9 | function type(obj) {
10 | //var types = "Boolean Number String Function Array Date RegExp Object".split(" "),
11 | var types = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object"],
12 | toString = Object.prototype.toString,
13 | class2type = {},
14 | count, name;
15 |
16 | //构造 class2type 表
17 | //{
18 | // "[object Object]" : "object",
19 | // "[object RegExp]" : "regexp",
20 | // "[object Date]" : "date",
21 | // "[object Array]" : "array",
22 | // "[object Function]" : "function",
23 | // "[object String]" : "string",
24 | // "[object Number]" : "number",
25 | // "[object Boolean]" : "boolean"
26 | //}
27 | count = types.length;
28 | while (count--) {
29 | name = types[count];
30 | class2type["[object " + name + "]"] = name.toLowerCase();
31 | }
32 |
33 | // 判断函数,初始化后再次调用会直接调用这个函数
34 | function _type(obj) {
35 | return obj == null ?
36 | String(obj) :
37 | class2type[toString.call(obj)] || "object";
38 | }
39 |
40 | //修改 type 函数指针,以便多次调用开销
41 | type = _type;
42 |
43 | return type(obj);
44 | }
45 |
--------------------------------------------------------------------------------
/src/javascript/util/util.js:
--------------------------------------------------------------------------------
1 | __inline('ajax.js');
2 | __inline('amd.js');
3 | __inline('appendToHead.js');
4 | __inline('bind.js');
5 | __inline('copyProperties.js');
6 | __inline('counter.js');
7 | __inline('derive.js');
8 | __inline('each.js');
9 | __inline('hasOwnProperty.js');
10 | __inline('inArray.js');
11 | __inline('isArray.js');
12 | __inline('isEmpty.js');
13 | __inline('isFunction.js');
14 | __inline('JSON.js');
15 | __inline('nextTick.js');
16 | __inline('queueCall.js');
17 | __inline('slice.js');
18 | __inline('toArray.js');
19 | __inline('type.js');
20 |
--------------------------------------------------------------------------------
/src/js_helper/bigRender.js:
--------------------------------------------------------------------------------
1 | /*
2 | * File: bigRender.js
3 | * Path: commmon/js/bigRender.js
4 | * Author: HuangYi
5 | * Modifier: HuangYi
6 | * Modified: 2013-7-16
7 | * Description: 延迟渲染js
8 | */
9 | var lazy = require("./lazy.js");
10 |
11 | function add(pagelet) {
12 |
13 | var lazyKey, ele, cssText;
14 | try {
15 | // if(pagelet.parent){
16 | // pagelet.parent.on("load",function(){
17 | // bindBigRender(pagelet);
18 | // });
19 | // }else{
20 | bindBigRender(pagelet);
21 | //}
22 | return true;
23 | } catch (e) {
24 | setTimeout(function() {
25 | throw e;
26 | }, 0);
27 | return false;
28 | }
29 | }
30 |
31 | function bindBigRender(pagelet) {
32 | var ele = document.getElementById(pagelet.id);
33 | addClass(ele, "g-bigrender");
34 |
35 | var lazyKey = lazy.add(ele, function() {
36 |
37 | pagelet.on("load", function() {
38 | removeClass(ele, "g-bigrender");
39 | });
40 | pagelet.load();
41 |
42 | });
43 |
44 | pagelet.on("unload", function() {
45 | lazy.remove(lazyKey);
46 | });
47 | }
48 |
49 | function addClass(element, className) {
50 | if (!element)
51 | return;
52 | var elementClassName = element.className;
53 | if (elementClassName.length == 0) {
54 | element.className = className;
55 | return;
56 | }
57 | if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
58 | return;
59 | element.className = elementClassName + " " + className;
60 | }
61 |
62 | function removeClass(element, className) {
63 | if (!element)
64 | return;
65 | var elementClassName = element.className;
66 | if (elementClassName.length == 0)
67 | return;
68 | if (elementClassName == className) {
69 | element.className = "";
70 | return;
71 | }
72 | if (elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
73 | element.className = elementClassName.replace((new RegExp("(^|\\s)" + className + "(\\s|$)")), " ");
74 | }
75 |
76 |
77 | module.exports = {
78 | add: add
79 | };
80 |
--------------------------------------------------------------------------------
/src/js_helper/lazy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 一个简单的lazy模块实现,基于getBoundingClientRect
3 | * 提供add和remove方法
4 | */
5 |
6 | function getViewportSize() {
7 | if (window.innerHeight != null) {
8 | return {
9 | width: window.innerWidth,
10 | height: window.innerHeight
11 | };
12 | }
13 | if (document.compatMode == 'CSS1Compat') { //标准模式
14 | return {
15 | width: document.documentElement.clientWidth,
16 | height: document.documentElement.clientHeight
17 | };
18 | }
19 | return {
20 | width: document.body.clientWidth,
21 | height: document.body.clientHeight
22 | };
23 | }
24 |
25 | function getElementOffset(element) {
26 | return {
27 | left: element.getBoundingClientRect().left,
28 | top: element.getBoundingClientRect().top
29 | };
30 | }
31 |
32 | function addHandler(element, type, handler) {
33 | if (element.addEventListener) {
34 | element.addEventListener(type, handler, false);
35 | } else if (element.attachEvent) {
36 | element.attachEvent("on" + type, handler);
37 | } else {
38 | element["on" + type] = handler;
39 | }
40 | }
41 |
42 | var data = {};
43 | var uniqueId = 0;
44 | var offsetBuffer = 50;
45 |
46 | function bind() {
47 | addHandler(window, 'resize', excute);
48 | addHandler(window, 'scroll', excute);
49 | }
50 |
51 | function excute() {
52 | var viewport = getViewportSize();
53 |
54 | for (var i in data) {
55 | var item = data[i];
56 | var ele = item[0];
57 | var callback = item[1];
58 | if (!ele || !ele.getBoundingClientRect) {
59 | return;
60 | }
61 |
62 | if (getElementOffset(ele).top < viewport.height + offsetBuffer) {
63 | callback && callback();
64 | remove(i);
65 | }
66 | }
67 | }
68 |
69 | function add(element, callback) {
70 | data[uniqueId++] = [element, callback];
71 | }
72 |
73 | function remove(id) {
74 | try {
75 | delete data[id];
76 | return true;
77 | } catch (e) {
78 | //return false;
79 | throw new Error('Remove unknow lazy element by id:' + id);
80 | }
81 | }
82 |
83 | bind();
84 |
85 | module.exports = {
86 | add: add,
87 | remove: remove
88 | };
89 |
--------------------------------------------------------------------------------
/src/js_helper/pageEmulator.js:
--------------------------------------------------------------------------------
1 | var cacheMaxTime = 0, // 缓存时间
2 | appOptions = {}, // app页面管理的options
3 | layer; // 事件代理层
4 |
5 | var EventUtil = {
6 | addHandler: function(element, type, handler, flag) {
7 | if (element.addEventListener) {
8 | element.addEventListener(type, handler, flag || false);
9 | } else if (element.attachEvent) {
10 | element.attachEvent("on" + type, handler);
11 | } else {
12 | element["on" + type] = handler;
13 | }
14 | },
15 | getEvent: function(event) {
16 | return event ? event : window.event;
17 | },
18 | getTarget: function(event) {
19 | //return event.tagert || event.srcElement;
20 | return event.target || event.srcElement;
21 | },
22 | preventDefault: function(event) {
23 | if (event.preventDefault) {
24 | event.preventDefault();
25 | } else {
26 | event.returnValue = false;
27 | }
28 | },
29 | stopPropagation: function(event) {
30 | if (event.stopPropagation) {
31 | event.stopPropagation();
32 | } else {
33 | event.cancelBubble = true;
34 | }
35 | },
36 | removeHandler: function(element, type, handler) {
37 | if (element.removeEventListener) {
38 | element.removeEventListener(type, handler, false);
39 | } else if (element.detachEvent) {
40 | element.detachEvent("on" + type, handler);
41 | } else {
42 | element["on" + type] = null;
43 | }
44 | }
45 | };
46 |
47 |
48 | /**
49 | * 启动页面管理
50 | * @param {Object} options 初始化参数
51 | * @param {String} options["selector"] 全局代理元素的选择器匹配,写法同 document.querySeletor 函数
52 | * @param {Number} options["cacheMaxTime"] 页面缓存时间
53 | * @param {Function|RegExp} options["validate"] url验证方法,
54 | * @return {void}
55 | */
56 |
57 | function start(options) {
58 |
59 | /**
60 | * 默认参数 {
61 | * selector : // 代理元素的选择器规则
62 | * cacheMaxTime: //缓存存活时间,默认5min
63 | * }
64 | */
65 | var defaultOptions = {
66 | cacheMaxTime: 5 * 60 * 1000,
67 | layer: document
68 | };
69 |
70 | appOptions = merge(defaultOptions, options);
71 | cacheMaxTime = appOptions.cacheMaxTime;
72 | layer = getLayer(appOptions.layer);
73 | bindEvent();
74 | }
75 |
76 | /**
77 | * 事件绑定
78 | * @return {void}
79 | */
80 |
81 | function bindEvent() {
82 | EventUtil.addHandler(layer, 'click', proxy, true);
83 | }
84 |
85 | function getLayer(ele) {
86 | if (typeof ele === "string") {
87 | return document.querySelector(ele);
88 | } else if (ele && ele.nodeType) {
89 | return ele;
90 | } else {
91 | return document.body
92 | }
93 | }
94 |
95 | /**
96 | * 简单merge两个对象
97 | * @param {object} _old
98 | * @param {object} _new
99 | * @returns {*}
100 | */
101 |
102 | function merge(_old, _new) {
103 | for (var i in _new) {
104 | if (_new.hasOwnProperty(i) && _new[i] !== null) {
105 | _old[i] = _new[i];
106 | }
107 | }
108 | return _old;
109 | }
110 |
111 | /**
112 | * 事件代理
113 | * @param {MouseEvent} 点击事件对象
114 | */
115 |
116 | function proxy(e) {
117 | var element = EventUtil.getTarget(e),
118 | parent = element,
119 | selector = appOptions.selector;
120 |
121 | var url, urlAttr, pagelets;
122 |
123 | while (parent !== layer) {
124 |
125 | urlAttr = parent.tagName.toLowerCase() === "a" ? "href" : "data-href";
126 | url = parent.getAttribute(urlAttr);
127 | pagelets = parent.getAttribute("data-pagelets");
128 |
129 | if (url && pagelets) {
130 | pagelets = pagelets.split(',');
131 |
132 | if (pagelets.length) {
133 |
134 | EventUtil.stopPropagation(e);
135 | EventUtil.preventDefault(e);
136 |
137 | BigPipe.fetch(pagelets, url);
138 | }
139 | break;
140 | } else {
141 | parent = parent.parentNode;
142 | }
143 | }
144 | }
145 |
146 | module.exports = {
147 | start: start
148 | };
149 |
--------------------------------------------------------------------------------
/src/plugin/block.script.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * @param array $params
8 | * @param string $content
9 | * @param Smarty $smarty
10 | * @param bool $repeat
11 | * @see BigPipe::currentContext
12 | * @see PageletContext->addRequire
13 | * @see PageletContext->addRequireAsync
14 | * @see PageletContext->addHook
15 | */
16 | function smarty_block_script($params, $content, $smarty, &$repeat)
17 | {
18 | if (!$repeat && isset($content)) {
19 | $eventType = isset($params['on']) ? $params['on'] : "load";
20 | $strict = (isset($params['strict']) && $params['strict'] == false) ? false : true;
21 | $context = BigPipe::currentContext();
22 |
23 | if (isset($params["sync"])) {
24 | foreach ($params["sync"] as $resource) {
25 | BigPipeResource::registModule($resource);
26 | }
27 | $context->addRequire($eventType, $params["sync"]);
28 | }
29 |
30 | if (isset($params["async"])) {
31 | foreach ($params["async"] as $resource) {
32 | BigPipeResource::registModule($resource);
33 | }
34 | $context->addRequireAsync($eventType, $params["async"]);
35 | }
36 |
37 | $context->addHook($eventType, $content, $strict);
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/src/plugin/compiler.body.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | */
13 | function smarty_compiler_body($params, $smarty){
14 | return
15 | '';
19 | }
20 |
21 | /**
22 | * smarty 编译插件 bodyclose
23 | *
24 | * 处理 {/body} 标签
25 | *
26 | * @param array $params
27 | * @param Smarty $smarty
28 | * @access public
29 | * @return string 编译后的php代码
30 | */
31 | function smarty_compiler_bodyclose($params, $smarty){
32 | return
33 | '';
37 | }
38 |
--------------------------------------------------------------------------------
/src/plugin/compiler.head.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | */
13 | function smarty_compiler_head($params, $smarty){
14 | return
15 | '';
19 | }
20 |
21 | /**
22 | * smarty 编译插件 headclose
23 | *
24 | * 处理 {/head} 标签
25 | *
26 | * @param array $params
27 | * @param Smarty $smarty
28 | * @access public
29 | * @return string 编译后的php代码
30 | */
31 | function smarty_compiler_headclose($params, $smarty){
32 | return
33 | '';
37 | }
38 |
--------------------------------------------------------------------------------
/src/plugin/compiler.html.php:
--------------------------------------------------------------------------------
1 | smarty)){'.
23 | 'do{'.
24 | 'if(' . BigPipe::compileOpenTag(BigPipe::TAG_HTML, $params) . '){'.
25 | '?>';
26 | }
27 |
28 | /**
29 | * smarty 编译插件 htmlclose
30 | *
31 | * 处理 {/html} 标签
32 | *
33 | * @param array $params
34 | * @param Smarty $smarty
35 | * @access public
36 | * @return string 编译后的php代码
37 | */
38 | function smarty_compiler_htmlclose($params, $smarty){
39 | return
40 | '';
46 | }
47 |
--------------------------------------------------------------------------------
/src/plugin/compiler.pagelet.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | * @see BigPipe::compileOpenTag
13 | */
14 | function smarty_compiler_pagelet($params, $smarty){
15 | return
16 | '';
20 | }
21 |
22 | /**
23 | * smarty 编译插件 pageletclose
24 | *
25 | * 处理 {/pagelet} 标签
26 | *
27 | * @param array $params
28 | * @param Smarty $smarty
29 | * @access public
30 | * @return string 编译后的php代码
31 | * @see BigPipe::compileCloseTag
32 | */
33 | function smarty_compiler_pageletclose($params, $smarty){
34 | return
35 | '';
39 | }
40 |
--------------------------------------------------------------------------------
/src/plugin/compiler.title.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @param array $params
9 | * @param Smarty $smarty
10 | * @access public
11 | * @return string 编译后的php代码
12 | * @see BigPipe::compileOpenTag
13 | */
14 | function smarty_compiler_title($params, $smarty){
15 | return
16 | '';
20 | }
21 |
22 | /**
23 | * smarty 编译插件 titleclose
24 | *
25 | * 处理 {/title} 标签
26 | *
27 | * @param array $params
28 | * @param Smarty $smarty
29 | * @access public
30 | * @return string 编译后的php代码
31 | * @see BigPipe::compileCloseTag
32 | */
33 | function smarty_compiler_titleclose($params, $smarty){
34 | return
35 | '';
39 | }
40 |
--------------------------------------------------------------------------------
/src/plugin/function.require.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * @param array $params
8 | * @param Smarty $smarty
9 | * @return void
10 | * @see BigPipeResource::registModule
11 | * @see BigPipe::currentContext
12 | * @see PageletContext->addRequire
13 | */
14 | function smarty_function_require($params, $smarty){
15 | $link = $params['name'];
16 | unset($params['name']);
17 |
18 | BigPipeResource::registModule($link);
19 |
20 | $context = BigPipe::currentContext();
21 | $resource = BigPipeResource::getResourceByPath($link);
22 |
23 | switch ($resource["type"]) {
24 | case 'css':
25 | $on = isset($params['on']) ? $params['on'] : 'beforedisplay';
26 | break;
27 | case 'js':
28 | $on = isset($params['on']) ? $params['on'] : 'load';
29 | break;
30 | }
31 |
32 | $context->addRequire($on, $link);
33 | }
34 |
--------------------------------------------------------------------------------
/src/plugin/function.widget.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * @param array $params
8 | * @param Smarty $template
9 | * @return Smarty template funtion call
10 | * @see BigPipeResource::registModule
11 | * @see BigPipeResource::getTplByPath
12 | * @see BigPipe::currentContext
13 | * @see PageletContext->addRequire
14 | */
15 |
16 | function smarty_function_widget($params, $template)
17 | {
18 | $name = $params['name'];
19 | $method = $params['method'];
20 | unset($params['name']);
21 | unset($params['method']);
22 |
23 | if(isset($params['call'])){
24 | $call = $params['call'];
25 | unset($params['call']);
26 | }
27 |
28 | BigPipeResource::registModule($name);
29 |
30 | $tpl = BigPipeResource::getTplByPath($name);
31 | // Auto add widget css and less deps (no js) to currentContext.
32 | if(!empty($tpl["deps"])){
33 |
34 | $deps = $tpl["deps"];
35 | $context = BigPipe::currentContext();
36 |
37 | foreach($deps as $dep){
38 | BigPipeResource::registModule($dep);
39 | $depResource = BigPipeResource::getResourceByPath($dep);
40 |
41 | if($depResource["type"] === "css"){
42 | $on = 'beforedisplay';
43 | $context->addRequire($on, $dep);
44 | }
45 | }
46 | }
47 |
48 | $smarty=$template->smarty;
49 | $tplpath = $tpl["uri"];
50 |
51 | // First try to call the mothed passed via the $call param,
52 | // in order to made it compatible for fisp.
53 | if(isset($call)){
54 | $call = 'smarty_template_function_' . $call;
55 | if(!function_exists($call)) {
56 | try {
57 | $smarty->fetch($tplpath);
58 | } catch (Exception $e) {
59 | throw new Exception("\nNo tpl here, via call \n\"$name\" \n@ \"$tplpath\"");
60 | }
61 | }
62 | if(function_exists($call)) {
63 | return $call($template, $params);
64 | }
65 | }
66 |
67 | // If there is no method named $call,
68 | // try to call mothed passed via the $method param
69 | $fn='smarty_template_function_' . $method;
70 | if(!function_exists($fn)) {
71 | try {
72 | $smarty->fetch($tplpath);
73 | } catch (Exception $e) {
74 | throw new Exception("\nNo tpl here,via method \n\"$name\" \n@ \"$tplpath\"");
75 | }
76 | }
77 |
78 | if(function_exists($fn)) {
79 | return $fn($template, $params);
80 | }
81 |
82 | // If still no method named $method,
83 | // try to construct a method name with the tpl path, via md5().
84 | // This is in order to support call method through dynamic tpl path.
85 | else
86 | {
87 | $methodName = preg_replace('/^_[a-fA-F0-9]{32}_/','',$method);
88 |
89 | if($method !== $methodName){
90 | $method = '_' . md5($name) . '_' . $methodName;
91 |
92 | $fn='smarty_template_function_' . $method;
93 |
94 | if(function_exists($fn)){
95 | return $fn($template, $params);
96 | }
97 | }
98 | throw new Exception("\nUndefined function \"$method\" \nin \"$name\" \n@ \"$tplpath\"");
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/plugin/lib/BigPipeResource.class.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 | class BigPipeResource
8 | {
9 | /**
10 | * 存储资源map
11 | */
12 | private static $map = array(
13 | "res"=>array(),
14 | "her"=>array()
15 | );
16 | /**
17 | * 存储已经注册的module
18 | */
19 | private static $registedMoudle = array();
20 | /**
21 | * 存储已经处理过返回给前端的Resources
22 | */
23 | public static $knownResources = array();
24 | /**
25 | * 初始化资源map
26 | *
27 | * @param array $map 类名
28 | * @static
29 | * @access public
30 | * @return void
31 | */
32 | public static function setupMap($map)
33 | {
34 | self::$map["res"] = self::$map["res"] + $map["res"];
35 | self::$map["her"] = self::$map["her"] + $map["her"];
36 | }
37 |
38 | /**
39 | * 注册一个module 将module的map存在$registedMoudle
40 | *
41 | * @param string $name 标准化资源路径
42 | * @static
43 | * @access public
44 | * @return void
45 | */
46 | public static function registModule($name)
47 | {
48 | $intPos = strpos($name, ':');
49 |
50 | if($intPos === false){
51 | return;
52 | } else {
53 | $femodule = substr($name, 0, $intPos);
54 | }
55 |
56 | $configPath = BIGPIPE_CONF_DIR;
57 |
58 | if(!in_array($femodule, self::$registedMoudle)){
59 |
60 | if(defined("FE_HTTPS") && FE_HTTPS){
61 | $femodulefix = $femodule . '-https';
62 | }else{
63 | $femodulefix = $femodule;
64 | }
65 | $mapPath = $configPath . '/' . $femodulefix . '-map.json';
66 | $map = json_decode(file_get_contents($mapPath), true);
67 | BigPipeResource::setupMap($map);
68 | self::$registedMoudle[] = $femodule;
69 | }
70 | }
71 |
72 | /**
73 | * 通过标准化路径获取tpl资源
74 | *
75 | * @param string $path 标准化资源路径
76 | * @static
77 | * @access public
78 | * @return resource
79 | */
80 | public static function getTplByPath($path)
81 | {
82 | return self::$map["res"][$path];
83 | }
84 |
85 | /**
86 | * 通过标准化路径获取资源
87 | *
88 | * @param string $path 标准化资源路径
89 | * @static
90 | * @access public
91 | * @return resource
92 | */
93 | public static function getResourceByPath($path, $type = null){
94 |
95 | $map = self::$map["her"];
96 | $resource = self::getResource($map, $path, $type);
97 | if($resource)
98 | return $resource;
99 | return false;
100 | }
101 |
102 | public static function getResource($map, $path, $type){
103 | foreach ($map as $id => $resource) {
104 | if( (!isset($type) || $type == $resource['type'])
105 | && in_array($path, $resource['defines'])){
106 | $resource['id'] = $id;
107 | if(!isset($resource['requires'])) $resource['requires'] = array();
108 | if(!isset($resource['requireAsyncs'])) $resource['requireAsyncs'] = array();
109 | return $resource;
110 | }
111 | }
112 | return false;
113 | }
114 |
115 | /**
116 | * 通过路径数组获取资源数组
117 | *
118 | * @param string $pathArr 标准化资源路径数组
119 | * @static
120 | * @access public
121 | * @return resources 资源数组
122 | */
123 | public static function pathToResource($pathArr, $type = null){
124 | $resources = array();
125 |
126 | foreach ($pathArr as $path) {
127 | $resource = self::getResourceByPath($path, $type);
128 | if($resource){
129 | $resources[$resource['id']] = $resource;
130 | }
131 | }
132 | return $resources;
133 | }
134 |
135 | /**
136 | * 通过资源数组获取依赖资源数组
137 | *
138 | * @param array $resources 资源数组
139 | * @param bool $asyncs 是否需要获取async依赖
140 | * @static
141 | * @access public
142 | * @return resources 依赖资源数组
143 | */
144 | public static function getDependResource($resources, $asyncs = true){
145 | $dependResources = array();
146 |
147 | $depends = $resources;
148 |
149 | while(!empty($depends)){
150 |
151 | $last = end($depends);
152 | array_pop($depends);
153 |
154 | $id = $last['id'];
155 |
156 | if(isset($dependResources[$id])){
157 | continue;
158 | }
159 | $dependResources[$id] = $last;
160 |
161 | $lastDepends = self::getDepend($last, $asyncs);
162 | if(!empty($lastDepends)){
163 | $depends = BigPipe::array_merge($depends, $lastDepends);
164 | }
165 | }
166 |
167 | return array_reverse($dependResources, true);
168 | }
169 |
170 | /**
171 | * 获取一个资源的依赖
172 | *
173 | * @param mixed $resource 资源数组
174 | * @param bool $asyncs 是否需要获取async依赖
175 | * @static
176 | * @access public
177 | * @return resources 依赖资源数组
178 | */
179 | private static function getDepend($resource, $asyncs){
180 | $requires = $resource['requires'];
181 |
182 | if($asyncs){
183 | $requires = array_merge($requires, $resource['requireAsyncs']);
184 | }
185 |
186 | if(count($requires) > 0 ){
187 | return $dependResources = self::pathToResource($requires);
188 | }
189 | return array();
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/src/plugin/lib/NoscriptController.class.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 |
8 | BigPipe::loadClass("PageController");
9 | BigPipe::loadClass("BigPipeResource");
10 |
11 | class NoScriptController extends PageController
12 | {
13 | const STAT_COLLECT = 1;
14 | const STAT_OUTPUT = 2;
15 |
16 | private $state = self::STAT_COLLECT;
17 | private $bodyHTML = null;
18 | private $bodyStyleLinks = array();
19 |
20 | public function __construct()
21 | {
22 | $this->actionChain = array(
23 | 'default' => false,
24 | // 收集
25 | 'collect_html_open' => array(
26 | 'outputOpenTag',
27 | true
28 | ),
29 | 'collect_body_open' => array(
30 | 'startCollect',
31 | true
32 | ),
33 | 'collect_block_open' => array(
34 | 'outputOpenTag',
35 | true
36 | ),
37 | 'collect_block_close' => array(
38 | 'outputCloseTag'
39 | ),
40 | 'collect_body_close' => array(
41 | 'collectBody'
42 | ),
43 | 'collect_more' => array(
44 | 'changeState',
45 | true
46 | ),
47 | // 输出
48 | 'output_head_open' => array(
49 | 'outputOpenTag',
50 | 'outputScriptReload',
51 | true
52 | ),
53 | 'output_title_open' => array(
54 | 'outputOpenTag',
55 | true
56 | ),
57 | 'output_title_close' => array(
58 | 'outputCloseTag'
59 | ),
60 | 'output_head_close' => array(
61 | 'outputStyle',
62 | 'outputCloseTag'
63 | ),
64 | 'output_body_open' => array(
65 | 'outputOpenTag',
66 | 'outputBody',
67 | false
68 | ),
69 | 'output_body_close' => array(
70 | 'outputCloseTag'
71 | ),
72 | 'output_html_close' => array(
73 | 'outputCloseTag'
74 | ),
75 | 'output_more' => false,
76 | );
77 | }
78 |
79 | protected function collectBody($context){
80 | $this->bodyHTML = ob_get_clean();
81 | }
82 |
83 | protected function outputStyle($context){
84 | $event = $context->parent->getEvent('beforedisplay');
85 |
86 | if($event != false){
87 | $styleLinks = $event->requires;
88 |
89 | $styleResources = BigPipeResource::pathToResource($styleLinks, 'css');
90 | $styleResources = BigPipeResource::getDependResource($styleResources);
91 | }
92 | foreach($styleResources as $resource){
93 | echo "";
94 | }
95 | }
96 |
97 | protected function outputScriptReload($context){
98 | echo '';
104 | }
105 |
106 | protected function outputBody($context){
107 | echo $this->bodyHTML;
108 | }
109 |
110 | protected function changeState(){
111 | switch($this->state){
112 | case self::STAT_COLLECT:
113 | $this->state=self::STAT_OUTPUT;
114 | break;
115 | case self::STAT_OUTPUT:
116 | break;
117 | default:
118 | break;
119 | }
120 | }
121 |
122 | /**
123 | * getActionKey 得到需要执行的动作索引
124 | *
125 | * @param mixed $context
126 | * @param mixed $action
127 | * @access protected
128 | * @return void
129 | */
130 | protected function getActionKey($type, $action)
131 | {
132 | $keys=array();
133 | switch($this->state) {
134 | case self::STAT_COLLECT:
135 | $keys[]="collect";
136 | break;
137 | case self::STAT_OUTPUT:
138 | $keys[]="output";
139 | break;
140 | default:
141 | }
142 |
143 | switch($type) {
144 | case BigPipe::TAG_HTML:
145 | $keys[] = "html";
146 | break;
147 | case BigPipe::TAG_HEAD:
148 | $keys[] = "head";
149 | break;
150 | case BigPipe::TAG_TITLE:
151 | $keys[] = "title";
152 | break;
153 | case BigPipe::TAG_BODY:
154 | $keys[] = "body";
155 | break;
156 | case BigPipe::TAG_PAGELET:
157 | $keys[] = "block";
158 | break;
159 | default:
160 | }
161 |
162 | switch($action) {
163 | case PageController::ACTION_OPEN:
164 | $keys[]="open";
165 | break;
166 | case PageController::ACTION_CLOSE:
167 | $keys[]="close";
168 | break;
169 | case PageController::ACTION_MORE:
170 | $keys[]="more";
171 | break;
172 | default:
173 | }
174 |
175 | $key=join("_", $keys);
176 | if(!isset($this->actionChain[$key])) {
177 | $key='default';
178 | }
179 | return $key;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/plugin/lib/PageController.class.php:
--------------------------------------------------------------------------------
1 |
7 | * zhangwentao
8 | */
9 |
10 | abstract class PageController
11 | {
12 | /**
13 | * 标签打开的动作类型
14 | * @see getActionKey
15 | */
16 | const ACTION_OPEN = 1;
17 | /**
18 | * 标签关闭的动作类型
19 | * @see getActionKey
20 | */
21 | const ACTION_CLOSE = 2;
22 | /**
23 | * 判断页面是否需要再次执行的动作类型
24 | * @see getActionKey
25 | */
26 | const ACTION_MORE = 3;
27 |
28 | /**
29 | * 执行的动作链,子类应该设置该属性,以便 doAction 调用
30 | *
31 | * @var array
32 | * @access protected
33 | */
34 | protected $actionChain = null;
35 |
36 | /**
37 | * 得到用于执行的动作链 key, 子类应该实现,以便从 actionChain 中查找用于执行的动作链
38 | *
39 | * @param PageletContext $context 当前 Pagelet 上下文
40 | * @param int $action 当前的动作类型
41 | * @abstract
42 | * @access protected
43 | * @return string
44 | */
45 | abstract protected function getActionKey($context, $action);
46 |
47 | /**
48 | * 执行某个函数链
49 | *
50 | * @param string $key 函数链名
51 | * @param PageletContext $context 当前 Pagelet 上下文
52 | * @access private
53 | * @return bool 函数链的执行结果
54 | */
55 | private function doAction($key, $context)
56 | {
57 | $ret = null;
58 | $actions = null;
59 |
60 | if (isset($this->actionChain[$key])) {
61 | $actions = $this->actionChain[$key];
62 | if (is_string($actions)) {
63 | // $actions = array(
64 | // $actions
65 | // );
66 | $actions = call_user_func(array(
67 | $this,
68 | $actions
69 | ), $context);
70 | }
71 | if (is_array($actions)) {
72 | foreach ($actions as $method) {
73 | if (is_string($method)) {
74 | $ret = call_user_func(array(
75 | $this,
76 | $method
77 | ), $context);
78 | } else {
79 | $ret = $method;
80 | }
81 | // 如果返回 false 直接退出返回
82 | if($ret === false){
83 | break;
84 | }
85 | }
86 | } else {
87 | $ret = $actions;
88 | }
89 | }
90 | return $ret;
91 | }
92 |
93 | /**
94 | * 标签打开时调用,控制标签的执行
95 | *
96 | * @param PageletContext $context 当前 Pagelet 上下文
97 | * @final
98 | * @access public
99 | * @return bool 当前 Pagelet 是否需要执行
100 | */
101 | public final function openTag($context)
102 | {
103 | return $this->doAction($this->getActionKey($context->type, self::ACTION_OPEN), $context);
104 | }
105 |
106 | /**
107 | * 标签关闭时调用
108 | *
109 | * @param PageletContext $context
110 | * @final
111 | * @access public
112 | * @return void
113 | */
114 | public final function closeTag($context)
115 | {
116 | $this->doAction($this->getActionKey($context->type, self::ACTION_CLOSE), $context);
117 | }
118 |
119 | /**
120 | * 页面完成一次执行后调用,用于控制页面是否重复执行
121 | *
122 | * @final
123 | * @access public
124 | * @return bool 页面是否重复执行
125 | */
126 | public final function hasMore()
127 | {
128 | return $this->doAction($this->getActionKey(BigPipe::TAG_NONE, self::ACTION_MORE), null);
129 | }
130 |
131 | /**
132 | * 输出打开标签
133 | *
134 | * @param PageletContext $context 当前 Pagelet 上下文
135 | * @access protected
136 | * @return void
137 | */
138 | protected function outputOpenTag($context)
139 | {
140 | echo $context->getOpenHTML();
141 | }
142 |
143 | /**
144 | * 输出闭合标签
145 | *
146 | * @param PageletContext $context 当前 Pagelet 上下文
147 | * @access protected
148 | * @return void
149 | */
150 | protected function outputCloseTag($context)
151 | {
152 | echo $context->getCloseHTML();
153 | }
154 |
155 | /**
156 | * 开始收集内容
157 | *
158 | * @param PageletContext $context 当前 Pagelet 上下文
159 | * @access protected
160 | * @return void
161 | */
162 | protected function startCollect($context)
163 | {
164 | ob_start();
165 | }
166 |
167 | /**
168 | * 将收集到的内容作为 pagelet 的 HTML 内容
169 | *
170 | * @param PageletContext $context 当前 Pagelet 上下文
171 | * @access protected
172 | * @return void
173 | */
174 | protected function collectHTML($context)
175 | {
176 | $context->html = ob_get_clean();
177 | }
178 |
179 | /**
180 | * collectScript 收集脚本
181 | *
182 | * @param PageletContext $context 当前 Pagelet 上下文
183 | * @access protected
184 | * @return void
185 | */
186 | protected function collectScript($context)
187 | {
188 | $context->parent->addScript(
189 | ob_get_clean(),
190 | $context->getBigPipeConfig("on", "load"),
191 | $context->getBigPipeConfig("deps"),
192 | $context->getBigPipeConfig("asyncs")
193 | );
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/src/plugin/lib/PageletContext.class.php:
--------------------------------------------------------------------------------
1 |
8 | * zhangwentao
9 | */
10 |
11 | BigPipe::loadClass("PageletEvent");
12 |
13 | class PageletContext
14 | {
15 |
16 | /**
17 | * 空标志
18 | */
19 | const FLG_NONE = 0;
20 |
21 | /**
22 | * 自动添加事件的标志
23 | * @see getEvent
24 | */
25 | const FLG_AUTO_ADD_EVENT = 1;
26 |
27 |
28 | /**
29 | * 如果指定参数没有值则添加参数
30 | * @see getParam
31 | */
32 | const FLG_APPEND_PARAM = 2;
33 |
34 | /**
35 | * 标签类型
36 | *
37 | * @var int
38 | * @access public
39 | */
40 | public $type = null;
41 |
42 | /**
43 | * 标签参数
44 | *
45 | * @var array
46 | * @access public
47 | */
48 | public $params = null;
49 |
50 | /**
51 | * 标签 HTML 内容
52 | *
53 | * @var string
54 | * @access public
55 | */
56 | public $html = null;
57 |
58 | /**
59 | * 父级Pagelet
60 | *
61 | * @var PageletContext
62 | * @access public
63 | */
64 | public $parent = null;
65 |
66 | /**
67 | * 子级 Pagelet 列表
68 | *
69 | * @var array
70 | * @access public
71 | */
72 | public $children = null;
73 |
74 | /**
75 | * 当前标签是否执行( open 时返回值是否为 true )
76 | *
77 | * @var bool
78 | * @access public
79 | */
80 | public $opened = false;
81 |
82 | /**
83 | * Pagelet 事件
84 | *
85 | * @var array
86 | * @access public
87 | */
88 | public $events = null;
89 |
90 | //FIXME delete
91 | private static $priority_list = array();
92 | private static $max_priority = 0;
93 | private $vars = null;
94 | public $priority = null; // 输出优先级
95 | public $priorityArray = null; //输出数组
96 | public $scripts = null; // script内容
97 | public $scriptLinks = null; // js 链接
98 | public $styles = null; // style内容
99 | public $styleLinks = null; // css 链接
100 | public $shouldShow = false;
101 |
102 | /**
103 | * 创建一个新的 Pagelet 节点上下文
104 | *
105 | * @param int $type 节点类型
106 | * @param array $params 节点参数
107 | * @access public
108 | * @return void
109 | */
110 | public function __construct($type, $params = null)
111 | {
112 | $this->type = $type;
113 | $this->params = $params;
114 | $this->children = array();
115 | $this->events = array();
116 |
117 | $this->scripts = array();
118 | $this->scriptLinks = array();
119 | $this->styles = array();
120 | $this->styleLinks = array();
121 |
122 | $this->renderMode = BigPipe::RENDER_MODE_DEFAULT;
123 | $this->vars = array();
124 |
125 | if($type == BigPipe::TAG_HEAD || $type == BigPipe::TAG_BODY){
126 | $this->popTarget = true;
127 | }
128 |
129 | }
130 |
131 | /**
132 | * 添加依赖资源
133 | *
134 | * @param string $eventType 事件类型
135 | * @param string|array $resourceName 资源名或者资源列表
136 | * @access public
137 | * @return void
138 | *
139 | * @example
140 | * $context = BigPipe::currentContext();
141 | * $context->addRequire("beforedisplay", "common:css/layout.css");
142 | * $context->addRequire("load", array("common:js/jquery.js"));
143 | */
144 | public function addRequire($eventType, $resourceName)
145 | {
146 | //var_dump($resourceName);
147 | if(isset($this->popTarget) && $this->popTarget == true){
148 | $target = $this->parent;
149 | }else{
150 | $target = $this;
151 | }
152 |
153 | $event = $target->getEvent($eventType, self::FLG_AUTO_ADD_EVENT);
154 | if (is_array($resourceName)) {
155 | foreach ($resourceName as $name) {
156 | $event->addRequire($name);
157 | }
158 | } else {
159 | $event->addRequire($resourceName);
160 | }
161 | }
162 |
163 | /**
164 | * 添加异步依赖资源
165 | *
166 | * @param string $eventType 事件类型
167 | * @param string|array $resourceName 资源名或者资源列表
168 | * @access public
169 | * @return void
170 | *
171 | * @see addRequire
172 | */
173 | public function addRequireAsync($eventType, $resourceName)
174 | {
175 | if(isset($this->popTarget) && $this->popTarget == true){
176 | $target = $this->parent;
177 | }else{
178 | $target = $this;
179 | }
180 | $event = $target->getEvent($eventType, self::FLG_AUTO_ADD_EVENT);
181 | if (is_array($resourceName)) {
182 | foreach ($resourceName as $name) {
183 | $event->addRequireAsync($name);
184 | }
185 | } else {
186 | $event->addRequireAsync($resourceName);
187 | }
188 | }
189 |
190 | /**
191 | * 添加 hook 函数
192 | *
193 | * @param string $eventType 要添加到的事件类型
194 | * @param string $scriptCode 添加的代码
195 | * @access public
196 | * @return void
197 | *
198 | * @example
199 | * $context = BigPipe::currentContext();
200 | * $context->addHook("load", "console.log(\"pagelet loaded!\")");
201 | *
202 | * @see addRequire
203 | * @see addRequireAsync
204 | */
205 | public function addHook($eventType, $scriptCode, $strict)
206 | {
207 | if(isset($this->popTarget) && $this->popTarget == true){
208 | $target = $this->parent;
209 | }else{
210 | $target = $this;
211 | }
212 | $event = $target->getEvent($eventType, self::FLG_AUTO_ADD_EVENT);
213 | $event->addHook($scriptCode, $strict);
214 | }
215 |
216 | /**
217 | * 得到事件对象
218 | *
219 | * @param string $eventType 事件类型
220 | * @param int $flag 事件添加标志,可选值:FLG_AUTO_ADD_EVENT
221 | * @access private
222 | * @return PageletEvent 指定的事件对象
223 | */
224 | public function getEvent($eventType, $flag = self::FLG_NONE)
225 | {
226 | if (isset($this->events[$eventType])) {
227 | return $this->events[$eventType];
228 | } else if ($flag & self::FLG_AUTO_ADD_EVENT) {
229 | $this->events[$eventType] = new PageletEvent();
230 | return $this->events[$eventType];
231 | } else {
232 | return false;
233 | }
234 | }
235 |
236 | /**
237 | * 得到参数,如果不存在此参数,则返回默认值
238 | *
239 | * @param string $name 参数名
240 | * @param mixed $default 默认值
241 | * @access public
242 | * @return mixed 参数值
243 | */
244 | public function getParam($name, $default = null, $flag = self::FLG_NONE)
245 | {
246 | if (isset($this->params[$name])) {
247 | return $this->params[$name];
248 | } else if ($flag & self::FLG_APPEND_PARAM) {
249 | $this->params[$name] = $default;
250 | }
251 | return $default;
252 | }
253 |
254 | /**
255 | * 得到标签名
256 | *
257 | * @access private
258 | * @return string 标签名
259 | */
260 | private function getTagName()
261 | {
262 | switch ($this->type) {
263 | case BigPipe::TAG_HTML:
264 | return 'html';
265 | case BigPipe::TAG_HEAD:
266 | return 'head';
267 | case BigPipe::TAG_TITLE:
268 | return 'title';
269 | case BigPipe::TAG_BODY:
270 | return 'body';
271 | case BigPipe::TAG_PAGELET:
272 | return $this->getParam("html-tag", "div");
273 | default:
274 | }
275 | }
276 |
277 | /**
278 | * 得到打开标签的 HTML
279 | *
280 | * @param array $params 标签参数,如果为false,则不显示参数
281 | * @access public
282 | * @return string 标签打开的 HTML
283 | */
284 | public function getOpenHTML($params = null)
285 | {
286 |
287 | $text = "";
288 | if( $this->type == BigPipe::TAG_HTML ){
289 | $text .= "";
290 | }
291 | $text .= '<' . $this->getTagName();
292 |
293 | if ($params !== false) {
294 | if (!isset($params)) {
295 | $params = $this->params;
296 | }
297 |
298 | // Parse _attributes param for Hao123-sub
299 | if($this->type == BigPipe::TAG_BODY && isset($params["_attributes"])){
300 | $attrs = $params["_attributes"];
301 | unset($params["_attributes"]);
302 |
303 | foreach ($attrs as $key => $value) {
304 | $text .= " $key=\"" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8', true) . "\"";
305 | }
306 | }
307 |
308 | foreach ($params as $key => $value) {
309 | if(!isset($value)) continue;
310 | if (strpos($key, BigPipe::ATTR_PREFIX) !== 0) {
311 | $text .= " $key=\"" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8', true) . "\"";
312 | }
313 | }
314 |
315 | if(!empty($this->renderMode) && $this->type === BigPipe::TAG_PAGELET) {
316 | $text .= " data-rm=\"$this->renderMode\"";
317 | }
318 | }
319 | $text .= '>';
320 | return $text;
321 | }
322 |
323 | /**
324 | * 得到闭合标签的HTML
325 | *
326 | * @access public
327 | * @return string 标签闭合的 HTML
328 | */
329 | public function getCloseHTML()
330 | {
331 | return '' . $this->getTagName() . '>';
332 | }
333 |
334 | private static function getPriorityString($arr)
335 | {
336 | $str = array();
337 | foreach ($arr as $pri) {
338 | $str[] = str_pad($pri, self::$max_priority, '0', STR_PAD_LEFT);
339 | }
340 | $str = implode('/', $str) . ".";
341 | return $str;
342 | }
343 |
344 | public static function uniquePriority()
345 | {
346 | $list = array();
347 | foreach (self::$priority_list as $arr) {
348 | $list[] = self::getPriorityString($arr);
349 | }
350 | $list = array_unique($list, SORT_STRING);
351 | rsort($list, SORT_STRING);
352 | return $list;
353 | }
354 |
355 | public function setPriority($priority)
356 | {
357 | if ($this->parent !== null && $this->parent->priorityArray !== null) {
358 | $priorityArray = $this->parent->priorityArray;
359 | } else {
360 | $priorityArray = array();
361 | }
362 | $priorityArray[] = $priority;
363 | $this->priorityArray = $priorityArray;
364 | self::$priority_list[] = $this->priorityArray;
365 | self::$max_priority = max(self::$max_priority, strlen($priority));
366 | }
367 |
368 | public function getPriority()
369 | {
370 | if ($this->priority === null) {
371 | $this->priority = self::getPriorityString($this->priorityArray);
372 | }
373 | return $this->priority;
374 | }
375 |
376 | public function get($key, $default = null)
377 | {
378 | if (isset($this->vars[$key])) {
379 | return $this->vars[$key];
380 | }
381 | return $default;
382 | }
383 |
384 | public function set($key, $value = null)
385 | {
386 | if (isset($value)) {
387 | $this->vars[$key] = $value;
388 | } elseif (isset($this->vars[$key])) {
389 | unset($this->vars[$key]);
390 | }
391 | return $value;
392 | }
393 |
394 | }
--------------------------------------------------------------------------------
/src/plugin/lib/PageletEvent.class.php:
--------------------------------------------------------------------------------
1 |
7 | * zhangwentao
8 | */
9 |
10 | class PageletEvent
11 | {
12 | /**
13 | * 事件的依赖资源
14 | *
15 | * @var mixed
16 | * @access public
17 | */
18 | public $requires = null;
19 |
20 | /**
21 | * 事件的异步依赖资源
22 | *
23 | * @var mixed
24 | * @access public
25 | */
26 | public $requireAsyncs = null;
27 |
28 | /**
29 | * 事件的钩子函数
30 | *
31 | * @var mixed
32 | * @access public
33 | */
34 | public $hooks = null;
35 |
36 | /**
37 | * 构造新的 PageletEvent 对象
38 | *
39 | * @access public
40 | * @return void
41 | */
42 | public function __construct()
43 | {
44 | $this->requires = array();
45 | $this->requireAsyncs = array();
46 | $this->hooks = array();
47 | }
48 |
49 | /**
50 | * 添加依赖资源
51 | *
52 | * @param string $resourceName 依赖资源名
53 | * @access public
54 | * @return void
55 | */
56 | public function addRequire($resourceName)
57 | {
58 | if (!in_array($resourceName, $this->requires)) {
59 | $this->requires[] = $resourceName;
60 | }
61 | }
62 |
63 | /**
64 | * 添加异步依赖资源
65 | *
66 | * @param string $resourceName 异步依赖资源名
67 | * @access public
68 | * @return void
69 | */
70 | public function addRequireAsync($resourceName)
71 | {
72 | if (!in_array($resourceName, $this->requireAsyncs)) {
73 | $this->requireAsyncs[] = $resourceName;
74 | }
75 | }
76 |
77 | /**
78 | * 添加钩子函数
79 | *
80 | * @param string $scriptCode 钩子函数代码
81 | * @access public
82 | * @return void
83 | */
84 | public function addHook($scriptCode, $strict)
85 | {
86 | if($strict){
87 | $scriptCode = "'use strict';\n" . $scriptCode;
88 | }
89 | $this->hooks[] = $scriptCode;
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/src/plugin/lib/SmartController.class.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | BigPipe::loadClass("FirstController");
10 | BigPipe::loadClass("BigPipeResource");
11 |
12 | class SmartController extends FirstController
13 | {
14 | const STAT_COLLECT = 1; // 收集阶段
15 | const STAT_OUTPUT = 2; // 输出阶段
16 |
17 | private $state = self::STAT_COLLECT; //初始状态
18 | private $headInnerHTML = null;
19 | private $bodyInnerHTML = null;
20 |
21 | private $loadedResource = array();
22 |
23 | protected $sessionId = 0; // 此次会话ID,用于自动生成不重复id,第一次默认为0
24 | protected $uniqIds = array(); // 不重复id种子
25 |
26 | /**
27 | * 构造函数
28 | *
29 | * @access public
30 | * @return void
31 | */
32 | public function __construct($sessions, $ids)
33 | {
34 | $this->ids = $ids;
35 | $this->sessions = $sessions;
36 |
37 | $this->pagelets = array();
38 | $this->cids = array();
39 | $this->oids = $ids;
40 |
41 | $this->actionChain = array(
42 | //收集阶段
43 | 'collect_pagelet_open' => 'collect_pagelet_open',
44 | 'collect_pagelet_close' => 'collect_pagelet_close',
45 | // 'collect_pagelet_open' => array(
46 | // //TODO 'outputPageletOpenTag',
47 | // 'addPagelet',
48 | // 'outputSmartOpenTag',
49 | // 'startCollect',
50 | // true
51 | // ),
52 | // 'collect_pagelet_close' => array(
53 | // 'collectHTML',
54 | // // 'setupBigrender',
55 | // 'outputSmartCloseTag'
56 | // ),
57 | 'collect_more' => array(
58 | 'changeState',
59 | true
60 | ),
61 | //输出阶段
62 | 'output_body_close' => array(
63 | 'outputPagelets',
64 | ),
65 | 'output_more' => false,
66 | 'default' => false
67 | );
68 | }
69 |
70 | /**
71 | * collect_pagelet_open 时的 actionChain
72 | *
73 | * @param PageletContext $context
74 | * @return mixed: actionChain
75 | */
76 | protected function collect_pagelet_open($context) {
77 | if($context->renderMode === BigPipe::RENDER_MODE_NONE) {
78 | return false;
79 | }
80 |
81 | $context->renderMode = BigPipe::RENDER_MODE_QUICKLING;
82 |
83 | $id = $context->getParam(
84 | "id",
85 | $this->sessionUniqId("__elm_"),
86 | PageletContext::FLG_APPEND_PARAM
87 | );
88 |
89 | if(isset($context->parent)){
90 | $parentId = $context->parent->getParam("id");
91 | if(!empty($parentId) && in_array($parentId, $this->ids)){
92 | $this->ids = array_merge($this->ids, array($id));
93 | $this->cids[] = $id;
94 | }
95 | }
96 | if(in_array($id, $this->ids)){
97 | $this->pagelets[] = $context;
98 | // var_dump($context->getParam("id"), $this->cids );
99 | if( in_array($context->getParam("id"), $this->cids ) ){
100 | return array(
101 | 'outputOpenTag',
102 | 'addPagelet',
103 | 'startCollect',
104 | true
105 | );
106 | }else{
107 | return array(
108 | // 'outputOpenTag',
109 | 'addPagelet',
110 | 'startCollect',
111 | true
112 | );
113 | }
114 | }else{
115 | // $context->renderMode = BigPipe::RENDER_MODE_NONE;
116 | // $context->opened = false;
117 | return false;
118 | }
119 | }
120 |
121 | /**
122 | * collect_pagelet_close 时的 actionChain
123 | *
124 | * @param PageletContext $context
125 | * @return mixed: actionChain
126 | */
127 | protected function collect_pagelet_close($context) {
128 |
129 | // if($context->renderMode === BigPipe::RENDER_MODE_NONE) {
130 | if(!$context->opened) {
131 | return false;
132 | }
133 |
134 | if( in_array($context->getParam("id"), $this->cids ) ){
135 | return array(
136 | 'collectHTML',
137 | 'outputCloseTag'
138 | );
139 | }
140 |
141 | return array(
142 | 'collectHTML',
143 | );
144 | }
145 |
146 | /**
147 | * 输出Quickling请求的pagelets
148 | *
149 | * @param PageletContext $context
150 | */
151 | protected function outputPagelets($context)
152 | {
153 | $pagelets = array();
154 | foreach ($this->pagelets as $pagelet) {
155 | $id = $pagelet->getParam("id");
156 | if( in_array($id, $this->ids) ){
157 | $config = $this->outputPagelet($pagelet);
158 |
159 | if( isset($this->sessions[$id]) ){
160 | $config["session"] = $this->sessions[$id];
161 | }
162 | $pagelets[] = $config;
163 | }
164 | }
165 |
166 | // 输出之前 设置 Content-Type: application/json
167 | header('Content-Type: application/json;charset=UTF-8');
168 | echo json_encode($pagelets);
169 | }
170 | /**
171 | * 按Quickling模式输出一个pagelet
172 | *
173 | * @param PageletContext $context
174 | */
175 | protected function outputPagelet($pagelet)
176 | {
177 | $resourceMap = array();
178 | $hooks = array();
179 | $config = $this->getPageletConfig($pagelet, $html, $resourceMap, $hooks, true);
180 | $config['quickling'] = true;
181 | $outputMap = array();
182 | //设置资源表
183 | if (!empty($resourceMap)) {
184 | $resourceMap = BigPipeResource::pathToResource($resourceMap);
185 | $resourceMap = BigPipeResource::getDependResource($resourceMap);
186 |
187 | $resourceMap = BigPipe::array_merge($resourceMap, $this->loadedResource);
188 |
189 | foreach ($resourceMap as $id => $resource) {
190 |
191 | if(isset(BigPipeResource::$knownResources[$id])){
192 | continue;
193 | }
194 |
195 | $requires = $resource['requires'];
196 | unset($resource['requires']);
197 | unset($resource['requireAsyncs']);
198 |
199 | $requireIds = array();
200 | if(!empty($requires)){
201 | $requires = BigPipeResource::pathToResource($requires);
202 | $requireIds = array_keys($requires);
203 | }
204 | $resource['deps'] = $requireIds;
205 | $resource['mods'] = $resource['defines'];
206 |
207 | unset($resource['defines']);
208 | unset($resource['id']);
209 | $outputMap[$id] = $resource;
210 | BigPipeResource::$knownResources[$id] = $resource;
211 | }
212 | }
213 |
214 | $config["resourceMap"] = $outputMap;
215 |
216 | return $config;
217 | }
218 | }
--------------------------------------------------------------------------------