diff options
| author | Vidhin Mehta <vme@tinyerp.com> | 2013-02-20 18:49:56 +0530 |
|---|---|---|
| committer | Vidhin Mehta <vme@tinyerp.com> | 2013-02-20 18:49:56 +0530 |
| commit | ca912e097585de760ceaa0dd1f0a4b942d59f254 (patch) | |
| tree | 8aa89a5ab59c183c13d8cf886617a5563c08dd56 /addons/web/static/src | |
| parent | 0709ca97e86a5b4895dd065f15d54410d48ba5c0 (diff) | |
| parent | f559b97ce91a4faaca92904bd66f3efd0726f5ea (diff) | |
[MERGE]7.0
bzr revid: vme@tinyerp.com-20130220131956-hrnbak55i9qdpe4y
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/base.css | 11 | ||||
| -rw-r--r-- | addons/web/static/src/css/base.sass | 7 | ||||
| -rw-r--r-- | addons/web/static/src/img/back-enable.jpg | bin | 0 -> 28373 bytes | |||
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 24 | ||||
| -rw-r--r-- | addons/web/static/src/js/formats.js | 10 | ||||
| -rw-r--r-- | addons/web/static/src/js/pyeval.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 200 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 62 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/views.js | 65 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 9 |
12 files changed, 273 insertions, 126 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 638f446c5ba..be26b7edd70 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1,4 +1,4 @@ -@charset "UTF-8"; +@charset "utf-8"; @font-face { font-family: "mnmliconsRegular"; src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot"); @@ -1242,8 +1242,6 @@ .openerp .oe_secondary_submenu { padding: 2px 0 8px 0; margin: 0; - width: 220px; - display: inline-block; } .openerp .oe_secondary_submenu li { position: relative; @@ -1417,7 +1415,13 @@ display: inline-block; overflow: hidden; } +.openerp .oe_view_manager { + display: table; + height: inherit; + width: 100%; +} .openerp .oe_view_manager .oe_view_manager_body { + display: table-row; height: inherit; } .openerp .oe_view_manager .oe_view_manager_view_kanban { @@ -3091,7 +3095,6 @@ } .kitten-mode-activated { - background-image: url(http://placekitten.com/g/1365/769); background-size: cover; background-attachment: fixed; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 42ade77ccbb..b308b825775 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1009,8 +1009,6 @@ $sheet-padding: 16px .oe_secondary_submenu padding: 2px 0 8px 0 margin: 0 - width: 220px - display: inline-block li position: relative margin: 0 @@ -1139,7 +1137,11 @@ $sheet-padding: 16px // }}} // ViewManager common {{{ .oe_view_manager + display: table + height: inherit + width: 100% .oe_view_manager_body + display: table-row height: inherit .oe_view_manager_view_kanban height: inherit @@ -2438,7 +2440,6 @@ $sheet-padding: 16px // }}} // Kitten Mode {{{ .kitten-mode-activated - background-image: url(http://placekitten.com/g/1365/769) background-size: cover background-attachment: fixed >* diff --git a/addons/web/static/src/img/back-enable.jpg b/addons/web/static/src/img/back-enable.jpg Binary files differnew file mode 100644 index 00000000000..79a7098f6da --- /dev/null +++ b/addons/web/static/src/img/back-enable.jpg diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 30a51026684..7b64358f005 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -481,6 +481,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ self.do_action("reload"); }, }, + _push_me: false, }; self.do_action(client_action); }); @@ -868,7 +869,7 @@ instance.web.Menu = instance.web.Widget.extend({ this.needaction_data = data; _.each(this.needaction_data, function (item, menu_id) { var $item = self.$secondary_menus.find('a[data-menu="' + menu_id + '"]'); - $item.remove('oe_menu_counter'); + $item.find('.oe_menu_counter').remove(); if (item.needaction_counter && item.needaction_counter > 0) { $item.append(QWeb.render("Menu.needaction_counter", { widget : item })); } @@ -1047,6 +1048,9 @@ instance.web.UserMenu = instance.web.Widget.extend({ }; this.update_promise = this.update_promise.then(fct, fct); }, + on_menu_help: function() { + window.open('http://help.openerp.com', '_blank'); + }, on_menu_logout: function() { this.trigger('user_logout'); }, @@ -1157,6 +1161,7 @@ instance.web.WebClient = instance.web.Client.extend({ return $.when(this._super()).then(function() { if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) { $("body").addClass("kitten-mode-activated"); + $("body").css("background-image", "url(" + instance.session.origin + "/web/static/src/img/back-enable.jpg" + ")"); if ($.blockUI) { $.blockUI.defaults.message = '<img src="http://www.amigrave.com/kitten.gif">'; } @@ -1347,9 +1352,10 @@ instance.web.WebClient = instance.web.Client.extend({ .then(function (result) { return self.action_mutex.exec(function() { if (options.needaction) { - result.context = new instance.web.CompoundContext( - result.context, - {search_default_message_unread: true}); + result.context = new instance.web.CompoundContext(result.context, { + search_default_message_unread: true, + search_disable_custom_filters: true, + }); } var completed = $.Deferred(); $.when(self.action_manager.do_action(result, { @@ -1370,13 +1376,9 @@ instance.web.WebClient = instance.web.Client.extend({ }); }, set_content_full_screen: function(fullscreen) { - if (fullscreen) { - $(".oe_webclient", this.$el).addClass("oe_content_full_screen"); - $("body").css({'overflow-y':'hidden'}); - } else { - $(".oe_webclient", this.$el).removeClass("oe_content_full_screen"); - $("body").css({'overflow-y':'scroll'}); - } + $(document.body).css('overflow-y', fullscreen ? 'hidden' : 'scroll'); + this.$('.oe_webclient').toggleClass( + 'oe_content_full_screen', fullscreen); }, has_uncommitted_changes: function() { var $e = $.Event('clear_uncommitted_changes'); diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 9432b02826f..851b60af229 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -166,9 +166,13 @@ instance.web.format_value = function (value, descriptor, value_if_empty) { value = Math.abs(value); pattern = '-' + pattern; } - return _.str.sprintf(pattern, - Math.floor(value), - Math.round((value % 1) * 60)); + var hour = Math.floor(value); + var min = Math.round((value % 1) * 60); + if (min == 60){ + min = 0; + hour = hour + 1; + } + return _.str.sprintf(pattern, hour, min); case 'many2one': // name_get value format return value[1] ? value[1].split("\n")[0] : value[1]; diff --git a/addons/web/static/src/js/pyeval.js b/addons/web/static/src/js/pyeval.js index 2c1b5ec9d7d..554c4aa7db0 100644 --- a/addons/web/static/src/js/pyeval.js +++ b/addons/web/static/src/js/pyeval.js @@ -689,15 +689,14 @@ openerp.web.pyeval = function (instance) { }; instance.web.pyeval.context = function () { - return { - uid: py.float.fromJSON(instance.session.uid), + return _.extend({ datetime: datetime, context_today: context_today, time: time, relativedelta: relativedelta, current_date: py.PY_call( time.strftime, [py.str.fromJSON('%Y-%m-%d')]), - }; + }, instance.session.user_context); }; /** diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index a0208cc6d7a..202af9ae4d4 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -127,41 +127,26 @@ my.InputView = instance.web.Widget.extend({ events: { focus: function () { this.trigger('focused', this); }, blur: function () { this.$el.text(''); this.trigger('blurred', this); }, - keydown: 'onKeydown' + keydown: 'onKeydown', + paste: 'onPaste', }, getSelection: function () { // get Text node - var root = this.$el[0].childNodes[0]; + var root = this.el.childNodes[0]; if (!root || !root.textContent) { // if input does not have a child node, or the child node is an // empty string, then the selection can only be (0, 0) return {start: 0, end: 0}; } - if (window.getSelection) { - var domRange = window.getSelection().getRangeAt(0); - assert(domRange.startContainer === root, - "selection should be in the input view"); - assert(domRange.endContainer === root, - "selection should be in the input view"); - return { - start: domRange.startOffset, - end: domRange.endOffset - } - } else if (document.selection) { - var ieRange = document.selection.createRange(); - var rangeParent = ieRange.parentElement(); - assert(rangeParent === root, - "selection should be in the input view"); - var offsetRange = document.body.createTextRange(); - offsetRange = offsetRange.moveToElementText(rangeParent); - offsetRange.setEndPoint("EndToStart", ieRange); - var start = offsetRange.text.length; - return { - start: start, - end: start + ieRange.text.length - } + var range = window.getSelection().getRangeAt(0); + assert(range.startContainer === root, + "selection should be in the input view"); + assert(range.endContainer === root, + "selection should be in the input view"); + return { + start: range.startOffset, + end: range.endOffset } - throw new Error("Could not get caret position"); }, onKeydown: function (e) { var sel; @@ -199,6 +184,50 @@ my.InputView = instance.web.Widget.extend({ } break; } + }, + setCursorAtEnd: function () { + var sel = window.getSelection(); + sel.removeAllRanges(); + var range = document.createRange(); + // in theory, range.selectNodeContents should work here. In practice, + // MSIE9 has issues from time to time, instead of selecting the inner + // text node it would select the reference node instead (e.g. in demo + // data, company news, copy across the "Company News" link + the title, + // from about half the link to half the text, paste in search box then + // hit the left arrow key, getSelection would blow up). + // + // Explicitly selecting only the inner text node (only child node at + // this point, though maybe we should assert that) avoiids the issue + range.selectNode(this.el.childNodes[0]); + range.collapse(false); + sel.addRange(range); + }, + onPaste: function () { + // In MSIE and Webkit, it is possible to get various representations of + // the clipboard data at this point e.g. + // window.clipboardData.getData('Text') and + // event.clipboardData.getData('text/plain') to ensure we have a plain + // text representation of the object (and probably ensure the object is + // pastable as well, so nobody puts an image in the search view) + // (nb: since it's not possible to alter the content of the clipboard + // — at least in Webkit — to ensure only textual content is available, + // using this would require 1. getting the text data; 2. manually + // inserting the text data into the content; and 3. cancelling the + // paste event) + // + // But Firefox doesn't support the clipboard API (as of FF18) + // although it correctly triggers the paste event (Opera does not even + // do that) => implement lowest-denominator system where onPaste + // triggers a followup "cleanup" pass after the data has been pasted + setTimeout(function () { + // Read text content (ignore pasted HTML) + var data = this.$el.text(); + // paste raw text back in + this.$el.empty().text(data); + // Set the cursor at the end of the text, so the cursor is not lost + // in some kind of error-spawning limbo. + this.setCursorAtEnd(); + }.bind(this), 0); } }); my.FacetView = instance.web.Widget.extend({ @@ -668,7 +697,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return filter.user_id && filter.is_default; }); if (personal_filter) { - this.custom_filters.enable_filter(personal_filter, true); + this.custom_filters.toggle_filter(personal_filter, true); return; } @@ -676,7 +705,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return !filter.user_id && filter.is_default; }); if (global_filter) { - this.custom_filters.enable_filter(global_filter, true); + this.custom_filters.toggle_filter(global_filter, true); return; } } @@ -1569,6 +1598,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ get_groupby: function () { return [filter.context]; }, get_domain: function () { return filter.domain; } }, + _id: filter['id'], is_custom_filter: true, values: [{label: filter.name, value: null}] }; @@ -1579,6 +1609,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ append_filter: function (filter) { var self = this; var key = this.key_for(filter); + var warning = _t("This filter is global and will be removed for everybody if you continue."); var $filter; if (key in this.$filters) { @@ -1596,6 +1627,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ $('<a class="oe_searchview_custom_delete">x</a>') .click(function (e) { e.stopPropagation(); + if (!(filter.user_id || confirm(warning))) { + return; + } self.model.call('unlink', [id]).done(function () { $filter.remove(); delete self.$filters[key]; @@ -1606,10 +1640,18 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } $filter.unbind('click').click(function () { - self.enable_filter(filter); + self.toggle_filter(filter); }); }, - enable_filter: function (filter, preventSearch) { + toggle_filter: function (filter, preventSearch) { + var current = this.view.query.find(function (facet) { + return facet.get('_id') === filter.id; + }); + if (current) { + this.view.query.remove(current); + this.$filters[this.key_for(filter)].removeClass('oe_selected'); + return; + } this.view.query.reset([this.facet_for(filter)], { preventSearch: preventSearch || false}); this.$filters[this.key_for(filter)].addClass('oe_selected'); @@ -1718,10 +1760,12 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ }); return $.when( this._super(), - new instance.web.Model(this.view.model).call('fields_get').done(function(data) { - self.fields = _.extend({ - id: { string: 'ID', type: 'id' } - }, data); + new instance.web.Model(this.view.model).call('fields_get', { + context: this.view.dataset.context + }).done(function(data) { + self.fields = _.extend({ + id: { string: 'ID', type: 'id' } + }, data); })).done(function () { self.append_proposition(); }); @@ -1772,6 +1816,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ template: 'SearchView.extended_search.proposition', events: { 'change .searchview_extended_prop_field': 'changed', + 'change .searchview_extended_prop_op': 'operator_changed', 'click .searchview_extended_delete_prop': function (e) { e.stopPropagation(); this.getParent().remove_proposition(this); @@ -1788,6 +1833,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ this._super(parent); this.fields = _(fields).chain() .map(function(val, key) { return _.extend({}, val, {'name': key}); }) + .filter(function (field) { return !field.deprecated; }) .sortBy(function(field) {return field.string;}) .value(); this.attrs = {_: _, fields: this.fields, selected: null}; @@ -1802,6 +1848,17 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); } }, + operator_changed: function (e) { + var $value = this.$('.searchview_extended_prop_value'); + switch ($(e.target).val()) { + case '∃': + case '∄': + $value.hide(); + break; + default: + $value.show(); + } + }, /** * Selects the provided field object * @@ -1830,7 +1887,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ .text(String(operator.text)) .appendTo(self.$('.searchview_extended_prop_op')); }); - var $value_loc = this.$('.searchview_extended_prop_value').empty(); + var $value_loc = this.$('.searchview_extended_prop_value').show().empty(); this.value.appendTo($value_loc); }, @@ -1838,19 +1895,12 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ if ( this.attrs.selected == null) return null; var field = this.attrs.selected; - var op = this.$('.searchview_extended_prop_op')[0]; - var operator = op.options[op.selectedIndex]; + var op_select = this.$('.searchview_extended_prop_op')[0]; + var operator = op_select.options[op_select.selectedIndex]; + return { - label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), { - field: field.string, - // According to spec, HTMLOptionElement#label should return - // HTMLOptionElement#text when not defined/empty, but it does - // not in older Webkit (between Safari 5.1.5 and Chrome 17) and - // Gecko (pre Firefox 7) browsers, so we need a manual fallback - // for those - operator: operator.label || operator.text, - value: this.value}), - value: [field.name, operator.value, this.value.get_value()] + label: this.value.get_label(field, operator), + value: this.value.get_domain(field, operator), }; } }); @@ -1860,6 +1910,37 @@ instance.web.search.ExtendedSearchProposition.Field = instance.web.Widget.extend this._super(parent); this.field = field; }, + get_label: function (field, operator) { + var format; + switch (operator.value) { + case '∃': case '∄': format = _t('%(field)s %(operator)s'); break; + default: format = _t('%(field)s %(operator)s "%(value)s"'); break; + } + return this.format_label(format, field, operator); + }, + format_label: function (format, field, operator) { + return _.str.sprintf(format, { + field: field.string, + // According to spec, HTMLOptionElement#label should return + // HTMLOptionElement#text when not defined/empty, but it does + // not in older Webkit (between Safari 5.1.5 and Chrome 17) and + // Gecko (pre Firefox 7) browsers, so we need a manual fallback + // for those + operator: operator.label || operator.text, + value: this + }); + }, + get_domain: function (field, operator) { + switch (operator.value) { + case '∃': return this.make_domain(field.name, '!=', false); + case '∄': return this.make_domain(field.name, '=', false); + default: return this.make_domain( + field.name, operator.value, this.get_value()); + } + }, + make_domain: function (field, operator, value) { + return [field, operator, value]; + }, /** * Returns a human-readable version of the value, in case the "logical" * and the "semantic" values of a field differ (as for selection fields, @@ -1879,7 +1960,9 @@ instance.web.search.ExtendedSearchProposition.Char = instance.web.search.Extende {value: "ilike", text: _lt("contains")}, {value: "not ilike", text: _lt("doesn't contain")}, {value: "=", text: _lt("is equal to")}, - {value: "!=", text: _lt("is not equal to")} + {value: "!=", text: _lt("is not equal to")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], get_value: function() { return this.$el.val(); @@ -1893,7 +1976,9 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext {value: ">", text: _lt("greater than")}, {value: "<", text: _lt("less than")}, {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "<=", text: _lt("less or equal than")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], /** * Date widgets live in view_form which is not yet loaded when this is @@ -1927,7 +2012,9 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte {value: ">", text: _lt("greater than")}, {value: "<", text: _lt("less than")}, {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "<=", text: _lt("less or equal than")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], toString: function () { return this.$el.val(); @@ -1952,7 +2039,9 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend {value: ">", text: _lt("greater than")}, {value: "<", text: _lt("less than")}, {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "<=", text: _lt("less or equal than")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], toString: function () { return this.$el.val(); @@ -1970,7 +2059,9 @@ instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.Ex template: 'SearchView.extended_search.proposition.selection', operators: [ {value: "=", text: _lt("is")}, - {value: "!=", text: _lt("is not")} + {value: "!=", text: _lt("is not")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], toString: function () { var select = this.$el[0]; @@ -1987,7 +2078,10 @@ instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.Exte {value: "=", text: _lt("is true")}, {value: "!=", text: _lt("is false")} ], - toString: function () { return ''; }, + get_label: function (field, operator) { + return this.format_label( + _t('%(field)s %(operator)s'), field, operator); + }, get_value: function() { return true; } diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 01d4bf6dd33..a023dd54174 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -251,9 +251,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.dataset.ids.push(state.id); } this.dataset.select_id(state.id); - if (warm) { - this.do_show(); - } + this.do_show({ reload: warm }); } }, /** @@ -511,9 +509,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var on_change = widget.node.attrs.on_change; if (on_change) { var change_spec = self.parse_on_change(on_change, widget); - var id = [self.datarecord.id == null ? [] : [self.datarecord.id]]; + var ids = []; + if (self.datarecord.id && !instance.web.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) { + // In case of a o2m virtual id, we should pass an empty ids list + ids.push(self.datarecord.id); + } def = new instance.web.Model(self.dataset.model).call( - change_spec.method, id.concat(change_spec.args)); + change_spec.method, [ids].concat(change_spec.args)); } else { def = $.when({}); } @@ -573,19 +575,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM ] }); } - if (result.domain) { - function edit_domain(node) { - if (typeof node !== "object") { - return; - } - var new_domain = result.domain[node.attrs.name]; - if (new_domain) { - node.attrs.domain = new_domain; - } - _(node.children).each(edit_domain); - } - edit_domain(this.fields_view.arch); - } + + var fields = this.fields; + _(result.domain).each(function (domain, fieldname) { + var field = fields[fieldname]; + if (!field) { return; } + field.node.attrs.domain = domain; + }); + return $.Deferred().resolve(); } catch(e) { console.error(e); @@ -2381,6 +2378,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({ type_of_date: "datetime", events: { 'change .oe_datepicker_master': 'change_datetime', + 'dragstart img.oe_datepicker_trigger': function () { return false; }, }, init: function(parent) { this._super(parent); @@ -2962,7 +2960,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc case $.ui.keyCode.DOWN: e.stopPropagation(); } - } + }, + 'dragstart .oe_m2o_drop_down_button img': function () { return false; }, + 'dragstart .oe_m2o_cm_button': function () { return false; } }, init: function(field_manager, node) { this._super(field_manager, node); @@ -4455,6 +4455,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ * options: * -readonly: only applicable when not in creation mode, default to false * - alternative_form_view + * - view_id * - write_function * - read_function * - create_function @@ -4521,7 +4522,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ _.extend(options, { $buttons: this.$buttonpane, }); - this.view_form = new instance.web.FormView(this, this.dataset, false, options); + this.view_form = new instance.web.FormView(this, this.dataset, this.options.view_id || false, options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } @@ -4550,6 +4551,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ }); var $cbutton = self.$buttonpane.find(".oe_abstractformpopup-form-close"); $cbutton.click(function() { + self.view_form.trigger('on_button_cancel'); self.check_exit(); }); self.view_form.do_show(); @@ -5221,6 +5223,8 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false; this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false; this.set({value: false}); + this.field_manager.on("view_content_has_changed", this, this.render_value); + this.selection_mutex = new $.Mutex(); }, start: function() { if (this.options.clickable) { @@ -5239,14 +5243,16 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ }, render_value: function() { var self = this; - self.get_selection().done(function() { - var content = QWeb.render("FieldStatus.content", {widget: self}); - self.$el.html(content); - var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}"); - var color = colors[self.get('value')]; - if (color) { - self.$("oe_active").css("color", color); - } + self.selection_mutex.exec(function() { + return self.get_selection().done(function() { + var content = QWeb.render("FieldStatus.content", {widget: self}); + self.$el.html(content); + var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}"); + var color = colors[self.get('value')]; + if (color) { + self.$("oe_active").css("color", color); + } + }); }); }, /** Get the selection and render it diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index fbbfabcacca..27e2c88daaf 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1186,7 +1186,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. } }); instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{ - passtrough_events: 'action deleted row_link', + passthrough_events: 'action deleted row_link', /** * Grouped display for the ListView. Handles basic DOM events and interacts * with the :js:class:`~DataGroup` bound to it. @@ -1406,7 +1406,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we // can have selections spanning multiple links var selection = self.get_selection(); $this.trigger(e, [selection.ids, selection.records]); - }).bind(this.passtrough_events, function (e) { + }).bind(this.passthrough_events, function (e) { // additional positional parameters are provided to trigger as an // Array, following the event type or event object, but are // provided to the .bind event handler as *args. diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index efc442de2a9..6be83e7af52 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -795,7 +795,7 @@ openerp.web.list_editable = function (instance) { }); instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{ - passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved", + passthrough_events: instance.web.ListView.Groups.prototype.passthrough_events + " edit saved", get_row_for: function (record) { return _(this.children).chain() .invoke('get_row_for', record) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 10d4104921b..5d1e16f4999 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -190,6 +190,14 @@ instance.web.ActionManager = instance.web.Widget.extend({ }); state = _.extend(params || {}, state); } + if (this.inner_action.context) { + if (this.inner_action.context.active_id) { + state["active_id"] = this.inner_action.context.active_id; + } + if (this.inner_action.context.active_ids) { + //state["active_ids"] = this.inner_action.context.active_ids.join(','); + } + } } if(!this.dialog) { this.getParent().do_push_state(state); @@ -212,8 +220,20 @@ instance.web.ActionManager = instance.web.Widget.extend({ } else { var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action; if (run_action) { + var add_context = {}; + if (state.active_id) { + add_context.active_id = state.active_id; + } + if (state.active_ids) { + // The jQuery BBQ plugin does some parsing on values that are valid integers. + // It means that if there's only one item, it will do parseInt() on it, + // otherwise it will keep the comma seperated list as string. + add_context.active_ids = state.active_ids.toString().split(',').map(function(id) { + return parseInt(id, 10) || id; + }); + } this.null_action(); - action_loaded = this.do_action(state.action); + action_loaded = this.do_action(state.action, { additional_context: add_context }); $.when(action_loaded || null).done(function() { instance.webclient.menu.has_been_loaded.done(function() { if (self.inner_action && self.inner_action.id) { @@ -249,12 +269,25 @@ instance.web.ActionManager = instance.web.Widget.extend({ } }); }, + /** + * Execute an OpenERP action + * + * @param {Number|String|Object} Can be either an action id, a client action or an action descriptor. + * @param {Object} [options] + * @param {Boolean} [options.clear_breadcrumbs=false] Clear the breadcrumbs history list + * @param {Function} [options.on_reverse_breadcrumb] Callback to be executed whenever an anterior breadcrumb item is clicked on. + * @param {Function} [options.on_close] Callback to be executed when the dialog is closed (only relevant for target=new actions) + * @param {Function} [options.action_menu_id] Manually set the menu id on the fly. + * @param {Object} [options.additional_context] Additional context to be merged with the action's context. + * @return {jQuery.Deferred} Action loaded + */ do_action: function(action, options) { options = _.defaults(options || {}, { clear_breadcrumbs: false, on_reverse_breadcrumb: function() {}, on_close: function() {}, action_menu_id: null, + additional_context: {}, }); if (action === false) { action = { type: 'ir.actions.act_window_close' }; @@ -269,9 +302,13 @@ instance.web.ActionManager = instance.web.Widget.extend({ } // Ensure context & domain are evaluated and can be manipulated/used - if (action.context) { - action.context = instance.web.pyeval.eval( - 'context', action.context); + var ncontext = new instance.web.CompoundContext(options.additional_context, action.context || {}); + action.context = instance.web.pyeval.eval('context', ncontext); + if (action.context.active_id || action.context.active_ids) { + // Here we assume that when an `active_id` or `active_ids` is used + // in the context, we are in a `related` action, so we disable the + // searchview's default custom filters. + action.context.search_disable_custom_filters = true; } if (action.domain) { action.domain = instance.web.pyeval.eval( @@ -598,6 +635,12 @@ instance.web.ViewManager = instance.web.Widget.extend({ self.trigger("controller_inited",view_type,controller); }); }, + /** + * @returns {Number|Boolean} the view id of the given type, false if not found + */ + get_view_id: function(view_type) { + return this.views[view_type] && this.views[view_type].view_id || false; + }, set_title: function(title) { this.$el.find('.oe_view_title_text:first').text(title); }, @@ -954,10 +997,11 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ }); }, do_create_view: function(view_type) { - var r = this._super.apply(this, arguments); - var view = this.views[view_type].controller; - view.set({ 'title': this.action.name }); - return r; + var self = this; + return this._super.apply(this, arguments).then(function() { + var view = self.views[view_type].controller; + view.set({ 'title': self.action.name }); + }); }, get_action_manager: function() { var cur = this; @@ -1271,11 +1315,6 @@ instance.web.View = instance.web.Widget.extend({ active_ids: [record_id], active_model: dataset.model }); - if (("" + action.context).match(/\bactive_id\b/)) { - // Special case: when the context is evaluted using - // the active_id, we want to disable the custom filters. - ncontext.add({ search_disable_custom_filters: true }); - } } ncontext.add(action.context || {}); action.context = ncontext; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 3e510329683..e639e9d07ee 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -403,8 +403,9 @@ <img class="oe_topbar_avatar" t-att-data-default-src="_s + '/web/static/src/img/user_menu_avatar.png'"/> <span class="oe_topbar_name"/> <ul class="oe_dropdown_menu"> - <li><a href="#" data-menu="about">About OpenERP</a></li> <li><a href="#" data-menu="settings">Preferences</a></li> + <li><a href="#" data-menu="about">About OpenERP</a></li> + <li><a href="#" data-menu="help">Help</a></li> <li><a href="#" data-menu="logout">Log out</a></li> </ul> </span> @@ -1414,8 +1415,6 @@ this.removeAttr('t-if'); </t> - <t t-jquery="tfoot > tr:last-child" t-operation="replace"/> - <t t-jquery="td.oe-actions"> this.removeAttr('t-if'); </t> @@ -1606,9 +1605,9 @@ <form> <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p> <p> - <input id="oe_searchview_custom_public" type="checkbox"/> + <input id="oe_searchview_custom_public" type="radio" name="oe_searchview_custom_filter"/> <label for="oe_searchview_custom_public">Share with all users</label> - <input id="oe_searchview_custom_default" type="checkbox"/> + <input id="oe_searchview_custom_default" type="radio" name="oe_searchview_custom_filter"/> <label for="oe_searchview_custom_default">Use by default</label> </p> <button>Save</button> |
