laydate.js 57 KB

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