summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorVidhin Mehta <vme@tinyerp.com>2013-02-20 18:49:56 +0530
committerVidhin Mehta <vme@tinyerp.com>2013-02-20 18:49:56 +0530
commitca912e097585de760ceaa0dd1f0a4b942d59f254 (patch)
tree8aa89a5ab59c183c13d8cf886617a5563c08dd56 /addons/web/static/src
parent0709ca97e86a5b4895dd065f15d54410d48ba5c0 (diff)
parentf559b97ce91a4faaca92904bd66f3efd0726f5ea (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.css11
-rw-r--r--addons/web/static/src/css/base.sass7
-rw-r--r--addons/web/static/src/img/back-enable.jpgbin0 -> 28373 bytes
-rw-r--r--addons/web/static/src/js/chrome.js24
-rw-r--r--addons/web/static/src/js/formats.js10
-rw-r--r--addons/web/static/src/js/pyeval.js5
-rw-r--r--addons/web/static/src/js/search.js200
-rw-r--r--addons/web/static/src/js/view_form.js62
-rw-r--r--addons/web/static/src/js/view_list.js4
-rw-r--r--addons/web/static/src/js/view_list_editable.js2
-rw-r--r--addons/web/static/src/js/views.js65
-rw-r--r--addons/web/static/src/xml/base.xml9
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
new file mode 100644
index 00000000000..79a7098f6da
--- /dev/null
+++ b/addons/web/static/src/img/back-enable.jpg
Binary files differ
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 &gt; 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>