summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/framework/view.js
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2015-03-09 16:34:27 +0100
committerGéry Debongnie <ged@odoo.com>2015-03-18 09:01:49 +0100
commite8a00bc50da435d3859118ca127d20a2eb9a0cab (patch)
tree039f6ec40b37392e76c8830a8e518ac40f7e7fec /addons/web/static/src/js/framework/view.js
parent9036084f065c7aca3b2199903f4da67db3501ba7 (diff)
[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code. Instead of using global (namespaced) variables everywhere (like openerp.web.whatever, ...), each file declare one (or more) module with its dependencies. Each module will be loaded after its dependencies, and will be given a reference to the return values. This change is a massive departure from the way the client worked previously. It will sadly break some/most history, but I am sure that the future git ecosystem will allow you to easily browse the history past this revision (one can dream). This commit feature: * main namespace is now odoo instead of openerp * a way to define modules and dependencies (inspired by requirejs) * many files have been splitted in simpler subfiles * basic support for debugging in odoo.__DEBUG__ * no global variable (i.e. nothing in window.openerp) * many bug fixes, many new bugs Future vision: this modularization should simplify future code development, simplify debugging and will allow a serious testing effort
Diffstat (limited to 'addons/web/static/src/js/framework/view.js')
-rw-r--r--addons/web/static/src/js/framework/view.js239
1 files changed, 239 insertions, 0 deletions
diff --git a/addons/web/static/src/js/framework/view.js b/addons/web/static/src/js/framework/view.js
new file mode 100644
index 00000000000..c315e53d710
--- /dev/null
+++ b/addons/web/static/src/js/framework/view.js
@@ -0,0 +1,239 @@
+odoo.define('web.View', ['web.core', 'web.data', 'web.pyeval', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var data = require('web.data');
+var pyeval = require('web.pyeval');
+var Widget = require('web.Widget');
+
+var View = Widget.extend({
+ // 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,
+ init: function(parent, dataset, view_id, options) {
+ this._super(parent);
+ this.ViewManager = parent;
+ this.dataset = dataset;
+ this.view_id = view_id;
+ this.$buttons = options && options.$buttons;
+ this.set_default_options(options);
+ },
+ 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: {}
+ });
+ },
+ /**
+ * Fetches and executes the action identified by ``action_data``.
+ *
+ * @param {Object} action_data the action descriptor data
+ * @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
+ * @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 {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)
+ */
+ do_execute_action: function (action_data, dataset, record_id, on_closed) {
+ var self = this;
+ var result_handler = function () {
+ if (on_closed) { on_closed.apply(null, arguments); }
+ if (self.getParent() && self.getParent().on_action_executed) {
+ return self.getParent().on_action_executed.apply(null, arguments);
+ }
+ };
+ var context = new data.CompoundContext(dataset.get_context(), action_data.context || {});
+
+ // response handler
+ var handler = function (action) {
+ if (action && action.constructor == Object) {
+ // filter out context keys that are specific to the current action.
+ // Wrong default_* and search_default_* values will no give the expected result
+ // Wrong group_by values will simply fail and forbid rendering of the destination view
+ var ncontext = new data.CompoundContext(
+ _.object(_.reject(_.pairs(dataset.get_context().eval()), function(pair) {
+ return pair[0].match('^(?:(?:default_|search_default_).+|.+_view_ref|group_by|group_by_no_leaf|active_id|active_ids)$') !== null;
+ }))
+ );
+ ncontext.add(action_data.context || {});
+ ncontext.add({active_model: dataset.model});
+ if (record_id) {
+ ncontext.add({
+ active_id: record_id,
+ active_ids: [record_id],
+ });
+ }
+ ncontext.add(action.context || {});
+ action.context = ncontext;
+ return self.do_action(action, {
+ on_close: result_handler,
+ });
+ } else {
+ self.do_action({"type":"ir.actions.act_window_close"});
+ return result_handler();
+ }
+ };
+
+ if (action_data.special === 'cancel') {
+ return handler({"type":"ir.actions.act_window_close"});
+ } else if (action_data.type=="object") {
+ var args = [[record_id]];
+ if (action_data.args) {
+ try {
+ // Warning: quotes and double quotes problem due to json and xml clash
+ // Maybe we should force escaping in xml or do a better parse of the args array
+ var additional_args = JSON.parse(action_data.args.replace(/'/g, '"'));
+ args = args.concat(additional_args);
+ } catch(e) {
+ console.error("Could not JSON.parse arguments", action_data.args);
+ }
+ }
+ args.push(context);
+ return dataset.call_button(action_data.name, args).then(handler).then(function () {
+ core.bus.trigger('do_reload_needaction');
+ });
+ } else if (action_data.type=="action") {
+ return this.rpc('/web/action/load', {
+ action_id: action_data.name,
+ context: _.extend(pyeval.eval('context', context), {'active_model': dataset.model, 'active_ids': dataset.ids, 'active_id': record_id}),
+ do_not_eval: true
+ }).then(handler);
+ } else {
+ 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 () {
+ var self = this;
+ if (this.$buttons) {
+ this.$buttons.show();
+ }
+ this.$el.show();
+ setTimeout(function () {
+ self.$el.parent().addClass('in');
+ }, 0);
+
+ core.bus.trigger('view_shown', this);
+ },
+ do_hide: function () {
+ if (this.$buttons) {
+ this.$buttons.hide();
+ }
+ this.$el.parent().removeClass('in');
+ this.$el.hide();
+ },
+ 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) {
+
+ },
+ /**
+ * Switches to a specific view type
+ */
+ do_switch_view: function() {
+ this.trigger.apply(this, ['switch_mode'].concat(_.toArray(arguments)));
+ },
+ do_search: function(domain, context, group_by) {
+ },
+ sidebar_eval_context: function () {
+ return $.when({});
+ },
+ /**
+ * Asks the view to reload itself, if the reloading is asynchronous should
+ * return a {$.Deferred} indicating when the reloading is done.
+ */
+ reload: function () {
+ return $.when();
+ },
+ /**
+ * Return whether the user can perform the action ('create', 'edit', 'delete') in this view.
+ * An action is disabled by setting the corresponding attribute in the view's main element,
+ * like: <form string="" create="false" edit="false" delete="false">
+ */
+ is_action_enabled: function(action) {
+ var attrs = this.fields_view.arch.attrs;
+ return (action in attrs) ? JSON.parse(attrs[action]) : true;
+ },
+ get_context: function () {
+ return {};
+ },
+});
+
+return View;
+
+});