diff --git a/examples/extend.html b/examples/extend.html index 6669b84f..d265a12a 100644 --- a/examples/extend.html +++ b/examples/extend.html @@ -1,30 +1,56 @@ - - - -自定义模块 - layui + + + + 扩展模块 - Layui + + + +
+

打开浏览器控制台查看测试结果

+
+ + - - + // 加载外部样式 + layui.link('https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css', function(link) { + console.log('prism.min.css loaded'); + }, 'prism'); + + diff --git a/examples/js/index.js b/examples/extends/index.js similarity index 62% rename from examples/js/index.js rename to examples/extends/index.js index e4eca313..e6fbbd77 100644 --- a/examples/js/index.js +++ b/examples/extends/index.js @@ -1,8 +1,8 @@ layui.define(function(exports){ - + exports('index', { - title: '模块入口' + title: 'index 扩展模块' }); -}); \ No newline at end of file +}); diff --git a/examples/extends/temp.js b/examples/extends/temp.js deleted file mode 100644 index d7487d59..00000000 --- a/examples/extends/temp.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - - @Name:layui.modDemo XX组件 - @Author:贤心 - @License:MIT - - */ - -layui.define(['laytpl'], function(exports){ - "use strict"; - - var $ = layui.$ - ,laytpl = layui.laytpl - - //模块名 - ,MOD_NAME = 'modDemo' - - //外部接口 - ,modeDemo = { - config: {} - ,index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0 - - //设置全局项 - ,set: function(options){ - var that = this; - that.config = $.extend({}, that.config, options); - return that; - } - - //事件监听 - ,on: function(events, callback){ - return layui.onevent.call(this, MOD_NAME, events, callback); - } - } - - //操作当前实例 - ,thisModule = function(){ - var that = this - ,options = that.config - ,id = options.id || that.index; - - thisModule.that[id] = that; //记录当前实例对象 - thisModule.config[id] = options; //记录当前实例配置项 - - return { - config: options - //重置实例 - ,reload: function(options){ - that.reload.call(that, options); - } - } - } - - //获取当前实例配置项 - ,getThisModuleConfig = function(id){ - var config = thisModule.config[id]; - if(!config) hint.error('The ID option was not found in the '+ MOD_NAME +' instance'); - return config || null; - } - - //字符常量 - ,ELEM = 'layui-modeDemo' - - - //主模板 - ,TPL_MAIN = ['
' - - ,'
'].join('') - - //构造器 - ,Class = function(options){ - var that = this; - that.index = ++transfer.index; - that.config = $.extend({}, that.config, transfer.config, options); - that.render(); - }; - - //默认配置 - Class.prototype.config = { - - }; - - //重载实例 - Class.prototype.reload = function(options){ - var that = this; - - layui.each(options, function(key, item){ - if(item.constructor === Array) delete that.config[key]; - }); - - that.config = $.extend(true, {}, that.config, options); - that.render(); - }; - - //渲染 - Class.prototype.render = function(){ - var that = this - ,options = that.config; - - //解析模板 - that.elem = $(TPL_MAIN); - - var othis = options.elem = $(options.elem); - if(!othis[0]) return; - - //索引 - that.key = options.id || that.index; - - //插入组件结构 - othis.html(that.elem); - - that.events(); //事件 - }; - - //事件 - Class.prototype.events = function(){ - var that = this; - - - }; - - - - //记录所有实例 - thisModule.that = {}; //记录所有实例对象 - thisModule.config = {}; //记录所有实例配置项 - - //重载实例 - modeDemo.reload = function(id, options){ - var that = thisModule.that[id]; - that.reload(options); - - return thisModule.call(that); - }; - - //核心入口 - modeDemo.render = function(options){ - var inst = new Class(options); - return thisTransfer.call(inst); - }; - - exports(MOD_NAME, modeDemo); -}); diff --git a/examples/js/child/test.js b/examples/extends/test.js similarity index 53% rename from examples/js/child/test.js rename to examples/extends/test.js index 56083520..94fc194b 100644 --- a/examples/js/child/test.js +++ b/examples/extends/test.js @@ -1,8 +1,9 @@ - +/** + * test + */ layui.define(function(exports){ - exports('test', { - title: '子目录模块加载' + title: 'test 扩展模块' }) -}); \ No newline at end of file +}); diff --git a/examples/extends/test/test1.js b/examples/extends/test/test1.js new file mode 100644 index 00000000..f66f7d26 --- /dev/null +++ b/examples/extends/test/test1.js @@ -0,0 +1,9 @@ +/** + * test1 + */ + +layui.define(function(exports){ + exports('test1', { + title: 'test1 扩展模块' + }) +}); diff --git a/examples/extends/test/test2.js b/examples/extends/test/test2.js new file mode 100644 index 00000000..5a15c077 --- /dev/null +++ b/examples/extends/test/test2.js @@ -0,0 +1,9 @@ +/** + * test2 + */ + +layui.define(function(exports){ + exports('test2', { + title: 'test2 扩展模块' + }) +}); diff --git a/src/layui.js b/src/layui.js index 78928f05..1867ec03 100644 --- a/src/layui.js +++ b/src/layui.js @@ -4,28 +4,40 @@ * MIT Licensed */ -;!function(win){ - "use strict"; +(function(window) { + 'use strict'; - var doc = win.document; + // 便于打包时的字符压缩 + var document = window.document; + var location = window.location; + + // 基础配置 var config = { - modules: {}, // 模块物理路径 - status: {}, // 模块加载状态 timeout: 10, // 符合规范的模块请求最长等待秒数 - event: {} // 模块自定义事件 + debug: false, // 是否开启调试模式 + version: false // 是否在模块请求时加入版本号参数(以更新模块缓存) }; - var Layui = function(){ - this.v = '2.10.0'; // Layui 版本号 + // 模块加载缓存信息 + var cache = { + modules: {}, // 模块物理路径 + status: {}, // 模块加载就绪状态 + event: {}, // 模块自定义事件 + callback: {} // 模块的回调 + }; + + // constructor + var Class = function() { + this.v = '2.11.0-beta.1'; // 版本号 }; // 识别预先可能定义的指定全局对象 - var GLOBAL = win.LAYUI_GLOBAL || {}; + var GLOBAL = window.LAYUI_GLOBAL || {}; // 获取 layui 所在目录 - var getPath = function(){ - var jsPath = (doc.currentScript && doc.currentScript.tagName.toUpperCase() === 'SCRIPT') ? doc.currentScript.src : function(){ - var js = doc.getElementsByTagName('script'); + var getPath = function() { + var jsPath = (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') ? document.currentScript.src : function(){ + var js = document.getElementsByTagName('script'); var last = js.length - 1; var src; for(var i = last; i > 0; i--){ @@ -41,15 +53,13 @@ }(); // 异常提示 - var error = function(msg, type){ + var error = function(msg, type) { type = type || 'log'; - win.console && console[type] && console[type]('layui error hint: ' + msg); + window.console && console[type] && console[type]('layui error hint: ' + msg); }; - var isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]'; - // 内置模块 - var modules = config.builtin = { + var builtinModules = config.builtin = { lay: 'lay', // 基础 DOM 操作 layer: 'layer', // 弹层 laydate: 'laydate', // 日期 @@ -78,93 +88,174 @@ 'layui.all': 'layui.all' // 聚合标识(功能性的,非真实模块) }; - // 记录基础数据 - Layui.prototype.cache = config; + /** + * 低版本浏览器适配 + * @see polyfill + */ - // 定义模块 - Layui.prototype.define = function(deps, factory){ + // Object.assign + if (typeof Object.assign !== 'function') { + Object.assign = function(target) { + var to = Object(target); + if (arguments.length < 2) return to; + + var sourcesIndex = 1; + for (; sourcesIndex < arguments.length; sourcesIndex++) { + var nextSource = arguments[sourcesIndex]; + if (!(nextSource === undefined || nextSource === null)) { + for (var nextKey in nextSource) { + // 确保属性是源对象自身的(而非来自继承) + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; + } + + /** + * 节点加载事件 + * @param {HTMLElement} node - script 或 link 节点 + * @param {Function} done + * @param {Function} error + */ + var onNodeLoad = function(node, done, error) { + // 资源加载完毕 + var onCompleted = function (e) { + var readyRegExp = /^(complete|loaded)$/; + if (e.type === 'load' || (readyRegExp.test((e.currentTarget || e.srcElement).readyState))) { + removeListener(); + typeof done === 'function' && done(e); + } + }; + // 资源加载失败 + var onError = function (e) { + removeListener(); + typeof error === 'function' && error(e); + }; + + // 移除事件 + var removeListener = function() { + if (node.detachEvent) { + node.detachEvent('onreadystatechange', onCompleted); + } else { + node.removeEventListener('load', onCompleted, false); + node.removeEventListener('error', onError, false); + } + }; + + // 添加事件 + if(node.attachEvent && !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0)){ + // 此处考虑到 IE9+ load 的稳定性,固仍然采用 onreadystatechange + node.attachEvent('onreadystatechange', onCompleted); + } else { + node.addEventListener('load', onCompleted, false); + node.addEventListener('error', onError, false); + } + }; + + // 或许配置及临时缓存信息 + Class.prototype.cache = Object.assign(config, cache); + + /** + * 全局配置 + * @param {Object} options + */ + Class.prototype.config = function(options) { + Object.assign(config, options); + return this; + }; + + /** + * 定义模块 + * @param {(string|string[])} deps - 依赖的模块列表 + * @param {Function} callback - 模块的回调 + */ + Class.prototype.define = function(deps, callback) { var that = this; - var type = typeof deps === 'function'; - var callback = function(){ - var setApp = function(app, exports){ - layui[app] = exports; - config.status[app] = true; + var useCallback = function() { + var setModule = function(mod, exports) { + layui[mod] = exports; // 将模块接口赋值在 layui 对象中 + cache.status[mod] = true; // 标记模块注册完成 }; - typeof factory === 'function' && factory(function(app, exports){ - setApp(app, exports); - config.callback[app] = function(){ - factory(setApp); + // 执行模块的回调 + typeof callback === 'function' && callback(function(mod, exports) { + setModule(mod, exports); + // 记录模块回调,以便需要时再执行 + cache.callback[mod] = function() { + callback(setModule); } }); return this; }; - type && ( - factory = deps, - deps = [] - ); + // 若未依赖模块 + if (typeof deps === 'function') { + callback = deps; + deps = []; + } - that.use(deps, callback, null, 'define'); + that.use(deps, useCallback, null, 'define'); return that; }; - // 使用特定模块 - Layui.prototype.use = function(apps, callback, exports, from){ + /** + * 使用模块 + * @param {(string|string[])} mods - 模块列表 + * @param {Function} callback - 回调 + */ + Class.prototype.use = function(mods, callback, exports, from) { var that = this; var dir = config.dir = config.dir ? config.dir : getPath; - var head = doc.getElementsByTagName('head')[0]; - apps = function(){ - if(typeof apps === 'string'){ - return [apps]; + // 整理模块队列 + mods = (function() { + if (typeof mods === 'string') { + return [mods]; } - // 当第一个参数为 function 时,则自动加载所有内置模块,且执行的回调即为该 function 参数; - else if(typeof apps === 'function'){ - callback = apps; + // 若第一个参数为 function ,则自动加载所有内置模块,且执行的回调即为该 function 参数; + else if(typeof mods === 'function') { + callback = mods; return ['all']; } - return apps; - }(); + return mods; + })(); - // 如果页面已经存在 jQuery 1.7+ 库且所定义的模块依赖 jQuery,则不加载内部 jquery 模块 - if(win.jQuery && jQuery.fn.on){ - that.each(apps, function(index, item){ - if(item === 'jquery'){ - apps.splice(index, 1); + // 获取 layui 静态资源所在 host + if (!config.host) { + config.host = (dir.match(/\/\/([\s\S]+?)\//)||['//'+ location.host +'/'])[0]; + } + + // 若参数异常 + if (!mods) return that; + + // 若页面已经存在 jQuery 且所定义的模块依赖 jQuery,则不加载内部 jquery 模块 + if (window.jQuery && jQuery.fn.on) { + that.each(mods, function(index, item) { + if (item === 'jquery') { + mods.splice(index, 1); } }); - layui.jquery = layui.$ = jQuery; + layui.jquery = layui.$ = window.jQuery; } - var item = apps[0]; - var timeout = 0; - + // 将模块的接口作为回调的参数传递 exports = exports || []; - // 静态资源host - config.host = config.host || (dir.match(/\/\/([\s\S]+?)\//)||['//'+ location.host +'/'])[0]; + // 加载当前队列的第一个模块 + var item = mods[0]; + var modInfo = that.modules[item]; // 当前模块信息 + // 是否为外部模块,即无需遵循 layui 轻量级模块规范的任意第三方组件。 + var isExternalModule = typeof modInfo === 'object'; - // 加载完毕 - function onScriptLoad(e, url){ - var readyRegExp = navigator.platform === 'PLaySTATION 3' ? /^complete$/ : /^(complete|loaded)$/ - if (e.type === 'load' || (readyRegExp.test((e.currentTarget || e.srcElement).readyState))) { - config.modules[item] = url; - head.removeChild(node); - (function poll() { - if(++timeout > config.timeout * 1000 / 4){ - return error(item + ' is not a valid module', 'error'); - } - config.status[item] ? onCallback() : setTimeout(poll, 4); - }()); - } - } - - // 回调 - function onCallback(){ + // 回调触发 + var onCallback = function () { exports.push(layui[item]); - apps.length > 1 ? - that.use(apps.slice(1), callback, exports, from) - : ( typeof callback === 'function' && function(){ + mods.length > 1 + ? that.use(mods.slice(1), callback, exports, from) + : (typeof callback === 'function' && function() { // 保证文档加载完毕再执行回调 if(layui.jquery && typeof layui.jquery === 'function' && from !== 'define'){ return layui.jquery(function(){ @@ -173,182 +264,241 @@ } callback.apply(layui, exports); }() ); - } + }; - // 如果引入了聚合板,内置的模块则不必重复加载 - if( apps.length === 0 || (layui['layui.all'] && modules[item]) ){ + // 回调轮询 + var pollCallback = function () { + var timeout = 0; // 超时计数器(秒) + var delay = 5; // 轮询等待毫秒数 + + // 轮询模块加载完毕状态 + (function poll() { + if (++timeout > config.timeout * 1000 / delay) { + return error(item + ' is not a valid module', 'error'); + }; + + // 根据模块加载完毕的标志来完成轮询 + // 若为任意外部模块,则标志为该模块接口是否存在; + // 若为遵循 layui 规范的模块,则标志为模块的 status 状态值 + (isExternalModule ? layui[item] = window[modInfo.api] : cache.status[item]) + ? onCallback() + : setTimeout(poll, delay); + })(); + }; + + // 若为发行版,则内置模块不必异步加载 + if (mods.length === 0 || (layui['layui.all'] && builtinModules[item])) { return onCallback(), that; } - /* - * 获取加载的模块 URL - * 如果是内置模块,则按照 dir 参数拼接模块路径 - * 如果是扩展模块,则判断模块路径值是否为 {/} 开头, - * 如果路径值是 {/} 开头,则模块路径即为后面紧跟的字符。 - * 否则,则按照 base 参数拼接模块路径 - */ + // 当前模块所在路径 + var modSrc = isExternalModule ? modInfo.src : modInfo; - var url = ( modules[item] ? (dir + 'modules/') - : (/^\{\/\}/.test(that.modules[item]) ? '' : (config.base || '')) - ) + (that.modules[item] || item) + '.js'; - url = url.replace(/^\{\/\}/, ''); + // 基础路径 + var basePath = builtinModules[item] + ? (dir + 'modules/') // 若为内置模块,则按照默认 dir 参数拼接模块 URL + : (modSrc ? '' : config.base); // 若为扩展模块,且模块路径已设置,则不必再重复拼接基础路径 - // 如果扩展模块(即:非内置模块)对象已经存在,则不必再加载 - if(!config.modules[item] && layui[item]){ - config.modules[item] = url; // 并记录起该扩展模块的 url + // 若从 layui.modules 为获取到模块路径, 则将传入的模块名视为路径名 + if (!modSrc) modSrc = item; + + // 过滤空格符和 .js 后缀 + modSrc = modSrc.replace(/\s/g, '').replace(/\.js[^\/\.]*$/, ''); + + // 拼接最终模块 URL + var url = basePath + modSrc + '.js'; + + // 若扩展模块对象已经存在,则不必再重复加载 + if(!cache.modules[item] && layui[item]){ + cache.modules[item] = url; // 并记录起该扩展模块的 url } // 首次加载模块 - if(!config.modules[item]){ - var node = doc.createElement('script'); + if (!cache.modules[item]) { + var head = document.getElementsByTagName('head')[0]; + var node = document.createElement('script'); node.async = true; - node.charset = 'utf-8'; - node.src = url + function(){ + node.charset = 'utf-8'; // 避免 IE9 的编码问题 + node.src = url + function() { var version = config.version === true - ? (config.v || (new Date()).getTime()) - : (config.version||''); + ? (config.v || (new Date()).getTime()) + : (config.version || ''); return version ? ('?v=' + version) : ''; }(); head.appendChild(node); - if(node.attachEvent && !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && !isOpera){ - node.attachEvent('onreadystatechange', function(e){ - onScriptLoad(e, url); - }); - } else { - node.addEventListener('load', function(e){ - onScriptLoad(e, url); - }, false); - } + // 节点加载事件 + onNodeLoad(node, function() { + head.removeChild(node); + pollCallback(); + }, function() { + head.removeChild(node); + }); - config.modules[item] = url; - } else { // 缓存 - (function poll() { - if(++timeout > config.timeout * 1000 / 4){ - return error(item + ' is not a valid module', 'error'); - } - (typeof config.modules[item] === 'string' && config.status[item]) - ? onCallback() - : setTimeout(poll, 4); - }()); + // 模块已首次加载的标记 + cache.modules[item] = url; + } else { // 再次 use 模块 + pollCallback(); } return that; }; - // 弃用原有的指定模块,以便重新扩展新的同名模块 - Layui.prototype.disuse = function(apps){ + // 记录全部模块 + Class.prototype.modules = Object.assign({}, builtinModules); + + /** + * 拓展模块 + * @param {Object} settings - 拓展模块的配置 + */ + Class.prototype.extend = function(settings) { var that = this; - apps = that.isArray(apps) ? apps : [apps]; - that.each(apps, function (index, item) { - if (!config.status[item]) { - // return error('module ' + item + ' is not exist'); + var base = config.base || ''; + var firstSymbolEXP = /^\{\/\}/; // 模块单独路径首字符表达式 + + settings = settings || {}; + + // 遍历拓展模块 + for (var modName in settings) { + if (that[modName] || that.modules[modName]) { // 验证模块是否被占用 + error('the '+ modName + ' module already exists, extend failure'); + } else { + var modInfo = settings[modName]; + // 若直接传入模块路径字符 + if (typeof modInfo === 'string') { + // 判断传入的模块路径是否特定首字符 + // 若存在特定首字符,则模块 URL 即为该首字符后面紧跟的字符 + // 否则,则按照 config.base 路径进行拼接 + if (firstSymbolEXP.test(modInfo)) base = ''; + modInfo = (base + modInfo).replace(firstSymbolEXP, ''); + } + that.modules[modName] = modInfo; } + } + + return that; + }; + + /** + * 弃用指定的模块,以便重新扩展新的同名模块。 + * @param {(string|string[])} mods - 模块列表 + */ + Class.prototype.disuse = function(mods) { + var that = this; + mods = that.isArray(mods) ? mods : [mods]; + that.each(mods, function (index, item) { delete that[item]; - delete modules[item]; + delete builtinModules[item]; delete that.modules[item]; - delete config.status[item]; - delete config.modules[item]; + delete cache.status[item]; + delete cache.modules[item]; }); return that; }; - // 获取节点的 style 属性值 - // currentStyle.getAttribute 参数为 camelCase 形式的字符串 - Layui.prototype.getStyle = function(node, name){ - var style = node.currentStyle ? node.currentStyle : win.getComputedStyle(node, null); + /** + * 获取节点的 style 属性值 + * currentStyle.getAttribute 参数为 camelCase 形式的字符串 + * @param {HTMLElement} node - 节点 + * @param {string} name - 属性名 + * @returns 属性值 + */ + Class.prototype.getStyle = function(node, name) { + var style = node.currentStyle ? node.currentStyle : window.getComputedStyle(node, null); return style.getPropertyValue ? style.getPropertyValue(name) : style.getAttribute(name.replace(/-(\w)/g, function(_, c){ return c ? c.toUpperCase() : '';})); }; - // css 外部加载器 - Layui.prototype.link = function(href, fn, cssname){ + /** + * CSS 外部加载器 + * @param {string} href - 外部 CSS 文件路径 + * @param {Function} callback - 回调函数 + * @param {string} id - 定义 link 标签的 id + */ + Class.prototype.link = function(href, callback, id) { var that = this; - var head = doc.getElementsByTagName('head')[0]; - var link = doc.createElement('link'); + var head = document.getElementsByTagName('head')[0]; + var link = document.createElement('link'); - if(typeof fn === 'string') cssname = fn; + // 若第二个参数为 string 类型,则该参数为 id + if (typeof callback === 'string') { + id = callback; + } - var app = (cssname || href).replace(/\.|\//g, ''); - var id = 'layuicss-'+ app; - var STAUTS_NAME = 'creating'; - var timeout = 0; + // 若加载多个 + if (typeof href === 'object') { + var isArr = that.type(id) === 'array'; + return that.each(href, function(index, value){ + that.link( + value, + index === href.length - 1 && callback, + isArr && id[index] + ); + }); + } + + // 若为传入 id ,则取路径 `//` 后面的字符拼接为 id,不含.与参数 + id = id || href.replace(/^(#|(http(s?)):\/\/|\/\/)|\.|\/|\?.+/g, ''); + id = 'layuicss-'+ id; link.href = href + (config.debug ? '?v='+new Date().getTime() : ''); link.rel = 'stylesheet'; link.id = id; - link.media = 'all'; - if(!doc.getElementById(id)){ + // 插入节点 + if (!document.getElementById(id)) { head.appendChild(link); } - if(typeof fn !== 'function') return that; + // 是否执行回调 + if (typeof callback !== 'function') { + return that; + } - // 轮询 css 是否加载完毕 - (function poll(status) { - var delay = 100; - var getLinkElem = doc.getElementById(id); // 获取动态插入的 link 元素 - - // 如果轮询超过指定秒数,则视为请求文件失败或 css 文件不符合规范 - if(++timeout > config.timeout * 1000 / delay){ - return error(href + ' timeout'); - } - - // css 加载就绪 - if(parseInt(that.getStyle(getLinkElem, 'width')) === 1989){ - // 如果参数来自于初始轮询(即未加载就绪时的),则移除 link 标签状态 - if(status === STAUTS_NAME) getLinkElem.removeAttribute('lay-status'); - // 如果 link 标签的状态仍为「创建中」,则继续进入轮询,直到状态改变,则执行回调 - getLinkElem.getAttribute('lay-status') === STAUTS_NAME ? setTimeout(poll, delay) : fn(); - } else { - getLinkElem.setAttribute('lay-status', STAUTS_NAME); - setTimeout(function(){ - poll(STAUTS_NAME); - }, delay); - } - }()); - - // 轮询css是否加载完毕 - /* - (function poll() { - if(++timeout > config.timeout * 1000 / 100){ - return error(href + ' timeout'); - }; - parseInt(that.getStyle(doc.getElementById(id), 'width')) === 1989 ? function(){ - fn(); - }() : setTimeout(poll, 100); - }()); - */ + onNodeLoad(link, function() { + callback(link); + }, function() { + error(href + ' load error', 'error'); + head.removeChild(link); // 移除节点 + }); return that; }; - // css 内部加载器 - Layui.prototype.addcss = function(firename, fn, cssname){ - return layui.link(config.dir + 'css/' + firename, fn, cssname); + /** + * CSS 内部加载器 + * @param {string} modName - 模块名 + */ + Class.prototype.addcss = function(modName, callback, id) { + return layui.link(config.dir + 'css/' + modName, callback, id); }; - // 存储模块的回调 - config.callback = {}; - - // 重新执行模块的工厂函数 - Layui.prototype.factory = function(modName){ - if(layui[modName]){ + /** + * 获取执行定义模块时的回调函数,factory 为向下兼容 + * @param {string} modName - 模块名 + * @returns {Function} + */ + Class.prototype.factory = function(modName) { + if (layui[modName]) { return typeof config.callback[modName] === 'function' ? config.callback[modName] : null; } }; - // 图片预加载 - Layui.prototype.img = function(url, callback, error) { + /** + * 图片预加载 + * @param {string} url - 图片路径 + * @param {Function} callback - 成功回调 + * @param {Function} error - 错误回调 + */ + Class.prototype.img = function(url, callback, error) { var img = new Image(); img.src = url; - if(img.complete){ + if (img.complete) { return callback(img); } img.onload = function(){ @@ -361,43 +511,12 @@ }; }; - // 全局配置 - Layui.prototype.config = function(options){ - options = options || {}; - for(var key in options){ - config[key] = options[key]; - } - return this; - }; - - // 记录全部模块 - Layui.prototype.modules = function(){ - var clone = {}; - for(var o in modules){ - clone[o] = modules[o]; - } - return clone; - }(); - - // 拓展模块 - Layui.prototype.extend = function(options){ - var that = this; - - // 验证模块是否被占用 - options = options || {}; - for(var o in options){ - if(that[o] || that.modules[o]){ - error(o+ ' Module already exists', 'error'); - } else { - that.modules[o] = options[o]; - } - } - - return that; - }; - - // location.hash 路由解析 - Layui.prototype.router = Layui.prototype.hash = function(hash){ + /** + * location.hash 路由解析 + * @param {string} hash 值 + * @returns {Object} + */ + Class.prototype.router = Class.prototype.hash = function(hash) { var that = this; var hash = hash || location.hash; var data = { @@ -414,7 +533,7 @@ hash = hash.replace(/([^#])(#.*$)/, '$1').split('/') || []; // 提取 Hash 结构 - that.each(hash, function(index, item){ + that.each(hash, function(index, item) { /^\w+=/.test(item) ? function(){ item = item.split('='); data.search[item[0]] = item[1]; @@ -424,12 +543,16 @@ return data; }; - // URL 解析 - Layui.prototype.url = function(href){ + /** + * URL 解析 + * @param {string} href - url 路径 + * @returns {Object} + */ + Class.prototype.url = function(href) { var that = this; var data = { // 提取 url 路径 - pathname: function(){ + pathname: function() { var pathname = href ? function(){ var str = (href.match(/\.[^.]+?\/.+/) || [])[0] || ''; @@ -451,19 +574,19 @@ ).replace(/^\?+/, '').split('&'); // 去除 ?,按 & 分割参数 // 遍历分割后的参数 - that.each(search, function(index, item){ - var _index = item.indexOf('=') - ,key = function(){ // 提取 key - if(_index < 0){ + that.each(search, function(index, item) { + var _index = item.indexOf('='); + var key = function() { // 提取 key + if (_index < 0) { return item.substr(0, item.length); - } else if(_index === 0){ + } else if(_index === 0) { return false; } else { return item.substr(0, _index); } }(); // 提取 value - if(key){ + if (key) { obj[key] = _index > 0 ? item.substr(_index + 1) : null; } }); @@ -472,7 +595,7 @@ }(), // 提取 Hash - hash: that.router(function(){ + hash: that.router(function() { return href ? ((href.match(/#.+/) || [])[0] || '/') : location.hash; @@ -482,15 +605,19 @@ return data; }; - // 本地持久存储 - Layui.prototype.data = function(table, settings, storage){ + /** + * 本地持久存储 + * @param {string} table - 表名 + * @param {Object} settings - 设置项 + * @param {Storage} storage - 存储对象,localStorage 或 sessionStorage + * @returns {Object} + */ + Class.prototype.data = function(table, settings, storage) { table = table || 'layui'; storage = storage || localStorage; - if(!win.JSON || !win.JSON.parse) return; - // 如果 settings 为 null,则删除表 - if(settings === null){ + if (settings === null) { return delete storage[table]; } @@ -504,24 +631,33 @@ var data = {}; } - if('value' in settings) data[settings.key] = settings.value; - if(settings.remove) delete data[settings.key]; + if ('value' in settings) data[settings.key] = settings.value; + if (settings.remove) delete data[settings.key]; storage[table] = JSON.stringify(data); return settings.key ? data[settings.key] : data; }; - // 本地临时存储 - Layui.prototype.sessionData = function(table, settings){ + /** + * 本地临时存储 + * @param {string} table - 表名 + * @param {Object} settings - 设置项 + * @returns {Object} + */ + Class.prototype.sessionData = function(table, settings) { return this.data(table, settings, sessionStorage); } - // 设备信息 - Layui.prototype.device = function(key){ + /** + * 设备信息 + * @param {string} key - 任意 key + * @returns {Object} + */ + Class.prototype.device = function(key) { var agent = navigator.userAgent.toLowerCase(); // 获取版本号 - var getVersion = function(label){ + var getVersion = function(label) { var exp = new RegExp(label + '/([^\\s\\_\\-]+)'); label = (agent.match(exp)||[])[1]; return label || false; @@ -529,19 +665,19 @@ // 返回结果集 var result = { - os: function(){ // 底层操作系统 - if(/windows/.test(agent)){ + os: function() { // 底层操作系统 + if (/windows/.test(agent)) { return 'windows'; - } else if(/linux/.test(agent)){ + } else if(/linux/.test(agent)) { return 'linux'; - } else if(/iphone|ipod|ipad|ios/.test(agent)){ + } else if(/iphone|ipod|ipad|ios/.test(agent)) { return 'ios'; - } else if(/mac/.test(agent)){ + } else if(/mac/.test(agent)) { return 'mac'; } }(), - ie: function(){ // ie 版本 - return (!!win.ActiveXObject || "ActiveXObject" in win) ? ( + ie: function() { // ie 版本 + return (!!window.ActiveXObject || "ActiveXObject" in window) ? ( (agent.match(/msie\s(\d+)/) || [])[1] || '11' // 由于 ie11 并没有 msie 的标识 ) : false; }(), @@ -549,7 +685,7 @@ }; // 任意的 key - if(key && !result[key]){ + if (key && !result[key]) { result[key] = getVersion(key); } @@ -562,18 +698,22 @@ }; // 提示 - Layui.prototype.hint = function(){ + Class.prototype.hint = function() { return { error: error }; }; - // typeof 类型细分 -> string/number/boolean/undefined/null、object/array/function/… - Layui.prototype._typeof = Layui.prototype.type = function(operand){ + /** + * typeof 类型细分 -> string/number/boolean/undefined/null、object/array/function/… + * @param {*} operand - 任意值 + * @returns {string} + */ + Class.prototype._typeof = Class.prototype.type = function(operand) { if(operand === null) return String(operand); // 细分引用类型 - return (typeof operand === 'object' || typeof operand === 'function') ? function(){ + return (typeof operand === 'object' || typeof operand === 'function') ? function() { var type = Object.prototype.toString.call(operand).match(/\s(.+)\]$/) || []; // 匹配类型字符 var classType = 'Function|Array|Date|RegExp|Object|Error|Symbol'; // 常见类型字符 @@ -586,13 +726,17 @@ }() : typeof operand; }; - // 对象是否具备数组结构(此处为兼容 jQuery 对象) - Layui.prototype._isArray = Layui.prototype.isArray = function(obj){ + /** + * 对象是否具备数组结构(此处为兼容 jQuery 对象) + * @param {Object} obj - 任意对象 + * @returns {boolean} + */ + Class.prototype._isArray = Class.prototype.isArray = function(obj) { var that = this; var len; var type = that.type(obj); - if(!obj || (typeof obj !== 'object') || obj === win) return false; + if (!obj || (typeof obj !== 'object') || obj === window) return false; len = 'length' in obj && obj.length; // 兼容 ie return type === 'array' || len === 0 || ( @@ -600,47 +744,61 @@ ); }; - // 遍历 - Layui.prototype.each = function(obj, fn){ + /** + * 遍历 + * @param {Object} obj - 任意对象 + * @param {Function} fn - 遍历回调 + */ + Class.prototype.each = function(obj, fn) { var key; var that = this; - var callFn = function(key, obj){ // 回调 - return fn.call(obj[key], key, obj[key]) + var callback = function(key, obj) { + return fn.call(obj[key], key, obj[key]); }; - if(typeof fn !== 'function') return that; + if (typeof fn !== 'function') { + return that; + } + obj = obj || []; // 优先处理数组结构 - if(that.isArray(obj)){ - for(key = 0; key < obj.length; key++){ - if(callFn(key, obj)) break; + if (that.isArray(obj)) { + for (key = 0; key < obj.length; key++) { + if(callback(key, obj)) break; } } else { - for(key in obj){ - if(callFn(key, obj)) break; + for (key in obj) { + if(callback(key, obj)) break; } } return that; }; - // 将数组中的成员对象按照某个 key 的 value 值进行排序 - Layui.prototype.sort = function(arr, key, desc, notClone){ + /** + * 将数组中的成员对象按照某个 key 的 value 值进行排序 + * @param {Object[]} arr - 任意数组 + * @param {string} key - 任意 key + * @param {boolean} desc - 是否降序 + * @param {boolean} notClone - 是否不对 arr 进行克隆 + * @returns {Object[]} + */ + Class.prototype.sort = function(arr, key, desc, notClone) { var that = this; var clone = notClone ? (arr || []) : JSON.parse( JSON.stringify(arr || []) ); // 若未传入 key,则直接返回原对象 - if(that.type(arr) === 'object' && !key){ + if (that.type(arr) === 'object' && !key) { return clone; - } else if(typeof arr !== 'object'){ // 若 arr 非对象 + } else if(typeof arr !== 'object') { // 若 arr 非对象 return [clone]; } // 开始排序 - clone.sort(function(o1, o2){ + clone.sort(function(o1, o2) { var v1 = o1[key]; var v2 = o2[key]; @@ -650,16 +808,17 @@ */ // 若比较的成员均为数字 - if(!isNaN(o1) && !isNaN(o2)) return o1 - o2; + if (!isNaN(o1) && !isNaN(o2)) return o1 - o2; + // 若比较的成员只存在某一个非对象 - if(!isNaN(o1) && isNaN(o2)){ + if (!isNaN(o1) && isNaN(o2)) { if(key && typeof o2 === 'object'){ v1 = o1; } else { return -1; } - } else if (isNaN(o1) && !isNaN(o2)){ - if(key && typeof o1 === 'object'){ + } else if (isNaN(o1) && !isNaN(o2)) { + if (key && typeof o1 === 'object') { v2 = o2; } else { return 1; @@ -676,10 +835,10 @@ var isNum = [!isNaN(v1), !isNaN(v2)]; // 若为数字比较 - if(isNum[0] && isNum[1]){ - if(v1 && (!v2 && v2 !== 0)){ // 数字 vs 空 + if (isNum[0] && isNum[1]) { + if(v1 && (!v2 && v2 !== 0)) { // 数字 vs 空 return 1; - } else if((!v1 && v1 !== 0) && v2){ // 空 vs 数字 + } else if((!v1 && v1 !== 0) && v2) { // 空 vs 数字 return -1; } else { // 数字 vs 数字 return v1 - v2; @@ -691,9 +850,9 @@ */ // 若为非数字比较 - if(!isNum[0] && !isNum[1]){ + if (!isNum[0] && !isNum[1]) { // 字典序比较 - if(v1 > v2){ + if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; @@ -703,7 +862,7 @@ } // 若为混合比较 - if(isNum[0] || !isNum[1]){ // 数字 vs 非数字 + if (isNum[0] || !isNum[1]) { // 数字 vs 非数字 return -1; } else if(!isNum[0] || isNum[1]) { // 非数字 vs 数字 return 1; @@ -715,10 +874,14 @@ return clone; }; - // 阻止事件冒泡 - Layui.prototype.stope = function(thisEvent){ - thisEvent = thisEvent || win.event; - try { thisEvent.stopPropagation() } catch(e){ + /** + * 阻止事件冒泡 + * @param {Event} thisEvent - 事件对象 + */ + Class.prototype.stope = function(thisEvent) { + try { + thisEvent.stopPropagation(); + } catch(e) { thisEvent.cancelBubble = true; } }; @@ -726,51 +889,63 @@ // 字符常理 var EV_REMOVE = 'LAYUI-EVENT-REMOVE'; - // 自定义模块事件 - Layui.prototype.onevent = function(modName, events, callback){ - if(typeof modName !== 'string' - || typeof callback !== 'function') return this; - - return Layui.event(modName, events, null, callback); + /** + * 自定义模块事件 + * @param {string} modName - 模块名 + * @param {string} events - 事件名 + * @param {Function} callback - 回调 + * @returns {Object} + */ + Class.prototype.onevent = function(modName, events, callback) { + if (typeof modName !== 'string' || typeof callback !== 'function') { + return this; + } + return Class.event(modName, events, null, callback); }; - // 执行自定义模块事件 - Layui.prototype.event = Layui.event = function(modName, events, params, fn){ + /** + * 执行自定义模块事件 + * @param {string} modName - 模块名 + * @param {string} events - 事件名 + * @param {Object} params - 参数 + * @param {Function} fn - 回调 + */ + Class.prototype.event = Class.event = function(modName, events, params, fn) { var that = this; var result = null; var filter = (events || '').match(/\((.*)\)$/)||[]; // 提取事件过滤器字符结构,如:select(xxx) var eventName = (modName + '.'+ events).replace(filter[0], ''); // 获取事件名称,如:form.select - var filterName = filter[1] || ''; // 获取过滤器名称,,如:xxx - var callback = function(_, item){ + var filterName = filter[1] || ''; // 获取过滤器名称, 如:xxx + var callback = function(_, item) { var res = item && item.call(that, params); res === false && result === null && (result = false); }; // 如果参数传入特定字符,则执行移除事件 - if(params === EV_REMOVE){ + if (params === EV_REMOVE) { delete (that.cache.event[eventName] || {})[filterName]; return that; } // 添加事件 - if(fn){ - config.event[eventName] = config.event[eventName] || {}; + if (fn) { + cache.event[eventName] = cache.event[eventName] || {}; if (filterName) { - // 带filter不支持重复事件 - config.event[eventName][filterName] = [fn]; + // 带 filter 不支持重复事件 + cache.event[eventName][filterName] = [fn]; } else { - // 不带filter处理的是所有的同类事件,应该支持重复事件 - config.event[eventName][filterName] = config.event[eventName][filterName] || []; - config.event[eventName][filterName].push(fn); + // 不带 filter 处理的是所有的同类事件,应该支持重复事件 + cache.event[eventName][filterName] = cache.event[eventName][filterName] || []; + cache.event[eventName][filterName].push(fn); } return this; } // 执行事件回调 - layui.each(config.event[eventName], function(key, item){ + layui.each(cache.event[eventName], function(key, item) { // 执行当前模块的全部事件 - if(filterName === '{*}'){ + if (filterName === '{*}') { layui.each(item, callback); return; } @@ -783,20 +958,36 @@ return result; }; - // 新增模块事件 - Layui.prototype.on = function(events, modName, callback){ + /** + * 新增模块事件 + * @param {string} events - 事件名 + * @param {string} modName - 模块名 + * @param {Function} callback - 回调 + * @returns {Object} + */ + Class.prototype.on = function(events, modName, callback) { var that = this; return that.onevent.call(that, modName, events, callback); } - // 移除模块事件 - Layui.prototype.off = function(events, modName){ + /** + * 移除模块事件 + * @param {string} events - 事件名 + * @param {string} modName - 模块名 + * @returns {Object} + */ + Class.prototype.off = function(events, modName) { var that = this; return that.event.call(that, modName, events, EV_REMOVE); }; - // 防抖 - Layui.prototype.debounce = function (func, wait) { + /** + * 防抖 + * @param {Function} func - 回调 + * @param {number} wait - 延时执行的毫秒数 + * @returns {Function} + */ + Class.prototype.debounce = function (func, wait) { var timeout; return function () { var context = this; @@ -808,8 +999,12 @@ } }; - // 节流 - Layui.prototype.throttle = function (func, wait) { + /** + * 节流 + * @param {Function} func - 回调 + * @param {number} wait - 不重复执行的毫秒数 + */ + Class.prototype.throttle = function (func, wait) { var cooldown = false; return function () { var context = this; @@ -824,8 +1019,6 @@ } }; - // exports layui - win.layui = new Layui(); - -}(window); // gulp build: layui-footer - + // export layui + window.layui = new Class(); +})(window);