diff --git a/bower.json b/bower.json
index 3fb468d943..0b38156350 100644
--- a/bower.json
+++ b/bower.json
@@ -19,7 +19,6 @@
"bootstrap": "~3.3.4",
"moment": "~2.10.2",
"eonasdan-bootstrap-datetimepicker": "~4.7.14",
- "typeahead.js": "~0.11.1",
"gettext.js": "~0.5.0",
"d3": "~3.5.0",
"c3": "~0.4.10"
diff --git a/index.html b/index.html
index 958f7ab8c3..baad555f6a 100644
--- a/index.html
+++ b/index.html
@@ -13,14 +13,12 @@ this repository contains the full copyright notices and license terms. -->
-
-
diff --git a/src/common.js b/src/common.js
index 8e9af9600c..c949a93980 100644
--- a/src/common.js
+++ b/src/common.js
@@ -2935,74 +2935,174 @@
});
Sao.common.processing = new Sao.common.Processing();
- Sao.common.get_completion = function(entry, source, match_selected) {
- entry.attr('autocomplete', 'off');
- entry.typeahead({
- 'minLength': 1,
- 'highlight': true
- }, {
- 'limit': Sao.config.limit,
- 'name': 'suggestions',
- 'source': source,
- 'displayKey': 'rec_name',
- }, {
- 'name': 'actions',
- 'source': function(query, process) {
- var results = [];
- if (entry.data('search')) {
- results.push({
- 'id': 'search',
- 'action': Sao.i18n.gettext('Search...'),
- 'query': query
- });
- }
- if (entry.data('create')) {
- results.push({
- 'id': 'create',
- 'action': Sao.i18n.gettext('Create...'),
- 'query': query
- });
- }
- process(results);
- },
- 'displayKey': 'query',
- 'templates': {
- 'suggestion': function(context) {
- return '
' + context.action + '
';
- }
- }
- });
- entry.on('typeahead:select', match_selected);
- };
- Sao.common.update_completion = function(entry, record, field, model,
- domain) {
- var prm = jQuery.Deferred();
- var update = function(search_text, domain) {
- if (entry.typeahead('val') != search_text) {
- return prm.reject();
- }
- if (!search_text || !model) {
- return prm.reject();
- }
- if (domain === undefined) {
- domain = field.get_domain(record);
- }
- var context = field.get_context(record);
- domain = [['rec_name', 'ilike', '%' + search_text + '%'], domain];
+ Sao.common.InputCompletion = Sao.class_(Object, {
+ init: function(el, source, match_selected, format) {
+ el.wrap('');
+ this.dropdown = el.parent();
+ this.input = el.find('input').add(el.filter('input')).first();
+ // bootstrap requires an element with data-toggle
+ jQuery('', {
+ 'data-toggle': 'dropdown'
+ }).appendTo(this.dropdown);
+ this.menu = jQuery('', {
+ 'class': 'dropdown-menu',
+ 'role': 'listbox'
+ }).appendTo(this.dropdown);
+ this.separator = jQuery('', {
+ 'role': 'separator',
+ 'class': 'divider'
+ }).appendTo(this.menu);
+ this.separator.hide();
- var sao_model = new Sao.Model(model);
- var search_prm = sao_model.execute('search_read', [domain, 0,
- Sao.config.limit, null, ['rec_name']], context);
- search_prm.then(function(results) {
- if (entry.typeahead('val') != search_text) {
- return prm.reject();
+ this.source = source;
+ this.match_selected = match_selected;
+ this.format = format;
+ this.action_activated = null;
+
+ this._search_text = null;
+
+ this.input.on('input', function() {
+ window.setTimeout(this._input.bind(this), 300,
+ this.input.val());
+ }.bind(this));
+ this.input.keydown(function(evt) {
+ if (evt.which == Sao.common.DOWN_KEYCODE) {
+ this.menu.find('a').first().focus();
+ evt.preventDefault();
+ } else if (evt.which == Sao.common.ESC_KEYCODE) {
+ if (this.dropdown.hasClass('open')) {
+ this.menu.dropdown('toggle');
+ }
+ } else if (evt.which == Sao.common.RETURN_KEYCODE) {
+ if (!this.dropdown.hasClass('open')) {
+ this.menu.dropdown('toggle');
+ }
}
- prm.resolve(results);
- }, prm.reject);
+ }.bind(this));
+ this.menu.on('hide.bs.dropdown', function() {
+ this.input.focus();
+ }.bind(this));
+ },
+ set_actions: function(actions, action_activated) {
+ if (action_activated !== undefined) {
+ this.action_activated = action_activated;
+ }
+ this.menu.find('li.action').remove();
+ if (jQuery.isEmptyObject(actions)) {
+ this.separator.hide();
+ return;
+ }
+ this.separator.show();
+ actions.forEach(function(action) {
+ var action_id = action[0];
+ var content = action[1];
+ jQuery('', {
+ 'class': 'action action-' + action_id
+ }).append(jQuery('', {
+ 'href': '#'
+ }).append(this._format_action(content)))
+ .click(function() {
+ if (this.action_activated) {
+ this.action_activated(action_id);
+ }
+ this.input.val('');
+ }.bind(this))
+ .appendTo(this.menu);
+ }, this);
+ },
+ _format: function(content) {
+ if (this.format) {
+ return this.format(content);
+ }
+ return content;
+ },
+ _format_action: function(content) {
+ if (this.format_action) {
+ return this.format_action(content);
+ }
+ return content;
+ },
+ _input: function(text) {
+ if (text != this.input.val()) {
+ return;
+ }
+ var prm;
+ if (this.source instanceof Array) {
+ prm = jQuery.when(source.filter(function(value) {
+ return value.toLowerCase().startsWith(text.toLowerCase());
+ }));
+ } else {
+ prm = this.source(text);
+ }
+ prm.then(function(values) {
+ if (text != this.input.val()) {
+ return;
+ }
+ this._set_selection(values);
+ }.bind(this));
+ },
+ _set_selection: function(values) {
+ if (values === undefined) {
+ values = [];
+ }
+ this.menu.find('li.completion').remove();
+ values.reverse().forEach(function(value) {
+ jQuery('', {
+ 'class': 'completion'
+ }).append(jQuery('', {
+ 'href': '#'
+ }).append(this._format(value)))
+ .click(function() {
+ if (this.match_selected) {
+ this.match_selected(value);
+ }
+ this.input.focus();
+ }.bind(this)).prependTo(this.menu);
+ }, this);
+ this.menu.find('a').first().keyup(function(evt) {
+ if (evt.which == Sao.common.UP_KEYCODE) {
+ this.input.focus();
+ }
+ }.bind(this));
+ if (!this.input.val()) {
+ if (this.dropdown.hasClass('open')) {
+ this.menu.dropdown('toggle');
+ }
+ } else {
+ if (!this.dropdown.hasClass('open')) {
+ this.menu.dropdown('toggle');
+ }
+ }
+ }
+ });
+
+ Sao.common.get_completion = function(el, source,
+ match_selected, action_activated) {
+ var format = function(content) {
+ return content.rec_name;
};
+ var completion = new Sao.common.InputCompletion(
+ el, source, match_selected, format);
+ completion.set_actions([
+ ['search', Sao.i18n.gettext('Search...')],
+ ['create', Sao.i18n.gettext('Create..')]],
+ action_activated);
+ };
- var search_text = entry.typeahead('val');
- window.setTimeout(update, 300, search_text, domain);
- return prm.promise();
+ Sao.common.update_completion = function(
+ entry, record, field, model, domain) {
+ var search_text = entry.val();
+ if (!search_text || !model) {
+ return jQuery.when();
+ }
+ if (domain === undefined) {
+ domain = field.get_domain(record);
+ }
+ var context = field.get_context(record);
+ domain = [['rec_name', 'ilike', '%' + search_text + '%'], domain];
+
+ var sao_model = new Sao.Model(model);
+ return sao_model.execute('search_read',
+ [domain, 0, Sao.config.limit, null, ['rec_name']], context);
};
}());
diff --git a/src/sao.js b/src/sao.js
index 13f2456dff..59432cda38 100644
--- a/src/sao.js
+++ b/src/sao.js
@@ -347,53 +347,31 @@ var Sao = {};
'placeholder': Sao.i18n.gettext('Search...')
});
this.el.append(this.search_entry);
- this.search_entry.typeahead({
- 'highlight': false
- }, {
- 'limit': Sao.config.limit,
- 'source': this.update.bind(this),
- 'displayKey': 'record_name',
- 'templates': {
- 'suggestion': function(context) {
- var html = jQuery('');
- var img = jQuery('
', {
- 'class': 'global-search-icon'
- });
- var img_prm = Sao.common.ICONFACTORY.register_icon(
- context.icon);
- img_prm.then(function(icon_url) {
- img.attr('src', icon_url);
- }.bind(this));
- html.append(img);
- html.append(jQuery('', {
- 'class': 'global-search-text'
- }).text(context.record_name));
- return html;
- }
- }
- });
- this.search_entry.on('typeahead:select',
- this.match_selected.bind(this));
+ var completion = new Sao.common.InputCompletion(
+ this.search_entry,
+ this.update.bind(this),
+ this.match_selected.bind(this),
+ this.format.bind(this));
},
- query: function(value) {
- if (!arguments.length) {
- return this.search_entry.typeahead('val');
- } else {
- return this.search_entry.typeahead('val', value);
- }
+ format: function(content) {
+ var el = jQuery('');
+ var img = jQuery('
', {
+ 'class': 'global-search-icon'
+ }).appendTo(el);
+ Sao.common.ICONFACTORY.register_icon(content.icon).then(
+ function(icon_url) {
+ img.attr('src', icon_url);
+ });
+ jQuery('', {
+ 'class': 'global-search-text'
+ }).text(content.record_name).appendTo(el);
+ return el;
},
- update: function(query, sync, async) {
- if (query != this.query()) {
- return;
- }
+ update: function(text) {
var ir_model = new Sao.Model('ir.model');
- var search_prm = ir_model.execute('global_search',
- [query, Sao.config.limit, Sao.main_menu_screen.model_name],
- Sao.main_menu_screen.context);
- search_prm.then(function(s_results) {
- if (query != this.query()) {
- return;
- }
+ return ir_model.execute('global_search',
+ [text, Sao.config.limit, Sao.main_menu_screen.model_name],
+ Sao.main_menu_screen.context).then(function(s_results) {
var results = [];
for (var i=0, len=s_results.length; i < len; i++) {
results.push({
@@ -404,10 +382,10 @@ var Sao = {};
'icon': s_results[i][5],
});
}
- async(results);
+ return results;
}.bind(this));
},
- match_selected: function(event_, item, name) {
+ match_selected: function(item) {
if (item.model == Sao.main_menu_screen.model_name) {
Sao.Action.exec_keyword('tree_open', {
'model': item.model,
@@ -422,7 +400,7 @@ var Sao = {};
};
Sao.Tab.create(params);
}
- this.query('');
+ this.search_entry.val('');
}
});
diff --git a/src/sao.less b/src/sao.less
index e2c70c6c0d..4f1a3ee439 100644
--- a/src/sao.less
+++ b/src/sao.less
@@ -246,3 +246,8 @@ input[type='color'] {
width: 100%;
z-index: 1000;
}
+
+img.global-search-icon {
+ width: 2em;
+ height: 2em;
+}
diff --git a/src/screen.js b/src/screen.js
index e83d4738eb..a2c92cd86a 100644
--- a/src/screen.js
+++ b/src/screen.js
@@ -28,7 +28,12 @@
'class': 'form-control',
'placeholder': Sao.i18n.gettext('Search')
});
+ this.search_list = jQuery('');
+ this.search_list.uniqueId();
+ this.search_entry.attr('list', this.search_list.attr('id'));
this.search_entry.keypress(this.key_press.bind(this));
+ this.search_entry.on('input', this.update.bind(this));
+
this.but_bookmark = jQuery('', {
type: 'button',
'class': 'btn btn-default dropdown-toggle',
@@ -78,6 +83,7 @@
'class': 'input-group-btn'
}).append(this.filter_button))
.append(this.search_entry)
+ .append(this.search_list)
.append(jQuery('', {
'class': 'input-group-btn'
}).append(this.but_star)
@@ -87,12 +93,6 @@
'class': 'col-md-8'
}).appendTo(this.filter_box));
- this.search_entry.attr('autocomplete', 'off');
- this.search_entry.typeahead({
- 'highlight': false
- }, {
- 'source': this.update.bind(this),
- });
this.but_prev = jQuery('', {
type: 'button',
@@ -166,15 +166,15 @@
this.search_entry.val(value);
this.bookmark_match();
},
- update: function(query, process) {
- var completions = this.screen.domain_parser().completion(query);
- var suggestions = [];
+ update: function() {
+ var completions = this.screen.domain_parser().completion(
+ this.get_text());
+ this.search_list.children().remove();
completions.forEach(function(e) {
- suggestions.push({
+ jQuery('', {
'value': e.trim()
- });
- });
- process(suggestions);
+ }).appendTo(this.search_list);
+ }, this);
},
set_star: function(star) {
var glyphicon = this.but_star.children('span.glyphicon');
@@ -257,18 +257,11 @@
}
this.set_star(false);
},
- search_val: function(value) {
- if (!arguments.length) {
- return this.search_entry.typeahead('val');
- } else {
- return this.search_entry.typeahead('val', value);
- }
- },
search_prev: function() {
- this.screen.search_prev(this.search_val());
+ this.screen.search_prev(this.get_text());
},
search_next: function() {
- this.screen.search_next(this.search_val());
+ this.screen.search_next(this.get_text());
},
get_tab_domain: function() {
if (!this.tab) {
@@ -278,7 +271,7 @@
return this.tab_domain[i][1];
},
do_search: function() {
- return this.screen.search_filter(this.search_val());
+ return this.screen.search_filter(this.get_text());
},
key_press: function(e) {
if (e.which == Sao.common.RETURN_KEYCODE) {
@@ -319,7 +312,7 @@
}
},
get_text: function() {
- return this.search_val();
+ return this.search_entry.val();
},
search_box: function() {
var domain_parser = this.screen.domain_parser();
diff --git a/src/typeahead.less b/src/typeahead.less
deleted file mode 100644
index f6b84a3408..0000000000
--- a/src/typeahead.less
+++ /dev/null
@@ -1,82 +0,0 @@
-@import "variables";
-
-.twitter-typeahead {
- width: 100%;
- .input-group &{
- //overwrite `display:inline-block` style
- display: table-cell!important;
- float: left;
- }
-
- & .tt-hint {
- background-color: transparent !important;
- }
-
- & img.global-search-icon {
- width: 2em;
- height: 2em;
- float: left;
- }
-
- & p.global-search-text {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
-}
-
-.tt-dataset-actions {
- font-style: italic;
- font-variant: small-caps;
- border-top: 1px #ddd solid;
-}
-
-.tt-menu {
- //dropdown menu
- position: absolute;
- top: 100%;
- left: 0;
- z-index: @zindex-dropdown;
- min-width: 160px;
- width: 100%;
- padding: 5px 0;
- margin: 0;
- list-style: none;
- font-size: @font-size-base;
- background-color: @dropdown-bg;
- border: 1px solid @dropdown-fallback-border;
- border: 1px solid @dropdown-border;
- border-bottom-right-radius: @border-radius-base;
- border-bottom-left-radius: @border-radius-base;
- box-shadow: 0 6px 12px rgba(0,0,0,.175);
- background-clip: padding-box;
- *border-right-width: 2px;
- *border-bottom-width: 2px;
-
- .tt-suggestion {
- //item
- display: block;
- padding: 3px 20px;
- clear: both;
- font-weight: normal;
- line-height: @line-height-base;
- color: @dropdown-link-color;
- white-space: nowrap;
-
- &.tt-cursor {
- //item selected
- text-decoration: none;
- outline: 0;
- background-color: @dropdown-link-hover-bg;
- color: @dropdown-link-hover-color;
- a {
- //link in item selected
- color: @dropdown-link-hover-color;
- }
- }
- p {
- margin: 0;
- }
- }
-}
-
diff --git a/src/view.js b/src/view.js
index ed11e3ab3c..7bc7c73d83 100644
--- a/src/view.js
+++ b/src/view.js
@@ -2759,11 +2759,13 @@
})).appendTo(buttons);
this.but_open.click(this.edit.bind(this));
if (!attributes.completion || attributes.completion == "1") {
- Sao.common.get_completion(this.entry,
- this._update_completion.bind(this),
- this._completion_match_selected.bind(this));
+ Sao.common.get_completion(group,
+ this._update_completion.bind(this),
+ this._completion_match_selected.bind(this),
+ this._completion_action_activated.bind(this));
this.wid_completion = true;
}
+ this.el.change(this.focus_out.bind(this));
this._readonly = false;
},
get_screen: function() {
@@ -2782,27 +2784,7 @@
if (jQuery.isEmptyObject(value)) {
value = '';
}
- this.value(value);
- },
- // This function is required because once typeahead is activated on an
- // entry then .val() function should not be used anymore.
- // In order to keep the same semantics as .val() we use the same
- // signature
- value: function(value) {
- if (this.wid_completion) {
- if (!arguments.length) {
- return this.entry.typeahead('val');
- } else {
- return this.entry.typeahead('val', value);
- }
- } else {
- if (!arguments.length) {
- return this.entry.val();
- } else {
- return this.entry.val(value);
- }
- }
-
+ this.entry.val(value);
},
get_text: function() {
var record = this.record();
@@ -2811,43 +2793,19 @@
}
return '';
},
- _set_completion: function() {
- this.entry.data('search', this.read_access());
- this.entry.data('create', this.create_access());
- },
- _update_completion: function(query, sync, async) {
- var record = this.record();
- if (!record) {
- return;
- }
- var field = this.field();
- var value = field.get(record);
- if (this.has_target(value)) {
- var id = this.id_from_value(value);
- if ((id !== undefined) && (id > 0)) {
+ focus_out: function() {
+ if (!this.attributes.completion ||
+ this.attributes.completion == "1") {
+ if (this.el.find('.dropdown').hasClass('open')) {
return;
}
}
- var model = this.get_model();
-
- var prm = Sao.common.update_completion(this.entry, record, field,
- model);
- prm.done(async);
+ Sao.View.Form.Many2One._super.focus_out.call(this);
},
- _completion_match_selected: function(event_, selected_item, name) {
- if (selected_item.id == 'search') {
- // it's necessary to set the value so that edit() can get it
- // and make the search on the user provided content
- this.value(selected_item.query);
- this.edit();
- this.value('');
- } else if (selected_item.id == 'create') {
- this.new_();
- this.value('');
- } else {
- this.record().field_set_client(this.field_name,
- this.value_from_id(selected_item.id,
- selected_item.rec_name), true);
+ set_value: function(record, field) {
+ if (field.get_client(record) != this.entry.val()) {
+ field.set_client(record, this.value_from_id(null, ''));
+ this.entry.val('');
}
},
display: function(record, field) {
@@ -2863,7 +2821,7 @@
this._set_completion();
if (!record) {
- this.value('');
+ this.entry.val('');
return;
}
this.set_text(field.get_client(record));
@@ -3018,9 +2976,9 @@
} else if (this.has_target(this.record().field_get(
this.field_name)) && editable) {
var value = this.get_text();
- if ((value != this.value()) ||
+ if ((value != this.entry.val()) ||
~delete_keys.indexOf(event_.which)) {
- this.value('');
+ this.entry.val('');
this.record().field_set_client(this.field_name,
this.value_from_id(null, ''));
}
@@ -3036,7 +2994,7 @@
var sao_model = new Sao.Model(model);
if (model && !this.has_target(value)) {
- var text = this.value();
+ var text = this.entry.value();
if (!this._readonly && (text ||
this.field().get_state_attrs(this.record())
.required)) {
@@ -3069,6 +3027,50 @@
});
}
}
+ },
+ _set_completion: function() {
+ var search = this.el.find('.action-search');
+ if (this.read_access()) {
+ search.removeClass('disabled');
+ } else {
+ search.addClass('disabled');
+ }
+ var create = this.el.find('.action-create');
+ if (this.create_access()) {
+ create.removeClass('disabled');
+ } else {
+ create.addClass('disabled');
+ }
+ },
+ _update_completion: function(text) {
+ var record = this.record();
+ if (!record) {
+ return;
+ }
+ var field = this.field();
+ var value = field.get(record);
+ if (this.has_target(value)) {
+ var id = this.id_from_value(value);
+ if ((id !== undefined) && (id > 0)) {
+ return jQuery.when();
+ }
+ }
+ var model = this.get_model();
+
+ return Sao.common.update_completion(
+ this.entry, record, field, model);
+ },
+ _completion_match_selected: function(value) {
+ this.record().field_set_client(this.field_name,
+ this.value_from_id(
+ value.id, value.rec_name), true);
+ },
+ _completion_action_activated: function(action) {
+ if (action == 'search') {
+ this.edit();
+ } else if (action == 'create') {
+ this.new_();
+ }
}
});