summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/chrome.js13
-rw-r--r--addons/web/static/src/js/core.js25
-rw-r--r--addons/web/static/src/js/data_export.js48
-rw-r--r--addons/web/static/src/js/openerpframework.js29
-rw-r--r--addons/web/static/src/js/search.js2
-rw-r--r--addons/web/static/src/js/view_form.js131
-rw-r--r--addons/web/static/src/js/view_list.js16
7 files changed, 204 insertions, 60 deletions
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 55ab5ec073d..0cdffb0d6b0 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -534,7 +534,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
'login': 'admin',
'password': form_obj['create_admin_pwd'],
'login_successful': function() {
- var url = '/?db=' + form_obj['db_name'];
+ var url = '/web?db=' + form_obj['db_name'];
if (self.session.debug) {
url += '&debug';
}
@@ -805,10 +805,11 @@ instance.web.redirect = function(url, wait) {
var load = function() {
var old = "" + window.location;
- if (old === url) {
- window.location.reload();
- } else {
- window.location = url;
+ var old_no_hash = old.split("#")[0];
+ var url_no_hash = url.split("#")[0];
+ location.assign(url);
+ if (old_no_hash === url_no_hash) {
+ location.reload(true);
}
};
@@ -1246,6 +1247,7 @@ instance.web.Client = instance.web.Widget.extend({
this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
ev.preventDefault();
var $toggle = $(this);
+ var doc_width = $(document).width();
var $menu = $toggle.siblings('.oe_dropdown_menu');
$menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
var state = $menu.is('.oe_opened');
@@ -1254,7 +1256,6 @@ instance.web.Client = instance.web.Widget.extend({
$toggle.add($menu).toggleClass('oe_opened', !state);
if (!state) {
// Move $menu if outside window's edge
- var doc_width = $(document).width();
var offset = $menu.offset();
var menu_width = $menu.width();
var x = doc_width - offset.left - menu_width - 2;
diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js
index 8c69f86c47e..37a217a8664 100644
--- a/addons/web/static/src/js/core.js
+++ b/addons/web/static/src/js/core.js
@@ -11,31 +11,6 @@ if (typeof(console) === "undefined") {
});
}
-// shim provided by mozilla for function.bind
-if (!Function.prototype.bind) {
- Function.prototype.bind = function (oThis) {
- if (typeof this !== "function") {
- // closest thing possible to the ECMAScript 5 internal IsCallable function
- throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
- }
-
- var aArgs = Array.prototype.slice.call(arguments, 1),
- fToBind = this,
- fNOP = function () {},
- fBound = function () {
- return fToBind.apply(this instanceof fNOP && oThis
- ? this
- : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments)));
- };
-
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
-
- return fBound;
- };
-}
-
var instance = openerp;
openerp.web.core = {};
diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js
index f642f0a334d..2725e18ea6e 100644
--- a/addons/web/static/src/js/data_export.js
+++ b/addons/web/static/src/js/data_export.js
@@ -62,8 +62,21 @@ instance.web.DataExport = instance.web.Dialog.extend({
});
}).change();
+ var got_domain = this.getParent().get_active_domain().then(function (domain) {
+ if (domain === undefined) {
+ self.ids_to_export = self.getParent().get_selected_ids();
+ self.domain = self.dataset.domain;
+ }
+ else {
+ self.ids_to_export = false;
+ self.domain = domain;
+ }
+ self.on_show_domain();
+ });
+
return $.when(
got_fields,
+ got_domain,
this.rpc('/web/export/formats', {}).done(this.do_setup_export_formats),
this.show_exports_list());
},
@@ -196,6 +209,9 @@ instance.web.DataExport = instance.web.Dialog.extend({
self.showcontent(record.id);
}
},
+ on_show_domain: function() {
+ this.$el.find('tr').first().find('td').append(QWeb.render('ExportTreeView-Domain', {'record': this}));
+ },
on_show_data: function(result, after) {
var self = this;
@@ -384,27 +400,17 @@ instance.web.DataExport = instance.web.Dialog.extend({
var export_format = this.$el.find("#export_format").val();
- this.getParent().get_active_domain().then(function (domain) {
- if (domain === undefined) {
- var ids_to_export = self.getParent().get_selected_ids();
- var domain = self.dataset.domain;
- }
- else {
- var ids_to_export = false;
- var domain = domain;
- }
- instance.web.blockUI();
- self.session.get_file({
- url: '/web/export/' + export_format,
- data: {data: JSON.stringify({
- model: self.dataset.model,
- fields: exported_fields,
- ids: ids_to_export,
- domain: domain,
- import_compat: !!self.$el.find("#import_compat").val(),
- })},
- complete: instance.web.unblockUI,
- });
+ instance.web.blockUI();
+ this.session.get_file({
+ url: '/web/export/' + export_format,
+ data: {data: JSON.stringify({
+ model: this.dataset.model,
+ fields: exported_fields,
+ ids: this.ids_to_export,
+ domain: this.domain,
+ import_compat: !!this.$el.find("#import_compat").val(),
+ })},
+ complete: instance.web.unblockUI,
});
},
close: function() {
diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js
index 027452b42fb..ab10d304b30 100644
--- a/addons/web/static/src/js/openerpframework.js
+++ b/addons/web/static/src/js/openerpframework.js
@@ -138,6 +138,7 @@ var openerp = {};
throw new Error("You can only instanciate objects with the 'new' operator");
}
// All construction is actually done in the init method
+ this._super = null;
if (!initializing && this.init) {
var ret = this.init.apply(this, arguments);
if (ret) { return ret; }
@@ -385,6 +386,14 @@ var Events = openerp.Class.extend({
}
});
+/**
+ Mixin containing an event system. Events are also registered by specifying the target object
+ (the object which will receive the event when it is raised). Both the event-emitting object
+ and the target object store or reference to each other. This is used to correctly remove all
+ reference to the event handler when any of the object is destroyed (when the destroy() method
+ from ParentedMixin is called). Removing those references is necessary to avoid memory leak
+ and phantom events (events which are raised and sent to a previously destroyed object).
+*/
openerp.EventDispatcherMixin = _.extend({}, openerp.ParentedMixin, {
__eventDispatcherMixin: true,
init: function() {
@@ -808,7 +817,7 @@ var genericJsonRpc = function(fct_name, params, fct) {
return result.result;
}
}, function() {
- console.error("JsonRPC communication error", _.toArray(arguments));
+ //console.error("JsonRPC communication error", _.toArray(arguments));
var def = $.Deferred();
return def.reject.apply(def, ["communication"].concat(_.toArray(arguments)));
});
@@ -890,11 +899,29 @@ openerp.jsonpRpc = function(url, fct_name, params, settings) {
});
// append the iframe to the DOM (will trigger the first load)
$form.after($iframe);
+ if (settings.timeout) {
+ realSetTimeout(function() {
+ deferred.reject({});
+ }, settings.timeout);
+ }
return deferred;
}
});
};
+var realSetTimeout = function(fct, millis) {
+ var finished = new Date().getTime() + millis;
+ var wait = function() {
+ var current = new Date().getTime();
+ if (current < finished) {
+ setTimeout(wait, finished - current);
+ } else {
+ fct();
+ }
+ };
+ setTimeout(wait, millis);
+};
+
openerp.Session = openerp.Class.extend(openerp.PropertiesMixin, {
triggers: {
'request': 'Request sent',
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index 6241fe6da28..e086678c937 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -1547,7 +1547,7 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
context: context
}).then(function (results) {
if (_.isEmpty(results)) { return null; }
- return [{label: _.escape(self.attrs.string)}].concat(
+ return [{label: self.attrs.string}].concat(
_(results).map(function (result) {
return {
label: _.escape(result[1]),
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 324896112cc..7a0890bdcfa 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -3664,7 +3664,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
_.extend(view.options, {
addable: null,
selectable: self.multi_selection,
- sortable: false,
+ sortable: true,
import_enabled: false,
deletable: true
});
@@ -5475,7 +5475,7 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
});
this.get_selection();
if (this.options.clickable) {
- this.$el.on('click','li:not(.oe_folded)',this.on_click_stage);
+ this.$el.on('click','li[data-id]',this.on_click_stage);
}
if (this.$el.parent().is('header')) {
this.$el.after('<div class="oe_clear"/>');
@@ -5629,6 +5629,131 @@ instance.web.form.FieldMonetary = instance.web.form.FieldFloat.extend({
},
});
+/*
+ This type of field display a list of checkboxes. It works only with m2ms. This field will display one checkbox for each
+ record existing in the model targeted by the relation, according to the given domain if one is specified. Checked records
+ will be added to the relation.
+*/
+instance.web.form.FieldMany2ManyCheckBoxes = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+ className: "oe_form_many2many_checkboxes",
+ init: function() {
+ this._super.apply(this, arguments);
+ this.set("value", {});
+ this.set("records", []);
+ this.field_manager.on("view_content_has_changed", this, function() {
+ var domain = new openerp.web.CompoundDomain(this.build_domain()).eval();
+ if (! _.isEqual(domain, this.get("domain"))) {
+ this.set("domain", domain);
+ }
+ });
+ this.records_orderer = new instance.web.DropMisordered();
+ },
+ initialize_field: function() {
+ instance.web.form.ReinitializeFieldMixin.initialize_field.call(this)
+ this.on("change:domain", this, this.query_records);
+ this.set("domain", new openerp.web.CompoundDomain(this.build_domain()).eval());
+ this.on("change:records", this, this.render_value);
+ },
+ query_records: function() {
+ var self = this;
+ var model = new openerp.Model(openerp.session, this.field.relation);
+ this.records_orderer.add(model.call("search", [this.get("domain")], {"context": this.build_context()}).then(function(record_ids) {
+ return model.call("name_get", [record_ids] , {"context": self.build_context()});
+ })).then(function(res) {
+ self.set("records", res);
+ });
+ },
+ render_value: function() {
+ this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this, selected: this.get("value")}));
+ var inputs = this.$("input");
+ inputs.change(_.bind(this.from_dom, this));
+ if (this.get("effective_readonly"))
+ inputs.attr("disabled", "true");
+ },
+ from_dom: function() {
+ var new_value = {};
+ this.$("input").each(function() {
+ var elem = $(this);
+ new_value[elem.data("record-id")] = elem.attr("checked") ? true : undefined;
+ });
+ if (! _.isEqual(new_value, this.get("value")))
+ this.internal_set_value(new_value);
+ },
+ set_value: function(value) {
+ value = value || [];
+ if (value.length >= 1 && value[0] instanceof Array) {
+ value = value[0][2];
+ }
+ var formatted = {};
+ _.each(value, function(el) {
+ formatted[JSON.stringify(el)] = true;
+ });
+ this._super(formatted);
+ },
+ get_value: function() {
+ var value = _.filter(_.keys(this.get("value")), function(el) {
+ return this.get("value")[el];
+ }, this);
+ value = _.map(value, function(el) {
+ return JSON.parse(el);
+ });
+ return [commands.replace_with(value)];
+ },
+});
+
+/**
+ This field can be applied on many2many and one2many. It is a read-only field that will display a single link whose name is
+ "<number of linked records> <label of the field>". When the link is clicked, it will redirect to another act_window
+ action on the model of the relation and show only the linked records.
+
+ Widget options:
+
+ * views: The views to display in the act_window action. Must be a list of tuples whose first element is the id of the view
+ to display (or False to take the default one) and the second element is the type of the view. Defaults to
+ [[false, "tree"], [false, "form"]] .
+*/
+instance.web.form.X2ManyCounter = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+ className: "oe_form_x2many_counter",
+ init: function() {
+ this._super.apply(this, arguments);
+ this.set("value", []);
+ _.defaults(this.options, {
+ "views": [[false, "tree"], [false, "form"]],
+ });
+ },
+ render_value: function() {
+ var text = _.str.sprintf("%d %s", this.val().length, this.string);
+ this.$().html(QWeb.render("X2ManyCounter", {text: text}));
+ this.$("a").click(_.bind(this.go_to, this));
+ },
+ go_to: function() {
+ return this.view.recursive_save().then(_.bind(function() {
+ var val = this.val();
+ var context = {};
+ if (this.field.type === "one2many") {
+ context["default_" + this.field.relation_field] = this.view.datarecord.id;
+ }
+ var domain = [["id", "in", val]];
+ return this.do_action({
+ type: 'ir.actions.act_window',
+ name: this.string,
+ res_model: this.field.relation,
+ views: this.options.views,
+ target: 'current',
+ context: context,
+ domain: domain,
+ });
+ }, this));
+ },
+ val: function() {
+ var value = this.get("value") || [];
+ if (value.length >= 1 && value[0] instanceof Array) {
+ value = value[0][2];
+ }
+ return value;
+ }
+});
+
/**
* Registry of form fields, called by :js:`instance.web.FormView`.
*
@@ -5664,6 +5789,8 @@ instance.web.form.widgets = new instance.web.Registry({
'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles',
'statusbar': 'instance.web.form.FieldStatus',
'monetary': 'instance.web.form.FieldMonetary',
+ 'many2many_checkboxes': 'instance.web.form.FieldMany2ManyCheckBoxes',
+ 'x2many_counter': 'instance.web.form.X2ManyCounter',
});
/**
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index f9ec22bf712..ba7c58b3820 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -508,10 +508,15 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
var reloaded = $.Deferred();
this.$el.find('.oe_list_content').append(
this.groups.render(function () {
- if ((self.dataset.index === null || self.dataset.index === undefined) && self.records.length ||
- self.dataset.index >= self.records.length) {
+ // Keep '=='. This is not a mistake, this is a wanted behaviour to match null & undefined
+ if (self.dataset.index == null) {
+ if (self.records.length) {
self.dataset.index = 0;
+ }
+ } else if (self.dataset.index >= self.records.length) {
+ self.dataset.index = 0;
}
+
self.compute_aggregates();
reloaded.resolve();
}));
@@ -802,9 +807,11 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
},
/**
* Calculate the active domain of the list view. This should be done only
- * if the header checkbox has been checked.
+ * if the header checkbox has been checked. This is done by evaluating the
+ * search results, and then adding the dataset domain (i.e. action domain).
*/
get_active_domain: function () {
+ var self = this;
if (this.$('.oe_list_record_selector').prop('checked')) {
var search_view = this.getParent().searchview;
var search_data = search_view.build_search_data();
@@ -813,7 +820,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
contexts: search_data.contexts,
group_by_seq: search_data.groupbys || []
}).then(function (results) {
- return results.domain;
+ var domain = self.dataset.domain.concat(results.domain || []);
+ return domain
});
}
else {