bjui-datepicker.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /*!
  2. * B-JUI v1.2 (http://b-jui.com)
  3. * Git@OSC (http://git.oschina.net/xknaan/B-JUI)
  4. * Copyright 2014 K'naan (xknaan@163.com).
  5. * Licensed under Apache (http://www.apache.org/licenses/LICENSE-2.0)
  6. */
  7. /* ========================================================================
  8. * B-JUI: bjui-datepicker.js v1.2
  9. * reference: util.date.js
  10. * @author K'naan (xknaan@163.com)
  11. * -- Modified from dwz.datepicker.js (author:ZhangHuihua@msn.com)
  12. * http://git.oschina.net/xknaan/B-JUI/blob/master/BJUI/js/bjui-datepicker.js
  13. * ========================================================================
  14. * Copyright 2014 K'naan.
  15. * Licensed under Apache (http://www.apache.org/licenses/LICENSE-2.0)
  16. * ======================================================================== */
  17. +function ($) {
  18. 'use strict';
  19. // DATEPICKER GLOBAL ELEMENTS
  20. // ======================
  21. var $box, $main, $prev, $next, $year, $month, $time, $timeinps, $spinner, $hh, $mm, $ss, $tm, $close, $days, $dayNames, $clearBtn, $okBtn
  22. $(function() {
  23. var INIT_DATEPICKER = function() {
  24. var cp = BJUI.regional.datepicker
  25. var calendar = FRAG.calendarFrag
  26. .replace('#close#', cp.close)
  27. .replace('#prev#', cp.prev)
  28. .replace('#next#', cp.next)
  29. .replace('#clear#', cp.clear)
  30. .replace('#ok#', cp.ok)
  31. $box = $(calendar).hide()
  32. $('body').append('<!-- datepicker -->').append($box)
  33. $main = $box.find('> .main')
  34. $prev = $box.find('a.prev')
  35. $next = $box.find('a.next')
  36. $year = $box.find('select[name=year]')
  37. $month = $box.find('select[name=month]')
  38. $time = $box.find('.time')
  39. $timeinps = $time.find(':text')
  40. $spinner = $time.find('ul > li')
  41. $hh = $time.find('.hh')
  42. $mm = $time.find('.mm')
  43. $ss = $time.find('.ss')
  44. $tm = $main.find('> .tm')
  45. $close = $box.find('.close')
  46. $days = $main.find('> .body > .days')
  47. $dayNames = $main.find('> .body > .dayNames')
  48. $clearBtn = $box.find('.clearBtn')
  49. $okBtn = $box.find('.okBtn')
  50. //regional
  51. var dayNames = '', dr = BJUI.regional.datepicker
  52. $.each(dr.dayNames, function(i, v) {
  53. dayNames += '<dt>'+ v +'</dt>'
  54. })
  55. $dayNames.html(dayNames)
  56. $.each(dr.monthNames, function(i, v) {
  57. var m = i + 1
  58. $month.append('<option value="'+ m +'">'+ v +'</option>')
  59. })
  60. $box.on('selectstart', function() { return false })
  61. }
  62. INIT_DATEPICKER()
  63. })
  64. // DATEPICKER CLASS DEFINITION
  65. // ======================
  66. var Datepicker = function(element, options) {
  67. this.$element = $(element)
  68. this.options = options
  69. this.tools = this.TOOLS()
  70. this.$dateBtn = null
  71. //动态minDate、maxDate
  72. var now = new Date()
  73. this.options.minDate = now.formatDateTm(this.options.minDate)
  74. this.options.maxDate = now.formatDateTm(this.options.maxDate)
  75. //events
  76. this.events = {
  77. focus_time : 'focus.bjui.datepicker.time',
  78. click_prev : 'click.bjui.datepicker.prev',
  79. click_next : 'click.bjui.datepicker.next',
  80. click_ok : 'click.bjui.datepicker.ok',
  81. click_days : 'click.bjui.datepicker.days',
  82. click_clear : 'click.bjui.datepicker.clear',
  83. click_close : 'click.bjui.datepicker.close',
  84. click_tm : 'click.bjui.datepicker.tm',
  85. click_spinner : 'click.bjui.datepicker.spinner',
  86. mousedown_sp : 'mousedown.bjui.datepicker.spinner',
  87. mouseup_sp : 'mouseup.bjui.datepicker.spinner',
  88. change_ym : 'change.bjui.datepicker.ym',
  89. click_time : 'click.bjui.datepicker.time',
  90. keydown_time : 'keydown.bjui.datepicker.time',
  91. keyup_time : 'keyup.bjui.datepicker.time'
  92. }
  93. }
  94. Datepicker.DEFAULTS = {
  95. pattern : 'yyyy-MM-dd',
  96. minDate : '1900-01-01',
  97. maxDate : '2099-12-31',
  98. mmStep : 1,
  99. ssStep : 1
  100. }
  101. Datepicker.EVENTS = {
  102. afterChange : 'afterchange.bjui.datepicker'
  103. }
  104. Datepicker.prototype.TOOLS = function() {
  105. var that = this
  106. var tools = {
  107. changeTmMenu: function(sltClass) {
  108. $tm.removeClass('hh').removeClass('mm').removeClass('ss')
  109. if (sltClass) {
  110. $tm.addClass(sltClass)
  111. $timeinps.removeClass('slt').filter('.'+ sltClass).addClass('slt')
  112. }
  113. },
  114. clickTmMenu: function($input, type) {
  115. $tm
  116. .find('> ul')
  117. .hide()
  118. .filter('.'+ type)
  119. .show()
  120. .find('> li')
  121. .off(that.events.click_tm)
  122. .on(that.events.click_tm, function() {
  123. var $li = $(this)
  124. var val = parseInt($li.text()) < 10 ? ('0'+ $li.text()) : $li.text()
  125. $input.val(val)
  126. })
  127. },
  128. keydownInt: function(e) {
  129. if (!((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode == BJUI.keyCode.DELETE || e.keyCode == BJUI.keyCode.BACKSPACE))) { return false }
  130. },
  131. changeTm: function($input, $btn) {
  132. var ivalue = parseInt($input.val()), istart = parseInt($input.data('start')) || 0, iend = parseInt($input.data('end'))
  133. var istep = parseInt($input.data('step') || 1)
  134. var type = $btn ? ($btn.data('add') ? $btn.data('add') : -1) : 0
  135. var newVal = ivalue
  136. if (type == 1) {
  137. if (ivalue <= iend - istep)
  138. newVal = ivalue + istep
  139. } else if (type == -1) {
  140. if (ivalue >= (istart + istep))
  141. newVal = ivalue - istep
  142. } else if (ivalue > iend) {
  143. newVal = iend
  144. } else if (ivalue < istart) {
  145. newVal = istart
  146. }
  147. if (newVal < 10) newVal = '0'+ newVal
  148. $input.val(newVal)
  149. },
  150. closeCalendar: function(flag) {
  151. tools.changeTmMenu()
  152. if (flag) {
  153. $(document).off(that.events.click_close)
  154. $box.hide()
  155. }
  156. },
  157. get: function(name) {
  158. return that.options[name]
  159. },
  160. getDays: function (y, m) {
  161. return m == 2 ? (y % 4 || (!(y % 100) && y % 400) ? 28 : 29) : (/4|6|9|11/.test(m) ? 30 : 31)
  162. },
  163. minMaxDate: function(sDate) {
  164. var _count = sDate.split('-').length - 1
  165. var _format = 'y-M-d'
  166. if (_count == 1) _format = 'y-M'
  167. else if (_count == 0) _format = 'y'
  168. return sDate.parseDate(_format)
  169. },
  170. getMinDate: function() {
  171. return this.minMaxDate(that.options.minDate)
  172. },
  173. getMaxDate: function() {
  174. var _sDate = that.options.maxDate
  175. var _count = _sDate.split('-').length - 1
  176. var _date = this.minMaxDate(_sDate)
  177. if (_count < 2) { //format:y-M、y
  178. var _day = this.getDays(_date.getFullYear(), _date.getMonth() + 1)
  179. _date.setDate(_day)
  180. if (_count == 0)//format:y
  181. _date.setMonth(11)
  182. }
  183. return _date
  184. },
  185. getDateWrap: function(date) {
  186. if (!date) date = this.parseDate(that.sDate) || new Date()
  187. var y = date.getFullYear()
  188. var m = date.getMonth() + 1
  189. var days = this.getDays(y, m)
  190. return {
  191. year: y, month: m, day: date.getDate(),
  192. hour: date.getHours(), minute: date.getMinutes(), second: date.getSeconds(),
  193. days: days, date: date
  194. }
  195. },
  196. changeDate: function(y, m, d) {
  197. var date = new Date(y, m - 1, d || 1)
  198. that.sDate = this.formatDate(date)
  199. return date
  200. },
  201. changeDateTime: function(y, M, d, H, m, s) {
  202. var date = new Date(y, M - 1, d, H, m, s)
  203. that.sDate = this.formatDate(date)
  204. return date
  205. },
  206. changeDay: function(day, chMonth) {
  207. if (!chMonth) chMonth = 0
  208. var dw = this.getDateWrap()
  209. return this.changeDate(dw.year, dw.month + parseInt(chMonth), day)
  210. },
  211. changeMonth: function(type) {
  212. var yearIndex = $year.get(0).selectedIndex
  213. var maxYear = $year.find('option').length
  214. var month = ($month.val() * 1) + type
  215. if (month == 0) {
  216. if (yearIndex == 0) {
  217. month = 1
  218. } else {
  219. month = 12
  220. yearIndex--
  221. $year.get(0).selectedIndex = yearIndex
  222. }
  223. } else if (month == 13) {
  224. if (yearIndex == (maxYear - 1)) {
  225. month = 12
  226. } else {
  227. month = 1
  228. yearIndex++
  229. $year.get(0).selectedIndex = yearIndex
  230. }
  231. }
  232. $month.val(month).change()
  233. },
  234. parseDate: function(sDate) {
  235. if (!sDate) return null
  236. return sDate.parseDate(that.options.pattern)
  237. },
  238. formatDate: function(date) {
  239. return date.formatDate(that.options.pattern)
  240. },
  241. hasHour: function() {
  242. return that.options.pattern.indexOf('H') != -1
  243. },
  244. hasMinute: function() {
  245. return that.options.pattern.indexOf('m') != -1
  246. },
  247. hasSecond: function() {
  248. return that.options.pattern.indexOf('s') != -1
  249. },
  250. hasTime: function() {
  251. return this.hasHour() || this.hasMinute() || this.hasSecond()
  252. },
  253. hasDate: function() {
  254. var _dateKeys = ['y','M','d','E']
  255. for (var i = 0; i < _dateKeys.length; i++) {
  256. if (that.options.pattern.indexOf(_dateKeys[i]) != -1) return true
  257. }
  258. return false
  259. },
  260. afterChange: function(date) {
  261. that.$element.trigger(Datepicker.EVENTS.afterChange, {value:date})
  262. }
  263. }
  264. return tools
  265. }
  266. Datepicker.prototype.addBtn = function() {
  267. var that = this, $element = that.$element
  268. if (!this.$dateBtn && !this.options.addbtn && !$element.parent().hasClass('wrap_bjui_btn_box')) {
  269. this.$dateBtn = $(FRAG.dateBtn)
  270. this.$element.css({'paddingRight':'15px'}).wrap('<span class="wrap_bjui_btn_box"></span>')
  271. var $box = this.$element.parent()
  272. var height = this.$element.addClass('form-control').innerHeight()
  273. $box.css({'position':'relative', 'display':'inline-block'})
  274. this.$dateBtn.css({'height':height, 'lineHeight':height +'px'}).appendTo($box)
  275. this.$dateBtn.on('selectstart', function() { return false })
  276. }
  277. }
  278. Datepicker.prototype.init = function() {
  279. if (this.$element.val()) this.sDate = this.$element.val().trim()
  280. var that = this
  281. var options = this.options
  282. var tools = this.tools
  283. var dw = tools.getDateWrap()
  284. var minDate = tools.getMinDate(), maxDate = tools.getMaxDate()
  285. var yearstart = minDate.getFullYear(), yearend = maxDate.getFullYear()
  286. $year.empty()
  287. for (var y = yearstart; y <= yearend; y++) {
  288. $year.append('<option value="'+ y +'"'+ (dw.year == y ? ' selected' : '') +'>'+ y +'</option>')
  289. }
  290. $month.val(dw.month)
  291. $year.add($month).off(this.events.change_ym).on(this.events.change_ym, function() {
  292. if (tools.hasTime()) {
  293. var $day = $days.find('.slt')
  294. var date = tools.changeDateTime($year.val(), $month.val(), $day.data('day'), dw.hour, dw.minute, dw.second)
  295. that.create(tools.getDateWrap(date), minDate, maxDate)
  296. } else {
  297. var $day = $days.find('.slt')
  298. var date = tools.changeDate($year.val(), $month.val(), $day.data('day'))
  299. that.create(tools.getDateWrap(date), minDate, maxDate)
  300. }
  301. })
  302. $prev.off(this.events.click_prev).on(this.events.click_prev, function() {
  303. that.tools.changeMonth(-1)
  304. })
  305. $next.off(this.events.click_prev).on(this.events.click_prev, function() {
  306. that.tools.changeMonth(1)
  307. })
  308. $clearBtn.off(this.events.click_clear).on(this.events.click_clear, function() {
  309. that.$element.val('')
  310. tools.closeCalendar(true)
  311. })
  312. $okBtn.off(this.events.click_ok).on(this.events.click_ok, function() {
  313. var $dd = $days.find('dd.slt')
  314. if ($dd.hasClass('disabled')) return false
  315. var date = tools.changeDay($dd.data('day'), $dd.data('month'))
  316. if (tools.hasTime()) {
  317. date.setHours(parseInt($hh.val()))
  318. date.setMinutes(parseInt($mm.val()))
  319. date.setSeconds(parseInt($ss.val()))
  320. }
  321. tools.closeCalendar(true)
  322. that.$element.val(tools.formatDate(date)).focus()
  323. //changedEvent
  324. tools.afterChange(date)
  325. })
  326. $close.off(this.events.click_close).on(this.events.click_close, function() {
  327. tools.closeCalendar(true)
  328. })
  329. $(document).on(this.events.click_close, function(e) {
  330. var $target = $(e.target)
  331. if (e.target == that.$element.get(0)) return
  332. if ($target.closest('#calendar').length) return
  333. if ($target.data('toggle') == 'datepicker' || $target.parent().data('toggle') == 'datepickerbtn' || $target.data('toggle') == 'datepickerbtn')
  334. tools.closeCalendar(false)
  335. else
  336. tools.closeCalendar(true)
  337. })
  338. this.create(dw, minDate, maxDate)
  339. }
  340. Datepicker.prototype.create = function(dw, minDate, maxDate) {
  341. var that = this
  342. var options = this.options
  343. var tools = this.tools
  344. var monthStart = new Date(dw.year, dw.month - 1, 1)
  345. var startDay = monthStart.getDay()
  346. var dayStr = ''
  347. if (startDay > 0) {
  348. monthStart.setMonth(monthStart.getMonth() - 1)
  349. var prevDateWrap = tools.getDateWrap(monthStart)
  350. for (var t = prevDateWrap.days - startDay + 1; t <= prevDateWrap.days; t++) {
  351. var _date = new Date(dw.year, dw.month - 2, t)
  352. var _ctrClass = (_date >= minDate && _date <= maxDate) ? '' : ' disabled'
  353. dayStr += '<dd class="other'+ _ctrClass +'" data-month="-1" data-day="'+ t +'">'+ t +'</dd>'
  354. }
  355. }
  356. for (var t = 1; t <= dw.days; t++) {
  357. var _date = new Date(dw.year, dw.month - 1, t)
  358. var _ctrClass = (_date >= minDate && _date <= maxDate) ? '' : 'disabled'
  359. if (t == dw.day)
  360. _ctrClass += ' slt'
  361. dayStr += '<dd class="'+ _ctrClass +'" data-day="'+ t +'">'+ t +'</dd>'
  362. }
  363. for (var t = 1; t <= 42 - startDay - dw.days; t++) {
  364. var _date = new Date(dw.year, dw.month, t)
  365. var _ctrClass = (_date >= minDate && _date <= maxDate) ? '' : ' disabled'
  366. dayStr += '<dd class="other'+ _ctrClass +'" data-month="1" data-day="'+ t +'">'+ t +'</dd>'
  367. }
  368. var $alldays = $days.html(dayStr).find('dd')
  369. $alldays.not('.disabled').off(this.events.click_days).on(this.events.click_days, function() {
  370. var $day = $(this)
  371. if (!tools.hasTime()) {
  372. var date = tools.changeDay($day.data('day'), $day.data('month'))
  373. tools.closeCalendar(true)
  374. that.$element.val(tools.formatDate(date)).focus()
  375. //changedEvent
  376. tools.afterChange(date)
  377. } else {
  378. $alldays.removeClass('slt')
  379. $day.addClass('slt')
  380. }
  381. })
  382. if (!tools.hasDate()) {
  383. $main.addClass('nodate') // only time
  384. } else {
  385. $main.removeClass('nodate')
  386. }
  387. if (tools.hasTime()) {
  388. $time.show()
  389. $hh.val(dw.hour < 10 ? ('0'+ dw.hour) : dw.hour).off(this.events.focus_time).on(this.events.focus_time, function() {
  390. tools.changeTmMenu('hh')
  391. })
  392. var iMinute = parseInt(dw.minute / options.mmStep) * options.mmStep
  393. $mm.val(iMinute < 10 ? ('0'+ iMinute) : iMinute).data('step', options.mmStep).off(this.events.focus_time).on(this.events.focus_time, function() {
  394. tools.changeTmMenu('mm')
  395. })
  396. $ss.val(tools.hasSecond() ? (dw.second < 10 ? ('0'+ dw.second) : dw.second) : '00').data('step', options.ssStep).off(this.events.focus_time).on(this.events.focus_time, function() {
  397. tools.changeTmMenu('ss')
  398. })
  399. $box.off('click').on('click', function(e) {
  400. if ($(e.target).closest('.time').length) return
  401. $tm.find('> ul').hide()
  402. tools.changeTmMenu()
  403. })
  404. $timeinps.off(this.events.keydown_time).on(this.events.keydown_time, tools.keydownInt).each(function() {
  405. var $input = $(this)
  406. $input.off(that.events.keyup_time).on(that.events.keyup_time, function() {
  407. tools.changeTm($input)
  408. })
  409. }).off(this.events.click_time).on(this.events.click_time, function() {
  410. tools.clickTmMenu($(this), $(this).data('type'))
  411. })
  412. var timer = null
  413. $spinner.off(this.events.click_spinner).on(this.events.click_spinner, function(e) {
  414. var $btn = $(this)
  415. $timeinps.filter('.slt').each(function() {
  416. tools.changeTm($(this), $btn)
  417. })
  418. e.preventDefault()
  419. }).off(this.events.mousedown_sp).on(this.events.mousedown_sp, function(e) {
  420. var $btn = $(this)
  421. timer = setInterval(function() {
  422. $timeinps.filter('.slt').each(function() {
  423. tools.changeTm($(this), $btn)
  424. })
  425. }, 150)
  426. }).off(this.events.mouseup_sp).on(this.events.mouseup_sp, function(e) {
  427. clearTimeout(timer)
  428. })
  429. if (!tools.hasHour()) $hh.attr('disabled', true)
  430. if (!tools.hasMinute()) $mm.attr('disabled', true)
  431. if (!tools.hasSecond()) $ss.attr('disabled', true)
  432. } else {
  433. $time.hide()
  434. }
  435. this.show()
  436. }
  437. Datepicker.prototype.show = function() {
  438. var offset = this.$element.offset()
  439. var iTop = offset.top + this.$element.get(0).offsetHeight
  440. // fix top
  441. var iBoxH = $box.outerHeight(true)
  442. if (iTop > iBoxH && iTop > $(window).height() - iBoxH)
  443. iTop = offset.top - iBoxH
  444. $box.css({
  445. left: offset.left,
  446. top: iTop
  447. }).show().click(function(e) {
  448. e.stopPropagation()
  449. })
  450. }
  451. // DATEPICKER PLUGIN DEFINITION
  452. // =======================
  453. function Plugin(option) {
  454. var args = arguments
  455. var property = option
  456. return this.each(function () {
  457. var $this = $(this)
  458. var options = $.extend({}, Datepicker.DEFAULTS, $this.data(), typeof option == 'object' && option)
  459. var data = $this.data('bjui.datepicker')
  460. if (!data) $this.data('bjui.datepicker', (data = new Datepicker(this, options)))
  461. if (typeof property == 'string' && $.isFunction(data[property])) {
  462. [].shift.apply(args)
  463. if (!args) data[property]()
  464. else data[property].apply(data, args)
  465. } else {
  466. data.init()
  467. }
  468. })
  469. }
  470. var old = $.fn.datepicker
  471. $.fn.datepicker = Plugin
  472. $.fn.datepicker.Constructor = Datepicker
  473. // DATEPICKER NO CONFLICT
  474. // =================
  475. $.fn.datepicker.noConflict = function () {
  476. $.fn.datepicker = old
  477. return this
  478. }
  479. // DATEPICKER DATA-API
  480. // ==============
  481. $(document).on(BJUI.eventType.initUI, function(e) {
  482. var $this = $(e.target).find('[data-toggle="datepicker"]')
  483. if (!$this.length) return
  484. if ($this.data('nobtn')) return
  485. Plugin.call($this, 'addBtn')
  486. })
  487. $(document).on('click.bjui.datepicker.data-api', '[data-toggle="datepickerbtn"]', function(e) {
  488. var $date = $(this).prevAll('[data-toggle="datepicker"]')
  489. if (!$date || !$date.is(':text')) return
  490. Plugin.call($date, $date.data())
  491. e.preventDefault()
  492. })
  493. $(document).on('click.bjui.datepicker.data-api', '[data-toggle="datepicker"]', function(e) {
  494. var $this = $(this)
  495. if ($this.data('onlybtn')) return
  496. if (!$this.is(':text')) return
  497. Plugin.call($this, $this.data())
  498. e.preventDefault()
  499. })
  500. }(jQuery);