diff options
Diffstat (limited to 'addons/web/static/src/js')
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 13 | ||||
| -rw-r--r-- | addons/web/static/src/js/core.js | 25 | ||||
| -rw-r--r-- | addons/web/static/src/js/data_export.js | 48 | ||||
| -rw-r--r-- | addons/web/static/src/js/openerpframework.js | 29 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 131 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 16 |
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 { |
