laydate.js 57 KB

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