diff options
| author | Christophe Matthieu <chm@openerp.com> | 2012-11-08 12:14:17 +0100 |
|---|---|---|
| committer | Christophe Matthieu <chm@openerp.com> | 2012-11-08 12:14:17 +0100 |
| commit | b865a94c6b7329701645790134bb4d2d40c2fcfa (patch) | |
| tree | a5d728247a345e37714f9abfbf91bd2ab7043296 /addons/web/static/src | |
| parent | d59417c879c60c6c124e1a9c638718d22e66880b (diff) | |
| parent | 7c3d2a37b50adefca82829fb2705aad0499e8257 (diff) | |
[MERGE] from trunk
bzr revid: chm@openerp.com-20121108111417-ds9eyxd32tihpuxs
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/base.css | 10 | ||||
| -rw-r--r-- | addons/web/static/src/css/base.sass | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 214 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 378 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 15 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 13 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_tree.js | 105 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 23 |
8 files changed, 450 insertions, 322 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index b030d0712f0..94d02758ad0 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -659,7 +659,7 @@ cursor: pointer; } .openerp .oe_dropdown_toggle { - color: rgba(0, 0, 0, 0.5); + color: rgba(0, 0, 0, 0.3); font-weight: normal; } .openerp .oe_dropdown_hover:hover .oe_dropdown_menu, .openerp .oe_dropdown_menu.oe_opened { @@ -2565,7 +2565,7 @@ background-color: #eeeeee; } .openerp .oe_list_editable .oe_list_content td.oe_list_field_cell { - padding: 4px 6px 3px 6px; + padding: 4px 6px 3px; } .openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) { color: transparent; @@ -2646,6 +2646,9 @@ margin: 0 !important; padding: 0; } +.openerp .oe_list .oe_form .oe_form_field_boolean { + padding: 1px 6px 3px; +} .openerp .oe_list .oe_list_content .oe_group_header { background-color: #fcfcfc; background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); @@ -2777,6 +2780,9 @@ content: "}"; color: #e0e0e0; } +.openerp .oe_list_content .oe_list_field_progressbar progress { + width: 100%; +} .openerp .tree_header { background-color: #f0f0f0; border-bottom: 1px solid #cacaca; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 79a688cf823..f911929e159 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -569,7 +569,7 @@ $sheet-padding: 16px position: relative cursor: pointer .oe_dropdown_toggle - color: rgba(0,0,0,0.5) + color: rgba(0,0,0,0.3) font-weight: normal .oe_dropdown_hover:hover .oe_dropdown_menu, .oe_dropdown_menu.oe_opened display: block @@ -2030,10 +2030,8 @@ $sheet-padding: 16px background-color: #eee $row-height: 27px - .oe_list_editable - .oe_list_content - td.oe_list_field_cell - padding: 4px 6px 3px 6px + .oe_list_editable .oe_list_content td.oe_list_field_cell + padding: 4px 6px 3px .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) * @@ -2106,6 +2104,10 @@ $sheet-padding: 16px position: absolute margin: 0 !important // dammit padding: 0 + .oe_form_field_boolean + // use padding similar to actual cell to correctly position the + // checkbox + padding: 1px 6px 3px .oe_list_content .oe_group_header @include vertical-gradient(#fcfcfc, #dedede) @@ -2193,6 +2195,8 @@ $sheet-padding: 16px .oe_list_handle @include text-to-entypo-icon("}",#E0E0E0,18px) margin-right: 7px + .oe_list_field_progressbar progress + width: 100% // }}} // Tree view {{{ .tree_header diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 520dcd9169f..d005c79e9b2 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -124,19 +124,10 @@ function assert(condition, message) { } my.InputView = instance.web.Widget.extend({ template: 'SearchView.InputView', - start: function () { - var p = this._super.apply(this, arguments); - this.$el.on('focus', this.proxy('onFocus')); - this.$el.on('blur', this.proxy('onBlur')); - this.$el.on('keydown', this.proxy('onKeydown')); - return p; - }, - onFocus: function () { - this.trigger('focused', this); - }, - onBlur: function () { - this.$el.text(''); - this.trigger('blurred', this); + events: { + focus: function () { this.trigger('focused', this); }, + blur: function () { this.$el.text(''); this.trigger('blurred', this); }, + keydown: 'onKeydown' }, getSelection: function () { // get Text node @@ -212,6 +203,27 @@ my.InputView = instance.web.Widget.extend({ }); my.FacetView = instance.web.Widget.extend({ template: 'SearchView.FacetView', + events: { + 'focus': function () { this.trigger('focused', this); }, + 'blur': function () { this.trigger('blurred', this); }, + 'click': function (e) { + if ($(e.target).is('.oe_facet_remove')) { + this.model.destroy(); + return false; + } + this.$el.focus(); + e.stopPropagation(); + }, + 'keydown': function (e) { + var keys = $.ui.keyCode; + switch (e.which) { + case keys.BACKSPACE: + case keys.DELETE: + this.model.destroy(); + return false; + } + } + }, init: function (parent, model) { this._super(parent); this.model = model; @@ -223,33 +235,11 @@ my.FacetView = instance.web.Widget.extend({ }, start: function () { var self = this; - this.$el.on('focus', function () { self.trigger('focused', self); }); - this.$el.on('blur', function () { self.trigger('blurred', self); }); - this.$el.on('click', function (e) { - if ($(e.target).is('.oe_facet_remove')) { - self.model.destroy(); - return false; - } - self.$el.focus(); - e.stopPropagation(); - }); - this.$el.on('keydown', function (e) { - var keys = $.ui.keyCode; - switch (e.which) { - case keys.BACKSPACE: - case keys.DELETE: - self.model.destroy(); - return false; - } - }); - var $e = self.$el.find('> span:last-child'); - var q = $.when(this._super()); - return q.pipe(function () { - var values = self.model.values.map(function (value) { + var $e = this.$('> span:last-child'); + return $.when(this._super()).pipe(function () { + return $.when.apply(null, self.model.values.map(function (value) { return new my.FacetValueView(self, value).appendTo($e); - }); - - return $.when.apply(null, values); + })); }); }, model_changed: function () { @@ -274,6 +264,48 @@ my.FacetValueView = instance.web.Widget.extend({ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.SearchView# */{ template: "SearchView", + events: { + // focus last input if view itself is clicked + 'click': function (e) { + if (e.target === this.$('.oe_searchview_facets')[0]) { + this.$('.oe_searchview_input:last').focus(); + } + }, + // when the completion list opens/refreshes, automatically select the + // first completion item so if the user just hits [RETURN] or [TAB] it + // automatically selects it + 'autocompleteopen': function () { + var menu = this.$el.data('autocomplete').menu; + menu.activate( + $.Event({ type: "mouseenter" }), + menu.element.children().first()); + }, + // search button + 'click button.oe_searchview_search': function (e) { + e.stopImmediatePropagation(); + this.do_search(); + }, + 'click .oe_searchview_clear': function (e) { + e.stopImmediatePropagation(); + this.query.reset(); + }, + 'click .oe_searchview_unfold_drawer': function (e) { + e.stopImmediatePropagation(); + this.$el.toggleClass('oe_searchview_open_drawer'); + }, + 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { + switch(e.which) { + case $.ui.keyCode.LEFT: + this.focusPreceding(this); + e.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.focusFollowing(this); + e.preventDefault(); + break; + } + } + }, /** * @constructs instance.web.SearchView * @extends instance.web.Widget @@ -331,55 +363,11 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); } - // Launch a search on clicking the oe_searchview_search button - this.$el.on('click', 'button.oe_searchview_search', function (e) { - e.stopImmediatePropagation(); - self.do_search(); - }); - - this.$el.on('keydown', - '.oe_searchview_input, .oe_searchview_facet', function (e) { - switch(e.which) { - case $.ui.keyCode.LEFT: - self.focusPreceding(this); - e.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - self.focusFollowing(this); - e.preventDefault(); - break; - } - }); - - this.$el.on('click', '.oe_searchview_clear', function (e) { - e.stopImmediatePropagation(); - self.query.reset(); - }); - this.$el.on('click', '.oe_searchview_unfold_drawer', function (e) { - e.stopImmediatePropagation(); - self.$el.toggleClass('oe_searchview_open_drawer'); - }); instance.web.bus.on('click', this, function(ev) { if ($(ev.target).parents('.oe_searchview').length === 0) { self.$el.removeClass('oe_searchview_open_drawer'); } }); - // Focus last input if the view itself is clicked (empty section of - // facets element) - this.$el.on('click', function (e) { - if (e.target === self.$el.find('.oe_searchview_facets')[0]) { - self.$el.find('.oe_searchview_input:last').focus(); - } - }); - // when the completion list opens/refreshes, automatically select the - // first completion item so if the user just hits [RETURN] or [TAB] it - // automatically selects it - this.$el.on('autocompleteopen', function () { - var menu = self.$el.data('autocomplete').menu; - menu.activate( - $.Event({ type: "mouseenter" }), - menu.element.children().first()); - }); return $.when(p, this.ready); }, @@ -480,7 +468,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * div[contenteditable].oe_searchview_input) */ currentInputValue: function () { - return this.$el.find('div.oe_searchview_input:focus').text(); + return this.$('div.oe_searchview_input:focus').text(); }, /** * Provide auto-completion result for req.term (an array to `resp`) @@ -510,7 +498,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea var input_index = _(this.input_subviews).indexOf( this.subviewForRoot( - this.$el.find('div.oe_searchview_input:focus')[0])); + this.$('div.oe_searchview_input:focus')[0])); this.query.add(ui.item.facet, {at: input_index / 2}); }, childFocused: function () { @@ -539,7 +527,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // _2: undefined if event=change, otherwise model var self = this; var started = []; - var $e = this.$el.find('div.oe_searchview_facets'); + var $e = this.$('div.oe_searchview_facets'); _.invoke(this.input_subviews, 'destroy'); this.input_subviews = []; @@ -664,7 +652,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // build drawer var drawer_started = $.when.apply( null, _(this.select_for_drawer()).invoke( - 'appendTo', this.$el.find('.oe_searchview_drawer'))); + 'appendTo', this.$('.oe_searchview_drawer'))); // load defaults var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( @@ -736,6 +724,9 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * * If at least one field failed its validation, triggers * :js:func:`instance.web.SearchView.on_invalid` instead. + * + * @param [_query] + * @param {Object} [options] */ do_search: function (_query, options) { if (options && options.preventSearch) { @@ -961,7 +952,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in */ search_change: function () { var self = this; - var $filters = this.$el.find('> li').removeClass('oe_selected'); + var $filters = this.$('> li').removeClass('oe_selected'); var facet = this.view.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { @@ -1512,7 +1503,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }).pipe(this.proxy('set_filters')); }, clear_selection: function () { - this.$el.find('li.oe_selected').removeClass('oe_selected'); + this.$('li.oe_selected').removeClass('oe_selected'); }, append_filter: function (filter) { var self = this; @@ -1524,7 +1515,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } else { var id = filter.id; $filter = this.filters[key] = $('<li></li>') - .appendTo(this.$el.find('.oe_searchview_custom_list')) + .appendTo(this.$('.oe_searchview_custom_list')) .addClass(filter.user_id ? 'oe_searchview_custom_private' : 'oe_searchview_custom_public') .text(filter.name); @@ -1559,8 +1550,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }, save_current: function () { var self = this; - var $name = this.$el.find('input:first'); - var private_filter = !this.$el.find('input:last').prop('checked'); + var $name = this.$('input:first'); + var private_filter = !this.$('input:last').prop('checked'); var search = this.view.build_search_data(); this.rpc('/web/session/eval_domain_and_context', { @@ -1665,11 +1656,20 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ }); }, append_proposition: function () { + var self = this; return (new instance.web.search.ExtendedSearchProposition(this, this.fields)) - .appendTo(this.$el.find('ul')); + .appendTo(this.$('ul')).then(function () { + self.$('button.oe_apply').prop('disabled', false); + }); + }, + remove_proposition: function (prop) { + // removing last proposition, disable apply button + if (this.getChildren().length <= 1) { + this.$('button.oe_apply').prop('disabled', true); + } + prop.destroy(); }, commit_search: function () { - var self = this; // Get domain sections from all propositions var children = this.getChildren(); var propositions = _.invoke(children, 'get_proposition'); @@ -1699,6 +1699,13 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ template: 'SearchView.extended_search.proposition', + events: { + 'change .searchview_extended_prop_field': 'changed', + 'click .searchview_extended_delete_prop': function (e) { + e.stopPropagation(); + this.getParent().remove_proposition(this); + } + }, /** * @constructs instance.web.search.ExtendedSearchProposition * @extends instance.web.Widget @@ -1716,17 +1723,10 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ this.value = null; }, start: function () { - var _this = this; - this.$el.find(".searchview_extended_prop_field").change(function() { - _this.changed(); - }); - this.$el.find('.searchview_extended_delete_prop').click(function () { - _this.destroy(); - }); - this.changed(); + return this._super().then(this.proxy('changed')); }, changed: function() { - var nval = this.$el.find(".searchview_extended_prop_field").val(); + var nval = this.$(".searchview_extended_prop_field").val(); if(this.attrs.selected == null || nval != this.attrs.selected.name) { this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); } @@ -1741,7 +1741,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ if(this.attrs.selected != null) { this.value.destroy(); this.value = null; - this.$el.find('.searchview_extended_prop_op').html(''); + this.$('.searchview_extended_prop_op').html(''); } this.attrs.selected = field; if(field == null) { @@ -1757,9 +1757,9 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ _.each(this.value.operators, function(operator) { $('<option>', {value: operator.value}) .text(String(operator.text)) - .appendTo(self.$el.find('.searchview_extended_prop_op')); + .appendTo(self.$('.searchview_extended_prop_op')); }); - var $value_loc = this.$el.find('.searchview_extended_prop_value').empty(); + var $value_loc = this.$('.searchview_extended_prop_value').empty(); this.value.appendTo($value_loc); }, @@ -1767,7 +1767,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ if ( this.attrs.selected == null) return null; var field = this.attrs.selected; - var op = this.$el.find('.searchview_extended_prop_op')[0]; + var op = this.$('.searchview_extended_prop_op')[0]; var operator = op.options[op.selectedIndex]; return { label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), { diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 9dd6e34c161..71c2b368fc1 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -114,7 +114,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }); this.is_initialized = $.Deferred(); this.mutating_mutex = new $.Mutex(); - this.on_change_mutex = new $.Mutex(); + this.on_change_list = []; + this.save_list = []; this.reload_mutex = new $.Mutex(); this.__clicked_inside = false; this.__blur_timeout = null; @@ -466,7 +467,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var splitted = field.split('.'); if (splitted.length > 1 && _.str.trim(splitted[0]) === "parent" && self.dataset.parent_view) { if (parent_fields === null) { - parent_fields = self.dataset.parent_view.get_fields_values([self.dataset.child_name]); + parent_fields = self.dataset.parent_view.get_fields_values(); } var p_val = parent_fields[_.str.trim(splitted[1])]; if (p_val !== undefined) { @@ -492,67 +493,72 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }, do_onchange: function(widget, processed) { var self = this; - return this.on_change_mutex.exec(function() { - try { - var def; - processed = processed || []; - processed.push(widget.name); - var on_change = widget.node.attrs.on_change; - if (on_change) { - var change_spec = self.parse_on_change(on_change, widget); - def = self.rpc('/web/dataset/onchange', { - model: self.dataset.model, - method: change_spec.method, - args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(change_spec.args), - context_id: change_spec.context_index == undefined ? null : change_spec.context_index + 1 - }); - } else { - def = $.when({}); - } - return def.pipe(function(response) { - if (widget.field['change_default']) { - var fieldname = widget.name - var value_; - if (response.value && (fieldname in response.value)) { - // Use value from onchange if onchange executed - value_ = response.value[fieldname]; - } else { - // otherwise get form value for field - value_ = self.fields[fieldname].get_value(); - } - var condition = fieldname + '=' + value_; - - if (value_) { - return self.rpc('/web/dataset/call', { - model: 'ir.values', - method: 'get_defaults', - args: [self.model, condition] - }).pipe(function (results) { - if (!results.length) { - return response; - } - if (!response.value) { - response.value = {}; - } - for(var i=0; i<results.length; ++i) { - // [whatever, key, value] - var triplet = results[i]; - response.value[triplet[1]] = triplet[2]; - } - return response; - }); - } - } - return response; - }).pipe(function(response) { - return self.on_processed_onchange(response, processed); + this.on_change_list = [{widget: widget, processed: processed}].concat(this.on_change_list); + return this._process_operations(); + }, + _process_onchange: function(on_change_obj) { + var self = this; + var widget = on_change_obj.widget; + var processed = on_change_obj.processed; + try { + var def; + processed = processed || []; + processed.push(widget.name); + var on_change = widget.node.attrs.on_change; + if (on_change) { + var change_spec = self.parse_on_change(on_change, widget); + def = self.rpc('/web/dataset/onchange', { + model: self.dataset.model, + method: change_spec.method, + args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(change_spec.args), + context_id: change_spec.context_index == undefined ? null : change_spec.context_index + 1 }); - } catch(e) { - console.error(e); - instance.webclient.crashmanager.show_message(e); - return $.Deferred().reject(); + } else { + def = $.when({}); } - }); + return def.pipe(function(response) { + if (widget.field['change_default']) { + var fieldname = widget.name + var value_; + if (response.value && (fieldname in response.value)) { + // Use value from onchange if onchange executed + value_ = response.value[fieldname]; + } else { + // otherwise get form value for field + value_ = self.fields[fieldname].get_value(); + } + var condition = fieldname + '=' + value_; + + if (value_) { + return self.rpc('/web/dataset/call', { + model: 'ir.values', + method: 'get_defaults', + args: [self.model, condition] + }).pipe(function (results) { + if (!results.length) { + return response; + } + if (!response.value) { + response.value = {}; + } + for(var i=0; i<results.length; ++i) { + // [whatever, key, value] + var triplet = results[i]; + response.value[triplet[1]] = triplet[2]; + } + return response; + }); + } + } + return response; + }).pipe(function(response) { + return self.on_processed_onchange(response, processed); + }); + } catch(e) { + console.error(e); + instance.webclient.crashmanager.show_message(e); + return $.Deferred().reject(); + } }, on_processed_onchange: function(result, processed) { try { @@ -588,6 +594,38 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM return $.Deferred().reject(); } }, + _process_operations: function() { + var self = this; + return this.mutating_mutex.exec(function() { + function iterate() { + var on_change_obj = self.on_change_list.shift(); + if (on_change_obj) { + return self._process_onchange(on_change_obj).pipe(function() { + return iterate(); + }); + } + var defs = []; + _.each(self.fields, function(field) { + defs.push(field.commit_value()); + }); + var args = _.toArray(arguments); + return $.when.apply($, defs).pipe(function() { + if (self.on_change_list.length !== 0) { + return iterate(); + } + var save_obj = self.save_list.pop(); + if (save_obj) { + return self._process_save(save_obj).pipe(function() { + save_obj.ret = _.toArray(arguments); + return iterate(); + }); + } + return $.when(); + }); + }; + return iterate(); + }); + }, _internal_set_values: function(values, exclude) { exclude = exclude || []; for (var f in values) { @@ -611,7 +649,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }, set_values: function(values) { var self = this; - return this.on_change_mutex.exec(function() { + return this.mutating_mutex.exec(function() { self._internal_set_values(values); }); }, @@ -765,8 +803,16 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM */ save: function(prepend_on_create) { var self = this; - return this.mutating_mutex.exec(function() { return self.is_initialized.pipe(function() { - try { + var save_obj = {prepend_on_create: prepend_on_create, ret: null}; + this.save_list.push(save_obj); + return this._process_operations().pipe(function() { + return $.when.apply($, save_obj.ret); + }); + }, + _process_save: function(save_obj) { + var self = this; + var prepend_on_create = save_obj.prepend_on_create; + try { var form_invalid = false, values = {}, first_invalid_field = null; @@ -810,11 +856,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } return save_deferral; } - } catch (e) { - console.error(e); - return $.Deferred().reject(); - } - });}); + } catch (e) { + console.error(e); + return $.Deferred().reject(); + } }, on_invalid: function() { var warnings = _(this.fields).chain() @@ -916,15 +961,11 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM return obj instanceof instance.web.form.FormWidget; }); }, - get_fields_values: function(blacklist) { - blacklist = blacklist || []; + get_fields_values: function() { var values = {}; var ids = this.get_selected_ids(); values["id"] = ids.length > 0 ? ids[0] : false; _.each(this.fields, function(value_, key) { - if (_.include(blacklist, key)) { - return; - } values[key] = value_.get_value(); }); return values; @@ -1085,9 +1126,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM compute_domain: function(expression) { return instance.web.form.compute_domain(expression, this.fields); }, - _build_view_fields_values: function(blacklist) { + _build_view_fields_values: function() { var a_dataset = this.dataset; - var fields_values = this.get_fields_values(blacklist); + var fields_values = this.get_fields_values(); var active_id = a_dataset.ids[a_dataset.index]; _.extend(fields_values, { active_id: active_id || false, @@ -1096,13 +1137,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM parent: {} }); if (a_dataset.parent_view) { - fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]); + fields_values.parent = a_dataset.parent_view.get_fields_values(); } return fields_values; }, - build_eval_context: function(blacklist) { + build_eval_context: function() { var a_dataset = this.dataset; - return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); + return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values()); }, }); @@ -1798,7 +1839,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi * Builds a new context usable for operations related to fields by merging * the fields'context with the action's context. */ - build_context: function(blacklist) { + build_context: function() { // only use the model's context if there is not context on the node var v_context = this.node.attrs.context; if (! v_context) { @@ -1806,7 +1847,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi } if (v_context.__ref || true) { //TODO: remove true - var fields_values = this.field_manager.build_eval_context(blacklist); + var fields_values = this.field_manager.build_eval_context(); v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values); } return v_context; @@ -1979,6 +2020,11 @@ instance.web.form.FieldInterface = { * Called when the translate button is clicked. */ on_translate: function() {}, + /** + This method is called by the form view before reading on_change values and before saving. It tells + the field to save its value before reading it using get_value(). Must return a promise. + */ + commit_value: function() {}, }; /** @@ -2122,7 +2168,10 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w } this.$el.attr('style', style); this.$el.css('minHeight', height); - } + }, + commit_value: function() { + return $.when(); + }, }); /** @@ -2216,6 +2265,13 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we }, focus: function() { this.$('input:first').focus(); + }, + set_dimensions: function (height, width) { + this._super(height, width); + this.$('input').css({ + height: height, + width: width + }); } }); @@ -2442,6 +2498,10 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc if (this.datewidget && this.datewidget.$input) { this.datewidget.$input.focus(); } + }, + set_dimensions: function (height, width) { + this._super(height, width); + this.datewidget.$input.css('height', height); } }); @@ -2502,7 +2562,7 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we this.$textarea.focus(); }, set_dimensions: function (height, width) { - this._super(); + this._super(height, width); this.$textarea.css({ width: width, minHeight: height @@ -2664,6 +2724,13 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan }, focus: function() { this.$el.find('select:first').focus(); + }, + set_dimensions: function (height, width) { + this._super(height, width); + this.$('select').css({ + height: height, + width: width + }); } }); @@ -3146,6 +3213,62 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc this.ed_def.reject(); return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); }, + set_dimensions: function (height, width) { + this._super(height, width); + this.$input.css('height', height); + } +}); + +instance.web.form.Many2OneButton = instance.web.form.AbstractField.extend({ + template: 'Many2OneButton', + init: function(field_manager, node) { + this._super.apply(this, arguments); + }, + start: function() { + this._super.apply(this, arguments); + this.set_button(); + }, + set_button: function() { + var self = this; + if (this.$button) { + this.$button.remove(); + } + var options = {}; + try { + options = py.eval(this.node.attrs.options); + } catch (e) {} + if (options.label) { + this.string = this.get('value') ? _t(options.label.edit) : _t(options.label.create); + } else { + this.string = ''; + } + this.node.attrs.icon = this.get('value') ? '/web/static/src/img/icons/gtk-yes.png' : '/web/static/src/img/icons/gtk-no.png'; + this.$button = $(QWeb.render('WidgetButton', {'widget': this})); + this.$el.append(this.$button); + this.$button.on('click', self.on_click); + }, + on_click: function(ev) { + var self = this; + this.popup = new instance.web.form.FormOpenPopup(this); + this.popup.show_element( + this.field.relation, + this.get('value'), + this.build_context(), + {title: this.string} + ); + this.popup.on('create_completed write_completed', self, function(r) { + self.set_value(r); + }); + }, + set_value: function(value_) { + var self = this; + if (value_ instanceof Array) { + value_ = value_[0]; + } + value_ = value_ || false; + this.set('value', value_); + this.set_button(); + }, }); /* @@ -3235,10 +3358,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.reload_current_view(); }, trigger_on_change: function() { - var tmp = this.doing_on_change; - this.doing_on_change = true; this.trigger('changed_value'); - this.doing_on_change = tmp; }, load_views: function() { var self = this; @@ -3433,7 +3553,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ var self = this; if (!this.dataset) return []; - this.save_any_view(); var val = this.dataset.delete_all ? [commands.delete_all()] : []; val = val.concat(_.map(this.dataset.ids, function(id) { var alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;}); @@ -3450,33 +3569,24 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.dataset.to_delete, function(x) { return commands['delete'](x.id);})); }, + commit_value: function() { + return this.save_any_view(); + }, save_any_view: function() { - if (this.doing_on_change) - return false; - return this.session.synchronized_mode(_.bind(function() { - if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view && - this.viewmanager.views[this.viewmanager.active_view] && - this.viewmanager.views[this.viewmanager.active_view].controller) { - var view = this.viewmanager.views[this.viewmanager.active_view].controller; - if (this.viewmanager.active_view === "form") { - if (!view.is_initialized.isResolved()) { - return false; - } - var res = $.when(view.save()); - if (!res.isResolved() && !res.isRejected()) { - console.warn("Asynchronous get_value() is not supported in form view."); - } - return res; - } else if (this.viewmanager.active_view === "list") { - var res = $.when(view.ensure_saved()); - if (!res.isResolved() && !res.isRejected()) { - console.warn("Asynchronous get_value() is not supported in list view."); - } - return res; + if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view && + this.viewmanager.views[this.viewmanager.active_view] && + this.viewmanager.views[this.viewmanager.active_view].controller) { + var view = this.viewmanager.views[this.viewmanager.active_view].controller; + if (this.viewmanager.active_view === "form") { + if (!view.is_initialized.isResolved()) { + return $.when(false); } + return $.when(view.save()); + } else if (this.viewmanager.active_view === "list") { + return $.when(view.ensure_saved()); } - return false; - }, this)); + } + return $.when(false); }, is_syntax_valid: function() { if (! this.viewmanager || ! this.viewmanager.views[this.viewmanager.active_view]) @@ -3544,7 +3654,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({ instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({ get_context: function() { - this.context = this.o2m.build_context([this.o2m.name]); + this.context = this.o2m.build_context(); return this.context; } }); @@ -3699,7 +3809,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ } // FIXME: why isn't there an API for this? if (this.editor.form.$el.hasClass('oe_form_dirty')) { - this.save_edition(); + this.ensure_saved(); return; } this.cancel_edition(); @@ -3818,11 +3928,11 @@ instance.web.form.One2ManyFormView = instance.web.FormView.extend({ }); var lazy_build_o2m_kanban_view = function() { -if (! instance.web_kanban || instance.web.form.One2ManyKanbanView) - return; -instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({ -}); -} + if (! instance.web_kanban || instance.web.form.One2ManyKanbanView) + return; + instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({ + }); +}; instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, { template: "FieldMany2ManyTags", @@ -3837,7 +3947,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in if (this.get("effective_readonly")) return; var self = this; - self.$text = $("textarea", this.$el); + self.$text = this.$("textarea"); self.$text.textext({ plugins : 'tags arrow autocomplete', autocomplete: { @@ -3861,9 +3971,8 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in }, tags: { isTagAllowed: function(tag) { - if (! tag.name) - return false; - return true; + return !!tag.name; + }, removeTag: function(tag) { var id = tag.data("id"); @@ -3895,7 +4004,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in self._drop_shown = true; }); self.tags = self.$text.textext()[0].tags(); - $("textarea", this.$el).focusout(function() { + self.$text.focusout(function() { self.$text.trigger("setInputData", ""); }).keydown(function(e) { if (e.which === $.ui.keyCode.TAB && self._drop_shown) { @@ -3920,8 +4029,10 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in render_value: function() { var self = this; var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); - var values = self.get("value") + var values = self.get("value"); var handle_names = function(data) { + if (self.isDestroyed()) + return; var indexed = {}; _.each(data, function(el) { indexed[el[0]] = el; @@ -3929,7 +4040,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in data = _.map(values, function(el) { return indexed[el]; }); if (! self.get("effective_readonly")) { self.tags.containerElement().children().remove(); - $("textarea", self.$el).css("padding-left", "3px"); + self.$('textarea').css("padding-left", "3px"); self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};})); } else { self.$el.html(QWeb.render("FieldMany2ManyTag", {elements: data})); @@ -4332,13 +4443,14 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ this.dataset.create_function = function(data, sup) { var fct = self.options.create_function || sup; return fct.call(this, data).then(function(r) { + self.trigger('create_completed saved', r); self.created_elements.push(r); }); }; this.dataset.write_function = function(id, data, options, sup) { var fct = self.options.write_function || sup; return fct.call(this, id, data, options).then(function() { - self.trigger('write_completed'); + self.trigger('write_completed saved'); }); }; this.dataset.parent_view = this.options.parent_view; @@ -4415,9 +4527,11 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ this.select_elements(this.created_elements); this.created_elements = []; } + this.trigger('closed'); this.destroy(); }, destroy: function () { + this.trigger('closed'); this.$el.dialog('close'); this._super(); }, @@ -4821,6 +4935,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ template: 'FieldBinaryImage', + placeholder: "/web/static/src/img/placeholder.png", render_value: function() { var self = this; var url; @@ -4834,7 +4949,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + this.view.dataset.model +'&id=' + id + '&field=' + field + '&t=' + (new Date().getTime()); } else { - url = "/web/static/src/img/placeholder.png"; + url = this.placeholder; } var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url })); this.$el.find('> img').remove(); @@ -4847,10 +4962,10 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ $img.css("margin-left", "" + (self.options.size[0] - $img.width()) / 2 + "px"); $img.css("margin-top", "" + (self.options.size[1] - $img.height()) / 2 + "px"); }); - }, - on_file_change: function() { - this.render_value(); - this._super.apply(this, arguments); + $img.on('error', function() { + $img.attr('src', self.placeholder); + instance.webclient.notification.warn(_t("Image"), _t("Could not display the selected image.")); + }); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { this.internal_set_value(file_base64); @@ -5130,6 +5245,7 @@ instance.web.form.widgets = new instance.web.Registry({ 'datetime' : 'instance.web.form.FieldDatetime', 'selection' : 'instance.web.form.FieldSelection', 'many2one' : 'instance.web.form.FieldMany2One', + 'many2onebutton' : 'instance.web.form.Many2OneButton', 'many2many' : 'instance.web.form.FieldMany2Many', 'many2many_tags' : 'instance.web.form.FieldMany2ManyTags', 'many2many_kanban' : 'instance.web.form.FieldMany2ManyKanban', diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index fd74fd8922b..e59fb3c4d84 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -970,7 +970,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // set) a human-readable version. m2o does not have this issue // because the non-human-readable is just a number, where the // human-readable version is a pair - if (value && (ref_match = /([\w\.]+),(\d+)/.exec(value))) { + if (value && (ref_match = /^([\w\.]+),(\d+)$/.exec(value))) { // reference values are in the shape "$model,$id" (as a // string), we need to split and name_get this pair in order // to get a correctly displayable value in the field @@ -1306,9 +1306,11 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we process_modifiers: false }); } catch (e) { - group_label = row_data[group_column.id].value; + group_label = _.str.escapeHTML(row_data[group_column.id].value); } - $group_column.text(_.str.sprintf("%s (%d)", + // group_label is html-clean (through format or explicit + // escaping if format failed), can inject straight into HTML + $group_column.html(_.str.sprintf("%s (%d)", group_label, group.length)); if (group.length && group.openable) { @@ -2010,6 +2012,7 @@ instance.web.list.columns = new instance.web.Registry({ 'field.progressbar': 'instance.web.list.ProgressBar', 'field.handle': 'instance.web.list.Handle', 'button': 'instance.web.list.Button', + 'field.many2onebutton': 'instance.web.list.Many2OneButton', }); instance.web.list.columns.for_ = function (id, field, node) { var description = _.extend({tag: node.tag}, field, node.attrs); @@ -2197,5 +2200,11 @@ instance.web.list.Handle = instance.web.list.Column.extend({ return '<div class="oe_list_handle">'; } }); +instance.web.list.Many2OneButton = instance.web.list.Column.extend({ + _format: function (row_data, options) { + this.has_value = !!row_data[this.id].value; + return QWeb.render('Many2OneButton.cell', {'widget': this}); + }, +}); }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 021e5549c18..85465e1c54e 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -12,6 +12,8 @@ openerp.web.list_editable = function (instance) { var self = this; this._super.apply(this, arguments); + this.saving_mutex = new $.Mutex(); + this._force_editability = null; this._context_editable = false; this.editor = this.make_editor(); @@ -162,10 +164,13 @@ openerp.web.list_editable = function (instance) { * @returns {$.Deferred} */ ensure_saved: function () { - if (!this.editor.is_editing()) { - return $.when(); - } - return this.save_edition(); + var self = this; + return this.saving_mutex.exec(function() { + if (!self.editor.is_editing()) { + return $.when(); + } + return self.save_edition(); + }); }, /** * Set up the edition of a record of the list view "inline" diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 05760420ba8..3f1dfabe80a 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -45,7 +45,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie view_type: "tree", toolbar: this.view_manager ? !!this.view_manager.sidebar : false, context: this.dataset.get_context() - }).then(this.on_loaded); + }, this.on_loaded); }, /** * Returns the list of fields needed to correctly read objects. @@ -63,12 +63,6 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie } return fields; }, - store_record:function(records){ - var self = this; - _(records).each(function (record) { - self.records[record.id] = record; - }); - }, on_loaded: function (fields_view) { var self = this; var has_toolbar = !!fields_view.arch.attrs.toolbar; @@ -93,19 +87,17 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie this.$el.addClass(this.fields_view.arch.attrs['class']); this.dataset.read_slice(this.fields_list()).then(function(records) { - self.store_record(records); if (!has_toolbar) { // WARNING: will do a second read on the same ids, but only on // first load so not very important - self.render_data({'null':records}) - self.getdata(_.pluck(records,"id")); + self.getdata(null, _(records).pluck('id')); return; } var $select = self.$el.find('select') .change(function () { var $option = $(this).find(':selected'); - self.getdata($option.val()); + self.getdata($option.val(), $option.data('children')); }); _(records).each(function (record) { self.records[record.id] = record; @@ -120,12 +112,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie $select.change(); } }); - this.$el.find("#tree_view_expand").click(function(){ - self.expand_all(); - }); - this.$el.find("#tree_view_collapse").click(function(){ - self.collpase_all(); - }); + // TODO store open nodes in url ?... this.do_push_state({}); @@ -141,22 +128,6 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie return [color, py.parse(py.tokenize(expr)), expr]; }).value(); }, - expand_all: function(){ - var self = this; - var tr = this.$el.find(".oe-treeview-table tbody tr[id^='treerow_']"); - _.each(tr,function(rec){ - self.showcontent($(rec).attr('data-id'),true); - }); - }, - collpase_all: function(){ - var self = this; - var root_tr = this.$el.find(".oe-treeview-table tbody tr[data-level='"+1+"']"); - _.each(root_tr,function(rec){ - if($(rec).hasClass('oe_open')){ - self.showcontent($(rec).attr('data-id'),false); - } - }); - }, /** * Returns the color for the provided record in the current view (from the * ``@colors`` attribute) @@ -193,44 +164,50 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie }); this.$el.delegate('.treeview-tr', 'click', function () { - var $this = $(this), + var is_loaded = 0, + $this = $(this), record_id = $this.data('id'), - bool = $this.parent().hasClass('oe_open'); - self.showcontent(record_id, !bool); + record = self.records[record_id], + children_ids = record[self.children_field]; + + _(children_ids).each(function(childid) { + if (self.$el.find('#treerow_' + childid).length) { + if (self.$el.find('#treerow_' + childid).is(':hidden')) { + is_loaded = -1; + } else { + is_loaded++; + } + } + }); + if (is_loaded === 0) { + if (!$this.parent().hasClass('oe_open')) { + self.getdata(record_id, children_ids); + } + } else { + self.showcontent(record_id, is_loaded < 0); + } }); }, // get child data of selected value - getdata: function (id) { + getdata: function (id, children_ids) { var self = this; - var parent_child ={}; - id = _.isArray(id)?id:parseInt(id); - var ir_model_data = new instance.web.Model(this.model,self.dataset.get_context() || {},[['id','child_of',id]]).query(); - ir_model_data._execute().then(function(records){ - self.store_record(records); - _.each(records,function(rec){ - if(rec[self.children_field].length === 0)return; - parent_child[rec.id] = []; - _.each(rec[self.children_field],function(key){ - parent_child[rec.id].push(self.records[key]); - }); - }) - self.render_data(parent_child); - }); - }, - render_data: function(groupby){ - var self = this; - _.each(_.keys(groupby),function(key){ - var $curr_node = self.$el.find('#treerow_' + key); - var record = groupby[key]; + + self.dataset.read_ids(children_ids, this.fields_list()).then(function(records) { + _(records).each(function (record) { + self.records[record.id] = record; + }); + + var $curr_node = self.$el.find('#treerow_' + id); var children_rows = QWeb.render('TreeView.rows', { - 'records': record, + 'records': records, 'children_field': self.children_field, 'fields_view': self.fields_view.arch.children, 'fields': self.fields, - 'level': ($curr_node.data('level') || 0) + 1, + 'level': $curr_node.data('level') || 0, 'render': instance.web.format_value, 'color_for': self.color_for }); + if ($curr_node.length) { $curr_node.addClass('oe_open'); $curr_node.after(children_rows); @@ -238,10 +215,8 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie self.$el.find('tbody').html(children_rows); } }); - self.collpase_all(); }, - // Get details in listview activate: function(id) { var self = this; @@ -284,5 +259,13 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie }, this); }, + do_show: function () { + this.$el.show(); + }, + + do_hide: function () { + this.$el.hide(); + this.hidden = true; + } }); }; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 275d31a9fde..215374cb6fb 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -567,16 +567,14 @@ </t> <t t-name="TreeView"> - <div class = "tree_header"> - <select t-if="toolbar" ></select> - <button id = "tree_view_collapse">Collapse All</button> - <button id = "tree_view_expand">Expand All</button> - </div> - <table class="oe-treeview-table"> + <select t-if="toolbar" style="width: 30%"> + </select> + <table class="oe_tree_table oe-treeview-table"> <thead> <tr> <th t-foreach="fields_view" t-as="field" - t-if="!field.attrs.modifiers.tree_invisible"> + t-if="!field.attrs.modifiers.tree_invisible" + class="treeview-header"> <t t-esc="field_value.attrs.string || fields[field.attrs.name].string" /> </th> </tr> @@ -588,11 +586,11 @@ <tr t-name="TreeView.rows" t-foreach="records" t-as="record" t-att-id="'treerow_' + record.id" - t-att-data-id="record.id" t-att-data-level="level"> + t-att-data-id="record.id" t-att-data-level="level + 1"> <t t-set="children" t-value="record[children_field]"/> <t t-set="class" t-value="children and children.length ? 'treeview-tr' : 'treeview-td'"/> <t t-set="rank" t-value="'oe-treeview-first'"/> - <t t-set="style" t-value="'background-position: ' + 19*(level-1) + 'px; padding-left: ' + (4 + 19*(level-1)) + 'px;'"/> + <t t-set="style" t-value="'background-position: ' + 19*(level) + 'px; padding-left: ' + (4 + 19*(level)) + 'px;'"/> <td t-foreach="fields_view" t-as="field" t-if="!field.attrs.modifiers.tree_invisible" @@ -1029,6 +1027,13 @@ </t> </span> </t> +<t t-name="Many2OneButton"> + <span class="oe_form_field"> + </span> +</t> +<t t-name="Many2OneButton.cell" + ><img t-attf-src="#{_s}/web/static/src/img/icons/gtk-#{widget.has_value ? 'yes' : 'no'}.png" width="16" height="16"/> +</t> <t t-name="FieldMany2ManyTags"> <div class="oe_form_field oe_tags" t-att-style="widget.node.attrs.style"> <t t-if="! widget.get('effective_readonly')"> |
