laydate.js 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855
  1. /**
  2. @Name : layDate 5.0.5 日期时间控件
  3. @Author: 贤心
  4. @Site:http://www.layui.com/laydate/
  5. @License:MIT
  6. */
  7. ;!function(){
  8. "use strict";
  9. var isLayui = window.layui && layui.define, ready = {
  10. getPath: function(){
  11. var js = document.scripts, script = js[js.length - 1], jsPath = script.src;
  12. if(script.getAttribute('merge')) return;
  13. return jsPath.substring(0, jsPath.lastIndexOf('/') + 1);
  14. }()
  15. //获取节点的style属性值
  16. ,getStyle: function(node, name){
  17. var style = node.currentStyle ? node.currentStyle : window.getComputedStyle(node, null);
  18. return style[style.getPropertyValue ? 'getPropertyValue' : 'getAttribute'](name);
  19. }
  20. //载入CSS配件
  21. ,link: function(href, fn, cssname){
  22. //未设置路径,则不主动加载css
  23. if(!laydate.path) return;
  24. var head = document.getElementsByTagName("head")[0], link = document.createElement('link');
  25. if(typeof fn === 'string') cssname = fn;
  26. var app = (cssname || href).replace(/\.|\//g, '');
  27. var id = 'layuicss-'+ app, timeout = 0;
  28. link.rel = 'stylesheet';
  29. link.href = laydate.path + href;
  30. link.id = id;
  31. if(!document.getElementById(id)){
  32. head.appendChild(link);
  33. }
  34. if(typeof fn !== 'function') return;
  35. //轮询css是否加载完毕
  36. (function poll() {
  37. if(++timeout > 8 * 1000 / 100){
  38. return window.console && console.error('laydate.css: Invalid');
  39. };
  40. parseInt(ready.getStyle(document.getElementById(id), 'width')) === 1989 ? fn() : setTimeout(poll, 100);
  41. }());
  42. }
  43. }
  44. ,laydate = {
  45. v: '5.0.5'
  46. ,config: {} //全局配置项
  47. ,index: (window.laydate && window.laydate.v) ? 100000 : 0
  48. ,path: ready.getPath
  49. //设置全局项
  50. ,set: function(options){
  51. var that = this;
  52. that.config = ready.extend({}, that.config, options);
  53. return that;
  54. }
  55. //主体CSS等待事件
  56. ,ready: function(fn){
  57. var cssname = 'laydate', ver = ''
  58. ,path = (isLayui ? 'modules/laydate/' : 'theme/') + 'default/laydate.css?v='+ laydate.v + ver;
  59. if(typeof define === 'function' && define.amd) return fn();
  60. isLayui ? layui.addcss(path, fn, cssname) : ready.link(path, fn, cssname);
  61. return this;
  62. }
  63. }
  64. //操作当前实例
  65. ,thisDate = function(){
  66. var that = this;
  67. return {
  68. //提示框
  69. hint: function(content){
  70. that.hint.call(that, content);
  71. }
  72. ,config: that.config
  73. };
  74. }
  75. //字符常量
  76. ,MOD_NAME = 'laydate', ELEM = '.layui-laydate', THIS = 'layui-this', SHOW = 'layui-show', HIDE = 'layui-hide', DISABLED = 'laydate-disabled', TIPS_OUT = '开始日期超出了结束日期<br>建议重新选择', LIMIT_YEAR = [100, 200000]
  77. ,ELEM_LIST = 'layui-laydate-list', ELEM_SELECTED = 'laydate-selected', ELEM_HINT = 'layui-laydate-hint', ELEM_PREV = 'laydate-day-prev', ELEM_NEXT = 'laydate-day-next', ELEM_FOOTER = 'layui-laydate-footer', ELEM_CONFIRM = '.laydate-btns-confirm', ELEM_TIME_TEXT = 'laydate-time-text', ELEM_TIME_BTN = '.laydate-btns-time'
  78. //组件构造器
  79. ,Class = function(options){
  80. var that = this;
  81. that.index = ++laydate.index;
  82. that.config = lay.extend({}, that.config, laydate.config, options);
  83. laydate.ready(function(){
  84. that.init();
  85. });
  86. }
  87. //DOM查找
  88. ,lay = function(selector){
  89. return new LAY(selector);
  90. }
  91. //DOM构造器
  92. ,LAY = function(selector){
  93. var index = 0
  94. ,nativeDOM = typeof selector === 'object' ? [selector] : (
  95. this.selector = selector
  96. ,document.querySelectorAll(selector || null)
  97. );
  98. for(; index < nativeDOM.length; index++){
  99. this.push(nativeDOM[index]);
  100. }
  101. };
  102. /*
  103. lay对象操作
  104. */
  105. LAY.prototype = [];
  106. LAY.prototype.constructor = LAY;
  107. //普通对象深度扩展
  108. lay.extend = function(){
  109. var ai = 1, args = arguments
  110. ,clone = function(target, obj){
  111. target = target || (obj.constructor === Array ? [] : {});
  112. for(var i in obj){
  113. //如果值为对象,则进入递归,继续深度合并
  114. target[i] = (obj[i] && (obj[i].constructor === Object))
  115. ? clone(target[i], obj[i])
  116. : obj[i];
  117. }
  118. return target;
  119. }
  120. args[0] = typeof args[0] === 'object' ? args[0] : {};
  121. for(; ai < args.length; ai++){
  122. if(typeof args[ai] === 'object'){
  123. clone(args[0], args[ai])
  124. }
  125. }
  126. return args[0];
  127. };
  128. //ie版本
  129. lay.ie = function(){
  130. var agent = navigator.userAgent.toLowerCase();
  131. return (!!window.ActiveXObject || "ActiveXObject" in window) ? (
  132. (agent.match(/msie\s(\d+)/) || [])[1] || '11' //由于ie11并没有msie的标识
  133. ) : false;
  134. }();
  135. //中止冒泡
  136. lay.stope = function(e){
  137. e = e || win.event;
  138. e.stopPropagation
  139. ? e.stopPropagation()
  140. : e.cancelBubble = true;
  141. };
  142. //对象遍历
  143. lay.each = function(obj, fn){
  144. var key
  145. ,that = this;
  146. if(typeof fn !== 'function') return that;
  147. obj = obj || [];
  148. if(obj.constructor === Object){
  149. for(key in obj){
  150. if(fn.call(obj[key], key, obj[key])) break;
  151. }
  152. } else {
  153. for(key = 0; key < obj.length; key++){
  154. if(fn.call(obj[key], key, obj[key])) break;
  155. }
  156. }
  157. return that;
  158. };
  159. //数字前置补零
  160. lay.digit = function(num, length, end){
  161. var str = '';
  162. num = String(num);
  163. length = length || 2;
  164. for(var i = num.length; i < length; i++){
  165. str += '0';
  166. }
  167. return num < Math.pow(10, length) ? str + (num|0) : num;
  168. };
  169. //创建元素
  170. lay.elem = function(elemName, attr){
  171. var elem = document.createElement(elemName);
  172. lay.each(attr || {}, function(key, value){
  173. elem.setAttribute(key, value);
  174. });
  175. return elem;
  176. };
  177. //追加字符
  178. LAY.addStr = function(str, new_str){
  179. str = str.replace(/\s+/, ' ');
  180. new_str = new_str.replace(/\s+/, ' ').split(' ');
  181. lay.each(new_str, function(ii, item){
  182. if(!new RegExp('\\b'+ item + '\\b').test(str)){
  183. str = str + ' ' + item;
  184. }
  185. });
  186. return str.replace(/^\s|\s$/, '');
  187. };
  188. //移除值
  189. LAY.removeStr = function(str, new_str){
  190. str = str.replace(/\s+/, ' ');
  191. new_str = new_str.replace(/\s+/, ' ').split(' ');
  192. lay.each(new_str, function(ii, item){
  193. var exp = new RegExp('\\b'+ item + '\\b')
  194. if(exp.test(str)){
  195. str = str.replace(exp, '');
  196. }
  197. });
  198. return str.replace(/\s+/, ' ').replace(/^\s|\s$/, '');
  199. };
  200. //查找子元素
  201. LAY.prototype.find = function(selector){
  202. var that = this;
  203. var index = 0, arr = []
  204. ,isObject = typeof selector === 'object';
  205. this.each(function(i, item){
  206. var nativeDOM = isObject ? [selector] : item.querySelectorAll(selector || null);
  207. for(; index < nativeDOM.length; index++){
  208. arr.push(nativeDOM[index]);
  209. }
  210. that.shift();
  211. });
  212. if(!isObject){
  213. that.selector = (that.selector ? that.selector + ' ' : '') + selector
  214. }
  215. lay.each(arr, function(i, item){
  216. that.push(item);
  217. });
  218. return that;
  219. };
  220. //DOM遍历
  221. LAY.prototype.each = function(fn){
  222. return lay.each.call(this, this, fn);
  223. };
  224. //添加css类
  225. LAY.prototype.addClass = function(className, type){
  226. return this.each(function(index, item){
  227. item.className = LAY[type ? 'removeStr' : 'addStr'](item.className, className)
  228. });
  229. };
  230. //移除css类
  231. LAY.prototype.removeClass = function(className){
  232. return this.addClass(className, true);
  233. };
  234. //是否包含css类
  235. LAY.prototype.hasClass = function(className){
  236. var has = false;
  237. this.each(function(index, item){
  238. if(new RegExp('\\b'+ className +'\\b').test(item.className)){
  239. has = true;
  240. }
  241. });
  242. return has;
  243. };
  244. //添加或获取属性
  245. LAY.prototype.attr = function(key, value){
  246. var that = this;
  247. return value === undefined ? function(){
  248. if(that.length > 0) return that[0].getAttribute(key);
  249. }() : that.each(function(index, item){
  250. item.setAttribute(key, value);
  251. });
  252. };
  253. //移除属性
  254. LAY.prototype.removeAttr = function(key){
  255. return this.each(function(index, item){
  256. item.removeAttribute(key);
  257. });
  258. };
  259. //设置HTML内容
  260. LAY.prototype.html = function(html){
  261. return this.each(function(index, item){
  262. item.innerHTML = html;
  263. });
  264. };
  265. //设置值
  266. LAY.prototype.val = function(value){
  267. return this.each(function(index, item){
  268. item.value = value;
  269. });
  270. };
  271. //追加内容
  272. LAY.prototype.append = function(elem){
  273. return this.each(function(index, item){
  274. typeof elem === 'object'
  275. ? item.appendChild(elem)
  276. : item.innerHTML = item.innerHTML + elem;
  277. });
  278. };
  279. //移除内容
  280. LAY.prototype.remove = function(elem){
  281. return this.each(function(index, item){
  282. elem ? item.removeChild(elem) : item.parentNode.removeChild(item);
  283. });
  284. };
  285. //事件绑定
  286. LAY.prototype.on = function(eventName, fn){
  287. return this.each(function(index, item){
  288. item.attachEvent ? item.attachEvent('on' + eventName, function(e){
  289. e.target = e.srcElement;
  290. fn.call(item, e);
  291. }) : item.addEventListener(eventName, fn, false);
  292. });
  293. };
  294. //解除事件
  295. LAY.prototype.off = function(eventName, fn){
  296. return this.each(function(index, item){
  297. item.detachEvent
  298. ? item.detachEvent('on'+ eventName, fn)
  299. : item.removeEventListener(eventName, fn, false);
  300. });
  301. };
  302. /*
  303. 组件操作
  304. */
  305. //是否闰年
  306. Class.isLeapYear = function(year){
  307. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  308. };
  309. //默认配置
  310. Class.prototype.config = {
  311. type: 'date' //控件类型,支持:year/month/date/time/datetime
  312. ,range: false //是否开启范围选择,即双控件
  313. ,format: 'yyyy-MM-dd' //默认日期格式
  314. ,value: null //默认日期,支持传入new Date(),或者符合format参数设定的日期格式字符
  315. ,min: '1900-1-1' //有效最小日期,年月日必须用“-”分割,时分秒必须用“:”分割。注意:它并不是遵循 format 设定的格式。
  316. ,max: '2099-12-31' //有效最大日期,同上
  317. ,trigger: 'focus' //呼出控件的事件
  318. ,show: false //是否直接显示,如果设置true,则默认直接显示控件
  319. ,showBottom: true //是否显示底部栏
  320. ,btns: ['clear', 'now', 'confirm'] //右下角显示的按钮,会按照数组顺序排列
  321. ,lang: 'cn' //语言,只支持cn/en,即中文和英文
  322. ,theme: 'default' //主题
  323. ,position: null //控件定位方式定位, 默认absolute,支持:fixed/absolute/static
  324. ,calendar: false //是否开启公历重要节日,仅支持中文版
  325. ,mark: {} //日期备注,如重要事件或活动标记
  326. ,zIndex: null //控件层叠顺序
  327. ,done: null //控件选择完毕后的回调,点击清空/现在/确定也均会触发
  328. ,change: null //日期时间改变后的回调
  329. };
  330. //多语言
  331. Class.prototype.lang = function(){
  332. var that = this
  333. ,options = that.config
  334. ,text = {
  335. cn: {
  336. weeks: ['日', '一', '二', '三', '四', '五', '六']
  337. ,time: ['时', '分', '秒']
  338. ,timeTips: '选择时间'
  339. ,startTime: '开始时间'
  340. ,endTime: '结束时间'
  341. ,dateTips: '返回日期'
  342. ,month: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
  343. ,tools: {
  344. confirm: '确定'
  345. ,clear: '清空'
  346. ,now: '现在'
  347. }
  348. }
  349. ,en: {
  350. weeks: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
  351. ,time: ['Hours', 'Minutes', 'Seconds']
  352. ,timeTips: 'Select Time'
  353. ,startTime: 'Start Time'
  354. ,endTime: 'End Time'
  355. ,dateTips: 'Select Date'
  356. ,month: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  357. ,tools: {
  358. confirm: 'Confirm'
  359. ,clear: 'Clear'
  360. ,now: 'Now'
  361. }
  362. }
  363. };
  364. return text[options.lang] || text['cn'];
  365. };
  366. //初始准备
  367. Class.prototype.init = function(){
  368. var that = this
  369. ,options = that.config
  370. ,dateType = 'yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s'
  371. ,isStatic = options.position === 'static'
  372. ,format = {
  373. year: 'yyyy'
  374. ,month: 'yyyy-MM'
  375. ,date: 'yyyy-MM-dd'
  376. ,time: 'HH:mm:ss'
  377. ,datetime: 'yyyy-MM-dd HH:mm:ss'
  378. };
  379. options.elem = lay(options.elem);
  380. options.eventElem = lay(options.eventElem);
  381. if(!options.elem[0]) return;
  382. //日期范围分隔符
  383. if(options.range === true) options.range = '-';
  384. //根据不同type,初始化默认format
  385. if(options.format === format.date){
  386. options.format = format[options.type];
  387. }
  388. //将日期格式转化成数组
  389. that.format = options.format.match(new RegExp(dateType + '|.', 'g')) || [];
  390. //生成正则表达式
  391. that.EXP_IF = '';
  392. that.EXP_SPLIT = '';
  393. lay.each(that.format, function(i, item){
  394. var EXP = new RegExp(dateType).test(item)
  395. ? '\\b\\d{1,'+ function(){
  396. if(/yyyy/.test(item)) return 4;
  397. if(/y/.test(item)) return 308;
  398. return 2;
  399. }() +'}\\b'
  400. : '\\' + item;
  401. that.EXP_IF = that.EXP_IF + EXP;
  402. that.EXP_SPLIT = that.EXP_SPLIT + (that.EXP_SPLIT ? '|' : '') + '('+ EXP + ')';
  403. });
  404. that.EXP_IF = new RegExp('^'+ (
  405. options.range ?
  406. that.EXP_IF + '\\s\\'+ options.range + '\\s' + that.EXP_IF
  407. : that.EXP_IF
  408. ) +'$');
  409. that.EXP_SPLIT = new RegExp(that.EXP_SPLIT, 'g');
  410. //如果不是input|textarea元素,则默认采用click事件
  411. if(!that.isInput(options.elem[0])){
  412. if(options.trigger === 'focus'){
  413. options.trigger = 'click';
  414. }
  415. }
  416. //设置唯一KEY
  417. if(!options.elem.attr('lay-key')){
  418. options.elem.attr('lay-key', that.index);
  419. options.eventElem.attr('lay-key', that.index);
  420. }
  421. //记录重要日期
  422. options.mark = lay.extend({}, (options.calendar && options.lang === 'cn') ? {
  423. '0-1-1': '元旦'
  424. ,'0-2-14': '情人'
  425. ,'0-3-8': '妇女'
  426. ,'0-3-12': '植树'
  427. ,'0-4-1': '愚人'
  428. ,'0-5-1': '劳动'
  429. ,'0-5-4': '青年'
  430. ,'0-6-1': '儿童'
  431. ,'0-9-10': '教师'
  432. ,'0-9-18': '国耻'
  433. ,'0-10-1': '国庆'
  434. ,'0-12-25': '圣诞'
  435. } : {}, options.mark);
  436. //获取限制内日期
  437. lay.each(['min', 'max'], function(i, item){
  438. var ymd = [], hms = [];
  439. if(typeof options[item] === 'number'){ //如果为数字
  440. var day = options[item]
  441. ,time = new Date().getTime()
  442. ,STAMP = 86400000 //代表一天的时间戳
  443. ,thisDate = new Date(
  444. day ? (
  445. day < STAMP ? time + day*STAMP : day //如果数字小于一天的时间戳,则数字为天数,否则为时间戳
  446. ) : time
  447. );
  448. ymd = [thisDate.getFullYear(), thisDate.getMonth() + 1, thisDate.getDate()];
  449. day < STAMP || (hms = [thisDate.getHours(), thisDate.getMinutes(), thisDate.getSeconds()]);
  450. } else {
  451. ymd = (options[item].match(/\d+-\d+-\d+/) || [''])[0].split('-');
  452. hms = (options[item].match(/\d+:\d+:\d+/) || [''])[0].split(':');
  453. }
  454. options[item] = {
  455. year: ymd[0] | 0 || new Date().getFullYear()
  456. ,month: ymd[1] ? (ymd[1] | 0) - 1 : new Date().getMonth()
  457. ,date: ymd[2] | 0 || new Date().getDate()
  458. ,hours: hms[0] | 0
  459. ,minutes: hms[1] | 0
  460. ,seconds: hms[2] | 0
  461. };
  462. });
  463. that.elemID = 'layui-laydate'+ options.elem.attr('lay-key');
  464. if(options.show || isStatic) that.render();
  465. isStatic || that.events();
  466. //默认赋值
  467. if(options.value){
  468. if(options.value.constructor === Date){
  469. that.setValue(that.parse(0, that.systemDate(options.value)));
  470. } else {
  471. that.setValue(options.value);
  472. }
  473. }
  474. };
  475. //控件主体渲染
  476. Class.prototype.render = function(){
  477. var that = this
  478. ,options = that.config
  479. ,lang = that.lang()
  480. ,isStatic = options.position === 'static'
  481. //主面板
  482. ,elem = that.elem = lay.elem('div', {
  483. id: that.elemID
  484. ,'class': [
  485. 'layui-laydate'
  486. ,options.range ? ' layui-laydate-range' : ''
  487. ,isStatic ? ' layui-laydate-static' : ''
  488. ,options.theme && options.theme !== 'default' && !/^#/.test(options.theme) ? (' laydate-theme-' + options.theme) : ''
  489. ].join('')
  490. })
  491. //主区域
  492. ,elemMain = that.elemMain = []
  493. ,elemHeader = that.elemHeader = []
  494. ,elemCont = that.elemCont = []
  495. ,elemTable = that.table = []
  496. //底部区域
  497. ,divFooter = that.footer = lay.elem('div', {
  498. 'class': ELEM_FOOTER
  499. });
  500. if(options.zIndex) elem.style.zIndex = options.zIndex;
  501. //单双日历区域
  502. lay.each(new Array(2), function(i){
  503. if(!options.range && i > 0){
  504. return true;
  505. }
  506. //头部区域
  507. var divHeader = lay.elem('div', {
  508. 'class': 'layui-laydate-header'
  509. })
  510. //左右切换
  511. ,headerChild = [function(){ //上一年
  512. var elem = lay.elem('i', {
  513. 'class': 'layui-icon laydate-icon laydate-prev-y'
  514. });
  515. elem.innerHTML = '&#xe65a;';
  516. return elem;
  517. }(), function(){ //上一月
  518. var elem = lay.elem('i', {
  519. 'class': 'layui-icon laydate-icon laydate-prev-m'
  520. });
  521. elem.innerHTML = '&#xe603;';
  522. return elem;
  523. }(), function(){ //年月选择
  524. var elem = lay.elem('div', {
  525. 'class': 'laydate-set-ym'
  526. }), spanY = lay.elem('span'), spanM = lay.elem('span');
  527. elem.appendChild(spanY);
  528. elem.appendChild(spanM);
  529. return elem;
  530. }(), function(){ //下一月
  531. var elem = lay.elem('i', {
  532. 'class': 'layui-icon laydate-icon laydate-next-m'
  533. });
  534. elem.innerHTML = '&#xe602;';
  535. return elem;
  536. }(), function(){ //下一年
  537. var elem = lay.elem('i', {
  538. 'class': 'layui-icon laydate-icon laydate-next-y'
  539. });
  540. elem.innerHTML = '&#xe65b;';
  541. return elem;
  542. }()]
  543. //日历内容区域
  544. ,divContent = lay.elem('div', {
  545. 'class': 'layui-laydate-content'
  546. })
  547. ,table = lay.elem('table')
  548. ,thead = lay.elem('thead'), theadTr = lay.elem('tr');
  549. //生成年月选择
  550. lay.each(headerChild, function(i, item){
  551. divHeader.appendChild(item);
  552. });
  553. //生成表格
  554. thead.appendChild(theadTr);
  555. lay.each(new Array(6), function(i){ //表体
  556. var tr = table.insertRow(0);
  557. lay.each(new Array(7), function(j){
  558. if(i === 0){
  559. var th = lay.elem('th');
  560. th.innerHTML = lang.weeks[j];
  561. theadTr.appendChild(th);
  562. }
  563. tr.insertCell(j);
  564. });
  565. });
  566. table.insertBefore(thead, table.children[0]); //表头
  567. divContent.appendChild(table);
  568. elemMain[i] = lay.elem('div', {
  569. 'class': 'layui-laydate-main laydate-main-list-'+ i
  570. });
  571. elemMain[i].appendChild(divHeader);
  572. elemMain[i].appendChild(divContent);
  573. elemHeader.push(headerChild);
  574. elemCont.push(divContent);
  575. elemTable.push(table);
  576. });
  577. //生成底部栏
  578. lay(divFooter).html(function(){
  579. var html = [], btns = [];
  580. if(options.type === 'datetime'){
  581. html.push('<span lay-type="datetime" class="laydate-btns-time">'+ lang.timeTips +'</span>');
  582. }
  583. lay.each(options.btns, function(i, item){
  584. var title = lang.tools[item] || 'btn';
  585. if(options.range && item === 'now') return;
  586. if(isStatic && item === 'clear') title = options.lang === 'cn' ? '重置' : 'Reset';
  587. btns.push('<span lay-type="'+ item +'" class="laydate-btns-'+ item +'">'+ title +'</span>');
  588. });
  589. html.push('<div class="laydate-footer-btns">'+ btns.join('') +'</div>');
  590. return html.join('');
  591. }());
  592. //插入到主区域
  593. lay.each(elemMain, function(i, main){
  594. elem.appendChild(main);
  595. });
  596. options.showBottom && elem.appendChild(divFooter);
  597. //生成自定义主题
  598. if(/^#/.test(options.theme)){
  599. var style = lay.elem('style')
  600. ,styleText = [
  601. '#{{id}} .layui-laydate-header{background-color:{{theme}};}'
  602. ,'#{{id}} .layui-this{background-color:{{theme}} !important;}'
  603. ].join('').replace(/{{id}}/g, that.elemID).replace(/{{theme}}/g, options.theme);
  604. if('styleSheet' in style){
  605. style.setAttribute('type', 'text/css');
  606. style.styleSheet.cssText = styleText;
  607. } else {
  608. style.innerHTML = styleText;
  609. }
  610. lay(elem).addClass('laydate-theme-molv');
  611. elem.appendChild(style);
  612. }
  613. //移除上一个控件
  614. that.remove();
  615. //如果是静态定位,则插入到指定的容器中,否则,插入到body
  616. isStatic ? options.elem.append(elem) : (
  617. document.body.appendChild(elem)
  618. ,that.position() //定位
  619. );
  620. that.checkDate().calendar(); //初始校验
  621. that.changeEvent(); //日期切换
  622. Class.thisElem = that.elemID;
  623. typeof options.ready === 'function' && options.ready(lay.extend({}, options.dateTime, {
  624. month: options.dateTime.month + 1
  625. }));
  626. };
  627. //控件移除
  628. Class.prototype.remove = function(){
  629. var that = this
  630. ,options = that.config
  631. ,elem = lay('#'+ that.elemID);
  632. if(elem[0] && options.position !== 'static'){
  633. that.checkDate(function(){
  634. elem.remove();
  635. });
  636. }
  637. return that;
  638. };
  639. //定位算法
  640. Class.prototype.position = function(){
  641. var that = this
  642. ,options = that.config
  643. ,elem = that.bindElem || options.elem[0]
  644. ,rect = elem.getBoundingClientRect() //绑定元素的坐标
  645. ,elemWidth = that.elem.offsetWidth //控件的宽度
  646. ,elemHeight = that.elem.offsetHeight //控件的高度
  647. //滚动条高度
  648. ,scrollArea = function(type){
  649. type = type ? 'scrollLeft' : 'scrollTop';
  650. return document.body[type] | document.documentElement[type];
  651. }
  652. ,winArea = function(type){
  653. return document.documentElement[type ? 'clientWidth' : 'clientHeight']
  654. }, margin = 5, left = rect.left, top = rect.bottom;
  655. //如果右侧超出边界
  656. if(left + elemWidth + margin > winArea('width')){
  657. left = winArea('width') - elemWidth - margin;
  658. }
  659. //如果底部超出边界
  660. if(top + elemHeight + margin > winArea()){
  661. top = rect.top > elemHeight //顶部是否有足够区域显示完全
  662. ? rect.top - elemHeight
  663. : winArea() - elemHeight;
  664. top = top - margin*2;
  665. }
  666. if(options.position){
  667. that.elem.style.position = options.position;
  668. }
  669. that.elem.style.left = left + (options.position === 'fixed' ? 0 : scrollArea(1)) + 'px';
  670. that.elem.style.top = top + (options.position === 'fixed' ? 0 : scrollArea()) + 'px';
  671. };
  672. //提示
  673. Class.prototype.hint = function(content){
  674. var that = this
  675. ,options = that.config
  676. ,div = lay.elem('div', {
  677. 'class': ELEM_HINT
  678. });
  679. div.innerHTML = content || '';
  680. lay(that.elem).find('.'+ ELEM_HINT).remove();
  681. that.elem.appendChild(div);
  682. clearTimeout(that.hinTimer);
  683. that.hinTimer = setTimeout(function(){
  684. lay(that.elem).find('.'+ ELEM_HINT).remove();
  685. }, 3000);
  686. };
  687. //获取递增/减后的年月
  688. Class.prototype.getAsYM = function(Y, M, type){
  689. type ? M-- : M++;
  690. if(M < 0){
  691. M = 11;
  692. Y--;
  693. }
  694. if(M > 11){
  695. M = 0;
  696. Y++;
  697. }
  698. return [Y, M];
  699. };
  700. //系统消息
  701. Class.prototype.systemDate = function(newDate){
  702. var thisDate = newDate || new Date();
  703. return {
  704. year: thisDate.getFullYear() //年
  705. ,month: thisDate.getMonth() //月
  706. ,date: thisDate.getDate() //日
  707. ,hours: newDate ? newDate.getHours() : 0 //时
  708. ,minutes: newDate ? newDate.getMinutes() : 0 //分
  709. ,seconds: newDate ? newDate.getSeconds() : 0 //秒
  710. }
  711. };
  712. //日期校验
  713. Class.prototype.checkDate = function(fn){
  714. var that = this
  715. ,thisDate = new Date()
  716. ,options = that.config
  717. ,dateTime = options.dateTime = options.dateTime || that.systemDate()
  718. ,thisMaxDate, error
  719. ,elem = that.bindElem || options.elem[0]
  720. ,valType = that.isInput(elem) ? 'val' : 'html'
  721. ,value = that.isInput(elem) ? elem.value : (options.position === 'static' ? '' : elem.innerHTML)
  722. //校验日期有效数字
  723. ,checkValid = function(dateTime){
  724. if(dateTime.year > LIMIT_YEAR[1]) dateTime.year = LIMIT_YEAR[1], error = true; //不能超过20万年
  725. if(dateTime.month > 11) dateTime.month = 11, error = true;
  726. if(dateTime.hours > 23) dateTime.hours = 0, error = true;
  727. if(dateTime.minutes > 59) dateTime.minutes = 0, dateTime.hours++, error = true;
  728. if(dateTime.seconds > 59) dateTime.seconds = 0, dateTime.minutes++, error = true;
  729. //计算当前月的最后一天
  730. thisMaxDate = laydate.getEndDate(dateTime.month + 1, dateTime.year);
  731. if(dateTime.date > thisMaxDate) dateTime.date = thisMaxDate, error = true;
  732. }
  733. //获得初始化日期值
  734. ,initDate = function(dateTime, value, index){
  735. var startEnd = ['startTime', 'endTime'];
  736. value = value.match(that.EXP_SPLIT);
  737. index = index || 0;
  738. if(options.range){
  739. that[startEnd[index]] = that[startEnd[index]] || {};
  740. }
  741. lay.each(that.format, function(i, item){
  742. var thisv = parseFloat(value[i]);
  743. if(value[i].length < item.length) error = true;
  744. if(/yyyy|y/.test(item)){ //年
  745. if(thisv < LIMIT_YEAR[0]) thisv = LIMIT_YEAR[0], error = true; //年不能低于100年
  746. dateTime.year = thisv;
  747. } else if(/MM|M/.test(item)){ //月
  748. if(thisv < 1) thisv = 1, error = true;
  749. dateTime.month = thisv - 1;
  750. } else if(/dd|d/.test(item)){ //日
  751. if(thisv < 1) thisv = 1, error = true;
  752. dateTime.date = thisv;
  753. } else if(/HH|H/.test(item)){ //时
  754. if(thisv < 1) thisv = 0, error = true;
  755. dateTime.hours = thisv;
  756. options.range && (that[startEnd[index]].hours = thisv);
  757. } else if(/mm|m/.test(item)){ //分
  758. if(thisv < 1) thisv = 0, error = true;
  759. dateTime.minutes = thisv;
  760. options.range && (that[startEnd[index]].minutes = thisv);
  761. } else if(/ss|s/.test(item)){ //秒
  762. if(thisv < 1) thisv = 0, error = true;
  763. dateTime.seconds = thisv;
  764. options.range && (that[startEnd[index]].seconds = thisv);
  765. }
  766. });
  767. checkValid(dateTime)
  768. };
  769. if(fn === 'limit') return checkValid(dateTime), that;
  770. value = value || options.value;
  771. if(typeof value === 'string'){
  772. value = value.replace(/\s+/g, ' ').replace(/^\s|\s$/g, '');
  773. }
  774. //如果点击了开始,单未选择结束就关闭,则重新选择开始
  775. if(that.startState && !that.endState){
  776. delete that.startState;
  777. that.endState = true;
  778. };
  779. if(typeof value === 'string' && value){
  780. if(that.EXP_IF.test(value)){ //校验日期格式
  781. if(options.range){
  782. value = value.split(' '+ options.range +' ');
  783. that.startDate = that.startDate || that.systemDate();
  784. that.endDate = that.endDate || that.systemDate();
  785. options.dateTime = lay.extend({}, that.startDate);
  786. lay.each([that.startDate, that.endDate], function(i, item){
  787. initDate(item, value[i], i);
  788. });
  789. } else {
  790. initDate(dateTime, value)
  791. }
  792. } else {
  793. that.hint('日期格式不合法<br>必须遵循下述格式:<br>'+ (
  794. options.range ? (options.format + ' '+ options.range +' ' + options.format) : options.format
  795. ) + '<br>已为你重置');
  796. error = true;
  797. }
  798. } else if(value && value.constructor === Date){ //如果值为日期对象时
  799. options.dateTime = that.systemDate(value);
  800. } else {
  801. options.dateTime = that.systemDate();
  802. delete that.startState;
  803. delete that.endState;
  804. delete that.startDate;
  805. delete that.endDate;
  806. delete that.startTime;
  807. delete that.endTime;
  808. }
  809. checkValid(dateTime);
  810. if(error && value){
  811. that.setValue(
  812. options.range ? (that.endDate ? that.parse() : '') : that.parse()
  813. );
  814. }
  815. fn && fn();
  816. return that;
  817. };
  818. //公历重要日期与自定义备注
  819. Class.prototype.mark = function(td, YMD){
  820. var that = this
  821. ,mark, options = that.config;
  822. lay.each(options.mark, function(key, title){
  823. var keys = key.split('-');
  824. if((keys[0] == YMD[0] || keys[0] == 0) //每年的每月
  825. && (keys[1] == YMD[1] || keys[1] == 0) //每月的每日
  826. && keys[2] == YMD[2]){ //特定日
  827. mark = title || YMD[2];
  828. }
  829. });
  830. mark && td.html('<span class="laydate-day-mark">'+ mark +'</span>');
  831. return that;
  832. };
  833. //无效日期范围的标记
  834. Class.prototype.limit = function(elem, date, index, time){
  835. var that = this
  836. ,options = that.config, timestrap = {}
  837. ,dateTime = options[index > 41 ? 'endDate' : 'dateTime']
  838. ,isOut, thisDateTime = lay.extend({}, dateTime, date || {});
  839. lay.each({
  840. now: thisDateTime
  841. ,min: options.min
  842. ,max: options.max
  843. }, function(key, item){
  844. timestrap[key] = that.newDate(lay.extend({
  845. year: item.year
  846. ,month: item.month
  847. ,date: item.date
  848. }, function(){
  849. var hms = {};
  850. lay.each(time, function(i, keys){
  851. hms[keys] = item[keys];
  852. });
  853. return hms;
  854. }())).getTime(); //time:是否比较时分秒
  855. });
  856. isOut = timestrap.now < timestrap.min || timestrap.now > timestrap.max;
  857. elem && elem[isOut ? 'addClass' : 'removeClass'](DISABLED);
  858. return isOut;
  859. };
  860. //日历表
  861. Class.prototype.calendar = function(value){
  862. var that = this
  863. ,options = that.config
  864. ,dateTime = value || options.dateTime
  865. ,thisDate = new Date(), startWeek, prevMaxDate, thisMaxDate
  866. ,lang = that.lang()
  867. ,isAlone = options.type !== 'date' && options.type !== 'datetime'
  868. ,index = value ? 1 : 0
  869. ,tds = lay(that.table[index]).find('td')
  870. ,elemYM = lay(that.elemHeader[index][2]).find('span');
  871. if(dateTime.year < LIMIT_YEAR[0]) dateTime.year = LIMIT_YEAR[0], that.hint('最低只能支持到公元'+ LIMIT_YEAR[0] +'年');
  872. if(dateTime.year > LIMIT_YEAR[1]) dateTime.year = LIMIT_YEAR[1], that.hint('最高只能支持到公元'+ LIMIT_YEAR[1] +'年');
  873. //记录初始值
  874. if(!that.firstDate){
  875. that.firstDate = lay.extend({}, dateTime);
  876. }
  877. //计算当前月第一天的星期
  878. thisDate.setFullYear(dateTime.year, dateTime.month, 1);
  879. startWeek = thisDate.getDay();
  880. prevMaxDate = laydate.getEndDate(dateTime.month, dateTime.year); //计算上个月的最后一天
  881. thisMaxDate = laydate.getEndDate(dateTime.month + 1, dateTime.year); //计算当前月的最后一天
  882. //赋值日
  883. lay.each(tds, function(index, item){
  884. var YMD = [dateTime.year, dateTime.month], st = 0;
  885. item = lay(item);
  886. item.removeAttr('class');
  887. if(index < startWeek){
  888. st = prevMaxDate - startWeek + index;
  889. item.addClass('laydate-day-prev');
  890. YMD = that.getAsYM(dateTime.year, dateTime.month, 'sub');
  891. } else if(index >= startWeek && index < thisMaxDate + startWeek){
  892. st = index - startWeek;
  893. if(!options.range){
  894. st + 1 === dateTime.date && item.addClass(THIS);
  895. }
  896. } else {
  897. st = index - thisMaxDate - startWeek;
  898. item.addClass('laydate-day-next');
  899. YMD = that.getAsYM(dateTime.year, dateTime.month);
  900. }
  901. YMD[1]++;
  902. YMD[2] = st + 1;
  903. item.attr('lay-ymd', YMD.join('-')).html(YMD[2]);
  904. that.mark(item, YMD).limit(item, {
  905. year: YMD[0]
  906. ,month: YMD[1] - 1
  907. ,date: YMD[2]
  908. }, index);
  909. });
  910. //同步头部年月
  911. lay(elemYM[0]).attr('lay-ym', dateTime.year + '-' + (dateTime.month + 1));
  912. lay(elemYM[1]).attr('lay-ym', dateTime.year + '-' + (dateTime.month + 1));
  913. if(options.lang === 'cn'){
  914. lay(elemYM[0]).attr('lay-type', 'year').html(dateTime.year + '年')
  915. lay(elemYM[1]).attr('lay-type', 'month').html((dateTime.month + 1) + '月');
  916. } else {
  917. lay(elemYM[0]).attr('lay-type', 'month').html(lang.month[dateTime.month]);
  918. lay(elemYM[1]).attr('lay-type', 'year').html(dateTime.year);
  919. }
  920. //初始默认选择器
  921. if(isAlone){
  922. if(options.range){
  923. value ? that.endDate = (that.endDate || {
  924. year: dateTime.year + (options.type === 'year' ? 1 : 0)
  925. ,month: dateTime.month + (options.type === 'month' ? 0 : -1)
  926. }) : (that.startDate = that.startDate || {
  927. year: dateTime.year
  928. ,month: dateTime.month
  929. });
  930. if(value){
  931. that.listYM = [
  932. [that.startDate.year, that.startDate.month + 1]
  933. ,[that.endDate.year, that.endDate.month + 1]
  934. ];
  935. that.list(options.type, 0).list(options.type, 1);
  936. //同步按钮可点状态
  937. options.type === 'time' ? that.setBtnStatus('时间'
  938. ,lay.extend({}, that.systemDate(), that.startTime)
  939. ,lay.extend({}, that.systemDate(), that.endTime)
  940. ) : that.setBtnStatus(true);
  941. }
  942. }
  943. if(!options.range){
  944. that.listYM = [[dateTime.year, dateTime.month + 1]];
  945. that.list(options.type, 0);
  946. }
  947. }
  948. //赋值双日历
  949. if(options.range && !value){
  950. var EYM = that.getAsYM(dateTime.year, dateTime.month)
  951. that.calendar(lay.extend({}, dateTime, {
  952. year: EYM[0]
  953. ,month: EYM[1]
  954. }));
  955. }
  956. //通过检测当前有效日期,来设定确定按钮是否可点
  957. if(!options.range) that.limit(lay(that.footer).find(ELEM_CONFIRM), null, 0, ['hours', 'minutes', 'seconds']);
  958. //标记选择范围
  959. if(options.range && value && !isAlone) that.stampRange();
  960. return that;
  961. };
  962. //生成年月时分秒列表
  963. Class.prototype.list = function(type, index){
  964. var that = this
  965. ,options = that.config
  966. ,dateTime = options.dateTime
  967. ,lang = that.lang()
  968. ,isAlone = options.range && options.type !== 'date' && options.type !== 'datetime' //独立范围选择器
  969. ,ul = lay.elem('ul', {
  970. 'class': ELEM_LIST + ' ' + ({
  971. year: 'laydate-year-list'
  972. ,month: 'laydate-month-list'
  973. ,time: 'laydate-time-list'
  974. })[type]
  975. })
  976. ,elemHeader = that.elemHeader[index]
  977. ,elemYM = lay(elemHeader[2]).find('span')
  978. ,elemCont = that.elemCont[index || 0]
  979. ,haveList = lay(elemCont).find('.'+ ELEM_LIST)[0]
  980. ,isCN = options.lang === 'cn'
  981. ,text = isCN ? '年' : ''
  982. ,listYM = that.listYM[index] || {}
  983. ,hms = ['hours', 'minutes', 'seconds']
  984. ,startEnd = ['startTime', 'endTime'][index];
  985. if(listYM[0] < 1) listYM[0] = 1;
  986. if(type === 'year'){ //年列表
  987. var yearNum, startY = yearNum = listYM[0] - 7;
  988. if(startY < 1) startY = yearNum = 1;
  989. lay.each(new Array(15), function(i){
  990. var li = lay.elem('li', {
  991. 'lay-ym': yearNum
  992. }), ymd = {year: yearNum};
  993. yearNum == listYM[0] && lay(li).addClass(THIS);
  994. li.innerHTML = yearNum + text;
  995. ul.appendChild(li);
  996. if(yearNum < that.firstDate.year){
  997. ymd.month = options.min.month;
  998. ymd.date = options.min.date;
  999. } else if(yearNum >= that.firstDate.year){
  1000. ymd.month = options.max.month;
  1001. ymd.date = options.max.date;
  1002. }
  1003. that.limit(lay(li), ymd, index);
  1004. yearNum++;
  1005. });
  1006. lay(elemYM[isCN ? 0 : 1]).attr('lay-ym', (yearNum - 8) + '-' + listYM[1])
  1007. .html((startY + text) + ' - ' + (yearNum - 1 + text));
  1008. } else if(type === 'month'){ //月列表
  1009. lay.each(new Array(12), function(i){
  1010. var li = lay.elem('li', {
  1011. 'lay-ym': i
  1012. }), ymd = {year: listYM[0], month: i};
  1013. i + 1 == listYM[1] && lay(li).addClass(THIS);
  1014. li.innerHTML = lang.month[i] + (isCN ? '月' : '');
  1015. ul.appendChild(li);
  1016. if(listYM[0] < that.firstDate.year){
  1017. ymd.date = options.min.date;
  1018. } else if(listYM[0] >= that.firstDate.year){
  1019. ymd.date = options.max.date;
  1020. }
  1021. that.limit(lay(li), ymd, index);
  1022. });
  1023. lay(elemYM[isCN ? 0 : 1]).attr('lay-ym', listYM[0] + '-' + listYM[1])
  1024. .html(listYM[0] + text);
  1025. } else if(type === 'time'){ //时间列表
  1026. //检测时分秒状态是否在有效日期时间范围内
  1027. var setTimeStatus = function(){
  1028. lay(ul).find('ol').each(function(i, ol){
  1029. lay(ol).find('li').each(function(ii, li){
  1030. that.limit(lay(li), [{
  1031. hours: ii
  1032. }, {
  1033. hours: that[startEnd].hours
  1034. ,minutes: ii
  1035. }, {
  1036. hours: that[startEnd].hours
  1037. ,minutes: that[startEnd].minutes
  1038. ,seconds: ii
  1039. }][i], index, [['hours'], ['hours', 'minutes'], ['hours', 'minutes', 'seconds']][i]);
  1040. });
  1041. });
  1042. if(!options.range) that.limit(lay(that.footer).find(ELEM_CONFIRM), that[startEnd], 0, ['hours', 'minutes', 'seconds']);
  1043. };
  1044. if(options.range){
  1045. if(!that[startEnd]) that[startEnd] = {
  1046. hours: 0
  1047. ,minutes: 0
  1048. ,seconds: 0
  1049. };
  1050. } else {
  1051. that[startEnd] = dateTime;
  1052. }
  1053. lay.each([24, 60, 60], function(i, item){
  1054. var li = lay.elem('li'), childUL = ['<p>'+ lang.time[i] +'</p><ol>'];
  1055. lay.each(new Array(item), function(ii){
  1056. childUL.push('<li'+ (that[startEnd][hms[i]] === ii ? ' class="'+ THIS +'"' : '') +'>'+ lay.digit(ii, 2) +'</li>');
  1057. });
  1058. li.innerHTML = childUL.join('') + '</ol>';
  1059. ul.appendChild(li);
  1060. });
  1061. setTimeStatus();
  1062. }
  1063. //插入容器
  1064. if(haveList) elemCont.removeChild(haveList);
  1065. elemCont.appendChild(ul);
  1066. //年月
  1067. if(type === 'year' || type === 'month'){
  1068. //显示切换箭头
  1069. lay(that.elemMain[index]).addClass('laydate-ym-show');
  1070. //选中
  1071. lay(ul).find('li').on('click', function(){
  1072. var ym = lay(this).attr('lay-ym') | 0;
  1073. if(lay(this).hasClass(DISABLED)) return;
  1074. if(index === 0){
  1075. dateTime[type] = ym;
  1076. if(isAlone) that.startDate[type] = ym;
  1077. that.limit(lay(that.footer).find(ELEM_CONFIRM), null, 0);
  1078. } else { //范围选择
  1079. if(isAlone){ //非date/datetime类型
  1080. that.endDate[type] = ym;
  1081. } else { //date/datetime类型
  1082. var YM = type === 'year'
  1083. ? that.getAsYM(ym, listYM[1] - 1, 'sub')
  1084. : that.getAsYM(listYM[0], ym, 'sub');
  1085. lay.extend(dateTime, {
  1086. year: YM[0]
  1087. ,month: YM[1]
  1088. });
  1089. }
  1090. }
  1091. if(options.type === 'year' || options.type === 'month'){
  1092. lay(ul).find('.'+ THIS).removeClass(THIS);
  1093. lay(this).addClass(THIS);
  1094. //如果为年月选择器,点击了年列表,则切换到月选择器
  1095. if(options.type === 'month' && type === 'year'){
  1096. that.listYM[index][0] = ym;
  1097. isAlone && (that[['startDate', 'endDate'][index]].year = ym);
  1098. that.list('month', index);
  1099. }
  1100. } else {
  1101. that.checkDate('limit').calendar();
  1102. that.closeList();
  1103. }
  1104. that.setBtnStatus(); //同步按钮可点状态
  1105. options.range || that.done(null, 'change');
  1106. lay(that.footer).find(ELEM_TIME_BTN).removeClass(DISABLED);
  1107. });
  1108. } else {
  1109. var span = lay.elem('span', {
  1110. 'class': ELEM_TIME_TEXT
  1111. }), scroll = function(){ //滚动条定位
  1112. lay(ul).find('ol').each(function(i){
  1113. var ol = this
  1114. ,li = lay(ol).find('li')
  1115. ol.scrollTop = 30*(that[startEnd][hms[i]] - 2);
  1116. if(ol.scrollTop <= 0){
  1117. li.each(function(ii, item){
  1118. if(!lay(this).hasClass(DISABLED)){
  1119. ol.scrollTop = 30*(ii - 2);
  1120. return true;
  1121. }
  1122. });
  1123. }
  1124. });
  1125. }, haveSpan = lay(elemHeader[2]).find('.'+ ELEM_TIME_TEXT);
  1126. scroll()
  1127. span.innerHTML = options.range ? [lang.startTime,lang.endTime][index] : lang.timeTips
  1128. lay(that.elemMain[index]).addClass('laydate-time-show');
  1129. if(haveSpan[0]) haveSpan.remove();
  1130. elemHeader[2].appendChild(span);
  1131. lay(ul).find('ol').each(function(i){
  1132. var ol = this;
  1133. //选择时分秒
  1134. lay(ol).find('li').on('click', function(){
  1135. var value = this.innerHTML | 0;
  1136. if(lay(this).hasClass(DISABLED)) return;
  1137. if(options.range){
  1138. that[startEnd][hms[i]] = value;
  1139. } else {
  1140. dateTime[hms[i]] = value;
  1141. }
  1142. lay(ol).find('.'+ THIS).removeClass(THIS);
  1143. lay(this).addClass(THIS);
  1144. //同步按钮可点状态
  1145. that.setBtnStatus(
  1146. null
  1147. ,lay.extend({}, that.systemDate(), that.startTime)
  1148. ,lay.extend({}, that.systemDate(), that.endTime)
  1149. );
  1150. setTimeStatus();
  1151. scroll();
  1152. (that.endDate || options.type === 'time') && that.done(null, 'change');
  1153. });
  1154. });
  1155. }
  1156. return that;
  1157. };
  1158. //记录列表切换后的年月
  1159. Class.prototype.listYM = [];
  1160. //关闭列表
  1161. Class.prototype.closeList = function(){
  1162. var that = this
  1163. ,options = that.config;
  1164. lay.each(that.elemCont, function(index, item){
  1165. lay(this).find('.'+ ELEM_LIST).remove();
  1166. lay(that.elemMain[index]).removeClass('laydate-ym-show laydate-time-show');
  1167. });
  1168. lay(that.elem).find('.'+ ELEM_TIME_TEXT).remove();
  1169. };
  1170. //检测结束日期是否超出开始日期
  1171. Class.prototype.setBtnStatus = function(tips, start, end){
  1172. var that = this
  1173. ,options = that.config
  1174. ,isOut, elemBtn = lay(that.footer).find(ELEM_CONFIRM)
  1175. ,isAlone = options.range && options.type !== 'date' && options.type !== 'datetime';
  1176. if(isAlone){
  1177. start = start || that.startDate;
  1178. end = end || that.endDate;
  1179. isOut = that.newDate(start).getTime() > that.newDate(end).getTime();
  1180. //如果不在有效日期内,直接禁用按钮,否则比较开始和结束日期
  1181. (that.limit(null, start) || that.limit(null, end))
  1182. ? elemBtn.addClass(DISABLED)
  1183. : elemBtn[isOut ? 'addClass' : 'removeClass'](DISABLED);
  1184. //是否异常提示
  1185. if(tips && isOut) that.hint(
  1186. typeof tips === 'string' ? TIPS_OUT.replace(/日期/g, tips) : TIPS_OUT
  1187. );
  1188. }
  1189. };
  1190. //转义为规定格式的日期字符
  1191. Class.prototype.parse = function(state, date){
  1192. var that = this
  1193. ,options = that.config
  1194. ,dateTime = date || (state
  1195. ? lay.extend({}, that.endDate, that.endTime)
  1196. : (options.range ? lay.extend({}, that.startDate, that.startTime) : options.dateTime))
  1197. ,format = that.format.concat();
  1198. //转义为规定格式
  1199. lay.each(format, function(i, item){
  1200. if(/yyyy|y/.test(item)){ //年
  1201. format[i] = lay.digit(dateTime.year, item.length);
  1202. } else if(/MM|M/.test(item)){ //月
  1203. format[i] = lay.digit(dateTime.month + 1, item.length);
  1204. } else if(/dd|d/.test(item)){ //日
  1205. format[i] = lay.digit(dateTime.date, item.length);
  1206. } else if(/HH|H/.test(item)){ //时
  1207. format[i] = lay.digit(dateTime.hours, item.length);
  1208. } else if(/mm|m/.test(item)){ //分
  1209. format[i] = lay.digit(dateTime.minutes, item.length);
  1210. } else if(/ss|s/.test(item)){ //秒
  1211. format[i] = lay.digit(dateTime.seconds, item.length);
  1212. }
  1213. });
  1214. //返回日期范围字符
  1215. if(options.range && !state){
  1216. return format.join('') + ' '+ options.range +' ' + that.parse(1);
  1217. }
  1218. return format.join('');
  1219. };
  1220. //创建指定日期时间对象
  1221. Class.prototype.newDate = function(dateTime){
  1222. return new Date(
  1223. dateTime.year || 1
  1224. ,dateTime.month || 0
  1225. ,dateTime.date || 1
  1226. ,dateTime.hours || 0
  1227. ,dateTime.minutes || 0
  1228. ,dateTime.seconds || 0
  1229. );
  1230. };
  1231. //赋值
  1232. Class.prototype.setValue = function(value){
  1233. var that = this
  1234. ,options = that.config
  1235. ,elem = that.bindElem || options.elem[0]
  1236. ,valType = that.isInput(elem) ? 'val' : 'html'
  1237. options.position === 'static' || lay(elem)[valType](value || '');
  1238. return this;
  1239. };
  1240. //标记范围内的日期
  1241. Class.prototype.stampRange = function(){
  1242. var that = this
  1243. ,options = that.config
  1244. ,startTime, endTime
  1245. ,tds = lay(that.elem).find('td');
  1246. if(options.range && !that.endDate) lay(that.footer).find(ELEM_CONFIRM).addClass(DISABLED);
  1247. if(!that.endDate) return;
  1248. startTime = that.newDate({
  1249. year: that.startDate.year
  1250. ,month: that.startDate.month
  1251. ,date: that.startDate.date
  1252. }).getTime();
  1253. endTime = that.newDate({
  1254. year: that.endDate.year
  1255. ,month: that.endDate.month
  1256. ,date: that.endDate.date
  1257. }).getTime();
  1258. if(startTime > endTime) return that.hint(TIPS_OUT);
  1259. lay.each(tds, function(i, item){
  1260. var ymd = lay(item).attr('lay-ymd').split('-')
  1261. ,thisTime = that.newDate({
  1262. year: ymd[0]
  1263. ,month: ymd[1] - 1
  1264. ,date: ymd[2]
  1265. }).getTime();
  1266. lay(item).removeClass(ELEM_SELECTED + ' ' + THIS);
  1267. if(thisTime === startTime || thisTime === endTime){
  1268. lay(item).addClass(
  1269. lay(item).hasClass(ELEM_PREV) || lay(item).hasClass(ELEM_NEXT)
  1270. ? ELEM_SELECTED
  1271. : THIS
  1272. );
  1273. }
  1274. if(thisTime > startTime && thisTime < endTime){
  1275. lay(item).addClass(ELEM_SELECTED);
  1276. }
  1277. });
  1278. };
  1279. //执行done/change回调
  1280. Class.prototype.done = function(param, type){
  1281. var that = this
  1282. ,options = that.config
  1283. ,start = lay.extend({}, that.startDate ? lay.extend(that.startDate, that.startTime) : options.dateTime)
  1284. ,end = lay.extend({}, lay.extend(that.endDate, that.endTime))
  1285. lay.each([start, end], function(i, item){
  1286. if(!('month' in item)) return;
  1287. lay.extend(item, {
  1288. month: item.month + 1
  1289. });
  1290. });
  1291. param = param || [that.parse(), start, end];
  1292. typeof options[type || 'done'] === 'function' && options[type || 'done'].apply(options, param);
  1293. return that;
  1294. };
  1295. //选择日期
  1296. Class.prototype.choose = function(td){
  1297. var that = this
  1298. ,options = that.config
  1299. ,dateTime = options.dateTime
  1300. ,tds = lay(that.elem).find('td')
  1301. ,YMD = td.attr('lay-ymd').split('-')
  1302. ,setDateTime = function(one){
  1303. var thisDate = new Date();
  1304. //同步dateTime
  1305. one && lay.extend(dateTime, YMD);
  1306. //记录开始日期
  1307. if(options.range){
  1308. that.startDate ? lay.extend(that.startDate, YMD) : (
  1309. that.startDate = lay.extend({}, YMD, that.startTime)
  1310. );
  1311. that.startYMD = YMD;
  1312. }
  1313. };
  1314. YMD = {
  1315. year: YMD[0] | 0
  1316. ,month: (YMD[1] | 0) - 1
  1317. ,date: YMD[2] | 0
  1318. };
  1319. if(td.hasClass(DISABLED)) return;
  1320. //范围选择
  1321. if(options.range){
  1322. lay.each(['startTime', 'endTime'], function(i, item){
  1323. that[item] = that[item] || {
  1324. hours: 0
  1325. ,minutes: 0
  1326. ,seconds: 0
  1327. };
  1328. });
  1329. if(that.endState){ //重新选择
  1330. setDateTime();
  1331. delete that.endState;
  1332. delete that.endDate;
  1333. that.startState = true;
  1334. tds.removeClass(THIS + ' ' + ELEM_SELECTED);
  1335. td.addClass(THIS);
  1336. } else if(that.startState){ //选中截止
  1337. td.addClass(THIS);
  1338. that.endDate ? lay.extend(that.endDate, YMD) : (
  1339. that.endDate = lay.extend({}, YMD, that.endTime)
  1340. );
  1341. //判断是否顺时或逆时选择
  1342. if(that.newDate(YMD).getTime() < that.newDate(that.startYMD).getTime()){
  1343. var startDate = lay.extend({}, that.endDate, {
  1344. hours: that.startDate.hours
  1345. ,minutes: that.startDate.minutes
  1346. ,seconds: that.startDate.seconds
  1347. });
  1348. lay.extend(that.endDate, that.startDate, {
  1349. hours: that.endDate.hours
  1350. ,minutes: that.endDate.minutes
  1351. ,seconds: that.endDate.seconds
  1352. });
  1353. that.startDate = startDate;
  1354. }
  1355. options.showBottom || that.done();
  1356. that.stampRange(); //标记范围内的日期
  1357. that.endState = true;
  1358. that.done(null, 'change');
  1359. } else { //选中开始
  1360. td.addClass(THIS);
  1361. setDateTime();
  1362. that.startState = true;
  1363. }
  1364. lay(that.footer).find(ELEM_CONFIRM)[that.endDate ? 'removeClass' : 'addClass'](DISABLED);
  1365. } else if(options.position === 'static'){ //直接嵌套的选中
  1366. setDateTime(true);
  1367. that.calendar().done().done(null, 'change');
  1368. } else if(options.type === 'date'){
  1369. setDateTime(true);
  1370. that.setValue(that.parse()).remove().done();
  1371. } else if(options.type === 'datetime'){
  1372. setDateTime(true);
  1373. that.calendar().done(null, 'change');
  1374. }
  1375. };
  1376. //底部按钮
  1377. Class.prototype.tool = function(btn, type){
  1378. var that = this
  1379. ,options = that.config
  1380. ,dateTime = options.dateTime
  1381. ,isStatic = options.position === 'static'
  1382. ,active = {
  1383. //选择时间
  1384. datetime: function(){
  1385. if(lay(btn).hasClass(DISABLED)) return;
  1386. that.list('time', 0);
  1387. options.range && that.list('time', 1);
  1388. lay(btn).attr('lay-type', 'date').html(that.lang().dateTips);
  1389. }
  1390. //选择日期
  1391. ,date: function(){
  1392. that.closeList();
  1393. lay(btn).attr('lay-type', 'datetime').html(that.lang().timeTips);
  1394. }
  1395. //清空、重置
  1396. ,clear: function(){
  1397. that.setValue('').remove();
  1398. isStatic && (
  1399. lay.extend(dateTime, that.firstDate)
  1400. ,that.calendar()
  1401. )
  1402. options.range && (
  1403. delete that.startState
  1404. ,delete that.endState
  1405. ,delete that.endDate
  1406. ,delete that.startTime
  1407. ,delete that.endTime
  1408. );
  1409. that.done(['', {}, {}]);
  1410. }
  1411. //现在
  1412. ,now: function(){
  1413. var thisDate = new Date();
  1414. lay.extend(dateTime, that.systemDate(), {
  1415. hours: thisDate.getHours()
  1416. ,minutes: thisDate.getMinutes()
  1417. ,seconds: thisDate.getSeconds()
  1418. });
  1419. that.setValue(that.parse()).remove();
  1420. isStatic && that.calendar();
  1421. that.done();
  1422. }
  1423. //确定
  1424. ,confirm: function(){
  1425. if(options.range){
  1426. if(!that.endDate) return that.hint('请先选择日期范围');
  1427. if(lay(btn).hasClass(DISABLED)) return that.hint(
  1428. options.type === 'time' ? TIPS_OUT.replace(/日期/g, '时间') : TIPS_OUT
  1429. );
  1430. } else {
  1431. if(lay(btn).hasClass(DISABLED)) return that.hint('不在有效日期或时间范围内');
  1432. }
  1433. that.done();
  1434. that.setValue(that.parse()).remove()
  1435. }
  1436. };
  1437. active[type] && active[type]();
  1438. };
  1439. //统一切换处理
  1440. Class.prototype.change = function(index){
  1441. var that = this
  1442. ,options = that.config
  1443. ,dateTime = options.dateTime
  1444. ,isAlone = options.range && (options.type === 'year' || options.type === 'month')
  1445. ,elemCont = that.elemCont[index || 0]
  1446. ,listYM = that.listYM[index]
  1447. ,addSubYeay = function(type){
  1448. var startEnd = ['startDate', 'endDate'][index]
  1449. ,isYear = lay(elemCont).find('.laydate-year-list')[0]
  1450. ,isMonth = lay(elemCont).find('.laydate-month-list')[0];
  1451. //切换年列表
  1452. if(isYear){
  1453. listYM[0] = type ? listYM[0] - 15 : listYM[0] + 15;
  1454. that.list('year', index);
  1455. }
  1456. if(isMonth){ //切换月面板中的年
  1457. type ? listYM[0]-- : listYM[0]++;
  1458. that.list('month', index);
  1459. }
  1460. if(isYear || isMonth){
  1461. lay.extend(dateTime, {
  1462. year: listYM[0]
  1463. });
  1464. if(isAlone) that[startEnd].year = listYM[0];
  1465. options.range || that.done(null, 'change');
  1466. that.setBtnStatus();
  1467. options.range || that.limit(lay(that.footer).find(ELEM_CONFIRM), {
  1468. year: listYM[0]
  1469. });
  1470. }
  1471. return isYear || isMonth;
  1472. };
  1473. return {
  1474. prevYear: function(){
  1475. if(addSubYeay('sub')) return;
  1476. dateTime.year--;
  1477. that.checkDate('limit').calendar();
  1478. options.range || that.done(null, 'change');
  1479. }
  1480. ,prevMonth: function(){
  1481. var YM = that.getAsYM(dateTime.year, dateTime.month, 'sub');
  1482. lay.extend(dateTime, {
  1483. year: YM[0]
  1484. ,month: YM[1]
  1485. });
  1486. that.checkDate('limit').calendar();
  1487. options.range || that.done(null, 'change');
  1488. }
  1489. ,nextMonth: function(){
  1490. var YM = that.getAsYM(dateTime.year, dateTime.month);
  1491. lay.extend(dateTime, {
  1492. year: YM[0]
  1493. ,month: YM[1]
  1494. });
  1495. that.checkDate('limit').calendar();
  1496. options.range || that.done(null, 'change');
  1497. }
  1498. ,nextYear: function(){
  1499. if(addSubYeay()) return;
  1500. dateTime.year++
  1501. that.checkDate('limit').calendar();
  1502. options.range || that.done(null, 'change');
  1503. }
  1504. };
  1505. };
  1506. //日期切换事件
  1507. Class.prototype.changeEvent = function(){
  1508. var that = this
  1509. ,options = that.config;
  1510. //日期选择事件
  1511. lay(that.elem).on('click', function(e){
  1512. lay.stope(e);
  1513. });
  1514. //年月切换
  1515. lay.each(that.elemHeader, function(i, header){
  1516. //上一年
  1517. lay(header[0]).on('click', function(e){
  1518. that.change(i).prevYear();
  1519. });
  1520. //上一月
  1521. lay(header[1]).on('click', function(e){
  1522. that.change(i).prevMonth();
  1523. });
  1524. //选择年月
  1525. lay(header[2]).find('span').on('click', function(e){
  1526. var othis = lay(this)
  1527. ,layYM = othis.attr('lay-ym')
  1528. ,layType = othis.attr('lay-type');
  1529. if(!layYM) return;
  1530. layYM = layYM.split('-');
  1531. that.listYM[i] = [layYM[0] | 0, layYM[1] | 0];
  1532. that.list(layType, i);
  1533. lay(that.footer).find(ELEM_TIME_BTN).addClass(DISABLED);
  1534. });
  1535. //下一月
  1536. lay(header[3]).on('click', function(e){
  1537. that.change(i).nextMonth();
  1538. });
  1539. //下一年
  1540. lay(header[4]).on('click', function(e){
  1541. that.change(i).nextYear();
  1542. });
  1543. });
  1544. //点击日期
  1545. lay.each(that.table, function(i, table){
  1546. var tds = lay(table).find('td');
  1547. tds.on('click', function(){
  1548. that.choose(lay(this));
  1549. });
  1550. });
  1551. //点击底部按钮
  1552. lay(that.footer).find('span').on('click', function(){
  1553. var type = lay(this).attr('lay-type');
  1554. that.tool(this, type);
  1555. });
  1556. };
  1557. //是否输入框
  1558. Class.prototype.isInput = function(elem){
  1559. return /input|textarea/.test(elem.tagName.toLocaleLowerCase());
  1560. };
  1561. //绑定的元素事件处理
  1562. Class.prototype.events = function(){
  1563. var that = this
  1564. ,options = that.config
  1565. //绑定呼出控件事件
  1566. ,showEvent = function(elem, bind){
  1567. elem.on(options.trigger, function(){
  1568. bind && (that.bindElem = this);
  1569. that.render();
  1570. });
  1571. };
  1572. if(!options.elem[0] || options.elem[0].eventHandler) return;
  1573. showEvent(options.elem, 'bind');
  1574. showEvent(options.eventElem);
  1575. //绑定关闭控件事件
  1576. lay(document).on('click', function(e){
  1577. if(e.target === options.elem[0]
  1578. || e.target === options.eventElem[0]
  1579. || e.target === lay(options.closeStop)[0]){
  1580. return;
  1581. }
  1582. that.remove();
  1583. }).on('keydown', function(e){
  1584. if(e.keyCode === 13){
  1585. if(lay('#'+ that.elemID)[0] && that.elemID === Class.thisElem){
  1586. e.preventDefault();
  1587. lay(that.footer).find(ELEM_CONFIRM)[0].click();
  1588. }
  1589. }
  1590. });
  1591. //自适应定位
  1592. lay(window).on('resize', function(){
  1593. if(!that.elem || !lay(ELEM)[0]){
  1594. return false;
  1595. }
  1596. that.position();
  1597. });
  1598. options.elem[0].eventHandler = true;
  1599. };
  1600. //核心接口
  1601. laydate.render = function(options){
  1602. var inst = new Class(options);
  1603. return thisDate.call(inst);
  1604. };
  1605. //得到某月的最后一天
  1606. laydate.getEndDate = function(month, year){
  1607. var thisDate = new Date();
  1608. //设置日期为下个月的第一天
  1609. thisDate.setFullYear(
  1610. year || thisDate.getFullYear()
  1611. ,month || (thisDate.getMonth() + 1)
  1612. ,1);
  1613. //减去一天,得到当前月最后一天
  1614. return new Date(thisDate.getTime() - 1000*60*60*24).getDate();
  1615. };
  1616. //暴露lay
  1617. window.lay = window.lay || lay;
  1618. //加载方式
  1619. isLayui ? (
  1620. laydate.ready()
  1621. ,layui.define(function(exports){ //layui加载
  1622. laydate.path = layui.cache.dir;
  1623. exports(MOD_NAME, laydate);
  1624. })
  1625. ) : (
  1626. (typeof define === 'function' && define.amd) ? define(function(){ //requirejs加载
  1627. return laydate;
  1628. }) : function(){ //普通script标签加载
  1629. laydate.ready();
  1630. window.laydate = laydate
  1631. }()
  1632. );
  1633. }();