├── .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 | ![her系统架构图](http://s0.hao123img.com/res/her/iframework.png) 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 | ![页面](http://s0.hao123img.com/res/her/htmlsmall.png) 31 | 32 | 代码如下: 33 | 34 | ``` 35 | 36 | 37 | 38 |
HTML内容
39 |
HTML内容
40 |
HTML内容
41 | 42 | 43 | 44 | ``` 45 | 传统页面的输出方式大家都很熟悉,无需赘述 46 | 47 | ###使用Pagelet标记区块 48 | 49 | ![页面](http://s0.hao123img.com/res/her/htmlsmall.png) 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 '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 '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 | } --------------------------------------------------------------------------------