layui.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. /*!
  2. @Name: layui
  3. @Description:经典模块化前端 UI 框架
  4. @License:MIT
  5. */
  6. ;!function(win){
  7. "use strict";
  8. var doc = document, config = {
  9. modules: {} //记录模块物理路径
  10. ,status: {} //记录模块加载状态
  11. ,timeout: 10 //符合规范的模块请求最长等待秒数
  12. ,event: {} //记录模块自定义事件
  13. }
  14. ,Layui = function(){
  15. this.v = '2.5.7'; //版本号
  16. }
  17. //获取layui所在目录
  18. ,getPath = function(){
  19. var jsPath = doc.currentScript ? doc.currentScript.src : function(){
  20. var js = doc.scripts
  21. ,last = js.length - 1
  22. ,src;
  23. for(var i = last; i > 0; i--){
  24. if(js[i].readyState === 'interactive'){
  25. src = js[i].src;
  26. break;
  27. }
  28. }
  29. return src || js[last].src;
  30. }();
  31. return jsPath.substring(0, jsPath.lastIndexOf('/') + 1);
  32. }()
  33. //异常提示
  34. ,error = function(msg){
  35. win.console && console.error && console.error('Layui hint: ' + msg);
  36. }
  37. ,isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]'
  38. //内置模块
  39. ,modules = {
  40. layer: 'modules/layer' //弹层
  41. ,laydate: 'modules/laydate' //日期
  42. ,laypage: 'modules/laypage' //分页
  43. ,laytpl: 'modules/laytpl' //模板引擎
  44. ,layim: 'modules/layim' //web通讯
  45. ,layedit: 'modules/layedit' //富文本编辑器
  46. ,form: 'modules/form' //表单集
  47. ,upload: 'modules/upload' //上传
  48. ,transfer: 'modules/transfer' //上传
  49. ,tree: 'modules/tree' //树结构
  50. ,table: 'modules/table' //表格
  51. ,element: 'modules/element' //常用元素操作
  52. ,rate: 'modules/rate' //评分组件
  53. ,colorpicker: 'modules/colorpicker' //颜色选择器
  54. ,slider: 'modules/slider' //滑块
  55. ,carousel: 'modules/carousel' //轮播
  56. ,flow: 'modules/flow' //流加载
  57. ,util: 'modules/util' //工具块
  58. ,code: 'modules/code' //代码修饰器
  59. ,jquery: 'modules/jquery' //DOM库(第三方)
  60. ,mobile: 'modules/mobile' //移动大模块 | 若当前为开发目录,则为移动模块入口,否则为移动模块集合
  61. ,'layui.all': '../layui.all' //PC模块合并版
  62. };
  63. //记录基础数据
  64. Layui.prototype.cache = config;
  65. //定义模块
  66. Layui.prototype.define = function(deps, factory){
  67. var that = this
  68. ,type = typeof deps === 'function'
  69. ,callback = function(){
  70. var setApp = function(app, exports){
  71. layui[app] = exports;
  72. config.status[app] = true;
  73. };
  74. typeof factory === 'function' && factory(function(app, exports){
  75. setApp(app, exports);
  76. config.callback[app] = function(){
  77. factory(setApp);
  78. }
  79. });
  80. return this;
  81. };
  82. type && (
  83. factory = deps,
  84. deps = []
  85. );
  86. if((!layui['layui.all'] && layui['layui.mobile'])){
  87. return callback.call(that);
  88. }
  89. that.use(deps, callback);
  90. return that;
  91. };
  92. //使用特定模块
  93. Layui.prototype.use = function(apps, callback, exports){
  94. var that = this
  95. ,dir = config.dir = config.dir ? config.dir : getPath
  96. ,head = doc.getElementsByTagName('head')[0];
  97. apps = typeof apps === 'string' ? [apps] : apps;
  98. //如果页面已经存在 jQuery 1.7+ 库且所定义的模块依赖 jQuery,则不加载内部 jquery 模块
  99. if(window.jQuery && jQuery.fn.on){
  100. that.each(apps, function(index, item){
  101. if(item === 'jquery'){
  102. apps.splice(index, 1);
  103. }
  104. });
  105. layui.jquery = layui.$ = jQuery;
  106. }
  107. var item = apps[0]
  108. ,timeout = 0;
  109. exports = exports || [];
  110. //静态资源host
  111. config.host = config.host || (dir.match(/\/\/([\s\S]+?)\//)||['//'+ location.host +'/'])[0];
  112. //加载完毕
  113. function onScriptLoad(e, url){
  114. var readyRegExp = navigator.platform === 'PLaySTATION 3' ? /^complete$/ : /^(complete|loaded)$/
  115. if (e.type === 'load' || (readyRegExp.test((e.currentTarget || e.srcElement).readyState))) {
  116. config.modules[item] = url;
  117. head.removeChild(node);
  118. (function poll() {
  119. if(++timeout > config.timeout * 1000 / 4){
  120. return error(item + ' is not a valid module');
  121. };
  122. config.status[item] ? onCallback() : setTimeout(poll, 4);
  123. }());
  124. }
  125. }
  126. //回调
  127. function onCallback(){
  128. exports.push(layui[item]);
  129. apps.length > 1 ?
  130. that.use(apps.slice(1), callback, exports)
  131. : ( typeof callback === 'function' && callback.apply(layui, exports) );
  132. }
  133. //如果引入了完整库(layui.all.js),内置的模块则不必再加载
  134. if(apps.length === 0
  135. || (layui['layui.all'] && modules[item])
  136. || (!layui['layui.all'] && layui['layui.mobile'] && modules[item])
  137. ){
  138. return onCallback(), that;
  139. }
  140. //获取加载的模块 URL
  141. //如果是内置模块,则按照 dir 参数拼接模块路径
  142. //如果是扩展模块,则判断模块路径值是否为 {/} 开头,
  143. //如果路径值是 {/} 开头,则模块路径即为后面紧跟的字符。
  144. //否则,则按照 base 参数拼接模块路径
  145. var url = ( modules[item] ? (dir + 'lay/')
  146. : (/^\{\/\}/.test(that.modules[item]) ? '' : (config.base || ''))
  147. ) + (that.modules[item] || item) + '.js';
  148. url = url.replace(/^\{\/\}/, '');
  149. //如果扩展模块(即:非内置模块)对象已经存在,则不必再加载
  150. if(!config.modules[item] && layui[item]){
  151. config.modules[item] = url; //并记录起该扩展模块的 url
  152. }
  153. //首次加载模块
  154. if(!config.modules[item]){
  155. var node = doc.createElement('script');
  156. node.async = true;
  157. node.charset = 'utf-8';
  158. node.src = url + function(){
  159. var version = config.version === true
  160. ? (config.v || (new Date()).getTime())
  161. : (config.version||'');
  162. return version ? ('?v=' + version) : '';
  163. }();
  164. head.appendChild(node);
  165. if(node.attachEvent && !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && !isOpera){
  166. node.attachEvent('onreadystatechange', function(e){
  167. onScriptLoad(e, url);
  168. });
  169. } else {
  170. node.addEventListener('load', function(e){
  171. onScriptLoad(e, url);
  172. }, false);
  173. }
  174. config.modules[item] = url;
  175. } else { //缓存
  176. (function poll() {
  177. if(++timeout > config.timeout * 1000 / 4){
  178. return error(item + ' is not a valid module');
  179. };
  180. (typeof config.modules[item] === 'string' && config.status[item])
  181. ? onCallback()
  182. : setTimeout(poll, 4);
  183. }());
  184. }
  185. return that;
  186. };
  187. //获取节点的style属性值
  188. Layui.prototype.getStyle = function(node, name){
  189. var style = node.currentStyle ? node.currentStyle : win.getComputedStyle(node, null);
  190. return style[style.getPropertyValue ? 'getPropertyValue' : 'getAttribute'](name);
  191. };
  192. //css外部加载器
  193. Layui.prototype.link = function(href, fn, cssname){
  194. var that = this
  195. ,link = doc.createElement('link')
  196. ,head = doc.getElementsByTagName('head')[0];
  197. if(typeof fn === 'string') cssname = fn;
  198. var app = (cssname || href).replace(/\.|\//g, '')
  199. ,id = link.id = 'layuicss-'+app
  200. ,timeout = 0;
  201. link.rel = 'stylesheet';
  202. link.href = href + (config.debug ? '?v='+new Date().getTime() : '');
  203. link.media = 'all';
  204. if(!doc.getElementById(id)){
  205. head.appendChild(link);
  206. }
  207. if(typeof fn !== 'function') return that;
  208. //轮询css是否加载完毕
  209. (function poll() {
  210. if(++timeout > config.timeout * 1000 / 100){
  211. return error(href + ' timeout');
  212. };
  213. parseInt(that.getStyle(doc.getElementById(id), 'width')) === 1989 ? function(){
  214. fn();
  215. }() : setTimeout(poll, 100);
  216. }());
  217. return that;
  218. };
  219. //存储模块的回调
  220. config.callback = {};
  221. //重新执行模块的工厂函数
  222. Layui.prototype.factory = function(modName){
  223. if(layui[modName]){
  224. return typeof config.callback[modName] === 'function'
  225. ? config.callback[modName]
  226. : null;
  227. }
  228. };
  229. //css内部加载器
  230. Layui.prototype.addcss = function(firename, fn, cssname){
  231. return layui.link(config.dir + 'css/' + firename, fn, cssname);
  232. };
  233. //图片预加载
  234. Layui.prototype.img = function(url, callback, error) {
  235. var img = new Image();
  236. img.src = url;
  237. if(img.complete){
  238. return callback(img);
  239. }
  240. img.onload = function(){
  241. img.onload = null;
  242. typeof callback === 'function' && callback(img);
  243. };
  244. img.onerror = function(e){
  245. img.onerror = null;
  246. typeof error === 'function' && error(e);
  247. };
  248. };
  249. //全局配置
  250. Layui.prototype.config = function(options){
  251. options = options || {};
  252. for(var key in options){
  253. config[key] = options[key];
  254. }
  255. return this;
  256. };
  257. //记录全部模块
  258. Layui.prototype.modules = function(){
  259. var clone = {};
  260. for(var o in modules){
  261. clone[o] = modules[o];
  262. }
  263. return clone;
  264. }();
  265. //拓展模块
  266. Layui.prototype.extend = function(options){
  267. var that = this;
  268. //验证模块是否被占用
  269. options = options || {};
  270. for(var o in options){
  271. if(that[o] || that.modules[o]){
  272. error('\u6A21\u5757\u540D '+ o +' \u5DF2\u88AB\u5360\u7528');
  273. } else {
  274. that.modules[o] = options[o];
  275. }
  276. }
  277. return that;
  278. };
  279. // location.hash 路由解析
  280. Layui.prototype.router = function(hash){
  281. var that = this
  282. ,hash = hash || location.hash
  283. ,data = {
  284. path: []
  285. ,search: {}
  286. ,hash: (hash.match(/[^#](#.*$)/) || [])[1] || ''
  287. };
  288. if(!/^#\//.test(hash)) return data; //禁止非路由规范
  289. hash = hash.replace(/^#\//, '');
  290. data.href = '/' + hash;
  291. hash = hash.replace(/([^#])(#.*$)/, '$1').split('/') || [];
  292. //提取 Hash 结构
  293. that.each(hash, function(index, item){
  294. /^\w+=/.test(item) ? function(){
  295. item = item.split('=');
  296. data.search[item[0]] = item[1];
  297. }() : data.path.push(item);
  298. });
  299. return data;
  300. };
  301. //URL 解析
  302. Layui.prototype.url = function(href){
  303. var that = this
  304. ,data = {
  305. //提取 url 路径
  306. pathname: function(){
  307. var pathname = href
  308. ? function(){
  309. var str = (href.match(/\.[^.]+?\/.+/) || [])[0] || '';
  310. return str.replace(/^[^\/]+/, '').replace(/\?.+/, '');
  311. }()
  312. : location.pathname;
  313. return pathname.replace(/^\//, '').split('/');
  314. }()
  315. //提取 url 参数
  316. ,search: function(){
  317. var obj = {}
  318. ,search = (href
  319. ? function(){
  320. var str = (href.match(/\?.+/) || [])[0] || '';
  321. return str.replace(/\#.+/, '');
  322. }()
  323. : location.search
  324. ).replace(/^\?+/, '').split('&'); //去除 ?,按 & 分割参数
  325. //遍历分割后的参数
  326. that.each(search, function(index, item){
  327. var _index = item.indexOf('=')
  328. ,key = function(){ //提取 key
  329. if(_index < 0){
  330. return item.substr(0, item.length);
  331. } else if(_index === 0){
  332. return false;
  333. } else {
  334. return item.substr(0, _index);
  335. }
  336. }();
  337. //提取 value
  338. if(key){
  339. obj[key] = _index > 0 ? item.substr(_index + 1) : null;
  340. }
  341. });
  342. return obj;
  343. }()
  344. //提取 Hash
  345. ,hash: that.router(function(){
  346. return href
  347. ? ((href.match(/#.+/) || [])[0] || '')
  348. : location.hash;
  349. }())
  350. };
  351. return data;
  352. };
  353. //本地持久性存储
  354. Layui.prototype.data = function(table, settings, storage){
  355. table = table || 'layui';
  356. storage = storage || localStorage;
  357. if(!win.JSON || !win.JSON.parse) return;
  358. //如果settings为null,则删除表
  359. if(settings === null){
  360. return delete storage[table];
  361. }
  362. settings = typeof settings === 'object'
  363. ? settings
  364. : {key: settings};
  365. try{
  366. var data = JSON.parse(storage[table]);
  367. } catch(e){
  368. var data = {};
  369. }
  370. if('value' in settings) data[settings.key] = settings.value;
  371. if(settings.remove) delete data[settings.key];
  372. storage[table] = JSON.stringify(data);
  373. return settings.key ? data[settings.key] : data;
  374. };
  375. //本地会话性存储
  376. Layui.prototype.sessionData = function(table, settings){
  377. return this.data(table, settings, sessionStorage);
  378. }
  379. //设备信息
  380. Layui.prototype.device = function(key){
  381. var agent = navigator.userAgent.toLowerCase()
  382. //获取版本号
  383. ,getVersion = function(label){
  384. var exp = new RegExp(label + '/([^\\s\\_\\-]+)');
  385. label = (agent.match(exp)||[])[1];
  386. return label || false;
  387. }
  388. //返回结果集
  389. ,result = {
  390. os: function(){ //底层操作系统
  391. if(/windows/.test(agent)){
  392. return 'windows';
  393. } else if(/linux/.test(agent)){
  394. return 'linux';
  395. } else if(/iphone|ipod|ipad|ios/.test(agent)){
  396. return 'ios';
  397. } else if(/mac/.test(agent)){
  398. return 'mac';
  399. }
  400. }()
  401. ,ie: function(){ //ie版本
  402. return (!!win.ActiveXObject || "ActiveXObject" in win) ? (
  403. (agent.match(/msie\s(\d+)/) || [])[1] || '11' //由于ie11并没有msie的标识
  404. ) : false;
  405. }()
  406. ,weixin: getVersion('micromessenger') //是否微信
  407. };
  408. //任意的key
  409. if(key && !result[key]){
  410. result[key] = getVersion(key);
  411. }
  412. //移动设备
  413. result.android = /android/.test(agent);
  414. result.ios = result.os === 'ios';
  415. result.mobile = (result.android || result.ios) ? true : false;
  416. return result;
  417. };
  418. //提示
  419. Layui.prototype.hint = function(){
  420. return {
  421. error: error
  422. }
  423. };
  424. //遍历
  425. Layui.prototype.each = function(obj, fn){
  426. var key
  427. ,that = this;
  428. if(typeof fn !== 'function') return that;
  429. obj = obj || [];
  430. if(obj.constructor === Object){
  431. for(key in obj){
  432. if(fn.call(obj[key], key, obj[key])) break;
  433. }
  434. } else {
  435. for(key = 0; key < obj.length; key++){
  436. if(fn.call(obj[key], key, obj[key])) break;
  437. }
  438. }
  439. return that;
  440. };
  441. //将数组中的对象按其某个成员排序
  442. Layui.prototype.sort = function(obj, key, desc){
  443. var clone = JSON.parse(
  444. JSON.stringify(obj || [])
  445. );
  446. if(!key) return clone;
  447. //如果是数字,按大小排序,如果是非数字,按字典序排序
  448. clone.sort(function(o1, o2){
  449. var isNum = /^-?\d+$/
  450. ,v1 = o1[key]
  451. ,v2 = o2[key];
  452. if(isNum.test(v1)) v1 = parseFloat(v1);
  453. if(isNum.test(v2)) v2 = parseFloat(v2);
  454. if(v1 && !v2){
  455. return 1;
  456. } else if(!v1 && v2){
  457. return -1;
  458. }
  459. if(v1 > v2){
  460. return 1;
  461. } else if (v1 < v2) {
  462. return -1;
  463. } else {
  464. return 0;
  465. }
  466. });
  467. desc && clone.reverse(); //倒序
  468. return clone;
  469. };
  470. //阻止事件冒泡
  471. Layui.prototype.stope = function(thisEvent){
  472. thisEvent = thisEvent || win.event;
  473. try { thisEvent.stopPropagation() } catch(e){
  474. thisEvent.cancelBubble = true;
  475. }
  476. };
  477. //自定义模块事件
  478. Layui.prototype.onevent = function(modName, events, callback){
  479. if(typeof modName !== 'string'
  480. || typeof callback !== 'function') return this;
  481. return Layui.event(modName, events, null, callback);
  482. };
  483. //执行自定义模块事件
  484. Layui.prototype.event = Layui.event = function(modName, events, params, fn){
  485. var that = this
  486. ,result = null
  487. ,filter = (events || '').match(/\((.*)\)$/)||[] //提取事件过滤器字符结构,如:select(xxx)
  488. ,eventName = (modName + '.'+ events).replace(filter[0], '') //获取事件名称,如:form.select
  489. ,filterName = filter[1] || '' //获取过滤器名称,,如:xxx
  490. ,callback = function(_, item){
  491. var res = item && item.call(that, params);
  492. res === false && result === null && (result = false);
  493. };
  494. //如果参数传入特定字符,则执行移除事件
  495. if(params === 'LAYUI-EVENT-REMOVE'){
  496. delete (that.cache.event[eventName] || {})[filterName];
  497. return that;
  498. }
  499. //添加事件
  500. if(fn){
  501. config.event[eventName] = config.event[eventName] || {};
  502. //这里不再对多次事件监听做支持,避免更多麻烦
  503. //config.event[eventName][filterName] ? config.event[eventName][filterName].push(fn) :
  504. config.event[eventName][filterName] = [fn];
  505. return this;
  506. }
  507. //执行事件回调
  508. layui.each(config.event[eventName], function(key, item){
  509. //执行当前模块的全部事件
  510. if(filterName === '{*}'){
  511. layui.each(item, callback);
  512. return;
  513. }
  514. //执行指定事件
  515. key === '' && layui.each(item, callback);
  516. (filterName && key === filterName) && layui.each(item, callback);
  517. });
  518. return result;
  519. };
  520. //新增模块事件
  521. Layui.prototype.on = function(events, modName, callback){
  522. var that = this;
  523. return that.onevent.call(that, modName, events, callback);
  524. }
  525. //移除模块事件
  526. Layui.prototype.off = function(events, modName){
  527. var that = this;
  528. return that.event.call(that, modName, events, 'LAYUI-EVENT-REMOVE');
  529. };
  530. win.layui = new Layui();
  531. }(window);