[IMP] move action name display to ViewManagerAction, move form's debug (display view...
[odoo/odoo.git] / addons / web / static / src / js / views.js
1 /*---------------------------------------------------------
2  * OpenERP web library
3  *---------------------------------------------------------*/
4
5 openerp.web.views = function(openerp) {
6
7 var _t = openerp.web._t;
8 var QWeb = openerp.web.qweb;
9
10 /**
11  * Registry for all the client actions key: tag value: widget
12  */
13 openerp.web.client_actions = new openerp.web.Registry();
14
15 openerp.web.ActionManager = openerp.web.Widget.extend({
16     identifier_prefix: "actionmanager",
17     init: function(parent) {
18         this._super(parent);
19         this.inner_viewmanager = null;
20         this.dialog = null;
21         this.dialog_viewmanager = null;
22         this.client_widget = null;
23         this.url = {}
24     },
25     render: function() {
26         return "<div id='"+this.element_id+"'></div>";
27     },
28     dialog_stop: function () {
29         if (this.dialog) {
30             this.dialog_viewmanager.stop();
31             this.dialog_viewmanager = null;
32             this.dialog.stop();
33             this.dialog = null;
34         }
35     },
36     content_stop: function () {
37         if (this.inner_viewmanager) {
38             this.inner_viewmanager.stop();
39             this.inner_viewmanager = null;
40         }
41         if (this.client_widget) {
42             this.client_widget.stop();
43             this.client_widget = null;
44         }
45     },
46     url_update: function(action) {
47         // this.url = {
48         //     "model": action.model,
49         //     "domain": action.domain,
50         // };
51         // action.res_model
52         // action.domain
53         // action.context
54         // after
55         // action.views
56         // action.res_id
57         // mode
58         // menu
59     },
60     url_stringify: function(action) {
61     },
62     url_parse: function(action) {
63     },
64     on_url_update: function(url) {
65     },
66     do_url_action: function(url) {
67     },
68     do_action: function(action, on_close) {
69         var type = action.type.replace(/\./g,'_');
70         var popup = action.target === 'new';
71         action.flags = _.extend({
72             views_switcher : !popup,
73             search_view : !popup,
74             action_buttons : !popup,
75             sidebar : !popup,
76             pager : !popup
77         }, action.flags || {});
78         if (!(type in this)) {
79             console.log("Action manager can't handle action of type " + action.type, action);
80             return;
81         }
82         this[type](action, on_close);
83     },
84     ir_actions_act_window: function (action, on_close) {
85         if (action.target === 'new') {
86             if (this.dialog == null) {
87                 this.dialog = new openerp.web.Dialog(this, { title: action.name, width: '80%' });
88                 if(on_close)
89                     this.dialog.on_close.add(on_close);
90                 this.dialog.start();
91             } else {
92                 this.dialog_viewmanager.stop();
93             }
94             this.dialog_viewmanager = new openerp.web.ViewManagerAction(this, action);
95             this.dialog_viewmanager.appendTo(this.dialog.$element);
96             this.dialog.open();
97         } else  {
98             this.dialog_stop();
99             this.content_stop();
100             this.inner_viewmanager = new openerp.web.ViewManagerAction(this, action);
101             this.inner_viewmanager.appendTo(this.$element);
102             this.url_update(action);
103         }
104         /* new window code
105             this.rpc("/web/session/save_session_action", { the_action : action}, function(key) {
106                 var url = window.location.protocol + "//" + window.location.host + window.location.pathname + "?" + jQuery.param({ s_action : "" + key });
107                 window.open(url,'_blank');
108             });
109         */
110     },
111     ir_actions_act_window_close: function (action, on_closed) {
112         this.dialog_stop();
113     },
114     ir_actions_server: function (action, on_closed) {
115         var self = this;
116         this.rpc('/web/action/run', {
117             action_id: action.id,
118             context: action.context || {}
119         }).then(function (action) {
120             self.do_action(action, on_closed)
121         });
122     },
123     ir_actions_client: function (action) {
124         this.content_stop();
125         var ClientWidget = openerp.web.client_actions.get_object(action.tag);
126         (this.client_widget = new ClientWidget(this, action.params)).appendTo(this);
127     },
128     ir_actions_report_xml: function(action) {
129         $.blockUI();
130         this.session.get_file({
131             url: '/web/report',
132             data: {action: JSON.stringify(action)},
133             complete: $.unblockUI
134         });
135     }
136 });
137
138 openerp.web.ViewManager =  openerp.web.Widget.extend(/** @lends openerp.web.ViewManager# */{
139     identifier_prefix: "viewmanager",
140     template: "ViewManager",
141     /**
142      * @constructs openerp.web.ViewManager
143      * @extends openerp.web.Widget
144      *
145      * @param parent
146      * @param dataset
147      * @param views
148      */
149     init: function(parent, dataset, views) {
150         this._super(parent);
151         this.model = dataset.model;
152         this.dataset = dataset;
153         this.searchview = null;
154         this.active_view = null;
155         this.views_src = _.map(views, function(x) {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;});
156         this.views = {};
157         this.flags = this.flags || {};
158         this.registry = openerp.web.views;
159     },
160     render: function() {
161         return QWeb.render(this.template, {
162             self: this,
163             prefix: this.element_id,
164             views: this.views_src});
165     },
166     /**
167      * @returns {jQuery.Deferred} initial view loading promise
168      */
169     start: function() {
170         this._super();
171         var self = this;
172         this.dataset.start();
173         this.$element.find('.oe_vm_switch button').click(function() {
174             self.on_mode_switch($(this).data('view-type'));
175         });
176         var views_ids = {};
177         _.each(this.views_src, function(view) {
178             self.views[view.view_type] = $.extend({}, view, {
179                 controller : null,
180                 options : _.extend({
181                     sidebar_id : self.element_id + '_sidebar_' + view.view_type,
182                     action : self.action,
183                     action_views_ids : views_ids
184                 }, self.flags, view.options || {})
185             });
186             views_ids[view.view_type] = view.view_id;
187         });
188         if (this.flags.views_switcher === false) {
189             this.$element.find('.oe_vm_switch').hide();
190         }
191         // switch to the first one in sequence
192         return this.on_mode_switch(this.views_src[0].view_type);
193     },
194     /**
195      * Asks the view manager to switch visualization mode.
196      *
197      * @param {String} view_type type of view to display
198      * @returns {jQuery.Deferred} new view loading promise
199      */
200     on_mode_switch: function(view_type) {
201         var self = this,
202             view_promise;
203         this.active_view = view_type;
204         var view = this.views[view_type];
205         if (!view.controller) {
206             // Lazy loading of views
207             var controllerclass = this.registry.get_object(view_type);
208             var controller = new controllerclass(this, this.element_id + '_view_' + view_type,
209                 this.dataset, view.view_id, view.options);
210             if (view.embedded_view) {
211                 controller.set_embedded_view(view.embedded_view);
212             }
213             controller.do_switch_view.add_last(this.on_mode_switch);
214             if (view_type === 'list' && this.flags.search_view === false && this.action && this.action['auto_search']) {
215                 // In case the search view is not instantiated: manually call ListView#search
216                 var domains = !_(self.action.domain).isEmpty()
217                                 ? [self.action.domain] : [],
218                    contexts = !_(self.action.context).isEmpty()
219                                 ? [self.action.context] : [];
220                 controller.on_loaded.add({
221                     callback: function () {
222                         controller.do_search(domains, contexts, []);
223                     },
224                     position: 'last',
225                     unique: true
226                 });
227             }
228             view_promise = controller.start();
229             $.when(view_promise).then(function() {
230                 self.on_controller_inited(view_type, controller);
231             });
232             this.views[view_type].controller = controller;
233         }
234
235
236         if (this.searchview) {
237             if (view.controller.searchable === false) {
238                 this.searchview.hide();
239             } else {
240                 this.searchview.show();
241             }
242         }
243
244         this.$element
245             .find('.views-switchers button').removeAttr('disabled')
246             .filter('[data-view-type="' + view_type + '"]')
247             .attr('disabled', true);
248
249         for (var view_name in this.views) {
250             if (!this.views.hasOwnProperty(view_name)) { continue; }
251             if (this.views[view_name].controller) {
252                 if (view_name === view_type) {
253                     $.when(view_promise).then(this.views[view_name].controller.do_show);
254                 } else {
255                     this.views[view_name].controller.do_hide();
256                 }
257             }
258         }
259         return view_promise;
260     },
261     /**
262      * Event launched when a controller has been inited.
263      *
264      * @param {String} view_type type of view
265      * @param {String} view the inited controller
266      */
267     on_controller_inited: function(view_type, view) {
268     },
269     /**
270      * Sets up the current viewmanager's search view.
271      *
272      * @param {Number|false} view_id the view to use or false for a default one
273      * @returns {jQuery.Deferred} search view startup deferred
274      */
275     setup_search_view: function(view_id, search_defaults) {
276         var self = this;
277         if (this.searchview) {
278             this.searchview.stop();
279         }
280         this.searchview = new openerp.web.SearchView(
281                 this, this.element_id + "_search", this.dataset,
282                 view_id, search_defaults);
283
284         this.searchview.on_search.add(function(domains, contexts, groupbys) {
285             var controller = self.views[self.active_view].controller;
286             controller.do_search.call(controller, domains, contexts, groupbys);
287         });
288         return this.searchview.start();
289     },
290     /**
291      * Called when one of the view want to execute an action
292      */
293     on_action: function(action) {
294     },
295     on_create: function() {
296     },
297     on_remove: function() {
298     },
299     on_edit: function() {
300     }
301 });
302
303 openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{
304     template:"ViewManagerAction",
305     /**
306      * @constructs openerp.web.ViewManagerAction
307      * @extends openerp.web.ViewManager
308      *
309      * @param {openerp.web.ActionManager} parent parent object/widget
310      * @param {Object} action descriptor for the action this viewmanager needs to manage its views.
311      */
312     init: function(parent, action) {
313         // dataset initialization will take the session from ``this``, so if we
314         // do not have it yet (and we don't, because we've not called our own
315         // ``_super()``) rpc requests will blow up.
316         this.session = parent.session;
317         this.action = action;
318         var dataset = new openerp.web.DataSetSearch(this, action.res_model, action.context, action.domain);
319         if (action.res_id) {
320             dataset.ids.push(action.res_id);
321             dataset.index = 0;
322         }
323         this._super(parent, dataset, action.views);
324         this.flags = this.action.flags || {};
325         if (action.res_model == 'board.board' && action.views.length == 1 && action.views) {
326             // Not elegant but allows to avoid form chrome (pager, save/new
327             // buttons, sidebar, ...) displaying
328             this.flags.search_view = this.flags.pager = this.flags.sidebar = this.flags.action_buttons = false;
329         }
330     },
331     /**
332      * Initializes the ViewManagerAction: sets up the searchview (if the
333      * searchview is enabled in the manager's action flags), calls into the
334      * parent to initialize the primary view and (if the VMA has a searchview)
335      * launches an initial search after both views are done rendering.
336      */
337     start: function() {
338         var self = this;
339         this.$element.find('.oe_get_xml_view').click(function () {
340             // TODO: add search view?
341             $('<pre>').text(openerp.web.json_node_to_xml(
342                 self.views[self.active_view].controller.fields_view.arch, true))
343                     .dialog({ width: '95%'});
344         });
345
346         var searchview_loaded;
347         if (this.flags.search_view !== false) {
348             var search_defaults = {};
349             _.each(this.action.context, function (value, key) {
350                 var match = /^search_default_(.*)$/.exec(key);
351                 if (match) {
352                     search_defaults[match[1]] = value;
353                 }
354             });
355             // init search view
356             var searchview_id = this.action['search_view_id'] && this.action['search_view_id'][0];
357
358             searchview_loaded = this.setup_search_view(
359                     searchview_id || false, search_defaults);
360         }
361
362         var main_view_loaded = this._super();
363
364         var manager_ready = $.when(searchview_loaded, main_view_loaded);
365         if (searchview_loaded && this.action['auto_search']) {
366             // schedule auto_search
367             manager_ready.then(this.searchview.do_search);
368         }
369         return manager_ready;
370     },
371     on_mode_switch: function (view_type) {
372         var self = this;
373         return $.when(
374             this._super(view_type),
375             this.shortcut_check(this.views[view_type])).then(function () {
376                 var view_id = self.views[self.active_view].controller.fields_view.view_id;
377                 self.$element.find('.oe_get_xml_view span').text(view_id);
378         });
379     },
380     shortcut_check : function(view) {
381         var self = this;
382         var grandparent = this.widget_parent && this.widget_parent.widget_parent;
383         // display shortcuts if on the first view for the action
384         var $shortcut_toggle = this.$element.find('.oe-shortcut-toggle');
385         if (!(grandparent instanceof openerp.web.WebClient) ||
386             !(view.view_type === this.views_src[0].view_type
387                 && view.view_id === this.views_src[0].view_id)) {
388             $shortcut_toggle.hide();
389             return;
390         }
391         $shortcut_toggle.removeClass('oe-shortcut-remove').show();
392         if (_(this.session.shortcuts).detect(function (shortcut) {
393                     return shortcut.res_id === self.session.active_id; })) {
394             $shortcut_toggle.addClass("oe-shortcut-remove");
395         }
396         this.shortcut_add_remove();
397     },
398     shortcut_add_remove: function() {
399         var self = this;
400         var $shortcut_toggle = this.$element.find('.oe-shortcut-toggle');
401         $shortcut_toggle
402             .unbind("click")
403             .click(function() {
404                 if ($shortcut_toggle.hasClass("oe-shortcut-remove")) {
405                     $(self.session.shortcuts.binding).trigger('remove-current');
406                     $shortcut_toggle.removeClass("oe-shortcut-remove");
407                 } else {
408                     $(self.session.shortcuts.binding).trigger('add', {
409                         'user_id': self.session.uid,
410                         'res_id': self.session.active_id,
411                         'resource': 'ir.ui.menu',
412                         'name': self.action.name
413                     });
414                     $shortcut_toggle.addClass("oe-shortcut-remove");
415                 }
416             });
417     }
418 });
419
420 openerp.web.Sidebar = openerp.web.Widget.extend({
421     init: function(parent, element_id) {
422         this._super(parent, element_id);
423         this.items = {};
424         this.sections = {};
425     },
426     start: function() {
427         this._super(this);
428         var self = this;
429         this.$element.html(QWeb.render('Sidebar'));
430         this.$element.find(".toggle-sidebar").click(function(e) {
431             self.do_toggle();
432         });
433     },
434     add_toolbar: function(toolbar) {
435         var self = this;
436         _.each([['print', "Reports"], ['action', "Actions"], ['relate', "Links"]], function(type) {
437             var items = toolbar[type[0]];
438             if (items.length) {
439                 for (var i = 0; i < items.length; i++) {
440                     items[i] = {
441                         label: items[i]['name'],
442                         action: items[i],
443                         classname: 'oe_sidebar_' + type[0]
444                     }
445                 }
446                 self.add_section(type[0], type[1], items);
447             }
448         });
449     },
450     add_section: function(code, name, items) {
451         // For each section, we pass a name/label and optionally an array of items.
452         // If no items are passed, then the section will be created as a custom section
453         // returning back an element_id to be used by a custom controller.
454         // Else, the section is a standard section with items displayed as links.
455         // An item is a dictonary : {
456         //    label: label to be displayed for the link,
457         //    action: action to be launch when the link is clicked,
458         //    callback: a function to be executed when the link is clicked,
459         //    classname: optional dom class name for the line,
460         //    title: optional title for the link
461         // }
462         // Note: The item should have one action or/and a callback
463         var self = this,
464             section_id = _.uniqueId(this.element_id + '_section_' + code + '_');
465         if (items) {
466             for (var i = 0; i < items.length; i++) {
467                 items[i].element_id = _.uniqueId(section_id + '_item_');
468                 this.items[items[i].element_id] = items[i];
469             }
470         }
471         var $section = $(QWeb.render("Sidebar.section", {
472             section_id: section_id,
473             name: name,
474             classname: 'oe_sidebar_' + code,
475             items: items
476         }));
477         if (items) {
478             $section.find('a.oe_sidebar_action_a').click(function() {
479                 var item = self.items[$(this).attr('id')];
480                 if (item.callback) {
481                     item.callback();
482                 }
483                 if (item.action) {
484                     var ids = self.widget_parent.get_selected_ids();
485                     if (ids.length == 0) {
486                         //TODO: make prettier warning?
487                         $("<div />").text(_t("You must choose at least one record.")).dialog({
488                             title: _t("Warning"),
489                             modal: true
490                         });
491                         return false;
492                     }
493                     var additional_context = {
494                         active_id: ids[0],
495                         active_ids: ids,
496                         active_model: self.widget_parent.dataset.model
497                     };
498                     self.rpc("/web/action/load", {
499                         action_id: item.action.id,
500                         context: additional_context
501                     }, function(result) {
502                         result.result.context = _.extend(result.result.context || {},
503                             additional_context);
504                         result.result.flags = result.result.flags || {};
505                         result.result.flags.new_window = true;
506                         self.do_action(result.result);
507                     });
508                 }
509                 return false;
510             });
511         }
512         $section.appendTo(this.$element.find('div.sidebar-actions'));
513         this.sections[code] = $section;
514         return section_id;
515     },
516     do_fold: function() {
517         this.$element.addClass('closed-sidebar').removeClass('open-sidebar');
518     },
519     do_unfold: function() {
520         this.$element.addClass('open-sidebar').removeClass('closed-sidebar');
521     },
522     do_toggle: function() {
523         this.$element.toggleClass('open-sidebar closed-sidebar');
524     }
525 });
526
527 openerp.web.TranslateDialog = openerp.web.Dialog.extend({
528     dialog_title: _t("Translations"),
529     init: function(view) {
530         // TODO fme: should add the language to fields_view_get because between the fields view get
531         // and the moment the user opens the translation dialog, the user language could have been changed
532         this.view_language = view.session.user_context.lang;
533         this['on_button' + _t("Save")] = this.on_button_Save;
534         this['on_button' + _t("Close")] = this.on_button_Close;
535         this._super(view, {
536             width: '80%',
537             height: '80%'
538         });
539         this.view = view;
540         this.view_type = view.fields_view.type || '';
541         this.$fields_form = null;
542         this.$view_form = null;
543         this.$sidebar_form = null;
544         this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name });
545         this.languages = null;
546         this.languages_loaded = $.Deferred();
547         (new openerp.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
548             [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }, this.on_languages_loaded);
549     },
550     start: function() {
551         var self = this;
552         this._super();
553         $.when(this.languages_loaded).then(function() {
554             self.$element.html(QWeb.render('TranslateDialog', { widget: self }));
555             self.$element.tabs();
556             if (!(self.view.translatable_fields && self.view.translatable_fields.length)) {
557                 self.hide_tabs('fields');
558                 self.select_tab('view');
559             }
560             self.$fields_form = self.$element.find('.oe_translation_form');
561             self.$fields_form.find('.oe_trad_field').change(function() {
562                 $(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value')));
563             });
564         });
565         return this;
566     },
567     on_languages_loaded: function(langs) {
568         this.languages = langs;
569         this.languages_loaded.resolve();
570     },
571     do_load_fields_values: function(callback) {
572         var self = this,
573             deffered = [];
574         this.$fields_form.find('.oe_trad_field').val('').removeClass('touched');
575         _.each(self.languages, function(lg) {
576             var deff = $.Deferred();
577             deffered.push(deff);
578             var callback = function(values) {
579                 _.each(self.translatable_fields_keys, function(f) {
580                     self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || '').attr('data-value', values[0][f] || '');
581                 });
582                 deff.resolve();
583             };
584             if (lg.code === self.view_language) {
585                 var values = {};
586                 _.each(self.translatable_fields_keys, function(field) {
587                     values[field] = self.view.fields[field].get_value();
588                 });
589                 callback([values]);
590             } else {
591                 self.rpc('/web/dataset/get', {
592                     model: self.view.dataset.model,
593                     ids: [self.view.datarecord.id],
594                     fields: self.translatable_fields_keys,
595                     context: self.view.dataset.get_context({
596                         'lang': lg.code
597                     })}, callback);
598             }
599         });
600         $.when.apply(null, deffered).then(callback);
601     },
602     show_tabs: function() {
603         for (var i = 0; i < arguments.length; i++) {
604             this.$element.find('ul.oe_translate_tabs li a[href$="' + arguments[i] + '"]').parent().show();
605         }
606     },
607     hide_tabs: function() {
608         for (var i = 0; i < arguments.length; i++) {
609             this.$element.find('ul.oe_translate_tabs li a[href$="' + arguments[i] + '"]').parent().hide();
610         }
611     },
612     select_tab: function(name) {
613         this.show_tabs(name);
614         var index = this.$element.find('ul.oe_translate_tabs li a[href$="' + arguments[i] + '"]').parent().index() - 1;
615         this.$element.tabs('select', index);
616     },
617     open: function(field) {
618         var self = this,
619             sup = this._super;
620         $.when(this.languages_loaded).then(function() {
621             if (self.view.translatable_fields && self.view.translatable_fields.length) {
622                 self.do_load_fields_values(function() {
623                     sup.call(self);
624                     if (field) {
625                         // TODO: focus and scroll to field
626                     }
627                 });
628             } else {
629                 sup.call(self);
630             }
631         });
632     },
633     on_button_Save: function() {
634         var trads = {},
635             self = this;
636         self.$fields_form.find('.oe_trad_field.touched').each(function() {
637             var field = $(this).attr('name').split('-');
638             if (!trads[field[0]]) {
639                 trads[field[0]] = {};
640             }
641             trads[field[0]][field[1]] = $(this).val();
642         });
643         _.each(trads, function(data, code) {
644             if (code === self.view_language) {
645                 _.each(data, function(value, field) {
646                     self.view.fields[field].set_value(value);
647                 });
648             } else {
649                 self.view.dataset.write(self.view.datarecord.id, data, { 'lang': code });
650             }
651         });
652         this.close();
653     },
654     on_button_Close: function() {
655         this.close();
656     }
657 });
658
659 /**
660  * @class
661  * @extends openerp.web.Widget
662  */
663 openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
664     set_default_options: function(options) {
665         this.options = options || {};
666         _.defaults(this.options, {
667             // All possible views options should be defaulted here
668             sidebar_id: null,
669             sidebar: true,
670             action: null,
671             action_views_ids: {}
672         });
673     },
674     open_translate_dialog: function(field) {
675         if (!this.translate_dialog) {
676             this.translate_dialog = new openerp.web.TranslateDialog(this).start();
677         }
678         this.translate_dialog.open(field);
679     },
680     /**
681      * Fetches and executes the action identified by ``action_data``.
682      *
683      * @param {Object} action_data the action descriptor data
684      * @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
685      * @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
686      * @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
687      * @param {Object} [action_data.context=null] additional action context, to add to the current context
688      * @param {openerp.web.DataSet} dataset a dataset object used to communicate with the server
689      * @param {Object} [record_id] the identifier of the object on which the action is to be applied
690      * @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action)
691      */
692     execute_action: function (action_data, dataset, record_id, on_closed) {
693         var self = this;
694         var handler = function (r) {
695             var action = r.result;
696             if (action && action.constructor == Object) {
697                 action.context = action.context || {};
698                 _.extend(action.context, {
699                     active_id: record_id || false,
700                     active_ids: [record_id || false],
701                     active_model: dataset.model
702                 });
703                 action.context = new openerp.web.CompoundContext(dataset.get_context(), action.context);
704                 self.do_action(action, on_closed);
705             } else if (on_closed) {
706                 on_closed(action);
707             }
708         };
709
710         var context = new openerp.web.CompoundContext(dataset.get_context(), action_data.context || {});
711
712         if (action_data.special) {
713             handler({result: {"type":"ir.actions.act_window_close"}});
714         } else if (action_data.type=="object") {
715             return dataset.call_button(action_data.name, [[record_id], context], handler);
716         } else if (action_data.type=="action") {
717             return this.rpc('/web/action/load', { action_id: parseInt(action_data.name, 10), context: context }, handler);
718         } else  {
719             return dataset.exec_workflow(record_id, action_data.name, handler);
720         }
721     },
722     /**
723      * Directly set a view to use instead of calling fields_view_get. This method must
724      * be called before start(). When an embedded view is set, underlying implementations
725      * of openerp.web.View must use the provided view instead of any other one.
726      *
727      * @param embedded_view A view.
728      */
729     set_embedded_view: function(embedded_view) {
730         this.embedded_view = embedded_view;
731         this.options.sidebar = false;
732     },
733     do_switch_view: function(view) {
734     },
735     set_common_sidebar_sections: function(sidebar) {
736         sidebar.add_section('customize', "Customize", [
737             {
738                 label: "Manage Views",
739                 callback: this.on_sidebar_manage_view,
740                 title: "Manage views of the current object"
741             }, {
742                 label: "Edit Workflow",
743                 callback: this.on_sidebar_edit_workflow,
744                 title: "Manage views of the current object",
745                 classname: 'oe_hide oe_sidebar_edit_workflow'
746             }, {
747                 label: "Customize Object",
748                 callback: this.on_sidebar_customize_object,
749                 title: "Manage views of the current object"
750             }
751         ]);
752         sidebar.add_section('other', "Other Options", [
753             {
754                 label: "Import",
755                 callback: this.on_sidebar_import
756             }, {
757                 label: "Export",
758                 callback: this.on_sidebar_export
759             }, {
760                 label: "Translate",
761                 callback: this.on_sidebar_translate,
762                 classname: 'oe_sidebar_translate oe_hide'
763             }, {
764                 label: "View Log",
765                 callback: this.on_sidebar_view_log,
766                 classname: 'oe_hide oe_sidebar_view_log'
767             }
768         ]);
769     },
770     on_sidebar_manage_view: function() {
771         if (this.fields_view && this.fields_view.arch) {
772             $('<xmp>' + openerp.web.json_node_to_xml(this.fields_view.arch, true) + '</xmp>').dialog({ width: '95%', height: 600});
773         } else {
774             this.notification.warn("Manage Views", "Could not find current view declaration");
775         }
776     },
777     on_sidebar_edit_workflow: function() {
778         console.log('Todo');
779     },
780     on_sidebar_customize_object: function() {
781         console.log('Todo');
782     },
783     on_sidebar_import: function() {
784     },
785     on_sidebar_export: function() {
786         var export_view = new openerp.web.DataExport(this, this.dataset);
787         export_view.start();
788     },
789     on_sidebar_translate: function() {
790         this.open_translate_dialog();
791     },
792     on_sidebar_view_log: function() {
793     }
794 });
795
796 /**
797  * Registry for all the main views
798  */
799 openerp.web.views = new openerp.web.Registry();
800
801 openerp.web.json_node_to_xml = function(node, single_quote, indent) {
802     // For debugging purpose, this function will convert a json node back to xml
803     // Maybe usefull for xml view editor
804
805     if (typeof(node) === 'string') {
806         return node;
807     }
808     else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) {
809         throw("Node a json node");
810     }
811     indent = indent || 0;
812     var sindent = new Array(indent + 1).join('\t'),
813         r = sindent + '<' + node.tag;
814     for (var attr in node.attrs) {
815         var vattr = node.attrs[attr];
816         if (typeof(vattr) !== 'string') {
817             // domains, ...
818             vattr = JSON.stringify(vattr);
819         }
820         vattr = vattr.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
821         if (single_quote) {
822             vattr = vattr.replace(/&quot;/g, "'");
823         }
824         r += ' ' + attr + '="' + vattr + '"';
825     }
826     if (node.children && node.children.length) {
827         r += '>\n';
828         var childs = [];
829         for (var i = 0, ii = node.children.length; i < ii; i++) {
830             childs.push(openerp.web.json_node_to_xml(node.children[i], single_quote, indent + 1));
831         }
832         r += childs.join('\n');
833         r += '\n' + sindent + '</' + node.tag + '>';
834         return r;
835     } else {
836         return r + '/>';
837     }
838 }
839
840 };
841
842 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: