(function($K) { $K.add('module', 'autocomplete', { init: function(app, context) { this.app = app; this.$doc = app.$doc; this.$win = app.$win; this.$body = app.$body; this.animate = app.animate; // defaults var defaults = { url: false, min: 2, labelClass: false, target: false, param: false }; // context this.context = context; this.params = context.getParams(defaults); this.$element = context.getElement(); this.$target = context.getTarget(); }, start: function() { this._build(); this.timeout = null; this.$element.on('keyup.kube.autocomplete', this._open.bind(this)); }, stop: function() { this.$box.remove(); this.$element.off('.kube.autocomplete'); this.$doc.off('.kube.autocomplete'); this.$win.off('.kube.autocomplete'); }, // private _build: function() { this.$box = $K.dom('<div />'); this.$box.addClass('autocomplete'); this.$box.addClass('is-hidden'); this.$body.append(this.$box); if (this.$target && !this._isInputTarget()) { this.$target.addClass('autocomplete-labels'); var $closes = this.$target.find('.close'); $closes.on('click', this._removeLabel.bind(this)); } }, _open: function(e) { if (e) e.preventDefault(); clearTimeout(this.timeout); var value = this.$element.val(); if (value.length >= this.params.min) { this._resize(); this.$win.on('resize.kube.autocomplete', this._resize.bind(this)); this.$doc.on('click.kube.autocomplete', this._close.bind(this)); this.$box.addClass('is-open'); this._listen(e); } else { this._close(e); } }, _close: function(e) { if (e) e.preventDefault(); this.$box.removeClass('is-open'); this.$box.addClass('is-hidden'); this.$doc.off('.kube.autocomplete'); this.$win.off('.kube.autocomplete'); }, _getPlacement: function(pos, height) { return ((this.$doc.height() - (pos.top + height)) < this.$box.height()) ? 'top' : 'bottom'; }, _resize: function() { this.$box.width(this.$element.width()); }, _getParamName: function() { return (this.params.param) ? this.params.param : this.$element.attr('name'); }, _getTargetName: function() { var name = this.$target.attr('data-name'); return (name) ? name : this.$target.attr('id'); }, _lookup: function() { var data = this._getParamName() + '=' + this.$element.val(); $K.ajax.post({ url: this.params.url, data: data, success: this._complete.bind(this) }); }, _complete: function(json) { this.$box.html(''); if (json.length === 0) return this._close(); for (var i = 0; i < json.length; i++) { var $item = $K.dom('<a>'); $item.attr('href', '#'); $item.attr('rel', json[i].id); $item.html(json[i].name); $item.on('click', this._set.bind(this)); this.$box.append($item); } var pos = this.$element.offset(); var height = this.$element.height(); var width = this.$element.width(); var placement = this._getPlacement(pos, height); var top = (placement === 'top') ? (pos.top - this.$box.height() - height) : (pos.top + height); this.$box.css({ width: width + 'px', top: top + 'px', left: pos.left + 'px' }); this.$box.removeClass('is-hidden'); }, _listen: function(e) { switch(e.which) { case 40: // down e.preventDefault(); this._select('next'); break; case 38: // up e.preventDefault(); this._select('prev'); break; case 13: // enter e.preventDefault(); this._set(); break; case 27: // esc this._close(e); break; default: this.timeout = setTimeout(this._lookup.bind(this), 300); break; } }, _select: function(type) { var $links = this.$box.find('a'); var $active = this.$box.find('.is-active'); $links.removeClass('is-active'); var $item = this._selectItem($active, $links, type); $item.addClass('is-active'); }, _selectItem: function($active, $links, type) { var $item; var isActive = ($active.length !== 0); var size = (type === 'next') ? 0 : ($links.length - 1); if (isActive) { $item = $active[type](); } if (!isActive || !$item || $item.length === 0) { $item = $links.eq(size); } return $item; }, _set: function(e) { var $active = this.$box.find('.is-active'); if (e) { e.preventDefault(); $active = $K.dom(e.target); } var id = $active.attr('rel'); var value = $active.html(); if (this.$target.length !== 0) { if (this._isInputTarget()) { this.$target.val(value); } else { var $added = this.$target.find('[data-id="' + id + '"]'); if ($added.length === 0) { this._addLabel(id, value); } } this.$element.val(''); } else { this.$element.val(value); } this.$element.focus(); this.app.broadcast('autocomplete.set', this, value); this._close(); }, _addLabel: function(id, name) { var $label = $K.dom('<span>'); $label.addClass('label'); $label.attr('data-id', id); $label.text(name + ' '); if (this.params.labelClass) { $label.addClass(this.params.labelClass); } var $close = $K.dom('<span>'); $close.addClass('close'); $close.on('click', this._removeLabel.bind(this)); var $input = $K.dom('<input>'); $input.attr('type', 'hidden'); $input.attr('name', this._getTargetName() + '[]'); $input.val(name); $label.append($close); $label.append($input); this.$target.append($label); }, _isInputTarget: function() { return (this.$target.get().tagName === 'INPUT'); }, _removeLabel: function(e) { e.preventDefault(); var $el = $K.dom(e.target); var $label = $el.closest('.label'); this.animate.run($label, 'fadeOut', function() { $label.remove(); }.bind(this)) } }); })(Kube);