You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

807 lines
22 KiB

  1. (function($K)
  2. {
  3. var datepickerId = 0;
  4. $K.add('module', 'datepicker', {
  5. translations: {
  6. en: {
  7. "days": [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
  8. "months": ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
  9. "months-short": ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  10. }
  11. },
  12. init: function(app, context)
  13. {
  14. this.app = app;
  15. this.$doc = app.$doc;
  16. this.$win = app.$win;
  17. this.$body = app.$body;
  18. this.lang = app.lang;
  19. this.animate = app.animate;
  20. // defaults
  21. var defaults = {
  22. year: false,
  23. month: false,
  24. day: false,
  25. format: '%d.%m.%Y', // %d, %m, %F, %M, %Y
  26. embed: false,
  27. target: false,
  28. selectYear: false,
  29. sundayFirst: false,
  30. startDate: false, // string
  31. endDate: false, // string
  32. animationOpen: 'slideDown',
  33. animationClose: 'slideUp',
  34. };
  35. // context
  36. this.context = context;
  37. this.params = context.getParams(defaults);
  38. this.$element = context.getElement();
  39. this.$target = context.getTarget();
  40. // local
  41. this.uuid = datepickerId++;
  42. this.namespace = '.kube.datepicker-' + this.uuid;
  43. this.dateRegexp = /^(.*?)(\/|\.|,|\s|\-)(.*?)(?:\/|\.|,|\s|\-)(.*?)$/;
  44. this.value = '';
  45. this.today = {};
  46. this.current = {};
  47. this.next = {};
  48. this.prev = {};
  49. this.selected = {};
  50. },
  51. // public
  52. start: function()
  53. {
  54. this.$element.attr('uuid', this.uuid);
  55. // start / end date
  56. this._buildStartEndDate();
  57. // create datepicker
  58. this.$datepicker = $K.create('class.datepicker.box', this.app, this);
  59. this.$datepicker.build();
  60. // append
  61. if (this.params.embed)
  62. {
  63. this.build();
  64. this.update();
  65. this.$datepicker.addClass('is-embed');
  66. this.$element.append(this.$datepicker);
  67. }
  68. else
  69. {
  70. this.$datepicker.addClass('is-hidden');
  71. this.$body.append(this.$datepicker);
  72. this.$element.on('click' + this.namespace, this._open.bind(this));
  73. }
  74. },
  75. stop: function()
  76. {
  77. this._disableEvents();
  78. this.$datepicker.remove();
  79. this.$element.off(this.namespace);
  80. this.$element.removeClass('datepicker-in');
  81. },
  82. build: function()
  83. {
  84. this._buildValue();
  85. this._buildTodayDate();
  86. this._buildSelectedDate();
  87. this._buildCurrentDate();
  88. },
  89. update: function()
  90. {
  91. this._buildPrevNextDate();
  92. this.$grid = $K.create('class.datepicker.grid', this.app, this);
  93. this.$grid.build();
  94. this.$datepicker.setControls(this.prev, this.next);
  95. this.$datepicker.setMonth(this.lang.get('months')[this.current.month]);
  96. this.$datepicker.setYear(this.current.year);
  97. this.$datepicker.setGrid(this.$grid);
  98. },
  99. // SET
  100. setDate: function(e)
  101. {
  102. e.preventDefault();
  103. var $item = $K.dom(e.target);
  104. if ($item.attr('data-disabled') === true) return this._close();
  105. var obj = {
  106. day: $item.attr('data-day'),
  107. month: $item.attr('data-month'),
  108. year: $item.attr('data-year')
  109. };
  110. var date = this._convertDateToFormat(obj);
  111. if (this.params.embed === false)
  112. {
  113. var $target = (this.$target.length !== 0) ? this.$target : this.$element;
  114. if ($target.get().tagName === 'INPUT')
  115. {
  116. $target.val(date);
  117. }
  118. else
  119. {
  120. $target.text(date);
  121. }
  122. this._close();
  123. }
  124. this.app.broadcast('datepicker.set', this, date, obj);
  125. },
  126. setNextMonth: function(e)
  127. {
  128. e.preventDefault();
  129. this.current = this.next;
  130. this.update();
  131. },
  132. setPrevMonth: function(e)
  133. {
  134. e.preventDefault();
  135. this.current = this.prev;
  136. this.update();
  137. },
  138. setYear: function()
  139. {
  140. this.current.year = this.$datepicker.getYearFromSelect();
  141. this.selected.day = false;
  142. this.$datepicker.setYear(this.current.year);
  143. this.update();
  144. },
  145. // BUILD
  146. _buildValue: function()
  147. {
  148. var $target = (this.$target.length !== 0) ? this.$target : this.$element;
  149. this.value = ($target.get().tagName === 'INPUT') ? $target.val() : $target.text().trim();
  150. },
  151. _buildTodayDate: function()
  152. {
  153. var date = new Date();
  154. this.today = {
  155. year: date.getFullYear(),
  156. month: parseInt(date.getMonth() + 1),
  157. day: parseInt(date.getDate())
  158. };
  159. },
  160. _buildSelectedDate: function()
  161. {
  162. this.selected = this._parseDateString(this.value);
  163. // set from params
  164. if (this.value === '')
  165. {
  166. this.selected.year = (this.params.year) ? this.params.year : this.selected.year;
  167. this.selected.month = (this.params.month) ? parseInt(this.params.month) : this.selected.month;
  168. this.selected.day = false;
  169. }
  170. },
  171. _buildCurrentDate: function()
  172. {
  173. this.current = this.selected;
  174. },
  175. _buildPrevNextDate: function()
  176. {
  177. // prev
  178. var date = this._getPrevYearAndMonth(this.current.year, this.current.month);
  179. this.prev = {
  180. year: date.year,
  181. month: date.month
  182. };
  183. // next
  184. var date = this._getNextYearAndMonth(this.current.year, this.current.month);
  185. this.next = {
  186. year: date.year,
  187. month: date.month
  188. };
  189. },
  190. _buildStartEndDate: function()
  191. {
  192. this.params.startDate = (this.params.startDate) ? this._parseDateString(this.params.startDate) : false;
  193. this.params.endDate = (this.params.endDate) ? this._parseDateString(this.params.endDate) : false;
  194. },
  195. _buildPosition: function()
  196. {
  197. this.position = {};
  198. var pos = this.$element.offset();
  199. var height = this.$element.innerHeight();
  200. var width = this.$element.innerWidth();
  201. var datepickerWidth = this.$datepicker.innerWidth();
  202. var datepickerHeight = this.$datepicker.innerHeight();
  203. var windowWidth = this.$win.width();
  204. var documentHeight = this.$doc.height();
  205. var right = 0;
  206. var left = pos.left;
  207. var top = pos.top + height + 1;
  208. this.position.type = 'left';
  209. if ((left + datepickerWidth) > windowWidth)
  210. {
  211. this.position.type = 'right';
  212. right = (windowWidth - (left + width));
  213. }
  214. if ((top + datepickerHeight) > documentHeight)
  215. {
  216. this.params.animationOpen = 'show';
  217. this.params.animationClose = 'hide';
  218. top = (top - datepickerHeight - height - 2);
  219. }
  220. this.position.top = top;
  221. this.position.left = left;
  222. this.position.right = right;
  223. },
  224. // OPEN
  225. _open: function(e)
  226. {
  227. if (e) e.preventDefault();
  228. if (this._isOpened()) return;
  229. this._closeAll();
  230. this.app.broadcast('datepicker.open', this);
  231. this.build();
  232. this.update();
  233. this._buildPosition();
  234. this._setPosition();
  235. this.animate.run(this.$datepicker, this.params.animationOpen, this._opened.bind(this));
  236. },
  237. _opened: function()
  238. {
  239. this._enableEvents();
  240. this.$element.addClass('datepicker-in');
  241. this.$datepicker.addClass('is-open');
  242. this.app.broadcast('datepicker.opened', this);
  243. },
  244. _isOpened: function()
  245. {
  246. return this.$datepicker.hasClass('is-open');
  247. },
  248. // CLOSE
  249. _close: function(e)
  250. {
  251. if (e && $K.dom(e.target).closest('.datepicker').length !== 0)
  252. {
  253. return;
  254. }
  255. if (!this._isOpened()) return;
  256. this.app.broadcast('datepicker.close', this);
  257. this.animate.run(this.$datepicker, this.params.animationClose, this._closed.bind(this));
  258. },
  259. _closed: function()
  260. {
  261. this._disableEvents();
  262. this.$datepicker.removeClass('is-open');
  263. this.$element.removeClass('datepicker-in');
  264. this.app.broadcast('datepicker.closed', this);
  265. },
  266. _closeAll: function()
  267. {
  268. $K.dom('.datepicker.is-open').each(function(node)
  269. {
  270. var $el = $K.dom(node);
  271. var id = $el.attr('data-uuid');
  272. this.$doc.off('.kube.datepicker-' + id);
  273. this.$win.off('.kube.datepicker-' + id);
  274. $el.removeClass('is-open');
  275. $el.addClass('is-hidden');
  276. }.bind(this));
  277. $K.dom('.datepicker-in').removeClass('datepicker-in');
  278. },
  279. // EVENTS
  280. _handleKeyboard: function(e)
  281. {
  282. if (e.which === 27) this._close();
  283. },
  284. _enableEvents: function()
  285. {
  286. this.$doc.on('keyup' + this.namespace, this._handleKeyboard.bind(this));
  287. this.$doc.on('click' + this.namespace + ' touchstart' + this.namespace, this._close.bind(this));
  288. this.$win.on('resize' + this.namespace, this._resizePosition.bind(this));
  289. },
  290. _disableEvents: function()
  291. {
  292. this.$doc.off(this.namespace);
  293. this.$win.off(this.namespace);
  294. },
  295. // POSITION
  296. _resizePosition: function()
  297. {
  298. this._buildPosition();
  299. this._setPosition();
  300. },
  301. _setPosition: function()
  302. {
  303. var left = 'auto';
  304. var right = this.position.right + 'px';
  305. if (this.position.type === 'left')
  306. {
  307. left = this.position.left + 'px',
  308. right = 'auto';
  309. }
  310. this.$datepicker.css({ top: this.position.top + 'px', left: left, right: right });
  311. },
  312. // PARSE
  313. _parseDateString: function(str)
  314. {
  315. var obj = {};
  316. var date = str.match(this.dateRegexp);
  317. var format = this.params.format.match(this.dateRegexp);
  318. obj.year = (date === null) ? this.today.year : parseInt(date[4]);
  319. if (format[1] === '%m' || format[1] === '%M' || format[1] === '%F')
  320. {
  321. obj.month = (date === null) ? this.today.month : this._parseMonth(format[1], date[1]);
  322. obj.day = (date === null) ? false : parseInt(date[3]);
  323. }
  324. else
  325. {
  326. obj.month = (date === null) ? this.today.month : this._parseMonth(format[3], date[3]);
  327. obj.day = (date === null) ? false : parseInt(date[1]);
  328. }
  329. obj.splitter = (date === null) ? '.' : date[2];
  330. return obj;
  331. },
  332. _parseMonth: function(type, month)
  333. {
  334. var index = parseInt(month);
  335. if (type === '%M') index = this.lang.get('months-short').indexOf(month);
  336. else if (type === '%F') index = this.lang.get('months').indexOf(month);
  337. return index;
  338. },
  339. // CONVERT
  340. _convertDateToFormat: function(obj)
  341. {
  342. var formated = this.params.format.replace('%d', obj.day);
  343. formated = formated.replace('%F', this.lang.get('months')[obj.month]);
  344. formated = formated.replace('%m', this._addZero(obj.month));
  345. formated = formated.replace('%M', this.lang.get('months-short')[obj.month]);
  346. formated = formated.replace('%Y', obj.year);
  347. return formated;
  348. },
  349. _addZero: function(str)
  350. {
  351. str = Number(str);
  352. return (str < 10) ? '0' + str : str;
  353. },
  354. // GET
  355. _getPrevYearAndMonth: function(year, month)
  356. {
  357. var date = {
  358. year: year,
  359. month: parseInt(month) - 1
  360. };
  361. if (date.month <= 0)
  362. {
  363. date.month = 12;
  364. date.year--;
  365. }
  366. return date;
  367. },
  368. _getNextYearAndMonth: function(year, month)
  369. {
  370. var date = {
  371. year: year,
  372. month: parseInt(month) + 1
  373. };
  374. if (date.month > 12)
  375. {
  376. date.month = 1;
  377. date.year++;
  378. }
  379. return date;
  380. }
  381. });
  382. })(Kube);
  383. (function($K)
  384. {
  385. $K.add('class', 'datepicker.box', {
  386. extends: ['dom'],
  387. init: function(app, datepicker)
  388. {
  389. this.app = app;
  390. this.lang = app.lang;
  391. this.datepicker = datepicker;
  392. this.params = datepicker.params;
  393. this.namespace = datepicker.namespace;
  394. this.selected = datepicker.selected;
  395. },
  396. build: function()
  397. {
  398. this._buildBox();
  399. this._buildHead();
  400. this._buildControlPrev();
  401. this._buildMonthBox();
  402. this._buildControlNext();
  403. this._buildWeekdays();
  404. this._buildBody();
  405. },
  406. getYearFromSelect: function()
  407. {
  408. return Number(this.$yearSelect.val());
  409. },
  410. setMonth: function(month)
  411. {
  412. this.$month.html(month);
  413. },
  414. setYear: function(year)
  415. {
  416. this.$yearValue.html(year);
  417. if (this.params.selectYear && this.$yearSelect)
  418. {
  419. this.$yearSelect.val(year);
  420. }
  421. },
  422. setGrid: function($grid)
  423. {
  424. this.$dbody.html('');
  425. this.$dbody.append($grid);
  426. },
  427. setControls: function(prev, next)
  428. {
  429. var buildDate = function(obj, d)
  430. {
  431. d = (d) ? d : obj.day;
  432. return new Date(obj.year + '/' + obj.month + '/' + d);
  433. }
  434. if (this.params.startDate)
  435. {
  436. var datePrev = buildDate(prev, 31);
  437. var start = buildDate(this.params.startDate);
  438. var fn = (start.getTime() > datePrev.getTime()) ? 'hide' : 'show';
  439. this.$prev[fn]();
  440. }
  441. if (this.params.endDate)
  442. {
  443. var dateNext = buildDate(next, 1);
  444. var end = buildDate(this.params.endDate);
  445. var fn = (end.getTime() < dateNext.getTime()) ? 'hide' : 'show';
  446. this.$next[fn]();
  447. }
  448. },
  449. // private
  450. _buildBox: function()
  451. {
  452. this.parse('<div>');
  453. this.addClass('datepicker');
  454. },
  455. _buildHead: function()
  456. {
  457. this.$head = $K.dom('<div class="datepicker-head">');
  458. this.append(this.$head);
  459. },
  460. _buildControlPrev: function()
  461. {
  462. this.$prev = $K.dom('<span class="datepicker-control datepicker-control-prev" />').html('&lt;');
  463. this.$prev.on('click' + this.namespace, this.datepicker.setPrevMonth.bind(this.datepicker));
  464. this.$head.append(this.$prev);
  465. },
  466. _buildControlNext: function()
  467. {
  468. this.$next = $K.dom('<span class="datepicker-control datepicker-control-next" />').html('&gt;');
  469. this.$next.on('click' + this.namespace, this.datepicker.setNextMonth.bind(this.datepicker));
  470. this.$head.append(this.$next);
  471. },
  472. _buildMonthBox: function()
  473. {
  474. this.$monthBox = $K.dom('<div class="datepicker-month-box">');
  475. this.$head.append(this.$monthBox);
  476. this._buildMonth();
  477. this._buildYear();
  478. this._buildYearSelect();
  479. },
  480. _buildMonth: function()
  481. {
  482. this.$month = $K.dom('<span />');
  483. this.$monthBox.append(this.$month);
  484. },
  485. _buildYear: function()
  486. {
  487. this.$year = $K.dom('<span />');
  488. this.$yearValue = $K.dom('<span />');
  489. this.$year.append(this.$yearValue);
  490. this.$monthBox.append(this.$year);
  491. },
  492. _buildYearSelect: function()
  493. {
  494. if (!this.params.selectYear) return;
  495. var now = new Date();
  496. var start = (this.params.startDate) ? this.params.startDate.year : (now.getFullYear() - 99);
  497. var end = (this.params.endDate) ? this.params.endDate.year : now.getFullYear();
  498. if ((end - start) < 2)
  499. {
  500. return;
  501. }
  502. this.$yearSelect = $K.dom('<select />');
  503. this.$year.append(this.$yearSelect);
  504. this.$year.append('<span class="datepicker-select-year-caret" />');
  505. this.$year.addClass('datepicker-select-year');
  506. for (var i = start; i <= end; i++)
  507. {
  508. var $option = $K.dom('<option value="' + i + '">' + i + '</option>');
  509. this.$yearSelect.append($option);
  510. }
  511. this.$yearSelect.on('change' + this.namespace, this.datepicker.setYear.bind(this.datepicker));
  512. },
  513. _buildWeekdays: function()
  514. {
  515. this.$weekdays = $K.dom('<div class="datepicker-weekdays">');
  516. var result = [];
  517. if (this.params.sundayFirst)
  518. {
  519. var last = this.lang.get('days').slice(6);
  520. result = this.lang.get('days').slice(0, 6);
  521. result.unshift(last[0]);
  522. }
  523. else
  524. {
  525. result = this.lang.get('days');
  526. }
  527. for (var i = 0; i < result.length; i++)
  528. {
  529. var tr = $K.dom('<span>').html(result[i]);
  530. this.$weekdays.append(tr);
  531. }
  532. this.append(this.$weekdays);
  533. },
  534. _buildBody: function()
  535. {
  536. this.$dbody = $K.dom('<div class="datepicker-body">');
  537. this.append(this.$dbody);
  538. }
  539. });
  540. })(Kube);
  541. (function($K)
  542. {
  543. $K.add('class', 'datepicker.grid', {
  544. extends: ['dom'],
  545. init: function(app, datepicker)
  546. {
  547. this.app = app;
  548. this.lang = app.lang;
  549. this.datepicker = datepicker;
  550. this.params = datepicker.params;
  551. this.namespace = datepicker.namespace;
  552. this.today = datepicker.today;
  553. this.selected = datepicker.selected;
  554. this.current = datepicker.current;
  555. this.prev = datepicker.prev;
  556. this.next = datepicker.next;
  557. // local
  558. this.daysInMonth = [0,31,28,31,30,31,30,31,31,30,31,30,31];
  559. },
  560. build: function()
  561. {
  562. this.parse('<div class="datepicker-grid">');
  563. var daysInCurrentMonth = this._getDaysInCurrentMonth();
  564. var daysInPrevMonth = this._getDaysInPrevMonth();
  565. var daysInNextMonth = this._getDaysInNextMonth();
  566. // start index
  567. var d = new Date(this.current.year, this.current.month - 1, 1);
  568. var startIndex = (this.params.sundayFirst) ? d.getDay() + 1 : ((!d.getDay()) ? 7 : d.getDay());
  569. var daysPrevMonthStart = daysInPrevMonth - startIndex + 2;
  570. var startCurrent = 8 - startIndex;
  571. var y = 1, c = 1, obj;
  572. for (var z = 0; z < 6; z++)
  573. {
  574. var tr = $K.dom('<div class="datepicker-row">');
  575. for (var i = 0; i < 7; i++)
  576. {
  577. if (z === 0)
  578. {
  579. var dayPrev = daysPrevMonthStart + i;
  580. if (dayPrev > daysInPrevMonth)
  581. {
  582. // current day
  583. obj = this._buildGridObj(i, y, this.current, false, false);
  584. y++;
  585. }
  586. else
  587. {
  588. // prev day
  589. obj = this._buildGridObj(i, dayPrev, this.prev, false, true);
  590. }
  591. }
  592. else if (y > daysInCurrentMonth)
  593. {
  594. // next day
  595. obj = this._buildGridObj(i, c, this.next, true, false);
  596. c++;
  597. }
  598. else
  599. {
  600. // current day
  601. obj = this._buildGridObj(i, y, this.current, false, false);
  602. y++;
  603. }
  604. tr.append(this._buildGridDay(obj));
  605. }
  606. this.append(tr);
  607. }
  608. },
  609. // private
  610. _buildGridObj: function(i, day, date, next, prev)
  611. {
  612. return {
  613. day: day,
  614. next: next,
  615. prev: prev,
  616. year: date.year,
  617. month: date.month,
  618. date: this._getGridDay(date.year, date.month, day),
  619. selected: this._isSelectedDate(date.year, date.month, day),
  620. today: this._isTodayDate(date.year, date.month, day),
  621. weekend: (i > 4),
  622. disabled: this._isDisabledDate(date.year, date.month, day)
  623. };
  624. },
  625. _buildGridDay: function(obj)
  626. {
  627. var td = $K.dom('<div class="datepicker-cell">');
  628. if (obj.next || obj.prev)
  629. {
  630. td.addClass('is-out');
  631. }
  632. if (obj.selected) td.addClass('is-selected');
  633. if (obj.today) td.addClass('is-today');
  634. if (obj.weekend && this.params.weekend) td.addClass('is-weekend');
  635. if (obj.disabled) td.addClass('is-disabled');
  636. var $item = $K.dom('<a>');
  637. $item.html(obj.day);
  638. $item.attr('href', '#');
  639. $item.attr('data-disabled', obj.disabled);
  640. $item.attr('data-date', obj.date);
  641. $item.attr('data-day', obj.day);
  642. $item.attr('data-month', obj.month);
  643. $item.attr('data-year', obj.year);
  644. $item.on('click', this.datepicker.setDate.bind(this.datepicker));
  645. return td.append($item);
  646. },
  647. _isSelectedDate: function(year, month, day)
  648. {
  649. return (this.selected.year === year && this.selected.month === month && this.selected.day === day);
  650. },
  651. _isTodayDate: function(year, month, day)
  652. {
  653. return (this.today.year === year && this.today.month === month && this.today.day === day);
  654. },
  655. _isDisabledDate: function(year, month, day)
  656. {
  657. var date = new Date(year + '/' + month + '/' + day);
  658. if (this.params.startDate)
  659. {
  660. var dateStart = new Date(this.params.startDate.year + '/' + this.params.startDate.month + '/' + this.params.startDate.day);
  661. if (date.getTime() < dateStart.getTime())
  662. {
  663. return true;
  664. }
  665. }
  666. if (this.params.endDate)
  667. {
  668. var dateEnd = new Date(this.params.endDate.year + '/' + this.params.endDate.month + '/' + this.params.endDate.day);
  669. if (date.getTime() > dateEnd.getTime())
  670. {
  671. return true;
  672. }
  673. }
  674. return false;
  675. },
  676. _getGridDay: function(year, month, day)
  677. {
  678. return year + '-' + month + '-' + day;
  679. },
  680. _getDaysInCurrentMonth: function()
  681. {
  682. return this._getDaysInMonth(this.current.year, this.current.month);
  683. },
  684. _getDaysInPrevMonth: function()
  685. {
  686. return this._getDaysInMonth(this.prev.year, this.prev.month);
  687. },
  688. _getDaysInNextMonth: function()
  689. {
  690. return this._getDaysInMonth(this.next.year, this.next.month);
  691. },
  692. _getDaysInMonth: function (year, month)
  693. {
  694. return (((0 === (year%4)) && ((0 !== (year%100)) || (0 === (year%400)))) && (month === 1)) ? 29 : this.daysInMonth[month];
  695. }
  696. });
  697. })(Kube);