diff --git a/addons/web/static/src/js/compatibility.js b/addons/web/static/src/js/compatibility.js index 9fda95be633..5532c8c388e 100644 --- a/addons/web/static/src/js/compatibility.js +++ b/addons/web/static/src/js/compatibility.js @@ -186,8 +186,7 @@ function start_modules (modules) { // Monkey-patching of the ListView for backward compatibiliy of the colors and // fonts row's attributes, as they are deprecated in 9.0. ListView.include({ - load_list: function(data) { - this._super(data); + willStart: function() { if (this.fields_view.arch.attrs.colors) { this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain() .compact() @@ -208,6 +207,8 @@ ListView.include({ return [font, py.parse(py.tokenize(expr)), expr]; }).value(); } + + return this._super(data); }, /** * Returns the style for the provided record in the current view (from the diff --git a/addons/web/static/src/js/framework/model.js b/addons/web/static/src/js/framework/model.js index 2621ca490d4..8e37f14218e 100644 --- a/addons/web/static/src/js/framework/model.js +++ b/addons/web/static/src/js/framework/model.js @@ -82,29 +82,31 @@ var Model = Class.extend({ * @param {Boolean} [args.toolbar=false] get the toolbar definition */ fields_view_get: function (options) { - return this.call('fields_view_get', { view_id: options.view_id, view_type: options.view_type, context: options.context, toolbar: options.toolbar || false - }).then(function postprocess(fvg) { - var doc = $.parseXML(fvg.arch).documentElement; - fvg.arch = utils.xml_to_json(doc, (doc.nodeName.toLowerCase() !== 'kanban')); - if ('id' in fvg.fields) { - // Special case for id's - var id_field = fvg.fields.id; - id_field.original_type = id_field.type; - id_field.type = 'id'; - } - _.each(fvg.fields, function(field) { - _.each(field.views || {}, function(view) { - postprocess(view); - }); + }).then(this.postprocess_fvg.bind(this)); + }, + + postprocess_fvg: function (fvg) { + var self = this; + var doc = $.parseXML(fvg.arch).documentElement; + fvg.arch = utils.xml_to_json(doc, (doc.nodeName.toLowerCase() !== 'kanban')); + if ('id' in fvg.fields) { + // Special case for id's + var id_field = fvg.fields.id; + id_field.original_type = id_field.type; + id_field.type = 'id'; + } + _.each(fvg.fields, function(field) { + _.each(field.views || {}, function(view) { + self.postprocess_fvg(view); }); - return fvg; }); - } + return fvg; + }, }); return Model; diff --git a/addons/web/static/src/js/framework/view.js b/addons/web/static/src/js/framework/view.js index 29307b61833..b6153475b0b 100644 --- a/addons/web/static/src/js/framework/view.js +++ b/addons/web/static/src/js/framework/view.js @@ -14,10 +14,11 @@ var View = Widget.extend({ this.do_action(action_data); } }, + defaults: { + action: {}, + }, // name displayed in view switchers display_name: '', - // define a view type for each view to allow automatic call to fields_view_get. - view_type: undefined, // used by views that need a searchview. searchable: true, // used by views that need a searchview but don't want it to be displayed. @@ -27,57 +28,28 @@ var View = Widget.extend({ multi_record: true, // icon is the font-awesome icon to display in the view switcher icon: 'fa-question', - init: function(parent, dataset, view_id, options) { + // whether or not the view requires the model's fields to render itself + require_fields: false, + init: function(parent, dataset, fields_view, options) { this._super(parent); this.ViewManager = parent; this.dataset = dataset; - this.view_id = view_id; - this.set_default_options(options); + this.model = dataset.model; + this.fields_view = fields_view; + this.options = _.defaults({}, options, this.defaults); }, - start: function () { - return this.load_view(); - }, - load_view: function() { - var self = this; - var view_loaded_def; - if (this.embedded_view) { - view_loaded_def = $.Deferred(); - $.async_when().done(function() { - view_loaded_def.resolve(self.embedded_view); - }); - } else { - if (! this.view_type) - console.warn("view_type is not defined", this); - view_loaded_def = this.dataset._model.fields_view_get({ - "view_id": this.view_id, - "view_type": this.view_type, - "toolbar": !!this.options.sidebar, - "context": this.dataset.get_context(), - }); - } - return this.alive(view_loaded_def).then(function(r) { - self.fields_view = r; - // add css classes that reflect the (absence of) access rights - self.$el.addClass('oe_view') - .toggleClass('oe_cannot_create', !self.is_action_enabled('create')) - .toggleClass('oe_cannot_edit', !self.is_action_enabled('edit')) - .toggleClass('oe_cannot_delete', !self.is_action_enabled('delete')); - return $.when(self.view_loading(r)).then(function() { - self.trigger('view_loaded', r); - }); - }); - }, - view_loading: function(r) { - }, - set_default_options: function(options) { - this.options = options || {}; - _.defaults(this.options, { - // All possible views options should be defaulted here - $sidebar: null, - sidebar_id: null, - action: null, - action_views_ids: {} - }); + /** + * Triggers event 'view_loaded'. + * Views extending start() must call this.super() once they are ready. + * @return {Deferred} + */ + start: function() { + // add css classes that reflect the (absence of) access rights + this.$el.addClass('oe_view') + .toggleClass('oe_cannot_create', !this.is_action_enabled('create')) + .toggleClass('oe_cannot_edit', !this.is_action_enabled('edit')) + .toggleClass('oe_cannot_delete', !this.is_action_enabled('delete')); + return this._super().then(this.trigger.bind(this, 'view_loaded')); }, /** * Fetches and executes the action identified by ``action_data``. @@ -87,7 +59,7 @@ var View = Widget.extend({ * @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``) * @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'`` * @param {Object} [action_data.context=null] additional action context, to add to the current context - * @param {instance.web.DataSet} dataset a dataset object used to communicate with the server + * @param {DataSet} dataset a dataset object used to communicate with the server * @param {Object} [record_id] the identifier of the object on which the action is to be applied * @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action) */ @@ -158,47 +130,16 @@ var View = Widget.extend({ return dataset.exec_workflow(record_id, action_data.name).then(handler); } }, - /** - * Directly set a view to use instead of calling fields_view_get. This method must - * be called before start(). When an embedded view is set, underlying implementations - * of instance.web.View must use the provided view instead of any other one. - * - * @param embedded_view A view. - */ - set_embedded_view: function(embedded_view) { - this.embedded_view = embedded_view; - }, do_show: function () { this._super(); core.bus.trigger('view_shown', this); }, - is_active: function () { - return this.ViewManager.active_view.controller === this; - }, - /** - * Wraps fn to only call it if the current view is the active one. If the - * current view is not active, doesn't call fn. - * - * fn can not return anything, as a non-call to fn can't return anything - * either - * - * @param {Function} fn function to wrap in the active guard - */ - guard_active: function (fn) { - var self = this; - return function () { - if (self.is_active()) { - fn.apply(self, arguments); - } - }; - }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { this.getParent().do_push_state(state); } }, do_load_state: function (state, warm) { - }, /** * This function should render the buttons of the view, set this.$buttons to diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js index 00826c34eb9..0996f2c080c 100644 --- a/addons/web/static/src/js/view_manager.js +++ b/addons/web/static/src/js/view_manager.js @@ -3,7 +3,6 @@ odoo.define('web.ViewManager', function (require) { var ControlPanelMixin = require('web.ControlPanelMixin'); var core = require('web.core'); -var data = require('web.data'); var framework = require('web.framework'); var Model = require('web.DataModel'); var pyeval = require('web.pyeval'); @@ -17,14 +16,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, { template: "ViewManager", /** * @param {Object} [dataset] - * @param {Array} [views] List of [view_id, view_type] + * @param {Array} [views] List of [view_id, view_type[, fields_view]] * @param {Object} [flags] various boolean describing UI state */ init: function(parent, dataset, views, flags, options) { var self = this; this._super(parent); - this.action = options && options.action; + this.action = options && options.action || {}; this.action_manager = options && options.action_manager; this.flags = flags || {}; this.dataset = dataset; @@ -33,7 +32,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { this.view_stack = []; // used for breadcrumbs this.active_view = null; this.registry = core.view_registry; - this.title = this.action && this.action.name; + this.title = this.action.name; this.is_in_DOM = false; // used to know if the view manager is attached in the DOM _.each(views, function (view) { var view_type = view[1] || view.view_type; @@ -44,45 +43,42 @@ var ViewManager = Widget.extend(ControlPanelMixin, { } var view_label = View.prototype.display_name; var view_descr = { - controller: null, - options: view.options || {}, - view_id: view[0] || view.view_id, - type: view_type, - label: view_label, - embedded_view: view.embedded_view, - title: self.title, - button_label: _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}), - multi_record: View.prototype.multi_record, - accesskey: View.prototype.accesskey, - icon: View.prototype.icon, - }; + accesskey: View.prototype.accesskey, + button_label: _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}), + controller: null, + fields_view: view[2] || view.fields_view, + embedded_view: view.embedded_view, + icon: View.prototype.icon, + label: view_label, + multi_record: View.prototype.multi_record, + options: view.options || {}, + require_fields: View.prototype.require_fields, + title: self.title, + type: view_type, + view_id: view[0] || view.view_id, + }; self.view_order.push(view_descr); self.views[view_type] = view_descr; }); this.first_view = this.views[options && options.view_type]; // view to open first - - // Listen to event 'switch_view' indicating that the VM must now display view wiew_type - this.on('switch_view', this, function(view_type) { - if (view_type === 'form' && this.active_view && this.active_view.type === 'form') { - this._display_view(view_type); - } else { - this.switch_mode(view_type); - } - }); + this.default_view = this.get_default_view(); + }, + willStart: function () { + var views_def; + var first_view_to_display = this.first_view || this.default_view; + if (!first_view_to_display.fields_view || (this.flags.search_view && !this.search_fields_view)) { + views_def = this.load_views(first_view_to_display.require_fields); + } + return $.when(this._super(), views_def); }, /** - * @returns {jQuery.Deferred} initial view loading promise + * @return {Deferred} initial view and search view (if any) loading promise */ start: function() { var self = this; - this._super(); - - var views_ids = {}; _.each(this.views, function (view) { - views_ids[view.type] = view.view_id; view.options = _.extend({ - action : self.action, - action_views_ids : views_ids, + action: self.action, }, self.flags, self.flags[view.type], view.options); view.$container = self.$(".oe-view-manager-view-" + view.type); }); @@ -99,16 +95,58 @@ var ViewManager = Widget.extend(ControlPanelMixin, { // If a non multi-record first_view is given, switch to it but first push the default_view // to the view_stack to complete the breadcrumbs - var default_view = this.get_default_view(); - if (this.first_view && !this.first_view.multi_record && default_view.multi_record) { - default_view.controller = this.create_view(default_view); - this.view_stack.push(default_view); + if (this.first_view && !this.first_view.multi_record && this.default_view.multi_record) { + this.default_view.controller = this.create_view(this.default_view); + this.view_stack.push(this.default_view); } - var view_to_load = this.first_view || default_view; + var view_to_load = this.first_view || this.default_view; var options = this.flags[view_to_load] && this.flags[view_to_load].options; var main_view_loaded = this.switch_mode(view_to_load.type, options); - return $.when(main_view_loaded, this.search_view_loaded); + return $.when(this._super(), main_view_loaded, this.search_view_loaded); + }, + /** + * Loads all missing field_views of views in this.views and the search view. + * + * @param {Boolean} [load_fields] whether or not to load the fields as well + * @return {Deferred} + */ + load_views: function (load_fields) { + var self = this; + var views = []; + _.each(this.views, function (view) { + if (!view.fields_view) { + views.push([view.view_id, view.type]); + } + }); + if (this.flags.search_view && !this.search_fields_view) { + var searchview_id = this.action.search_view_id && this.action.search_view_id[0]; + views.push([searchview_id || false, 'search']); + } + var options = { + action_id: this.action.id, + load_fields: load_fields && !this.fields_get_def, + toolbar: this.flags.sidebar, + }; + return this.dataset._model.call('load_views', { + views: views, + options: options, + context: this.dataset.get_context(), + }).then(function (result) { + _.each(result.fields_views, function (fields_view, view_type) { + if (view_type === 'search') { + self.search_fields_view = self.dataset._model.postprocess_fvg(fields_view); + } else { + self.views[view_type].fields_view = self.dataset._model.postprocess_fvg(fields_view); + } + }); + if (result.filters) { + self.search_filters = result.filters; + } + if (result.fields) { + self.fields_get_def = $.when(result.fields); + } + }); }, /** * Returns the default view with the following fallbacks: @@ -120,6 +158,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, { get_default_view: function() { return this.views[this.flags.default_view || this.view_order[0].type]; }, + get_fields: function() { + if (!this.fields_get_def) { + this.fields_get_def = this.dataset._model.call('fields_get', { + context: this.dataset.get_context() + }); + } + return this.fields_get_def; + }, switch_mode: function(view_type, view_options) { var self = this; var view = this.views[view_type]; @@ -131,51 +177,60 @@ var ViewManager = Widget.extend(ControlPanelMixin, { this.currently_switching = true; // prevent overlapping switches } - if (view.multi_record) { - this.view_stack = []; - } else if (this.view_stack.length > 0 && !(_.last(this.view_stack).multi_record)) { - // Replace the last view by the new one if both are mono_record - this.view_stack.pop(); - } - this.view_stack.push(view); - this.active_view = view; - - if (!view.loaded) { - if (!view.controller) { - view.controller = this.create_view(view, view_options); - } - view.$fragment = $('
'); - view.loaded = view.controller.appendTo(view.$fragment).done(function () { - // Remove the unnecessary outer div - view.$fragment = view.$fragment.contents(); - self.trigger("controller_inited", view.type, view.controller); - }); + // Ensure that the fields_view has been loaded + var views_def; + if (!view.fields_view) { + views_def = this.load_views(view.require_fields); } - // Call do_search on the searchview to compute domains, contexts and groupbys - if (this.search_view_loaded && - this.flags.auto_search && - view.controller.searchable !== false) { - this.active_search = $.Deferred(); - $.when(this.search_view_loaded, view.loaded).done(function() { - self.searchview.do_search(); - }); - } - - return $.when(view.loaded, this.active_search) - .then(function() { - return self._display_view(view_options, old_view).then(function() { - self.trigger('switch_mode', view_type, view_options); - }); - }).fail(function(e) { - if (!(e && e.code === 200 && e.data.exception_type)) { - self.do_warn(_t("Error"), view.controller.display_name + _t(" view couldn't be loaded")); - } - // Restore internal state - self.active_view = old_view; + return $.when(views_def).then(function () { + if (view.multi_record) { + self.view_stack = []; + } else if (self.view_stack.length > 0 && !(_.last(self.view_stack).multi_record)) { + // Replace the last view by the new one if both are mono_record self.view_stack.pop(); - }).always(function () { - self.currently_switching = false; + } + self.view_stack.push(view); + + self.active_view = view; + + if (!view.loaded) { + if (!view.controller) { + view.controller = self.create_view(view, view_options); + } + view.$fragment = $('
'); + view.loaded = view.controller.appendTo(view.$fragment).done(function () { + // Remove the unnecessary outer div + view.$fragment = view.$fragment.contents(); + self.trigger("controller_inited", view.type, view.controller); + }); + } + + // Call do_search on the searchview to compute domains, contexts and groupbys + if (self.search_view_loaded && + self.flags.auto_search && + view.controller.searchable !== false) { + self.active_search = $.Deferred(); + $.when(self.search_view_loaded, view.loaded).done(function() { + self.searchview.do_search(); + }); + } + + return $.when(view.loaded, self.active_search) + .then(function() { + return self._display_view(view_options, old_view).then(function() { + self.trigger('switch_mode', view_type, view_options); + }); + }).fail(function(e) { + if (!(e && e.code === 200 && e.data.exception_type)) { + self.do_warn(_t("Error"), view.controller.display_name + _t(" view couldn't be loaded")); + } + // Restore internal state + self.active_view = old_view; + self.view_stack.pop(); + }); + }).always(function () { + self.currently_switching = false; }); }, _display_view: function (view_options, old_view) { @@ -218,17 +273,12 @@ var ViewManager = Widget.extend(ControlPanelMixin, { var self = this; var View = this.registry.get(view.type); var options = _.clone(view.options); - - if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline')) || + if (view.type === "form" && ((this.action.target === 'new' || this.action.target === 'inline') || (view_options && view_options.mode === 'edit'))) { options.initial_mode = options.initial_mode || 'edit'; } - var controller = new View(this, this.dataset, view.view_id, options); - - if (view.embedded_view) { - controller.set_embedded_view(view.embedded_view); - } + var controller = new View(this, this.dataset, view.fields_view, options); controller.on('switch_mode', this, this.switch_mode.bind(this)); controller.on('history_back', this, function () { @@ -306,12 +356,6 @@ var ViewManager = Widget.extend(ControlPanelMixin, { } return this.active_view.control_elements; }, - /** - * @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; - }, /** * Sets up the current viewmanager's search view. * Sets $searchview and $searchview_buttons in control_elements to send to the ControlPanel @@ -325,28 +369,25 @@ var ViewManager = Widget.extend(ControlPanelMixin, { this.searchview.destroy(); } - var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; - var search_defaults = {}; - - var context = this.action ? this.action.context : []; + var context = this.action.context || []; _.each(context, function (value, key) { var match = /^search_default_(.*)$/.exec(key); if (match) { search_defaults[match[1]] = value; } }); - - var options = { hidden: this.flags.search_view === false, disable_custom_filters: this.flags.search_disable_custom_filters, $buttons: $("
"), action: this.action, + search_defaults: search_defaults, + filters: this.search_filters, }; // Instantiate the SearchView, but do not append it nor its buttons to the DOM as this will // be done later, simultaneously to all other ControlPanel elements - this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options); + this.searchview = new SearchView(this, this.dataset, this.search_fields_view, options); this.searchview.on('search_data', this, this.search.bind(this)); return $.when(this.searchview.appendTo($("
"))).done(function() { diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js index 7364eeec203..1b29a2819b9 100644 --- a/addons/web/static/src/js/views/form_common.js +++ b/addons/web/static/src/js/views/form_common.js @@ -769,7 +769,7 @@ var AbstractField = FormWidget.extend(FieldInterface, { on_translate: function() { var self = this; var trans = new data.DataSet(this, 'ir.translation'); - return trans.call_button('translate_fields', [this.view.dataset.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).done(function(r) { + return trans.call_button('translate_fields', [this.view.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).done(function(r) { self.do_action(r); }); }, @@ -793,7 +793,6 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager * options: * -readonly: only applicable when not in creation mode, default to false * - alternative_form_view - * - view_id * - write_function * - read_function * - create_function @@ -814,7 +813,6 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager this.context = options.context || {}; this.options = _.extend(this.options || {}, options || {}); - this.on('closed', this, this.select); this.on_selected = options.on_selected || (function() {}); }, @@ -838,6 +836,8 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager }; this.dataset.parent_view = this.options.parent_view; this.dataset.child_name = this.options.child_name; + + this.on('closed', this, this.select); }, select: function() { @@ -898,33 +898,41 @@ var FormViewDialog = ViewDialog.extend({ open: function() { var self = this; - self._super(); - self.init_dataset(); + var _super = this._super.bind(this); + this.init_dataset(); - if (self.res_id) { - self.dataset.ids = [self.res_id]; - self.dataset.index = 0; + if (this.res_id) { + this.dataset.ids = [this.res_id]; + this.dataset.index = 0; } else { - self.dataset.index = null; + this.dataset.index = null; } - var options = _.clone(self.options.form_view_options) || {}; - if (self.res_id !== null) { - options.initial_mode = self.options.readonly ? "view" : "edit"; + var options = _.clone(this.options.form_view_options) || {}; + if (this.res_id !== null) { + options.initial_mode = this.options.readonly ? "view" : "edit"; } _.extend(options, { - $buttons: self.$buttons, + $buttons: this.$buttons, }); var FormView = core.view_registry.get('form'); - self.view_form = new FormView(self, self.dataset, self.options.view_id || false, options); - if (self.options.alternative_form_view) { - self.view_form.set_embedded_view(self.options.alternative_form_view); + var fields_view_def; + if (this.options.alternative_form_view) { + fields_view_def = $.when(this.options.alternative_form_view); + } else { + fields_view_def = this.dataset._model.fields_view_get({ + view_id: this.options.view_id || false, + view_type: 'form', + context: this.dataset.get_context(), + toolbar: false, + }); } - - self.do_hide(); - self.view_form.appendTo(self.$el); - self.view_form.on("form_view_loaded", self, function() { - self.view_form.do_show().then(function() { - self.do_show(); + fields_view_def.then(function (fields_view) { + self.view_form = new FormView(self, self.dataset, fields_view, options); + var fragment = document.createDocumentFragment(); + self.view_form.appendTo(fragment).then(function () { + self.view_form.do_show().then(function() { + _super().$el.append(fragment); + }); }); }); @@ -965,49 +973,64 @@ var SelectCreateDialog = ViewDialog.extend({ }, open: function() { - this._super(); - this.init_dataset(); - if (this.options.initial_view == "search") { - var context = pyeval.sync_eval_domains_and_contexts({ - domains: [], - contexts: [this.context] - }).context; - var search_defaults = {}; - _.each(context, function (value_, key) { - var match = /^search_default_(.*)$/.exec(key); - if (match) { - search_defaults[match[1]] = value_; - } - }); - this.setup(search_defaults); - } else { // "form" - this.create_edit_record(); + if(this.options.initial_view !== "search") { + return this.create_edit_record(); } + + var self = this; + var _super = this._super.bind(this); + this.init_dataset(); + var context = pyeval.sync_eval_domains_and_contexts({ + domains: [], + contexts: [this.context] + }).context; + var search_defaults = {}; + _.each(context, function (value_, key) { + var match = /^search_default_(.*)$/.exec(key); + if (match) { + search_defaults[match[1]] = value_; + } + }); + this.dataset._model.call('load_views', { + views: [[false, 'list'], [false, 'search']], + context: this.dataset.get_context(), + }).then(function (result) { + var fields_views = _.mapObject(result.fields_views, self.dataset._model.postprocess_fvg); + self.setup(search_defaults, fields_views).then(function (fragment) { + _super().$el.append(fragment); + }); + }); return this; }, - setup: function(search_defaults) { + setup: function(search_defaults, fields_views) { var self = this; if (this.searchview) { this.searchview.destroy(); } - var $header = $('
').addClass('o_modal_header').appendTo(this.$el); + var fragment = document.createDocumentFragment(); + var $header = $('
').addClass('o_modal_header').appendTo(fragment); var $pager = $('
').addClass('o_pager').appendTo($header); - var $buttons = $('
').addClass('o_search_options').appendTo($header); - this.searchview = new SearchView(this, this.dataset, false, search_defaults, {$buttons: $buttons}); - this.searchview.on('search_data', self, function(domains, contexts, groupbys) { - if (self.initial_ids) { - self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]), - contexts.concat(self.context), groupbys); - self.initial_ids = undefined; + var options = { + $buttons: $('
').addClass('o_search_options').appendTo($header), + search_defaults: search_defaults, + }; + + this.searchview = new SearchView(this, this.dataset, fields_views.search, options); + this.searchview.on('search_data', this, function(domains, contexts, groupbys) { + if (this.initial_ids) { + this.do_search(domains.concat([[["id", "in", this.initial_ids]], this.domain]), + contexts.concat(this.context), groupbys); + this.initial_ids = undefined; } else { - self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys); + this.do_search(domains.concat([this.domain]), contexts.concat(this.context), groupbys); } }); - this.searchview.prependTo($header).done(function() { + return this.searchview.prependTo($header).then(function() { self.searchview.toggle_visibility(true); + self.view_list = new SelectCreateListView(self, - self.dataset, false, + self.dataset, fields_views.list, _.extend({'deletable': false, 'selectable': !self.options.disable_multiple_selection, 'import_enabled': false, @@ -1019,17 +1042,6 @@ var SelectCreateDialog = ViewDialog.extend({ e.cancel = true; }); self.view_list.popup = self; - self.view_list.appendTo(self.$el).then(function() { - self.view_list.do_show(); - self.view_list.render_pager($pager); - }).then(function() { - if (self.options.initial_facet) { - self.searchview.query.reset([self.options.initial_facet], { - preventSearch: true, - }); - } - self.searchview.do_search(); - }); var buttons = [ {text: _t("Cancel"), classes: "btn-default o_form_button_cancel", close: true} @@ -1045,6 +1057,19 @@ var SelectCreateDialog = ViewDialog.extend({ }}); } self.set_buttons(buttons); + + return self.view_list.appendTo(fragment).then(function() { + self.view_list.do_show(); + self.view_list.render_pager($pager); + if (self.options.initial_facet) { + self.searchview.query.reset([self.options.initial_facet], { + preventSearch: true, + }); + } + self.searchview.do_search(); + + return fragment; + }); }); }, do_search: function(domains, contexts, groupbys) { @@ -1060,8 +1085,8 @@ var SelectCreateDialog = ViewDialog.extend({ this.$footer.find(".o_selectcreatepopup_search_select").prop('disabled', this.selected_ids.length <= 0); }, create_edit_record: function() { - new FormViewDialog(this.getParent(), this.options).open(); this.close(); + return new FormViewDialog(this.__parentedParent, this.options).open(); }, }); diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js index 79318721efa..3702b67f976 100644 --- a/addons/web/static/src/js/views/form_relational_widgets.js +++ b/addons/web/static/src/js/views/form_relational_widgets.js @@ -12,7 +12,6 @@ var Model = require('web.DataModel'); var session = require('web.session'); var utils = require('web.utils'); var ViewManager = require('web.ViewManager'); -var formats = require('web.formats'); var _t = core._t; var QWeb = core.qweb; @@ -752,11 +751,9 @@ var FieldX2Many = AbstractManyField.extend({ var view = { view_id: false, view_type: view_type === "tree" ? "list" : view_type, - options: {} + fields_view: self.field.views && self.field.views[view_type], + options: {}, }; - if (self.field.views && self.field.views[view_type]) { - view.embedded_view = self.field.views[view_type]; - } if(view.view_type === "list") { _.extend(view.options, { action_buttons: false, // to avoid 'Save' and 'Discard' buttons to appear in X2M fields @@ -825,9 +822,7 @@ var FieldX2Many = AbstractManyField.extend({ }); }); $.async_when().done(function () { - if (!self.isDestroyed()) { - self.viewmanager.appendTo(self.$el); - } + self.alive( self.viewmanager.appendTo(self.$el)); }); return def; }, @@ -900,7 +895,7 @@ var X2ManyDataSet = data.BufferedDataSet.extend({ var X2ManyViewManager = ViewManager.extend({ init: function(parent, dataset, views, flags, x2many_views) { // By default, render buttons and pager in X2M fields, but no sidebar - var flags = _.extend({}, flags, { + flags = _.extend({}, flags, { headless: false, search_view: false, action_buttons: true, @@ -926,7 +921,6 @@ var X2ManyViewManager = ViewManager.extend({ res_model: self.x2m.field.relation, res_id: id, context: self.x2m.build_context(), - title: _t("Open: ") + self.x2m.string, create_function: function(data, options) { return self.x2m.data_create(data, options); @@ -953,7 +947,6 @@ var X2ManyViewManager = ViewManager.extend({ var X2ManyListView = ListView.extend({ is_valid: function () { - var self = this; if (!this.fields_view || !this.editable()){ return true; } @@ -967,7 +960,7 @@ var X2ManyListView = ListView.extend({ field.no_rerender = true; current_values[field.name] = field.get('value'); }); - var cached_records = _.filter(this.dataset.cache, function(item){return !_.isEmpty(item.values)}); + var cached_records = _.filter(this.dataset.cache, function(item){return !_.isEmpty(item.values);}); var valid = _.every(cached_records, function(record){ _.each(fields, function(field){ var value = record.values[field.name]; @@ -1019,14 +1012,16 @@ var X2ManyList = ListView.List.extend({ .click(function (e) { e.preventDefault(); e.stopPropagation(); - // FIXME: there should also be an API for that one - if (self.view.editor.form.__blur_timeout) { - clearTimeout(self.view.editor.form.__blur_timeout); - self.view.editor.form.__blur_timeout = false; + var def; + if (self.view.editable()) { + // FIXME: there should also be an API for that one + if (self.view.editor.form.__blur_timeout) { + clearTimeout(self.view.editor.form.__blur_timeout); + self.view.editor.form.__blur_timeout = false; + } + def = self.view.save_edition(); } - self.view.save_edition().done(function () { - self.view.do_add_record(); - }); + $.when(def).done(self.view.do_add_record.bind(self)); })); var $padding = this.$current.find('tr:not([data-id]):first'); @@ -1041,12 +1036,12 @@ var X2ManyList = ListView.List.extend({ var One2ManyListView = X2ManyListView.extend({ _template: 'One2Many.listview', - init: function (parent, dataset, view_id, options) { - var self = this; - this._super(parent, dataset, view_id, _.extend(options || {}, { + init: function () { + this._super.apply(this, arguments); + this.options = _.extend(this.options, { GroupsType: One2ManyGroups, ListType: X2ManyList - })); + }); this.on('edit:after', this, this.proxy('_after_edit')); this.on('save:before cancel:before', this, this.proxy('_before_unedit')); @@ -1293,15 +1288,12 @@ var FieldOne2Many = FieldX2Many.extend({ }, }); -/** - * @class - * @extends instance.web.ListView - */ -var Many2ManyListView = X2ManyListView.extend(/** @lends instance.web.form.Many2ManyListView# */{ - init: function (parent, dataset, view_id, options) { - this._super(parent, dataset, view_id, _.extend(options || {}, { +var Many2ManyListView = X2ManyListView.extend({ + init: function () { + this._super.apply(this, arguments); + this.options = _.extend(this.options, { ListType: X2ManyList, - })); + }); }, do_add_record: function () { var self = this; @@ -1323,7 +1315,7 @@ var Many2ManyListView = X2ManyListView.extend(/** @lends instance.web.form.Many2 do_activate_record: function(index, id) { var self = this; var pop = new common.FormViewDialog(this, { - res_model: this.dataset.model, + res_model: this.model, res_id: id, context: this.x2m.build_context(), title: _t("Open: ") + this.x2m.string, diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js index 96ae03eab2f..8e973cfd779 100644 --- a/addons/web/static/src/js/views/form_view.js +++ b/addons/web/static/src/js/views/form_view.js @@ -16,45 +16,29 @@ var _lt = core._lt; var QWeb = core.qweb; var Class = core.Class; -/** @namespace */ -// instance.web.form = instance.web.form || {}; - /** * Properties: * - actual_mode: always "view", "edit" or "create". Read-only property. Determines * the mode used by the view. */ var FormView = View.extend(common.FieldManagerMixin, { - /** - * Indicates that this view is not searchable, and thus that no search - * view should be displayed (if there is one active). - */ + accesskey: "F", + defaults: _.extend({}, View.prototype.defaults, { + not_interactible_on_create: false, + initial_mode: "view", + disable_autofocus: false, + footer_to_buttons: false, + }), + display_name: _lt('Form'), + icon: 'fa-edit', + multi_record: false, + // Indicates that this view is not searchable, and thus that no search view should be displayed. searchable: false, template: "FormView", - display_name: _lt('Form'), - view_type: "form", - multi_record: false, - accesskey: "F", - icon: 'fa-edit', - /** - * @constructs instance.web.FormView - * @extends instance.web.View - * - * @param {instance.web.Session} session the current openerp session - * @param {instance.web.DataSet} dataset the dataset this view will work with - * @param {String} view_id the identifier of the OpenERP view object - * - * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance - */ - init: function(parent, dataset, view_id, options) { + + init: function() { var self = this; - this._super(parent); - this.ViewManager = parent; - this.set_default_options(options); - this.dataset = dataset; - this.model = dataset.model; - this.view_id = view_id || false; - this.fields_view = {}; + this._super.apply(this, arguments); this.fields = {}; this.fields_order = []; this.datarecord = {}; @@ -67,12 +51,6 @@ var FormView = View.extend(common.FieldManagerMixin, { this.widgets_registry = core.form_custom_registry; this.has_been_loaded = $.Deferred(); this.translatable_fields = []; - _.defaults(this.options, { - "not_interactible_on_create": false, - "initial_mode": "view", - "disable_autofocus": false, - "footer_to_buttons": false, - }); this.is_initialized = $.Deferred(); this.mutating_mutex = new utils.Mutex(); this.save_list = []; @@ -104,34 +82,13 @@ var FormView = View.extend(common.FieldManagerMixin, { }); }); }, - view_loading: function(r) { - return this.load_form(r); - }, - destroy: function() { - _.each(this.get_widgets(), function(w) { - w.off('focused blurred'); - w.destroy(); - }); - if (this.$el) { - this.$el.off('.formBlur'); - } - this._super(); - }, - load_form: function(data) { + start: function() { var self = this; - if (!data) { - throw new Error(_t("No data provided.")); - } - if (this.arch) { - throw "Form view does not support multiple calls to load_form"; - } - this.fields_order = []; - this.fields_view = data; this.rendering_engine.set_fields_registry(this.fields_registry); this.rendering_engine.set_tags_registry(this.tags_registry); this.rendering_engine.set_widgets_registry(this.widgets_registry); - this.rendering_engine.set_fields_view(data); + this.rendering_engine.set_fields_view(this.fields_view); var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); this.rendering_engine.render_to($dest); @@ -143,7 +100,7 @@ var FormView = View.extend(common.FieldManagerMixin, { // Add bounce effect on button 'Edit' when click on readonly page view. this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) { - if(self.get("actual_mode") == "view" && self.$buttons && !$(e.target).is('[data-toggle]')) { + if(self.get("actual_mode") === "view" && self.$buttons && !$(e.target).is('[data-toggle]')) { var $button = self.$buttons.find(".oe_form_button_edit"); $button.openerpBounce(); e.stopPropagation(); @@ -152,14 +109,23 @@ var FormView = View.extend(common.FieldManagerMixin, { }); //bounce effect on red button when click on statusbar. this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) { - if((self.get("actual_mode") == "view")) { + if((self.get("actual_mode") === "view")) { var $button = self.$el.find(".oe_highlight:not(.o_form_invisible)").css({'float':'left','clear':'none'}); $button.openerpBounce(); e.stopPropagation(); } }); - this.trigger('form_view_loaded', data); - return $.when(); + return this._super(); + }, + destroy: function() { + _.each(this.get_widgets(), function(w) { + w.off('focused blurred'); + w.destroy(); + }); + if (this.$el) { + this.$el.off('.formBlur'); + } + this._super(); }, /** * Render the buttons according to the FormView.buttons template and add listeners on it. @@ -181,14 +147,10 @@ var FormView = View.extend(common.FieldManagerMixin, { // Show or hide the buttons according to the view mode this.toggle_buttons(); - this.$buttons.on('click', '.oe_form_button_create', - this.guard_active(this.on_button_create)); - this.$buttons.on('click', '.oe_form_button_edit', - this.guard_active(this.on_button_edit)); - this.$buttons.on('click', '.oe_form_button_save', - this.guard_active(this.on_button_save)); - this.$buttons.on('click', '.oe_form_button_cancel', - this.guard_active(this.on_button_cancel)); + this.$buttons.on('click', '.oe_form_button_create', this.on_button_create); + this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit); + this.$buttons.on('click', '.oe_form_button_save', this.on_button_save); + this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel); if ($node) { this.$buttons.appendTo($node); @@ -311,10 +273,8 @@ var FormView = View.extend(common.FieldManagerMixin, { } }, /** - * - * @param {Object} [options] - * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view". - * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record + * @param {Boolean} [options.mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view". + * @param {Boolean} [options.reload=true] whether the form should reload its content on show, or use the currently loaded record * @return {$.Deferred} */ do_show: function (options) { @@ -521,7 +481,7 @@ var FormView = View.extend(common.FieldManagerMixin, { // In case of a o2m virtual id, we should pass an empty ids list ids.push(self.datarecord.id); } - def = self.alive(new Model(self.dataset.model).call( + def = self.alive(self.dataset.call( "onchange", [ids, values, trigger_field_name, onchange_specs, context])); } this.onchanges_mutex.exec(function(){ @@ -1199,7 +1159,7 @@ var FormView = View.extend(common.FieldManagerMixin, { all_users = d.$el.find('#formview_default_all').is(':checked'); new data.DataSet(self, 'ir.values').call( 'set_default', [ - self.dataset.model, + self.model, field_to_set, self.fields[field_to_set].get_value(), all_users, diff --git a/addons/web/static/src/js/views/graph_view.js b/addons/web/static/src/js/views/graph_view.js index 8a604640fc9..1df6cd14063 100644 --- a/addons/web/static/src/js/views/graph_view.js +++ b/addons/web/static/src/js/views/graph_view.js @@ -6,7 +6,6 @@ odoo.define('web.GraphView', function (require) { var core = require('web.core'); var GraphWidget = require('web.GraphWidget'); -var Model = require('web.DataModel'); var View = require('web.View'); var _lt = core._lt; @@ -17,21 +16,30 @@ var GraphView = View.extend({ className: 'oe_graph', display_name: _lt('Graph'), icon: 'fa-bar-chart', - view_type: 'graph', + require_fields: true, - init: function(parent, dataset, view_id, options) { - this._super(parent, dataset, view_id, options); + init: function () { + this._super.apply(this, arguments); - this.model = new Model(dataset.model, {group_by_no_leaf: true}); this.measures = []; this.active_measure = '__count__'; this.initial_groupbys = []; this.widget = undefined; }, - start: function () { - var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()}) - .then(this.prepare_fields.bind(this)); - + willStart: function () { + var self = this; + var load_fields = this.ViewManager.get_fields().then(this.prepare_fields.bind(this)); + this.fields_view.arch.children.forEach(function (field) { + var name = field.attrs.name; + if (field.attrs.interval) { + name += ':' + field.attrs.interval; + } + if (field.attrs.type === 'measure') { + self.active_measure = name; + } else { + self.initial_groupbys.push(name); + } + }); return $.when(this._super(), load_fields); }, /** @@ -59,20 +67,6 @@ var GraphView = View.extend({ $(li).toggleClass('selected', $(li).data('field') === self.active_measure); }); }, - view_loading: function (fvg) { - var self = this; - fvg.arch.children.forEach(function (field) { - var name = field.attrs.name; - if (field.attrs.interval) { - name += ':' + field.attrs.interval; - } - if (field.attrs.type === 'measure') { - self.active_measure = name; - } else { - self.initial_groupbys.push(name); - } - }); - }, do_show: function () { this.do_push_state({}); return this._super(); @@ -92,7 +86,7 @@ var GraphView = View.extend({ do_search: function (domain, context, group_by) { if (!this.widget) { this.initial_groupbys = context.graph_groupbys || this.initial_groupbys; - this.widget = new GraphWidget(this, this.dataset.model, { + this.widget = new GraphWidget(this, this.model, { measure: context.graph_measure || this.active_measure, mode: context.graph_mode || this.active_mode, domain: domain, diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index b8d0c569609..d42d34a98d2 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -32,33 +32,33 @@ var row_decoration = [ 'decoration-warning' ]; -var ListView = View.extend( /** @lends instance.web.ListView# */ { +var ListView = View.extend({ _template: 'ListView', accesskey: 'L', - icon: 'fa-list-ul', display_name: _lt('List'), - defaults: { + defaults: _.extend({}, View.prototype.defaults, { // records can be selected one by one - 'selectable': true, + selectable: true, // list rows can be deleted - 'deletable': false, + deletable: false, // whether the column headers should be displayed - 'header': true, + header: true, // display addition button, with that label - 'addable': _lt("Create"), + addable: _lt("Create"), // whether the list view can be sorted, note that once a view has been // sorted it can not be reordered anymore - 'sortable': true, + sortable: true, // whether the view rows can be reordered (via vertical drag & drop) - 'reorderable': true, - 'action_buttons': true, + reorderable: true, + action_buttons: true, //whether the editable property of the view has to be disabled - 'disable_editable_mode': false, - }, - view_type: 'tree', + disable_editable_mode: false, + }), events: { 'click thead th.oe_sortable[data-id]': 'sort_by_column' }, + icon: 'fa-list-ul', + /** * Core class for list-type displays. * @@ -69,11 +69,11 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { * See constructor parameters and method documentations for information on * the default behaviors and possible options for the list view. * - * @constructs instance.web.ListView - * @extends instance.web.View + * @constructs ListView + * @extends View * * @param parent parent object - * @param {instance.web.DataSet} dataset the dataset the view should work with + * @param {DataSet} dataset the dataset the view should work with * @param {String} view_id the listview's identifier, if any * @param {Object} options A set of options used to configure the view * @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox) @@ -83,13 +83,14 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { * @param {Boolean} [options.sortable=true] is it possible to sort the table by clicking on column headers * @param {Boolean} [options.reorderable=true] is it possible to reorder list rows */ - init: function(parent, dataset, view_id, options) { + init: function() { var self = this; - this._super(parent); - this.set_default_options(_.extend({}, this.defaults, options || {})); - this.dataset = dataset; - this.model = dataset.model; - this.view_id = view_id; + this._super.apply(this, arguments); + this.options = _.defaults(this.options, { + GroupsType: ListView.Groups, + ListType: ListView.List, + }); + this.previous_colspan = null; this.colors = null; this.fonts = null; @@ -105,8 +106,8 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { } else { this.groups.datagroup = new DataGroup( this, this.model, - dataset.get_domain(), - dataset.get_context()); + this.dataset.get_domain(), + this.dataset.get_context()); this.groups.datagroup.sort = this.dataset._sort; } @@ -125,21 +126,22 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { if (!this.options.$pager || !this.options.$pager.length) { this.options.$pager = false; } + + this.name = "" + this.fields_view.arch.attrs.string; + // the view's number of records per page (|| section) this._limit = (this.options.limit || this.defaults.limit || (this.getParent().action || {}).limit || + parseInt(this.fields_view.arch.attrs.limit, 10) || 80); - }, - view_loading: function(r) { - return this.load_list(r); - }, - set_default_options: function (options) { - this._super(options); - _.defaults(this.options, { - GroupsType: ListView.Groups, - ListType: ListView.List - }); + + //Sort + var default_order = this.fields_view.arch.attrs.default_order, + unsorted = !this.dataset._sort.length; + if (unsorted && default_order && !this.grouped) { + this.dataset.set_sort(default_order.split(',')); + } }, /** * Set a custom Group construct as the root of the List View. @@ -169,14 +171,41 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { } }); }, - /** - * View startup method, the default behavior is to set the ``oe_list`` - * class on its root element and to perform an RPC load call. - * - * @returns {$.Deferred} loading promise - */ + willStart: function() { + // Retrieve the decoration defined on the model's list view + this.decoration = _.pick(this.fields_view.arch.attrs, function(value, key) { + return row_decoration.indexOf(key) >= 0; + }); + this.decoration = _.mapObject(this.decoration, function(value) { + return py.parse(py.tokenize(value)); + }); + + this.setup_columns(this.fields_view.fields, this.grouped); + + return this._super(); + }, start: function() { + var self = this; + this.$el.html(QWeb.render(this._template, this)); + this.$el.addClass(this.fields_view.arch.attrs['class']); this.$el.addClass('oe_list o_list_view'); + + // Selecting records + this.$('.oe_list_record_selector').click(function() { + var checked = self.$('.oe_list_record_selector').prop('checked'); + self.$('.oe_list_record_selector input').prop('checked', checked || false); + var selection = self.groups.get_selection(); + $(self.groups).trigger('selected', [selection.ids, selection.records]); + }); + + if(this.dataset._sort.length){ + if(this.dataset._sort[0].indexOf('-') === -1){ + this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown"); + }else { + this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup"); + } + } + this.trigger('list_view_loaded', data, this.grouped); return this._super(); }, /** @@ -201,73 +230,6 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { }); return classnames; }, - /** - * Called after loading the list view's description, sets up such things - * as the view table's columns, renders the table itself and hooks up the - * various table-level and row-level DOM events (action buttons, deletion - * buttons, selection of records, [New] button, selection of a given - * record, ...) - * - * Sets up the following: - * - * * Processes arch and fields to generate a complete field descriptor for each field - * * Create the table itself and allocate visible columns - * * Hook in the top-level (header) [New|Add] and [Delete] button - * * Sets up showing/hiding the top-level [Delete] button based on records being selected or not - * * Sets up event handlers for action buttons and per-row deletion button - * * Hooks global callback for clicking on a row - * - * @param {Object} data wrapped fields_view_get result - * @param {Object} data.fields_view fields_view_get result (processed) - * @param {Object} data.fields_view.fields mapping of fields for the current model - * @param {Object} data.fields_view.arch current list view descriptor - * @param {Boolean} grouped Is the list view grouped - */ - load_list: function(data) { - var self = this; - this.fields_view = data; - this.name = "" + this.fields_view.arch.attrs.string; - this._limit = parseInt(this.fields_view.arch.attrs.limit, 10) || this._limit; - - // Retrieve the decoration defined on the model's list view - this.decoration = _.pick(this.fields_view.arch.attrs, function(value, key) { - return row_decoration.indexOf(key) >= 0; - }); - this.decoration = _.mapObject(this.decoration, function(value) { - return py.parse(py.tokenize(value)); - }); - - this.setup_columns(this.fields_view.fields, this.grouped); - - this.$el.html(QWeb.render(this._template, this)); - this.$el.addClass(this.fields_view.arch.attrs['class']); - - // Head hook - // Selecting records - this.$el.find('.oe_list_record_selector').click(function(){ - self.$el.find('.oe_list_record_selector input').prop('checked', - self.$el.find('.oe_list_record_selector').prop('checked') || false); - var selection = self.groups.get_selection(); - $(self.groups).trigger( - 'selected', [selection.ids, selection.records]); - }); - - //Sort - var default_order = this.fields_view.arch.attrs.default_order, - unsorted = !this.dataset._sort.length; - if (unsorted && default_order && !this.grouped) { - this.dataset.set_sort(default_order.split(',')); - } - - if(this.dataset._sort.length){ - if(this.dataset._sort[0].indexOf('-') == -1){ - this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown"); - }else { - this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup"); - } - } - this.trigger('list_view_loaded', data, this.grouped); - }, /** * Render the buttons according to the ListView.buttons template and * add listeners on it. @@ -499,16 +461,6 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { this.sidebar.do_hide(); } }, - /** - * Reloads the list view based on the current settings (dataset & al) - * - * @deprecated - * @param {Boolean} [grouped] Should the list be displayed grouped - * @param {Object} [context] context to send the server while loading the view - */ - reload_view: function (grouped, context) { - return this.load_view(context); - }, /** * re-renders the content of the list view * @@ -617,8 +569,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { this.no_leaf = !!context['group_by_no_leaf']; this.grouped = !!group_by; - return this.alive(this.load_view(context)).then( - this.proxy('reload_content')); + return this.reload_content(); }, /** * Handles the signal to delete lines from the records list @@ -720,7 +671,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { c.set_eval_context(_.extend({ active_id: id, active_ids: [id], - active_model: this.dataset.model + active_model: this.model }, this.records.get(id).toContext())); if (action.context) { c.add(action.context); @@ -960,7 +911,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { }); core.view_registry.add('list', ListView); -ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{ +ListView.List = Class.extend({ /** * List display for the ListView, handles basic DOM events and transforms * them in the relevant higher-level events, to which the list view (or @@ -1187,7 +1138,7 @@ ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{ } } return column.format(record.toForm().data, { - model: this.dataset.model, + model: this.model, id: record.get('id') }); }, @@ -1299,7 +1250,7 @@ ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{ } }); -ListView.Groups = Class.extend( /** @lends instance.web.ListView.Groups# */{ +ListView.Groups = Class.extend({ passthrough_events: 'action deleted row_link', /** * Grouped display for the ListView. Handles basic DOM events and interacts diff --git a/addons/web/static/src/js/views/list_view_editable.js b/addons/web/static/src/js/views/list_view_editable.js index 2bd6dc1ab25..627e033840e 100644 --- a/addons/web/static/src/js/views/list_view_editable.js +++ b/addons/web/static/src/js/views/list_view_editable.js @@ -38,19 +38,18 @@ var Editor = Widget.extend({ delegate: this.getParent(), }); this.delegate = this.options.delegate; - this.record = null; - this.form = new (this.options.formView)(this, this.delegate.dataset, false, { + }, + start: function () { + var self = this; + var form_fields_view = this._validate_view(this.delegate.edition_view(this)); + this.form = new (this.options.formView)(this, this.delegate.dataset, form_fields_view, { initial_mode: 'edit', is_list_editable: true, disable_autofocus: true, $buttons: $(), $pager: $(), }); - }, - start: function () { - var self = this; - this.form.embedded_view = this._validate_view(this.delegate.edition_view(this)); return $.when(this._super(), this.form.appendTo($('
')).then(function() { self.form.$el.addClass(self.$el.attr('class')); self.replaceElement(self.form.$el); @@ -258,7 +257,7 @@ ListView.include(/** @lends instance.web.ListView# */{ this._super.apply(this, arguments); } }, - load_list: function (data, grouped) { + start: function () { // tree/@editable takes priority on everything else if present. var result = this._super.apply(this, arguments); diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js index 7adc806c61f..5559906e905 100644 --- a/addons/web/static/src/js/views/pivot_view.js +++ b/addons/web/static/src/js/views/pivot_view.js @@ -19,10 +19,6 @@ var _t = core._t; var QWeb = core.qweb; var PivotView = View.extend({ - template: 'PivotView', - icon: 'fa-table', - display_name: _lt('Pivot'), - view_type: 'pivot', events: { 'click .oe-opened': 'on_open_header_click', 'click .oe-closed': 'on_closed_header_click', @@ -30,17 +26,20 @@ var PivotView = View.extend({ 'click td': 'on_cell_click', 'click .measure-row': 'on_measure_row_click', }, + display_name: _lt('Pivot'), + icon: 'fa-table', + require_fields: true, + template: 'PivotView', - init: function(parent, dataset, view_id, options) { - this._super(parent, dataset, view_id, options); - this.model = new Model(dataset.model, {group_by_no_leaf: true}); + init: function() { + this._super.apply(this, arguments); + this._model = new Model(this.model, {group_by_no_leaf: true}); - this.fields = {}; this.measures = {}; this.groupable_fields = {}; this.ready = false; // will be ready after the first do_search this.data_loaded = $.Deferred(); - this.title = options.title; + this.title = this.options.title || this.fields_view.arch.attrs.string; this.main_row = { root: undefined, @@ -63,30 +62,72 @@ var PivotView = View.extend({ this.numbering = {}; this.widgets = []; + + this.enable_linking = !this.fields_view.arch.attrs.disable_linking; }, willStart: function () { var self = this; - return session.rpc('/web/pivot/check_xlwt').then(function(result) { + + var load_fields = this.ViewManager.get_fields(); + var load_xlwt = session.rpc('/web/pivot/check_xlwt').then(function(result) { self.xlwt_installed = result; }); + + this.fields_view.arch.children.forEach(function (field) { + var name = field.attrs.name; + if (field.attrs.interval) { + name += ':' + field.attrs.interval; + } + //noinspection FallThroughInSwitchStatementJS + switch (field.attrs.type) { + case 'measure': + self.widgets.push(field.attrs.widget || ""); + self.active_measures.push(name); + break; + case 'col': + self.initial_col_groupby.push(name); + break; + default: + if ('operator' in field.attrs) { + self.active_measures.push(name); + break; + } + case 'row': + self.initial_row_groupby.push(name); + } + }); + if ((!this.active_measures.length) || this.fields_view.arch.attrs.display_quantity) { + this.active_measures.push('__count__'); + } + return $.when(load_fields, load_xlwt, this._super()).then(function (fields) { + self.prepare_fields(fields); + // add active measures to the measure list. This is very rarely necessary, but it + // can be useful if one is working with a functional field non stored, but in a + // model with an overrided read_group method. In this case, the pivot view could + // work, and the measure should be allowed. However, be careful if you define a + // measure in your pivot view: non stored functional fields will probably not work + // (their aggregate will always be 0). + _.each(self.active_measures, function (m) { + if (!(m in self.measures)) { + self.measures[m] = self.fields[m]; + } + }); + }); }, start: function () { this.$table_container = this.$('.o-pivot-table'); - - var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()}) - .then(this.prepare_fields.bind(this)); - - return $.when(this._super(), load_fields).then(this.render_field_selection.bind(this)); - }, - render_field_selection: function () { - var self = this; - - var context = {fields: _.chain(this.groupable_fields).pairs().sortBy(function(f){return f[1].string;}).value()}; + this.$el.toggleClass('oe_enable_linking', this.enable_linking); + var context = { + fields: _.chain(this.groupable_fields).pairs().sortBy(function(f) { + return f[1].string; + }).value(), + }; this.$field_selection = this.$('.o-field-selection'); this.$field_selection.html(QWeb.render('PivotView.FieldSelection', context)); - core.bus.on('click', self, function () { - self.$field_selection.find('ul').first().hide(); + core.bus.on('click', this, function () { + this.$field_selection.find('ul').first().hide(); }); + return this._super(); }, /** * Render the buttons according to the PivotView.buttons template and @@ -127,49 +168,6 @@ var PivotView = View.extend({ this.sidebar.appendTo($node); } }, - view_loading: function (fvg) { - var self = this; - this.title = this.title || fvg.arch.attrs.string; - this.enable_linking = !fvg.arch.attrs.disable_linking; - this.$el.toggleClass('oe-enable-linking', this.enable_linking); - fvg.arch.children.forEach(function (field) { - var name = field.attrs.name; - if (field.attrs.interval) { - name += ':' + field.attrs.interval; - } - //noinspection FallThroughInSwitchStatementJS - switch (field.attrs.type) { - case 'measure': - self.widgets.push(field.attrs.widget || ""); - self.active_measures.push(name); - break; - case 'col': - self.initial_col_groupby.push(name); - break; - default: - if ('operator' in field.attrs) { - self.active_measures.push(name); - break; - } - case 'row': - self.initial_row_groupby.push(name); - } - }); - if ((!self.active_measures.length) || fvg.arch.attrs.display_quantity){ - self.active_measures.push('__count__'); - } - // add active measures to the measure list. This is very rarely necessary, but it - // can be useful if one is working with a functional field non stored, but in a - // model with an overrided read_group method. In this case, the pivot view could - // work, and the measure should be allowed. However, be careful if you define a - // measure in your pivot view: non stored functional fields will probably not work - // (their aggregate will always be 0). - _.each(this.active_measures, function (m) { - if (!(m in self.measures)) { - self.measures[m] = self.fields[m]; - } - }); - }, prepare_fields: function (fields) { var self = this, groupable_types = ['many2one', 'char', 'boolean', @@ -277,15 +275,14 @@ var PivotView = View.extend({ col_domain = this.headers[col_id].domain, context = _.omit(_.clone(this.context), 'group_by'); - var views = [ - [this.options.action_views_ids.list || false, 'list'], - [this.options.action_views_ids.form || false, 'form'] - ]; + var views = _.filter(this.options.action.views, function (view) { + return view[1] === 'form' || view[1] === 'list'; + }); return this.do_action({ type: 'ir.actions.act_window', name: this.title, - res_model: this.model.name, + res_model: this.model, views: views, view_type : "list", view_mode : "list", @@ -345,7 +342,7 @@ var PivotView = View.extend({ groupbys.push([field].concat(other_groupbys.slice(0,i))); } return $.when.apply(null, groupbys.map(function (groupby) { - return self.model.query(fields) + return self._model.query(fields) .filter(header.domain.length ? header.domain : self.domain) .context(self.context) .lazy(false) @@ -398,7 +395,7 @@ var PivotView = View.extend({ } } return $.when.apply(null, groupbys.map(function (groupby) { - return self.model.query(fields) + return self._model.query(fields) .filter(self.domain) .context(self.context) .lazy(false) diff --git a/addons/web/static/src/js/views/search_menus.js b/addons/web/static/src/js/views/search_menus.js index 3bd7a3aaba5..c26cdc35ea8 100644 --- a/addons/web/static/src/js/views/search_menus.js +++ b/addons/web/static/src/js/views/search_menus.js @@ -26,18 +26,34 @@ return Widget.extend({ } }, }, - init: function (parent, query, target_model, action_id) { + init: function (parent, query, target_model, action_id, filters) { this._super.apply(this,arguments); this.searchview = parent; this.query = query; this.target_model = target_model; this.model = new Model('ir.filters'); - this.filters = {}; - this.$filters = {}; this.action_id = action_id; + if (filters) { + this.filters = {}; + _.each(filters, this.add_filter.bind(this)); + } + }, + willStart: function () { + var self = this; + var def; + if (!this.filters) { + def = this.model.call('get_filters', [this.target_model, this.action_id], { + context: this.searchview.dataset.context, + }).then(function (filters) { + self.filters = {}; + _.each(filters, self.add_filter.bind(self)); + }); + } + return $.when(def, this._super()); }, start: function () { var self = this; + this.$filters = {}; this.$save_search = this.$('.oe-save-search'); this.$save_name = this.$('.oe-save-name'); this.$inputs = this.$save_name.find('input'); @@ -55,12 +71,10 @@ return Widget.extend({ } }) .on('reset', this.proxy('clear_selection')); - return this.model.call('get_filters', [this.target_model, this.action_id], - {context: this.searchview.dataset.context}) - .done(this.proxy('prepare_dropdown_menu')); - }, - prepare_dropdown_menu: function (filters) { - filters.map(this.append_filter.bind(this)); + + _.each(this.filters, this.append_filter.bind(this)); + + return this._super(); }, toggle_save_menu: function (is_open) { this.$save_search @@ -115,7 +129,7 @@ return Widget.extend({ var filter = { name: filter_name, user_id: shared_filter ? false : session.uid, - model_id: this.searchview.dataset.model, + model_id: this.target_model, context: results.context, domain: results.domain, sort: JSON.stringify(this.searchview.dataset._sort), @@ -126,6 +140,7 @@ return Widget.extend({ filter.id = id; self.toggle_save_menu(false); self.$save_name.find('input').val('').prop('checked', false); + self.add_filter(filter); self.append_filter(filter); self.toggle_filter(filter, true); }); @@ -187,31 +202,39 @@ return Widget.extend({ clear_selection: function () { this.$('li.selected').removeClass('selected'); }, + /** + * Adds a filter description to the filters dict + * @param {Object} [filter] the filter description + */ + add_filter: function (filter) { + this.filters[this.key_for(filter)] = filter; + }, + /** + * Creates a $filter JQuery node, adds it to the $filters dict and appends it to the filter menu + * @param {Object} [filter] the filter description + */ append_filter: function (filter) { - var self = this, - key = this.key_for(filter), - $filter; + var self = this; + var key = this.key_for(filter); this.$divider.show(); - if (key in this.$filters) { - $filter = this.$filters[key]; - } else { - this.filters[key] = filter; - $filter = $('
  • ') - .insertBefore(this.$divider) + if (!(key in this.$filters)) { + var $filter = $('
  • ') + .addClass(filter.user_id ? 'oe_searchview_custom_private' + : 'oe_searchview_custom_public') .toggleClass('oe_searchview_custom_default', filter.is_default) - .append($('').text(filter.name)); - + .append($('').text(filter.name)) + .append($('', { + class: 'fa fa-trash-o remove-filter', + on: { + click: function (event) { + event.stopImmediatePropagation(); + self.remove_filter(filter, $filter, key); + }, + }, + })) + .insertBefore(this.$divider); this.$filters[key] = $filter; - this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private' - : 'oe_searchview_custom_public'); - $('') - .addClass('fa fa-trash-o remove-filter') - .click(function (event) { - event.stopImmediatePropagation(); - self.remove_filter(filter, $filter, key); - }) - .appendTo($filter); } this.$filters[key].unbind('click').click(function () { self.toggle_filter(filter); @@ -282,22 +305,12 @@ return Widget.extend({ } }, }, - init: function (parent, filters, fields_def) { + init: function (parent, filters) { this._super(parent); this.filters = filters || []; this.searchview = parent; this.propositions = []; - this.fields_def = fields_def.then(function (data) { - var fields = { - id: { string: 'ID', type: 'id', searchable: true } - }; - _.each(data, function(field_def, field_name) { - if (field_def.selectable !== false && field_name !== 'id') { - fields[field_name] = field_def; - } - }); - return fields; - }); + this.custom_filters_open = false; }, start: function () { var self = this; @@ -311,23 +324,41 @@ return Widget.extend({ $('
  • ').insertBefore(self.$add_filter); } }); - this.append_proposition().then(function (prop) { - prop.$el.hide(); - }); + }, + get_fields: function () { + if (!this._fields_def) { + this._fields_def = this.searchview.ViewManager.get_fields().then(function (data) { + var fields = { + id: { string: 'ID', type: 'id', searchable: true } + }; + _.each(data, function(field_def, field_name) { + if (field_def.selectable !== false && field_name !== 'id') { + fields[field_name] = field_def; + } + }); + return fields; + }); + } + return this._fields_def; }, toggle_custom_filter_menu: function (is_open) { - this.$add_filter - .toggleClass('o_closed_menu', !(_.isUndefined(is_open)) ? !is_open : undefined) - .toggleClass('o_open_menu', is_open); - this.$add_filter_menu.toggle(is_open); - if (this.$add_filter.hasClass('o_closed_menu') && (!this.propositions.length)) { - this.append_proposition(); + var self = this; + this.custom_filters_open = !_.isUndefined(is_open) ? is_open : !this.custom_filters_open; + var def; + if (this.custom_filters_open && !this.propositions.length) { + def = this.append_proposition(); } - this.$('.oe-filter-condition').toggle(is_open); + $.when(def).then(function () { + self.$add_filter + .toggleClass('o_closed_menu', !self.custom_filters_open) + .toggleClass('o_open_menu', self.custom_filters_open); + self.$add_filter_menu.toggle(self.custom_filters_open); + self.$('.oe-filter-condition').toggle(self.custom_filters_open); + }); }, append_proposition: function () { var self = this; - return this.fields_def.then(function (fields) { + return this.get_fields().then(function (fields) { var prop = new search_filters.ExtendedSearchProposition(self, fields); self.propositions.push(prop); prop.insertBefore(self.$add_filter_menu); @@ -387,15 +418,13 @@ return Widget.extend({ this.toggle_add_menu(); }, }, - init: function (parent, groups, fields_def) { + init: function (parent, groups) { this._super(parent); this.groups = groups || []; this.groupable_fields = []; this.searchview = parent; - this.fields_def = fields_def.then(this.proxy('get_groupable_fields')); }, start: function () { - var self = this; this.$menu = this.$('.group-by-menu'); var divider = this.$menu.find('.divider'); _.invoke(this.groups, 'insertBefore', divider); @@ -403,35 +432,43 @@ return Widget.extend({ divider.show(); } this.$add_group = this.$menu.find('.add-custom-group'); - this.fields_def.then(function () { - self.$menu.append(QWeb.render('GroupByMenuSelector', self)); - self.$add_group_menu = self.$('.oe-add-group'); - self.$group_selector = self.$('.oe-group-selector'); - self.$('.oe-select-group').click(function () { - self.toggle_add_menu(false); - var field = self.$group_selector.find(':selected').data('name'); - self.add_groupby_to_menu(field); - }); - }); }, - get_groupable_fields: function (fields) { - var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime']; - var filter_group_field = _.filter(fields, function(field, name) { - if (field.store && _.contains(groupable_types, field.type)) { - field.name = name; - return field; - } - }); - this.groupable_fields = _.sortBy(filter_group_field, 'string'); + get_fields: function () { + var self = this; + if (!this._fields_def) { + this._fields_def = this.searchview.ViewManager.get_fields().then(function (fields) { + var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime']; + var filter_group_field = _.filter(fields, function(field, name) { + if (field.store && _.contains(groupable_types, field.type)) { + field.name = name; + return field; + } + }); + self.groupable_fields = _.sortBy(filter_group_field, 'string'); + + self.$menu.append(QWeb.render('GroupByMenuSelector', self)); + self.$add_group_menu = self.$('.oe-add-group'); + self.$group_selector = self.$('.oe-group-selector'); + self.$('.oe-select-group').click(function () { + self.toggle_add_menu(false); + var field = self.$group_selector.find(':selected').data('name'); + self.add_groupby_to_menu(field); + }); + }); + } + return this._fields_def; }, toggle_add_menu: function (is_open) { - this.$add_group - .toggleClass('o_closed_menu', !(_.isUndefined(is_open)) ? !is_open : undefined) - .toggleClass('o_open_menu', is_open); - this.$add_group_menu.toggle(is_open); - if (this.$add_group.hasClass('o_open_menu')) { - this.$group_selector.focus(); - } + var self = this; + this.get_fields().then(function () { + self.$add_group + .toggleClass('o_closed_menu', !(_.isUndefined(is_open)) ? !is_open : undefined) + .toggleClass('o_open_menu', is_open); + self.$add_group_menu.toggle(is_open); + if (self.$add_group.hasClass('o_open_menu')) { + self.$group_selector.focus(); + } + }); }, add_groupby_to_menu: function (field_name) { var filter = new search_inputs.Filter({attrs:{ diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js index caf6d5a9c74..4dc6b541a6a 100644 --- a/addons/web/static/src/js/views/search_view.js +++ b/addons/web/static/src/js/views/search_view.js @@ -6,10 +6,9 @@ var core = require('web.core'); var FavoriteMenu = require('web.FavoriteMenu'); var FilterMenu = require('web.FilterMenu'); var GroupByMenu = require('web.GroupByMenu'); -var Model = require('web.DataModel'); var pyeval = require('web.pyeval'); var search_inputs = require('web.search_inputs'); -var utils = require('web.utils'); +var View = require('web.View'); var Widget = require('web.Widget'); var Backbone = window.Backbone; @@ -317,8 +316,14 @@ var FacetValueView = Widget.extend({ } }); -var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{ - template: "SearchView", +var SearchView = View.extend({ + defaults: _.extend({}, View.prototype.defaults, { + hidden: false, + disable_filters: false, + disable_groupby: false, + disable_favorites: false, + disable_custom_filters: false, + }), events: { // focus last input if view itself is clicked 'click': function (e) { @@ -355,44 +360,36 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{ this.$el.autocomplete('widget').css('z-index', 9999); }, }, + template: "SearchView", + /** - * @constructs instance.web.SearchView - * @extends instance.web.Widget + * @constructs SearchView + * @extends View * * @param parent * @param dataset - * @param view_id - * @param defaults + * @param fields_view * @param {Object} [options] * @param {Boolean} [options.hidden=false] hide the search view * @param {Boolean} [options.disable_custom_filters=false] do not load custom filters from ir.filters */ - init: function(parent, dataset, view_id, defaults, options) { - this.options = _.defaults(options || {}, { - hidden: false, - disable_filters: false, - disable_groupby: false, - disable_favorites: false, - disable_custom_filters: false, - }); - this._super(parent); + init: function() { + this._super.apply(this, arguments); + this.title = this.options.action && this.options.action.name; + this.action_id = this.options.action && this.options.action.id; this.query = undefined; - this.dataset = dataset; - this.view_id = view_id; - this.title = options.action && options.action.name; this.search_fields = []; this.filters = []; this.groupbys = []; this.visible_filters = (localStorage.visible_search_menu === 'true'); this.input_subviews = []; // for user input in searchbar - this.defaults = defaults || {}; - this.headless = this.options.hidden && _.isEmpty(this.defaults); + this.search_defaults = this.options.search_defaults || {}; + this.headless = this.options.hidden && _.isEmpty(this.search_defaults); this.$buttons = this.options.$buttons; this.filter_menu = undefined; this.groupby_menu = undefined; this.favorite_menu = undefined; - this.action_id = this.options && this.options.action && this.options.action.id; }, start: function() { if (this.headless) { @@ -405,43 +402,29 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{ .on('add change reset remove', this.proxy('do_search')) .on('change', this.proxy('renderChangedFacets')) .on('add reset remove', this.proxy('renderFacets')); - var load_view = this.dataset._model.fields_view_get({ - view_id: this.view_id, - view_type: 'search', - context: this.dataset.get_context(), - }); this.$('.oe_searchview_unfold_drawer') .toggleClass('fa-caret-down', !this.visible_filters) .toggleClass('fa-caret-up', this.visible_filters); - return this.alive($.when(this._super(), this.alive(load_view).then(this.view_loaded.bind(this)))); - }, - get_title: function() { - return this.title; - }, - view_loaded: function (r) { var menu_defs = []; - this.fields_view_get = r; - this.view_id = this.view_id || r.view_id; this.prepare_search_inputs(); if (this.$buttons) { - var fields_def = new Model(this.dataset.model).call('fields_get', { - context: this.dataset.get_context() - }); - if (!this.options.disable_filters) { - this.filter_menu = new FilterMenu(this, this.filters, fields_def); + this.filter_menu = new FilterMenu(this, this.filters); menu_defs.push(this.filter_menu.appendTo(this.$buttons)); } if (!this.options.disable_groupby) { - this.groupby_menu = new GroupByMenu(this, this.groupbys, fields_def); + this.groupby_menu = new GroupByMenu(this, this.groupbys); menu_defs.push(this.groupby_menu.appendTo(this.$buttons)); } if (!this.options.disable_favorites) { - this.favorite_menu = new FavoriteMenu(this, this.query, this.dataset.model, this.action_id); + this.favorite_menu = new FavoriteMenu(this, this.query, this.dataset.model, this.action_id, this.options.filters); menu_defs.push(this.favorite_menu.appendTo(this.$buttons)); } } - return $.when.apply($, menu_defs).then(this.proxy('set_default_filters')); + return $.when.apply($, menu_defs).then(this.set_default_filters.bind(this)); + }, + get_title: function() { + return this.title; }, set_default_filters: function () { var self = this, @@ -449,10 +432,10 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{ if (!self.options.disable_custom_filters && default_custom_filter) { return this.favorite_menu.toggle_filter(default_custom_filter, true); } - if (!_.isEmpty(this.defaults)) { + if (!_.isEmpty(this.search_defaults)) { var inputs = this.search_fields.concat(this.filters, this.groupbys), - defaults = _.invoke(inputs, 'facet_for_defaults', this.defaults); - return $.when.apply(null, defaults).then(function () { + search_defaults = _.invoke(inputs, 'facet_for_defaults', this.search_defaults); + return $.when.apply(null, search_defaults).then(function () { self.query.reset(_(arguments).compact(), {preventSearch: true}); }); } @@ -667,7 +650,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{ // * this.group_by: group_bys prepare_search_inputs: function () { var self = this, - arch = this.fields_view_get.arch; + arch = this.fields_view.arch; var filters = [].concat.apply([], _.map(arch.children, function (item) { return item.tag !== 'group' ? eval_item(item) : item.children.map(eval_item); @@ -702,7 +685,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{ } if (filter.item.tag === 'field') { var attrs = filter.item.attrs; - var field = self.fields_view_get.fields[attrs.name]; + var field = self.fields_view.fields[attrs.name]; // M2O combined with selection widget is pointless and broken in search views, // but has been used in the past for unsupported hacks -> ignore it diff --git a/addons/web/static/src/js/views/tree_view.js b/addons/web/static/src/js/views/tree_view.js index ab9193095ea..6246a1494f5 100644 --- a/addons/web/static/src/js/views/tree_view.js +++ b/addons/web/static/src/js/views/tree_view.js @@ -14,41 +14,21 @@ var View = require('web.View'); var _lt = core._lt; var QWeb = core.qweb; -var TreeView = View.extend(/** @lends instance.web.TreeView# */{ +var TreeView = View.extend({ display_name: _lt('Tree'), icon: 'fa-align-left', - view_type: 'tree', - /** - * Indicates that this view is not searchable, and thus that no search - * view should be displayed (if there is one active). - */ - searchable : false, + // Indicates that this view is not searchable, and thus that no search view should be displayed. + searchable: false, + /** * Genuine tree view (the one displayed as a tree, not the list) - * - * @constructs instance.web.TreeView - * @extends instance.web.View - * - * @param parent - * @param dataset - * @param view_id - * @param options */ - init: function(parent, dataset, view_id, options) { - this._super(parent); - this.dataset = dataset; - this.model = dataset.model; - this.view_id = view_id; - + init: function() { + this._super.apply(this, arguments); this.records = {}; - - this.options = _.extend({}, this.defaults, options || {}); - _.bindAll(this, 'color_for'); - }, - - view_loading: function(r) { - return this.load_tree(r); + this.fields = this.fields_view.fields; + this.children_field = this.fields_view.field_parent; }, /** @@ -67,20 +47,29 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{ } return fields; }, - load_tree: function (fields_view) { - var self = this; - var has_toolbar = !!fields_view.arch.attrs.toolbar; - // field name in OpenERP is kinda stupid: this is the name of the field - // holding the ids to the children of the current node, why call it - // field_parent? - this.children_field = fields_view.field_parent; - this.fields_view = fields_view; + willStart: function () { _(this.fields_view.arch.children).each(function (field) { if (field.attrs.modifiers) { field.attrs.modifiers = JSON.parse(field.attrs.modifiers); } }); - this.fields = fields_view.fields; + + if (this.fields_view.arch.attrs.colors) { + this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain() + .compact() + .map(function(color_pair) { + var pair = color_pair.split(':'), + color = pair[0], + expr = pair[1]; + return [color, py.parse(py.tokenize(expr)), expr]; + }).value(); + } + + return this._super(); + }, + start: function () { + var self = this; + var has_toolbar = !!this.fields_view.arch.attrs.toolbar; this.hook_row_click(); this.$el.html(QWeb.render('TreeView', { 'title': this.fields_view.arch.attrs.string, @@ -120,17 +109,7 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{ // TODO store open nodes in url ?... this.do_push_state({}); - if (!this.fields_view.arch.attrs.colors) { - return; - } - this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain() - .compact() - .map(function(color_pair) { - var pair = color_pair.split(':'), - color = pair[0], - expr = pair[1]; - return [color, py.parse(py.tokenize(expr)), expr]; - }).value(); + return this._super(); }, /** * Returns the color for the provided record in the current view (from the @@ -225,7 +204,7 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{ activate: function(id) { var self = this; var local_context = { - active_model: self.dataset.model, + active_model: self.model, active_id: id, active_ids: [id] }; @@ -234,7 +213,7 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{ this.dataset.get_context(), local_context)); return this.rpc('/web/treeview/action', { id: id, - model: this.dataset.model, + model: this.model, context: ctx }).then(function (actions) { if (!actions.length) { return; } @@ -260,11 +239,6 @@ var TreeView = View.extend(/** @lends instance.web.TreeView# */{ $child_row.toggle(show); }, this); }, - - do_hide: function () { - this.hidden = true; - this._super(); - } }); core.view_registry.add('tree', TreeView); diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index d3d45c347a1..1606dabaf85 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -193,17 +193,6 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test } return []; }); - mock.add('demo:fields_view_get', function () { - return { - type: 'tree', - fields: { - a: {type: 'char', string: "A"}, - b: {type: 'char', string: "B"}, - c: {type: 'char', string: "C"} - }, - arch: '', - }; - }); mock.add('demo:onchange', function () { return {}; }); @@ -224,7 +213,16 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test }); var ds = new data.DataSetStatic(null, 'demo', null, [1]); - var l = new ListView({}, ds, false, {editable: 'top'}); + var fields_view = ds._model.postprocess_fvg({ + type: 'tree', + fields: { + a: {type: 'char', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: '', + }); + var l = new ListView({}, ds, fields_view, {editable: 'top'}); var $fix = $( "#qunit-fixture"); return l.appendTo($fix) @@ -250,22 +248,21 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView'], function (test }); odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], function (test, mock) { - + function fields_view_get () { + return { + type: 'tree', + fields: { + a: {type: 'char', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: '', + }; + } function setup () { mock.add('demo:read', function () { return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }]; }); - mock.add('demo:fields_view_get', function () { - return { - type: 'tree', - fields: { - a: {type: 'char', string: "A"}, - b: {type: 'char', string: "B"}, - c: {type: 'char', string: "C"} - }, - arch: '', - }; - }); } test('edition events',function (assert, data, ListView) { @@ -276,7 +273,8 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], functio counter: 0, onEvent: function (e) { this.counter++; } }; - var l = new ListView({}, ds, false, {editable: 'top'}); + var fields_view = ds._model.postprocess_fvg(fields_view_get()); + var l = new ListView({}, ds, fields_view, {editable: 'top'}); l.on('edit:before edit:after', o, o.onEvent); var $fix = $( "#qunit-fixture"); @@ -297,7 +295,8 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView'], functio setup(); var edit_after = false; var ds = new data.DataSetStatic(null, 'demo', null, [1]); - var l = new ListView({}, ds, false, {editable: 'top'}); + var fields_view = ds._model.postprocess_fvg(fields_view_get()); + var l = new ListView({}, ds, fields_view, {editable: 'top'}); l.on('edit:before', {}, function (e) { e.cancel = true; }); @@ -329,16 +328,6 @@ odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView'], functi mock.add('demo:onchange', function () { return {}; }); - - mock.add('demo:fields_view_get', function () { - return { - type: 'tree', - fields: { - a: {type: 'char', string: "A"} - }, - arch: '', - }; - }); mock.add('demo:read', function (args, kwargs) { if (_.isEmpty(args[0])) { return []; @@ -358,7 +347,14 @@ odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView'], functi mock.add('demo:on_write', function () { return [42]; }); var ds = new data.DataSetStatic(null, 'demo', null, []); - var l = new ListView({}, ds, false, {editable: 'top'}); + var fields_view = ds._model.postprocess_fvg({ + type: 'tree', + fields: { + a: {type: 'char', string: "A"} + }, + arch: '', + }); + var l = new ListView({}, ds, fields_view, {editable: 'top'}); var $fix = $( "#qunit-fixture"); return l.appendTo($fix) diff --git a/addons/web/static/test/list.js b/addons/web/static/test/list.js index 26e17aff77d..1a19e567364 100644 --- a/addons/web/static/test/list.js +++ b/addons/web/static/test/list.js @@ -3,15 +3,6 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data'], function (test test('record-deletion', function (assert, ListView, data) { assert.expect(2); - mock.add('demo:fields_view_get', function () { - return { - type: 'tree', - fields: { - a: {type: 'char', string: "A"} - }, - arch: '