flectra.define('web.AutoComplete', function (require) { "use strict"; var Widget = require('web.Widget'); return Widget.extend({ template: "SearchView.autocomplete", // Parameters for autocomplete constructor: // // parent: this is used to detect keyboard events // // options.source: function ({term:query}, callback). This function will be called to // obtain the search results corresponding to the query string. It is assumed that // options.source will call callback with the results. // options.select: function (ev, {item: {facet:facet}}). Autocomplete widget will call // that function when a selection is made by the user // options.get_search_string: function (). This function will be called by autocomplete // to obtain the current search string. init: function (parent, options) { this._super(parent); this.$input = parent.$el; this.source = options.source; this.select = options.select; this.get_search_string = options.get_search_string; this.current_result = null; this.searching = true; this.search_string = ''; this.current_search = null; }, start: function () { var self = this; this.$input.on('keyup', function (ev) { if (ev.which === $.ui.keyCode.RIGHT) { self.searching = true; ev.preventDefault(); return; } if (ev.which === $.ui.keyCode.ENTER) { if (self.search_string.length) { self.select_item(ev); } return; } var search_string = self.get_search_string(); if (self.search_string !== search_string) { if (search_string.length) { self.search_string = search_string; self.initiate_search(search_string); } else { self.close(); } } }); this.$input.on('keypress', function (ev) { self.search_string = self.search_string + String.fromCharCode(ev.which); if (self.search_string.length) { self.searching = true; var search_string = self.search_string; self.initiate_search(search_string); } else { self.close(); } }); this.$input.on('keydown', function (ev) { switch (ev.which) { case $.ui.keyCode.ENTER: // TAB and direction keys are handled at KeyDown because KeyUp // is not guaranteed to fire. // See e.g. https://github.com/aef-/jquery.masterblaster/issues/13 case $.ui.keyCode.TAB: if (self.search_string.length) { self.select_item(ev); } break; case $.ui.keyCode.DOWN: self.move('down'); self.searching = false; ev.preventDefault(); break; case $.ui.keyCode.UP: self.move('up'); self.searching = false; ev.preventDefault(); break; case $.ui.keyCode.RIGHT: self.searching = false; var current = self.current_result; if (current && current.expand && !current.expanded) { self.expand(); self.searching = true; } ev.preventDefault(); break; case $.ui.keyCode.ESCAPE: self.close(); self.searching = false; break; } }); }, initiate_search: function (query) { if (query === this.search_string && query !== this.current_search) { this.search(query); } }, search: function (query) { var self = this; this.current_search = query; this.source({term:query}, function (results) { if (results.length) { self.render_search_results(results); self.focus_element(self.$('li:first-child')); } else { self.close(); } }); }, render_search_results: function (results) { var self = this; var $list = this.$el; $list.empty(); results.forEach(function (result) { var $item = self.make_list_item(result).appendTo($list); result.$el = $item; }); this.show(); }, make_list_item: function (result) { var self = this; var $li = $('
  • ') .hover(function () {self.focus_element($li);}) .mousedown(function (ev) { if (ev.button === 0) { // left button self.select(ev, {item: {facet: result.facet}}); self.close(); } }) .data('result', result); if (result.expand) { var $expand = $('').appendTo($li); $expand.mousedown(function (ev) { ev.stopPropagation(); ev.preventDefault(); // to prevent dropdown from closing if (result.expanded) { self.fold(); } else { self.expand(); } }); $expand.click(function(ev) { ev.preventDefault(); // to prevent url from changing due to href="#" }); result.expanded = false; } if (result.indent) $li.addClass('o-indent'); $li.append($('').html(result.label)); return $li; }, expand: function () { var self = this; var current_result = this.current_result; current_result.expand(this.get_search_string()).then(function (results) { (results || [{label: '(no result)'}]).reverse().forEach(function (result) { result.indent = true; var $li = self.make_list_item(result); current_result.$el.after($li); }); self.current_result.expanded = true; self.current_result.$el.find('a.o-expand').removeClass('o-expand').addClass('o-expanded'); }); }, fold: function () { var $next = this.current_result.$el.next(); while ($next.hasClass('o-indent')) { $next.remove(); $next = this.current_result.$el.next(); } this.current_result.expanded = false; this.current_result.$el.find('a.o-expanded').removeClass('o-expanded').addClass('o-expand'); }, focus_element: function ($li) { this.$('li').removeClass('o-selection-focus'); $li.addClass('o-selection-focus'); this.current_result = $li.data('result'); }, select_item: function (ev) { if (this.current_result.facet) { this.select(ev, {item: {facet: this.current_result.facet}}); this.close(); } }, show: function () { this.$el.show(); }, close: function () { this.current_search = null; this.search_string = ''; this.searching = true; this.$el.hide(); }, move: function (direction) { var $next; if (direction === 'down') { $next = this.$('li.o-selection-focus').next(); if (!$next.length) $next = this.$('li').first(); } else { $next = this.$('li.o-selection-focus').prev(); if (!$next.length) $next = this.$('li').last(); } this.focus_element($next); }, is_expandable: function () { return !!this.$('.o-selection-focus .o-expand').length; }, }); });