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 = $('