[FIX] web,base: m2o empty name results
M2O use the autocomplete lib to display its content. The problem is
that if a result is empty (or only composed of whitespaces), a
divider is displayed in the dropdown.
Commit 1d9f7d279a was fixing this by only displaying non-empty
name results... but it did not work as many models implement the
name_search method the wrong way.
The new solution is to add a default display for record values which
are empty strings, at the right places in the webclient components.
This commit is contained in:
@@ -32,6 +32,7 @@ odoo.define('web.ControlPanel', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var ControlPanel = Widget.extend({
|
||||
@@ -190,8 +191,9 @@ var ControlPanel = Widget.extend({
|
||||
_render_breadcrumbs_li: function (bc, index, length) {
|
||||
var self = this;
|
||||
var is_last = (index === length-1);
|
||||
var li_content = _.escape(bc.title.trim()) || data.noDisplayContent;
|
||||
var $bc = $('<li>')
|
||||
.append(is_last ? _.escape(bc.title) : $('<a>').text(bc.title))
|
||||
.append(is_last ? li_content : $('<a>').html(li_content))
|
||||
.toggleClass('active', is_last);
|
||||
if (!is_last) {
|
||||
$bc.click(function () {
|
||||
|
||||
@@ -29,7 +29,7 @@ function serialize_sort(criterion) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse of the serialize_sort function: convert an array of SQL-like sort
|
||||
* Reverse of the serialize_sort function: convert an array of SQL-like sort
|
||||
* descriptors into a list of fields prefixed with '-' if necessary.
|
||||
*/
|
||||
function deserialize_sort(criterion) {
|
||||
@@ -205,7 +205,7 @@ var Query = Class.extend({
|
||||
* Creates a new query with the provided parameter lazy replacing the current
|
||||
* query's own.
|
||||
*
|
||||
* @param {Boolean} lazy indicates if the read_group should return only the
|
||||
* @param {Boolean} lazy indicates if the read_group should return only the
|
||||
* first level of groupby records, or should return the records grouped by
|
||||
* all levels at once (so, it makes only 1 db request).
|
||||
* @returns {openerp.web.Query}
|
||||
@@ -276,11 +276,11 @@ var QueryGroup = Class.extend({
|
||||
var group_size = fixed_group[count_key] || fixed_group.__count || 0;
|
||||
var leaf_group = fixed_group.__context.group_by.length === 0;
|
||||
|
||||
var value = (grouping_fields.length === 1)
|
||||
var value = (grouping_fields.length === 1)
|
||||
? fixed_group[grouping_fields[0]]
|
||||
: _.map(grouping_fields, function (field) { return fixed_group[field]; });
|
||||
var grouped_on = (grouping_fields.length === 1)
|
||||
? grouping_fields[0]
|
||||
var grouped_on = (grouping_fields.length === 1)
|
||||
? grouping_fields[0]
|
||||
: grouping_fields;
|
||||
this.attributes = {
|
||||
folded: !!(fixed_group.__fold),
|
||||
@@ -371,7 +371,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
|
||||
read_ids: function (ids, fields, options) {
|
||||
if (_.isEmpty(ids))
|
||||
return $.Deferred().resolve([]);
|
||||
|
||||
|
||||
options = options || {};
|
||||
var method = 'read';
|
||||
var ids_arg = ids;
|
||||
@@ -525,7 +525,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
|
||||
return this._model.call('name_get', [ids], {context: this.get_context()});
|
||||
},
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {String} name name to perform a search for/on
|
||||
* @param {Array} [domain=[]] filters for the objects returned, OpenERP domain
|
||||
* @param {String} [operator='ilike'] matching operator to use with the provided name value
|
||||
@@ -580,7 +580,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
|
||||
return undefined;
|
||||
},
|
||||
/**
|
||||
* Set the sort criteria on the dataset.
|
||||
* Set the sort criteria on the dataset.
|
||||
*
|
||||
* @param {Array} fields_list: list of fields order descriptors, as used by
|
||||
* Odoo's ORM (such as 'name desc', 'product_id', 'order_date asc')
|
||||
@@ -769,7 +769,7 @@ var BufferedDataSet = DataSetStatic.extend({
|
||||
cached.values = _.extend({'id': id}, cached.from_read, cached.changes, cached.readonly_fields);
|
||||
return cached;
|
||||
},
|
||||
create: function(data, options) {
|
||||
create: function(data, options) {
|
||||
var changes = _.extend({}, this.last_default_get, data);
|
||||
var cached = this._update_cache(_.uniqueId(this.virtual_id_prefix), _.extend({'changes': changes, 'to_create': true}, options));
|
||||
this.trigger("dataset_changed", data, options);
|
||||
@@ -863,7 +863,7 @@ var BufferedDataSet = DataSetStatic.extend({
|
||||
// sorting an array where all items are considered equal is a worst-case that
|
||||
// will randomize the array with an unstable sort! Therefore we must avoid
|
||||
// sorting if there are no sort_fields (i.e. all items are considered equal)
|
||||
// See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11
|
||||
// See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11
|
||||
// http://code.google.com/p/v8/issues/detail?id=90
|
||||
if (sort_fields.length) {
|
||||
records.sort(function (a, b) {
|
||||
@@ -1118,7 +1118,6 @@ function compute_domain (expr, fields) {
|
||||
return _.all(stack, _.identity);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
Query: Query,
|
||||
DataSet: DataSet,
|
||||
@@ -1129,6 +1128,7 @@ return {
|
||||
CompoundContext: CompoundContext,
|
||||
CompoundDomain: CompoundDomain,
|
||||
compute_domain: compute_domain,
|
||||
noDisplayContent: "<em class=\"text-warning\">" + _t("Unnamed") + "</em>",
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ var ReinitializeFieldMixin = _.extend({}, ReinitializeWidgetMixin, {
|
||||
|
||||
/**
|
||||
A mixin containing some useful methods to handle completion inputs.
|
||||
|
||||
|
||||
The widget containing this option can have these arguments in its widget options:
|
||||
- no_quick_create: if true, it will disable the quick create
|
||||
*/
|
||||
@@ -190,16 +190,15 @@ var CompletionFieldMixin = {
|
||||
exclusion_domain.push(['id', 'not in', ids_blacklist]);
|
||||
}
|
||||
|
||||
var empty_search = !search_val.trim();
|
||||
return this.orderer.add(dataset.name_search(
|
||||
empty_search ? ["", " ", " ", " "] : search_val, new data.CompoundDomain(self.build_domain(), exclusion_domain),
|
||||
empty_search ? "not in" : 'ilike', this.limit + 1, self.build_context())).then(function (data) {
|
||||
self.last_search = data;
|
||||
search_val, new data.CompoundDomain(self.build_domain(), exclusion_domain),
|
||||
'ilike', this.limit + 1, self.build_context())).then(function(_data) {
|
||||
self.last_search = _data;
|
||||
// possible selections for the m2o
|
||||
var values = _.map(data, function(x) {
|
||||
var values = _.map(_data, function(x) {
|
||||
x[1] = x[1].split("\n")[0];
|
||||
return {
|
||||
label: _.str.escapeHTML(x[1]),
|
||||
label: _.str.escapeHTML(x[1].trim()) || data.noDisplayContent,
|
||||
value: x[1],
|
||||
name: x[1],
|
||||
id: x[0],
|
||||
@@ -212,15 +211,15 @@ var CompletionFieldMixin = {
|
||||
values.push({
|
||||
label: _t("Search More..."),
|
||||
action: function() {
|
||||
dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(data) {
|
||||
self._search_create_popup("search", data);
|
||||
dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(_data) {
|
||||
self._search_create_popup("search", _data);
|
||||
});
|
||||
},
|
||||
classname: 'o_m2o_dropdown_option'
|
||||
});
|
||||
}
|
||||
// quick create
|
||||
var raw_result = _(data.result).map(function(x) {return x[1];});
|
||||
var raw_result = _(_data.result).map(function(x) {return x[1];});
|
||||
if (search_val.length > 0 && !_.include(raw_result, search_val) &&
|
||||
! (self.options && (self.options.no_create || self.options.no_quick_create))) {
|
||||
self.can_create && values.push({
|
||||
@@ -905,7 +904,7 @@ var FormViewDialog = ViewDialog.extend({
|
||||
var self = this;
|
||||
var _super = this._super.bind(this);
|
||||
this.init_dataset();
|
||||
|
||||
|
||||
if (this.res_id) {
|
||||
this.dataset.ids = [this.res_id];
|
||||
this.dataset.index = 0;
|
||||
@@ -972,7 +971,7 @@ var SelectCreateDialog = ViewDialog.extend({
|
||||
_.defaults(this.options, { initial_view: "search" });
|
||||
this.initial_ids = this.options.initial_ids;
|
||||
},
|
||||
|
||||
|
||||
open: function() {
|
||||
if(this.options.initial_view !== "search") {
|
||||
return this.create_edit_record();
|
||||
@@ -1101,7 +1100,7 @@ var DomainEditorDialog = SelectCreateDialog.extend({
|
||||
get_groupby: function () { return []; },
|
||||
get_domain: function () { return options.default_domain; },
|
||||
},
|
||||
values: [{label: _t("Selected domain"), value: null}],
|
||||
values: [{label: _t("Selected domain"), value: null}],
|
||||
}});
|
||||
|
||||
this._super(parent, options);
|
||||
|
||||
@@ -177,7 +177,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
|
||||
this.$input.change(input_changed);
|
||||
this.$input.on('click', function() {
|
||||
if (self.$input.autocomplete("widget").is(":visible")) {
|
||||
self.$input.autocomplete("close");
|
||||
self.$input.autocomplete("close");
|
||||
} else {
|
||||
if (self.get("value") && ! self.floating) {
|
||||
self.$input.autocomplete("search", "");
|
||||
@@ -308,7 +308,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
|
||||
render_value: function(no_recurse) {
|
||||
var self = this;
|
||||
if (!this.get("value")) {
|
||||
this.display_string("");
|
||||
this.display_string(null);
|
||||
return;
|
||||
}
|
||||
var display = this.display_value["" + this.get("value")];
|
||||
@@ -336,14 +336,15 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
|
||||
}
|
||||
}
|
||||
},
|
||||
display_string: function(str) {
|
||||
display_string: function (str) {
|
||||
var noValue = (str === null);
|
||||
if (!this.get("effective_readonly")) {
|
||||
this.$input.val(str.split("\n")[0]);
|
||||
this.$input.val(noValue ? "" : (str.split("\n")[0].trim() || $(data.noDisplayContent).text()));
|
||||
this.current_display = this.$input.val();
|
||||
this.$follow_button.toggle(!this.is_false());
|
||||
this.$el.toggleClass('o_with_button', !!this.$follow_button && this.$follow_button.length > 0 && !this.is_false());
|
||||
} else {
|
||||
this.$el.html(_.escape(str).split("\n").join("<br/>"));
|
||||
this.$el.html(noValue ? "" : (_.escape(str.trim()).split("\n").join("<br/>") || data.noDisplayContent));
|
||||
// Define callback to perform when clicking on the field
|
||||
if (!this.options.no_open) {
|
||||
// Remove potential previously added event handler
|
||||
@@ -627,7 +628,7 @@ var AbstractManyField = common.AbstractField.extend({
|
||||
add_ids = [],
|
||||
command_list = [],
|
||||
id, index, record;
|
||||
|
||||
|
||||
_.each(this.get('value'), function (id) {
|
||||
index = starting_ids.indexOf(id);
|
||||
if (index !== -1) {
|
||||
@@ -697,7 +698,7 @@ var FieldX2Many = AbstractManyField.extend({
|
||||
default_view: 'tree',
|
||||
init: function(field_manager, node) {
|
||||
this._super(field_manager, node);
|
||||
|
||||
|
||||
this.is_loaded = $.Deferred();
|
||||
this.initial_is_loaded = this.is_loaded;
|
||||
this.is_started = false;
|
||||
@@ -1422,19 +1423,15 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
|
||||
},
|
||||
render_value: function() {
|
||||
var self = this;
|
||||
var values = self.get("value");
|
||||
var handle_names = function(data) {
|
||||
if (self.isDestroyed())
|
||||
return;
|
||||
var indexed = {};
|
||||
_.each(data, function(el) {
|
||||
indexed[el['id']] = el;
|
||||
var values = this.get("value");
|
||||
var handle_names = function(_data) {
|
||||
_.each(_data, function(el) {
|
||||
el.display_name = el.display_name.trim() ? _.str.escapeHTML(el.display_name) : data.noDisplayContent;
|
||||
});
|
||||
data = _.map(values, function(el) { return indexed[el]; });
|
||||
self.render_tag(data);
|
||||
self.render_tag(_data);
|
||||
};
|
||||
if (!values || values.length > 0) {
|
||||
return self.get_render_data(values).done(handle_names);
|
||||
return this.alive(this.get_render_data(values)).done(handle_names);
|
||||
} else {
|
||||
handle_names([]);
|
||||
}
|
||||
@@ -1493,7 +1490,7 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
|
||||
var old_color = tag.data('color');
|
||||
tag.removeClass('o_tag_color_' + old_color);
|
||||
tag.data('color', color);
|
||||
tag.addClass('o_tag_color_' + color);
|
||||
tag.addClass('o_tag_color_' + color);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -750,7 +750,7 @@
|
||||
<t t-name="FieldMany2ManyTag">
|
||||
<t t-foreach="elements" t-as="el">
|
||||
<span t-attf-class="badge dropdown #{'o_tag_color_' + (el['color'] || 0)}" t-att-data-color="(el['color'] || 0)" t-att-data-index="el_index" t-att-data-id="el['id']">
|
||||
<span class="o_badge_text"><t t-esc="el['display_name']"/></span>
|
||||
<span class="o_badge_text"><t t-raw="el['display_name']"/></span>
|
||||
<span t-if="!readonly" class="fa fa-times o_delete"/>
|
||||
</span>
|
||||
</t>
|
||||
@@ -1120,7 +1120,7 @@
|
||||
<div class="o_export_format"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="o_export_panel">
|
||||
<div class="o_left_panel">
|
||||
<h4>Available fields</h4>
|
||||
|
||||
Reference in New Issue
Block a user