[IMP] web: trigger an event when webclient is ready
The main purpose of this commit is to be consistent with the enterprise edition, in which the event 'DOM_updated' is triggered when new content is inserted in the DOM. This way, addons that need to know when a view has been attached in the DOM (e.g. web_tip) can listen to this event and will work fine with both editions of the web addon. With the actual structure of the webclient (action_manager, view_manager), there is no easy way to decide whether or not a view is attached in the DOM, because every rendering is performed in a detached element. For instance, when a view_manager instantiates a new view, it appends it in its $el once the view is ready, but it can't know if its $el is attached in the DOM (it is only the case if the view_manager is the current action of the action manager). This rev. introduces utility functions to centralize the appending/prepending of content, so that an event can be triggered (at a unique place) when the content is really in the DOM. These functions are used by the the action_manager and the view_manager. Another change is that the action_manager keeps the view_manager informed when they are attached in/detached from the DOM, through the variable is_in_DOM.
This commit is contained in:
@@ -43,6 +43,18 @@ var Action = core.Class.extend({
|
||||
set_on_reverse_breadcrumb: function(on_reverse_breadcrumb) {
|
||||
this.on_reverse_breadcrumb = on_reverse_breadcrumb;
|
||||
},
|
||||
/**
|
||||
* Stores the DOM fragment of the action
|
||||
* @param {jQuery} [fragment] the DOM fragment
|
||||
*/
|
||||
set_fragment: function($fragment) {
|
||||
this.$fragment = $fragment;
|
||||
},
|
||||
/**
|
||||
* Not implemented for client actions
|
||||
*/
|
||||
set_is_in_DOM: function() {
|
||||
},
|
||||
/**
|
||||
* @return {Object} the description of the action
|
||||
*/
|
||||
@@ -61,6 +73,12 @@ var Action = core.Class.extend({
|
||||
get_nb_views: function() {
|
||||
return 0;
|
||||
},
|
||||
/**
|
||||
* @return {jQuery} the DOM fragment of the action
|
||||
*/
|
||||
get_fragment: function() {
|
||||
return this.$fragment;
|
||||
},
|
||||
});
|
||||
/**
|
||||
* Specialization of Action for client actions that are Widgets
|
||||
@@ -108,6 +126,13 @@ var ViewManagerAction = WidgetAction.extend({
|
||||
_super.call(self);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Sets is_in_DOM on this.widget
|
||||
* @param {Boolean} [is_in_DOM] true iff the widget is attached in the DOM
|
||||
*/
|
||||
set_is_in_DOM: function(is_in_DOM) {
|
||||
this.widget.is_in_DOM = is_in_DOM;
|
||||
},
|
||||
/**
|
||||
* @return {Array} array of Objects that will be interpreted to display the breadcrumbs
|
||||
*/
|
||||
@@ -215,7 +240,15 @@ var ActionManager = Widget.extend({
|
||||
// document only when it's ready
|
||||
var new_widget_fragment = document.createDocumentFragment();
|
||||
return $.when(this.inner_widget.appendTo(new_widget_fragment)).done(function() {
|
||||
self.$el.append(new_widget_fragment);
|
||||
// Detach the fragment of the previous action and store it within the action
|
||||
if (old_action) {
|
||||
old_action.set_fragment(old_widget.$el.detach());
|
||||
old_action.set_is_in_DOM(false);
|
||||
}
|
||||
|
||||
framework.append(self.$el, new_widget_fragment, true);
|
||||
self.inner_action.set_is_in_DOM(true);
|
||||
|
||||
// Hide the old_widget as it will be removed from the DOM when it
|
||||
// is destroyed
|
||||
if (old_widget) {
|
||||
@@ -269,17 +302,28 @@ var ActionManager = Widget.extend({
|
||||
select_action: function(action, index) {
|
||||
var self = this;
|
||||
return this.webclient.clear_uncommitted_changes().then(function() {
|
||||
// Set the new inner_widget and clear the action_stack
|
||||
self.inner_widget = action.widget;
|
||||
// Set the new inner_action/widget and update the action stack
|
||||
var old_action = self.inner_action;
|
||||
var action_index = self.action_stack.indexOf(action);
|
||||
self.clear_action_stack(self.action_stack.splice(action_index + 1));
|
||||
var to_destroy = self.action_stack.splice(action_index + 1);
|
||||
self.inner_action = action;
|
||||
self.inner_widget = action.widget;
|
||||
|
||||
// Hide the ControlPanel if the widget doesn't use it
|
||||
if (!self.inner_widget.need_control_panel) {
|
||||
self.main_control_panel.do_hide();
|
||||
}
|
||||
|
||||
return action.restore(index);
|
||||
return $.when(action.restore(index)).done(function() {
|
||||
if (action !== old_action) {
|
||||
// Clear the action stack (this also removes the current action from the DOM)
|
||||
self.clear_action_stack(to_destroy);
|
||||
|
||||
// Append the fragment of the action to restore to self.$el
|
||||
framework.append(self.$el, action.get_fragment(), true);
|
||||
self.inner_action.set_is_in_DOM(true);
|
||||
}
|
||||
});
|
||||
}).fail(function() {
|
||||
return $.Deferred().reject();
|
||||
});
|
||||
|
||||
@@ -322,10 +322,47 @@ $.extend( proto, {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Private function that triggers an event on core.bus
|
||||
* @param {htmlString or Element or Array or jQuery} [content] the content that
|
||||
* has been attached in the DOM
|
||||
*/
|
||||
function _notify (content) {
|
||||
core.bus.trigger('DOM_updated', content);
|
||||
}
|
||||
/**
|
||||
* Appends content in a jQuery object and optionnally triggers an event
|
||||
* @param {jQuery} [$target] the node where content will be appended
|
||||
* @param {htmlString or Element or Array or jQuery} [content] DOM element,
|
||||
* array of elements, HTML string or jQuery object to append to $target
|
||||
* @param {jQuery} [trigger] true to trigger an event, false otherwise
|
||||
*/
|
||||
function append ($target, content, trigger) {
|
||||
$target.append(content);
|
||||
if (trigger) {
|
||||
_notify(content);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Prepends content in a jQuery object and optionnally triggers an event
|
||||
* @param {jQuery} [$target] the node where content will be prepended
|
||||
* @param {htmlString or Element or Array or jQuery} [content] DOM element,
|
||||
* array of elements, HTML string or jQuery object to prepend to $target
|
||||
* @param {jQuery} [trigger] true to trigger an event, false otherwise
|
||||
*/
|
||||
function prepend ($target, content, trigger) {
|
||||
$target.prepend(content);
|
||||
if (trigger) {
|
||||
_notify(content);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blockUI: blockUI,
|
||||
unblockUI: unblockUI,
|
||||
redirect: redirect,
|
||||
append: append,
|
||||
prepend: prepend,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -166,18 +166,9 @@ var View = Widget.extend({
|
||||
this.embedded_view = embedded_view;
|
||||
},
|
||||
do_show: function () {
|
||||
var self = this;
|
||||
this.$el.show();
|
||||
setTimeout(function () {
|
||||
self.$el.parent().addClass('in');
|
||||
}, 0);
|
||||
|
||||
this._super();
|
||||
core.bus.trigger('view_shown', this);
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$el.parent().removeClass('in');
|
||||
this.$el.hide();
|
||||
},
|
||||
is_active: function () {
|
||||
return this.ViewManager.active_view.controller === this;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ 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');
|
||||
var SearchView = require('web.SearchView');
|
||||
@@ -45,6 +46,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
this.active_view = null;
|
||||
this.registry = core.view_registry;
|
||||
this.title = this.action && 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;
|
||||
var View = core.view_registry.get(view_type, true);
|
||||
@@ -120,6 +122,8 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
switch_mode: function(view_type, no_store, view_options) {
|
||||
var self = this;
|
||||
var view = this.views[view_type];
|
||||
var old_view = this.active_view;
|
||||
var switched = $.Deferred();
|
||||
|
||||
if (!view) {
|
||||
return $.Deferred().reject();
|
||||
@@ -153,19 +157,23 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
self.searchview.do_search();
|
||||
});
|
||||
}
|
||||
return $.when(view.created, this.active_search).done(function () {
|
||||
self._display_view(view_options);
|
||||
self.trigger('switch_mode', view_type, no_store, view_options);
|
||||
$.when(view.created, this.active_search).done(function () {
|
||||
self._display_view(view_options, old_view).done(function() {
|
||||
self.trigger('switch_mode', view_type, no_store, view_options);
|
||||
switched.resolve();
|
||||
});
|
||||
});
|
||||
return switched;
|
||||
},
|
||||
_display_view: function (view_options) {
|
||||
_display_view: function (view_options, old_view) {
|
||||
var self = this;
|
||||
var view_controller = this.active_view.controller;
|
||||
var view_fragment = this.active_view.$fragment;
|
||||
var view_control_elements = this.render_view_control_elements();
|
||||
|
||||
// Show the view
|
||||
this.active_view.$container.show();
|
||||
$.when(view_controller.do_show(view_options)).done(function () {
|
||||
return $.when(view_controller.do_show(view_options)).done(function () {
|
||||
// Prepare the ControlPanel content and update it
|
||||
var cp_status = {
|
||||
active_view_selector: '.oe-cp-switch-' + self.active_view.type,
|
||||
@@ -176,6 +184,15 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
search_view_hidden: view_controller.searchable === false,
|
||||
};
|
||||
self.update_control_panel(cp_status);
|
||||
|
||||
// Detach the old view but not ui-autocomplete elements to let
|
||||
// jquery-ui garbage-collect them
|
||||
if (old_view) {
|
||||
old_view.$container.contents().not('.ui-autocomplete').detach();
|
||||
}
|
||||
|
||||
// Append the view fragment to its $container
|
||||
framework.append(self.active_view.$container, view_fragment, self.is_in_DOM);
|
||||
});
|
||||
},
|
||||
create_view: function(view, view_options) {
|
||||
@@ -189,11 +206,8 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
options.initial_mode = options.initial_mode || 'edit';
|
||||
}
|
||||
var controller = new View(this, this.dataset, view.view_id, options);
|
||||
var $container = view.$container;
|
||||
|
||||
$container.hide();
|
||||
view.controller = controller;
|
||||
view.$container = $container;
|
||||
view.$fragment = $('<div>');
|
||||
|
||||
if (view.embedded_view) {
|
||||
controller.set_embedded_view(view.embedded_view);
|
||||
@@ -211,10 +225,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
|
||||
controller.on('view_loaded', this, function () {
|
||||
view_loaded.resolve();
|
||||
});
|
||||
return $.when(controller.appendTo($container), view_loaded)
|
||||
.done(function () {
|
||||
self.trigger("controller_inited", view.type, controller);
|
||||
});
|
||||
|
||||
// render the view in a fragment so that it is appended in the view's
|
||||
// $container only when it's ready
|
||||
return $.when(controller.appendTo(view.$fragment), view_loaded).done(function () {
|
||||
// Remove the unnecessary outer div
|
||||
view.$fragment = view.$fragment.contents();
|
||||
self.trigger("controller_inited", view.type, controller);
|
||||
});
|
||||
},
|
||||
select_view: function (index) {
|
||||
var view_type = this.view_stack[index].type;
|
||||
|
||||
@@ -480,7 +480,7 @@
|
||||
<div t-name="ViewManager" class="oe-view-manager">
|
||||
<div class="oe-view-manager-content">
|
||||
<t t-foreach="widget.views" t-as="view">
|
||||
<div t-attf-class="oe-view-manager-view-#{view} fade"/>
|
||||
<div t-attf-class="oe-view-manager-view-#{view}"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user