diff options
| author | Géry Debongnie <ged@odoo.com> | 2015-03-09 16:34:27 +0100 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2015-03-18 09:01:49 +0100 |
| commit | e8a00bc50da435d3859118ca127d20a2eb9a0cab (patch) | |
| tree | 039f6ec40b37392e76c8830a8e518ac40f7e7fec /addons/web/static/src/js/view_manager.js | |
| parent | 9036084f065c7aca3b2199903f4da67db3501ba7 (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/view_manager.js')
| -rw-r--r-- | addons/web/static/src/js/view_manager.js | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js new file mode 100644 index 00000000000..dfef449fe9e --- /dev/null +++ b/addons/web/static/src/js/view_manager.js @@ -0,0 +1,514 @@ +odoo.define('web.ViewManager', ['web.core', 'web.data', 'web.Dialog', 'web.formats', 'web.framework', 'web.Model', 'web.pyeval', 'web.utils', 'web.SearchView', 'web.session', 'web.Widget'], function (require) { +"use strict"; + +var core = require('web.core'); +var data = require('web.data'); +var Dialog = require('web.Dialog'); +var formats = require('web.formats'); +var framework = require('web.framework'); +var Model = require('web.Model'); +var pyeval = require('web.pyeval'); +var utils = require('web.utils'); +var SearchView = require('web.SearchView'); +var session = require('web.session'); +var Widget = require('web.Widget'); + +var _t = core._t; +var QWeb = core.qweb; + +var ViewManager = Widget.extend({ + template: "ViewManager", + /** + * @param {Object} [dataset] null object (... historical reasons) + * @param {Array} [views] List of [view_id, view_type] + * @param {Object} [flags] various boolean describing UI state + */ + init: function(parent, dataset, views, flags, action) { + if (action) { + flags = action.flags || {}; + if (!('auto_search' in flags)) { + flags.auto_search = action.auto_search !== false; + } + if (action.res_model === 'board.board' && action.view_mode === 'form') { + action.target = 'inline'; + // Special case for Dashboards + _.extend(flags, { + views_switcher : false, + display_title : false, + search_view : false, + pager : false, + sidebar : false, + action_buttons : false + }); + } + this.action = action; + this.action_manager = parent; + this.debug = session.debug; + dataset = new data.DataSetSearch(this, action.res_model, action.context, action.domain); + if (action.res_id) { + dataset.ids.push(action.res_id); + dataset.index = 0; + } + views = action.views; + } + var self = this; + this._super(parent); + + this.flags = flags || {}; + this.dataset = dataset; + this.view_order = []; + this.url_states = {}; + this.views = {}; + this.view_stack = []; // used for breadcrumbs + this.active_view = null; + this.searchview = null; + this.active_search = null; + this.registry = core.view_registry; + this.title = this.action && this.action.name; + + _.each(views, function (view) { + var view_type = view[1] || view.view_type, + View = core.view_registry.get(view_type, true), + view_label = View ? View.prototype.display_name: (void 'nope'), + 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.action && self.action.name, + button_label: View ? _.str.sprintf(_t('%(view_type)s view'), {'view_type': (view_label || view_type)}) : (void 'nope'), + }; + self.view_order.push(view_descr); + self.views[view_type] = view_descr; + }); + this.multiple_views = (self.view_order.length > 1); + }, + /** + * @returns {jQuery.Deferred} initial view loading promise + */ + start: function() { + var self = this; + var default_view = this.flags.default_view || this.view_order[0].type, + default_options = this.flags[default_view] && this.flags[default_view].options; + + if (this.flags.headless) { + this.$('.oe-view-manager-header').hide(); + } + this._super(); + var $sidebar = this.flags.sidebar ? this.$('.oe-view-manager-sidebar') : undefined, + $pager = this.$('.oe-view-manager-pager'); + + this.$breadcrumbs = this.$('.oe-view-title'); + this.$switch_buttons = this.$('.oe-view-manager-switch button'); + this.$header = this.$('.oe-view-manager-header'); + this.$header_col = this.$header.find('.oe-header-title'); + this.$search_col = this.$header.find('.oe-view-manager-search-view'); + this.$switch_buttons.click(function (event) { + var view_type = $(this).data('view-type'); + if ((view_type === 'form') && (self.active_view.type === 'form')) { + self._display_view(view_type); + } else { + self.switch_mode(view_type); + } + }); + var views_ids = {}; + _.each(this.views, function (view) { + views_ids[view.type] = view.view_id; + view.options = _.extend({ + $buttons: self.$('.oe-' + view.type + '-buttons'), + $sidebar : $sidebar, + $pager : $pager, + action : self.action, + action_views_ids : views_ids, + }, self.flags, self.flags[view.type], view.options); + view.$container = self.$(".oe-view-manager-view-" + view.type); + + // show options.$buttons as views will put their $buttons inside it + // and call show/hide on them + view.options.$buttons.show(); + self.$('.oe-vm-switch-' + view.type).tooltip(); + }); + this.$('.oe_debug_view').click(this.on_debug_changed); + this.$el.addClass("oe_view_manager_" + ((this.action && this.action.target) || 'current')); + + this.search_view_loaded = this.setup_search_view(); + var main_view_loaded = this.switch_mode(default_view, null, default_options); + + return $.when(main_view_loaded, this.search_view_loaded); + }, + + switch_mode: function(view_type, no_store, view_options) { + var self = this, + view = this.views[view_type]; + + if (!view) { + return $.Deferred().reject(); + } + if ((view_type !== 'form') && (view_type !== 'diagram')) { + this.view_stack = []; + } + this.view_stack.push(view); + + // Hide active view (at first rendering, there is no view to hide) + if (this.active_view && this.active_view !== view) { + if (this.active_view.controller) this.active_view.controller.do_hide(); + if (this.active_view.$container) this.active_view.$container.hide(); + } + this.active_view = view; + + if (!view.created) { + view.created = this.create_view.bind(this)(view, view_options); + } + this.active_search = $.Deferred(); + + if (this.searchview && + this.flags.auto_search && + view.controller.searchable !== false) { + $.when(this.search_view_loaded, view.created).done(this.searchview.do_search); + } else { + this.active_search.resolve(); + } + + self.update_header(); + return $.when(view.created, this.active_search).done(function () { + self.active_view = view; + self._display_view(view_options); + self.trigger('switch_mode', view_type, no_store, view_options); + if (self.debug) { + self.$('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { + view: self.active_view.controller, + view_manager: self, + uid: session.uid, + })); + } + }); + }, + update_header: function () { + this.$switch_buttons.removeClass('active'); + this.$('.oe-vm-switch-' + this.active_view.type).addClass('active'); + }, + _display_view: function (view_options) { + var self = this; + this.active_view.$container.show(); + $.when(this.active_view.controller.do_show(view_options)).done(function () { + if (self.searchview) { + var is_hidden = self.active_view.controller.searchable === false; + self.searchview.toggle_visibility(!is_hidden); + self.$header_col.toggleClass('col-md-6', !is_hidden).toggleClass('col-md-12', is_hidden); + self.$search_col.toggle(!is_hidden); + } + self.display_breadcrumbs(); + }); + }, + display_breadcrumbs: function () { + var self = this; + if (!this.action_manager) return; + var breadcrumbs = this.action_manager.get_breadcrumbs(); + if (!breadcrumbs.length) return; + + var $breadcrumbs = breadcrumbs.map(function (bc, index) { + return make_breadcrumb(bc, index === breadcrumbs.length - 1); + }); + + this.$breadcrumbs + .empty() + .append($breadcrumbs); + + function make_breadcrumb (bc, is_last) { + var $bc = $('<li>') + .append(is_last ? bc.title : $('<a>').text(bc.title)) + .toggleClass('active', is_last); + if (!is_last) { + $bc.click(function () { + self.action_manager.select_widget(bc.widget, bc.index); + }); + } + return $bc; + } + }, + create_view: function(view, view_options) { + var self = this, + View = this.registry.get(view.type), + options = _.clone(view.options), + view_loaded = $.Deferred(); + + if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline')) + || (view_options && view_options.mode === 'edit'))) { + options.initial_mode = 'edit'; + } + var controller = new View(this, this.dataset, view.view_id, options), + $container = view.$container; + + $container.hide(); + view.controller = controller; + view.$container = $container; + + if (view.embedded_view) { + controller.set_embedded_view(view.embedded_view); + } + controller.on('switch_mode', this, this.switch_mode.bind(this)); + controller.on('history_back', this, function () { + if (self.action_manager) self.action_manager.trigger('history_back'); + }); + controller.on("change:title", this, function() { + self.display_breadcrumbs(); + }); + controller.on('view_loaded', this, function () { + view_loaded.resolve(); + }); + this.$('.oe-view-manager-pager > span').hide(); + return $.when(controller.appendTo($container), view_loaded) + .done(function () { + self.trigger("controller_inited", view.type, controller); + }); + }, + select_view: function (index) { + var view_type = this.view_stack[index].type; + this.view_stack.splice(index); + return this.switch_mode(view_type); + }, + /** + * @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. + * + * @param {Number|false} view_id the view to use or false for a default one + * @returns {jQuery.Deferred} search view startup deferred + */ + setup_search_view: function() { + if (this.searchview) { + 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 : []; + _.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: this.$('.oe-search-options'), + action: this.action, + }; + this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options); + + this.searchview.on('search_data', this, this.search.bind(this)); + return this.searchview.appendTo(this.$(".oe-view-manager-search-view:first")); + }, + search: function(domains, contexts, groupbys) { + var self = this, + controller = this.active_view.controller, + action_context = this.action.context || {}, + view_context = controller.get_context(); + pyeval.eval_domains_and_contexts({ + domains: [this.action.domain || []].concat(domains || []), + contexts: [action_context, view_context].concat(contexts || []), + group_by_seq: groupbys || [] + }).done(function (results) { + if (results.error) { + self.active_search.resolve(); + throw new Error( + _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s", + JSON.stringify(results.error))); + } + self.dataset._model = new Model( + self.dataset.model, results.context, results.domain); + var groupby = results.group_by.length + ? results.group_by + : action_context.group_by; + if (_.isString(groupby)) { + groupby = [groupby]; + } + if (!controller.grouped && !_.isEmpty(groupby)){ + self.dataset.set_sort([]); + } + $.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() { + self.active_search.resolve(); + }); + }); + }, + do_push_state: function(state) { + if (this.action_manager) { + state.view_type = this.active_view.type; + this.action_manager.do_push_state(state); + } + }, + do_load_state: function(state, warm) { + if (state.view_type && state.view_type !== this.active_view.type) { + // warning: this code relies on the fact that switch_mode has an immediate side + // effect (setting the 'active_view' to its new value) AND an async effect (the + // view is created/loaded). So, the next statement (do_load_state) is executed + // on the new view, after it was initialized, but before it is fully loaded and + // in particular, before the do_show method is called. + this.switch_mode(state.view_type, true); + } + this.active_view.controller.do_load_state(state, warm); + }, + on_debug_changed: function (evt) { + var self = this, + params = $(evt.target).data(), + val = params.action, + current_view = this.active_view.controller; + switch (val) { + case 'fvg': + var dialog = new Dialog(this, { title: _t("Fields View Get") }).open(); + $('<pre>').text(utils.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el); + break; + case 'tests': + this.do_action({ + name: _t("JS Tests"), + target: 'new', + type : 'ir.actions.act_url', + url: '/web/tests?mod=*' + }); + break; + case 'get_metadata': + var ids = current_view.get_selected_ids(); + if (ids.length === 1) { + this.dataset.call('get_metadata', [ids]).done(function(result) { + var dialog = new Dialog(this, { + title: _.str.sprintf(_t("Metadata (%s)"), self.dataset.model), + size: 'medium', + buttons: { + Ok: function() { this.parents('.modal').modal('hide');} + }, + }, QWeb.render('ViewManagerDebugViewLog', { + perm : result[0], + format : formats.format_value + })); + dialog.open(); + }); + } + break; + case 'toggle_layout_outline': + current_view.rendering_engine.toggle_layout_debugging(); + break; + case 'set_defaults': + current_view.open_defaults_dialog(); + break; + case 'translate': + this.do_action({ + name: _t("Technical Translation"), + res_model : 'ir.translation', + domain : [['type', '!=', 'object'], '|', ['name', '=', this.dataset.model], ['name', 'ilike', this.dataset.model + ',']], + views: [[false, 'list'], [false, 'form']], + type : 'ir.actions.act_window', + view_type : "list", + view_mode : "list" + }); + break; + case 'fields': + this.dataset.call('fields_get', [false, {}]).done(function (fields) { + var $root = $('<dl>'); + _(fields).each(function (attributes, name) { + $root.append($('<dt>').append($('<h4>').text(name))); + var $attrs = $('<dl>').appendTo($('<dd>').appendTo($root)); + _(attributes).each(function (def, name) { + if (def instanceof Object) { + def = JSON.stringify(def); + } + $attrs + .append($('<dt>').text(name)) + .append($('<dd style="white-space: pre-wrap;">').text(def)); + }); + }); + new Dialog(self, { + title: _.str.sprintf(_t("Model %s fields"), + self.dataset.model), + buttons: { + Ok: function() { this.parents('.modal').modal('hide');} + }, + }, $root).open(); + }); + break; + case 'edit_workflow': + return this.do_action({ + res_model : 'workflow', + name: _t('Edit Workflow'), + domain : [['osv', '=', this.dataset.model]], + views: [[false, 'list'], [false, 'form'], [false, 'diagram']], + type : 'ir.actions.act_window', + view_type : 'list', + view_mode : 'list' + }); + case 'edit': + this.do_edit_resource(params.model, params.id, evt.target.text); + break; + case 'manage_filters': + this.do_action({ + res_model: 'ir.filters', + name: _t('Manage Filters'), + views: [[false, 'list'], [false, 'form']], + type: 'ir.actions.act_window', + context: { + search_default_my_filters: true, + search_default_model_id: this.dataset.model + } + }); + break; + case 'print_workflow': + if (current_view.get_selected_ids && current_view.get_selected_ids().length == 1) { + framework.blockUI(); + var action = { + context: { active_ids: current_view.get_selected_ids() }, + report_name: "workflow.instance.graph", + datas: { + model: this.dataset.model, + id: current_view.get_selected_ids()[0], + nested: true, + } + }; + this.session.get_file({ + url: '/web/report', + data: {action: JSON.stringify(action)}, + complete: framework.unblockUI + }); + } else { + self.do_warn("Warning", "No record selected."); + } + break; + case 'leave_debug': + window.location.search="?"; + break; + default: + if (val) { + console.warn("No debug handler for ", val); + } + } + }, + do_edit_resource: function(model, id, name) { + this.do_action({ + res_model : model, + res_id : id, + name: name, + type : 'ir.actions.act_window', + view_type : 'form', + view_mode : 'form', + views : [[false, 'form']], + target : 'new', + flags : { + action_buttons : true, + headless: true, + } + }); + }, +}); + +return ViewManager; + +}); |
