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.

2459 lines
66 KiB

4 years ago
  1. (function($K)
  2. {
  3. $K.add('module', 'autocomplete', {
  4. init: function(app, context)
  5. {
  6. this.app = app;
  7. this.$doc = app.$doc;
  8. this.$win = app.$win;
  9. this.$body = app.$body;
  10. this.animate = app.animate;
  11. // defaults
  12. var defaults = {
  13. url: false,
  14. min: 2,
  15. labelClass: false,
  16. target: false,
  17. param: false
  18. };
  19. // context
  20. this.context = context;
  21. this.params = context.getParams(defaults);
  22. this.$element = context.getElement();
  23. this.$target = context.getTarget();
  24. },
  25. start: function()
  26. {
  27. this._build();
  28. this.timeout = null;
  29. this.$element.on('keyup.kube.autocomplete', this._open.bind(this));
  30. },
  31. stop: function()
  32. {
  33. this.$box.remove();
  34. this.$element.off('.kube.autocomplete');
  35. this.$doc.off('.kube.autocomplete');
  36. this.$win.off('.kube.autocomplete');
  37. },
  38. // private
  39. _build: function()
  40. {
  41. this.$box = $K.dom('<div />');
  42. this.$box.addClass('autocomplete');
  43. this.$box.addClass('is-hidden');
  44. this.$body.append(this.$box);
  45. if (this.$target && !this._isInputTarget())
  46. {
  47. this.$target.addClass('autocomplete-labels');
  48. var $closes = this.$target.find('.close');
  49. $closes.on('click', this._removeLabel.bind(this));
  50. }
  51. },
  52. _open: function(e)
  53. {
  54. if (e) e.preventDefault();
  55. clearTimeout(this.timeout);
  56. var value = this.$element.val();
  57. if (value.length >= this.params.min)
  58. {
  59. this._resize();
  60. this.$win.on('resize.kube.autocomplete', this._resize.bind(this));
  61. this.$doc.on('click.kube.autocomplete', this._close.bind(this));
  62. this.$box.addClass('is-open');
  63. this._listen(e);
  64. }
  65. else
  66. {
  67. this._close(e);
  68. }
  69. },
  70. _close: function(e)
  71. {
  72. if (e) e.preventDefault();
  73. this.$box.removeClass('is-open');
  74. this.$box.addClass('is-hidden');
  75. this.$doc.off('.kube.autocomplete');
  76. this.$win.off('.kube.autocomplete');
  77. },
  78. _getPlacement: function(pos, height)
  79. {
  80. return ((this.$doc.height() - (pos.top + height)) < this.$box.height()) ? 'top' : 'bottom';
  81. },
  82. _resize: function()
  83. {
  84. this.$box.width(this.$element.width());
  85. },
  86. _getParamName: function()
  87. {
  88. return (this.params.param) ? this.params.param : this.$element.attr('name');
  89. },
  90. _getTargetName: function()
  91. {
  92. var name = this.$target.attr('data-name');
  93. return (name) ? name : this.$target.attr('id');
  94. },
  95. _lookup: function()
  96. {
  97. var data = this._getParamName() + '=' + this.$element.val();
  98. $K.ajax.post({
  99. url: this.params.url,
  100. data: data,
  101. success: this._complete.bind(this)
  102. });
  103. },
  104. _complete: function(json)
  105. {
  106. this.$box.html('');
  107. if (json.length === 0) return this._close();
  108. for (var i = 0; i < json.length; i++)
  109. {
  110. var $item = $K.dom('<a>');
  111. $item.attr('href', '#');
  112. $item.attr('rel', json[i].id);
  113. $item.html(json[i].name);
  114. $item.on('click', this._set.bind(this));
  115. this.$box.append($item);
  116. }
  117. var pos = this.$element.offset();
  118. var height = this.$element.height();
  119. var width = this.$element.width();
  120. var placement = this._getPlacement(pos, height);
  121. var top = (placement === 'top') ? (pos.top - this.$box.height() - height) : (pos.top + height);
  122. this.$box.css({ width: width + 'px', top: top + 'px', left: pos.left + 'px' });
  123. this.$box.removeClass('is-hidden');
  124. },
  125. _listen: function(e)
  126. {
  127. switch(e.which)
  128. {
  129. case 40: // down
  130. e.preventDefault();
  131. this._select('next');
  132. break;
  133. case 38: // up
  134. e.preventDefault();
  135. this._select('prev');
  136. break;
  137. case 13: // enter
  138. e.preventDefault();
  139. this._set();
  140. break;
  141. case 27: // esc
  142. this._close(e);
  143. break;
  144. default:
  145. this.timeout = setTimeout(this._lookup.bind(this), 300);
  146. break;
  147. }
  148. },
  149. _select: function(type)
  150. {
  151. var $links = this.$box.find('a');
  152. var $active = this.$box.find('.is-active');
  153. $links.removeClass('is-active');
  154. var $item = this._selectItem($active, $links, type);
  155. $item.addClass('is-active');
  156. },
  157. _selectItem: function($active, $links, type)
  158. {
  159. var $item;
  160. var isActive = ($active.length !== 0);
  161. var size = (type === 'next') ? 0 : ($links.length - 1);
  162. if (isActive)
  163. {
  164. $item = $active[type]();
  165. }
  166. if (!isActive || !$item || $item.length === 0)
  167. {
  168. $item = $links.eq(size);
  169. }
  170. return $item;
  171. },
  172. _set: function(e)
  173. {
  174. var $active = this.$box.find('.is-active');
  175. if (e)
  176. {
  177. e.preventDefault();
  178. $active = $K.dom(e.target);
  179. }
  180. var id = $active.attr('rel');
  181. var value = $active.html();
  182. if (this.$target.length !== 0)
  183. {
  184. if (this._isInputTarget())
  185. {
  186. this.$target.val(value);
  187. }
  188. else
  189. {
  190. var $added = this.$target.find('[data-id="' + id + '"]');
  191. if ($added.length === 0)
  192. {
  193. this._addLabel(id, value);
  194. }
  195. }
  196. this.$element.val('');
  197. }
  198. else
  199. {
  200. this.$element.val(value);
  201. }
  202. this.$element.focus();
  203. this.app.broadcast('autocomplete.set', this, value);
  204. this._close();
  205. },
  206. _addLabel: function(id, name)
  207. {
  208. var $label = $K.dom('<span>');
  209. $label.addClass('label');
  210. $label.attr('data-id', id);
  211. $label.text(name + ' ');
  212. if (this.params.labelClass)
  213. {
  214. $label.addClass(this.params.labelClass);
  215. }
  216. var $close = $K.dom('<span>');
  217. $close.addClass('close');
  218. $close.on('click', this._removeLabel.bind(this));
  219. var $input = $K.dom('<input>');
  220. $input.attr('type', 'hidden');
  221. $input.attr('name', this._getTargetName() + '[]');
  222. $input.val(name);
  223. $label.append($close);
  224. $label.append($input);
  225. this.$target.append($label);
  226. },
  227. _isInputTarget: function()
  228. {
  229. return (this.$target.get().tagName === 'INPUT');
  230. },
  231. _removeLabel: function(e)
  232. {
  233. e.preventDefault();
  234. var $el = $K.dom(e.target);
  235. var $label = $el.closest('.label');
  236. this.animate.run($label, 'fadeOut', function()
  237. {
  238. $label.remove();
  239. }.bind(this))
  240. }
  241. });
  242. })(Kube);
  243. (function($K)
  244. {
  245. $K.add('module', 'combobox', {
  246. init: function(app, context)
  247. {
  248. this.app = app;
  249. this.$win = app.$win;
  250. // defaults
  251. var defaults = {
  252. placeholder: ''
  253. };
  254. // context
  255. this.context = context;
  256. this.params = context.getParams(defaults);
  257. this.$element = context.getElement();
  258. },
  259. start: function()
  260. {
  261. this._buildSource();
  262. this._buildCaret();
  263. this._buildEvent();
  264. },
  265. stop: function()
  266. {
  267. this.$sourceBox.after(this.$element);
  268. this.$sourceBox.remove();
  269. this.$element.off('.kube.combobox');
  270. this.$win.off('.kube.combobox');
  271. },
  272. // private
  273. _buildSource: function()
  274. {
  275. this.$sourceBox = $K.dom('<div>');
  276. this.$sourceBox.addClass('combobox');
  277. this.$source = $K.dom('<input>');
  278. this.$source.attr('type', 'text');
  279. this.$source.attr('placeholder', this.params.placeholder);
  280. this.$sourceBox.width(this.$element.width());
  281. this.$sourceBox.append(this.$source);
  282. this.$element.after(this.$sourceBox);
  283. this.$element.attr('class', '');
  284. this.$element.attr('style', '');
  285. this.$sourceBox.append(this.$element);
  286. this.$win.on('resize.kube.combobox', this._resize.bind(this));
  287. },
  288. _buildCaret: function()
  289. {
  290. this.$sourceCaret = $K.dom('<span>');
  291. this.$sourceCaret.addClass('combobox-caret');
  292. this.$sourceBox.append(this.$sourceCaret);
  293. },
  294. _buildEvent: function()
  295. {
  296. this.$element.on('change.kube.combobox', this._select.bind(this));
  297. this.$source.on('keyup.kube.combobox', this._type.bind(this));
  298. },
  299. _resize: function()
  300. {
  301. this.$sourceBox.width(this.$element.width());
  302. },
  303. _type: function(e)
  304. {
  305. var value = this.$source.val();
  306. this.app.broadcast('combobox.set', this, value);
  307. if (this.$sourceValue) this.$sourceValue.remove();
  308. if (value.trim() === '') return;
  309. this.$sourceValue = $K.dom('<option>');
  310. this.$sourceValue.attr('value', value);
  311. this.$sourceValue.attr('selected', true);
  312. this.$sourceValue.text(value);
  313. this.$sourceValue.addClass('is-hidden');
  314. this.$element.append(this.$sourceValue);
  315. },
  316. _select: function(e)
  317. {
  318. var el = e.target;
  319. var value = el.options[el.selectedIndex].text;
  320. if (this.$sourceValue) this.$sourceValue.remove();
  321. this.$source.val(value);
  322. this.app.broadcast('combobox.set', this, value);
  323. }
  324. });
  325. })(Kube);
  326. (function($K)
  327. {
  328. var datepickerId = 0;
  329. $K.add('module', 'datepicker', {
  330. translations: {
  331. en: {
  332. "days": [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
  333. "months": ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
  334. "months-short": ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  335. }
  336. },
  337. init: function(app, context)
  338. {
  339. this.app = app;
  340. this.$doc = app.$doc;
  341. this.$win = app.$win;
  342. this.$body = app.$body;
  343. this.lang = app.lang;
  344. this.animate = app.animate;
  345. // defaults
  346. var defaults = {
  347. year: false,
  348. month: false,
  349. day: false,
  350. format: '%d.%m.%Y', // %d, %m, %F, %M, %Y
  351. embed: false,
  352. target: false,
  353. selectYear: false,
  354. sundayFirst: false,
  355. startDate: false, // string
  356. endDate: false, // string
  357. animationOpen: 'slideDown',
  358. animationClose: 'slideUp',
  359. };
  360. // context
  361. this.context = context;
  362. this.params = context.getParams(defaults);
  363. this.$element = context.getElement();
  364. this.$target = context.getTarget();
  365. // local
  366. this.uuid = datepickerId++;
  367. this.namespace = '.kube.datepicker-' + this.uuid;
  368. this.dateRegexp = /^(.*?)(\/|\.|,|\s|\-)(.*?)(?:\/|\.|,|\s|\-)(.*?)$/;
  369. this.value = '';
  370. this.today = {};
  371. this.current = {};
  372. this.next = {};
  373. this.prev = {};
  374. this.selected = {};
  375. },
  376. // public
  377. start: function()
  378. {
  379. this.$element.attr('uuid', this.uuid);
  380. // start / end date
  381. this._buildStartEndDate();
  382. // create datepicker
  383. this.$datepicker = $K.create('class.datepicker.box', this.app, this);
  384. this.$datepicker.build();
  385. // append
  386. if (this.params.embed)
  387. {
  388. this.build();
  389. this.update();
  390. this.$datepicker.addClass('is-embed');
  391. this.$element.append(this.$datepicker);
  392. }
  393. else
  394. {
  395. this.$datepicker.addClass('is-hidden');
  396. this.$body.append(this.$datepicker);
  397. this.$element.on('click' + this.namespace, this._open.bind(this));
  398. }
  399. },
  400. stop: function()
  401. {
  402. this._disableEvents();
  403. this.$datepicker.remove();
  404. this.$element.off(this.namespace);
  405. this.$element.removeClass('datepicker-in');
  406. },
  407. build: function()
  408. {
  409. this._buildValue();
  410. this._buildTodayDate();
  411. this._buildSelectedDate();
  412. this._buildCurrentDate();
  413. },
  414. update: function()
  415. {
  416. this._buildPrevNextDate();
  417. this.$grid = $K.create('class.datepicker.grid', this.app, this);
  418. this.$grid.build();
  419. this.$datepicker.setControls(this.prev, this.next);
  420. this.$datepicker.setMonth(this.lang.get('months')[this.current.month]);
  421. this.$datepicker.setYear(this.current.year);
  422. this.$datepicker.setGrid(this.$grid);
  423. },
  424. // SET
  425. setDate: function(e)
  426. {
  427. e.preventDefault();
  428. var $item = $K.dom(e.target);
  429. if ($item.attr('data-disabled') === true) return this._close();
  430. var obj = {
  431. day: $item.attr('data-day'),
  432. month: $item.attr('data-month'),
  433. year: $item.attr('data-year')
  434. };
  435. var date = this._convertDateToFormat(obj);
  436. if (this.params.embed === false)
  437. {
  438. var $target = (this.$target.length !== 0) ? this.$target : this.$element;
  439. if ($target.get().tagName === 'INPUT')
  440. {
  441. $target.val(date);
  442. }
  443. else
  444. {
  445. $target.text(date);
  446. }
  447. this._close();
  448. }
  449. this.app.broadcast('datepicker.set', this, date, obj);
  450. },
  451. setNextMonth: function(e)
  452. {
  453. e.preventDefault();
  454. this.current = this.next;
  455. this.update();
  456. },
  457. setPrevMonth: function(e)
  458. {
  459. e.preventDefault();
  460. this.current = this.prev;
  461. this.update();
  462. },
  463. setYear: function()
  464. {
  465. this.current.year = this.$datepicker.getYearFromSelect();
  466. this.selected.day = false;
  467. this.$datepicker.setYear(this.current.year);
  468. this.update();
  469. },
  470. // BUILD
  471. _buildValue: function()
  472. {
  473. var $target = (this.$target.length !== 0) ? this.$target : this.$element;
  474. this.value = ($target.get().tagName === 'INPUT') ? $target.val() : $target.text().trim();
  475. },
  476. _buildTodayDate: function()
  477. {
  478. var date = new Date();
  479. this.today = {
  480. year: date.getFullYear(),
  481. month: parseInt(date.getMonth() + 1),
  482. day: parseInt(date.getDate())
  483. };
  484. },
  485. _buildSelectedDate: function()
  486. {
  487. this.selected = this._parseDateString(this.value);
  488. // set from params
  489. if (this.value === '')
  490. {
  491. this.selected.year = (this.params.year) ? this.params.year : this.selected.year;
  492. this.selected.month = (this.params.month) ? parseInt(this.params.month) : this.selected.month;
  493. this.selected.day = false;
  494. }
  495. },
  496. _buildCurrentDate: function()
  497. {
  498. this.current = this.selected;
  499. },
  500. _buildPrevNextDate: function()
  501. {
  502. // prev
  503. var date = this._getPrevYearAndMonth(this.current.year, this.current.month);
  504. this.prev = {
  505. year: date.year,
  506. month: date.month
  507. };
  508. // next
  509. var date = this._getNextYearAndMonth(this.current.year, this.current.month);
  510. this.next = {
  511. year: date.year,
  512. month: date.month
  513. };
  514. },
  515. _buildStartEndDate: function()
  516. {
  517. this.params.startDate = (this.params.startDate) ? this._parseDateString(this.params.startDate) : false;
  518. this.params.endDate = (this.params.endDate) ? this._parseDateString(this.params.endDate) : false;
  519. },
  520. _buildPosition: function()
  521. {
  522. this.position = {};
  523. var pos = this.$element.offset();
  524. var height = this.$element.innerHeight();
  525. var width = this.$element.innerWidth();
  526. var datepickerWidth = this.$datepicker.innerWidth();
  527. var datepickerHeight = this.$datepicker.innerHeight();
  528. var windowWidth = this.$win.width();
  529. var documentHeight = this.$doc.height();
  530. var right = 0;
  531. var left = pos.left;
  532. var top = pos.top + height + 1;
  533. this.position.type = 'left';
  534. if ((left + datepickerWidth) > windowWidth)
  535. {
  536. this.position.type = 'right';
  537. right = (windowWidth - (left + width));
  538. }
  539. if ((top + datepickerHeight) > documentHeight)
  540. {
  541. this.params.animationOpen = 'show';
  542. this.params.animationClose = 'hide';
  543. top = (top - datepickerHeight - height - 2);
  544. }
  545. this.position.top = top;
  546. this.position.left = left;
  547. this.position.right = right;
  548. },
  549. // OPEN
  550. _open: function(e)
  551. {
  552. if (e) e.preventDefault();
  553. if (this._isOpened()) return;
  554. this._closeAll();
  555. this.app.broadcast('datepicker.open', this);
  556. this.build();
  557. this.update();
  558. this._buildPosition();
  559. this._setPosition();
  560. this.animate.run(this.$datepicker, this.params.animationOpen, this._opened.bind(this));
  561. },
  562. _opened: function()
  563. {
  564. this._enableEvents();
  565. this.$element.addClass('datepicker-in');
  566. this.$datepicker.addClass('is-open');
  567. this.app.broadcast('datepicker.opened', this);
  568. },
  569. _isOpened: function()
  570. {
  571. return this.$datepicker.hasClass('is-open');
  572. },
  573. // CLOSE
  574. _close: function(e)
  575. {
  576. if (e && $K.dom(e.target).closest('.datepicker').length !== 0)
  577. {
  578. return;
  579. }
  580. if (!this._isOpened()) return;
  581. this.app.broadcast('datepicker.close', this);
  582. this.animate.run(this.$datepicker, this.params.animationClose, this._closed.bind(this));
  583. },
  584. _closed: function()
  585. {
  586. this._disableEvents();
  587. this.$datepicker.removeClass('is-open');
  588. this.$element.removeClass('datepicker-in');
  589. this.app.broadcast('datepicker.closed', this);
  590. },
  591. _closeAll: function()
  592. {
  593. $K.dom('.datepicker.is-open').each(function(node)
  594. {
  595. var $el = $K.dom(node);
  596. var id = $el.attr('data-uuid');
  597. this.$doc.off('.kube.datepicker-' + id);
  598. this.$win.off('.kube.datepicker-' + id);
  599. $el.removeClass('is-open');
  600. $el.addClass('is-hidden');
  601. }.bind(this));
  602. $K.dom('.datepicker-in').removeClass('datepicker-in');
  603. },
  604. // EVENTS
  605. _handleKeyboard: function(e)
  606. {
  607. if (e.which === 27) this._close();
  608. },
  609. _enableEvents: function()
  610. {
  611. this.$doc.on('keyup' + this.namespace, this._handleKeyboard.bind(this));
  612. this.$doc.on('click' + this.namespace + ' touchstart' + this.namespace, this._close.bind(this));
  613. this.$win.on('resize' + this.namespace, this._resizePosition.bind(this));
  614. },
  615. _disableEvents: function()
  616. {
  617. this.$doc.off(this.namespace);
  618. this.$win.off(this.namespace);
  619. },
  620. // POSITION
  621. _resizePosition: function()
  622. {
  623. this._buildPosition();
  624. this._setPosition();
  625. },
  626. _setPosition: function()
  627. {
  628. var left = 'auto';
  629. var right = this.position.right + 'px';
  630. if (this.position.type === 'left')
  631. {
  632. left = this.position.left + 'px',
  633. right = 'auto';
  634. }
  635. this.$datepicker.css({ top: this.position.top + 'px', left: left, right: right });
  636. },
  637. // PARSE
  638. _parseDateString: function(str)
  639. {
  640. var obj = {};
  641. var date = str.match(this.dateRegexp);
  642. var format = this.params.format.match(this.dateRegexp);
  643. obj.year = (date === null) ? this.today.year : parseInt(date[4]);
  644. if (format[1] === '%m' || format[1] === '%M' || format[1] === '%F')
  645. {
  646. obj.month = (date === null) ? this.today.month : this._parseMonth(format[1], date[1]);
  647. obj.day = (date === null) ? false : parseInt(date[3]);
  648. }
  649. else
  650. {
  651. obj.month = (date === null) ? this.today.month : this._parseMonth(format[3], date[3]);
  652. obj.day = (date === null) ? false : parseInt(date[1]);
  653. }
  654. obj.splitter = (date === null) ? '.' : date[2];
  655. return obj;
  656. },
  657. _parseMonth: function(type, month)
  658. {
  659. var index = parseInt(month);
  660. if (type === '%M') index = this.lang.get('months-short').indexOf(month);
  661. else if (type === '%F') index = this.lang.get('months').indexOf(month);
  662. return index;
  663. },
  664. // CONVERT
  665. _convertDateToFormat: function(obj)
  666. {
  667. var formated = this.params.format.replace('%d', obj.day);
  668. formated = formated.replace('%F', this.lang.get('months')[obj.month]);
  669. formated = formated.replace('%m', this._addZero(obj.month));
  670. formated = formated.replace('%M', this.lang.get('months-short')[obj.month]);
  671. formated = formated.replace('%Y', obj.year);
  672. return formated;
  673. },
  674. _addZero: function(str)
  675. {
  676. str = Number(str);
  677. return (str < 10) ? '0' + str : str;
  678. },
  679. // GET
  680. _getPrevYearAndMonth: function(year, month)
  681. {
  682. var date = {
  683. year: year,
  684. month: parseInt(month) - 1
  685. };
  686. if (date.month <= 0)
  687. {
  688. date.month = 12;
  689. date.year--;
  690. }
  691. return date;
  692. },
  693. _getNextYearAndMonth: function(year, month)
  694. {
  695. var date = {
  696. year: year,
  697. month: parseInt(month) + 1
  698. };
  699. if (date.month > 12)
  700. {
  701. date.month = 1;
  702. date.year++;
  703. }
  704. return date;
  705. }
  706. });
  707. })(Kube);
  708. (function($K)
  709. {
  710. $K.add('class', 'datepicker.box', {
  711. extends: ['dom'],
  712. init: function(app, datepicker)
  713. {
  714. this.app = app;
  715. this.lang = app.lang;
  716. this.datepicker = datepicker;
  717. this.params = datepicker.params;
  718. this.namespace = datepicker.namespace;
  719. this.selected = datepicker.selected;
  720. },
  721. build: function()
  722. {
  723. this._buildBox();
  724. this._buildHead();
  725. this._buildControlPrev();
  726. this._buildMonthBox();
  727. this._buildControlNext();
  728. this._buildWeekdays();
  729. this._buildBody();
  730. },
  731. getYearFromSelect: function()
  732. {
  733. return Number(this.$yearSelect.val());
  734. },
  735. setMonth: function(month)
  736. {
  737. this.$month.html(month);
  738. },
  739. setYear: function(year)
  740. {
  741. this.$yearValue.html(year);
  742. if (this.params.selectYear && this.$yearSelect)
  743. {
  744. this.$yearSelect.val(year);
  745. }
  746. },
  747. setGrid: function($grid)
  748. {
  749. this.$dbody.html('');
  750. this.$dbody.append($grid);
  751. },
  752. setControls: function(prev, next)
  753. {
  754. var buildDate = function(obj, d)
  755. {
  756. d = (d) ? d : obj.day;
  757. return new Date(obj.year + '/' + obj.month + '/' + d);
  758. }
  759. if (this.params.startDate)
  760. {
  761. var datePrev = buildDate(prev, 31);
  762. var start = buildDate(this.params.startDate);
  763. var fn = (start.getTime() > datePrev.getTime()) ? 'hide' : 'show';
  764. this.$prev[fn]();
  765. }
  766. if (this.params.endDate)
  767. {
  768. var dateNext = buildDate(next, 1);
  769. var end = buildDate(this.params.endDate);
  770. var fn = (end.getTime() < dateNext.getTime()) ? 'hide' : 'show';
  771. this.$next[fn]();
  772. }
  773. },
  774. // private
  775. _buildBox: function()
  776. {
  777. this.parse('<div>');
  778. this.addClass('datepicker');
  779. },
  780. _buildHead: function()
  781. {
  782. this.$head = $K.dom('<div class="datepicker-head">');
  783. this.append(this.$head);
  784. },
  785. _buildControlPrev: function()
  786. {
  787. this.$prev = $K.dom('<span class="datepicker-control datepicker-control-prev" />').html('&lt;');
  788. this.$prev.on('click' + this.namespace, this.datepicker.setPrevMonth.bind(this.datepicker));
  789. this.$head.append(this.$prev);
  790. },
  791. _buildControlNext: function()
  792. {
  793. this.$next = $K.dom('<span class="datepicker-control datepicker-control-next" />').html('&gt;');
  794. this.$next.on('click' + this.namespace, this.datepicker.setNextMonth.bind(this.datepicker));
  795. this.$head.append(this.$next);
  796. },
  797. _buildMonthBox: function()
  798. {
  799. this.$monthBox = $K.dom('<div class="datepicker-month-box">');
  800. this.$head.append(this.$monthBox);
  801. this._buildMonth();
  802. this._buildYear();
  803. this._buildYearSelect();
  804. },
  805. _buildMonth: function()
  806. {
  807. this.$month = $K.dom('<span />');
  808. this.$monthBox.append(this.$month);
  809. },
  810. _buildYear: function()
  811. {
  812. this.$year = $K.dom('<span />');
  813. this.$yearValue = $K.dom('<span />');
  814. this.$year.append(this.$yearValue);
  815. this.$monthBox.append(this.$year);
  816. },
  817. _buildYearSelect: function()
  818. {
  819. if (!this.params.selectYear) return;
  820. var now = new Date();
  821. var start = (this.params.startDate) ? this.params.startDate.year : (now.getFullYear() - 99);
  822. var end = (this.params.endDate) ? this.params.endDate.year : now.getFullYear();
  823. if ((end - start) < 2)
  824. {
  825. return;
  826. }
  827. this.$yearSelect = $K.dom('<select />');
  828. this.$year.append(this.$yearSelect);
  829. this.$year.append('<span class="datepicker-select-year-caret" />');
  830. this.$year.addClass('datepicker-select-year');
  831. for (var i = start; i <= end; i++)
  832. {
  833. var $option = $K.dom('<option value="' + i + '">' + i + '</option>');
  834. this.$yearSelect.append($option);
  835. }
  836. this.$yearSelect.on('change' + this.namespace, this.datepicker.setYear.bind(this.datepicker));
  837. },
  838. _buildWeekdays: function()
  839. {
  840. this.$weekdays = $K.dom('<div class="datepicker-weekdays">');
  841. var result = [];
  842. if (this.params.sundayFirst)
  843. {
  844. var last = this.lang.get('days').slice(6);
  845. result = this.lang.get('days').slice(0, 6);
  846. result.unshift(last[0]);
  847. }
  848. else
  849. {
  850. result = this.lang.get('days');
  851. }
  852. for (var i = 0; i < result.length; i++)
  853. {
  854. var tr = $K.dom('<span>').html(result[i]);
  855. this.$weekdays.append(tr);
  856. }
  857. this.append(this.$weekdays);
  858. },
  859. _buildBody: function()
  860. {
  861. this.$dbody = $K.dom('<div class="datepicker-body">');
  862. this.append(this.$dbody);
  863. }
  864. });
  865. })(Kube);
  866. (function($K)
  867. {
  868. $K.add('class', 'datepicker.grid', {
  869. extends: ['dom'],
  870. init: function(app, datepicker)
  871. {
  872. this.app = app;
  873. this.lang = app.lang;
  874. this.datepicker = datepicker;
  875. this.params = datepicker.params;
  876. this.namespace = datepicker.namespace;
  877. this.today = datepicker.today;
  878. this.selected = datepicker.selected;
  879. this.current = datepicker.current;
  880. this.prev = datepicker.prev;
  881. this.next = datepicker.next;
  882. // local
  883. this.daysInMonth = [0,31,28,31,30,31,30,31,31,30,31,30,31];
  884. },
  885. build: function()
  886. {
  887. this.parse('<div class="datepicker-grid">');
  888. var daysInCurrentMonth = this._getDaysInCurrentMonth();
  889. var daysInPrevMonth = this._getDaysInPrevMonth();
  890. var daysInNextMonth = this._getDaysInNextMonth();
  891. // start index
  892. var d = new Date(this.current.year, this.current.month - 1, 1);
  893. var startIndex = (this.params.sundayFirst) ? d.getDay() + 1 : ((!d.getDay()) ? 7 : d.getDay());
  894. var daysPrevMonthStart = daysInPrevMonth - startIndex + 2;
  895. var startCurrent = 8 - startIndex;
  896. var y = 1, c = 1, obj;
  897. for (var z = 0; z < 6; z++)
  898. {
  899. var tr = $K.dom('<div class="datepicker-row">');
  900. for (var i = 0; i < 7; i++)
  901. {
  902. if (z === 0)
  903. {
  904. var dayPrev = daysPrevMonthStart + i;
  905. if (dayPrev > daysInPrevMonth)
  906. {
  907. // current day
  908. obj = this._buildGridObj(i, y, this.current, false, false);
  909. y++;
  910. }
  911. else
  912. {
  913. // prev day
  914. obj = this._buildGridObj(i, dayPrev, this.prev, false, true);
  915. }
  916. }
  917. else if (y > daysInCurrentMonth)
  918. {
  919. // next day
  920. obj = this._buildGridObj(i, c, this.next, true, false);
  921. c++;
  922. }
  923. else
  924. {
  925. // current day
  926. obj = this._buildGridObj(i, y, this.current, false, false);
  927. y++;
  928. }
  929. tr.append(this._buildGridDay(obj));
  930. }
  931. this.append(tr);
  932. }
  933. },
  934. // private
  935. _buildGridObj: function(i, day, date, next, prev)
  936. {
  937. return {
  938. day: day,
  939. next: next,
  940. prev: prev,
  941. year: date.year,
  942. month: date.month,
  943. date: this._getGridDay(date.year, date.month, day),
  944. selected: this._isSelectedDate(date.year, date.month, day),
  945. today: this._isTodayDate(date.year, date.month, day),
  946. weekend: (i > 4),
  947. disabled: this._isDisabledDate(date.year, date.month, day)
  948. };
  949. },
  950. _buildGridDay: function(obj)
  951. {
  952. var td = $K.dom('<div class="datepicker-cell">');
  953. if (obj.next || obj.prev)
  954. {
  955. td.addClass('is-out');
  956. }
  957. if (obj.selected) td.addClass('is-selected');
  958. if (obj.today) td.addClass('is-today');
  959. if (obj.weekend && this.params.weekend) td.addClass('is-weekend');
  960. if (obj.disabled) td.addClass('is-disabled');
  961. var $item = $K.dom('<a>');
  962. $item.html(obj.day);
  963. $item.attr('href', '#');
  964. $item.attr('data-disabled', obj.disabled);
  965. $item.attr('data-date', obj.date);
  966. $item.attr('data-day', obj.day);
  967. $item.attr('data-month', obj.month);
  968. $item.attr('data-year', obj.year);
  969. $item.on('click', this.datepicker.setDate.bind(this.datepicker));
  970. return td.append($item);
  971. },
  972. _isSelectedDate: function(year, month, day)
  973. {
  974. return (this.selected.year === year && this.selected.month === month && this.selected.day === day);
  975. },
  976. _isTodayDate: function(year, month, day)
  977. {
  978. return (this.today.year === year && this.today.month === month && this.today.day === day);
  979. },
  980. _isDisabledDate: function(year, month, day)
  981. {
  982. var date = new Date(year + '/' + month + '/' + day);
  983. if (this.params.startDate)
  984. {
  985. var dateStart = new Date(this.params.startDate.year + '/' + this.params.startDate.month + '/' + this.params.startDate.day);
  986. if (date.getTime() < dateStart.getTime())
  987. {
  988. return true;
  989. }
  990. }
  991. if (this.params.endDate)
  992. {
  993. var dateEnd = new Date(this.params.endDate.year + '/' + this.params.endDate.month + '/' + this.params.endDate.day);
  994. if (date.getTime() > dateEnd.getTime())
  995. {
  996. return true;
  997. }
  998. }
  999. return false;
  1000. },
  1001. _getGridDay: function(year, month, day)
  1002. {
  1003. return year + '-' + month + '-' + day;
  1004. },
  1005. _getDaysInCurrentMonth: function()
  1006. {
  1007. return this._getDaysInMonth(this.current.year, this.current.month);
  1008. },
  1009. _getDaysInPrevMonth: function()
  1010. {
  1011. return this._getDaysInMonth(this.prev.year, this.prev.month);
  1012. },
  1013. _getDaysInNextMonth: function()
  1014. {
  1015. return this._getDaysInMonth(this.next.year, this.next.month);
  1016. },
  1017. _getDaysInMonth: function (year, month)
  1018. {
  1019. return (((0 === (year%4)) && ((0 !== (year%100)) || (0 === (year%400)))) && (month === 1)) ? 29 : this.daysInMonth[month];
  1020. }
  1021. });
  1022. })(Kube);
  1023. (function($K)
  1024. {
  1025. $K.add('module', 'editable', {
  1026. init: function(app, context)
  1027. {
  1028. this.app = app;
  1029. // defaults
  1030. var defaults = {
  1031. classname: 'editable',
  1032. focus: false
  1033. };
  1034. // context
  1035. this.context = context;
  1036. this.params = context.getParams(defaults);
  1037. this.$element = context.getElement();
  1038. },
  1039. // public
  1040. start: function()
  1041. {
  1042. this.$element.addClass(this.params.classname).attr('contenteditable', true);
  1043. this._setFocus();
  1044. this._setEvents();
  1045. },
  1046. stop: function()
  1047. {
  1048. this.$element.removeClass(this.params.classname).removeAttr('contenteditable');
  1049. this.$element.off('.kube.editable');
  1050. },
  1051. // private
  1052. _setEvents: function()
  1053. {
  1054. this.$element.on('keydown.kube.editable', this._keydown.bind(this));
  1055. this.$element.on('paste.kube.editable', this._paste.bind(this));
  1056. this.$element.on('blur.kube.editable', this._blur.bind(this));
  1057. },
  1058. _setFocus: function()
  1059. {
  1060. if (this.params.focus) this.$element.focus();
  1061. },
  1062. _checkEmpty: function()
  1063. {
  1064. if (!this.$element.text().replace(" ", "").length)
  1065. {
  1066. this.$element.empty();
  1067. }
  1068. },
  1069. _paste: function(e)
  1070. {
  1071. e.preventDefault();
  1072. var event = (e.originalEvent || e);
  1073. var text = '';
  1074. if (event.clipboardData)
  1075. {
  1076. text = event.clipboardData.getData('text/plain');
  1077. document.execCommand('insertText', false, text);
  1078. }
  1079. else if (window.clipboardData)
  1080. {
  1081. text = window.clipboardData.getData('Text');
  1082. document.selection.createRange().pasteHTML(text);
  1083. }
  1084. },
  1085. _blur: function(e)
  1086. {
  1087. this._checkEmpty();
  1088. },
  1089. _keydown: function(e)
  1090. {
  1091. // disable enter key
  1092. if (e.which === 13) e.preventDefault();
  1093. }
  1094. });
  1095. })(Kube);
  1096. (function($K)
  1097. {
  1098. $K.add('module', 'magicquery', {
  1099. init: function(app, context)
  1100. {
  1101. this.app = app;
  1102. this.response = app.response;
  1103. // defaults
  1104. var defaults = {
  1105. url: false
  1106. };
  1107. // context
  1108. this.context = context;
  1109. this.params = context.getParams(defaults);
  1110. this.$element = context.getElement();
  1111. },
  1112. // public
  1113. start: function()
  1114. {
  1115. this.$element.on('click.kube.magicquery', this._send.bind(this));
  1116. },
  1117. stop: function()
  1118. {
  1119. this._enable();
  1120. this.$element.off('.kube.magicquery');
  1121. },
  1122. // private
  1123. _disable: function()
  1124. {
  1125. this.$element.attr('disabled', true);
  1126. },
  1127. _enable: function()
  1128. {
  1129. this.$element.removeAttr('disabled');
  1130. },
  1131. _send: function(e)
  1132. {
  1133. e.preventDefault();
  1134. this._disable();
  1135. $K.ajax.post({
  1136. url: this.params.url,
  1137. success: this._parse.bind(this)
  1138. });
  1139. },
  1140. _parse: function(data)
  1141. {
  1142. this._enable();
  1143. var json = this.response.parse(data);
  1144. if (json)
  1145. {
  1146. this.app.broadcast('magicquery.success', this, json);
  1147. }
  1148. },
  1149. });
  1150. })(Kube);
  1151. (function($K)
  1152. {
  1153. $K.add('module', 'number', {
  1154. init: function(app, context)
  1155. {
  1156. this.app = app;
  1157. // context
  1158. this.context = context;
  1159. this.$element = context.getElement();
  1160. },
  1161. // public
  1162. start: function()
  1163. {
  1164. this.$input = this.$element.find('input[type="number"]');
  1165. this.$btnUp = this.$element.find('.is-up');
  1166. this.$btnDown = this.$element.find('.is-down');
  1167. this._buildStep();
  1168. this._buildMin();
  1169. this._buildMax();
  1170. if (!this._isDisabled())
  1171. {
  1172. this.$btnUp.on('click.kube.number', this._increase.bind(this));
  1173. this.$btnDown.on('click.kube.number', this._decrease.bind(this));
  1174. }
  1175. },
  1176. stop: function()
  1177. {
  1178. this.$btnUp.off('.kube.number');
  1179. this.$btnDown.off('.kube.number');
  1180. },
  1181. // private
  1182. _buildStep: function()
  1183. {
  1184. var step = this.$input.attr('step');
  1185. this.step = (step) ? parseFloat(step) : 1;
  1186. },
  1187. _buildMin: function()
  1188. {
  1189. var min = this.$input.attr('min');
  1190. this.min = (min) ? parseFloat(min) : false;
  1191. },
  1192. _buildMax: function()
  1193. {
  1194. var max = this.$input.attr('max');
  1195. this.max = (max) ? parseFloat(max) : false;
  1196. },
  1197. _isDisabled: function()
  1198. {
  1199. return this.$input.attr('disabled');
  1200. },
  1201. _getValue: function()
  1202. {
  1203. var value = parseFloat(this.$input.val());
  1204. var min = (this.min === false) ? 0 : this.min;
  1205. return (isNaN(value)) ? min : value;
  1206. },
  1207. _increase: function(e)
  1208. {
  1209. if (e)
  1210. {
  1211. e.preventDefault();
  1212. e.stopPropagation();
  1213. }
  1214. var oldValue = this._getValue();
  1215. var newVal = (this.max !== false && oldValue >= this.max) ? oldValue : oldValue + this.step;
  1216. this.$input.val(newVal);
  1217. },
  1218. _decrease: function(e)
  1219. {
  1220. if (e)
  1221. {
  1222. e.preventDefault();
  1223. e.stopPropagation();
  1224. }
  1225. var oldValue = this._getValue();
  1226. var newVal = (this.min !== false && oldValue <= this.min) ? oldValue : oldValue - this.step;
  1227. this.$input.val(newVal);
  1228. }
  1229. });
  1230. })(Kube);
  1231. (function($K)
  1232. {
  1233. $K.add('module', 'selector', {
  1234. init: function(app, context)
  1235. {
  1236. this.app = app;
  1237. // context
  1238. this.context = context;
  1239. this.$element = context.getElement();
  1240. },
  1241. // public
  1242. start: function()
  1243. {
  1244. this.$selector = this._buildSelector();
  1245. this.$selector.on('change.kube.selector', this._toggle.bind(this));
  1246. },
  1247. stop: function()
  1248. {
  1249. this.$selector.off('.kube.selector');
  1250. },
  1251. // private
  1252. _isSelect: function()
  1253. {
  1254. return (this.$element.get().tagName === 'SELECT');
  1255. },
  1256. _isHashValue: function(value)
  1257. {
  1258. return (value.search(/^#/) === 0);
  1259. },
  1260. _buildSelector: function()
  1261. {
  1262. return (this._isSelect()) ? this.$element : this.$element.find('input[type="radio"]');
  1263. },
  1264. _getValue: function()
  1265. {
  1266. return (this._isSelect()) ? this.$selector.val() : this.$selector.filter(':checked').val();
  1267. },
  1268. _getBoxes: function()
  1269. {
  1270. var $boxes = $K.dom([]);
  1271. var $targets = (this._isSelect()) ? this.$selector.find('option') : this.$selector;
  1272. $targets.each(function(node)
  1273. {
  1274. if (this._isHashValue(node.value))
  1275. {
  1276. $boxes.add($K.dom(node.value));
  1277. }
  1278. }.bind(this));
  1279. return $boxes;
  1280. },
  1281. _toggle: function()
  1282. {
  1283. var value = this._getValue();
  1284. var $boxes = this._getBoxes();
  1285. var $box = $K.dom(value);
  1286. $boxes.addClass('is-hidden');
  1287. $box.removeClass('is-hidden');
  1288. this.app.broadcast('selector.opened', this, $box);
  1289. }
  1290. });
  1291. })(Kube);
  1292. (function($K)
  1293. {
  1294. $K.add('module', 'slider', {
  1295. init: function(app, context)
  1296. {
  1297. this.app = app;
  1298. this.$win = app.$win;
  1299. this.$doc = app.$doc;
  1300. // defaults
  1301. var defaults = {
  1302. min: 0,
  1303. max: 100,
  1304. step: 1,
  1305. value: 0,
  1306. target: false
  1307. };
  1308. // context
  1309. this.context = context;
  1310. this.params = context.getParams(defaults);
  1311. this.$element = context.getElement();
  1312. this.$target = context.getTarget();
  1313. // local
  1314. this.isTicks = false;
  1315. },
  1316. // public
  1317. start: function()
  1318. {
  1319. this._buildTrack();
  1320. this._buildFill();
  1321. this._buildHandle();
  1322. this._buildTicks();
  1323. this.update();
  1324. this.$win.on('resize.kube.slider', this._resize.bind(this));
  1325. this.$element.on('mousedown.kube.slider touchstart.kube.slider', this._handleDown.bind(this));
  1326. },
  1327. stop: function()
  1328. {
  1329. this.$win.off('.kube.slider');
  1330. this.$doc.off('.kube.slider');
  1331. this.$element.off('.kube.slider');
  1332. },
  1333. update: function(value)
  1334. {
  1335. this.value = (value) ? value : this.params.value;
  1336. this.value = (this.value < this.params.min) ? this.params.min : this.value;
  1337. this.handleWidth = this.$handle.width();
  1338. this.trackWidth = this.$track.width();
  1339. this.maxHandlePosition = this.trackWidth - this.handleWidth;
  1340. this.fixPosition = this.handleWidth / 2;
  1341. this.position = this._getPositionFromValue(this.value);
  1342. this._setPosition(this.position);
  1343. this._setTarget();
  1344. },
  1345. // private
  1346. _resize: function()
  1347. {
  1348. this._buildTicks();
  1349. this.update(this.value);
  1350. },
  1351. _isDisabled: function()
  1352. {
  1353. return (this.$element.hasClass('is-disabled') || this.$element.attr('disabled'));
  1354. },
  1355. _buildTrack: function()
  1356. {
  1357. this.$track = $K.dom('<div />');
  1358. this.$track.addClass('slider-track');
  1359. this.$element.prepend(this.$track);
  1360. },
  1361. _buildFill: function()
  1362. {
  1363. this.$fill = $K.dom('<div />');
  1364. this.$fill.addClass('slider-fill');
  1365. this.$track.append(this.$fill);
  1366. },
  1367. _buildHandle: function()
  1368. {
  1369. this.$handle = $K.dom('<div />');
  1370. this.$handle.addClass('slider-handle');
  1371. this.$track.append(this.$handle);
  1372. },
  1373. _buildTicks: function()
  1374. {
  1375. this.$ticks = this.$element.find('.slider-ticks span');
  1376. var size = this.$ticks.length;
  1377. this.isTicks = (size !== 0)
  1378. if (!this.isTicks) return;
  1379. var handleWidth = this.$handle.width();
  1380. var width = this.$element.width() - handleWidth;
  1381. var fix = handleWidth/2;
  1382. var step = width/(size-1);
  1383. var start = fix;
  1384. this.$ticks.each(function(node, i)
  1385. {
  1386. var $node = $K.dom(node);
  1387. var left = start + step * i;
  1388. $node.css({ 'left': left + 'px', 'width': step + 'px', 'text-indent': '-' + (step-fix) + 'px' });
  1389. });
  1390. },
  1391. _handleDown: function(e)
  1392. {
  1393. e.preventDefault();
  1394. if (this._isDisabled()) return;
  1395. this.$doc.on('mousemove.kube.slider touchmove.kube.slider', this._handleMove.bind(this));
  1396. this.$doc.on('mouseup.kube.slider touchend.kube.slider', this._handleEnd.bind(this));
  1397. var pos = (e.touches && e.touches.length > 0) ? e.changedTouches[0].clientX : e.clientX;
  1398. var trackPos = this.$track.offset().left;
  1399. var setPos = (pos - trackPos - this.fixPosition);
  1400. this._setPosition(setPos);
  1401. this._setTarget();
  1402. },
  1403. _handleMove: function(e)
  1404. {
  1405. e.preventDefault();
  1406. var pos = (e.touches && e.touches.length > 0) ? e.changedTouches[0].clientX : e.clientX;
  1407. var trackPos = this.$track.offset().left;
  1408. var setPos = (pos - trackPos - this.fixPosition);
  1409. this._setPosition(setPos);
  1410. this._setTarget();
  1411. },
  1412. _handleEnd: function(e)
  1413. {
  1414. e.preventDefault();
  1415. this.$doc.off('.kube.slider');
  1416. },
  1417. _setPosition: function(pos)
  1418. {
  1419. pos = this._getEdge(pos, 0, this.maxHandlePosition);
  1420. var value = this._getValueFromPosition(pos);
  1421. var newPos = this._getPositionFromValue(value);
  1422. // update ui
  1423. this.$fill.css('width', (newPos + this.fixPosition) + 'px');
  1424. this.$handle.css('left', newPos + 'px');
  1425. // update globals
  1426. this.position = newPos;
  1427. this.value = value;
  1428. },
  1429. _setTarget: function()
  1430. {
  1431. this.app.broadcast('slider.set', this, this.value);
  1432. if (this.$target.length === 0) return;
  1433. var tag = this.$target.get().tagName;
  1434. if (tag === 'INPUT' || tag === 'SELECT') this.$target.val(this.value);
  1435. else this.$target.text(this.value);
  1436. },
  1437. _getPositionFromValue: function(value)
  1438. {
  1439. var percentage = (value - this.params.min)/(this.params.max - this.params.min);
  1440. return pos = (!Number.isNaN(percentage)) ? percentage * this.maxHandlePosition : 0;
  1441. },
  1442. _getValueFromPosition: function(pos)
  1443. {
  1444. var percentage = ((pos) / (this.maxHandlePosition || 1));
  1445. var value = this.params.step * Math.round(percentage * (this.params.max - this.params.min) / this.params.step) + this.params.min;
  1446. return Number((value).toFixed((this.params.step + '').replace('.', '').length - 1));
  1447. },
  1448. _getEdge: function(pos, min, max)
  1449. {
  1450. if (pos < min) return min;
  1451. if (pos > max) return max;
  1452. return pos;
  1453. }
  1454. });
  1455. })(Kube);
  1456. (function($K)
  1457. {
  1458. $K.add('module', 'upload', {
  1459. init: function(app, context)
  1460. {
  1461. this.app = app;
  1462. this.utils = app.utils;
  1463. this.animate = app.animate;
  1464. this.response = app.response;
  1465. this.progress = app.progress;
  1466. // defaults
  1467. var defaults = {
  1468. size: 120, // pixels
  1469. url: false,
  1470. urlRemove: false,
  1471. param: false,
  1472. type: false, // image, file
  1473. multiple: false,
  1474. placeholder: 'Drop files here or click to upload',
  1475. progress: false,
  1476. target: false,
  1477. append: false
  1478. };
  1479. // context
  1480. this.context = context;
  1481. this.params = context.getParams(defaults);
  1482. this.$element = context.getElement();
  1483. this.$target = context.getTarget();
  1484. // local
  1485. this.statusMap = ['hover', 'error', 'success', 'drop'];
  1486. },
  1487. // public
  1488. start: function()
  1489. {
  1490. this._buildBox();
  1491. this._buildInput();
  1492. this._buildCount();
  1493. this._buildType();
  1494. this._buildPlaceholder();
  1495. this._buildSize();
  1496. this._buildMultiple();
  1497. this._buildItems();
  1498. this._buildEvents();
  1499. },
  1500. stop: function()
  1501. {
  1502. this.$box.remove();
  1503. this.$element.off('.kube.upload');
  1504. },
  1505. // private
  1506. _buildBox: function()
  1507. {
  1508. if (this.params.type === 'image')
  1509. {
  1510. this.$box = this.$element.find('.upload-item');
  1511. }
  1512. else
  1513. {
  1514. this.$box = this.$element;
  1515. }
  1516. },
  1517. _buildInput: function()
  1518. {
  1519. this.$input = $K.dom('<input>');
  1520. this.$input.attr('type', 'file');
  1521. this.$input.attr('name', this._getParamName());
  1522. this.$input.hide();
  1523. this.$element.before(this.$input);
  1524. },
  1525. _buildCount: function()
  1526. {
  1527. this.$inputCount = $K.dom('<input>');
  1528. this.$inputCount.attr('type', 'hidden');
  1529. this.$inputCount.attr('name', this._getParamName() + '-count');
  1530. this.$inputCount.val(0);
  1531. this.$element.before(this.$inputCount);
  1532. },
  1533. _buildType: function()
  1534. {
  1535. this.isBox = this.$element.hasClass('upload');
  1536. },
  1537. _buildPlaceholder: function()
  1538. {
  1539. if (this.isBox)
  1540. {
  1541. var $placeholder = $K.dom('<span>');
  1542. $placeholder.addClass('upload-placeholder');
  1543. $placeholder.html(this.params.placeholder);
  1544. this.$element.append($placeholder);
  1545. }
  1546. },
  1547. _buildSize: function()
  1548. {
  1549. if (this.isBox)
  1550. {
  1551. this.$box.css({
  1552. height: this.params.size + 'px'
  1553. });
  1554. }
  1555. else if (this.params.type === 'image')
  1556. {
  1557. this.$box.css({
  1558. width: this.params.size + 'px',
  1559. height: this.params.size + 'px'
  1560. });
  1561. }
  1562. },
  1563. _buildMultiple: function()
  1564. {
  1565. this.isMultiple = this.params.multiple;
  1566. if (this.isMultiple)
  1567. {
  1568. this.$input.attr('multiple', 'true');
  1569. }
  1570. },
  1571. _buildItems: function()
  1572. {
  1573. if (!this.params.type) return;
  1574. var isFile = (this.params.type === 'file');
  1575. var $target = (isFile) ? this.$target : this.$element;
  1576. var fn = (isFile) ? '_removeFile' : '_removeImage';
  1577. var $closes = $target.find('.close');
  1578. $closes.on('click', this[fn].bind(this));
  1579. if (!isFile)
  1580. {
  1581. $closes.closest('.upload-item').addClass('is-uploaded');
  1582. }
  1583. this.$inputCount.val($closes.length);
  1584. },
  1585. _buildEvents: function()
  1586. {
  1587. this.$input.on('change.redactor.upload', this._change.bind(this));
  1588. this.$box.on('click.redactor.upload', this._click.bind(this));
  1589. this.$box.on('drop.redactor.upload', this._drop.bind(this));
  1590. this.$box.on('dragover.redactor.upload', this._dragover.bind(this));
  1591. this.$box.on('dragleave.redactor.upload', this._dragleave.bind(this));
  1592. },
  1593. // Events
  1594. _click: function(e)
  1595. {
  1596. e.preventDefault();
  1597. var $el = $K.dom(e.target);
  1598. if ($el.hasClass('close')) return;
  1599. this.$input.click();
  1600. },
  1601. _change: function(e)
  1602. {
  1603. this.app.broadcast('upload.start', this);
  1604. this._send(e, this.$input.get().files);
  1605. },
  1606. _drop: function(e)
  1607. {
  1608. e.preventDefault();
  1609. this._clearStatuses();
  1610. this._setStatus('drop');
  1611. this.app.broadcast('upload.start', this);
  1612. this._send(e);
  1613. },
  1614. _dragover: function(e)
  1615. {
  1616. e.preventDefault();
  1617. this._setStatus('hover');
  1618. return false;
  1619. },
  1620. _dragleave: function(e)
  1621. {
  1622. e.preventDefault();
  1623. this._removeStatus('hover');
  1624. return false;
  1625. },
  1626. // Count
  1627. _upCount: function()
  1628. {
  1629. var val = this.$inputCount.val();
  1630. val++;
  1631. this.$inputCount.val(val);
  1632. },
  1633. _downCount: function()
  1634. {
  1635. var val = this.$inputCount.val();
  1636. val--;
  1637. val = (val < 0) ? 0 : val;
  1638. this.$inputCount.val(val);
  1639. },
  1640. _clearCount: function()
  1641. {
  1642. this.$inputCount.val(0);
  1643. },
  1644. // Name
  1645. _getParamName: function()
  1646. {
  1647. return (this.params.param) ? this.params.param : 'file';
  1648. },
  1649. _getHiddenName: function()
  1650. {
  1651. var name = this._getParamName();
  1652. return (this.isMultiple) ? name + '-uploaded[]' : name + '-uploaded';
  1653. },
  1654. // Status
  1655. _clearStatuses: function()
  1656. {
  1657. this.$box.removeClass('is-upload-' + this.statusMap.join(' is-upload-'));
  1658. },
  1659. _setStatus: function(status)
  1660. {
  1661. this.$box.addClass('is-upload-' + status);
  1662. },
  1663. _removeStatus: function(status)
  1664. {
  1665. this.$box.removeClass('is-upload-' + status);
  1666. },
  1667. // Target
  1668. _clearTarget: function()
  1669. {
  1670. var $items = this.$target.find('.upload-item');
  1671. $items.each(function(node)
  1672. {
  1673. var $node = $K.dom(node);
  1674. this._removeFileRequest($node.attr('data-id'));
  1675. }.bind(this));
  1676. this._clearCount();
  1677. this.$target.html('');
  1678. },
  1679. _clearBox: function()
  1680. {
  1681. var $items = this.$target.find('.upload-item');
  1682. $items.each(function(node)
  1683. {
  1684. var $node = $K.dom(node);
  1685. this._removeFileRequest($node.attr('data-id'));
  1686. }.bind(this));
  1687. this._clearCount();
  1688. this.$target.html('');
  1689. },
  1690. // Remove
  1691. _removeFile: function(e)
  1692. {
  1693. e.preventDefault();
  1694. var $el = $K.dom(e.target);
  1695. var $item = $el.closest('.upload-item');
  1696. var id = $item.attr('data-id');
  1697. this.animate.run($item, 'fadeOut', function()
  1698. {
  1699. $item.remove();
  1700. this._downCount();
  1701. this._removeFileRequest(id);
  1702. // clear target
  1703. if (this.$target.find('.upload-item').length === 0)
  1704. {
  1705. this.$target.html('');
  1706. }
  1707. }.bind(this))
  1708. },
  1709. _removeImage: function(e)
  1710. {
  1711. e.preventDefault();
  1712. var $el = $K.dom(e.target);
  1713. var $item = $el.closest('.upload-item');
  1714. var id = $item.attr('data-id');
  1715. if (this.isMultiple)
  1716. {
  1717. this.animate.run($item, 'fadeOut', function()
  1718. {
  1719. $item.remove();
  1720. this._downCount();
  1721. this._removeFileRequest(id);
  1722. }.bind(this))
  1723. }
  1724. else
  1725. {
  1726. var $img = $item.find('img');
  1727. $el.hide();
  1728. this.animate.run($img, 'fadeOut', function()
  1729. {
  1730. this.$box.html('');
  1731. this.$box.removeClass('is-uploaded');
  1732. this._clearCount();
  1733. this._removeFileRequest(id);
  1734. }.bind(this))
  1735. }
  1736. },
  1737. _removeFileRequest: function(id)
  1738. {
  1739. if (this.params.urlRemove)
  1740. {
  1741. $K.ajax.post({
  1742. url: this.params.urlRemove,
  1743. data: { id: id }
  1744. });
  1745. }
  1746. },
  1747. // Send
  1748. _send: function(e, files)
  1749. {
  1750. e = e.originalEvent || e;
  1751. files = (files) ? files : e.dataTransfer.files;
  1752. var data = new FormData();
  1753. var name = this._getParamName();
  1754. data = this._buildData(name, files, data);
  1755. if (this.params.append)
  1756. {
  1757. data = this.utils.extendData(data, this.params.append);
  1758. }
  1759. this._sendData(data, files, e);
  1760. },
  1761. _sendData: function(data, files, e)
  1762. {
  1763. if (this.params.progress) this.progress.show();
  1764. $K.ajax.post({
  1765. url: this.params.url,
  1766. data: data,
  1767. before: function(xhr)
  1768. {
  1769. return this.app.broadcast('upload.beforeSend', this, xhr);
  1770. }.bind(this),
  1771. success: function(response)
  1772. {
  1773. this._complete(response, e);
  1774. }.bind(this)
  1775. });
  1776. },
  1777. _buildData: function(name, files, data)
  1778. {
  1779. for (var i = 0; i < files.length; i++)
  1780. {
  1781. data.append(name + '[]', files[i]);
  1782. }
  1783. return data;
  1784. },
  1785. _complete: function (response, e)
  1786. {
  1787. this._clearStatuses();
  1788. if (this.params.progress) this.progress.hide();
  1789. // error
  1790. var json = (Array.isArray(response)) ? response[0] : response;
  1791. if (typeof json.type !== 'undefined' && json.type === 'error')
  1792. {
  1793. this._setStatus('error');
  1794. this.response.parse(response);
  1795. this.app.broadcast('upload.error', this, response);
  1796. }
  1797. // complete
  1798. else
  1799. {
  1800. this._setStatus('success');
  1801. switch (this.params.type)
  1802. {
  1803. case 'image':
  1804. this._completeBoxImage(response);
  1805. break;
  1806. case 'file':
  1807. this._completeBoxFile(response);
  1808. break;
  1809. default:
  1810. this._completeBoxUpload(response);
  1811. }
  1812. this.app.broadcast('upload.complete', this, response);
  1813. setTimeout(this._clearStatuses.bind(this), 500);
  1814. }
  1815. },
  1816. _completeBoxUpload: function(response)
  1817. {
  1818. this.response.parse(response);
  1819. },
  1820. _completeBoxImage: function(response)
  1821. {
  1822. for (var key in response)
  1823. {
  1824. // img
  1825. var $img = $K.dom('<img>');
  1826. $img.attr('src', response[key].url);
  1827. // close
  1828. var $close = $K.dom('<span>');
  1829. $close.addClass('close');
  1830. $close.on('click', this._removeImage.bind(this));
  1831. // hidden
  1832. var $hidden = $K.dom('<input>');
  1833. $hidden.attr('type', 'hidden');
  1834. $hidden.attr('name', this._getHiddenName());
  1835. $hidden.val(response[key].id);
  1836. // item
  1837. var $item = $K.dom('<div>');
  1838. $item.addClass('upload-item is-uploaded');
  1839. $item.attr('data-id', response[key].id);
  1840. if (this.isMultiple)
  1841. {
  1842. // append
  1843. $item.append($close);
  1844. $item.append($img);
  1845. $item.append($hidden);
  1846. this.$box.last().before($item);
  1847. }
  1848. // single
  1849. else
  1850. {
  1851. var $lastImg = this.$box.find('img');
  1852. if ($lastImg.length !== 0)
  1853. {
  1854. this._removeFileRequest(this.$box.attr('data-id'));
  1855. }
  1856. this.$box.html('');
  1857. this.$box.attr('data-id', response[key].id);
  1858. this.$box.append($close);
  1859. this.$box.append($img);
  1860. this.$box.append($hidden);
  1861. return;
  1862. }
  1863. }
  1864. },
  1865. _completeBoxFile: function(response)
  1866. {
  1867. if (!this.isMultiple) this._clearTarget();
  1868. for (var key in response)
  1869. {
  1870. // item
  1871. var $item = $K.dom('<div>');
  1872. $item.addClass('upload-item');
  1873. $item.attr('data-id', response[key].id);
  1874. // file
  1875. var $file = $K.dom('<span>');
  1876. $file.html(response[key].name);
  1877. // close
  1878. var $close = $K.dom('<span>');
  1879. $close.addClass('close');
  1880. $close.on('click', this._removeFile.bind(this));
  1881. // hidden
  1882. var $hidden = $K.dom('<input>');
  1883. $hidden.attr('type', 'hidden');
  1884. $hidden.attr('name', this._getHiddenName());
  1885. $hidden.val(response[key].id);
  1886. // size
  1887. if (typeof response[key].size !== 'undefined')
  1888. {
  1889. var $size = $K.dom('<em>');
  1890. $size.html(response[key].size);
  1891. $file.append($size);
  1892. }
  1893. // append
  1894. $item.append($close);
  1895. $item.append($file);
  1896. $item.append($hidden);
  1897. // target
  1898. this.$target.append($item);
  1899. this._upCount();
  1900. }
  1901. }
  1902. });
  1903. })(Kube);
  1904. (function($K)
  1905. {
  1906. $K.add('module', 'validate', {
  1907. init: function(app, context)
  1908. {
  1909. this.app = app;
  1910. this.$win = app.$win;
  1911. this.progress = app.progress;
  1912. this.response = app.response;
  1913. // defaults
  1914. var defaults = {
  1915. errorClass: 'is-error',
  1916. send: true,
  1917. trigger: false,
  1918. shortcut: false,
  1919. progress: false
  1920. };
  1921. // context
  1922. this.context = context;
  1923. this.params = context.getParams(defaults);
  1924. this.$element = context.getElement();
  1925. },
  1926. // public
  1927. start: function()
  1928. {
  1929. this._disableDefaultValidation();
  1930. this._enableShortcut();
  1931. if (this.params.trigger)
  1932. {
  1933. this._startTrigger();
  1934. }
  1935. else
  1936. {
  1937. this._startSubmit();
  1938. }
  1939. },
  1940. stop: function()
  1941. {
  1942. this.enableButtons();
  1943. this.clear();
  1944. this.$element.off('.kube.validate');
  1945. this.$win.off('.kube.validate');
  1946. if (this.$trigger) this.$trigger.off('.');
  1947. },
  1948. clear: function()
  1949. {
  1950. this.$element.find('.' + this.params.errorClass).each(this._clearError.bind(this));
  1951. },
  1952. disableButtons: function()
  1953. {
  1954. this.$element.find('button').attr('disabled', true);
  1955. },
  1956. enableButtons: function()
  1957. {
  1958. this.$element.find('button').removeAttr('disabled');
  1959. },
  1960. // private
  1961. _build: function(e)
  1962. {
  1963. e.preventDefault();
  1964. if (this.params.send) this._send();
  1965. else this.app.broadcast('validate.send', this);
  1966. return false;
  1967. },
  1968. _send: function()
  1969. {
  1970. if (this.params.progress)
  1971. {
  1972. this.progress.show();
  1973. }
  1974. this.disableButtons();
  1975. this._saveCodeMirror();
  1976. this.app.broadcast('validate.send', this);
  1977. $K.ajax.post({
  1978. url: this.$element.attr('action'),
  1979. data: this.$element.serialize(),
  1980. success: this._parse.bind(this)
  1981. });
  1982. return false;
  1983. },
  1984. _parse: function(data)
  1985. {
  1986. this.enableButtons();
  1987. this.clear();
  1988. if (this.params.progress)
  1989. {
  1990. this.progress.hide();
  1991. }
  1992. var json = this.response.parse(data);
  1993. if (!json)
  1994. {
  1995. this.app.broadcast('validate.error', this, json);
  1996. }
  1997. else if (typeof json.type !== 'undefined' && json.type === 'error')
  1998. {
  1999. this._setErrors(json.errors);
  2000. this.app.broadcast('validate.error', this, json.errors);
  2001. }
  2002. else
  2003. {
  2004. this.app.broadcast('validate.success', this, json);
  2005. }
  2006. },
  2007. _setErrors: function(errors)
  2008. {
  2009. for (var name in errors)
  2010. {
  2011. var text = errors[name];
  2012. var $el = this.$element.find('[name=' + name + ']');
  2013. if ($el.length !== 0)
  2014. {
  2015. $el.addClass(this.params.errorClass);
  2016. this._setFieldEvent($el, name);
  2017. if (text !== '')
  2018. {
  2019. this._showErrorText(name, text);
  2020. }
  2021. }
  2022. }
  2023. },
  2024. _setFieldEvent: function($el, name)
  2025. {
  2026. var eventName = this._getFieldEventName($el);
  2027. $el.on(eventName + '.kube.validate', function()
  2028. {
  2029. this._clearError($el);
  2030. }.bind(this));
  2031. },
  2032. _showErrorText: function(name, text)
  2033. {
  2034. var $el = this.$element.find('#' + name + '-validation-error');
  2035. $el.addClass(this.params.errorClass);
  2036. $el.html(text);
  2037. $el.removeClass('is-hidden');
  2038. },
  2039. _getFieldEventName: function($el)
  2040. {
  2041. return ($el.get().tagName === 'SELECT' || $el.attr('type') === 'checkbox' || $el.attr('type') === 'radio') ? 'change' : 'keyup';
  2042. },
  2043. _clearError: function(node)
  2044. {
  2045. var $el = $K.dom(node);
  2046. var $errorEl = this.$element.find('#' + $el.attr('name') + '-validation-error');
  2047. $errorEl.removeClass(this.params.errorClass);
  2048. $errorEl.html('');
  2049. $errorEl.addClass('is-hidden');
  2050. $el.removeClass(this.params.errorClass).off('.kube.validate');
  2051. },
  2052. _saveCodeMirror: function()
  2053. {
  2054. $K.dom('.CodeMirror').each(function(node)
  2055. {
  2056. node.CodeMirror.save();
  2057. });
  2058. },
  2059. _disableDefaultValidation: function()
  2060. {
  2061. this.$element.attr('novalidate', 'novalidate');
  2062. },
  2063. _enableShortcut: function()
  2064. {
  2065. if (!this.params.shortcut) return;
  2066. // ctrl + s or cmd + s
  2067. this.$win.on('keydown.kube.validate', this._handleShortcut.bind(this));
  2068. },
  2069. _handleShortcut: function(e)
  2070. {
  2071. if (((e.ctrlKey || e.metaKey) && e.which === 83))
  2072. {
  2073. e.preventDefault();
  2074. return this._send();
  2075. }
  2076. return true;
  2077. },
  2078. _startTrigger: function()
  2079. {
  2080. this.$trigger = $(this.opts.trigger);
  2081. this.$element.on('submit', function() { return false; });
  2082. this.$trigger.off('.kube.validate');
  2083. this.$trigger.on('click.kube.validate', this._build.bind(this));
  2084. },
  2085. _startSubmit: function()
  2086. {
  2087. this.$element.on('submit.kube.validate', this._build.bind(this));
  2088. }
  2089. });
  2090. })(Kube);
  2091. (function($K)
  2092. {
  2093. $K.add('module', 'visibility', {
  2094. init: function(app, context)
  2095. {
  2096. this.app = app;
  2097. this.$win = app.$win;
  2098. // defaults
  2099. var defaults = {
  2100. tolerance: 15 // px
  2101. };
  2102. // context
  2103. this.context = context;
  2104. this.params = context.getParams(defaults);
  2105. this.$element = context.getElement();
  2106. },
  2107. // public
  2108. start: function()
  2109. {
  2110. this.$win.on('scroll.kube.visibility resize.kube.visibility', this._check.bind(this));
  2111. this._check();
  2112. },
  2113. stop: function()
  2114. {
  2115. this.$win.off('.kube.visibility');
  2116. },
  2117. // private
  2118. _check: function()
  2119. {
  2120. var docViewTop = this.$win.scrollTop();
  2121. var docViewBottom = docViewTop + this.$win.height();
  2122. var elemTop = this.$element.offset().top;
  2123. var elemBottom = elemTop + this.$element.height();
  2124. var check = ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) && (elemBottom <= (docViewBottom + this.params.tolerance)) && (elemTop >= docViewTop));
  2125. if (check)
  2126. {
  2127. this.app.broadcast('visibility.visible', this, this.$element);
  2128. }
  2129. else
  2130. {
  2131. this.app.broadcast('visibility.invisible', this, this.$element);
  2132. }
  2133. }
  2134. });
  2135. })(Kube);