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.

284 lines
7.0 KiB

  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);