summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/abstract_web_client.js245
-rw-r--r--addons/web/static/src/js/action_manager.js212
-rw-r--r--addons/web/static/src/js/apps.js173
-rw-r--r--addons/web/static/src/js/boot.js1
-rw-r--r--addons/web/static/src/js/compatibility.js4
-rw-r--r--addons/web/static/src/js/control_panel.js51
-rw-r--r--addons/web/static/src/js/framework/ajax.js17
-rw-r--r--addons/web/static/src/js/framework/core.js16
-rw-r--r--addons/web/static/src/js/framework/crash_manager.js17
-rw-r--r--addons/web/static/src/js/framework/data.js12
-rw-r--r--addons/web/static/src/js/framework/misc.js64
-rw-r--r--addons/web/static/src/js/framework/model.js6
-rw-r--r--addons/web/static/src/js/framework/session.js2
-rw-r--r--addons/web/static/src/js/framework/utils.js1
-rw-r--r--addons/web/static/src/js/framework/view.js36
-rw-r--r--addons/web/static/src/js/framework/widget.js2
-rw-r--r--addons/web/static/src/js/menu.js6
-rw-r--r--addons/web/static/src/js/view_manager.js142
-rw-r--r--addons/web/static/src/js/views/form_common.js75
-rw-r--r--addons/web/static/src/js/views/form_relational_widgets.js426
-rw-r--r--addons/web/static/src/js/views/form_upgrade_widgets.js101
-rw-r--r--addons/web/static/src/js/views/form_view.js439
-rw-r--r--addons/web/static/src/js/views/form_widgets.js848
-rw-r--r--addons/web/static/src/js/views/graph_view.js26
-rw-r--r--addons/web/static/src/js/views/graph_widget.js1
-rw-r--r--addons/web/static/src/js/views/list_view.js543
-rw-r--r--addons/web/static/src/js/views/list_view_editable.js41
-rw-r--r--addons/web/static/src/js/views/pivot_view.js100
-rw-r--r--addons/web/static/src/js/views/search_filters.js33
-rw-r--r--addons/web/static/src/js/views/search_inputs.js3
-rw-r--r--addons/web/static/src/js/views/search_menus.js68
-rw-r--r--addons/web/static/src/js/views/search_view.js480
-rw-r--r--addons/web/static/src/js/views/tree_view.js22
-rw-r--r--addons/web/static/src/js/web_client.js223
-rw-r--r--addons/web/static/src/js/widgets/auto_complete.js220
-rw-r--r--addons/web/static/src/js/widgets/change_password.js2
-rw-r--r--addons/web/static/src/js/widgets/data_export.js608
-rw-r--r--addons/web/static/src/js/widgets/debug_manager.js845
-rw-r--r--addons/web/static/src/js/widgets/loading.js3
-rw-r--r--addons/web/static/src/js/widgets/pager.js76
-rw-r--r--addons/web/static/src/js/widgets/sidebar.js37
-rw-r--r--addons/web/static/src/js/widgets/switch_company_menu.js79
-rw-r--r--addons/web/static/src/js/widgets/user_menu.js100
43 files changed, 3373 insertions, 3033 deletions
diff --git a/addons/web/static/src/js/abstract_web_client.js b/addons/web/static/src/js/abstract_web_client.js
new file mode 100644
index 00000000000..31aa0d6f3dd
--- /dev/null
+++ b/addons/web/static/src/js/abstract_web_client.js
@@ -0,0 +1,245 @@
+odoo.define('web.AbstractWebClient', function (require) {
+"use strict";
+
+var ActionManager = require('web.ActionManager');
+var core = require('web.core');
+var crash_manager = require('web.crash_manager');
+var Loading = require('web.Loading');
+var NotificationManager = require('web.notification').NotificationManager;
+var session = require('web.session');
+var utils = require('web.utils');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+
+var WebClient = Widget.extend({
+ custom_events: {
+ notification: function (e) {
+ if(this.notification_manager) {
+ this.notification_manager.notify(e.data.title, e.data.message, e.data.sticky);
+ }
+ },
+ warning: function (e) {
+ if(this.notification_manager) {
+ this.notification_manager.warn(e.data.title, e.data.message, e.data.sticky);
+ }
+ },
+ clear_uncommitted_changes: function (e) {
+ this.clear_uncommitted_changes().then(e.data.callback);
+ },
+ },
+ init: function(parent, client_options) {
+ this.client_options = {};
+ this._super(parent);
+ this.origin = undefined;
+ if (client_options) {
+ _.extend(this.client_options, client_options);
+ }
+ this._current_state = null;
+ this.menu_dm = new utils.DropMisordered();
+ this.action_mutex = new utils.Mutex();
+ this.set('title_part', {"zopenerp": "Odoo"});
+ },
+ start: function() {
+ var self = this;
+
+ this.on("change:title_part", this, this._title_changed);
+ this._title_changed();
+
+ return session.session_bind(this.origin)
+ .then(function () {
+ self.bind_events();
+ return $.when(
+ self.set_action_manager(),
+ self.set_notification_manager(),
+ self.set_loading()
+ );
+ }).then(function () {
+ if (session.session_is_valid()) {
+ return self.show_application();
+ } else {
+ // database manager needs the webclient to keep going even
+ // though it has no valid session
+ return $.when();
+ }
+ }).then(function () {
+ if (self.client_options.action) {
+ self.do_action(self.client_options.action);
+ delete(self.client_options.action);
+ }
+ core.bus.trigger('web_client_ready');
+ });
+ },
+ bind_events: function() {
+ var self = this;
+ $('.oe_systray').show();
+ this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function() {
+ $(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
+ });
+ this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
+ ev.preventDefault();
+ var $toggle = $(this);
+ var doc_width = $(document).width();
+ var $menu = $toggle.siblings('.oe_dropdown_menu');
+ $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
+ var state = $menu.is('.oe_opened');
+ setTimeout(function() {
+ // Do not alter propagation
+ $toggle.add($menu).toggleClass('oe_opened', !state);
+ if (!state) {
+ // Move $menu if outside window's edge
+ var offset = $menu.offset();
+ var menu_width = $menu.width();
+ var x = doc_width - offset.left - menu_width - 2;
+ if (x < 0) {
+ $menu.offset({ left: offset.left + x }).width(menu_width);
+ }
+ }
+ }, 0);
+ });
+ core.bus.on('click', this, function(ev) {
+ $('.tooltip').remove();
+ if (!$(ev.target).is('input[type=file]')) {
+ self.$('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened');
+ }
+ });
+ core.bus.on('set_full_screen', this, function (full_screen) {
+ this.set_content_full_screen(full_screen);
+ });
+ core.bus.on('connection_lost', this, this.on_connection_lost);
+ core.bus.on('connection_restored', this, this.on_connection_restored);
+
+ // crash manager integration
+ session.on('error', crash_manager, crash_manager.rpc_error);
+ window.onerror = function (message, file, line, col, error) {
+ var traceback = error ? error.stack : '';
+ crash_manager.show_error({
+ type: _t("Client Error"),
+ message: message,
+ data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback}
+ });
+ };
+ },
+ set_action_manager: function() {
+ this.action_manager = new ActionManager(this, {webclient: this});
+ return this.action_manager.appendTo(this.$('.o_main_content'));
+ },
+ set_notification_manager: function() {
+ this.notification_manager = new NotificationManager(this);
+ return this.notification_manager.appendTo(this.$el);
+ },
+ set_loading: function() {
+ this.loading = new Loading(this);
+ return this.loading.appendTo(this.$el);
+ },
+ show_application: function() {
+ },
+ clear_uncommitted_changes: function() {
+ var def = $.Deferred().resolve();
+ core.bus.trigger('clear_uncommitted_changes', function chain_callbacks(callback) {
+ def = def.then(callback);
+ });
+ return def;
+ },
+ destroy_content: function() {
+ _.each(_.clone(this.getChildren()), function(el) {
+ el.destroy();
+ });
+ this.$el.children().remove();
+ },
+ // --------------------------------------------------------------
+ // Window title handling
+ // --------------------------------------------------------------
+ /**
+ Sets the first part of the title of the window, dedicated to the current action.
+ */
+ set_title: function(title) {
+ this.set_title_part("action", title);
+ },
+ /**
+ Sets an arbitrary part of the title of the window. Title parts are identified by strings. Each time
+ a title part is changed, all parts are gathered, ordered by alphabetical order and displayed in the
+ title of the window separated by '-'.
+ */
+ set_title_part: function(part, title) {
+ var tmp = _.clone(this.get("title_part"));
+ tmp[part] = title;
+ this.set("title_part", tmp);
+ },
+ _title_changed: function() {
+ var parts = _.sortBy(_.keys(this.get("title_part")), function(x) { return x; });
+ var tmp = "";
+ _.each(parts, function(part) {
+ var str = this.get("title_part")[part];
+ if (str) {
+ tmp = tmp ? tmp + " - " + str : str;
+ }
+ }, this);
+ document.title = tmp;
+ },
+ // --------------------------------------------------------------
+ // do_*
+ // --------------------------------------------------------------
+ /**
+ * When do_action is performed on the WebClient, forward it to the main ActionManager
+ * This allows to widgets that are not inside the ActionManager to perform do_action
+ */
+ do_action: function() {
+ return this.action_manager.do_action.apply(this, arguments);
+ },
+ do_reload: function() {
+ var self = this;
+ return this.session.session_reload().then(function () {
+ session.load_modules(true).then(
+ self.menu.proxy('do_reload'));
+ });
+ },
+ do_push_state: function(state) {
+ if ('title' in state) {
+ this.set_title(state.title);
+ delete state.title;
+ }
+ var url = '#' + $.param(state);
+ this._current_state = $.deparam($.param(state), false); // stringify all values
+ $.bbq.pushState(url);
+ this.trigger('state_pushed', state);
+ },
+ // --------------------------------------------------------------
+ // Connection notifications
+ // --------------------------------------------------------------
+ on_connection_lost: function () {
+ this.connection_notification = this.notification_manager.notify(
+ _t('Connection lost'),
+ _t('Trying to reconnect...'),
+ true
+ );
+ },
+ on_connection_restored: function () {
+ if (this.connection_notification) {
+ this.connection_notification.destroy();
+ this.notification_manager.notify(
+ _t('Connection restored'),
+ _t('You are back online'),
+ false
+ );
+ this.connection_notification = false;
+ }
+ },
+ // --------------------------------------------------------------
+ // Scrolltop handling
+ // --------------------------------------------------------------
+ get_scrollTop: function () {
+ },
+ //--------------------------------------------------------------
+ // Misc.
+ //--------------------------------------------------------------
+ set_content_full_screen: function(fullscreen) {
+ $(document.body).css('overflow-y', fullscreen ? 'hidden' : 'scroll');
+ this.$('.oe_webclient').toggleClass(
+ 'oe_content_full_screen', fullscreen);
+ },
+});
+
+return WebClient;
+
+});
diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js
index 359b174a28d..be5e6c6c996 100644
--- a/addons/web/static/src/js/action_manager.js
+++ b/addons/web/static/src/js/action_manager.js
@@ -23,24 +23,17 @@ var Action = core.Class.extend({
this.title = action.display_name || action.name;
},
/**
- * Not implemented for client actions
- * @return {Deferred} a rejected Deferred
- */
- appendTo: function() {
- return $.Deferred().reject();
- },
- /**
* This method should restore this previously loaded action
- * Calls on_reverse_breadcrumb callback if defined
+ * Calls on_reverse_breadcrumb_callback if defined
* @return {Deferred} resolved when widget is enabled
*/
restore: function() {
- if (this.on_reverse_breadcrumb) {
- return this.on_reverse_breadcrumb();
+ if (this.on_reverse_breadcrumb_callback) {
+ return this.on_reverse_breadcrumb_callback();
}
},
/**
- * Not implemented for functions
+ * There is nothing to detach in the case of a client function
*/
detach: function() {
},
@@ -50,11 +43,16 @@ var Action = core.Class.extend({
destroy: function() {
},
/**
- * Sets the on_reverse_breadcrumb callback to be called when coming back to that action
- * @param {Function} [on_reverse_breadcrumb] the callback
+ * Sets the on_reverse_breadcrumb_callback to be called when coming back to that action
+ * @param {Function} [callback] the callback
+ */
+ set_on_reverse_breadcrumb: function(callback) {
+ this.on_reverse_breadcrumb_callback = callback;
+ },
+ /**
+ * Not implemented for client actions
*/
- set_on_reverse_breadcrumb: function(on_reverse_breadcrumb) {
- this.on_reverse_breadcrumb = on_reverse_breadcrumb;
+ set_scrollTop: function() {
},
/**
* Stores the DOM fragment of the action
@@ -65,8 +63,10 @@ var Action = core.Class.extend({
},
/**
* Not implemented for client actions
+ * @return {int} the number of pixels the webclient is scrolled when leaving the action
*/
- set_is_in_DOM: function() {
+ get_scrollTop: function() {
+ return 0;
},
/**
* @return {Object} the description of the action
@@ -113,16 +113,6 @@ var WidgetAction = Action.extend({
});
},
/**
- * Wraps the action's widget in a container and appends it to el
- * @param {DocumentFragment} [el] where to append the widget
- * @return {Deferred} resolved when the widget is appended
- */
- appendTo: function(el) {
- this.$client_action_container = $('<div>').addClass('oe_client_action');
- this.$client_action_container.appendTo(el);
- return this.widget.appendTo(this.$client_action_container);
- },
- /**
* Restores WidgetAction by calling do_show on its widget
*/
restore: function() {
@@ -132,18 +122,20 @@ var WidgetAction = Action.extend({
});
},
/**
- * Detaches the client action's container from the DOM
- * @return {jQuery} the action's container
+ * Detaches the action's widget from the DOM
+ * @return the widget's $el
*/
detach: function() {
- return this.$client_action_container.detach();
+ // Hack to remove badly inserted nvd3 tooltips ; should be removed when upgrading nvd3 lib
+ $('body > .nvtooltip').remove();
+
+ return framework.detach([{widget: this.widget}]);
},
/**
* Destroys the widget
*/
destroy: function() {
this.widget.destroy();
- this.detach();
},
});
/**
@@ -151,14 +143,6 @@ var WidgetAction = Action.extend({
*/
var ViewManagerAction = WidgetAction.extend({
/**
- * Appends the action's widget to el
- * @param {DocumentFragment} [el] where to append the widget
- * @return {Deferred} resolved when the widget is appended
- */
- appendTo: function(el) {
- return this.widget.appendTo(el);
- },
- /**
* Restores a ViewManagerAction
* Switches to the requested view by calling select_view on the ViewManager
* @param {int} [view_index] the index of the view to select
@@ -170,27 +154,27 @@ var ViewManagerAction = WidgetAction.extend({
});
},
/**
- * Sets is_in_DOM on this.widget
- * @param {Boolean} [is_in_DOM] true iff the widget is attached in the DOM
+ * Sets the on_reverse_breadcrumb_callback and the scrollTop to apply when
+ * coming back to that action
+ * @param {Function} [callback] the callback
+ * @param {int} [scrollTop] the number of pixels to scroll
*/
- set_is_in_DOM: function(is_in_DOM) {
- this.widget.is_in_DOM = is_in_DOM;
+ set_on_reverse_breadcrumb: function(callback, scrollTop) {
+ this._super(callback);
+ this.set_scrollTop(scrollTop);
},
/**
- * Detaches the view_manager from the DOM
- * @return {jQuery} the view_manager's $el
+ * Sets the scroll position of the widgets's active_view
+ * @param {int} [scrollTop] the number of pixels to scroll
*/
- detach: function() {
- // Hack to remove badly inserted nvd3 tooltips ; should be removed when upgrading nvd3 lib
- $('body > .nvtooltip').remove();
-
- return this.widget.$el.detach();
+ set_scrollTop: function(scrollTop) {
+ this.widget.active_view.controller.set_scrollTop(scrollTop);
},
/**
- * Destroys the widget
+ * @return {int} the number of pixels the webclient is scrolled when leaving the action
*/
- destroy: function() {
- this.widget.destroy();
+ get_scrollTop: function() {
+ return this.widget.active_view.controller.get_scrollTop();
},
/**
* @return {Array} array of Objects that will be interpreted to display the breadcrumbs
@@ -214,13 +198,31 @@ var ViewManagerAction = WidgetAction.extend({
});
var ActionManager = Widget.extend({
- template: "ActionManager",
- init: function(parent) {
+ template: 'ActionManager',
+ /**
+ * Called each time the action manager is attached into the DOM
+ */
+ on_attach_callback: function() {
+ this.is_in_DOM = true;
+ if (this.inner_widget && this.inner_widget.on_attach_callback) {
+ this.inner_widget.on_attach_callback();
+ }
+ },
+ /**
+ * Called each time the action manager is detached from the DOM
+ */
+ on_detach_callback: function() {
+ this.is_in_DOM = false;
+ if (this.inner_widget && this.inner_widget.on_detach_callback) {
+ this.inner_widget.on_detach_callback();
+ }
+ },
+ init: function(parent, options) {
this._super(parent);
this.action_stack = [];
this.inner_action = null;
this.inner_widget = null;
- this.webclient = parent;
+ this.webclient = options && options.webclient;
this.dialog = null;
this.dialog_widget = null;
this.on('history_back', this, this.proxy('history_back'));
@@ -236,8 +238,15 @@ var ActionManager = Widget.extend({
this.select_action(action, index);
}, 200, true));
- // Append the main control panel to the DOM (inside the ActionManager jQuery element)
- this.main_control_panel.appendTo(this.$el);
+ // Listen to event "DOM_updated" to restore the scroll position
+ core.bus.on('DOM_updated', this, function() {
+ if (this.inner_action) {
+ this.trigger_up('scrollTo', {offset: this.inner_action.get_scrollTop() || 0});
+ }
+ });
+
+ // Insert the main control panel into the DOM
+ return this.main_control_panel.insertBefore(this.$el);
},
dialog_stop: function (reason) {
if (this.dialog) {
@@ -256,9 +265,9 @@ var ActionManager = Widget.extend({
*/
push_action: function(widget, action_descr, options) {
var self = this;
- var old_widget = this.inner_widget;
- var old_action = this.inner_action;
var old_action_stack = this.action_stack;
+ var old_action = this.inner_action;
+ var old_widget = this.inner_widget;
options = options || {};
// Empty action_stack if requested
@@ -277,8 +286,8 @@ var ActionManager = Widget.extend({
}
// Set on_reverse_breadcrumb callback on previous inner_action
- if (old_action) {
- old_action.set_on_reverse_breadcrumb(options.on_reverse_breadcrumb);
+ if (this.webclient && old_action) {
+ old_action.set_on_reverse_breadcrumb(options.on_reverse_breadcrumb, this.webclient.get_scrollTop());
}
// Update action_stack (must be done before appendTo to properly
@@ -295,25 +304,19 @@ var ActionManager = Widget.extend({
// render the inner_widget in a fragment, and append it to the
// document only when it's ready
var new_widget_fragment = document.createDocumentFragment();
- return $.when(this.inner_action.appendTo(new_widget_fragment)).done(function() {
+ return $.when(this.inner_widget.appendTo(new_widget_fragment)).done(function() {
// Detach the fragment of the previous action and store it within the action
if (old_action) {
old_action.set_fragment(old_action.detach());
- old_action.set_is_in_DOM(false);
}
if (!widget.need_control_panel) {
// Hide the main ControlPanel for widgets that do not use it
self.main_control_panel.do_hide();
}
-
- 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) {
- old_widget.do_hide();
- }
+ framework.append(self.$el, new_widget_fragment, {
+ in_DOM: self.is_in_DOM,
+ callbacks: [{widget: self.inner_widget}],
+ });
if (options.clear_breadcrumbs) {
self.clear_action_stack(old_action_stack);
}
@@ -325,6 +328,11 @@ var ActionManager = Widget.extend({
self.inner_widget = old_widget;
});
},
+ set_scrollTop: function(scrollTop) {
+ if (this.inner_action) {
+ this.inner_action.set_scrollTop(scrollTop);
+ }
+ },
get_breadcrumbs: function () {
return _.flatten(_.map(this.action_stack, function (action) {
return action.get_breadcrumbs();
@@ -368,7 +376,9 @@ var ActionManager = Widget.extend({
},
select_action: function(action, index) {
var self = this;
- return this.webclient.clear_uncommitted_changes().then(function() {
+ var def = this.webclient && this.webclient.clear_uncommitted_changes() || $.when();
+
+ return def.then(function() {
// 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);
@@ -376,19 +386,21 @@ var ActionManager = Widget.extend({
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 $.when(action.restore(index)).done(function() {
+ // Hide the ControlPanel if the widget doesn't use it
+ if (!self.inner_widget.need_control_panel) {
+ self.main_control_panel.do_hide();
+ }
+ // Attach the DOM of the action and restore the scroll position only if necessary
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);
+ framework.append(self.$el, action.get_fragment(), {
+ in_DOM: self.is_in_DOM,
+ callbacks: [{widget: action.widget}],
+ });
}
});
}).fail(function() {
@@ -406,7 +418,7 @@ var ActionManager = Widget.extend({
}
},
do_push_state: function(state) {
- if (!this.webclient || !this.webclient.do_push_state || this.dialog) {
+ if (!this.webclient || this.dialog) {
return;
}
state = state || {};
@@ -491,13 +503,6 @@ var ActionManager = Widget.extend({
res_id: state.id,
view_type: state.view_type,
});
- $.when(action_loaded || null).done(function() {
- self.webclient.menu.is_bound.done(function() {
- if (self.inner_action && self.inner_action.get_action_descr().id) {
- self.webclient.menu.open_action(self.inner_action.get_action_descr().id);
- }
- });
- });
}
}
} else if (state.model && state.id) {
@@ -524,9 +529,9 @@ var ActionManager = Widget.extend({
});
}
- $.when(action_loaded || null).done(function() {
+ return $.when(action_loaded || null).done(function() {
if (self.inner_widget && self.inner_widget.do_load_state) {
- self.inner_widget.do_load_state(state, warm);
+ return self.inner_widget.do_load_state(state, warm);
}
});
},
@@ -628,7 +633,9 @@ var ActionManager = Widget.extend({
});
}
- return this[type](action, options);
+ return $.when(this[type](action, options)).then(function() {
+ return action;
+ });
},
null_action: function() {
this.dialog_stop();
@@ -645,6 +652,7 @@ var ActionManager = Widget.extend({
* @return {*}
*/
ir_actions_common: function(executor, options) {
+ var self = this;
if (executor.action.target === 'new') {
var pre_dialog = (this.dialog && !this.dialog.isDestroyed()) ? this.dialog : null;
if (pre_dialog){
@@ -693,13 +701,16 @@ var ActionManager = Widget.extend({
this.dialog_widget.set_cp_bus(new core.Bus());
}
this.dialog_widget.setParent(this.dialog);
- this.dialog.open();
-
- return this.dialog_widget.appendTo(this.dialog.$el);
+
+ var fragment = document.createDocumentFragment();
+ return this.dialog_widget.appendTo(fragment).then(function() {
+ self.dialog.open().$el.append(fragment);
+ if(options.state && self.dialog_widget.do_load_state) {
+ return self.dialog_widget.do_load_state(options.state);
+ }
+ }).then(function () { return executor.action; });
}
- // Clear uncommitted changes on the current inner widget if there is one
- var self = this;
- var def = (this.inner_widget && this.webclient.clear_uncommitted_changes()) || $.when();
+ var def = this.inner_action && this.webclient && this.webclient.clear_uncommitted_changes() || $.when();
return def.then(function() {
self.dialog_stop(executor.action);
return self.push_action(executor.widget(), executor.action, options);
@@ -764,11 +775,11 @@ var ActionManager = Widget.extend({
},
ir_actions_server: function (action, options) {
var self = this;
- this.rpc('/web/action/run', {
+ return this.rpc('/web/action/run', {
action_id: action.id,
context: action.context || {}
- }).done(function (action) {
- self.do_action(action, options);
+ }).then(function (action) {
+ return self.do_action(action, options);
});
},
ir_actions_report_xml: function(action, options) {
@@ -818,6 +829,7 @@ var ActionManager = Widget.extend({
if (action.target === 'self') {
framework.redirect(url);
+ return $.Deferred(); // The action is finished only when the redirection is done
} else {
window.open(url, '_blank');
}
diff --git a/addons/web/static/src/js/apps.js b/addons/web/static/src/js/apps.js
index 156fe9e10b7..5dd5b576fc4 100644
--- a/addons/web/static/src/js/apps.js
+++ b/addons/web/static/src/js/apps.js
@@ -1,4 +1,4 @@
-odoo.define('base.apps', function (require) {
+odoo.define('web.Apps', function (require) {
"use strict";
var core = require('web.core');
@@ -9,10 +9,9 @@ var web_client = require('web.web_client');
var Widget = require('web.Widget');
var _t = core._t;
-var qweb = core.qweb;
var apps_client = null;
-
+var qweb = core.qweb;
var Apps = Widget.extend({
template: 'EmptyComponent',
@@ -106,92 +105,98 @@ var Apps = Widget.extend({
}
},
- start: function() {
+ _on_update_count: function(m) {
var self = this;
- return self.get_client().
- done(function(client) {
- self.client = client;
-
- var qs = (session.debug ? 'debug&' : '') + 'db=' + client.dbname;
- var u = client.origin + '/apps/embed/client?' + qs;
- var css = {width: '100%', height: '400px'};
- self.$ifr = $('<iframe>').attr('src', u);
-
- $(window).on("message.apps", self.proxy('_on_message'));
-
- self.on('message:ready', self, function(m) {
- var w = this.$ifr[0].contentWindow;
- var act = {
- type: 'ir.actions.client',
- tag: this.remote_action_tag,
- params: _.extend({}, this.params, {
- db: this.session.db,
- origin: this.session.origin,
- })
- };
- w.postMessage({type:'action', action: act}, client.origin);
+ var count = m.count;
+ var get_upd_menu_id = function() {
+ if (_.isUndefined(self._upd_menu_id)) {
+ var IMD = new Model('ir.model.data');
+ return IMD.call('get_object_reference', ['base', 'menu_module_updates']).then(function(r) {
+ var mid = r[1];
+ if(r[0] !== 'ir.ui.menu') {
+ // invalid reference, return null
+ mid = null;
+ }
+ self._upd_menu_id = mid;
+ return mid;
});
+ } else {
+ return $.Deferred().resolve(self._upd_menu_id).promise();
+ }
+ };
- self.on('message:set_height', self, function(m) {
- this.$ifr.height(m.height);
- });
+ $.when(get_upd_menu_id()).done(function(menu_id) {
+ if (_.isNull(menu_id)) {
+ return;
+ }
+ var $menu = web_client.menu.$secondary_menus.find(_.str.sprintf('a[data-menu=%s]', menu_id));
+ if ($menu.length === 0) {
+ return;
+ }
+ if (_.isUndefined(count)) {
+ count = 0;
+ }
+ var needupdate = $menu.find('#menu_counter');
+ if (needupdate && needupdate.length !== 0) {
+ if (count > 0) {
+ needupdate.text(count);
+ } else {
+ needupdate.remove();
+ }
+ } else if (count > 0) {
+ $menu.append(qweb.render("Menu.needaction_counter", {widget: {needaction_counter: count}}));
+ }
+ });
+ },
- self.on('message:update_count', self, function(m) {
- var count = m.count;
- var get_upd_menu_id = function() {
- if (_.isUndefined(self._upd_menu_id)) {
- var IMD = new Model('ir.model.data');
- return IMD.call('get_object_reference', ['base', 'menu_module_updates']).then(function(r) {
- var mid = r[1];
- if(r[0] !== 'ir.ui.menu') {
- // invalid reference, return null
- mid = null;
- }
- self._upd_menu_id = mid;
- return mid;
- });
- } else {
- return $.Deferred().resolve(self._upd_menu_id).promise();
- }
- };
-
- $.when(get_upd_menu_id()).done(function(menu_id) {
- if (_.isNull(menu_id)) {
- return;
- }
- var $menu = web_client.menu.$secondary_menus.find(_.str.sprintf('a[data-menu=%s]', menu_id));
- if ($menu.length === 0) {
- return;
- }
- if (_.isUndefined(count)) {
- count = 0;
- }
- var needupdate = $menu.find('#menu_counter');
- if (needupdate && needupdate.length !== 0) {
- if (count > 0) {
- needupdate.text(count);
- } else {
- needupdate.remove();
- }
- } else if (count > 0) {
- $menu.append(qweb.render("Menu.needaction_counter", {widget: {needaction_counter: count}}));
- }
- });
- });
+ start: function() {
+ var self = this;
+ var def = $.Deferred();
+ self.get_client().then(function(client) {
+ self.client = client;
+
+ var qs = (session.debug ? 'debug&' : '') + 'db=' + client.dbname;
+ var u = client.origin + '/apps/embed/client?' + qs;
+ var css = {width: '100%', height: '400px'};
+ self.$ifr = $('<iframe>').attr('src', u);
+
+ $(window).on("message.apps", self.proxy('_on_message'));
+
+ self.on('message:ready', self, function(m) {
+ var w = this.$ifr[0].contentWindow;
+ var act = {
+ type: 'ir.actions.client',
+ tag: this.remote_action_tag,
+ params: _.extend({}, this.params, {
+ db: this.session.db,
+ origin: this.session.origin,
+ })
+ };
+ w.postMessage({type:'action', action: act}, client.origin);
+ });
- self.on('message:blockUI', self, function() { framework.blockUI(); });
- self.on('message:unblockUI', self, function() { framework.unblockUI(); });
- self.on('message:warn', self, function(m) {self.do_warn(m.title, m.message, m.sticky); });
-
- self.$ifr.appendTo(self.$el).css(css).addClass('apps-client');
- }).
- fail(function(client) {
- self.do_warn(_t('Odoo Apps will be available soon'), _t('Showing locally available modules'), true);
- self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
- self.do_action(action);
- web_client.menu.open_action(action.id);
- });
+ self.on('message:set_height', self, function(m) {
+ this.$ifr.height(m.height);
+ });
+
+ self.on('message:update_count', self, self._on_update_count);
+
+ self.on('message:blockUI', self, function() { framework.blockUI(); });
+ self.on('message:unblockUI', self, function() { framework.unblockUI(); });
+ self.on('message:warn', self, function(m) {self.do_warn(m.title, m.message, m.sticky); });
+
+ self.$ifr.appendTo(self.$el).css(css).addClass('apps-client');
+
+ def.resolve();
+ }, function() {
+ self.do_warn(_t('Odoo Apps will be available soon'), _t('Showing locally available modules'), true);
+ return session.rpc('/web/action/load', {action_id: self.failback_action_id}).then(function(action) {
+ return self.do_action(action);
+ }).always(function () {
+ def.reject();
});
+ });
+ return def;
},
});
@@ -202,4 +207,6 @@ var AppsUpdates = Apps.extend({
core.action_registry.add("apps", Apps);
core.action_registry.add("apps.updates", AppsUpdates);
+return Apps;
+
});
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 34abca7ef03..666520b263d 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -199,7 +199,6 @@
process_jobs: function (jobs, services) {
var job;
var require;
- var time;
function process_job (job) {
var require = make_require(job);
diff --git a/addons/web/static/src/js/compatibility.js b/addons/web/static/src/js/compatibility.js
index 5532c8c388e..7fa2d4733db 100644
--- a/addons/web/static/src/js/compatibility.js
+++ b/addons/web/static/src/js/compatibility.js
@@ -22,7 +22,6 @@ var form_relational = require('web.form_relational'); // necessary
var form_widgets = require('web.form_widgets'); // necessary
var framework = require('web.framework');
var ListView = require('web.ListView');
-var Menu = require('web.Menu');
var Model = require('web.DataModel');
var pyeval = require('web.pyeval');
var Registry = require('web.Registry');
@@ -96,7 +95,6 @@ openerp.web.FormView = FormView;
openerp.web.json_node_to_xml = utils.json_node_to_xml;
openerp.web.ListView = ListView;
-openerp.web.Menu = Menu;
openerp.web.Model = Model;
openerp.web.normalize_format = time.strftime_to_moment_format;
openerp.web.py_eval = pyeval.py_eval;
@@ -208,7 +206,7 @@ ListView.include({
}).value();
}
- return this._super(data);
+ return this._super();
},
/**
* Returns the style for the provided record in the current view (from the
diff --git a/addons/web/static/src/js/control_panel.js b/addons/web/static/src/js/control_panel.js
index 5e2cc36a6a1..3a233564368 100644
--- a/addons/web/static/src/js/control_panel.js
+++ b/addons/web/static/src/js/control_panel.js
@@ -54,17 +54,15 @@ var ControlPanel = Widget.extend({
* @return {jQuery.Deferred}
*/
start: function() {
- this.$title_col = this.$('.oe-cp-title');
-
// Exposed jQuery nodesets
this.nodes = {
- $breadcrumbs: this.$('.oe-view-title'),
- $buttons: this.$('.oe-cp-buttons'),
- $pager: this.$('.oe-cp-pager'),
- $searchview: this.$('.oe-cp-search-view'),
- $searchview_buttons: this.$('.oe-search-options'),
- $sidebar: this.$('.oe-cp-sidebar'),
- $switch_buttons: this.$('.oe-cp-switch-buttons'),
+ $breadcrumbs: this.$('.breadcrumb'),
+ $buttons: this.$('.o_cp_buttons'),
+ $pager: this.$('.o_cp_pager'),
+ $searchview: this.$('.o_cp_searchview'),
+ $searchview_buttons: this.$('.o_search_options'),
+ $sidebar: this.$('.o_cp_sidebar'),
+ $switch_buttons: this.$('.o_cp_switch_buttons'),
};
// By default, hide the ControlPanel and remove its contents from the DOM
@@ -120,7 +118,7 @@ var ControlPanel = Widget.extend({
// Update the searchview and switch buttons
this._update_search_view(status.searchview, status.search_view_hidden);
- if (status.active_view_selector) {
+ if (status.active_view_selector) {
this._update_switch_buttons(status.active_view_selector);
}
}
@@ -182,22 +180,25 @@ var ControlPanel = Widget.extend({
*/
_render_breadcrumbs: function (breadcrumbs) {
var self = this;
-
return breadcrumbs.map(function (bc, index) {
- return make_breadcrumb(bc, index === breadcrumbs.length - 1);
+ return self._render_breadcrumbs_li(bc, index, breadcrumbs.length);
});
-
- function make_breadcrumb (bc, is_last) {
- var $bc = $('<li>')
- .append(is_last ? _.escape(bc.title) : $('<a>').text(bc.title))
- .toggleClass('active', is_last);
- if (!is_last) {
- $bc.click(function () {
- self.trigger("on_breadcrumb_click", bc.action, bc.index);
- });
- }
- return $bc;
+ },
+ /**
+ * Private function that renders a breadcrumbs' li Jquery element
+ */
+ _render_breadcrumbs_li: function (bc, index, length) {
+ var self = this;
+ var is_last = (index === length-1);
+ var $bc = $('<li>')
+ .append(is_last ? _.escape(bc.title) : $('<a>').text(bc.title))
+ .toggleClass('active', is_last);
+ if (!is_last) {
+ $bc.click(function () {
+ self.trigger("on_breadcrumb_click", bc.action, bc.index);
+ });
}
+ return $bc;
},
/**
* Private function that updates the SearchView's visibility and extend the
@@ -211,8 +212,10 @@ var ControlPanel = Widget.extend({
// have been appended to a jQuery node not in the DOM at SearchView initialization
searchview.$buttons = this.nodes.$searchview_buttons;
searchview.toggle_visibility(!is_hidden);
- this.$title_col.toggleClass('col-md-6', !is_hidden).toggleClass('col-md-12', is_hidden);
}
+
+ this.nodes.$searchview.toggle(!is_hidden);
+ this.$el.toggleClass('o_breadcrumb_full', !!is_hidden);
},
});
diff --git a/addons/web/static/src/js/framework/ajax.js b/addons/web/static/src/js/framework/ajax.js
index 869e0214ad9..0301cd4e96c 100644
--- a/addons/web/static/src/js/framework/ajax.js
+++ b/addons/web/static/src/js/framework/ajax.js
@@ -2,8 +2,8 @@ odoo.define('web.ajax', function (require) {
"use strict";
var core = require('web.core');
-var time = require('web.time');
var utils = require('web.utils');
+var time = require('web.time');
function genericJsonRpc (fct_name, params, fct) {
var data = {
@@ -14,6 +14,7 @@ function genericJsonRpc (fct_name, params, fct) {
};
var xhr = fct(data);
var result = xhr.pipe(function(result) {
+ core.bus.trigger('rpc:result', data, result);
if (result.error !== undefined) {
if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") {
console.error("Server application error", JSON.stringify(result.error));
@@ -42,7 +43,7 @@ function jsonRpc(url, fct_name, params, settings) {
contentType: 'application/json'
}));
});
-};
+}
function jsonpRpc(url, fct_name, params, settings) {
settings = settings || {};
@@ -116,7 +117,7 @@ function jsonpRpc(url, fct_name, params, settings) {
return deferred;
}
});
-};
+}
// helper
function realSetTimeout (fct, millis) {
@@ -140,7 +141,7 @@ function loadCSS(url) {
'type': 'text/css'
}));
}
-};
+}
function loadJS(url) {
var def = $.Deferred();
@@ -165,7 +166,7 @@ function loadJS(url) {
head.appendChild(script);
}
return def;
-};
+}
/**
* Cooperative file download implementation, for ajaxy APIs.
@@ -301,7 +302,7 @@ function post (controller_url, data) {
var Def = $.Deferred();
var postData = new FormData();
-
+
$.each(data, function(i,val) {
postData.append(i, val);
});
@@ -311,7 +312,7 @@ function post (controller_url, data) {
var xhr = new XMLHttpRequest();
if(xhr.upload) xhr.upload.addEventListener('progress', progressHandler(Def), false);
-
+
var ajaxDef = $.ajax(controller_url, {
xhr: function() {return xhr;},
data: postData,
@@ -324,8 +325,6 @@ function post (controller_url, data) {
return Def;
}
-
-
var loadXML = (function () {
var loading = false;
var urls = [];
diff --git a/addons/web/static/src/js/framework/core.js b/addons/web/static/src/js/framework/core.js
index 30138102663..f9ab78f1c8e 100644
--- a/addons/web/static/src/js/framework/core.js
+++ b/addons/web/static/src/js/framework/core.js
@@ -177,20 +177,16 @@ return {
_lt: _lt,
// registries
- view_registry: new Registry(),
- crash_registry: new Registry(),
action_registry : new Registry(),
- form_widget_registry: new Registry(),
- form_tag_registry: new Registry(),
+ crash_registry: new Registry(),
form_custom_registry: new Registry(),
+ form_tag_registry: new Registry(),
+ form_widget_registry: new Registry(),
list_widget_registry: new Registry(),
- search_widgets_registry: new Registry(),
- search_filters_registry: new Registry(),
-
- // necessary to make the kanban view compatible between
- // community and enterprise edition
one2many_view_registry: new Registry(),
-
+ search_filters_registry: new Registry(),
+ search_widgets_registry: new Registry(),
+ view_registry: new Registry(),
};
diff --git a/addons/web/static/src/js/framework/crash_manager.js b/addons/web/static/src/js/framework/crash_manager.js
index 4ab40f456bc..99d949ac311 100644
--- a/addons/web/static/src/js/framework/crash_manager.js
+++ b/addons/web/static/src/js/framework/crash_manager.js
@@ -40,22 +40,17 @@ var CrashManager = core.Class.extend({
if (!this.active) {
return;
}
- if (this.$indicator){
+ if (this.connection_lost) {
return;
}
if (error.code == -32098) {
- $.blockUI({ message: '' , overlayCSS: {'z-index': 9999, backgroundColor: '#FFFFFF', opacity: 0.0, cursor: 'wait'}});
- this.$indicator = $('<div class="oe_indicator">' + _t("Trying to reconnect... ") + '<i class="fa fa-refresh"></i></div>');
- this.$indicator.prependTo("body");
- var timeinterval = setInterval(function(){
+ core.bus.trigger('connection_lost');
+ this.connection_lost = true;
+ var timeinterval = setInterval(function() {
ajax.jsonRpc('/web/webclient/version_info').then(function() {
clearInterval(timeinterval);
- self.$indicator.html(_t("You are back online"));
- self.$indicator.delay(2000).fadeOut('slow',function(){
- $(this).remove();
- self.$indicator.remove();
- });
- $.unblockUI();
+ core.bus.trigger('connection_restored');
+ self.connection_lost = false;
});
}, 2000);
return;
diff --git a/addons/web/static/src/js/framework/data.js b/addons/web/static/src/js/framework/data.js
index a1b8e1602e9..592e402d927 100644
--- a/addons/web/static/src/js/framework/data.js
+++ b/addons/web/static/src/js/framework/data.js
@@ -320,6 +320,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
this.index = null;
this._sort = [];
this._model = new Model(model, context);
+ this.orderer = new utils.DropMisordered();
},
previous: function () {
this.index -= 1;
@@ -408,10 +409,11 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
read_slice: function (fields, options) {
var self = this;
options = options || {};
- return this._model.query(fields)
+ var query = this._model.query(fields)
.limit(options.limit || false)
.offset(options.offset || 0)
- .all().done(function (records) {
+ .all();
+ return this.orderer.add(query).done(function (records) {
self.ids = _(records).pluck('id');
});
},
@@ -761,12 +763,6 @@ var BufferedDataSet = DataSetStatic.extend({
_.extend(cached.from_read, options.from_read);
_.extend(cached.changes, options.changes);
_.extend(cached.readonly_fields, options.readonly_fields);
- // discard values from cached.changes that are in cached.from_read
- _.each(cached.changes, function (v, k) {
- if (cached.from_read[k] === v) {
- delete cached.changes[k];
- }
- });
if (options.to_create !== undefined) cached.to_create = options.to_create;
if (options.to_delete !== undefined) cached.to_delete = options.to_delete;
}
diff --git a/addons/web/static/src/js/framework/misc.js b/addons/web/static/src/js/framework/misc.js
index b8c5e5e927e..73345df94c0 100644
--- a/addons/web/static/src/js/framework/misc.js
+++ b/addons/web/static/src/js/framework/misc.js
@@ -211,6 +211,16 @@ if ('nv' in window) {
nv.tooltip.cleanup = function () {
$('.nvtooltip').remove();
};
+
+ // monkey patch nvd3 to prevent it to display a tooltip (position: absolute) with
+ // a negative `top`; with this patch the highest tooltip's position is still in the
+ // graph
+ var originalCalcTooltipPosition = nv.tooltip.calcTooltipPosition;
+ nv.tooltip.calcTooltipPosition = function () {
+ var container = originalCalcTooltipPosition.apply(this, arguments);
+ container.style.top = container.style.top.split('px')[0] < 0 ? 0 + 'px' : container.style.top;
+ return container;
+ };
}
// Bootstrap customization
@@ -303,11 +313,18 @@ $.extend( proto, {
});
/**
- * Private function that triggers an event on core.bus
+ * Private function to notify that something has been attached in the DOM
* @param {htmlString or Element or Array or jQuery} [content] the content that
* has been attached in the DOM
+ * @params {Array} [callbacks] array of {widget: w, callback_args: args} such
+ * that on_attach_callback() will be called on each w with arguments args
*/
-function _notify (content) {
+function _notify (content, callbacks) {
+ _.each(callbacks, function(c) {
+ if (c.widget && c.widget.on_attach_callback) {
+ c.widget.on_attach_callback(c.callback_args);
+ }
+ });
core.bus.trigger('DOM_updated', content);
}
/**
@@ -315,12 +332,14 @@ function _notify (content) {
* @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
+ * @param {Boolean} [options.in_DOM] true if $target is in the DOM
+ * @param {Array} [options.callbacks] array of objects describing the callbacks
+ * to perform (see _notify for a complete description)
*/
-function append ($target, content, trigger) {
+function append ($target, content, options) {
$target.append(content);
- if (trigger) {
- _notify(content);
+ if (options && options.in_DOM) {
+ _notify(content, options.callbacks);
}
}
/**
@@ -328,13 +347,37 @@ function append ($target, content, trigger) {
* @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
+ * @param {Boolean} [options.in_DOM] true if $target is in the DOM
+ * @param {Array} [options.callbacks] array of objects describing the callbacks
+ * to perform (see _notify for a complete description)
*/
-function prepend ($target, content, trigger) {
+function prepend ($target, content, options) {
$target.prepend(content);
- if (trigger) {
- _notify(content);
+ if (options && options.in_DOM) {
+ _notify(content, options.callbacks);
+ }
+}
+
+/**
+ * Detaches widgets from the DOM and performs their on_detach_callback()
+ * @param {Array} [to_detach] array of {widget: w, callback_args: args} such
+ * that w.$el will be detached and w.on_detach_callback(args) will be called
+ * @param {jQuery} [options.$to_detach] if given, detached instead of widgets' $el
+ * @return {jQuery} the detached elements
+ */
+function detach (to_detach, options) {
+ _.each(to_detach, function(d) {
+ if (d.widget.on_detach_callback) {
+ d.widget.on_detach_callback(d.callback_args);
+ }
+ });
+ var $to_detach = options && options.$to_detach;
+ if (!$to_detach) {
+ $to_detach = $(_.map(to_detach, function(d) {
+ return d.widget.el;
+ }));
}
+ return $to_detach.detach();
}
/**
@@ -358,6 +401,7 @@ return {
redirect: redirect,
append: append,
prepend: prepend,
+ detach: detach,
getPosition: getPosition,
};
diff --git a/addons/web/static/src/js/framework/model.js b/addons/web/static/src/js/framework/model.js
index fc1422d799a..dd516bb61ba 100644
--- a/addons/web/static/src/js/framework/model.js
+++ b/addons/web/static/src/js/framework/model.js
@@ -6,14 +6,12 @@ var session = require('web.session');
var Model = Class.extend({
/**
- new openerp.web.Model([session,] model_name[, context[, domain]])
+ new openerp.web.Model(name[, context[, domain]])
@constructs instance.web.Model
@extends instance.web.Class
- @param {openerp.web.Session} [session] The session object used to communicate with
- the server.
- @param {String} model_name name of the OpenERP model this object is bound to
+ @param {String} name name of the OpenERP model this object is bound to
@param {Object} [context]
@param {Array} [domain]
*/
diff --git a/addons/web/static/src/js/framework/session.js b/addons/web/static/src/js/framework/session.js
index 71819cc6b7a..8a9269b8dff 100644
--- a/addons/web/static/src/js/framework/session.js
+++ b/addons/web/static/src/js/framework/session.js
@@ -236,7 +236,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
return d;
},
load_qweb: function(mods) {
- this.qweb_mutex.exec(function() {
+ this.qweb_mutex.exec(function () {
return $.get('/web/webclient/qweb?mods=' + mods).then(function (doc) {
if (!doc) { return; }
qweb.add_template(doc);
diff --git a/addons/web/static/src/js/framework/utils.js b/addons/web/static/src/js/framework/utils.js
index d50647090d6..8d8d8dab6ee 100644
--- a/addons/web/static/src/js/framework/utils.js
+++ b/addons/web/static/src/js/framework/utils.js
@@ -423,7 +423,6 @@ function swap(array, elem1, elem2) {
array[i1] = elem2;
}
-
return {
divmod: divmod,
modf: modf,
diff --git a/addons/web/static/src/js/framework/view.js b/addons/web/static/src/js/framework/view.js
index fbbede916e5..d2cc73a4b71 100644
--- a/addons/web/static/src/js/framework/view.js
+++ b/addons/web/static/src/js/framework/view.js
@@ -27,6 +27,8 @@ var View = Widget.extend({
// multi_record is used to distinguish views displaying a single record
// (e.g. FormView) from those that display several records (e.g. ListView)
multi_record: true,
+ // indicates whether or not the view is mobile-friendly
+ mobile_friendly: false,
// icon is the font-awesome icon to display in the view switcher
icon: 'fa-question',
// whether or not the view requires the model's fields to render itself
@@ -45,11 +47,8 @@ var View = Widget.extend({
* @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'));
+ // add classname that reflect the (absence of) access rights
+ this.$el.toggleClass('o_cannot_create', !this.is_action_enabled('create'));
return this._super().then(this.trigger.bind(this, 'view_loaded'));
},
/**
@@ -76,7 +75,7 @@ var View = Widget.extend({
// response handler
var handler = function (action) {
- if (action && action.constructor == Object) {
+ 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
@@ -106,7 +105,7 @@ var View = Widget.extend({
if (action_data.special === 'cancel') {
return handler({"type":"ir.actions.act_window_close"});
- } else if (action_data.type=="object") {
+ } else if (action_data.type === "object") {
var args = [[record_id]];
if (action_data.args) {
try {
@@ -122,7 +121,7 @@ var View = Widget.extend({
return dataset.call_button(action_data.name, args).then(handler).then(function () {
core.bus.trigger('do_reload_needaction');
});
- } else if (action_data.type=="action") {
+ } else if (action_data.type === "action") {
return data_manager.load_action(action_data.name, _.extend(pyeval.eval('context', context), {
active_model: dataset.model,
active_ids: dataset.ids,
@@ -144,27 +143,24 @@ var View = Widget.extend({
do_load_state: function (state, warm) {
},
/**
- * This function should render the buttons of the view, set this.$buttons to
- * the produced jQuery element and define some listeners on it.
- * This function should be called after start().
+ * This function should render the action buttons of the view.
+ * It should be called after start().
* @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted
* $node may be undefined, in which case the View can insert the buttons somewhere else
*/
render_buttons: function($node) {
},
/**
- * This function should instantiate and render the sidebar of the view, set this.sidebar to
- * the instantiated Sidebar Widget and possibly add custom items in it.
- * This function should be called after start().
+ * This function should render the sidebar of the view.
+ * It should be called after start().
* @param {jQuery} [$node] a jQuery node where the sidebar should be inserted
* $node may be undefined, in which case the View can insert the sidebar somewhere else
*/
render_sidebar: function($node) {
},
/**
- * This function should render the pager of the view, set this.$pager to
- * the produced jQuery element and define some listeners on it.
- * This function should be called after start().
+ * This function should render the pager of the view.
+ * It should be called after start().
* @param {jQuery} [$node] a jQuery node where the pager should be inserted
* $node may be undefined, in which case the View can insert the pager somewhere else
*/
@@ -200,6 +196,12 @@ var View = Widget.extend({
get_context: function () {
return {};
},
+ set_scrollTop: function(scrollTop) {
+ this.scrollTop = scrollTop;
+ },
+ get_scrollTop: function() {
+ return this.scrollTop;
+ }
});
return View;
diff --git a/addons/web/static/src/js/framework/widget.js b/addons/web/static/src/js/framework/widget.js
index a6b189dc832..25a30ae6511 100644
--- a/addons/web/static/src/js/framework/widget.js
+++ b/addons/web/static/src/js/framework/widget.js
@@ -32,7 +32,7 @@ var mixins = core.mixins;
* },
* start: function() {
* // stuff you want to make after the rendering, `this.$el` holds a correct value
- * this.$el.find(".my_button").click(/* an example of event binding * /);
+ * this.$(".my_button").click(/* an example of event binding * /);
*
* // if you have some asynchronous operations, it's a good idea to return
* // a promise in start()
diff --git a/addons/web/static/src/js/menu.js b/addons/web/static/src/js/menu.js
index 4d8671595bd..c244305b7aa 100644
--- a/addons/web/static/src/js/menu.js
+++ b/addons/web/static/src/js/menu.js
@@ -15,7 +15,7 @@ var Menu = Widget.extend({
this.data = {data:{children:[]}};
this.on("menu_bound", this, function() {
// launch the fetch of needaction counters, asynchronous
- var $all_menus = self.$el.parents('body').find('.oe_webclient').find('[data-menu]');
+ var $all_menus = self.$el.parents('.o_web_client').find('.o_sub_menu').find('[data-menu]');
var all_menu_ids = _.map($all_menus, function (menu) {return parseInt($(menu).attr('data-menu'), 10);});
if (!_.isEmpty(all_menu_ids)) {
this.do_load_needaction(all_menu_ids);
@@ -34,7 +34,7 @@ var Menu = Widget.extend({
},
bind_menu: function() {
var self = this;
- this.$secondary_menus = this.$el.parents().find('.oe_secondary_menus_container');
+ this.$secondary_menus = this.$el.parents().find('.o_sub_menu');
this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
this.$el.on('click', 'a[data-menu]', function (event) {
event.preventDefault();
@@ -160,7 +160,7 @@ var Menu = Widget.extend({
$sub_menu.show();
// Hide/Show the leftbar menu depending of the presence of sub-items
- this.$secondary_menus.parent('.oe_leftbar').toggle(!!$sub_menu.children().length);
+ this.$secondary_menus.toggle(!!$sub_menu.children().length);
// Activate current menu item and show parents
this.$secondary_menus.find('.active').removeClass('active');
diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js
index 0dfab67b4ea..6a8a162e144 100644
--- a/addons/web/static/src/js/view_manager.js
+++ b/addons/web/static/src/js/view_manager.js
@@ -14,7 +14,25 @@ var QWeb = core.qweb;
var _t = core._t;
var ViewManager = Widget.extend(ControlPanelMixin, {
- template: "ViewManager",
+ className: "o_view_manager_content",
+ /**
+ * Called each time the view manager is attached into the DOM
+ */
+ on_attach_callback: function() {
+ this.is_in_DOM = true;
+ if (this.active_view && this.active_view.controller.on_attach_callback) {
+ this.active_view.controller.on_attach_callback();
+ }
+ },
+ /**
+ * Called each time the view manager is detached from the DOM
+ */
+ on_detach_callback: function() {
+ this.is_in_DOM = false;
+ if (this.active_view && this.active_view.controller.on_detach_callback) {
+ this.active_view.controller.on_detach_callback();
+ }
+ },
/**
* @param {Object} [dataset]
* @param {Array} [views] List of [view_id, view_type[, fields_view]]
@@ -34,12 +52,11 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
this.active_view = null;
this.registry = core.view_registry;
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;
var View = self.registry.get(view_type);
if (!View) {
- console.error("View type", "'"+view[1]+"'", "is not present in the view registry.");
+ console.error("View type", "'"+view_type+"'", "is not present in the view registry.");
return;
}
var view_label = View.prototype.display_name;
@@ -48,9 +65,9 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
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,
+ mobile_friendly: View.prototype.mobile_friendly,
multi_record: View.prototype.multi_record,
options: view.options || {},
require_fields: View.prototype.require_fields,
@@ -81,12 +98,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
view.options = _.extend({
action: self.action,
}, self.flags, self.flags[view.type], view.options);
- view.$container = self.$(".oe-view-manager-view-" + view.type);
});
-
- this.$el.addClass("oe_view_manager_" + ((this.action && this.action.target) || 'current'));
-
- this.control_elements = {};
if (this.flags.search_view) {
this.search_view_loaded = this.setup_search_view();
}
@@ -224,33 +236,40 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
var view_control_elements = this.render_view_control_elements();
// Show the view
- this.active_view.$container.show();
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,
+ active_view_selector: '.o_cp_switch_' + self.active_view.type,
breadcrumbs: self.action_manager && self.action_manager.get_breadcrumbs(),
- cp_content: _.extend({}, self.control_elements, view_control_elements),
+ cp_content: _.extend({}, self.searchview_elements, view_control_elements),
hidden: self.flags.headless,
searchview: self.searchview,
search_view_hidden: view_controller.searchable === false || view_controller.searchview_hidden,
};
self.update_control_panel(cp_status);
- if (old_view) {
- // Detach the old view but not ui-autocomplete elements to let
- // jquery-ui garbage-collect them
- old_view.$container.contents().not('.ui-autocomplete').detach();
-
- // Hide old view (at first rendering, there is no view to hide)
- if (self.active_view !== old_view) {
- if (old_view.controller) old_view.controller.do_hide();
- if (old_view.$container) old_view.$container.hide();
+ // Detach the old view and store it
+ if (old_view && old_view !== self.active_view) {
+ // Store the scroll position
+ if (self.action_manager && self.action_manager.webclient) {
+ old_view.controller.set_scrollTop(self.action_manager.webclient.get_scrollTop());
}
+ // Do not detach ui-autocomplete elements to let jquery-ui garbage-collect them
+ var $to_detach = self.$el.contents().not('.ui-autocomplete');
+ old_view.$fragment = framework.detach([{widget: old_view.controller}], {$to_detach: $to_detach});
}
- // Append the view fragment to its $container
- framework.append(self.active_view.$container, view_fragment, self.is_in_DOM);
+ // If the user switches from a multi-record to a mono-record view,
+ // the action manager should be scrolled to the top.
+ if (old_view && old_view.controller.multi_record === true && view_controller.multi_record === false) {
+ view_controller.set_scrollTop(0);
+ }
+
+ // Append the view fragment to self.$el
+ framework.append(self.$el, view_fragment, {
+ in_DOM: self.is_in_DOM,
+ callbacks: [{widget: view_controller}],
+ });
});
},
create_view: function(view, view_options) {
@@ -282,39 +301,50 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
return this.switch_mode(view_type);
},
/**
- * Renders the switch buttons and adds listeners on them but does not append them to the DOM
- * Sets $switch_buttons in control_elements to send to the ControlPanel
- * @param {Object} [src] the source requesting the switch_buttons
- * @param {Array} [views] the array of views
+ * Renders the switch buttons for multi- and mono-record views and adds
+ * listeners on them, but does not append them to the DOM
+ * Sets switch_buttons.$mono and switch_buttons.$multi to send to the ControlPanel
*/
render_switch_buttons: function() {
- if (this.flags.views_switcher && this.view_order.length > 1) {
- var self = this;
+ var self = this;
- // Render switch buttons but do not append them to the DOM as this will
- // be done later, simultaneously to all other ControlPanel elements
- this.control_elements.$switch_buttons = $(QWeb.render('ViewManager.switch-buttons', {views: self.view_order}));
+ // Partition the views according to their multi-/mono-record status
+ var views = _.partition(this.view_order, function(view) {
+ return view.multi_record === true;
+ });
+ var multi_record_views = views[0];
+ var mono_record_views = views[1];
- // Create bootstrap tooltips
- _.each(this.views, function(view) {
- self.control_elements.$switch_buttons.siblings('.oe-cp-switch-' + view.type).tooltip();
- });
+ // Inner function to render and prepare switch_buttons
+ var _render_switch_buttons = function(views) {
+ if (views.length > 1) {
+ var $switch_buttons = $(QWeb.render('ViewManager.switch-buttons', {views: views}));
+ // Create bootstrap tooltips
+ _.each(views, function(view) {
+ $switch_buttons.filter('.o_cp_switch_' + view.type).tooltip();
+ });
+ // Add onclick event listener
+ $switch_buttons.filter('button').click(_.debounce(function(event) {
+ var view_type = $(event.target).data('view-type');
+ self.switch_mode(view_type);
+ }, 200, true));
+ return $switch_buttons;
+ }
+ };
- // Add onclick event listener
- this.control_elements.$switch_buttons.siblings('button').click(_.debounce(function(event) {
- var view_type = $(event.target).data('view-type');
- self.switch_mode(view_type);
- }, 200, true));
- }
+ // Render switch buttons but do not append them to the DOM as this will
+ // be done later, simultaneously to all other ControlPanel elements
+ this.switch_buttons = {};
+ this.switch_buttons.$multi = _render_switch_buttons(multi_record_views);
+ this.switch_buttons.$mono = _render_switch_buttons(mono_record_views);
},
/**
- * Renders the control elements (buttons, sidebar, pager) of the current view
- * This must be done when active_search is resolved (for KanbanViews)
+ * Renders the control elements (buttons, sidebar, pager) of the current view.
* Fills this.active_view.control_elements dictionnary with the rendered
- * elements and the adequate view switcher, to send to the ControlPanel
+ * elements and the adequate view switcher, to send to the ControlPanel.
* Warning: it should be called before calling do_show on the view as the
* sidebar is extended to listen on the load_record event triggered as soon
- * as do_show is done (the sidebar should thus be instantiated before)
+ * as do_show is done (the sidebar should thus be instantiated before).
*/
render_view_control_elements: function() {
if (!this.active_view.control_elements) {
@@ -335,6 +365,15 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
elements = _.mapObject(elements, function($node) {
return $node && $node.contents();
});
+ // Use the adequate view switcher (mono- or multi-record)
+ if (this.switch_buttons) {
+ if (this.active_view.multi_record) {
+ elements.$switch_buttons = this.switch_buttons.$multi;
+ } else {
+ elements.$switch_buttons = this.switch_buttons.$mono;
+ }
+ }
+
// Store the rendered elements in the active_view to allow restoring them later
this.active_view.control_elements = elements;
}
@@ -342,10 +381,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
},
/**
* Sets up the current viewmanager's search view.
- * Sets $searchview and $searchview_buttons in control_elements to send to the ControlPanel
- *
- * @param {Number|false} view_id the view to use or false for a default one
- * @returns {jQuery.Deferred} search view startup deferred
+ * Sets $searchview and $searchview_buttons in searchview_elements to send to the ControlPanel
*/
setup_search_view: function() {
var self = this;
@@ -361,6 +397,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
search_defaults[match[1]] = value;
}
});
+
var options = {
hidden: this.flags.search_view === false,
disable_custom_filters: this.flags.search_disable_custom_filters,
@@ -374,8 +411,9 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
this.searchview.on('search_data', this, this.search.bind(this));
return $.when(this.searchview.appendTo($("<div>"))).done(function() {
- self.control_elements.$searchview = self.searchview.$el;
- self.control_elements.$searchview_buttons = self.searchview.$buttons.contents();
+ self.searchview_elements = {};
+ self.searchview_elements.$searchview = self.searchview.$el;
+ self.searchview_elements.$searchview_buttons = self.searchview.$buttons.contents();
});
},
/**
diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js
index 1c86a86c1ec..b3d078dbc1a 100644
--- a/addons/web/static/src/js/views/form_common.js
+++ b/addons/web/static/src/js/views/form_common.js
@@ -215,7 +215,7 @@ var CompletionFieldMixin = {
self._search_create_popup("search", data);
});
},
- classname: 'oe_m2o_dropdown_option'
+ classname: 'o_m2o_dropdown_option'
});
}
// quick create
@@ -228,7 +228,7 @@ var CompletionFieldMixin = {
action: function() {
self._quick_create(search_val);
},
- classname: 'oe_m2o_dropdown_option'
+ classname: 'o_m2o_dropdown_option'
});
}
// create...
@@ -238,14 +238,14 @@ var CompletionFieldMixin = {
action: function() {
self._search_create_popup("form", undefined, self._create_context(search_val));
},
- classname: 'oe_m2o_dropdown_option'
+ classname: 'o_m2o_dropdown_option'
});
}
else if (values.length === 0) {
values.push({
label: _t("No results to show..."),
action: function() {},
- classname: 'oe_m2o_dropdown_option'
+ classname: 'o_m2o_dropdown_option'
});
}
@@ -275,7 +275,7 @@ var CompletionFieldMixin = {
// all search/create popup handling
_search_create_popup: function(view, ids, context) {
var self = this;
- new SelectCreateDialog(this, {
+ new SelectCreateDialog(this, _.extend({}, (this.options || {}), {
res_model: self.field.relation,
domain: self.build_domain(),
context: new data.CompoundContext(self.build_context(), context || {}),
@@ -287,7 +287,7 @@ var CompletionFieldMixin = {
self.add_id(element_ids[0]);
self.focus();
}
- }).open();
+ })).open();
},
/**
* To implement.
@@ -474,7 +474,7 @@ var FormWidget = Widget.extend(InvisibilityChangerMixin, {
debug: session.debug,
widget: widget
});
- },
+ }
}, options || {});
//only show tooltip if we are in debug or if we have a help to show, otherwise it will display
//as empty
@@ -666,16 +666,16 @@ var AbstractField = FormWidget.extend(FieldInterface, {
this._check_css_flags();
});
- this.$translate = $(); // Enterprise compatibility
+ this.$translate = (_t.database.multi_lang && this.field.translate) ? $('<button/>', {
+ type: 'button',
+ }).addClass('o_field_translate fa fa-globe btn btn-link') : $();
},
+
renderElement: function() {
var self = this;
this._super();
- if (this.field.translate && this.view) {
- this.$el.addClass('oe_form_field_translatable');
- this.$el.find('.oe_field_translate').click(this.on_translate);
- }
- this.$label = this.view ? this.view.$el.find('label[for=' + this.id_for_label + ']') : $();
+ this.$el.addClass('o_form_field');
+ this.$label = this.view ? this.view.$('label[for=' + this.id_for_label + ']') : $();
this.do_attach_tooltip(this, this.$label[0] || this.$el);
if (session.debug) {
this.$label.off('dblclick').on('dblclick', function() {
@@ -707,16 +707,23 @@ var AbstractField = FormWidget.extend(FieldInterface, {
});
this.render_value();
this._toggle_label();
+
+ if (this.view) {
+ this.$translate
+ .insertAfter(this.$el)
+ .on('click', _.bind(this.on_translate, this));
+ }
},
_toggle_label: function() {
var empty = this.get('effective_readonly') && this.is_false();
+ this.$label.toggleClass('o_form_label_empty', empty).toggleClass('o_form_label_false', this.get('effective_readonly') && this.get('value') === false);
this.$el.toggleClass('o_form_field_empty', empty);
},
/**
* Private. Do not use.
*/
_set_required: function() {
- this.$el.toggleClass('oe_form_required', this.get("required"));
+ this.$el.toggleClass('o_form_required', this.get("required"));
},
set_value: function(value_) {
this.set({'value': value_});
@@ -752,10 +759,11 @@ var AbstractField = FormWidget.extend(FieldInterface, {
return this.get('value') === false;
},
_check_css_flags: function() {
- if (this.field.translate) {
- this.$el.find('.oe_field_translate').toggle(this.field_manager.get('actual_mode') !== "create");
- }
- this.$el.toggleClass('oe_form_invalid', !this.disable_utility_classes && !!this.field_manager.get('display_invalid_fields') && !this.is_valid());
+ var show_translate = (!this.get('effective_readonly') && this.field_manager.get('actual_mode') !== "create");
+ this.$translate.toggleClass('o_translate_active', !!show_translate);
+
+ this.$el.add(this.$label)
+ .toggleClass('o_form_invalid', !this.disable_utility_classes && !!this.field_manager.get('display_invalid_fields') && !this.is_valid());
},
focus: function() {
return false;
@@ -770,11 +778,10 @@ var AbstractField = FormWidget.extend(FieldInterface, {
self.do_action(r);
});
},
-
- set_dimensions: function (height, width) {
+ set_dimensions: function(height, width) {
this.$el.css({
width: width,
- minHeight: height
+ height: height,
});
},
commit_value: function() {
@@ -802,7 +809,7 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager
options.dialogClass = options.dialogClass || '';
options.dialogClass += ' o_act_window';
- this._super(parent, _.clone(options));
+ this._super(parent, $.extend(true, {}, options));
this.res_model = options.res_model || null;
this.res_id = options.res_id || null;
@@ -864,7 +871,7 @@ var FormViewDialog = ViewDialog.extend({
];
if(!readonly) {
- options.buttons.splice(0, 0, {text: _t("Save") + ((multi_select)? " " + _t(" & Close") : ""), classes: "btn-primary o_formdialog_save", click: function() { // o_formdialog_save class for web_tests!
+ options.buttons.splice(0, 0, {text: _t("Save") + ((multi_select)? " " + _t(" & Close") : ""), classes: "btn-primary", click: function() {
self.view_form.onchanges_mutex.def.then(function() {
if (!self.view_form.warning_displayed) {
$.when(self.view_form.save()).done(function() {
@@ -923,6 +930,7 @@ var FormViewDialog = ViewDialog.extend({
var fragment = document.createDocumentFragment();
self.view_form.appendTo(fragment).then(function () {
self.view_form.do_show().then(function() {
+ self.view_form.autofocus();
_super().$el.append(fragment);
});
});
@@ -941,7 +949,7 @@ var SelectCreateListView = ListView.extend({
this.popup.close();
},
do_select: function(ids, records) {
- this._super(ids, records);
+ this._super.apply(this, arguments);
this.popup.on_click_element(ids);
}
});
@@ -1022,7 +1030,7 @@ var SelectCreateDialog = ViewDialog.extend({
_.extend({'deletable': false,
'selectable': !self.options.disable_multiple_selection,
'import_enabled': false,
- '$buttons': self.$footer,
+ '$buttons': self.$buttons,
'disable_editable_mode': true,
'pager': true,
}, self.options.list_view_options || {}));
@@ -1030,6 +1038,9 @@ var SelectCreateDialog = ViewDialog.extend({
e.cancel = true;
});
self.view_list.popup = self;
+ self.view_list.on('list_view_loaded', self, function() {
+ this.on_view_list_loaded();
+ });
var buttons = [
{text: _t("Cancel"), classes: "btn-default o_form_button_cancel", close: true}
@@ -1076,6 +1087,7 @@ var SelectCreateDialog = ViewDialog.extend({
this.close();
return new FormViewDialog(this.__parentedParent, this.options).open();
},
+ on_view_list_loaded: function() {},
});
var DomainEditorDialog = SelectCreateDialog.extend({
@@ -1095,11 +1107,11 @@ var DomainEditorDialog = SelectCreateDialog.extend({
},
get_domain: function (selected_ids) {
- var group_domain = [],
- domain;
- if (this.$('input.oe_list_record_selector').prop('checked')) {
+ var group_domain = [];
+ var domain;
+ if (this.$('.o_list_record_selector input').prop('checked')) {
if (this.view_list.grouped) {
- var group_domain = _.chain(_.values(this.view_list.groups.children))
+ group_domain = _.chain(_.values(this.view_list.groups.children))
.filter(function (child) { return child.records.length; })
.map(function (c) { return c.datagroup.domain;})
.value();
@@ -1118,6 +1130,11 @@ var DomainEditorDialog = SelectCreateDialog.extend({
}
return this.dataset.domain.concat(group_domain).concat(domain || []);
},
+
+ on_view_list_loaded: function() {
+ this.$('.o_list_record_selector input').prop('checked', true);
+ this.$footer.find(".o_selectcreatepopup_search_select").prop('disabled', false);
+ },
});
return {
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 fc852108035..8e64599f470 100644
--- a/addons/web/static/src/js/views/form_relational_widgets.js
+++ b/addons/web/static/src/js/views/form_relational_widgets.js
@@ -6,7 +6,6 @@ var core = require('web.core');
var data = require('web.data');
var Dialog = require('web.Dialog');
var common = require('web.form_common');
-var FormView = require('web.FormView');
var ListView = require('web.ListView');
var Model = require('web.DataModel');
var session = require('web.session');
@@ -56,16 +55,15 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
events: {
'keydown input': function (e) {
switch (e.which) {
- case $.ui.keyCode.UP:
- case $.ui.keyCode.DOWN:
- e.stopPropagation();
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.DOWN:
+ e.stopPropagation();
}
},
},
init: function(field_manager, node) {
this._super(field_manager, node);
common.CompletionFieldMixin.init.call(this);
- this.set({'value': false});
this.display_value = {};
this.display_value_backup = {};
this.last_search = [];
@@ -91,8 +89,9 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
common.ReinitializeFieldMixin.initialize_field.call(this);
},
initialize_content: function() {
- if (!this.get("effective_readonly"))
+ if (!this.get("effective_readonly")) {
this.render_editable();
+ }
},
destroy_content: function () {
if (this.$dropdown) {
@@ -125,16 +124,15 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
},
render_editable: function() {
var self = this;
- this.$input = this.$el.find("input");
+ this.$input = this.$("input");
this.init_error_displayer();
-
self.$input.on('focus', function() {
self.hide_error_displayer();
});
- this.$dropdown = this.$el.find(".oe_m2o_drop_down_button");
- this.$follow_button = $(".oe_m2o_cm_button", this.$el);
+ this.$dropdown = this.$(".o_dropdown_button");
+ this.$follow_button = this.$(".o_external_button");
this.$follow_button.click(function(ev) {
ev.preventDefault();
@@ -177,8 +175,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
};
this.$input.keydown(input_changed);
this.$input.change(input_changed);
- this.$dropdown.click(function() {
- self.$input.focus();
+ this.$input.on('click', function() {
if (self.$input.autocomplete("widget").is(":visible")) {
self.$input.autocomplete("close");
} else {
@@ -298,7 +295,6 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
});
// set position for list of suggestions box
this.$input.autocomplete( "option", "position", { my : "left top", at: "left bottom" } );
- this.$input.autocomplete("widget").openerpClass();
// used to correct a bug when selecting an element by pushing 'enter' in an editable list
this.$input.keyup(function(e) {
if (e.which === 13) { // ENTER
@@ -311,7 +307,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
},
render_value: function(no_recurse) {
var self = this;
- if (! this.get("value")) {
+ if (!this.get("value")) {
this.display_string("");
return;
}
@@ -320,7 +316,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
this.display_string(display);
return;
}
- if (! no_recurse) {
+ if (!no_recurse) {
var dataset = new data.DataSetStatic(this, this.field.relation, self.build_context());
var def = this.alive(dataset.name_get([self.get("value")])).done(function(data) {
if (!data[0]) {
@@ -341,38 +337,33 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
}
},
display_string: function(str) {
- var self = this;
if (!this.get("effective_readonly")) {
this.$input.val(str.split("\n")[0]);
this.current_display = this.$input.val();
- if (this.is_false()) {
- this.$('.oe_m2o_cm_button').css({'display':'none'});
- } else {
- this.$('.oe_m2o_cm_button').css({'display':'inline'});
- }
+ this.$follow_button.toggle(!this.is_false());
+ this.$el.toggleClass('o_with_button', !!this.$follow_button && this.$follow_button.length > 0 && !this.is_false());
} else {
- var lines = _.escape(str).split("\n");
- var link = "";
- var follow = "";
- link = lines[0];
- follow = _.rest(lines).join("<br />");
- if (follow)
- link += "<br />";
- var $link = this.$el.find('.oe_form_uri')
- .unbind('click')
- .html(link);
- if (! this.options.no_open)
- $link.click(function () {
- var context = self.build_context().eval();
- var model_obj = new Model(self.field.relation);
- model_obj.call('get_formview_action', [self.get("value"), context]).then(function(action){
- self.do_action(action);
- });
- return false;
- });
- $(".oe_form_m2o_follow", this.$el).html(follow);
+ this.$el.html(_.escape(str).split("\n").join("<br/>"));
+ // Define callback to perform when clicking on the field
+ if (!this.options.no_open) {
+ // Remove potential previously added event handler
+ this.$el.off('click');
+ // Ensure that the callback is performed only once even with multiple clicks
+ var execute_formview_action_once = _.once(this.execute_formview_action.bind(this));
+ this.$el.click(function (ev) {
+ ev.preventDefault();
+ execute_formview_action_once();
+ });
+ }
}
},
+ execute_formview_action: function() {
+ var self = this;
+ var context = self.build_context().eval();
+ (new Model(self.field.relation)).call('get_formview_action', [self.get("value"), context]).then(function(action) {
+ self.do_action(action);
+ });
+ },
set_value: function(value_) {
if (value_ instanceof Array) {
this.display_value = {};
@@ -418,11 +409,6 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
this.no_ed = false;
return res;
},
- set_dimensions: function (height, width) {
- this._super(height, width);
- if (!this.get("effective_readonly") && this.$input)
- this.$input.css('height', height);
- }
});
/**
@@ -586,40 +572,39 @@ var AbstractManyField = common.AbstractField.extend({
_.each(command_list, function(command) {
self.mutex.exec(function() {
var id = command[1];
- if (!id || _.isString(id) || _.isNumber(id)) {
- switch (command[0]) {
- case COMMANDS.CREATE:
- var data = _.clone(command[2]);
- delete data.id;
- return dataset.create(data, internal_options).then(function (id) {
+ switch (command[0]) {
+ case COMMANDS.CREATE:
+ var data = _.clone(command[2]);
+ delete data.id;
+ return dataset.create(data, internal_options).then(function (id) {
+ dataset.ids.push(id);
+ res = id;
+ });
+ case COMMANDS.UPDATE:
+ return dataset.write(id, command[2], internal_options).then(function () {
+ if (dataset.ids.indexOf(id) === -1) {
dataset.ids.push(id);
res = id;
- });
- case COMMANDS.UPDATE:
- return dataset.write(id, command[2], internal_options).then(function () {
- if (dataset.ids.indexOf(id) === -1) {
- dataset.ids.push(id);
- res = id;
- }
- });
- case COMMANDS.FORGET:
- return dataset.unlink([id]);
- case COMMANDS.DELETE:
- return dataset.unlink([id]);
- case COMMANDS.LINK_TO:
- if (dataset.ids.indexOf(id) === -1) {
- return dataset.add_ids([id], internal_options);
}
- return;
- case COMMANDS.DELETE_ALL:
- return dataset.reset_ids([], {keep_read_data: true});
- case COMMANDS.REPLACE_WITH:
- dataset.ids = [];
- return dataset.alter_ids(command[2], internal_options);
- }
+ });
+ case COMMANDS.FORGET:
+ return dataset.unlink([id]);
+ case COMMANDS.DELETE:
+ return dataset.unlink([id]);
+ case COMMANDS.LINK_TO:
+ if (dataset.ids.indexOf(id) === -1) {
+ return dataset.add_ids([id], internal_options);
+ }
+ return;
+ case COMMANDS.DELETE_ALL:
+ return dataset.reset_ids([], {keep_read_data: true});
+ case COMMANDS.REPLACE_WITH:
+ dataset.ids = [];
+ return dataset.alter_ids(command[2], internal_options);
+ default:
+ throw new Error("send_commands to '"+self.name+"' receive a non command value." +
+ "\n" + JSON.stringify(command_list));
}
- throw new Error("send_commands to '"+self.name+"' receive a non command value." +
- "\n" + JSON.stringify(command_list));
});
});
@@ -660,10 +645,6 @@ var AbstractManyField = common.AbstractField.extend({
if (record.to_create) {
command_list.push(COMMANDS.create(values));
} else {
- if (index === -1) {
- // because the UPDATE below does not imply LINK_TO
- command_list.push(COMMANDS.link_to(id));
- }
command_list.push(COMMANDS.update(record.id, values));
}
return;
@@ -724,13 +705,12 @@ var FieldX2Many = AbstractManyField.extend({
},
start: function() {
this._super.apply(this, arguments);
- this.$el.addClass('oe_form_field');
-
var self = this;
this.load_views();
var destroy = function() {
self.is_loaded = self.is_loaded.then(function() {
+ self.renderElement();
self.viewmanager.destroy();
return $.when(self.load_views()).done(function() {
self.reload_current_view();
@@ -828,7 +808,8 @@ var FieldX2Many = AbstractManyField.extend({
});
});
$.async_when().done(function () {
- self.alive( self.viewmanager.appendTo(self.$el));
+ self.$el.addClass('o_view_manager_content');
+ self.alive(self.viewmanager.attachTo(self.$el));
});
return def;
},
@@ -881,6 +862,9 @@ var FieldX2Many = AbstractManyField.extend({
}
return true;
},
+ is_false: function() {
+ return false;
+ },
});
var X2ManyDataSet = data.BufferedDataSet.extend({
@@ -891,10 +875,15 @@ var X2ManyDataSet = data.BufferedDataSet.extend({
self.context.add(context);
});
return this.context;
- }
+ },
});
var X2ManyViewManager = ViewManager.extend({
+ custom_events: {
+ // Catch event scrollTo to prevent scrolling to the top when using the
+ // pager of List and Kanban views in One2Many fields
+ 'scrollTo': function() {},
+ },
init: function(parent, dataset, views, flags, x2many_views) {
// By default, render buttons and pager in X2M fields, but no sidebar
flags = _.extend({}, flags, {
@@ -984,6 +973,15 @@ var X2ManyListView = ListView.extend({
});
return valid;
},
+ render_pager: function($node, options) {
+ options = _.extend(options || {}, {
+ single_page_hidden: true,
+ });
+ this._super($node, options);
+ },
+ display_nocontent_helper: function () {
+ return false;
+ },
});
/**
@@ -1008,7 +1006,7 @@ var X2ManyList = ListView.List.extend({
var $cell = $('<td>', {
colspan: columns,
- 'class': 'oe_form_field_x2many_list_row_add'
+ 'class': 'o_form_field_x2many_list_row_add'
}).append(
$('<a>', {href: '#'}).text(_t("Add an item"))
.click(function (e) {
@@ -1037,7 +1035,6 @@ var X2ManyList = ListView.List.extend({
});
var One2ManyListView = X2ManyListView.extend({
- _template: 'One2Many.listview',
init: function () {
this._super.apply(this, arguments);
this.options = _.extend(this.options, {
@@ -1177,7 +1174,7 @@ var One2ManyListView = X2ManyListView.extend({
var click_outside = ($target.closest('.ui-autocomplete,.btn,.modal-backdrop').length === 0);
// Check if click inside the current list editable
- var $o2m = $target.closest(".oe_list_editable");
+ var $o2m = $target.closest(".o_list_editable");
if($o2m.length && $o2m[0] === this.el) {
click_outside = false;
}
@@ -1251,39 +1248,19 @@ var One2ManyGroups = ListView.Groups.extend({
}
});
-var One2ManyFormView = FormView.extend({
- form_template: 'One2Many.formview',
- load_form: function(data) {
- this._super(data);
- var self = this;
- this.$buttons.find('button.oe_form_button_create').click(function() {
- self.save().done(self.on_button_new);
- });
- },
- do_notify_change: function() {
- if (this.dataset.parent_view) {
- this.dataset.parent_view.do_notify_change();
- } else {
- this._super.apply(this, arguments);
- }
- }
-});
-
var FieldOne2Many = FieldX2Many.extend({
init: function() {
this._super.apply(this, arguments);
this.x2many_views = {
- form: One2ManyFormView,
kanban: core.view_registry.get('one2many_kanban'),
list: One2ManyListView,
};
},
start: function() {
- this.$el.addClass('oe_form_field_one2many');
+ this.$el.addClass('o_form_field_one2many');
return this._super.apply(this, arguments);
},
commit_value: function() {
- var self = this;
var view = this.viewmanager.active_view;
if(view.type === "list" && view.controller.editable()) {
return this.mutex.def.then(function () {
@@ -1357,7 +1334,7 @@ var FieldMany2Many = FieldX2Many.extend({
};
},
start: function() {
- this.$el.addClass('oe_form_field_many2many');
+ this.$el.addClass('o_form_field_many2many');
return this._super.apply(this, arguments);
}
});
@@ -1373,7 +1350,7 @@ var FieldMany2ManyKanban = FieldMany2Many.extend({
});
var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, common.ReinitializeFieldMixin, {
- className: "oe_form_field o_form_field_many2manytags",
+ className: "o_form_field_many2manytags",
tag_template: "FieldMany2ManyTag",
events: {
@@ -1435,7 +1412,6 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
}
},
get_render_data: function(ids){
- var self = this;
this.dataset.cancel_read();
var fields = this.fields.color ? ['display_name', 'name', 'color'] : ['display_name', 'name']; // TODO master: remove useless 'name'
return this.dataset.read_ids(ids, fields);
@@ -1475,26 +1451,30 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
}
return false;
},
- set_dimensions: function (height, width) {
- this._super(height, width);
- var $input = this.$('input');
- if (!this.get("effective_readonly") && $input) {
- $input.css('height', height);
+ set_dimensions: function(height, width) {
+ if(this.many2one) {
+ this.many2one.$el.css('height', 'auto');
+ }
+ this.$el.css({
+ width: width,
+ minHeight: height,
+ });
+ if(this.many2one) {
+ this.many2one.$el.css('height', this.$el.height());
}
},
open_color_picker: function(ev){
- this.mutex.exec(function(){
- if (this.fields.color) {
- this.$color_picker = $(QWeb.render('FieldMany2ManyTag.colorpicker', {
- 'widget': this,
- 'tag_id': $(ev.currentTarget).data('id'),
- }));
- $(ev.currentTarget).append(this.$color_picker);
- this.$color_picker.dropdown('toggle');
- this.$color_picker.attr("tabindex", 1).focus();
- }
- }.bind(this));
+ if (this.fields.color) {
+ this.$color_picker = $(QWeb.render('FieldMany2ManyTag.colorpicker', {
+ 'widget': this,
+ 'tag_id': $(ev.currentTarget).data('id'),
+ }));
+
+ $(ev.currentTarget).append(this.$color_picker);
+ this.$color_picker.dropdown('toggle');
+ this.$color_picker.attr("tabindex", 1).focus();
+ }
},
close_color_picker: function(){
this.$color_picker.remove();
@@ -1513,24 +1493,76 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
var old_color = tag.data('color');
tag.removeClass('o_tag_color_' + old_color);
tag.data('color', color);
- tag.addClass('o_tag_color_' + color);
+ tag.addClass('o_tag_color_' + color);
});
},
-
});
/**
* Widget for (many2many field) to upload one or more file in same time and display in list.
* The user can delete his files.
- * Options on attribute ; "blockui" {Boolean} block the UI or not
- * during the file is uploading
+ * Options on attribute ; "blockui" {Boolean} block the UI or not during the file is uploading
*/
var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.ReinitializeFieldMixin, {
template: "FieldBinaryFileUploader",
+ events: {
+ 'click .o_attach': function(e) {
+ this.$('.o_form_input_file').click();
+ },
+ 'change .o_form_input_file': function(e) {
+ e.stopPropagation();
+
+ var $target = $(e.target);
+ var value = $target.val();
+
+ if(value !== '') {
+ if(this.data[0] && this.data[0].upload) { // don't upload more of one file in same time
+ return false;
+ }
+
+ var filename = value.replace(/.*[\\\/]/, '');
+ for(var id in this.get('value')) {
+ // if the files exits, delete the file before upload (if it's a new file)
+ if(this.data[id] && (this.data[id].filename || this.data[id].name) == filename && !this.data[id].no_unlink) {
+ this.ds_file.unlink([id]);
+ }
+ }
+
+ if(this.node.attrs.blockui > 0) { // block UI or not
+ framework.blockUI();
+ }
+
+ // TODO : unactivate send on wizard and form
+
+ // submit file
+ this.$('form.o_form_binary_form').submit();
+ this.$(".oe_fileupload").hide();
+ // add file on data result
+ this.data[0] = {
+ id: 0,
+ name: filename,
+ filename: filename,
+ url: '',
+ upload: true,
+ };
+ }
+ },
+ 'click .oe_delete': function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ var file_id = $(e.currentTarget).data("id");
+ if(file_id) {
+ var files = _.without(this.get('value'), file_id);
+ if(!this.data[file_id].no_unlink) {
+ this.ds_file.unlink([file_id]);
+ }
+ this.set({'value': files});
+ }
+ },
+ },
init: function(field_manager, node) {
- this._super(field_manager, node);
- this.field_manager = field_manager;
- this.node = node;
+ this._super.apply(this, arguments);
this.session = session;
if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') {
throw _.str.sprintf(_t("The type of the field '%s' must be a many2many field with a relation to 'ir.attachment' model."), this.field.string);
@@ -1541,40 +1573,39 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
$(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this));
},
- initialize_content: function() {
- this.$el.on('change', 'input.o_form_input_file', this.on_file_change );
- },
- get_file_url: function (attachment) {
+ get_file_url: function(attachment) {
return '/web/content/' + attachment.id + '?download=true';
},
- read_name_values : function () {
+ read_name_values : function() {
var self = this;
// don't reset know values
var ids = this.get('value');
- var _value = _.filter(ids, function (id) { return typeof self.data[id] == 'undefined'; } );
+ var _value = _.filter(ids, function(id) { return self.data[id] === undefined; });
// send request for get_name
- if (_value.length) {
- return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname', 'mimetype']]).then(function (datas) {
- _.each(datas, function (data) {
- data.no_unlink = true;
- data.url = self.get_file_url(data);
- self.data[data.id] = data;
- });
- return ids;
- });
+ if(_value.length) {
+ return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname', 'mimetype']])
+ .then(process_data);
} else {
return $.when(ids);
}
+
+ function process_data(datas) {
+ _.each(datas, function(data) {
+ data.no_unlink = true;
+ data.url = self.get_file_url(data);
+ self.data[data.id] = data;
+ });
+ return ids;
+ }
},
- render_value: function () {
+ render_value: function() {
var self = this;
this.read_name_values().then(function (ids) {
- var render = $(QWeb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}));
- render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
- self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
+ self.$('.oe_placeholder_files, .oe_attachments')
+ .replaceWith($(QWeb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids})));
// reinit input type file
- var $input = self.$('input.o_form_input_file');
+ var $input = self.$('.o_form_input_file');
$input.after($input.clone(true)).remove();
self.$(".oe_fileupload").show();
@@ -1587,77 +1618,26 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ
});
});
},
- on_file_change: function (event) {
- event.stopPropagation();
- var self = this;
- var $target = $(event.target);
- if ($target.val() !== '') {
- var filename = $target.val().replace(/.*[\\\/]/,'');
- // don't uplode more of one file in same time
- if (self.data[0] && self.data[0].upload ) {
- return false;
- }
- for (var id in this.get('value')) {
- // if the files exits, delete the file before upload (if it's a new file)
- if (self.data[id] && (self.data[id].filename || self.data[id].name) == filename && !self.data[id].no_unlink ) {
- self.ds_file.unlink([id]);
- }
- }
-
- // block UI or not
- if(this.node.attrs.blockui>0) {
- framework.blockUI();
- }
-
- // TODO : unactivate send on wizard and form
-
- // submit file
- this.$('form.o_form_binary_form').submit();
- this.$(".oe_fileupload").hide();
- // add file on data result
- this.data[0] = {
- 'id': 0,
- 'name': filename,
- 'filename': filename,
- 'url': '',
- 'upload': true
- };
- }
- },
- on_file_loaded: function (event, result) {
- var files = this.get('value');
-
- // unblock UI
- if(this.node.attrs.blockui>0) {
+ on_file_loaded: function(e, result) {
+ if(this.node.attrs.blockui > 0) { // unblock UI
framework.unblockUI();
}
- if (result.error || !result.id ) {
- this.do_warn( _t('Uploading Error'), result.error);
+ if(result.error || !result.id) {
+ this.do_warn(_t('Uploading Error'), result.error);
delete this.data[0];
} else {
- if (this.data[0] && this.data[0].filename == result.filename && this.data[0].upload) {
+ if(this.data[0] && this.data[0].filename === result.filename && this.data[0].upload) {
delete this.data[0];
}
result.url = this.get_file_url(result);
this.data[result.id] = result;
var values = _.clone(this.get('value'));
values.push(result.id);
- this.set({'value': values});
+ this.set({value: values});
}
this.render_value();
},
- on_file_delete: function (event) {
- event.stopPropagation();
- var file_id=$(event.target).closest('.oe_delete').data("id");
- if (file_id) {
- var files = _.filter(this.get('value'), function (id) {return id != file_id;});
- if(!this.data[file_id].no_unlink) {
- this.ds_file.unlink([file_id]);
- }
- this.set({'value': files});
- }
- },
});
/*
@@ -1672,7 +1652,7 @@ var FieldMany2ManyCheckBoxes = AbstractManyField.extend(common.ReinitializeField
this.set("records", []);
this.field_manager.on("view_content_has_changed", this, function() {
var domain = new data.CompoundDomain(this.build_domain()).eval();
- if (! _.isEqual(domain, this.get("domain"))) {
+ if (!_.isEqual(domain, this.get("domain"))) {
this.set("domain", domain);
}
});
@@ -1694,18 +1674,22 @@ var FieldMany2ManyCheckBoxes = AbstractManyField.extend(common.ReinitializeField
});
},
render_value: function() {
- this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this}));
- var inputs = this.$("input");
- inputs.change(_.bind(this.from_dom, this));
- if (this.get("effective_readonly"))
- inputs.attr("disabled", "true");
+ this.$el.html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this}));
+ var $inputs = this.$("input");
+ $inputs.change(_.bind(this.from_dom, this));
+ if (this.get("effective_readonly")) {
+ $inputs.attr("disabled", "true");
+ }
},
from_dom: function() {
var new_value = this.$("input:checked").map(function() { return +$(this).data("record-id"); }).get();
- if (! _.isEqual(new_value, this.get("value"))) {
+ if (!_.isEqual(new_value, this.get("value"))) {
this.internal_set_value(new_value);
}
- }
+ },
+ is_false: function() {
+ return false;
+ },
});
core.form_widget_registry
@@ -1718,9 +1702,13 @@ core.form_widget_registry
.add('many2many_binary', FieldMany2ManyBinaryMultiFiles)
.add('many2many_checkboxes', FieldMany2ManyCheckBoxes);
+core.one2many_view_registry
+ .add('list', One2ManyListView);
+
return {
FieldMany2ManyTags: FieldMany2ManyTags,
AbstractManyField: AbstractManyField,
+ FieldMany2One: FieldMany2One,
};
});
diff --git a/addons/web/static/src/js/views/form_upgrade_widgets.js b/addons/web/static/src/js/views/form_upgrade_widgets.js
new file mode 100644
index 00000000000..ce32ef0664b
--- /dev/null
+++ b/addons/web/static/src/js/views/form_upgrade_widgets.js
@@ -0,0 +1,101 @@
+odoo.define('web.form_upgrade_widgets', function (require) {
+"use strict";
+
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var form_widgets = require('web.form_widgets');
+var framework = require('web.framework');
+var Model = require('web.DataModel');
+
+var _t = core._t;
+var QWeb = core.qweb;
+
+/**
+ * This widget is intended to be used in config settings.
+ * When checked, an upgrade popup is showed to the user.
+ */
+var AbstractFieldUpgrade = {
+ events: {
+ 'click input': 'on_click_input',
+ },
+
+ start: function() {
+ this._super.apply(this, arguments);
+
+ this.get_enterprise_label().after($("<span>", {
+ text: "Enterprise",
+ 'class': "label label-primary oe_inline"
+ }));
+ },
+
+ open_dialog: function() {
+ var message = $(QWeb.render('EnterpriseUpgrade'));
+
+ var buttons = [
+ {
+ text: _t("Upgrade now"),
+ classes: 'btn-primary',
+ close: true,
+ click: this.confirm_upgrade,
+ },
+ {
+ text: _t("Cancel"),
+ close: true,
+ },
+ ];
+
+ return new Dialog(this, {
+ size: 'medium',
+ buttons: buttons,
+ $content: $('<div>', {
+ html: message,
+ }),
+ title: _t("Odoo Enterprise"),
+ }).open();
+ },
+
+ confirm_upgrade: function() {
+ new Model("res.users").call("search_count", [[["share", "=", false]]]).then(function(data) {
+ framework.redirect("https://www.odoo.com/odoo-enterprise/upgrade?num_users=" + data);
+ });
+ },
+
+ get_enterprise_label: function() {},
+ on_click_input: function() {},
+};
+
+var UpgradeBoolean = form_widgets.FieldBoolean.extend(AbstractFieldUpgrade, {
+ template: "FieldUpgradeBoolean",
+
+ get_enterprise_label: function() {
+ return this.$label;
+ },
+
+ on_click_input: function() {
+ if(this.$checkbox.prop("checked")) {
+ this.open_dialog().on('closed', this, function() {
+ this.$checkbox.prop("checked", false);
+ });
+ }
+ },
+});
+
+var UpgradeRadio = form_widgets.FieldRadio.extend(AbstractFieldUpgrade, {
+ get_enterprise_label: function() {
+ return this.$('label').last();
+ },
+
+ on_click_input: function(event) {
+ if($(event.target).val() === 1) {
+ this.open_dialog().on('closed', this, function() {
+ this.$('input').first().prop("checked", true);
+ });
+ }
+ },
+});
+
+core.form_widget_registry
+ .add('upgrade_boolean', UpgradeBoolean)
+ .add('upgrade_radio', UpgradeRadio);
+
+});
diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js
index 7a2cf428400..d2c7a21eb04 100644
--- a/addons/web/static/src/js/views/form_view.js
+++ b/addons/web/static/src/js/views/form_view.js
@@ -1,12 +1,14 @@
odoo.define('web.FormView', function (require) {
"use strict";
+var common = require('web.form_common');
var core = require('web.core');
var crash_manager = require('web.crash_manager');
var data = require('web.data');
var Dialog = require('web.Dialog');
-var common = require('web.form_common');
+var FormRenderingEngine = require('web.FormRenderingEngine');
var Model = require('web.DataModel');
+var Pager = require('web.Pager');
var Sidebar = require('web.Sidebar');
var utils = require('web.utils');
var View = require('web.View');
@@ -14,7 +16,6 @@ var View = require('web.View');
var _t = core._t;
var _lt = core._lt;
var QWeb = core.qweb;
-var Class = core.Class;
/**
* Properties:
@@ -22,7 +23,7 @@ var Class = core.Class;
* the mode used by the view.
*/
var FormView = View.extend(common.FieldManagerMixin, {
- accesskey: "F",
+ className: "o_form_view",
defaults: _.extend({}, View.prototype.defaults, {
not_interactible_on_create: false,
initial_mode: "view",
@@ -34,8 +35,20 @@ var FormView = View.extend(common.FieldManagerMixin, {
multi_record: false,
// Indicates that this view is not searchable, and thus that no search view should be displayed.
searchable: false,
- template: "FormView",
+ /**
+ * Called each time the form view is attached into the DOM
+ */
+ on_attach_callback: function() {
+ this.trigger('attached');
+ this.autofocus();
+ },
+ /**
+ * Called each time the form view is detached from the DOM
+ */
+ on_detach_callback: function() {
+ this.trigger('detached');
+ },
init: function() {
var self = this;
this._super.apply(this, arguments);
@@ -60,14 +73,11 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.__clicked_inside = false;
this.__blur_timeout = null;
this.rendering_engine = new FormRenderingEngine(this);
- self.set({actual_mode: self.options.initial_mode});
+ this.set({actual_mode: this.options.initial_mode});
this.has_been_loaded.done(function() {
self._build_onchange_specs();
- self.check_actual_mode();
- self.on("change:actual_mode", self, self.check_actual_mode);
self.on("change:actual_mode", self, self.toggle_buttons);
self.on("change:actual_mode", self, self.toggle_sidebar);
- self.on("change:actual_mode", self, self.do_update_pager);
});
self.on("load_record", self, self.load_record);
core.bus.on('clear_uncommitted_changes', this, function(chain_callbacks) {
@@ -90,8 +100,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.rendering_engine.set_tags_registry(this.tags_registry);
this.rendering_engine.set_widgets_registry(this.widgets_registry);
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);
+ this.rendering_engine.render_to(this.$el);
this.$el.on('mousedown.formBlur', function () {
self.__clicked_inside = true;
@@ -100,22 +109,14 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.has_been_loaded.resolve();
// 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) {
+ this.$(".oe_title,.o_group").on('mouseup', function (e) { // 'mouseup' event because some widget need bootstrap click event to go up to body
if(self.get("actual_mode") === "view" && self.$buttons && !$(e.target).is('[data-toggle]')) {
- var $button = self.$buttons.find(".oe_form_button_edit");
+ var $button = self.$buttons.find(".o_form_button_edit");
$button.openerpBounce();
e.stopPropagation();
core.bus.trigger('click', e);
}
});
- //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")) {
- var $button = self.$el.find(".oe_highlight:not(.o_form_invisible)").css({'float':'left','clear':'none'});
- $button.openerpBounce();
- e.stopPropagation();
- }
- });
return this._super();
},
destroy: function() {
@@ -133,10 +134,10 @@ var FormView = View.extend(common.FieldManagerMixin, {
* Set this.$buttons with the produced jQuery element
* @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted
* $node may be undefined, in which case the FormView inserts them into this.options.$buttons
- * or into a div of its template
+ * if it exists
*/
render_buttons: function($node) {
- this.$buttons = $('<div/>', {'class': 'oe_form_buttons'});
+ this.$buttons = $('<div/>');
var $footer = this.$('footer');
if (this.options.action_buttons !== false || this.options.footer_to_buttons && $footer.children().length === 0) {
@@ -148,23 +149,17 @@ 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.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);
+ this.$buttons.on('click', '.o_form_button_create', this.on_button_create);
+ this.$buttons.on('click', '.o_form_button_edit', this.on_button_edit);
+ this.$buttons.on('click', '.o_form_button_save', this.on_button_save);
+ this.$buttons.on('click', '.o_form_button_cancel', this.on_button_cancel);
- if ($node) {
- this.$buttons.appendTo($node);
- } else {
- this.$('.oe_form_buttons').replaceWith(this.$buttons);
- }
+ this.$buttons.appendTo($node);
},
/**
* Instantiate and render the sidebar if a sidebar is requested
* Sets this.sidebar
* @param {jQuery} [$node] a jQuery node where the sidebar should be inserted
- * $node may be undefined, in which case the FormView inserts the sidebar in a
- * div of its template
**/
render_sidebar: function($node) {
if (!this.sidebar && this.options.sidebar) {
@@ -177,7 +172,6 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.is_action_enabled('create') && { label: _t('Duplicate'), callback: this.on_button_duplicate }
]));
- $node = $node || this.$('.oe_form_sidebar');
this.sidebar.appendTo($node);
// Show or hide the sidebar according to the view mode
@@ -185,41 +179,33 @@ var FormView = View.extend(common.FieldManagerMixin, {
}
},
/**
- * Render the pager according to the FormView.pager template and add listeners on it.
- * Set this.$pager with the produced jQuery element
- * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted
+ * Instantiate and render the pager and add listeners on it.
+ * Set this.pager
+ * @param {jQuery} [$node] a jQuery node where the pager should be inserted
* $node may be undefined, in which case the FormView inserts the pager into this.options.$pager
- * or into a div of its template
*/
render_pager: function($node) {
if (this.options.pager) {
var self = this;
- if (this.$pager) {
- this.$pager.remove();
- }
- if (this.get("actual_mode") === "create") {
- return;
- }
- this.$pager = $(QWeb.render("FormView.pager", {'widget': self}));
- this.$pager.on('click','a[data-pager-action]',function() {
- var $el = $(this);
- if ($el.attr("disabled")) {
- return;
- }
- var action = $el.data('pager-action');
- var def = $.when(self.execute_pager_action(action));
- $el.attr("disabled");
- def.always(function() {
- $el.removeAttr("disabled");
+ var options = {
+ validate: _.bind(this.can_be_discarded, this),
+ };
+
+ this.pager = new Pager(this, this.dataset.ids.length, this.dataset.index + 1, 1, options);
+ this.pager.on('pager_changed', this, function (new_state) {
+ this.pager.disable();
+ this.dataset.index = new_state.current_min - 1;
+ this.trigger('pager_action_executed');
+ $.when(this.reload()).then(function () {
+ self.pager.enable();
});
});
- this.do_update_pager();
- $node = $node || this.options.$pager;
- if ($node) {
- this.$pager.appendTo($node);
- } else {
- this.$('.oe_form_pager').replaceWith(this.$pager);
+ this.pager.appendTo($node = $node || this.options.$pager);
+
+ // Hide the pager in create mode
+ if (this.get("actual_mode") === "create") {
+ this.pager.do_hide();
}
}
},
@@ -241,6 +227,16 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.sidebar.do_toggle(this.get("actual_mode") === "view");
}
},
+ update_pager: function() {
+ if (this.pager) {
+ // Hide the pager in create mode
+ if (this.get("actual_mode") === "create") {
+ this.pager.do_hide();
+ } else {
+ this.pager.update_state({size: this.dataset.ids.length, current_min: this.dataset.index + 1});
+ }
+ }
+ },
widgetFocused: function() {
// Clear click flag if used to focus a widget
this.__clicked_inside = false;
@@ -281,11 +277,6 @@ var FormView = View.extend(common.FieldManagerMixin, {
do_show: function (options) {
var self = this;
options = options || {};
- this._super();
- this.$el.css({
- opacity: '0',
- filter: 'alpha(opacity = 0)'
- });
this.$el.removeClass('oe_form_dirty');
var shown = this.has_been_loaded;
@@ -307,10 +298,6 @@ var FormView = View.extend(common.FieldManagerMixin, {
}
return $.when(shown, this._super()).then(function() {
self._actualize_mode(options.mode || self.options.initial_mode);
- self.$el.css({
- opacity: '1',
- filter: 'alpha(opacity = 100)'
- });
core.bus.trigger('form_view_shown', self);
});
},
@@ -324,6 +311,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.datarecord = record;
this._actualize_mode();
this.set({ 'title' : record.id ? record.display_name : _t("New") });
+ this.update_pager();
this.record_loaded = $.Deferred();
_(this.fields).each(function (field, f) {
@@ -342,19 +330,17 @@ var FormView = View.extend(common.FieldManagerMixin, {
self.rendering_engine.init_fields().then(function() {
self.is_initialized.resolve();
self.record_loaded.resolve();
- self.do_update_pager(record.id === null || record.id === undefined);
if (self.sidebar) {
- self.sidebar.do_attachement_update(self.dataset, self.datarecord.id);
+ self.sidebar.do_attachement_update(self.dataset, self.datarecord.id);
}
if (record.id) {
self.do_push_state({id:record.id});
} else {
self.do_push_state({});
}
- self.$el.removeClass('oe_form_dirty');
- self.autofocus();
+ self.$el.removeClass('oe_form_dirty');
});
- });
+ });
},
/**
* Loads and sets up the default values for the model as the current
@@ -370,7 +356,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
self.trigger('load_record', _.clone(r));
});
}
- return self.trigger('load_record', {});
+ return $.when().then(this.trigger.bind(this, 'load_record', {}));
},
on_form_changed: function() {
this.trigger("view_content_has_changed");
@@ -378,40 +364,6 @@ var FormView = View.extend(common.FieldManagerMixin, {
do_notify_change: function() {
this.$el.addClass('oe_form_dirty');
},
- execute_pager_action: function(action) {
- var self = this;
- return this.can_be_discarded().then(function() {
- switch (action) {
- case 'first':
- self.dataset.index = 0;
- break;
- case 'previous':
- self.dataset.previous();
- break;
- case 'next':
- self.dataset.next();
- break;
- case 'last':
- self.dataset.index = self.dataset.ids.length - 1;
- break;
- }
- self.trigger('pager_action_executed');
- return self.reload();
- });
- },
- do_update_pager: function(hide_index) {
- if (this.$pager) {
- // Hide the pager in create mode or when there is only one record
- var pager_visible = (this.get("actual_mode") !== "create") && (this.dataset.ids.length > 1);
- this.$pager.toggle(pager_visible);
- if (hide_index === true) {
- this.$pager.find(".oe_form_pager_state").html("");
- } else {
- this.$pager.find(".oe_form_pager_state").html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length));
- }
- }
- },
-
_build_onchange_specs: function() {
var self = this;
var find = function(field_name, root) {
@@ -664,15 +616,15 @@ var FormView = View.extend(common.FieldManagerMixin, {
} else if (mode === "create") {
mode = "edit";
}
+
+ var viewMode = (mode === "view");
+ this.$el.toggleClass('o_form_readonly', viewMode).toggleClass('o_form_editable', !viewMode);
+
this.render_value_defs = [];
this.set({actual_mode: mode});
- },
- check_actual_mode: function(source, options) {
- if(this.get("actual_mode") === "view") {
- this.$el.removeClass('oe_form_editable').addClass('oe_form_readonly');
- } else {
- this.$el.removeClass('oe_form_readonly').addClass('oe_form_editable');
- _.defer(_.bind(this.autofocus, this));
+
+ if(!viewMode) {
+ this.autofocus();
}
},
autofocus: function() {
@@ -699,7 +651,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.$('.oe_form_buttons').add(this.$buttons).find('button.o_disabled').removeClass('o_disabled').prop('disabled', false);
this.is_disabled = false;
},
- on_button_save: function(e) {
+ on_button_save: function() {
var self = this;
if (this.is_disabled) {
return;
@@ -716,7 +668,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
self.enable_button();
});
},
- on_button_cancel: function(event) {
+ on_button_cancel: function() {
var self = this;
this.can_be_discarded().then(function() {
if (self.get('actual_mode') === 'create') {
@@ -733,10 +685,11 @@ var FormView = View.extend(common.FieldManagerMixin, {
},
on_button_new: function() {
var self = this;
- this.to_edit_mode();
return $.when(this.has_been_loaded).then(function() {
return self.can_be_discarded().then(function() {
- return self.load_defaults();
+ return self.load_defaults().then(function() {
+ self.to_edit_mode();
+ });
});
});
},
@@ -763,7 +716,8 @@ var FormView = View.extend(common.FieldManagerMixin, {
if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
self.dataset.unlink([self.datarecord.id]).done(function() {
if (self.dataset.size()) {
- self.execute_pager_action('next');
+ self.reload();
+ self.update_pager();
} else {
self.do_action('history_back');
}
@@ -790,7 +744,9 @@ var FormView = View.extend(common.FieldManagerMixin, {
title: _t("Warning"),
confirm_callback: function() {
self.$el.removeClass('oe_form_dirty');
- def.resolve();
+ this.on('closed', null, function() { // 'this' is the dialog widget
+ def.resolve();
+ });
},
cancel_callback: function() {
def.reject();
@@ -951,7 +907,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids));
this.dataset.index = 0;
}
- this.do_update_pager();
+ this.update_pager();
if (this.sidebar) {
this.sidebar.do_attachement_update(this.dataset, this.datarecord.id);
}
@@ -1090,7 +1046,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
$box.remove();
});
// add content inside notification box on top of the sheet/form
- var $target = this.$('.oe_form_sheet').length ? this.$('.oe_form_sheet') : this.$el;
+ var $target = this.$('.o_form_sheet_bg').length ? this.$('.o_form_sheet_bg') : this.$el;
$target.prepend($box);
},
open_defaults_dialog: function () {
@@ -1153,7 +1109,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
var $defaults = d.$el.find('#formview_default_fields');
var field_to_set = $defaults.val();
if (!field_to_set) {
- $defaults.parent().addClass('oe_form_invalid');
+ $defaults.parent().addClass('o_form_invalid');
return;
}
var condition = d.$el.find('#formview_default_conditions').val(),
@@ -1234,21 +1190,29 @@ var FormView = View.extend(common.FieldManagerMixin, {
},
});
-/**
- * Interface to be implemented by rendering engines for the form view.
- */
-var FormRenderingEngineInterface = Class.extend({
- set_fields_view: function(fields_view) {},
- set_fields_registry: function(fields_registry) {},
- render_to: function($el) {},
+core.view_registry.add('form', FormView);
+
+return FormView;
+
});
+
+odoo.define('web.FormRenderingEngine', function (require) {
+"use strict";
+
+var common = require('web.form_common');
+var core = require('web.core');
+var utils = require('web.utils');
+
+var _t = core._t;
+var QWeb = core.qweb;
+
/**
* Default rendering engine for the form view.
*
* It is necessary to set the view using set_view() before usage.
*/
-var FormRenderingEngine = FormRenderingEngineInterface.extend({
+return core.Class.extend({
init: function(view) {
this.view = view;
},
@@ -1289,7 +1253,7 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
$('board', doc).each(function() {
$(this).attr('layout', $(this).attr('style'));
});
- return $('<div class="oe_form"/>').append(utils.xml_to_str(doc));
+ return $(utils.xml_to_str(doc));
},
render_to: function($target) {
var self = this;
@@ -1305,7 +1269,8 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
this.labels = {};
this.process(this.$form);
- this.$form.appendTo(this.$target);
+ this.$form.contents().appendTo(this.$target);
+ this.handle_common_properties(this.$target, this.$form);
this.to_replace = [];
@@ -1382,9 +1347,6 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
args[0] = $tag;
return fn.apply(self, args);
} else {
- if( tagname === 'header') {
- $tag.addClass('o_statusbar_buttons');
- }
// generic tag handling, just process children
$tag.children().each(function() {
self.process($(this));
@@ -1408,14 +1370,14 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
process_sheet: function($sheet) {
var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes());
this.handle_common_properties($new_sheet, $sheet);
- var $dst = $new_sheet.find('.oe_form_sheet');
+ var $dst = $new_sheet.find('.o_form_sheet');
$sheet.contents().appendTo($dst);
$sheet.before($new_sheet).remove();
this.process($new_sheet);
},
process_form: function($form) {
if ($form.find('> sheet').length === 0) {
- $form.addClass('oe_form_nosheet');
+ $form.addClass('o_form_nosheet');
}
var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
this.handle_common_properties($new_form, $form);
@@ -1479,119 +1441,99 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
},
process_group: function($group) {
var self = this;
- $group.children('field').each(function() {
- self.preprocess_field($(this));
- });
- var $new_group = this.render_element('FormRenderingGroup', $group.getAttributes());
- var $table;
- if ($new_group.first().is('table.oe_form_group')) {
- $table = $new_group;
- } else if ($new_group.filter('table.oe_form_group').length) {
- $table = $new_group.filter('table.oe_form_group').first();
- } else {
- $table = $new_group.find('table.oe_form_group').first();
- }
- var $tr, $td,
- cols = parseInt($group.attr('col') || 2, 10),
- row_cols = cols;
+ // Processes a group containing nested groups
+ // Renders a div for each nested group, into a div that is the outer group
+ // The nested groups's width is a percentage according to col and colspan attributes
+ var _process_outer_group = function(col) {
+ var $new_group = self.render_element('FormRenderingOuterGroup', $group.getAttributes());
+ $group.children().each(function() {
+ var $child = $(this);
+ var tagName = $child[0].tagName.toLowerCase();
+ var colspan = parseInt($child.attr('colspan') || 1, 10);
- var children = [];
- $group.children().each(function() {
- var $child = $(this);
- var colspan = parseInt($child.attr('colspan') || 1, 10);
- var tagName = $child[0].tagName.toLowerCase();
- var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
- var newline = tagName === 'newline';
-
- // Note FME: those classes are used in layout debug mode
- if ($tr && row_cols > 0 && (newline || row_cols < colspan)) {
- $tr.addClass('oe_form_group_row_incomplete');
- if (newline) {
- $tr.addClass('oe_form_group_row_newline');
+ if (tagName === 'newline') {
+ $new_group.append($('<br>')); // Skip to the next line
+ return;
}
- }
- if (newline) {
- $tr = null;
- return;
- }
- if (!$tr || row_cols < colspan) {
- $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table);
- row_cols = cols;
- } else if (tagName==='group') {
- // When <group> <group/><group/> </group>, we need a spacing between the two groups
- $td.addClass('oe_group_right');
- }
- row_cols -= colspan;
+ // compute child's classname from col and colspan attributes
+ $child.addClass('o_group_col_' + Math.min(Math.floor(colspan/col*12), 12));
+ $new_group.append($child);
- // invisibility transfer
- var field_modifiers = JSON.parse($child.attr('modifiers') || '{}');
- var invisible = field_modifiers.invisible;
- self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible})));
+ children.push($child[0]);
+ });
+ return $new_group;
+ };
+ // Processes a group containing no nested group
+ // Renders an HTML table
+ var _process_inner_group = function(col) {
+ var $new_group = self.render_element('FormRenderingInnerGroup', $group.getAttributes());
+ $new_group.find('td').attr('colspan', col); // Group's title spans all column
+ var row_col = col;
+ var $row = null;
- $tr.append($td.append($child));
- children.push($child[0]);
- });
- if (row_cols && $td) {
- $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
- }
- $group.before($new_group).remove();
+ $group.children().each(function() {
+ var $child = $(this);
+ var tagName = $child[0].tagName.toLowerCase();
+ var colspan = parseInt($child.attr('colspan') || 1, 10);
- $table.find('> tbody > tr').each(function() {
- var to_compute = [],
- row_cols = cols,
- total = 100;
- $(this).children().each(function() {
- var $td = $(this),
- $child = $td.children(':first');
- if ($child.attr('cell-class')) {
- $td.addClass($child.attr('cell-class'));
+ if (tagName === 'newline') {
+ $row = null; // Start with a new row
+ return;
}
- switch ($child[0].tagName.toLowerCase()) {
- case 'separator':
- break;
- case 'label':
- if ($child.attr('for')) {
- $td.attr('width', '1%').addClass('oe_form_group_cell_label');
- row_cols-= $td.attr('colspan') || 1;
- total--;
- }
- break;
- default:
- var width = _.str.trim($child.attr('width') || ''),
- iwidth = parseInt(width, 10);
- if (iwidth) {
- if (width.substr(-1) === '%') {
- total -= iwidth;
- width = iwidth + '%';
- } else {
- // Absolute width
- $td.css('min-width', width + 'px');
- }
- $td.attr('width', width);
- $child.removeAttr('width');
- row_cols-= $td.attr('colspan') || 1;
- } else {
- to_compute.push($td);
- }
-
+ row_col -= colspan;
+ if (!$row || row_col < 0) { // Append a new row to the group
+ $row = $('<tr>').appendTo($new_group);
+ row_col = col-colspan;
}
+ $('<td>').attr('colspan', colspan).append($child).appendTo($row);
+
+ children.push($child[0]);
});
- if (row_cols) {
- var unit = Math.floor(total / row_cols);
- if (!$(this).is('.oe_form_group_row_incomplete')) {
- _.each(to_compute, function($td) {
- var width = parseInt($td.attr('colspan'), 10) * unit;
- $td.attr('width', width + '%');
- total -= width;
- });
- }
- }
+
+ // Some kind of an hack
+ // Add width on table's cell to avoid automatic layout of table which
+ // makes first col to take full width available (typically, first col contains label)
+ $new_group.find('tr').each(function() {
+ var $tr = $(this);
+ var nonlabel_width = 100/(col - $tr.find('td > label:first-child').length);
+ $tr.find('td').each(function() {
+ var $td = $(this);
+ var $child = $td.children(':first');
+ if ($child.is("label")) {
+ $td.addClass("o_td_label");
+ } else {
+ $td.css("width", (nonlabel_width*parseInt($td.attr('colspan')))+"%");
+ }
+ });
+
+ });
+ return $new_group;
+ };
+
+ // Preprocess field children
+ $group.children('field').each(function() {
+ self.preprocess_field($(this));
});
+
+ // Process group
+ var nested_groups = $group.children('group').length; // Detect possible nested groups
+ var children = [];
+ var col = parseInt($group.attr('col') || 2, 10);
+ var $new_group;
+ if (nested_groups) {
+ $new_group = _process_outer_group(col);
+ } else {
+ $new_group = _process_inner_group(col);
+ }
+ this.handle_common_properties($new_group, $group);
+
+ // Process group's children
+ $group.before($new_group).remove();
_.each(children, function(el) {
self.process($(el));
});
- this.handle_common_properties($new_group, $group);
+
return $new_group;
},
process_notebook: function($notebook) {
@@ -1658,17 +1600,8 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
var dict = {
string: $label.attr('string') || (field_orm || {}).string || '',
help: $label.attr('help') || (field_orm || {}).help || '',
- _for: name ? _.uniqueId('oe-field-input-') : undefined,
+ _for: name ? _.uniqueId('o_field_input_') : undefined,
};
- var align = parseFloat(dict.align);
- if (isNaN(align) || align === 1) {
- align = 'right';
- } else if (align === 0) {
- align = 'left';
- } else {
- align = 'center';
- }
- dict.align = align;
var $new_label = this.render_element('FormRenderingLabel', dict);
$label.before($new_label).remove();
this.handle_common_properties($new_label, $label);
@@ -1698,8 +1631,4 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({
}
});
-core.view_registry.add('form', FormView);
-
-return FormView;
-
});
diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js
index 2a2a36cdaba..2aeaaa17111 100644
--- a/addons/web/static/src/js/views/form_widgets.js
+++ b/addons/web/static/src/js/views/form_widgets.js
@@ -5,17 +5,17 @@ var core = require('web.core');
var crash_manager = require('web.crash_manager');
var data = require('web.data');
var datepicker = require('web.datepicker');
+var dom_utils = require('web.dom_utils');
+var Priority = require('web.Priority');
var ProgressBar = require('web.ProgressBar');
var Dialog = require('web.Dialog');
var common = require('web.form_common');
var formats = require('web.formats');
var framework = require('web.framework');
var Model = require('web.DataModel');
-var Priority = require('web.Priority');
var pyeval = require('web.pyeval');
var session = require('web.session');
var utils = require('web.utils');
-var dom_utils = require('web.dom_utils');
var _t = core._t;
var QWeb = core.qweb;
@@ -119,24 +119,25 @@ var WidgetButton = common.FormWidget.extend({
var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, {
template: 'FieldChar',
- widget_class: 'oe_form_field_char',
events: {
- 'change input': 'store_dom_value',
+ 'change': 'store_dom_value',
},
init: function (field_manager, node) {
this._super(field_manager, node);
this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
},
initialize_content: function() {
- this.setupFocus(this.$('input'));
+ if(!this.get('effective_readonly') && !this.$input) {
+ this.$input = this.$el;
+ }
+ this.setupFocus(this.$el);
+ },
+ destroy_content: function() {
+ this.$input = undefined;
},
store_dom_value: function () {
- if (!this.get('effective_readonly')
- && this.$('input').length
- && this.is_syntax_valid()) {
- this.internal_set_value(
- this.parse_value(
- this.$('input').val()));
+ if (this.$input && this.is_syntax_valid()) {
+ this.internal_set_value(this.parse_value(this.$input.val()));
}
},
commit_value: function () {
@@ -145,20 +146,19 @@ var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, {
},
render_value: function() {
var show_value = this.format_value(this.get('value'), '');
- if (!this.get("effective_readonly")) {
- this.$el.find('input').val(show_value);
+ if (this.$input) {
+ this.$input.val(show_value);
} else {
if (this.password) {
show_value = new Array(show_value.length + 1).join('*');
}
- this.$(".oe_form_char_content").text(show_value);
+ this.$el.text(show_value);
}
},
is_syntax_valid: function() {
- if (!this.get("effective_readonly") && this.$("input").size() > 0) {
+ if (this.$input) {
try {
- this.parse_value(this.$('input').val(), '');
- return true;
+ this.parse_value(this.$input.val(), '');
} catch(e) {
return false;
}
@@ -175,31 +175,30 @@ var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, {
return this.get('value') === '' || this._super();
},
focus: function() {
- var input = this.$('input:first')[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- this.$('input').css({
- height: height,
- width: width
- });
- }
+ if (this.$input) {
+ return this.$input.focus();
+ }
+ return false;
+ },
});
-var KanbanSelection = FieldChar.extend({
+var KanbanSelection = common.AbstractField.extend({
template: "FormSelection",
- init: function (field_manager, node) {
- this._super(field_manager, node);
+ events: {
+ 'click a': function(e) {
+ e.preventDefault();
+ },
+ 'mouseup a': function(e) {
+ e.stopPropagation();
+ },
+ 'click li': 'set_kanban_selection'
},
start: function () {
- var self = this;
- this.states = [];
- this._super.apply(this, arguments);
// hook on form view content changed: recompute the states, because it may be related to the current stage
- this.getParent().on('view_content_has_changed', self, function () {
- self.render_value();
+ this.view.on('view_content_has_changed', this, function () {
+ this.render_value();
});
+ return this._super();
},
prepare_dropdown_selection: function() {
var self = this;
@@ -233,6 +232,7 @@ var KanbanSelection = FieldChar.extend({
this._super();
this.states = this.prepare_dropdown_selection();
var self = this;
+
// Adapt "FormSelection"
var current_state = _.find(this.states, function(state) {
return state.name === self.get('value');
@@ -248,7 +248,6 @@ var KanbanSelection = FieldChar.extend({
var $dropdown = this.$el.find('.dropdown-menu');
$dropdown.children().remove(); // remove old items
$items.appendTo($dropdown);
- this.$el.find('a').on('click', this.set_kanban_selection.bind(this));
},
/* setting the value: in view mode, perform an asynchronous call and reload
the form view; in edit mode, use set_value to save the new value that will
@@ -259,7 +258,7 @@ var KanbanSelection = FieldChar.extend({
var li = $(ev.target).closest('li');
if (li.length) {
var value = String(li.data('value'));
- if (this.view.get('actual_mode') == 'view') {
+ if (this.view.get('actual_mode') === 'view') {
var write_values = {};
write_values[self.name] = value;
return this.view.dataset._model.call(
@@ -339,66 +338,35 @@ var FieldID = FieldChar.extend({
var FieldEmail = FieldChar.extend({
template: 'FieldEmail',
- initialize_content: function() {
- this._super();
- var $button = this.$el.find('button');
- $button.click(this.on_button_clicked);
- this.setupFocus($button);
+ prefix: 'mailto',
+ init: function() {
+ this._super.apply(this, arguments);
+ this.clickable = true;
},
render_value: function() {
- if (!this.get("effective_readonly")) {
- this._super();
- } else {
- this.$el.find('a')
- .attr('href', 'mailto:' + this.get('value'))
- .text(this.get('value') || '');
- }
- },
- on_button_clicked: function() {
- if (!this.get('value') || !this.is_syntax_valid()) {
- this.do_warn(_t("E-mail Error"), _t("Can't send email to invalid e-mail address"));
- } else {
- location.href = 'mailto:' + this.get('value');
+ this._super();
+ if (this.get("effective_readonly") && this.clickable) {
+ this.$el.attr('href', this.prefix + ':' + this.get('value'));
}
}
});
-var FieldUrl = FieldChar.extend({
- template: 'FieldUrl',
- initialize_content: function() {
- this._super();
- var $button = this.$el.find('button');
- $button.click(this.on_button_clicked);
- this.setupFocus($button);
- },
+var FieldUrl = FieldEmail.extend({
render_value: function() {
- if (!this.get("effective_readonly")) {
- this._super();
- } else {
+ this._super();
+ if(this.get("effective_readonly")) {
var tmp = this.get('value');
var s = /(\w+):(.+)|^\.{0,2}\//.exec(tmp);
if (!s) {
tmp = "http://" + this.get('value');
}
var text = this.get('value') ? this.node.attrs.text || tmp : '';
- this.$el.find('a').attr('href', tmp).text(text);
- }
- },
- on_button_clicked: function() {
- if (!this.get('value')) {
- this.do_warn(_t("Resource Error"), _t("This resource is empty"));
- } else {
- var url = $.trim(this.get('value'));
- if(/^www\./i.test(url))
- url = 'http://'+url;
- window.open(url);
+ this.$el.attr('href', tmp).text(text);
}
}
});
var FieldFloat = FieldChar.extend({
- is_field_number: true,
- widget_class: 'oe_form_field_float',
init: function (field_manager, node) {
this._super(field_manager, node);
this.internal_set_value(0);
@@ -408,19 +376,18 @@ var FieldFloat = FieldChar.extend({
this.digits = this.field.digits;
}
},
+ initialize_content: function() {
+ this._super();
+ this.$el.addClass('o_form_field_number');
+ },
set_value: function(value_) {
if (value_ === false || value_ === undefined) {
- // As in GTK client, floats default to 0
- value_ = 0;
+ value_ = 0; // As in GTK client, floats default to 0
}
if (this.digits !== undefined && this.digits.length === 2) {
value_ = utils.round_decimals(value_, this.digits[1]);
}
- this._super.apply(this, [value_]);
- },
- focus: function () {
- var $input = this.$('input:first');
- return $input.length ? $input.select() : false;
+ this._super(value_);
}
});
@@ -474,7 +441,7 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin,
} else {
this.$('.o_count').text(_t('No selected record'));
var $arrow = this.$('button span').detach();
- this.$('button').text(_('Select records ')).append($("<span/>").addClass('fa fa-arrow-right'));
+ this.$('button').text(_t('Select records ')).append($("<span/>").addClass('fa fa-arrow-right'));
}
},
on_click: function(event) {
@@ -497,60 +464,58 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin,
},
});
-var FieldDatetime = common.AbstractField.extend(common.ReinitializeFieldMixin, {
- template: "FieldDatetime",
+var FieldDate = common.AbstractField.extend(common.ReinitializeFieldMixin, {
+ tagName: "span",
+ className: "o_form_field_date",
build_widget: function() {
- return new datepicker.DateTimeWidget(this);
+ return new datepicker.DateWidget(this);
},
- destroy_content: function() {
+ initialize_content: function() {
if (this.datewidget) {
this.datewidget.destroy();
this.datewidget = undefined;
}
- },
- initialize_content: function() {
+
if (!this.get("effective_readonly")) {
this.datewidget = this.build_widget();
- this.datewidget.on('datetime_changed', this, _.bind(function() {
+ this.datewidget.on('datetime_changed', this, function() {
this.internal_set_value(this.datewidget.get_value());
- }, this));
- this.datewidget.appendTo(this.$el);
- this.setupFocus(this.datewidget.$input);
+ });
+
+ var self = this;
+ this.datewidget.appendTo('<div>').done(function() {
+ self.datewidget.$el.addClass(self.$el.attr('class'));
+ self.replaceElement(self.datewidget.$el);
+ self.datewidget.$input.addClass('o_form_input');
+ self.setupFocus(self.datewidget.$input);
+ });
}
},
render_value: function() {
- if (!this.get("effective_readonly")) {
- this.datewidget.set_value(this.get('value'));
- } else {
+ if (this.get("effective_readonly")) {
this.$el.text(formats.format_value(this.get('value'), this, ''));
+ } else {
+ this.datewidget.set_value(this.get('value'));
}
},
is_syntax_valid: function() {
- if (!this.get("effective_readonly") && this.datewidget) {
- return this.datewidget.is_valid();
- }
- return true;
+ return this.get("effective_readonly") || !this.datewidget || this.datewidget.is_valid();
},
is_false: function() {
return this.get('value') === '' || this._super();
},
focus: function() {
- var input = this.datewidget && this.datewidget.$input[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
if (!this.get("effective_readonly")) {
- this.datewidget.$input.css('height', height);
+ return this.datewidget.$input.focus();
}
- }
+ return false;
+ },
});
-var FieldDate = FieldDatetime.extend({
- template: "FieldDate",
+var FieldDatetime = FieldDate.extend({
build_widget: function() {
- return new datepicker.DateWidget(this);
- }
+ return new datepicker.DateTimeWidget(this);
+ },
});
var FieldText = common.AbstractField.extend(common.ReinitializeFieldMixin, {
@@ -566,46 +531,37 @@ var FieldText = common.AbstractField.extend(common.ReinitializeFieldMixin, {
e.stopPropagation();
}
},
- 'change textarea': 'store_dom_value',
+ 'change': 'store_dom_value',
},
initialize_content: function() {
- if (! this.get("effective_readonly")) {
- this.$textarea = this.$el.find('textarea');
+ if (!this.get("effective_readonly")) {
this.auto_sized = false;
- this.default_height = this.$textarea.css('height');
- if (this.default_height === '0px') this.default_height = '90px';
- if (this.get("effective_readonly")) {
- this.$textarea.attr('disabled', 'disabled');
- }
- this.setupFocus(this.$textarea);
- } else {
- this.$textarea = undefined;
+ this.setupFocus(this.$el);
}
},
commit_value: function () {
- if (! this.get("effective_readonly") && this.$textarea) {
+ if (!this.get("effective_readonly")) {
this.store_dom_value();
}
return this._super();
},
store_dom_value: function () {
- this.internal_set_value(formats.parse_value(this.$textarea.val(), this));
+ this.internal_set_value(formats.parse_value(this.$el.val(), this));
},
render_value: function() {
- if (! this.get("effective_readonly")) {
- var show_value = formats.format_value(this.get('value'), this, '');
- this.$textarea.val(show_value);
- dom_utils.autoresize(this.$textarea, {parent: this, min_height: parseInt(this.default_height)});
- } else {
+ if (this.get("effective_readonly")) {
var txt = this.get("value") || '';
- this.$(".oe_form_text_content").text(txt);
+ this.$el.text(txt);
+ } else {
+ var show_value = formats.format_value(this.get('value'), this, '');
+ this.$el.val(show_value);
+ dom_utils.autoresize(this.$el, {parent: this});
}
},
is_syntax_valid: function() {
- if (!this.get("effective_readonly") && this.$textarea) {
+ if (!this.get("effective_readonly")) {
try {
- formats.parse_value(this.$textarea.val(), this, '');
- return true;
+ formats.parse_value(this.$el.val(), this, '');
} catch(e) {
return false;
}
@@ -616,51 +572,48 @@ var FieldText = common.AbstractField.extend(common.ReinitializeFieldMixin, {
return this.get('value') === '' || this._super();
},
focus: function($el) {
- var input = !this.get("effective_readonly") && this.$textarea && this.$textarea[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- if (!this.get("effective_readonly") && this.$textarea) {
- this.$textarea.css({
- width: width,
- minHeight: height
- });
+ if(!this.get("effective_readonly")) {
+ return this.$el.focus();
}
+ return false;
+ },
+ set_dimensions: function(height, width) {
+ this.$el.css({
+ width: width,
+ minHeight: height,
+ });
},
});
var FieldBoolean = common.AbstractField.extend({
template: 'FieldBoolean',
+ events: {
+ 'click': function() {
+ this.internal_set_value(this.$checkbox.prop('checked'));
+ }
+ },
start: function() {
- var self = this;
- this.$checkbox = $("input", this.$el);
+ this.$checkbox = this.$('input');
+
+ this.$checkbox.prop('disabled', this.get("effective_readonly"));
+ this.on("change:effective_readonly", this, function() {
+ this.$checkbox.prop('disabled', this.get("effective_readonly"));
+ });
+
this.setupFocus(this.$checkbox);
- this.$el.click(_.bind(function() {
- this.internal_set_value(this.$checkbox.is(':checked'));
- }, this));
- var check_readonly = function() {
- self.$checkbox.prop('disabled', self.get("effective_readonly"));
- self.click_disabled_boolean();
- };
- this.on("change:effective_readonly", this, check_readonly);
- check_readonly.call(this);
- this._super.apply(this, arguments);
+
+ return this._super();
},
render_value: function() {
- this.$checkbox[0].checked = this.get('value');
+ this.$checkbox.prop('checked', this.get('value'));
},
focus: function() {
- var input = this.$checkbox && this.$checkbox[0];
- return input ? input.focus() : false;
- },
- click_disabled_boolean: function(){
- var $disabled = this.$el.find('input[type=checkbox]:disabled');
- $disabled.each(function (){
- $(this).next('div').remove();
- $(this).closest("span").append($('<div class="boolean"></div>'));
- });
- }
+ return this.$checkbox.focus();
+ },
+ set_dimensions: function(height, width) {}, // Checkboxes have a fixed height and width (even in list editable)
+ is_false: function() {
+ return false;
+ },
});
/**
@@ -706,9 +659,7 @@ var FieldBooleanButton = common.AbstractField.extend({
},
});
-/**
- The progressbar field expect a float from 0 to 100.
-*/
+// The progressbar field expects a float from 0 to 100.
var FieldProgressBar = common.AbstractField.extend(common.ReinitializeFieldMixin, {
initialize_content: function() {
if(this.progressbar) {
@@ -734,96 +685,41 @@ var FieldProgressBar = common.AbstractField.extend(common.ReinitializeFieldMixin
render_value: function() {
this.progressbar.set_value(this.get('value'));
},
+ is_false: function() {
+ return false;
+ },
});
-/**
- The PercentPie field expect a float from 0 to 100.
-*/
+// The PercentPie field expects a float from 0 to 100.
var FieldPercentPie = common.AbstractField.extend({
template: 'FieldPercentPie',
-
+ start: function() {
+ this.$left_mask = this.$('.o_mask').first();
+ this.$right_mask = this.$('.o_mask').last();
+ this.$pie_value = this.$('.o_pie_value');
+ return this._super();
+ },
render_value: function() {
- var value = this.get('value'),
- formatted_value = Math.round(value || 0) + '%',
- svg = this.$('svg')[0];
-
- svg.innerHTML = "";
- nv.addGraph(function() {
- var width = 42, height = 42;
- var chart = nv.models.pieChart()
- .width(width)
- .height(height)
- .margin({top: 0, right: 0, bottom: 0, left: 0})
- .donut(true)
- .showLegend(false)
- .showLabels(false)
- .color(['#7C7BAD','#DDD'])
- .donutRatio(0.62);
-
- chart.tooltip.enabled(false);
-
- d3.select(svg)
- .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
- .transition()
- .call(chart)
- .attr('style', 'width: ' + width + 'px; height:' + height + 'px;');
-
- d3.select(svg)
- .append("text")
- .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'})
- .style({"font-size": "10px", "font-weight": "bold"})
- .text(formatted_value);
-
- return chart;
- });
-
- }
-});
+ var value = this.get('value') || 0, degValue = 360*value/100;
-/**
- The FieldBarChart expectsa list of values (indeed)
-*/
-var FieldBarChart = common.AbstractField.extend({
- template: 'FieldBarChart',
+ this.$right_mask.toggleClass('o_full', degValue >= 180);
- render_value: function() {
- var value = JSON.parse(this.get('value'));
- var svg = this.$('svg')[0];
- svg.innerHTML = "";
- nv.addGraph(function() {
- var width = 34, height = 34;
- var chart = nv.models.discreteBarChart()
- .x(function (d) { return d.tooltip; })
- .y(function (d) { return d.value; })
- .width(width)
- .height(height)
- .margin({top: 0, right: 0, bottom: 0, left: 0})
- .showValues(false)
- .transition(350)
- .showXAxis(false)
- .showYAxis(false);
-
- chart.tooltip.enabled(false);
-
- d3.select(svg)
- .datum([{key: 'values', values: value}])
- .transition()
- .call(chart)
- .attr('style', 'width: ' + (width + 4) + 'px; height: ' + (height + 8) + 'px;');
-
- nv.utils.windowResize(chart.update);
-
- return chart;
- });
-
+ var leftDeg = 'rotate(' + ((degValue < 180)? 180 : degValue) + 'deg)';
+ var rightDeg = 'rotate(' + ((degValue < 180)? degValue : 0) + 'deg)';
+ this.$left_mask.css({transform: leftDeg, msTransform: leftDeg, mozTransform: leftDeg, webkitTransform: leftDeg});
+ this.$right_mask.css({transform: rightDeg, msTransform: rightDeg, mozTransform: rightDeg, webkitTransform: rightDeg});
+
+ this.$pie_value.html(Math.round(value) + '%');
+ },
+ is_false: function() {
+ return false;
}
});
-
var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin, {
template: 'FieldSelection',
events: {
- 'change select': 'store_dom_value',
+ 'change': 'store_dom_value',
},
init: function(field_manager, node) {
this._super(field_manager, node);
@@ -832,7 +728,7 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin,
this.records_orderer = new utils.DropMisordered();
this.field_manager.on("view_content_has_changed", this, function() {
var domain = new data.CompoundDomain(this.build_domain()).eval();
- if (! _.isEqual(domain, this.get("domain"))) {
+ if (!_.isEqual(domain, this.get("domain"))) {
this.set("domain", domain);
}
});
@@ -869,27 +765,27 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin,
// current row or switching to the next one)
// * If the user presses [RETURN] with a select closed (side-effect:
// also if the user opened the select and pressed [RETURN] without
- // changing the selected value), takes the action as validating the
- // row
- var ischanging = false;
- var $select = this.$el.find('select')
- .change(function () { ischanging = true; })
- .click(function () { ischanging = false; })
- .keyup(function (e) {
- if (e.which !== 13 || !ischanging) { return; }
- e.stopPropagation();
- ischanging = false;
- });
- this.setupFocus($select);
+ // changing the selected value), takes the action as validating the row
+ if(!this.get('effective_readonly')) {
+ var ischanging = false;
+ this.$el
+ .change(function () { ischanging = true; })
+ .click(function () { ischanging = false; })
+ .keyup(function (e) {
+ if (e.which !== 13 || !ischanging) { return; }
+ e.stopPropagation();
+ ischanging = false;
+ });
+ this.setupFocus(this.$el);
+ }
},
commit_value: function () {
this.store_dom_value();
return this._super();
},
store_dom_value: function () {
- if (!this.get('effective_readonly') && this.$('select').length) {
- var val = JSON.parse(this.$('select').val());
- this.internal_set_value(val);
+ if (!this.get('effective_readonly')) {
+ this.internal_set_value(JSON.parse(this.$el.val()));
}
},
set_value: function(value_) {
@@ -901,28 +797,29 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin,
var values = this.get("values");
values = [[false, this.node.attrs.placeholder || '']].concat(values);
var found = _.find(values, function(el) { return el[0] === this.get("value"); }, this);
- if (! found) {
+ if (!found) {
found = [this.get("value"), _t('Unknown')];
values = [found].concat(values);
}
- if (! this.get("effective_readonly")) {
- this.$().html(QWeb.render("FieldSelectionSelect", {widget: this, values: values}));
- this.$("select").val(JSON.stringify(found[0]));
+ if (!this.get("effective_readonly")) {
+ this.$el.empty();
+ for(var i = 0 ; i < values.length ; i++) {
+ this.$el.append($('<option/>', {
+ value: JSON.stringify(values[i][0]),
+ html: values[i][1]
+ }))
+ }
+ this.$el.val(JSON.stringify(found[0]));
} else {
this.$el.text(found[1]);
}
},
focus: function() {
- var input = this.$('select:first')[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- this.$('select').css({
- height: height,
- width: width
- });
- }
+ if (!this.get("effective_readonly")) {
+ return this.$el.focus();
+ }
+ return false;
+ },
});
/**
@@ -932,20 +829,20 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin,
the time difference between UTC time and local time, in minutes.
*/
var TimezoneMismatch = FieldSelection.extend({
- initialize_content: function(){
+ initialize_content: function() {
this._super.apply(this, arguments);
this.tz_offset_field = (this.options && this.options.tz_offset_field) || this.tz_offset_field || 'tz_offset';
this.set({"tz_offset": this.field_manager.get_field_value(this.tz_offset_field)});
this.on("change:tz_offset", this, this.render_value);
},
- start: function(){
+ start: function() {
this._super.apply(this, arguments);
// trigger a render_value when tz_offset field change
this.field_manager.on("field_changed:" + this.tz_offset_field, this, function() {
this.set({"tz_offset": this.field_manager.get_field_value(this.tz_offset_field)});
});
},
- check_timezone: function(){
+ check_timezone: function() {
var user_offset = this.get('tz_offset');
if (user_offset) {
var offset = -(new Date().getTimezoneOffset());
@@ -956,17 +853,15 @@ var TimezoneMismatch = FieldSelection.extend({
}
return false;
},
- render_value: function(){
+ render_value: function() {
this._super.apply(this, arguments);
+ this.$label.next('.o_tz_warning').remove();
if(this.check_timezone()){
- this.$label.find('.oe_tz_warning').remove();
var options = _.extend({
delay: { show: 501, hide: 0 },
title: _t("Timezone Mismatch : The timezone of your browser doesn't match the selected one. The time in Odoo is displayed according to your field timezone."),
});
- this.$label.css('white-space', 'normal');
- $(QWeb.render('WebClient.timezone_warning')).appendTo(this.$label);
- this.$label.find('.oe_tz_warning').tooltip(options);
+ $('<span/>').addClass('fa fa-exclamation-triangle o_tz_warning').insertAfter(this.$label).tooltip(options);
}
}
});
@@ -979,8 +874,8 @@ var LabelSelection = FieldSelection.extend({
render_value: function() {
this._super.apply(this, arguments);
if (this.get("effective_readonly")) {
- var btn_class = this.classes[this.get('value')] || 'default';
- this.$el.wrapInner($('<span/>').addClass('label label-' + btn_class));
+ var bt_class = this.classes[this.get('value')] || 'default';
+ this.$el.html($('<span/>', {html: this.$el.html()}).addClass('label label-' + bt_class));
}
},
});
@@ -1001,7 +896,6 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, {
this.uniqueId = _.uniqueId("radio");
},
initialize_content: function () {
- this.on("change:effective_readonly", this, this.render_value);
this.field_manager.on("view_content_has_changed", this, this.get_selection);
this.get_selection();
},
@@ -1043,7 +937,7 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, {
def.resolve();
}
return def.then(function () {
- if (! _.isEqual(selection, self.selection)) {
+ if (!_.isEqual(selection, self.selection)) {
self.selection = _.clone(selection);
self.renderElement();
self.render_value();
@@ -1066,14 +960,16 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, {
},
render_value: function () {
var self = this;
- this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
- this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
- this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
+ if(this.get('effective_readonly')) {
+ this.$el.html(this.get('value')? this.get('value')[1] : "");
+ } else {
+ this.$("input").filter(function() {return this.value == self.get_value();}).prop("checked", true);
+ }
}
});
var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin, {
- template: 'FieldReference',
+ className: 'o_row',
init: function(field_manager, node) {
this._super(field_manager, node);
this.reference_ready = true;
@@ -1086,9 +982,8 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin,
},
initialize_content: function() {
var self = this;
- var fm = new common.DefaultFieldManager(this);
- this.fm = fm;
- fm.extend_field_desc({
+ this.fm = new common.DefaultFieldManager(this);
+ this.fm.extend_field_desc({
"selection": {
selection: this.field_manager.get_field_desc(this.name).selection,
type: "selection",
@@ -1098,23 +993,23 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin,
type: "many2one",
},
});
- this.selection = new FieldSelection(fm, { attrs: {
+ this.selection = new FieldSelection(this.fm, { attrs: {
name: 'selection',
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
}});
this.selection.on("change:value", this, this.on_selection_changed);
- this.selection.appendTo(this.$(".oe_form_view_reference_selection"));
+ this.selection.appendTo(this.$el);
this.selection
.on('focused', null, function () {self.trigger('focused');})
.on('blurred', null, function () {self.trigger('blurred');});
var FieldMany2One = core.form_widget_registry.get('many2one');
- this.m2o = new FieldMany2One(fm, { attrs: {
+ this.m2o = new FieldMany2One(this.fm, { attrs: {
name: 'Referenced Document',
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
}});
this.m2o.on("change:value", this, this.data_changed);
- this.m2o.appendTo(this.$(".oe_form_view_reference_m2o"));
+ this.m2o.appendTo(this.$el);
this.m2o
.on('focused', null, function () {self.trigger('focused');})
.on('blurred', null, function () {self.trigger('blurred');});
@@ -1151,6 +1046,9 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin,
this.m2o.do_toggle(!!this.get('value')[0]);
this.reference_ready = true;
},
+ is_false: function() {
+ return !this.get_value();
+ },
});
var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
@@ -1161,7 +1059,7 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
this.useFileAPI = !!window.FileReader;
this.max_upload_size = 25 * 1024 * 1024; // 25Mo
if (!this.useFileAPI) {
- this.fileupload_id = _.uniqueId('oe_fileupload');
+ this.fileupload_id = _.uniqueId('o_fileupload');
$(window).on(this.fileupload_id, function() {
var args = [].slice.call(arguments).slice(1);
self.on_file_uploaded.apply(self, args);
@@ -1175,13 +1073,14 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
this._super.apply(this, arguments);
},
initialize_content: function() {
- var self= this;
- this.$('input.o_form_input_file').change(this.on_file_change);
- this.$('button.oe_form_binary_file_save').click(this.on_save_as);
- this.$('.oe_form_binary_file_clear').click(this.on_clear);
- this.$('.oe_form_binary_file_edit').click(function() {
- self.$('input.o_form_input_file').click();
+ this.$inputFile = this.$('.o_form_input_file');
+ this.$inputFile.change(this.on_file_change);
+ var self = this;
+ this.$('.o_select_file_button').click(function() {
+ self.$inputFile.click();
});
+ this.$('.o_save_file_button').click(this.on_save_as);
+ this.$('.o_clear_file_button').click(this.on_clear);
},
on_file_change: function(e) {
var self = this;
@@ -1202,26 +1101,28 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
self.on_file_uploaded(file.size, file.name, file.type, data);
};
} else {
- this.$el.find('form.o_form_binary_form input[name=session_id]').val(this.session.session_id);
- this.$el.find('form.o_form_binary_form').submit();
+ this.$('form.o_form_binary_form input[name=session_id]').val(this.session.session_id);
+ this.$('form.o_form_binary_form').submit();
}
- this.$el.find('.oe_form_binary_progress').show();
- this.$el.find('.oe_form_binary').hide();
+ this.$('.o_form_binary_progress').show();
+ this.$('button').hide();
}
},
- on_file_uploaded: function(size, name, content_type, file_base64) {
+ on_file_uploaded: function(size, name) {
if (size === false) {
this.do_warn(_t("File Upload"), _t("There was a problem while uploading your file"));
// TODO: use openerp web crashmanager
console.warn("Error while uploading file : ", name);
} else {
- this.filename = name;
this.on_file_uploaded_and_valid.apply(this, arguments);
}
- this.$el.find('.oe_form_binary_progress').hide();
- this.$el.find('.oe_form_binary').show();
+ this.$('.o_form_binary_progress').hide();
+ this.$('button').show();
},
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
+ this.binary_value = true;
+ this.set_filename(name);
+ this.set_value(file_base64);
},
on_save_as: function(ev) {
var value = this.get('value');
@@ -1248,7 +1149,6 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
'error': c.rpc_error.bind(c)
});
ev.stopPropagation();
- return false;
}
},
set_filename: function(value) {
@@ -1262,129 +1162,95 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
}
},
on_clear: function() {
- if (this.get('value') !== false) {
- this.binary_value = false;
- this.internal_set_value(false);
- }
- return false;
+ this.binary_value = false;
+ this.set_filename('');
+ this.set_value(false); // FIXME do not really remove the value
}
});
var FieldBinaryFile = FieldBinary.extend({
template: 'FieldBinaryFile',
initialize_content: function() {
+ var self = this;
this._super();
if (this.get("effective_readonly")) {
- var self = this;
- this.$el.find('a').click(function(ev) {
+ this.$el.click(function(ev) {
if (self.get('value')) {
self.on_save_as(ev);
}
return false;
});
+ } else {
+ this.$input = this.$('.o_form_input').eq(0);
+ this.$input.on('click', function() {
+ self.$inputFile.click();
+ });
}
},
render_value: function() {
- var show_value;
- if (!this.get("effective_readonly")) {
- if (this.node.attrs.filename) {
- show_value = this.view.datarecord[this.node.attrs.filename] || '';
- } else {
- show_value = (this.get('value') !== null && this.get('value') !== undefined && this.get('value') !== false) ? this.get('value') : '';
+ var filename = this.view.datarecord[this.node.attrs.filename];
+ if (this.get("effective_readonly")) {
+ this.do_toggle(!!this.get('value'));
+ if (this.get('value')) {
+ this.$el.empty().append($("<span/>").addClass('fa fa-download'));
+ if (filename) {
+ this.$el.append(" " + filename);
+ }
}
- this.$el.find('input').eq(0).val(show_value);
} else {
- this.$el.find('a').toggle(!!this.get('value'));
- if (this.get('value')) {
- show_value = _t("Download");
- if (this.view)
- show_value += " " + (this.view.datarecord[this.node.attrs.filename] || '');
- this.$el.find('a').text(show_value);
+ if(this.get('value')) {
+ this.$el.children().removeClass('o_hidden');
+ this.$('.o_select_file_button').first().addClass('o_hidden');
+ this.$input.val(filename || this.get('value'));
+ } else {
+ this.$el.children().addClass('o_hidden');
+ this.$('.o_select_file_button').first().removeClass('o_hidden');
}
}
- },
- on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- this.binary_value = true;
- this.set_filename(name);
- this.internal_set_value(file_base64);
- var show_value = name + " (" + utils.human_size(size) + ")";
- this.$el.find('input').eq(0).val(show_value);
- },
- on_clear: function() {
- this._super.apply(this, arguments);
- this.$el.find('input').eq(0).val('');
- this.set_filename('');
- },
- set_value: function(value_){
- var changed = value_ !== this.get_value();
- this._super.apply(this, arguments);
- // Trigger value change if size is the same
- if (!changed){
- this.trigger("change:value", this, {
- oldValue: value_,
- newValue: value_
- });
- }
- }
+ }
});
var FieldBinaryImage = FieldBinary.extend({
template: 'FieldBinaryImage',
placeholder: "/web/static/src/img/placeholder.png",
render_value: function() {
- var self = this;
- var url;
- this.session = session;
- if (this.get('value') && !utils.is_bin_size(this.get('value'))) {
- url = 'data:image/png;base64,' + this.get('value');
- } else if (this.get('value')) {
- var id = JSON.stringify(this.view.datarecord.id || null);
- var field = this.name;
- if (this.options.preview_image)
- field = this.options.preview_image;
- url = session.url('/web/image', {
- model: this.view.dataset.model,
- id: id,
- field: field,
- unique: (this.view.datarecord.__last_update || '').replace(/[^0-9]/g, ''),
- });
- } else {
- url = this.placeholder;
+ var url = this.placeholder;
+ if(this.get('value')) {
+ if(!utils.is_bin_size(this.get('value'))) {
+ url = 'data:image/png;base64,' + this.get('value');
+ } else {
+ url = session.url('/web/image', {
+ model: this.view.dataset.model,
+ id: JSON.stringify(this.view.datarecord.id || null),
+ field: (this.options.preview_image)? this.options.preview_image : this.name,
+ unique: (this.view.datarecord.__last_update || '').replace(/[^0-9]/g, ''),
+ });
+ }
}
- var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url }));
- $($img).click(function(e) {
+
+ var $img = $(QWeb.render("FieldBinaryImage-img", {widget: this, url: url}));
+
+ var self = this;
+ $img.click(function(e) {
if(self.view.get("actual_mode") == "view") {
- var $button = $(".oe_form_button_edit");
+ var $button = $(".o_form_button_edit");
$button.openerpBounce();
e.stopPropagation();
}
});
- this.$el.find('> img').remove();
+ this.$('> img').remove();
+ if (self.options.size) {
+ $img.css("width", "" + self.options.size[0] + "px");
+ $img.css("height", "" + self.options.size[1] + "px");
+ }
this.$el.prepend($img);
- $img.load(function() {
- if (! self.options.size)
- return;
- $img.css("max-width", "" + self.options.size[0] + "px");
- $img.css("max-height", "" + self.options.size[1] + "px");
- });
$img.on('error', function() {
self.on_clear();
$img.attr('src', self.placeholder);
self.do_warn(_t("Image"), _t("Could not display the selected image."));
});
},
- on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- this.internal_set_value(file_base64);
- this.binary_value = true;
- this.render_value();
- this.set_filename(name);
- },
- on_clear: function() {
- this._super.apply(this, arguments);
- this.render_value();
- this.set_filename('');
- },
- set_value: function(value_){
+ set_value: function(value_) {
var changed = value_ !== this.get_value();
this._super.apply(this, arguments);
// By default, on binary images read, the server returns the binary size
@@ -1397,7 +1263,16 @@ var FieldBinaryImage = FieldBinary.extend({
newValue: value_
});
}
- }
+ },
+ is_false: function() {
+ return false;
+ },
+ set_dimensions: function(height, width) {
+ this.$el.css({
+ maxWidth: width,
+ minHeight: height,
+ });
+ },
});
var FieldStatus = common.AbstractField.extend({
@@ -1423,13 +1298,14 @@ var FieldStatus = common.AbstractField.extend({
});
this.get_selection();
if (this.options.clickable) {
- this.$el.on('click','li[data-id]',this.on_click_stage);
- }
- if (this.$el.parent().is('header')) {
- this.$el.after('<div class="oe_clear"/>');
+ this.bind_stage_click();
}
this._super();
},
+ bind_stage_click: function () {
+ // This function is overriden in the enterprise webclient
+ this.$el.on('click','li[data-id]',this.on_click_stage);
+ },
set_value: function(value_) {
if (value_ instanceof Array) {
value_ = value_[0];
@@ -1468,15 +1344,15 @@ var FieldStatus = common.AbstractField.extend({
var fold_field = this.options.fold_field;
var calculation = _.bind(function() {
- if (this.field.type == "many2one") {
- return self.get_distant_fields().then(function (fields) {
+ if (this.field.type === "many2one") {
+ return self.get_distant_fields().then(function () {
return new data.DataSetSearch(self, self.field.relation, self.build_context(), self.get("evaluated_selection_domain"))
.read_slice(_.union(_.keys(self.distant_fields), ['id']), {}).then(function (records) {
var ids = _.pluck(records, 'id');
return self.dataset.name_get(ids).then(function (records_name) {
_.each(records, function (record) {
- var name = _.find(records_name, function (val) {return val[0] == record.id;})[1];
- if (fold_field && record[fold_field] && record.id != self.get('value')) {
+ var name = _.find(records_name, function (val) {return val[0] === record.id;})[1];
+ if (fold_field && record[fold_field] && record.id !== self.get('value')) {
selection_folded.push([record.id, name]);
} else {
selection_unfolded.push([record.id, name]);
@@ -1492,7 +1368,7 @@ var FieldStatus = common.AbstractField.extend({
var select = this.field.selection;
for(var i=0; i < select.length; i++) {
var key = select[i][0];
- if(key == this.get('value') || !this.options.visible || this.options.visible.indexOf(key) != -1) {
+ if(key === this.get('value') || !this.options.visible || this.options.visible.indexOf(key) !== -1) {
selection_unfolded.push(select[i]);
}
}
@@ -1529,13 +1405,12 @@ var FieldStatus = common.AbstractField.extend({
return;
}
var val;
- if (this.field.type == "many2one") {
+ if (this.field.type === "many2one") {
val = parseInt($li.data("id"), 10);
- }
- else {
+ } else {
val = $li.data("id");
}
- if (val != self.get('value')) {
+ if (val !== self.get('value')) {
this.view.recursive_save().done(function() {
var change = {};
change[self.name] = val;
@@ -1548,8 +1423,7 @@ var FieldStatus = common.AbstractField.extend({
});
var FieldMonetary = FieldFloat.extend({
- template: "FieldMonetary",
- widget_class: 'oe_form_field_float oe_form_field_monetary',
+ template: 'FieldMonetary',
init: function() {
this._super.apply(this, arguments);
this.set({"currency": false});
@@ -1567,6 +1441,23 @@ var FieldMonetary = FieldFloat.extend({
this.on("change:currency_info", this, this.update);
return tmp;
},
+ initialize_content: function() {
+ if(!this.get('effective_readonly')) {
+ this.$input = this.$('input');
+ this.add_symbol();
+ } else {
+ this.$input = undefined;
+ }
+
+ this._super();
+ },
+ add_symbol: function() {
+ var currency = this.get('currency_info');
+ if(currency) {
+ var before = (currency.position === 'before');
+ this.$el[(before)? 'prepend' : 'append']($('<span/>', {html: currency.symbol}));
+ }
+ },
get_currency_info: function() {
var self = this;
if (this.get("currency") === false) {
@@ -1576,15 +1467,21 @@ var FieldMonetary = FieldFloat.extend({
return self.set({"currency_info": session.get_currency(self.get("currency"))});
},
update: function() {
- if (this.view.options.is_list_editable){
+ if (this.view.options.is_list_editable) {
return;
} else {
return this.reinitialize();
}
},
+ render_value: function() {
+ this._super();
+ if(this.get('effective_readonly')) {
+ this.add_symbol();
+ }
+ },
get_digits_precision: function() {
return this.node.attrs.digits || this.field.digits || (this.get('currency_info') && this.get('currency_info').digits);
- },
+ },
parse_value: function(val, def) {
return formats.parse_value(val, {type: "float", digits: this.get_digits_precision()}, def);
},
@@ -1625,7 +1522,6 @@ var StatInfo = common.AbstractField.extend({
this.$el.html(QWeb.render("StatInfo", options));
this.$el.addClass('o_stat_info');
},
-
});
/**
@@ -1633,14 +1529,18 @@ var StatInfo = common.AbstractField.extend({
switching between a green bullet / gray bullet.
*/
var FieldToggleBoolean = common.AbstractField.extend({
+ template: "toggle_button",
+ events: {
+ 'click': 'set_toggle_button'
+ },
render_value: function () {
- this.icon = this.get_value() ? 'gtk-yes.png' : 'gtk-normal.png';
- this.$el.html(QWeb.render("FieldToggleBoolean", {'widget': this}));
- this.$('.oe_toggle_button').on('click', this.set_toggle_button.bind(this));
+ var $img = this.$('img');
+ var src = $img.attr('src');
+ $img.attr('src', src.substr(0, src.lastIndexOf('/')+1) + (this.get_value() ? 'gtk-yes.png' : 'gtk-normal.png'));
},
set_toggle_button: function () {
var self = this;
- var toggle_value = this.get_value() === false ? true: false;
+ var toggle_value = !this.get_value();
if (this.view.get('actual_mode') == 'view') {
var rec_values = {};
rec_values[self.node.attrs.name] = toggle_value;
@@ -1658,91 +1558,8 @@ var FieldToggleBoolean = common.AbstractField.extend({
reload_record: function () {
this.view.reload();
},
-});
-
-/**
- This widget is intended to be used in config settings.
- When checked, an upgrade popup is showed to the user.
-*/
-
-var AbstractFieldUpgrade = {
- events: {
- 'click input': 'on_click_input',
- },
-
- start: function() {
- this._super.apply(this, arguments);
-
- this.get_enterprise_label().after($("<span>", {
- text: "Enterprise",
- 'class': "label label-primary oe_inline"
- }));
- },
-
- open_dialog: function() {
- var message = $(QWeb.render('EnterpriseUpgrade'));
-
- var buttons = [
- {
- text: _t("Upgrade now"),
- classes: 'btn-primary',
- close: true,
- click: this.confirm_upgrade,
- },
- {
- text: _t("Cancel"),
- close: true,
- },
- ];
-
- return new Dialog(this, {
- size: 'medium',
- buttons: buttons,
- $content: $('<div>', {
- html: message,
- }),
- title: _t("Odoo Enterprise"),
- }).open();
- },
-
- confirm_upgrade: function() {
- new Model("res.users").call("search_count", [[["share", "=", false]]]).then(function(data) {
- framework.redirect("https://www.odoo.com/odoo-enterprise/upgrade?num_users=" + data);
- });
- },
-
- get_enterprise_label: function() {},
- on_click_input: function() {},
-};
-
-var UpgradeBoolean = FieldBoolean.extend(AbstractFieldUpgrade, {
- template: "FieldUpgradeBoolean",
-
- get_enterprise_label: function() {
- return this.$label;
- },
-
- on_click_input: function(event) {
- if(this.$checkbox.prop("checked")) {
- this.open_dialog().on('closed', this, function() {
- this.$checkbox.prop("checked", false);
- });
- }
- },
-});
-
-var UpgradeRadio = FieldRadio.extend(AbstractFieldUpgrade, {
-
- get_enterprise_label: function() {
- return this.$('label').last();
- },
-
- on_click_input: function(event) {
- if($(event.target).val() == 1) {
- this.open_dialog().on('closed', this, function() {
- this.$('input').first().prop("checked", true);
- });
- }
+ is_false: function() {
+ return false;
},
});
@@ -1770,7 +1587,6 @@ core.form_widget_registry
.add('toggle_button', FieldToggleBoolean)
.add('float', FieldFloat)
.add('percentpie', FieldPercentPie)
- .add('barchart', FieldBarChart)
.add('integer', FieldFloat)
.add('float_time', FieldFloat)
.add('progressbar', FieldProgressBar)
@@ -1782,9 +1598,7 @@ core.form_widget_registry
.add('kanban_state_selection', KanbanSelection)
.add('statinfo', StatInfo)
.add('timezone_mismatch', TimezoneMismatch)
- .add('label_selection', LabelSelection)
- .add('upgrade_boolean', UpgradeBoolean)
- .add('upgrade_radio', UpgradeRadio);
+ .add('label_selection', LabelSelection);
/**
@@ -1796,8 +1610,12 @@ core.form_widget_registry
core.form_tag_registry.add('button', WidgetButton);
return {
+ FieldBoolean: FieldBoolean,
FieldChar: FieldChar,
+ FieldEmail: FieldEmail,
FieldFloat: FieldFloat,
+ FieldRadio: FieldRadio,
+ FieldStatus: FieldStatus,
FieldMonetary: FieldMonetary,
WidgetButton: WidgetButton
};
diff --git a/addons/web/static/src/js/views/graph_view.js b/addons/web/static/src/js/views/graph_view.js
index 62ee1e62c23..c8ff16cd0f1 100644
--- a/addons/web/static/src/js/views/graph_view.js
+++ b/addons/web/static/src/js/views/graph_view.js
@@ -14,7 +14,7 @@ var _t = core._t;
var QWeb = core.qweb;
var GraphView = View.extend({
- className: 'oe_graph',
+ className: 'o_graph',
display_name: _lt('Graph'),
icon: 'fa-bar-chart',
require_fields: true,
@@ -54,12 +54,14 @@ var GraphView = View.extend({
if ($node) {
var context = {measures: _.pairs(_.omit(this.measures, '__count__'))};
this.$buttons = $(QWeb.render('GraphView.buttons', context));
- this.$measure_list = this.$buttons.find('.oe-measure-list');
+ this.$measure_list = this.$buttons.find('.o_graph_measures_list');
this.update_measure();
this.$buttons.find('button').tooltip();
this.$buttons.click(this.on_button_click.bind(this));
- this.$buttons.appendTo($node);
+ this.$buttons.find('.o_graph_button[data-mode="' + this.widget.mode + '"]').addClass('active');
+
+ this.$buttons.appendTo($node);
}
},
update_measure: function () {
@@ -86,7 +88,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.initial_groupbys = context.graph_groupbys || (group_by.length ? group_by : this.initial_groupbys);
this.widget = new GraphWidget(this, this.model, {
measure: context.graph_measure || this.active_measure,
mode: context.graph_mode || this.active_mode,
@@ -112,14 +114,16 @@ var GraphView = View.extend({
},
on_button_click: function (event) {
var $target = $(event.target);
- if ($target.hasClass('oe-bar-mode')) {this.widget.set_mode('bar');}
- if ($target.hasClass('oe-line-mode')) {this.widget.set_mode('line');}
- if ($target.hasClass('oe-pie-mode')) {this.widget.set_mode('pie');}
- if ($target.parents('.oe-measure-list').length) {
- var parent = $target.parent(),
- field = parent.data('field');
+ if ($target.hasClass('o_graph_button')) {
+ this.widget.set_mode($target.data('mode'));
+ this.$buttons.find('.o_graph_button.active').removeClass('active');
+ $target.addClass('active');
+ }
+ else if ($target.parents('.o_graph_measures_list').length) {
+ var parent = $target.parent();
+ var field = parent.data('field');
this.active_measure = field;
- parent.toggleClass('selected');
+ event.preventDefault();
event.stopPropagation();
this.update_measure();
this.widget.set_measure(this.active_measure);
diff --git a/addons/web/static/src/js/views/graph_widget.js b/addons/web/static/src/js/views/graph_widget.js
index a86ea35e449..e3786408125 100644
--- a/addons/web/static/src/js/views/graph_widget.js
+++ b/addons/web/static/src/js/views/graph_widget.js
@@ -14,6 +14,7 @@ var QWeb = core.qweb;
var MAX_LEGEND_LENGTH = 25 * (1 + config.device.size_class);
return Widget.extend({
+ className: "o_graph_svg_container",
init: function (parent, model, options) {
this._super(parent);
this.context = options.context;
diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js
index d42d34a98d2..9214eb27152 100644
--- a/addons/web/static/src/js/views/list_view.js
+++ b/addons/web/static/src/js/views/list_view.js
@@ -8,6 +8,7 @@ var DataExport = require('web.DataExport');
var formats = require('web.formats');
var common = require('web.list_common');
var Model = require('web.DataModel');
+var Pager = require('web.Pager');
var pyeval = require('web.pyeval');
var session = require('web.session');
var Sidebar = require('web.Sidebar');
@@ -34,8 +35,7 @@ var row_decoration = [
var ListView = View.extend({
_template: 'ListView',
- accesskey: 'L',
- display_name: _lt('List'),
+ accesskey: "L",
defaults: _.extend({}, View.prototype.defaults, {
// records can be selected one by one
selectable: true,
@@ -54,8 +54,14 @@ var ListView = View.extend({
//whether the editable property of the view has to be disabled
disable_editable_mode: false,
}),
+ display_name: _lt('List'),
events: {
- 'click thead th.oe_sortable[data-id]': 'sort_by_column'
+ 'click thead th.o_column_sortable[data-id]': 'sort_by_column',
+ 'click .oe_view_nocontent': function() {
+ if (this.$buttons) {
+ this.$buttons.width(this.$buttons.width() + 1).openerpBounce();
+ }
+ },
},
icon: 'fa-list-ul',
@@ -92,8 +98,7 @@ var ListView = View.extend({
});
this.previous_colspan = null;
- this.colors = null;
- this.fonts = null;
+ this.decoration = null;
this.columns = [];
@@ -111,7 +116,6 @@ var ListView = View.extend({
this.groups.datagroup.sort = this.dataset._sort;
}
- this.page = 0;
this.records.bind('change', function (event, record, key) {
if (!_(self.aggregate_columns).chain()
.pluck('name').contains(key).value()) {
@@ -127,6 +131,7 @@ var ListView = View.extend({
this.options.$pager = false;
}
+ this.options.deletable = this.options.deletable && this.is_action_enabled('delete');
this.name = "" + this.fields_view.arch.attrs.string;
// the view's number of records per page (|| section)
@@ -135,14 +140,26 @@ var ListView = View.extend({
(this.getParent().action || {}).limit ||
parseInt(this.fields_view.arch.attrs.limit, 10) ||
80);
+ // the index of the first displayed record (starting from 1)
+ this.current_min = 1;
- //Sort
- var default_order = this.fields_view.arch.attrs.default_order,
- unsorted = !this.dataset._sort.length;
+ // Sort
+ var default_order = this.fields_view.arch.attrs.default_order;
+ var unsorted = !this.dataset._sort.length;
if (unsorted && default_order && !this.grouped) {
this.dataset.set_sort(default_order.split(','));
}
},
+ 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));
+ });
+ return this._super();
+ },
/**
* Set a custom Group construct as the root of the List View.
*
@@ -171,43 +188,6 @@ var ListView = View.extend({
}
});
},
- 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();
- },
/**
* Computes and returns the classnames for the provided record (from the
* ``@decoration`` attribute)
@@ -231,24 +211,58 @@ var ListView = View.extend({
return classnames;
},
/**
+ * Renders the table itself and inserts its content
+ * Computes and displays the aggregates
+ * Hooks up the selection of records event
+ * Adds adequate classname on the sorted column
+ */
+ load_list: function() {
+ var self = this;
+
+ // Render the table and append its content
+ this.$el.html(QWeb.render(this._template, this));
+ this.$el.addClass(this.fields_view.arch.attrs['class']);
+ if (this.grouped) {
+ this.$('.o_list_view').addClass('o_list_view_grouped');
+ }
+ this.$('.o_list_view').append(this.groups.elements);
+
+ // Compute the aggregates and display them in the list's footer
+ this.compute_aggregates();
+
+ // Head hook
+ // Selecting records
+ this.$('thead .o_list_record_selector input').click(function() {
+ self.$('tbody .o_list_record_selector input').prop('checked', $(this).prop('checked') || false);
+ var selection = self.groups.get_selection();
+ $(self.groups).trigger('selected', [selection.ids, selection.records]);
+ });
+
+ // Sort
+ if (this.dataset._sort.length) {
+ if (this.dataset._sort[0].indexOf('-') === -1) {
+ this.$('th[data-id=' + this.dataset._sort[0] + ']').addClass("o-sort-down");
+ } else {
+ this.$('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("o-sort-up");
+ }
+ }
+
+ this.trigger('list_view_loaded', data, this.grouped);
+ return $.when();
+ },
+ /**
* Render the buttons according to the ListView.buttons template and
* add listeners on it.
* Set this.$buttons with the produced jQuery element
* @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted
* $node may be undefined, in which case the ListView inserts them into this.options.$buttons
- * or into a div of its template
+ * if it exists
*/
render_buttons: function($node) {
if (!this.$buttons) {
this.$buttons = $(QWeb.render("ListView.buttons", {'widget': this}));
-
this.$buttons.find('.o_list_button_add').click(this.proxy('do_add_record'));
-
- if ($node) {
- this.$buttons.appendTo($node);
- } else {
- this.$('.oe_list_buttons').replaceWith(this.$buttons);
- }
+ this.$buttons.appendTo($node);
}
},
/**
@@ -271,7 +285,7 @@ var ListView = View.extend({
this.is_action_enabled('delete') && { label: _t('Delete'), callback: this.do_delete_selected }
]));
- $node = $node || this.options.$sidebar || this.$('.oe_list_sidebar');
+ $node = $node || this.options.$sidebar;
this.sidebar.appendTo($node);
// Hide the sidebar by default (it will be shown as soon as a record is selected)
@@ -279,72 +293,52 @@ var ListView = View.extend({
}
},
/**
- * Render the pager according to the ListView.pager template and add listeners on it.
- * Set this.$pager with the produced jQuery element
- * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted
+ * Instantiate and render the pager and add listeners on it.
+ * Set this.pager
+ * @param {jQuery} [$node] a jQuery node where the pager should be inserted
* $node may be undefined, in which case the ListView inserts the pager into this.options.$pager
- * or into a div of its template
*/
- render_pager: function($node) {
- if (!this.$pager && this.options.pager) {
- var self = this;
-
- this.$pager = $(QWeb.render("ListView.pager", {'widget': self}));
- this.$pager
- .on('click', 'a[data-pager-action]', function () {
- var $this = $(this);
- var max_page_index = Math.ceil(self.dataset.size() / self._limit) - 1;
- switch ($this.data('pager-action')) {
- case 'first':
- self.page = 0;
- break;
- case 'last':
- self.page = max_page_index;
- break;
- case 'next':
- self.page += 1;
- break;
- case 'previous':
- self.page -= 1;
- break;
+ render_pager: function($node, options) {
+ if (!this.pager && this.options.pager) {
+ this.pager = new Pager(this, this.dataset.size(), 1, this._limit, options);
+ this.pager.appendTo($node || this.options.$pager);
+
+ this.pager.on('pager_changed', this, function (new_state) {
+ var self = this;
+ var limit_changed = (this._limit !== new_state.limit);
+
+ this._limit = new_state.limit;
+ this.current_min = new_state.current_min;
+ this.reload_content().then(function() {
+ // Reset the scroll position to the top on page changed only
+ if (!limit_changed) {
+ self.set_scrollTop(0);
+ self.trigger_up('scrollTo', {offset: 0});
}
- if (self.page < 0) {
- self.page = max_page_index;
- } else if (self.page > max_page_index) {
- self.page = 0;
- }
- self.reload_content();
- }).find('.oe_list_pager_state')
- .click(function (e) {
- e.stopPropagation();
- var $this = $(this);
-
- var $select = $('<select>')
- .appendTo($this.empty())
- .click(function (e) {e.stopPropagation();})
- .append('<option value="80">80</option>' +
- '<option value="200">200</option>' +
- '<option value="500">500</option>' +
- '<option value="2000">2000</option>' +
- '<option value="NaN">' + _t("Unlimited") + '</option>')
- .change(function () {
- var val = parseInt($select.val(), 10);
- self._limit = (isNaN(val) ? null : val);
- self.page = 0;
- self.reload_content();
- }).blur(function() {
- $(this).trigger('change');
- })
- .val(self._limit || 'NaN');
- });
- this.configure_pager(this.dataset);
-
- $node = $node || this.options.$pager;
- if ($node) {
- this.$pager.appendTo($node);
- } else {
- this.$('.oe_list_pager').replaceWith(this.$pager);
+ });
+ });
+ }
+ },
+ /**
+ * Updates the pager based on the provided dataset's information
+ *
+ * Horrifying side-effect: sets the dataset's data on this.dataset?
+ *
+ * @param {instance.web.DataSet} [dataset]
+ * @param {int} [current_min] the min pager value
+ */
+ update_pager: function (dataset, current_min) {
+ this.dataset.ids = dataset.ids;
+ // Not exactly clean
+ if (dataset._length !== undefined) {
+ this.dataset._length = dataset._length;
+ }
+ if (this.pager && !this.grouped) {
+ var new_state = { size: this.dataset.size(), limit: this._limit };
+ if (current_min) {
+ new_state.current_min = current_min;
}
+ this.pager.update_state(new_state);
}
},
sort_by_column: function (e) {
@@ -357,54 +351,14 @@ var ListView = View.extend({
return false;
}
this.dataset.sort(col_name);
- if($column.hasClass("sortdown") || $column.hasClass("sortup")) {
- $column.toggleClass("sortup sortdown");
+ if($column.hasClass("o-sort-down") || $column.hasClass("o-sort-up")) {
+ $column.toggleClass("o-sort-up o-sort-down");
} else {
- $column.addClass("sortdown");
- }
- $column.siblings('.oe_sortable').removeClass("sortup sortdown");
-
- this.reload_content();
- },
- /**
- * Configures the ListView pager based on the provided dataset's information
- *
- * Horrifying side-effect: sets the dataset's data on this.dataset?
- *
- * @param {instance.web.DataSet} dataset
- */
- configure_pager: function (dataset) {
- this.dataset.ids = dataset.ids;
- // Not exactly clean
- if (dataset._length) {
- this.dataset._length = dataset._length;
+ $column.addClass("o-sort-down");
}
- if (this.$pager) {
- if (this.grouped) {
- // page count is irrelevant on grouped page, replace by limit
- this.$pager.find('.oe-pager-buttons').hide();
- this.$pager.find('.oe_list_pager_state').text(this._limit || '∞');
- } else {
- var total = dataset.size();
- var limit = this._limit || total;
- this.$pager.find('.oe-pager-buttons').toggle(total > limit);
- this.$pager.find('.oe_pager_value').toggle(total !== 0);
- var spager = '-';
- if (total) {
- var range_start = this.page * limit + 1;
- var range_stop = range_start - 1 + limit;
- if (this.records.length) {
- range_stop = range_start - 1 + this.records.length;
- }
- if (range_stop > total) {
- range_stop = total;
- }
- spager = _.str.sprintf(_t("%d-%d of %d"), range_start, range_stop, total);
- }
+ $column.siblings('.o_column_sortable').removeClass("o-sort-up o-sort-down");
- this.$pager.find('.oe_list_pager_state').text(spager);
- }
- }
+ return this.reload_content();
},
/**
* Sets up the listview's columns: merges view and fields data, move
@@ -422,8 +376,7 @@ var ListView = View.extend({
return for_(id, fields[id], field);
}));
if (grouped) {
- this.columns.unshift(
- new ListView.MetaColumn('_group', _t("Group")));
+ this.columns.unshift(new ListView.MetaColumn('_group'));
}
this.visible_columns = _.filter(this.columns, function (column) {
@@ -468,27 +421,27 @@ var ListView = View.extend({
*/
reload_content: synchronized(function () {
var self = this;
- self.$el.find('.oe_list_record_selector').prop('checked', false);
+ this.setup_columns(this.fields_view.fields, this.grouped);
+ this.$('tbody .o_list_record_selector input').prop('checked', false);
this.records.reset();
var reloaded = $.Deferred();
- reloaded.then(function () {
- self.configure_pager(self.dataset);
- });
- this.$el.find('.oe_list_content').append(
- this.groups.render(function () {
- if (self.dataset.index === null) {
- if (self.records.length) {
- self.dataset.index = 0;
- }
- } else if (self.dataset.index >= self.records.length) {
- self.dataset.index = self.records.length ? 0 : null;
+ this.groups.render(function () {
+ if (self.dataset.index === null) {
+ if (self.records.length) {
+ self.dataset.index = 0;
+ }
+ } else if (self.dataset.index >= self.records.length) {
+ self.dataset.index = self.records.length ? 0 : null;
+ }
+ self.load_list().then(function () {
+ if (!self.grouped && self.display_nocontent_helper()) {
+ self.no_result();
}
-
- self.compute_aggregates();
reloaded.resolve();
- }));
+ });
+ });
this.do_push_state({
- page: this.page,
+ min: this.current_min,
limit: this._limit
});
return reloaded.promise();
@@ -531,8 +484,8 @@ var ListView = View.extend({
do_load_state: function(state, warm) {
var reload = false;
- if (state.page && this.page !== state.page) {
- this.page = state.page;
+ if (state.min && this.current_min !== state.min) {
+ this.current_min = state.min;
reload = true;
}
if (state.limit) {
@@ -558,7 +511,7 @@ var ListView = View.extend({
* @param {Object} results results of evaluating domain and process for a search
*/
do_search: function (domain, context, group_by) {
- this.page = 0;
+ this.current_min = 1;
this.groups.datagroup = new DataGroup(
this, this.model, domain, context, group_by);
this.groups.datagroup.sort = this.dataset._sort;
@@ -569,6 +522,10 @@ var ListView = View.extend({
this.no_leaf = !!context['group_by_no_leaf'];
this.grouped = !!group_by;
+ // Hide the pager in grouped mode
+ if (this.pager && this.grouped) {
+ this.pager.do_hide();
+ }
return this.reload_content();
},
/**
@@ -581,26 +538,25 @@ var ListView = View.extend({
return;
}
var self = this;
+
return $.when(this.dataset.unlink(ids)).done(function () {
_(ids).each(function (id) {
self.records.remove(self.records.get(id));
});
// Hide the table if there is no more record in the dataset
- if (self.dataset.size() === 0) {
+ if (self.display_nocontent_helper()) {
self.no_result();
} else {
+ // Load previous page if the current one is empty
if (self.records.length === 0 && self.dataset.size() > 0) {
- //Trigger previous manually to navigate to previous page,
- //If all records are deleted on current page.
- self.$pager.find('ul li:first a').trigger('click');
- } else if (self.dataset.size() === self._limit) {
- //Reload listview to update current page with next page records
- //because pager going to be hidden if dataset.size == limit
+ self.pager.previous();
+ }
+ // Reload the list view if we are not on the last page
+ if (self.current_min + self._limit - 1 < self.dataset.size()) {
self.reload();
- } else {
- self.configure_pager(self.dataset);
}
}
+ self.update_pager(self.dataset);
self.compute_aggregates();
});
},
@@ -613,7 +569,7 @@ var ListView = View.extend({
do_select: function (ids, records, deselected) {
// uncheck header hook if at least one row has been deselected
if (deselected) {
- this.$('.oe_list_record_selector').prop('checked', false);
+ this.$('thead .o_list_record_selector input').prop('checked', false);
}
if (!ids.length) {
@@ -810,13 +766,14 @@ var ListView = View.extend({
this.display_aggregates(aggregates);
},
display_aggregates: function (aggregation) {
- var $footer_cells = this.$el.find('.oe_list_footer');
+ var $footer_cells = this.$('tfoot td');
_(this.aggregate_columns).each(function (column) {
if (!column['function']) {
return;
}
$footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id))
+ .addClass('o_list_number')
.html(column.format(aggregation, { process_modifiers: false }));
});
},
@@ -831,7 +788,7 @@ var ListView = View.extend({
*/
get_active_domain: function () {
var self = this;
- if (this.$('.oe_list_record_selector').prop('checked')) {
+ if (this.$('thead .o_list_record_selector input').prop('checked')) {
var search_view = this.getParent().searchview;
var search_data = search_view.build_search_data();
return pyeval.eval_domains_and_contexts({
@@ -859,7 +816,7 @@ var ListView = View.extend({
pad_columns: function (count, options) {
options = options || {};
// padding for action/pager header
- var $first_header = this.$el.find('thead tr:first th');
+ var $first_header = this.$('thead tr:first th');
var colspan = $first_header.attr('colspan');
if (colspan) {
if (!this.previous_colspan) {
@@ -882,7 +839,7 @@ var ListView = View.extend({
* Removes all padding columns of the table
*/
unpad_columns: function () {
- this.$el.find('.oe_list_padding').remove();
+ this.$('.oe_list_padding').remove();
if (this.previous_colspan) {
this.$el
.find('thead tr:first th')
@@ -890,23 +847,20 @@ var ListView = View.extend({
this.previous_colspan = null;
}
},
+ display_nocontent_helper: function () {
+ return (this.dataset.size() === 0);
+ },
no_result: function () {
- this.$el.find('.oe_view_nocontent').remove();
- if (this.groups.group_by
- || !this.options.action
- || !this.options.action.help) {
+ this.$('.oe_view_nocontent').remove();
+ if (this.groups.group_by ||
+ !this.options.action ||
+ !this.options.action.help) {
return;
}
- this.$el.find('table:first').hide();
+ this.$('table:first').hide();
this.$el.prepend(
$('<div class="oe_view_nocontent">').html(this.options.action.help)
);
- if (this.$buttons) {
- var $buttons = this.$buttons;
- this.$el.find('.oe_view_nocontent').click(function() {
- $buttons.width($buttons.width() + 1).openerpBounce();
- });
- }
}
});
core.view_registry.add('list', ListView);
@@ -975,9 +929,8 @@ ListView.List = Class.extend({
$row = self.$current.children(
'[data-id=' + record.get('id') + ']');
}
-
var $newRow = $(self.render_record(record));
- $newRow.find('.oe_list_record_selector input').prop('checked', !!$row.find('.oe_list_record_selector input').prop('checked'));
+ $newRow.find('.o_list_record_selector input').prop('checked', !!$row.find('.o_list_record_selector input').prop('checked'));
$row.replaceWith($newRow);
},
'add': function (ev, records, record, index) {
@@ -1012,14 +965,14 @@ ListView.List = Class.extend({
*/
e.preventDefault();
})
- .delegate('th.oe_list_record_selector', 'click', function (e) {
+ .delegate('td.o_list_record_selector', 'click', function (e) {
e.stopPropagation();
var selection = self.get_selection();
var checked = $(e.currentTarget).find('input').prop('checked');
$(self).trigger(
'selected', [selection.ids, selection.records, ! checked]);
})
- .delegate('td.oe_list_record_delete', 'click', function (e) {
+ .delegate('td.o_list_record_delete', 'click', function (e) {
e.stopPropagation();
var $row = $(e.target).closest('tr');
$(self).trigger('deleted', [[self.row_id($row)]]);
@@ -1028,7 +981,7 @@ ListView.List = Class.extend({
$('<input />').appendTo('body').focus().remove();
}
})
- .delegate('td.oe_list_field_cell button', 'click', function (e) {
+ .delegate('td button', 'click', function (e) {
e.stopPropagation();
var $target = $(e.currentTarget),
field = $target.closest('td').data('field'),
@@ -1158,7 +1111,7 @@ ListView.List = Class.extend({
}
var cells = [];
if (this.options.selectable) {
- cells.push('<th class="oe_list_record_selector"></td>');
+ cells.push('<td class="o_list_record_selector"></td>');
}
_(this.columns).each(function(column) {
if (column.invisible === '1') {
@@ -1167,7 +1120,7 @@ ListView.List = Class.extend({
cells.push('<td title="' + column.string + '">&nbsp;</td>');
});
if (this.options.deletable) {
- cells.push('<td class="oe_list_record_delete"></td>');
+ cells.push('<td class="o_list_record_delete"></td>');
}
cells.unshift('<tr>');
cells.push('</tr>');
@@ -1187,7 +1140,7 @@ ListView.List = Class.extend({
return result;
}
var records = this.records;
- this.$current.find('th.oe_list_record_selector input:checked')
+ this.$current.find('td.o_list_record_selector input:checked')
.closest('tr').each(function () {
var record = records.get($(this).data('id'));
result.ids.push(record.get('id'));
@@ -1206,14 +1159,12 @@ ListView.List = Class.extend({
return $(row).data('id');
},
/**
- * Death signal, cleans up list display
+ * Death signal, cleans up records's callbacks
*/
on_records_reset: function () {
_(this.record_callbacks).each(function (callback, event) {
this.records.unbind(event, callback);
}, this);
- if (!this.$current) { return; }
- this.$current.remove();
},
get_records: function () {
return this.records.map(function (record) {
@@ -1279,7 +1230,7 @@ ListView.Groups = Class.extend({
this.$row = null;
this.children = {};
- this.page = 0;
+ this.pager = null; // group pager
var self = this;
this.records.bind('reset', function () {
@@ -1315,51 +1266,46 @@ ListView.Groups = Class.extend({
}
return red_letter_tboday;
},
- make_paginator: function () {
+ /**
+ * Renders the group pager and append it to the group's header
+ */
+ render_group_pager: function () {
var self = this;
- var $prev = $('<button type="button" data-pager-action="previous">&lt;</button>')
- .click(function (e) {
- e.stopPropagation();
- if (self.page > 0) {
- self.page -= 1;
- } else {
- self.page = self.max_page_index;
- }
-
- self.$row.closest('tbody').next()
- .replaceWith(self.render());
+ if (this.datagroup.length > this.view._limit) {
+ this.pager = new Pager(this, this.datagroup.length, 1, this.view._limit);
+ this.pager.on('pager_changed', this, function (state) {
+ self.view._limit = state.limit;
+ self.current_min = state.current_min;
+ self.render().then(function() {
+ self.$row.closest('tbody').next().replaceWith($(self.elements));
+ });
});
- var $next = $('<button type="button" data-pager-action="next">&gt;</button>')
- .click(function (e) {
- e.stopPropagation();
- if (self.page < self.max_page_index) {
- self.page += 1;
- } else {
- self.page = 0;
- }
- self.$row.closest('tbody').next()
- .replaceWith(self.render());
+ var $last_cell = this.$row.children().last();
+ $last_cell.addClass('o_group_pager');
+ // Prevent group to fold when clicking on the pager
+ $last_cell.click(function(e) {
+ e.stopPropagation();
});
- this.$row.children().last()
- .addClass('oe_list_group_pagination')
- .append($prev)
- .append('<span class="oe_list_pager_state"></span>')
- .append($next);
+ this.pager.appendTo($last_cell);
+ }
},
open: function (point_insertion) {
- this.render().insertAfter(point_insertion);
+ this.render();
+ $(this.elements).insertAfter(point_insertion);
var no_subgroups = _(this.datagroup.group_by).isEmpty(),
records_terminated = !this.datagroup.context['group_by_no_leaf'];
if (no_subgroups && records_terminated) {
- this.make_paginator();
+ this.render_group_pager();
}
},
close: function () {
- this.$row.children().last().find('button').remove();
- this.$row.children().last().find('span').remove();
+ if (this.pager) {
+ this.pager.destroy();
+ }
this.records.reset();
+ this.$to_be_removed.remove();
},
/**
* Prefixes ``$node`` with floated spaces in order to indent it relative
@@ -1391,20 +1337,20 @@ ListView.Groups = Class.extend({
self.bind_child_events(child);
child.datagroup = group;
- var $row = child.$row = $('<tr class="oe_group_header">');
+ var $row = child.$row = $('<tr class="o_group_header">');
if (group.openable && group.length) {
$row.click(function (e) {
if (!$row.data('open')) {
$row.data('open', true)
- .find('span.ui-icon')
- .removeClass('ui-icon-triangle-1-e')
- .addClass('ui-icon-triangle-1-s');
+ .find('span.fa')
+ .removeClass('fa-caret-right')
+ .addClass('fa-caret-down');
child.open(self.point_insertion(e.currentTarget));
} else {
$row.removeData('open')
- .find('span.ui-icon')
- .removeClass('ui-icon-triangle-1-s')
- .addClass('ui-icon-triangle-1-e');
+ .find('span.fa')
+ .removeClass('fa-caret-down')
+ .addClass('fa-caret-right');
child.close();
// force recompute the selection as closing group reset properties
var selection = self.get_selection();
@@ -1414,7 +1360,7 @@ ListView.Groups = Class.extend({
}
placeholder.appendChild($row[0]);
- var $group_column = $('<th class="oe_list_group_name">').appendTo($row);
+ var $group_column = $('<th class="o_group_name">').appendTo($row);
// Don't fill this if group_by_no_leaf but no group_by
if (group.grouped_on) {
var row_data = {};
@@ -1449,12 +1395,9 @@ ListView.Groups = Class.extend({
if (group.length && group.openable) {
// Make openable if not terminal group & group_by_no_leaf
- $group_column.prepend('<span class="ui-icon ui-icon-triangle-1-e" style="float: left;">');
+ $group_column.prepend('<span class="fa fa-caret-right" style="padding-right: 5px;">');
} else {
- // Kinda-ugly hack: jquery-ui has no "empty" icon, so set
- // wonky background position to ensure nothing is displayed
- // there but the rest of the behavior is ui-icon's
- $group_column.prepend('<span class="ui-icon" style="float: left; background-position: 150px 150px">');
+ $group_column.prepend('<span class="fa">');
}
}
self.indent($group_column, group.level);
@@ -1511,13 +1454,11 @@ ListView.Groups = Class.extend({
});
this.bind_child_events(list);
- var view = this.view,
- limit = view._limit,
- page = this.datagroup.openable ? this.page : view.page;
+ var view = this.view;
+ var current_min = this.datagroup.openable ? this.current_min : view.current_min;
var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name');
- var options = { offset: page * limit, limit: limit, context: {bin_size: true} };
- //TODO xmo: investigate why we need to put the setTimeout
+ var options = { offset: current_min - 1, limit: view._limit, context: {bin_size: true} };
return $.async_when().then(function() {
return dataset.read_slice(fields, options).then(function (records) {
// FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently
@@ -1525,34 +1466,12 @@ ListView.Groups = Class.extend({
self.records.reset(null, {silent: true});
}
if (!self.datagroup.openable) {
- view.configure_pager(dataset);
- } else {
- if (dataset.size() == records.length) {
- // only one page
- self.$row.find('td.oe_list_group_pagination').find('button').remove();
- self.$row.find('td.oe_list_group_pagination').find('span').remove();
- } else {
- self.max_page_index = Math.ceil(dataset.size() / limit) - 1;
- self.$row
- .find('.oe_list_pager_state')
- .text(_.str.sprintf(_t("%(page)d/%(page_count)d"), {
- page: page + 1,
- page_count: self.max_page_index + 1
- }))
- .end()
- .find('button[data-pager-action=previous]')
- .toggleClass('disabled', page === 0)
- .end()
- .find('button[data-pager-action=next]')
- .toggleClass('disabled', page === self.max_page_index);
- }
+ // Update the main list view pager
+ view.update_pager(dataset, current_min);
}
self.records.add(records, {silent: true});
list.render();
- if (_.isEmpty(records)) {
- view.no_result();
- }
return list;
});
});
@@ -1580,7 +1499,7 @@ ListView.Groups = Class.extend({
helper: 'clone'
});
if (sequence_field) {
- list.$current.sortable('option', 'handle', '.oe_list_field_handle');
+ list.$current.sortable('option', 'handle', '.o_row_handle');
}
list.$current.sortable('option', {
start: function (e, ui) {
@@ -1631,7 +1550,7 @@ ListView.Groups = Class.extend({
var $el = $('<tbody>');
this.elements = [$el[0]];
- this.datagroup.list(
+ return this.datagroup.list(
_(this.view.visible_columns).chain()
.filter(function (column) { return column.tag === 'field';})
.pluck('name').value(),
@@ -1640,7 +1559,7 @@ ListView.Groups = Class.extend({
self.render_groups(groups));
if (post_render) { post_render(); }
}, function (dataset) {
- self.render_dataset(dataset).then(function (list) {
+ return self.render_dataset(dataset).then(function (list) {
self.children[null] = list;
self.elements =
[list.$current.replaceAll($el)[0]];
@@ -1650,7 +1569,6 @@ ListView.Groups = Class.extend({
self.view.trigger('view_list_rendered');
});
});
- return $el;
},
/**
* Returns the ids of all selected records for this group, and the records
@@ -1669,8 +1587,11 @@ ListView.Groups = Class.extend({
return {ids: ids, records: records};
},
on_records_reset: function () {
+ this.$to_be_removed = $(this.elements);
+ _.each(this.children, function(child){
+ this.$to_be_removed = this.$to_be_removed.add(child.$to_be_removed);
+ }.bind(this));
this.children = {};
- $(this.elements).remove();
},
get_records: function () {
if (_(this.children).isEmpty()) {
@@ -1725,13 +1646,12 @@ var DataGroup = Class.extend({
fields = _.unique((fields || []).concat(this.group_by));
}
var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by);
- $.when(query).done(function (querygroups) {
+ return $.when(query).then(function (querygroups) {
// leaf node
if (!querygroups) {
var ds = new data.DataSetSearch(self, self.model.name, self.model.context(), self.model.domain());
ds._sort = self.sort;
- ifRecords(ds);
- return;
+ return ifRecords(ds);
}
// internal node
var child_datagroups = _(querygroups).map(function (group) {
@@ -1763,7 +1683,7 @@ var StaticDataGroup = DataGroup.extend({
this.dataset = dataset;
},
list: function (fields, ifGroups, ifRecords) {
- ifRecords(this.dataset);
+ return ifRecords(this.dataset);
}
});
@@ -1811,6 +1731,10 @@ var Column = Class.extend({
C.prototype = this;
return new C(aggregation_func, this[aggregation_func]);
},
+ heading: function () {
+ return _.escape(this.string);
+ },
+ width: function () {},
/**
*
* @param row_data record whose values should be displayed in the cell
@@ -1834,7 +1758,8 @@ var Column = Class.extend({
? ''
: options.value_if_empty;
}
- return this._format(row_data, options);
+ var f = this._format(row_data, options);
+ return (f !== '')? f : '&nbsp;';
},
/**
* Method to override in order to provide alternative HTML content for the
@@ -1890,7 +1815,7 @@ var ColumnBoolean = Column.extend({
* @private
*/
_format: function (row_data, options) {
- return _.str.sprintf('<input type="checkbox" %s readonly="readonly"/>',
+ return _.str.sprintf('<div class="o_checkbox"><input type="checkbox" %s disabled="disabled"/><span/></div>',
row_data[this.id].value ? 'checked="checked"' : '');
}
});
@@ -1965,14 +1890,19 @@ var ColumnHandle = Column.extend({
this._super.apply(this, arguments);
// Handle overrides the field to not be form-editable.
this.modifiers.readonly = true;
+ this.string = ""; // Don't display the column header
+ },
+ heading: function () {
+ return '<span class="o_row_handle fa fa-arrows invisible"></span>';
},
+ width: function () { return 1; },
/**
* Return styling hooks for a drag handle
*
* @private
*/
_format: function (row_data, options) {
- return '<i class="oe_list_handle fa fa-circle"/>';
+ return '<span class="o_row_handle fa fa-arrows"/>';
}
});
@@ -2042,8 +1972,9 @@ var ColumnUrl = Column.extend({
var ColumnMonetary = Column.extend({
_format: function (row_data, options) {
+ var options = pyeval.py_eval(this.options || '{}');
//name of currency field is defined either by field attribute, in view options or we assume it is named currency_id
- var currency_field = (this.options && this.options.currency_field) || this.currency_field || 'currency_id';
+ var currency_field = (_.isEmpty(options) === false && options.currency_field) || this.currency_field || 'currency_id';
var currency_id = row_data[currency_field] && row_data[currency_field].value[0];
var currency = session.get_currency(currency_id);
var digits_precision = this.digits || (currency && currency.digits);
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 8937b8bb36f..f4a4c695334 100644
--- a/addons/web/static/src/js/views/list_view_editable.js
+++ b/addons/web/static/src/js/views/list_view_editable.js
@@ -69,9 +69,9 @@ var Editor = Widget.extend({
if (!(arch.attrs && arch.attrs.version === "7.0")) {
throw new Error("Editor delegate's #edition_view must be a version 7 view");
}
- if (!/\boe_form_container\b/.test(arch.attrs['class'])) {
+ if (!/\bo_list_editable_form\b/.test(arch.attrs['class'])) {
throw new Error("Editor delegate's #edition_view must have the class " +
- "'boe_form_container' on its root element");
+ "'o_list_editable_form' on its root element");
}
return edition_view;
},
@@ -183,12 +183,12 @@ ListView.include(/** @lends instance.web.ListView# */{
}
});
this.on('edit:after', this, function () {
- self.$el.add(self.$buttons).addClass('oe_editing');
+ self.$el.add(self.$buttons).addClass('o-editing');
self.$('.ui-sortable').sortable('disable');
});
this.on('save:after cancel:after', this, function () {
self.$('.ui-sortable').sortable('enable');
- self.$el.add(self.$buttons).removeClass('oe_editing');
+ self.$el.add(self.$buttons).removeClass('o-editing');
});
},
destroy: function () {
@@ -260,7 +260,7 @@ ListView.include(/** @lends instance.web.ListView# */{
this._super.apply(this, arguments);
}
},
- start: function () {
+ load_list: function (data, grouped) {
// tree/@editable takes priority on everything else if present.
var result = this._super.apply(this, arguments);
@@ -271,10 +271,10 @@ ListView.include(/** @lends instance.web.ListView# */{
this.editor = new Editor(this); // Editor is not restartable due to formview not being restartable
if(this.editable()) {
- this.$el.addClass('oe_list_editable');
+ this.$el.addClass('o_list_editable');
return $.when(result, this.editor.prependTo(this.$el).done(this.proxy('setup_events')));
} else {
- this.$el.removeClass('oe_list_editable');
+ this.$el.removeClass('o_list_editable');
}
return result;
},
@@ -366,11 +366,11 @@ ListView.include(/** @lends instance.web.ListView# */{
fields[field_name] = field;
self.fields_for_resize.push({field: field, cell: cell});
}, options).then(function () {
- $recordRow.addClass('oe_edition');
+ $recordRow.addClass('o_row_edition');
self.resize_fields();
// Local function that returns true if field is visible and editable
var is_focusable = function(field) {
- return field && field.$el.is(':visible:not(.oe_readonly)');
+ return field && field.$el.is(':visible:not(.o_readonly)');
};
var focus_field = options && options.focus_field ? options.focus_field : undefined;
if (!is_focusable(fields[focus_field])) {
@@ -423,16 +423,21 @@ ListView.include(/** @lends instance.web.ListView# */{
resize_field: function (field, cell) {
var $cell = $(cell);
field.set_dimensions($cell.outerHeight(), $cell.outerWidth());
- field.$el.addClass('o_temp_visible').css({top: 0, left: 0}).position({
- my: 'left top',
- at: 'left top',
- of: $cell,
- }).removeClass('o_temp_visible');
+ position_element(field.$el, 'left top');
+ position_element(field.$translate, 'right top');
if(field.get('effective_readonly')) {
- field.$el.addClass('oe_readonly');
+ field.$el.addClass('o_readonly');
}
if(field.widget == "handle") {
- field.$el.addClass('oe_list_field_handle');
+ field.$el.addClass('o_row_handle');
+ }
+
+ function position_element($el, pos) {
+ $el.addClass('o_temp_visible').css({top: 0, left: 0}).position({
+ my: pos,
+ at: pos,
+ of: $cell,
+ }).removeClass('o_temp_visible');
}
},
/**
@@ -541,7 +546,7 @@ ListView.include(/** @lends instance.web.ListView# */{
var view = $.extend(true, {}, this.fields_view);
view.arch.tag = 'form';
_.extend(view.arch.attrs, {
- 'class': 'oe_form_container',
+ 'class': 'o_list_editable_form',
version: '7.0'
});
_(view.arch.children).chain()
@@ -817,7 +822,7 @@ ListView.List.include(/** @lends instance.web.ListView.List# */{
var record_id = $(event.currentTarget).data('id');
return this.view.start_edition(
((record_id)? this.records.get(record_id) : null), {
- focus_field: $(event.target).not(".oe_readonly").data('field'),
+ focus_field: $(event.target).not(".o_readonly").data('field'),
}).fail(function() {
return _super.apply(self, args); // The record can't be edited so open it in a modal (use-case: readonly mode)
});
diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js
index 9dc2c4c2cdd..c7710b86605 100644
--- a/addons/web/static/src/js/views/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot_view.js
@@ -21,11 +21,11 @@ var QWeb = core.qweb;
var PivotView = View.extend({
events: {
- 'click .oe-opened': 'on_open_header_click',
- 'click .oe-closed': 'on_closed_header_click',
- 'click .o-field-menu': 'on_field_menu_selection',
+ 'click .o_pivot_header_cell_opened': 'on_open_header_click',
+ 'click .o_pivot_header_cell_closed': 'on_closed_header_click',
+ 'click .o_pivot_field_menu': 'on_field_menu_selection',
'click td': 'on_cell_click',
- 'click .measure-row': 'on_measure_row_click',
+ 'click .o_pivot_measure_row': 'on_measure_row_click',
},
display_name: _lt('Pivot'),
icon: 'fa-table',
@@ -100,6 +100,7 @@ var PivotView = View.extend({
if ((!this.active_measures.length) || this.fields_view.arch.attrs.display_quantity) {
this.active_measures.push('__count__');
}
+
return $.when(fields_def, xlwt_def, this._super()).then(function (fields) {
self.prepare_fields(fields);
// add active measures to the measure list. This is very rarely necessary, but it
@@ -116,14 +117,13 @@ var PivotView = View.extend({
});
},
start: function () {
- this.$table_container = this.$('.o-pivot-table');
- this.$el.toggleClass('oe_enable_linking', this.enable_linking);
+ this.$el.toggleClass('o_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 = this.$('.o_field_selection');
this.$field_selection.html(QWeb.render('PivotView.FieldSelection', context));
core.bus.on('click', this, function () {
this.$field_selection.find('ul').first().hide();
@@ -161,11 +161,6 @@ var PivotView = View.extend({
render_sidebar: function($node) {
if (this.xlwt_installed && $node && this.options.sidebar) {
this.sidebar = new Sidebar(this, {editable: this.is_action_enabled('edit')});
- this.sidebar.add_items('other', [{
- label: _t("Download xls"),
- callback: this.download_table.bind(this),
- }]);
-
this.sidebar.appendTo($node);
}
},
@@ -207,11 +202,12 @@ var PivotView = View.extend({
},
do_show: function () {
var self = this;
+ var _super = this._super.bind(this);
this.do_push_state({});
- this.data_loaded.done(function () {
+ return this.data_loaded.done(function () {
self.display_table();
+ _super();
});
- return this._super();
},
get_context: function () {
return !this.ready ? {} : {
@@ -222,16 +218,17 @@ var PivotView = View.extend({
},
on_button_click: function (event) {
var $target = $(event.target);
- if ($target.hasClass('oe-pivot-flip')) { return this.flip();}
- if ($target.hasClass('oe-pivot-expand-all')) {return this.expand_all();}
- if ($target.parents('.oe-measure-list').length) {
- var parent = $target.parent(),
- field = parent.data('field');
+ if ($target.hasClass('o_pivot_flip_button')) { return this.flip(); }
+ if ($target.hasClass('o_pivot_expand_button')) { return this.expand_all(); }
+ if ($target.parents('.o_pivot_measures_list').length) {
+ var parent = $target.parent();
+ var field = parent.data('field');
parent.toggleClass('selected');
+ event.preventDefault();
event.stopPropagation();
return this.toggle_measure(field);
}
- if ($target.hasClass('oe-pivot-download')) {
+ if ($target.hasClass('o_pivot_download')) {
return this.download_table();
}
},
@@ -252,21 +249,23 @@ var PivotView = View.extend({
this.expand_header(header, groupbys[header.path.length - 1])
.then(this.proxy('display_table'));
} else {
- var $test = $(event.target);
- var pos = $test.position();
this.last_header_selected = id;
+ var $test = $(event.target);
var $menu = this.$field_selection.find('ul').first();
- $menu.css('top', pos.top + $test.parent().height() - 2);
- $menu.css('left', pos.left + event.offsetX);
+ var position = $test.position();
+ $menu.css({
+ top: position.top + $test.height(),
+ left: position.left + event.offsetX,
+ });
$menu.show();
event.stopPropagation();
}
},
on_cell_click: function (event) {
var $target = $(event.target);
- if ($target.hasClass('oe-closed')
- || $target.hasClass('oe-opened')
- || $target.hasClass('oe-empty')
+ if ($target.hasClass('o_pivot_header_cell_closed')
+ || $target.hasClass('o_pivot_header_cell_opened')
+ || $target.hasClass('o_empty')
|| !this.enable_linking) {
return;
}
@@ -297,11 +296,10 @@ var PivotView = View.extend({
col_id = $target.data('id'),
measure = $target.data('measure');
- this.sort_rows(col_id, measure, $target.hasClass('o-sorted-asc'));
+ this.sort_rows(col_id, measure, $target.hasClass('o_pivot_measure_row_sorted_asc'));
this.display_table();
},
sort_rows: function (col_id, measure, descending) {
- console.log('order', descending);
var self = this;
traverse_tree(this.main_row.root, function (header) {
header.children.sort(compare);
@@ -321,6 +319,7 @@ var PivotView = View.extend({
}
},
on_field_menu_selection: function (event) {
+ event.preventDefault();
var field = $(event.target).parent().data('field'),
interval = $(event.target).data('interval'),
header = this.headers[this.last_header_selected];
@@ -550,13 +549,22 @@ var PivotView = View.extend({
}
return find_path_in_tree(root, path);
},
+ _clean_table: function () {
+ return this.$el
+ .find('table')
+ .remove()
+ .end()
+ .find('.oe_view_nocontent')
+ .remove()
+ .end();
+ },
display_table: function () {
if (!this.active_measures.length || !this.has_data) {
- return this.$table_container.empty().append(QWeb.render('PivotView.nodata'));
+ return this._clean_table().append(QWeb.render('PivotView.nodata'));
}
var $fragment = $(document.createDocumentFragment()),
$table = $('<table>')
- .addClass('table table-hover table-condensed')
+ .addClass('table table-hover table-condensed table-bordered')
.appendTo($fragment),
$thead = $('<thead>').appendTo($table),
$tbody = $('<tbody>').appendTo($table),
@@ -572,8 +580,8 @@ var PivotView = View.extend({
$table.on('hover', 'td', function () {
$table.find('col:eq(' + $(this).index()+')').toggleClass('hover');
});
- this.$table_container.empty().append($fragment);
- this.$table_container.find('.oe-opened,.oe-closed').tooltip();
+ this._clean_table().append($fragment);
+ this.$('.o_pivot_header_cell_opened,.o_pivot_header_cell_closed').tooltip();
},
draw_headers: function ($thead, headers) {
var self = this,
@@ -591,26 +599,27 @@ var PivotView = View.extend({
.text(cell.title)
.attr('rowspan', cell.height)
.attr('colspan', cell.width);
- if (cell.total) {
- $cell.addClass('oe-total');
- }
if (i > 0) {
$cell.attr('title', groupby_labels[i-1]);
}
if (cell.expanded !== undefined) {
- $cell.addClass(cell.expanded ? 'oe-opened' : 'oe-closed');
+ $cell.addClass(cell.expanded ? 'o_pivot_header_cell_opened' : 'o_pivot_header_cell_closed');
$cell.data('id', cell.id);
}
if (cell.measure) {
- $cell.addClass('measure-row text-muted')
- .text(this.measures[cell.measure].string)
- .toggleClass('oe-total', cell.is_bold);
+ $cell.addClass('o_pivot_measure_row text-muted')
+ .text(this.measures[cell.measure].string);
$cell.data('id', cell.id).data('measure', cell.measure);
if (cell.id === this.sorted_column.id && cell.measure === this.sorted_column.measure) {
- $cell.addClass('o-sorted o-sorted-' + this.sorted_column.order);
+ $cell.addClass('o_pivot_measure_row_sorted_' + this.sorted_column.order);
}
}
$row.append($cell);
+
+ $cell.toggleClass('hidden-xs', (cell.expanded !== undefined) || (cell.measure !== undefined && j < headers[i].length - this.active_measures.length));
+ if (cell.height > 1) {
+ $cell.css('padding', 0);
+ }
}
$thead.append($row);
}
@@ -635,7 +644,7 @@ var PivotView = View.extend({
.text(rows[i].title)
.data('id', rows[i].id)
.css('padding-left', (5 + rows[i].indent * 30) + 'px')
- .addClass(rows[i].expanded ? 'oe-opened' : 'oe-closed');
+ .addClass(rows[i].expanded ? 'o_pivot_header_cell_opened' : 'o_pivot_header_cell_closed');
if (rows[i].indent > 0) $header.attr('title', groupby_labels[rows[i].indent - 1]);
$header.appendTo($row);
for (j = 0; j < length; j++) {
@@ -643,12 +652,15 @@ var PivotView = View.extend({
$cell = $('<td>')
.data('id', rows[i].id)
.data('col_id', rows[i].col_ids[Math.floor(j / nbr_measures)])
- .toggleClass('oe-empty', !value)
- .text(value);
+ .toggleClass('o_empty', !value)
+ .text(value)
+ .addClass('o_pivot_cell_value text-right');
if (((j >= length - this.active_measures.length) && display_total) || i === 0){
$cell.css('font-weight', 'bold');
}
$row.append($cell);
+
+ $cell.toggleClass('hidden-xs', j < length - this.active_measures.length);
}
$tbody.append($row);
}
diff --git a/addons/web/static/src/js/views/search_filters.js b/addons/web/static/src/js/views/search_filters.js
index c6193488578..041cda35580 100644
--- a/addons/web/static/src/js/views/search_filters.js
+++ b/addons/web/static/src/js/views/search_filters.js
@@ -12,9 +12,9 @@ var _lt = core._lt;
var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{
template: 'SearchView.extended_search.proposition',
events: {
- 'change .searchview_extended_prop_field': 'changed',
- 'change .searchview_extended_prop_op': 'operator_changed',
- 'click .searchview_extended_delete_prop': function (e) {
+ 'change .o_searchview_extended_prop_field': 'changed',
+ 'change .o_searchview_extended_prop_op': 'operator_changed',
+ 'click .o_searchview_extended_delete_prop': function (e) {
e.stopPropagation();
this.getParent().remove_proposition(this);
},
@@ -40,13 +40,13 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext
return this._super().done(this.proxy('changed'));
},
changed: function() {
- var nval = this.$(".searchview_extended_prop_field").val();
+ var nval = this.$(".o_searchview_extended_prop_field").val();
if(this.attrs.selected === null || this.attrs.selected === undefined || nval != this.attrs.selected.name) {
this.select_field(_.detect(this.fields, function(x) {return x.name == nval;}));
}
},
operator_changed: function (e) {
- var $value = this.$('.searchview_extended_prop_value');
+ var $value = this.$('.o_searchview_extended_prop_value');
switch ($(e.target).val()) {
case '∃':
case '∄':
@@ -66,7 +66,7 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext
if(this.attrs.selected !== null && this.attrs.selected !== undefined) {
this.value.destroy();
this.value = null;
- this.$('.searchview_extended_prop_op').html('');
+ this.$('.o_searchview_extended_prop_op').html('');
}
this.attrs.selected = field;
if(field === null || field === undefined) {
@@ -80,9 +80,9 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext
_.each(this.value.operators, function(operator) {
$('<option>', {value: operator.value})
.text(String(operator.text))
- .appendTo(self.$('.searchview_extended_prop_op'));
+ .appendTo(self.$('.o_searchview_extended_prop_op'));
});
- var $value_loc = this.$('.searchview_extended_prop_value').show().empty();
+ var $value_loc = this.$('.o_searchview_extended_prop_value').show().empty();
this.value.appendTo($value_loc);
},
@@ -90,7 +90,7 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext
if (this.attrs.selected === null || this.attrs.selected === undefined)
return null;
var field = this.attrs.selected,
- op_select = this.$('.searchview_extended_prop_op')[0],
+ op_select = this.$('.o_searchview_extended_prop_op')[0],
operator = op_select.options[op_select.selectedIndex];
return {
@@ -155,7 +155,10 @@ ExtendedSearchProposition.Field = Widget.extend({
});
ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({
- template: 'SearchView.extended_search.proposition.char',
+ tagName: 'input',
+ attributes: {
+ type: 'text'
+ },
operators: [
{value: "ilike", text: _lt("contains")},
{value: "not ilike", text: _lt("doesn't contain")},
@@ -170,7 +173,7 @@ ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({
});
ExtendedSearchProposition.DateTime = ExtendedSearchProposition.Field.extend({
- template: 'SearchView.extended_search.proposition.empty',
+ tagName: 'span',
operators: [
{value: "=", text: _lt("is equal to")},
{value: "!=", text: _lt("is not equal to")},
@@ -204,7 +207,11 @@ ExtendedSearchProposition.Date = ExtendedSearchProposition.DateTime.extend({
});
ExtendedSearchProposition.Integer = ExtendedSearchProposition.Field.extend({
- template: 'SearchView.extended_search.proposition.integer',
+ tagName: 'input',
+ attributes: {
+ type: 'number',
+ value: '0',
+ },
operators: [
{value: "=", text: _lt("is equal to")},
{value: "!=", text: _lt("is not equal to")},
@@ -280,7 +287,7 @@ ExtendedSearchProposition.Selection = ExtendedSearchProposition.Field.extend({
});
ExtendedSearchProposition.Boolean = ExtendedSearchProposition.Field.extend({
- template: 'SearchView.extended_search.proposition.empty',
+ tagName: 'span',
operators: [
{value: "=", text: _lt("is true")},
{value: "!=", text: _lt("is false")}
diff --git a/addons/web/static/src/js/views/search_inputs.js b/addons/web/static/src/js/views/search_inputs.js
index 44b79999259..0263f270172 100644
--- a/addons/web/static/src/js/views/search_inputs.js
+++ b/addons/web/static/src/js/views/search_inputs.js
@@ -541,6 +541,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{
return {
category: _t("Filter"),
icon: this.icon,
+ separator: _t(" or "),
values: values,
field: this
};
@@ -617,6 +618,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{
});
},
toggle_filter: function (e) {
+ e.preventDefault();
e.stopPropagation();
this.toggle(this.filters[Number($(e.target).parent().data('index'))]);
},
@@ -686,6 +688,7 @@ var GroupbyGroup = FilterGroup.extend({
return {
category: _t("Group By"),
icon: this.icon,
+ separator: " > ",
values: values,
field: this.searchview._s_groupby
};
diff --git a/addons/web/static/src/js/views/search_menus.js b/addons/web/static/src/js/views/search_menus.js
index 9edb817c567..10ab44ef30c 100644
--- a/addons/web/static/src/js/views/search_menus.js
+++ b/addons/web/static/src/js/views/search_menus.js
@@ -15,12 +15,16 @@ return Widget.extend({
'click li': function (event) {
event.stopImmediatePropagation();
},
- 'click .oe-save-search a': function () {
+ 'click li a': function (event) {
+ event.preventDefault();
+ },
+ 'click .o_save_search a': function (event) {
+ event.preventDefault();
this.toggle_save_menu();
},
- 'click .oe-save-name button': 'save_favorite',
+ 'click .o_save_name button': 'save_favorite',
'hidden.bs.dropdown': 'close_menus',
- 'keyup .oe-save-name input': function (ev) {
+ 'keyup .o_save_name input': function (ev) {
if (ev.which === $.ui.keyCode.ENTER) {
this.save_favorite();
}
@@ -38,8 +42,8 @@ return Widget.extend({
start: function () {
var self = this;
this.$filters = {};
- this.$save_search = this.$('.oe-save-search');
- this.$save_name = this.$('.oe-save-name');
+ this.$save_search = this.$('.o_save_search');
+ this.$save_name = this.$('.o_save_name');
this.$inputs = this.$save_name.find('input');
this.$divider = this.$('.divider');
this.$inputs.eq(0).val(this.searchview.get_title());
@@ -116,7 +120,6 @@ return Widget.extend({
model_id: this.target_model,
context: results.context,
domain: results.domain,
- sort: JSON.stringify(this.searchview.dataset._sort),
is_default: default_filter,
action_id: this.action_id,
};
@@ -204,12 +207,11 @@ return Widget.extend({
this.$divider.show();
if (!(key in this.$filters)) {
var $filter = $('<li>')
- .addClass(filter.user_id ? 'oe_searchview_custom_private'
- : 'oe_searchview_custom_public')
- .toggleClass('oe_searchview_custom_default', filter.is_default)
- .append($('<a>').text(filter.name))
+ .addClass(filter.user_id ? 'o-searchview-custom-private'
+ : 'o-searchview-custom-public')
+ .append($('<a>', {'href': '#'}).text(filter.name))
.append($('<span>', {
- class: 'fa fa-trash-o remove-filter',
+ class: 'fa fa-trash-o o-remove-filter',
on: {
click: function (event) {
event.stopImmediatePropagation();
@@ -235,13 +237,6 @@ return Widget.extend({
}
this.query.reset([this.facet_for(filter)], {
preventSearch: preventSearch || false});
-
- // Load sort settings on view
- if (!_.isUndefined(filter.sort)){
- var sort_items = JSON.parse(filter.sort);
- this.searchview.dataset.set_sort(sort_items);
- }
-
this.$filters[this.key_for(filter)].addClass('selected');
},
remove_filter: function (filter, $filter, key) {
@@ -277,16 +272,20 @@ var Widget = require('web.Widget');
return Widget.extend({
template: 'SearchView.FilterMenu',
events: {
- 'click .oe-add-filter': function () {
+ 'click .o_add_filter': function (event) {
+ event.preventDefault();
this.toggle_custom_filter_menu();
},
- 'click li': function (event) {event.stopImmediatePropagation();},
+ 'click li': function (event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ },
'hidden.bs.dropdown': function () {
this.toggle_custom_filter_menu(false);
},
- 'click .oe-add-condition': 'append_proposition',
- 'click .oe-apply-filter': 'commit_search',
- 'keyup .searchview_extended_prop_value': function (ev) {
+ 'click .o_add_condition': 'append_proposition',
+ 'click .o_apply_filter': 'commit_search',
+ 'keyup .o_searchview_extended_prop_value': function (ev) {
if (ev.which === $.ui.keyCode.ENTER) {
this.commit_search();
}
@@ -301,10 +300,10 @@ return Widget.extend({
},
start: function () {
var self = this;
- this.$menu = this.$('.filters-menu');
- this.$add_filter = this.$('.oe-add-filter');
- this.$apply_filter = this.$('.oe-apply-filter');
- this.$add_filter_menu = this.$('.oe-add-filter-menu');
+ this.$menu = this.$('.o_filters_menu');
+ this.$add_filter = this.$('.o_add_filter');
+ this.$apply_filter = this.$('.o_apply_filter');
+ this.$add_filter_menu = this.$('.o_add_filter_menu');
_.each(this.filters, function (group) {
if (group.is_visible()) {
group.insertBefore(self.$add_filter);
@@ -340,7 +339,7 @@ return Widget.extend({
.toggleClass('o_closed_menu', !self.custom_filters_open)
.toggleClass('o_open_menu', self.custom_filters_open);
self.$add_filter_menu.toggle(self.custom_filters_open);
- self.$('.oe-filter-condition').toggle(self.custom_filters_open);
+ self.$('.o_filter_condition').toggle(self.custom_filters_open);
});
},
append_proposition: function () {
@@ -402,7 +401,8 @@ return Widget.extend({
'hidden.bs.dropdown': function () {
this.toggle_add_menu(false);
},
- 'click .add-custom-group a': function () {
+ 'click .o_add_custom_group a': function (event) {
+ event.preventDefault();
this.toggle_add_menu();
},
},
@@ -413,13 +413,13 @@ return Widget.extend({
this.searchview = parent;
},
start: function () {
- this.$menu = this.$('.group-by-menu');
+ this.$menu = this.$('.o_group_by_menu');
var divider = this.$menu.find('.divider');
_.invoke(this.groups, 'insertBefore', divider);
if (this.groups.length) {
divider.show();
}
- this.$add_group = this.$menu.find('.add-custom-group');
+ this.$add_group = this.$menu.find('.o_add_custom_group');
},
get_fields: function () {
var self = this;
@@ -435,9 +435,9 @@ return Widget.extend({
self.groupable_fields = _.sortBy(filter_group_field, 'string');
self.$menu.append(QWeb.render('GroupByMenuSelector', self));
- self.$add_group_menu = self.$('.oe-add-group');
- self.$group_selector = self.$('.oe-group-selector');
- self.$('.oe-select-group').click(function () {
+ self.$add_group_menu = self.$('.o_add_group');
+ self.$group_selector = self.$('.o_group_selector');
+ self.$('.o_select_group').click(function () {
self.toggle_add_menu(false);
var field = self.$group_selector.find(':selected').data('name');
self.add_groupby_to_menu(field);
diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js
index 125b7c9c152..fa1794cdc7c 100644
--- a/addons/web/static/src/js/views/search_view.js
+++ b/addons/web/static/src/js/views/search_view.js
@@ -2,6 +2,7 @@ odoo.define('web.SearchView', function (require) {
"use strict";
var AutoComplete = require('web.AutoComplete');
+var config = require('web.config');
var core = require('web.core');
var data_manager = require('web.data_manager');
var FavoriteMenu = require('web.FavoriteMenu');
@@ -9,9 +10,9 @@ var FilterMenu = require('web.FilterMenu');
var GroupByMenu = require('web.GroupByMenu');
var pyeval = require('web.pyeval');
var search_inputs = require('web.search_inputs');
-var utils = require('web.utils');
var View = require('web.View');
var Widget = require('web.Widget');
+var _t = core._t;
var Backbone = window.Backbone;
@@ -134,125 +135,32 @@ var InputView = Widget.extend({
template: 'SearchView.InputView',
events: {
focus: function () { this.trigger('focused', this); },
- blur: function () { this.$el.text(''); this.trigger('blurred', this); },
+ blur: function () { this.$el.val(''); this.trigger('blurred', this); },
keydown: 'onKeydown',
- paste: 'onPaste',
- },
- getSelection: function () {
- this.el.normalize();
- // get Text node
- var root = this.el.childNodes[0];
- if (!root || !root.textContent) {
- // if input does not have a child node, or the child node is an
- // empty string, then the selection can only be (0, 0)
- return {start: 0, end: 0};
- }
- var range = window.getSelection().getRangeAt(0);
- // In Firefox, depending on the way text is selected (drag, double- or
- // triple-click) the range may start or end on the parent of the
- // selected text node‽ Check for this condition and fixup the range
- // note: apparently with C-a this can go even higher?
- if (range.startContainer === this.el && range.startOffset === 0) {
- range.setStart(root, 0);
- }
- if (range.endContainer === this.el && range.endOffset === 1) {
- range.setEnd(root, root.length);
- }
- utils.assert(range.startContainer === root,
- "selection should be in the input view");
- utils.assert(range.endContainer === root,
- "selection should be in the input view");
- return {
- start: range.startOffset,
- end: range.endOffset
- };
},
onKeydown: function (e) {
- this.el.normalize();
- var sel;
switch (e.which) {
- // Do not insert newline, but let it bubble so searchview can use it
- case $.ui.keyCode.ENTER:
- e.preventDefault();
- break;
+ case $.ui.keyCode.BACKSPACE:
+ if(this.$el.val() === '') {
+ var preceding = this.getParent().siblingSubview(this, -1);
+ if (preceding && (preceding instanceof FacetView)) {
+ preceding.model.destroy();
+ }
+ }
+ break;
- // FIXME: may forget content if non-empty but caret at index 0, ok?
- case $.ui.keyCode.BACKSPACE:
- sel = this.getSelection();
- if (sel.start === 0 && sel.start === sel.end) {
- e.preventDefault();
- var preceding = this.getParent().siblingSubview(this, -1);
- if (preceding && (preceding instanceof FacetView)) {
- preceding.model.destroy();
+ case $.ui.keyCode.LEFT: // Stop propagation to parent if not at beginning of input value
+ if(this.el.selectionStart > 0) {
+ e.stopPropagation();
}
- }
- break;
+ break;
- // let left/right events propagate to view if caret is at input border
- // and not a selection
- case $.ui.keyCode.LEFT:
- sel = this.getSelection();
- if (sel.start !== 0 || sel.start !== sel.end) {
- e.stopPropagation();
- }
- break;
- case $.ui.keyCode.RIGHT:
- sel = this.getSelection();
- var len = this.$el.text().length;
- if (sel.start !== len || sel.start !== sel.end) {
- e.stopPropagation();
- }
- break;
+ case $.ui.keyCode.RIGHT: // Stop propagation to parent if not at end of input value
+ if(this.el.selectionStart < this.$el.val().length) {
+ e.stopPropagation();
+ }
+ break;
}
- },
- setCursorAtEnd: function () {
- this.el.normalize();
- var sel = window.getSelection();
- sel.removeAllRanges();
- var range = document.createRange();
- // in theory, range.selectNodeContents should work here. In practice,
- // MSIE9 has issues from time to time, instead of selecting the inner
- // text node it would select the reference node instead (e.g. in demo
- // data, company news, copy across the "Company News" link + the title,
- // from about half the link to half the text, paste in search box then
- // hit the left arrow key, getSelection would blow up).
- //
- // Explicitly selecting only the inner text node (only child node
- // since we've normalized the parent) avoids the issue
- range.selectNode(this.el.childNodes[0]);
- range.collapse(false);
- sel.addRange(range);
- },
- onPaste: function () {
- this.el.normalize();
- // In MSIE and Webkit, it is possible to get various representations of
- // the clipboard data at this point e.g.
- // window.clipboardData.getData('Text') and
- // event.clipboardData.getData('text/plain') to ensure we have a plain
- // text representation of the object (and probably ensure the object is
- // pastable as well, so nobody puts an image in the search view)
- // (nb: since it's not possible to alter the content of the clipboard
- // — at least in Webkit — to ensure only textual content is available,
- // using this would require 1. getting the text data; 2. manually
- // inserting the text data into the content; and 3. cancelling the
- // paste event)
- //
- // But Firefox doesn't support the clipboard API (as of FF18)
- // although it correctly triggers the paste event (Opera does not even
- // do that) => implement lowest-denominator system where onPaste
- // triggers a followup "cleanup" pass after the data has been pasted
- setTimeout(function () {
- // Read text content (ignore pasted HTML)
- var data = this.$el.text();
- if (!data)
- return;
- // paste raw text back in
- this.$el.empty().text(data);
- this.el.normalize();
- // Set the cursor at the end of the text, so the cursor is not lost
- // in some kind of error-spawning limbo.
- this.setCursorAtEnd();
- }.bind(this), 0);
}
});
@@ -262,7 +170,7 @@ var FacetView = Widget.extend({
'focus': function () { this.trigger('focused', this); },
'blur': function () { this.trigger('blurred', this); },
'click': function (e) {
- if ($(e.target).is('.oe_facet_remove')) {
+ if ($(e.target).hasClass('o_facet_remove')) {
this.model.destroy();
return false;
}
@@ -272,10 +180,10 @@ var FacetView = Widget.extend({
'keydown': function (e) {
var keys = $.ui.keyCode;
switch (e.which) {
- case keys.BACKSPACE:
- case keys.DELETE:
- this.model.destroy();
- return false;
+ case keys.BACKSPACE:
+ case keys.DELETE:
+ this.model.destroy();
+ return false;
}
}
},
@@ -290,9 +198,12 @@ var FacetView = Widget.extend({
},
start: function () {
var self = this;
- var $e = this.$('> span:last-child');
+ var $e = this.$('.o_facet_values').last();
return $.when(this._super()).then(function () {
- return $.when.apply(null, self.model.values.map(function (value) {
+ return $.when.apply(null, self.model.values.map(function (value, index) {
+ if (index > 0) {
+ $('<span/>', {html: self.model.get('separator') || _t(" or ")}).addClass('o_facet_values_sep').appendTo($e);
+ }
return new FacetValueView(self, value).appendTo($e);
}));
});
@@ -319,49 +230,34 @@ var FacetValueView = Widget.extend({
});
var SearchView = View.extend({
- defaults: _.extend({}, View.prototype.defaults, {
- hidden: false,
- disable_filters: false,
- disable_groupby: false,
- disable_favorites: false,
- disable_custom_filters: false,
- }),
events: {
- // focus last input if view itself is clicked
- 'click': function (e) {
- if (e.target === this.$('.oe_searchview_facets')[0]) {
- this.$('.oe_searchview_input:last').focus();
- }
- },
- // search button
- 'click div.oe_searchview_search': function (e) {
- e.stopImmediatePropagation();
- this.do_search();
- },
- 'click .oe_searchview_unfold_drawer': function (e) {
- e.stopImmediatePropagation();
- $(e.target).toggleClass('fa-caret-down fa-caret-up');
+ 'click .o_searchview_more': function (e) {
+ $(e.target).toggleClass('fa-search-plus fa-search-minus');
localStorage.visible_search_menu = (localStorage.visible_search_menu !== 'true');
this.toggle_buttons();
},
- 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
+ 'keydown .o_searchview_input, .o_searchview_facet': function (e) {
switch(e.which) {
- case $.ui.keyCode.LEFT:
- this.focusPreceding(e.target);
- e.preventDefault();
- break;
- case $.ui.keyCode.RIGHT:
- if (!this.autocomplete.is_expandable()) {
- this.focusFollowing(e.target);
- }
- e.preventDefault();
- break;
+ case $.ui.keyCode.LEFT:
+ this.focusPreceding(e.target);
+ e.preventDefault();
+ break;
+ case $.ui.keyCode.RIGHT:
+ if(!this.autocomplete.is_expandable()) {
+ this.focusFollowing(e.target);
+ }
+ e.preventDefault();
+ break;
}
},
- 'autocompleteopen': function () {
- this.$el.autocomplete('widget').css('z-index', 9999);
- },
},
+ defaults: _.extend({}, View.prototype.defaults, {
+ hidden: false,
+ disable_filters: false,
+ disable_groupby: false,
+ disable_favorites: false,
+ disable_custom_filters: false,
+ }),
template: "SearchView",
/**
@@ -377,9 +273,9 @@ var SearchView = View.extend({
*/
init: function() {
this._super.apply(this, arguments);
- this.title = this.options.action && this.options.action.name;
- this.action_id = this.options.action && this.options.action.id;
this.query = undefined;
+ this.title = this.options.action && this.options.action.name;
+ this.action_id = this.options.action && this.options.action.id;
this.search_fields = [];
this.filters = [];
this.groupbys = [];
@@ -408,15 +304,14 @@ var SearchView = View.extend({
this.do_hide();
}
this.toggle_visibility(false);
- this.$facets_container = this.$('div.oe_searchview_facets');
this.setup_global_completion();
this.query = new SearchQuery()
.on('add change reset remove', this.proxy('do_search'))
.on('change', this.proxy('renderChangedFacets'))
.on('add reset remove', this.proxy('renderFacets'));
- this.$('.oe_searchview_unfold_drawer')
- .toggleClass('fa-caret-down', !this.visible_filters)
- .toggleClass('fa-caret-up', this.visible_filters);
+ this.$('.o_searchview_more')
+ .toggleClass('fa-search-minus', this.visible_filters)
+ .toggleClass('fa-search-plus', !this.visible_filters);
var menu_defs = [];
this.prepare_search_inputs();
if (this.$buttons) {
@@ -516,8 +411,8 @@ var SearchView = View.extend({
if (this.$buttons) {
this.$buttons.toggle(!this.headless && is_visible && this.visible_filters);
}
- if (!this.headless && is_visible) {
- this.$('div.oe_searchview_input').last().focus();
+ if (!config.device.touch && config.device.size_class >= config.device.SIZES.SM) {
+ this.$('input').focus();
}
},
toggle_buttons: function (is_visible) {
@@ -535,7 +430,7 @@ var SearchView = View.extend({
source: this.proxy('complete_global_search'),
select: this.proxy('select_completion'),
get_search_string: function () {
- return self.$('div.oe_searchview_input').text();
+ return self.$('.o_searchview_input').val().trim();
},
});
this.autocomplete.appendTo(this.$el);
@@ -569,10 +464,11 @@ var SearchView = View.extend({
*/
select_completion: function (e, ui) {
e.preventDefault();
- var input_index = _(this.input_subviews).indexOf(
- this.subviewForRoot(
- this.$('div.oe_searchview_input:focus')[0]));
- this.query.add(ui.item.facet, {at: input_index / 2});
+ if(ui.item.facet.values && ui.item.facet.values.length && String(ui.item.facet.values[0].value).trim() !== "") {
+ this.query.add(ui.item.facet);
+ } else {
+ this.query.trigger('add');
+ }
},
subviewForRoot: function (subview_root) {
return _(this.input_subviews).detect(function (subview) {
@@ -609,34 +505,21 @@ var SearchView = View.extend({
_.invoke(this.input_subviews, 'destroy');
this.input_subviews = [];
- var i = new InputView(this);
- started.push(i.appendTo(this.$facets_container));
- this.input_subviews.push(i);
this.query.each(function (facet) {
var f = new FacetView(this, facet);
- started.push(f.appendTo(self.$facets_container));
+ started.push(f.appendTo(self.$el));
self.input_subviews.push(f);
-
- var i = new InputView(this);
- started.push(i.appendTo(self.$facets_container));
- self.input_subviews.push(i);
}, this);
+ var i = new InputView(this);
+ started.push(i.appendTo(self.$el));
+ self.input_subviews.push(i);
_.each(this.input_subviews, function (childView) {
childView.on('focused', self, self.proxy('childFocused'));
childView.on('blurred', self, self.proxy('childBlurred'));
});
$.when.apply(null, started).then(function () {
- if (options && options.focus_input === false) return;
- var input_to_focus;
- // options.at: facet inserted at given index, focus next input
- // otherwise just focus last input
- if (!options || typeof options.at !== 'number') {
- input_to_focus = _.last(self.input_subviews);
- } else {
- input_to_focus = self.input_subviews[(options.at + 1) * 2];
- }
- input_to_focus.$el.focus();
+ _.last(self.input_subviews).$el.focus();
});
},
childFocused: function () {
@@ -726,230 +609,3 @@ _.extend(SearchView, {
return SearchView;
});
-
-odoo.define('web.AutoComplete', function (require) {
-"use strict";
-
-var Widget = require('web.Widget');
-
-return Widget.extend({
- template: "SearchView.autocomplete",
-
- // Parameters for autocomplete constructor:
- //
- // parent: this is used to detect keyboard events
- //
- // options.source: function ({term:query}, callback). This function will be called to
- // obtain the search results corresponding to the query string. It is assumed that
- // options.source will call callback with the results.
- // options.select: function (ev, {item: {facet:facet}}). Autocomplete widget will call
- // that function when a selection is made by the user
- // options.get_search_string: function (). This function will be called by autocomplete
- // to obtain the current search string.
- init: function (parent, options) {
- this._super(parent);
- this.$input = parent.$el;
- this.source = options.source;
- this.select = options.select;
- this.get_search_string = options.get_search_string;
-
- this.current_result = null;
-
- this.searching = true;
- this.search_string = '';
- this.current_search = null;
- },
- start: function () {
- var self = this;
- this.$input.on('keyup', function (ev) {
- if (ev.which === $.ui.keyCode.RIGHT) {
- self.searching = true;
- ev.preventDefault();
- return;
- }
- if (ev.which === $.ui.keyCode.ENTER) {
- if (self.search_string.length) {
- self.select_item(ev);
- }
- return;
- }
- var search_string = self.get_search_string();
- if (self.search_string !== search_string) {
- if (search_string.length) {
- self.search_string = search_string;
- self.initiate_search(search_string);
- } else {
- self.close();
- }
- }
- });
- this.$input.on('keypress', function (ev) {
- self.search_string = self.search_string + String.fromCharCode(ev.which);
- if (self.search_string.length) {
- self.searching = true;
- var search_string = self.search_string;
- self.initiate_search(search_string);
- } else {
- self.close();
- }
- });
- this.$input.on('keydown', function (ev) {
- switch (ev.which) {
- case $.ui.keyCode.ENTER:
-
- // TAB and direction keys are handled at KeyDown because KeyUp
- // is not guaranteed to fire.
- // See e.g. https://github.com/aef-/jquery.masterblaster/issues/13
- case $.ui.keyCode.TAB:
- if (self.search_string.length) {
- self.select_item(ev);
- }
- break;
- case $.ui.keyCode.DOWN:
- self.move('down');
- self.searching = false;
- ev.preventDefault();
- break;
- case $.ui.keyCode.UP:
- self.move('up');
- self.searching = false;
- ev.preventDefault();
- break;
- case $.ui.keyCode.RIGHT:
- self.searching = false;
- var current = self.current_result;
- if (current && current.expand && !current.expanded) {
- self.expand();
- self.searching = true;
- }
- ev.preventDefault();
- break;
- case $.ui.keyCode.ESCAPE:
- self.close();
- self.searching = false;
- break;
- }
- });
- },
- initiate_search: function (query) {
- if (query === this.search_string && query !== this.current_search) {
- this.search(query);
- }
- },
- search: function (query) {
- var self = this;
- this.current_search = query;
- this.source({term:query}, function (results) {
- if (results.length) {
- self.render_search_results(results);
- self.focus_element(self.$('li:first-child'));
- } else {
- self.close();
- }
- });
- },
- render_search_results: function (results) {
- var self = this;
- var $list = this.$('ul');
- $list.empty();
- var render_separator = false;
- results.forEach(function (result) {
- if (result.is_separator) {
- if (render_separator)
- $list.append($('<li>').addClass('oe-separator'));
- render_separator = false;
- } else {
- var $item = self.make_list_item(result).appendTo($list);
- result.$el = $item;
- render_separator = true;
- }
- });
- this.show();
- },
- make_list_item: function (result) {
- var self = this;
- var $li = $('<li>')
- .hover(function () {self.focus_element($li);})
- .mousedown(function (ev) {
- if (ev.button === 0) { // left button
- self.select(ev, {item: {facet: result.facet}});
- self.close();
- } else {
- ev.preventDefault();
- }
- })
- .data('result', result);
- if (result.expand) {
- var $expand = $('<span class="oe-expand">').text('▶').appendTo($li);
- $expand.mousedown(function (ev) {
- ev.preventDefault();
- ev.stopPropagation();
- if (result.expanded)
- self.fold();
- else
- self.expand();
- });
- result.expanded = false;
- }
- if (result.indent) $li.addClass('oe-indent');
- $li.append($('<span>').html(result.label));
- return $li;
- },
- expand: function () {
- var self = this;
- var current_result = this.current_result;
- current_result.expand(this.get_search_string()).then(function (results) {
- (results || [{label: '(no result)'}]).reverse().forEach(function (result) {
- result.indent = true;
- var $li = self.make_list_item(result);
- current_result.$el.after($li);
- });
- current_result.expanded = true;
- current_result.$el.find('span.oe-expand').html('▼');
- });
- },
- fold: function () {
- var $next = this.current_result.$el.next();
- while ($next.hasClass('oe-indent')) {
- $next.remove();
- $next = this.current_result.$el.next();
- }
- this.current_result.expanded = false;
- this.current_result.$el.find('span.oe-expand').html('▶');
- },
- focus_element: function ($li) {
- this.$('li').removeClass('oe-selection-focus');
- $li.addClass('oe-selection-focus');
- this.current_result = $li.data('result');
- },
- select_item: function (ev) {
- if (this.current_result.facet) {
- this.select(ev, {item: {facet: this.current_result.facet}});
- this.close();
- }
- },
- show: function () {
- this.$el.show();
- },
- close: function () {
- this.current_search = null;
- this.search_string = '';
- this.searching = true;
- this.$el.hide();
- },
- move: function (direction) {
- var $next;
- if (direction === 'down') {
- $next = this.$('li.oe-selection-focus').nextAll(':not(.oe-separator)').first();
- if (!$next.length) $next = this.$('li:first-child');
- } else {
- $next = this.$('li.oe-selection-focus').prevAll(':not(.oe-separator)').first();
- if (!$next.length) $next = this.$('li:not(.oe-separator)').last();
- }
- this.focus_element($next);
- },
- is_expandable: function () {
- return !!this.$('.oe-selection-focus .oe-expand').length;
- },
-});
-});
diff --git a/addons/web/static/src/js/views/tree_view.js b/addons/web/static/src/js/views/tree_view.js
index 6246a1494f5..aeb206257fe 100644
--- a/addons/web/static/src/js/views/tree_view.js
+++ b/addons/web/static/src/js/views/tree_view.js
@@ -87,7 +87,7 @@ var TreeView = View.extend({
return;
}
- var $select = self.$el.find('select')
+ var $select = self.$('select')
.change(function () {
var $option = $(this).find(':selected');
self.getdata($option.val(), $option.data('children'));
@@ -155,8 +155,8 @@ var TreeView = View.extend({
children_ids = record[self.children_field];
_(children_ids).each(function(childid) {
- if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').length ) {
- if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').is(':hidden')) {
+ if (self.$('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').length ) {
+ if (self.$('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').is(':hidden')) {
is_loaded = -1;
} else {
is_loaded++;
@@ -164,7 +164,7 @@ var TreeView = View.extend({
}
});
if (is_loaded === 0) {
- if (!$this.parent().hasClass('oe_open')) {
+ if (!$this.parent().hasClass('o_open')) {
self.getdata(record_id, children_ids);
}
} else {
@@ -180,7 +180,7 @@ var TreeView = View.extend({
_(records).each(function (record) {
self.records[record.id] = record;
});
- var $curr_node = self.$el.find('#treerow_' + id);
+ var $curr_node = self.$('#treerow_' + id);
var children_rows = QWeb.render('TreeView.rows', {
'records': records,
'children_field': self.children_field,
@@ -192,10 +192,10 @@ var TreeView = View.extend({
'row_parent_id': id
});
if ($curr_node.length) {
- $curr_node.addClass('oe_open');
+ $curr_node.addClass('o_open');
$curr_node.after(children_rows);
} else {
- self.$el.find('tbody').html(children_rows);
+ self.$('tbody').html(children_rows);
}
});
},
@@ -229,11 +229,11 @@ var TreeView = View.extend({
// show & hide the contents
showcontent: function (curnode,record_id, show) {
- curnode.parent('tr').toggleClass('oe_open', show);
+ curnode.parent('tr').toggleClass('o_open', show);
_(this.records[record_id][this.children_field]).each(function (child_id) {
- var $child_row = this.$el.find('[id=treerow_' + child_id + '][data-row-parent-id='+ curnode.data('id') +']');
- if ($child_row.hasClass('oe_open')) {
- $child_row.toggleClass('oe_open',show);
+ var $child_row = this.$('[id=treerow_' + child_id + '][data-row-parent-id='+ curnode.data('id') +']');
+ if ($child_row.hasClass('o_open')) {
+ $child_row.toggleClass('o_open',show);
this.showcontent($child_row, child_id, false);
}
$child_row.toggle(show);
diff --git a/addons/web/static/src/js/web_client.js b/addons/web/static/src/js/web_client.js
index 640449d74b3..213aa70eed4 100644
--- a/addons/web/static/src/js/web_client.js
+++ b/addons/web/static/src/js/web_client.js
@@ -1,177 +1,26 @@
odoo.define('web.WebClient', function (require) {
"use strict";
-var ActionManager = require('web.ActionManager');
+var AbstractWebClient = require('web.AbstractWebClient');
var core = require('web.core');
-var crash_manager = require('web.crash_manager');
var data = require('web.data');
var data_manager = require('web.data_manager');
var framework = require('web.framework');
-var Loading = require('web.Loading');
var Menu = require('web.Menu');
var Model = require('web.DataModel');
-var NotificationManager = require('web.notification').NotificationManager;
var session = require('web.session');
var SystrayMenu = require('web.SystrayMenu');
var UserMenu = require('web.UserMenu');
-var utils = require('web.utils');
-var Widget = require('web.Widget');
-var _t = core._t;
-
-var WebClient = Widget.extend({
+return AbstractWebClient.extend({
events: {
'click .oe_logo_edit_admin': 'logo_edit',
- 'click .oe_logo img': 'on_logo_click',
- },
- custom_events: {
- 'notification': function (e) {
- if(this.notification_manager) {
- this.notification_manager.notify(e.data.title, e.data.message, e.data.sticky);
- }
- },
- 'warning': function (e) {
- if(this.notification_manager) {
- this.notification_manager.warn(e.data.title, e.data.message, e.data.sticky);
- }
- },
- },
-
- init: function(parent, client_options) {
- this.client_options = {};
- this._super(parent);
- this.origin = undefined;
- if (client_options) {
- _.extend(this.client_options, client_options);
- }
- this._current_state = null;
- this.menu_dm = new utils.DropMisordered();
- this.action_mutex = new utils.Mutex();
- this.set('title_part', {"zopenerp": "Odoo"});
- },
- start: function() {
- var self = this;
- this.on("change:title_part", this, this._title_changed);
- this._title_changed();
-
- core.bus.on('web_client_toggle_bars', this, function () {
- this.toggle_bars.apply(this, arguments);
- });
-
- document.body.classList.add('o_web_client');
- return session.session_bind(this.origin).then(function() {
- self.bind_events();
- return self.show_common();
- }).then(function() {
- if (session.session_is_valid()) {
- self.show_application();
- }
- if (self.client_options.action) {
- self.action_manager.do_action(self.client_options.action);
- delete(self.client_options.action);
- }
- core.bus.trigger('web_client_ready');
- });
- },
- bind_events: function() {
- var self = this;
- $('.oe_systray').show();
- this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function() {
- $(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
- });
- this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
+ 'click .oe_logo img': function(ev) {
ev.preventDefault();
- var $toggle = $(this);
- var doc_width = $(document).width();
- var $menu = $toggle.siblings('.oe_dropdown_menu');
- $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
- var state = $menu.is('.oe_opened');
- setTimeout(function() {
- // Do not alter propagation
- $toggle.add($menu).toggleClass('oe_opened', !state);
- if (!state) {
- // Move $menu if outside window's edge
- var offset = $menu.offset();
- var menu_width = $menu.width();
- var x = doc_width - offset.left - menu_width - 2;
- if (x < 0) {
- $menu.offset({ left: offset.left + x }).width(menu_width);
- }
- }
- }, 0);
- });
- core.bus.on('click', this, function(ev) {
- $('.tooltip').remove();
- if (!$(ev.target).is('input[type=file]')) {
- self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened');
- }
- });
- core.bus.on('set_full_screen', this, function (full_screen) {
- this.set_content_full_screen(full_screen);
- });
- },
- on_logo_click: function(ev) {
- ev.preventDefault();
- return this.clear_uncommitted_changes().then(function() {
- framework.redirect("/web" + (core.debug ? "?debug" : ""));
- });
- },
- show_common: function() {
- var self = this;
- session.on('error', crash_manager, crash_manager.rpc_error);
- self.notification_manager = new NotificationManager(this);
- self.notification_manager.appendTo(self.$('.openerp'));
- self.loading = new Loading(self);
- self.loading.appendTo(self.$('.openerp_webclient_container'));
- self.action_manager = new ActionManager(self);
- self.action_manager.replace(self.$('.oe_application'));
-
- window.onerror = function (message, file, line, col, error) {
- var traceback = error ? error.stack : '';
- crash_manager.show_error({
- type: _t("Client Error"),
- message: message,
- data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback}
+ return this.clear_uncommitted_changes().then(function() {
+ framework.redirect("/web" + (core.debug ? "?debug" : ""));
});
- };
-
- },
- toggle_bars: function(value) {
- this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
- },
- clear_uncommitted_changes: function() {
- var def = $.Deferred().resolve();
- core.bus.trigger('clear_uncommitted_changes', function chain_callbacks(callback) {
- def = def.then(callback);
- });
- return def;
- },
- /**
- Sets the first part of the title of the window, dedicated to the current action.
- */
- set_title: function(title) {
- this.set_title_part("action", title);
- },
- /**
- Sets an arbitrary part of the title of the window. Title parts are identified by strings. Each time
- a title part is changed, all parts are gathered, ordered by alphabetical order and displayed in the
- title of the window separated by '-'.
- */
- set_title_part: function(part, title) {
- var tmp = _.clone(this.get("title_part"));
- tmp[part] = title;
- this.set("title_part", tmp);
- },
- _title_changed: function() {
- var parts = _.sortBy(_.keys(this.get("title_part")), function(x) { return x; });
- var tmp = "";
- _.each(parts, function(part) {
- var str = this.get("title_part")[part];
- if (str) {
- tmp = tmp ? tmp + " - " + str : str;
- }
- }, this);
- document.title = tmp;
+ },
},
show_application: function() {
var self = this;
@@ -186,9 +35,8 @@ var WebClient = Widget.extend({
// Create the user menu (rendered client-side)
self.user_menu = new UserMenu(self);
- var user_menu_loaded = self.user_menu.appendTo(this.$el.parents().find('.oe_user_menu_placeholder'));
- self.user_menu.on('user_logout', self, self.on_logout);
- self.user_menu.do_update();
+ var $user_menu_placeholder = $('body').find('.oe_user_menu_placeholder').show();
+ var user_menu_loaded = self.user_menu.appendTo($user_menu_placeholder);
// Create the systray menu (rendered server-side)
self.systray_menu = new SystrayMenu(self);
@@ -208,6 +56,9 @@ var WebClient = Widget.extend({
delete(self.client_options.action_post_login);
}
},
+ toggle_bars: function(value) {
+ this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
+ },
update_logo: function() {
var company = session.company_id;
var img = session.url('/web/binary/company_logo' + '?db=' + session.db + (company ? '&company=' + company : ''));
@@ -237,37 +88,12 @@ var WebClient = Widget.extend({
});
return false;
},
- /**
- * When do_action is performed on the WebClient, forward it to the main ActionManager
- * This allows to widgets that are not inside the ActionManager to perform do_action
- */
- do_action: function() {
- return this.action_manager.do_action.apply(this, arguments);
- },
- destroy_content: function() {
- _.each(_.clone(this.getChildren()), function(el) {
- el.destroy();
- });
- this.$el.children().remove();
- },
- do_reload: function() {
- var self = this;
- return this.session.session_reload().then(function () {
- session.load_modules(true).then(
- self.menu.proxy('do_reload')); });
- },
- on_logout: function() {
- var self = this;
- this.clear_uncommitted_changes().then(function() {
- self.action_manager.do_action('logout');
- });
- },
bind_hashchange: function() {
var self = this;
$(window).bind('hashchange', this.on_hashchange);
var state = $.bbq.getState(true);
- if (_.isEmpty(state) || state.action == "login") {
+ if (_.isEmpty(state) || state.action === "login") {
self.menu.is_bound.done(function() {
new Model("res.users").call("read", [session.uid, ["action_id"]]).done(function(data) {
if(data.action_id) {
@@ -277,7 +103,8 @@ var WebClient = Widget.extend({
var first_menu_id = self.menu.$el.find("a:first").data("menu");
if(first_menu_id) {
self.menu.menu_click(first_menu_id);
- } }
+ }
+ }
});
});
} else {
@@ -295,19 +122,16 @@ var WebClient = Widget.extend({
});
} else {
state._push_me = false; // no need to push state back...
- this.action_manager.do_load_state(state, !!this._current_state);
+ this.action_manager.do_load_state(state, !!this._current_state).then(function () {
+ var action = self.action_manager.get_inner_action();
+ if (action) {
+ self.menu.open_action(action.action_descr.id);
+ }
+ });
}
}
this._current_state = stringstate;
},
- do_push_state: function(state) {
- this.set_title(state.title);
- delete state.title;
- var url = '#' + $.param(state);
- this._current_state = $.deparam($.param(state), false); // stringify all values
- $.bbq.pushState(url);
- this.trigger('state_pushed', state);
- },
on_menu_action: function(options) {
var self = this;
return this.menu_dm.add(data_manager.load_action(options.action_id))
@@ -337,13 +161,6 @@ var WebClient = Widget.extend({
});
});
},
- set_content_full_screen: function(fullscreen) {
- $(document.body).css('overflow-y', fullscreen ? 'hidden' : 'scroll');
- this.$('.oe_webclient').toggleClass(
- 'oe_content_full_screen', fullscreen);
- },
});
-return WebClient;
-
});
diff --git a/addons/web/static/src/js/widgets/auto_complete.js b/addons/web/static/src/js/widgets/auto_complete.js
new file mode 100644
index 00000000000..883206ae4c2
--- /dev/null
+++ b/addons/web/static/src/js/widgets/auto_complete.js
@@ -0,0 +1,220 @@
+odoo.define('web.AutoComplete', function (require) {
+"use strict";
+
+var Widget = require('web.Widget');
+
+return Widget.extend({
+ template: "SearchView.autocomplete",
+
+ // Parameters for autocomplete constructor:
+ //
+ // parent: this is used to detect keyboard events
+ //
+ // options.source: function ({term:query}, callback). This function will be called to
+ // obtain the search results corresponding to the query string. It is assumed that
+ // options.source will call callback with the results.
+ // options.select: function (ev, {item: {facet:facet}}). Autocomplete widget will call
+ // that function when a selection is made by the user
+ // options.get_search_string: function (). This function will be called by autocomplete
+ // to obtain the current search string.
+ init: function (parent, options) {
+ this._super(parent);
+ this.$input = parent.$el;
+ this.source = options.source;
+ this.select = options.select;
+ this.get_search_string = options.get_search_string;
+
+ this.current_result = null;
+
+ this.searching = true;
+ this.search_string = '';
+ this.current_search = null;
+ },
+ start: function () {
+ var self = this;
+ this.$input.on('keyup', function (ev) {
+ if (ev.which === $.ui.keyCode.RIGHT) {
+ self.searching = true;
+ ev.preventDefault();
+ return;
+ }
+ if (ev.which === $.ui.keyCode.ENTER) {
+ if (self.search_string.length) {
+ self.select_item(ev);
+ }
+ return;
+ }
+ var search_string = self.get_search_string();
+ if (self.search_string !== search_string) {
+ if (search_string.length) {
+ self.search_string = search_string;
+ self.initiate_search(search_string);
+ } else {
+ self.close();
+ }
+ }
+ });
+ this.$input.on('keypress', function (ev) {
+ self.search_string = self.search_string + String.fromCharCode(ev.which);
+ if (self.search_string.length) {
+ self.searching = true;
+ var search_string = self.search_string;
+ self.initiate_search(search_string);
+ } else {
+ self.close();
+ }
+ });
+ this.$input.on('keydown', function (ev) {
+ switch (ev.which) {
+ case $.ui.keyCode.ENTER:
+
+ // TAB and direction keys are handled at KeyDown because KeyUp
+ // is not guaranteed to fire.
+ // See e.g. https://github.com/aef-/jquery.masterblaster/issues/13
+ case $.ui.keyCode.TAB:
+ if (self.search_string.length) {
+ self.select_item(ev);
+ }
+ break;
+ case $.ui.keyCode.DOWN:
+ self.move('down');
+ self.searching = false;
+ ev.preventDefault();
+ break;
+ case $.ui.keyCode.UP:
+ self.move('up');
+ self.searching = false;
+ ev.preventDefault();
+ break;
+ case $.ui.keyCode.RIGHT:
+ self.searching = false;
+ var current = self.current_result;
+ if (current && current.expand && !current.expanded) {
+ self.expand();
+ self.searching = true;
+ }
+ ev.preventDefault();
+ break;
+ case $.ui.keyCode.ESCAPE:
+ self.close();
+ self.searching = false;
+ break;
+ }
+ });
+ },
+ initiate_search: function (query) {
+ if (query === this.search_string && query !== this.current_search) {
+ this.search(query);
+ }
+ },
+ search: function (query) {
+ var self = this;
+ this.current_search = query;
+ this.source({term:query}, function (results) {
+ if (results.length) {
+ self.render_search_results(results);
+ self.focus_element(self.$('li:first-child'));
+ } else {
+ self.close();
+ }
+ });
+ },
+ render_search_results: function (results) {
+ var self = this;
+ var $list = this.$el;
+ $list.empty();
+ results.forEach(function (result) {
+ var $item = self.make_list_item(result).appendTo($list);
+ result.$el = $item;
+ });
+ this.show();
+ },
+ make_list_item: function (result) {
+ var self = this;
+ var $li = $('<li>')
+ .hover(function () {self.focus_element($li);})
+ .mousedown(function (ev) {
+ if (ev.button === 0) { // left button
+ self.select(ev, {item: {facet: result.facet}});
+ self.close();
+ }
+ })
+ .data('result', result);
+ if (result.expand) {
+ var $expand = $('<a class="o-expand" href="#">').appendTo($li);
+ $expand.mousedown(function (ev) {
+ ev.stopPropagation();
+ ev.preventDefault(); // to prevent dropdown from closing
+ if (result.expanded) {
+ self.fold();
+ } else {
+ self.expand();
+ }
+ });
+ $expand.click(function(ev) {
+ ev.preventDefault(); // to prevent url from changing due to href="#"
+ });
+ result.expanded = false;
+ }
+ if (result.indent) $li.addClass('o-indent');
+ $li.append($('<a href="#">').html(result.label));
+ return $li;
+ },
+ expand: function () {
+ var self = this;
+ var current_result = this.current_result;
+ current_result.expand(this.get_search_string()).then(function (results) {
+ (results || [{label: '(no result)'}]).reverse().forEach(function (result) {
+ result.indent = true;
+ var $li = self.make_list_item(result);
+ current_result.$el.after($li);
+ });
+ self.current_result.expanded = true;
+ self.current_result.$el.find('a.o-expand').removeClass('o-expand').addClass('o-expanded');
+ });
+ },
+ fold: function () {
+ var $next = this.current_result.$el.next();
+ while ($next.hasClass('o-indent')) {
+ $next.remove();
+ $next = this.current_result.$el.next();
+ }
+ this.current_result.expanded = false;
+ this.current_result.$el.find('a.o-expanded').removeClass('o-expanded').addClass('o-expand');
+ },
+ focus_element: function ($li) {
+ this.$('li').removeClass('o-selection-focus');
+ $li.addClass('o-selection-focus');
+ this.current_result = $li.data('result');
+ },
+ select_item: function (ev) {
+ if (this.current_result.facet) {
+ this.select(ev, {item: {facet: this.current_result.facet}});
+ this.close();
+ }
+ },
+ show: function () {
+ this.$el.show();
+ },
+ close: function () {
+ this.current_search = null;
+ this.search_string = '';
+ this.searching = true;
+ this.$el.hide();
+ },
+ move: function (direction) {
+ var $next;
+ if (direction === 'down') {
+ $next = this.$('li.o-selection-focus').next();
+ if (!$next.length) $next = this.$('li').first();
+ } else {
+ $next = this.$('li.o-selection-focus').prev();
+ if (!$next.length) $next = this.$('li').last();
+ }
+ this.focus_element($next);
+ },
+ is_expandable: function () {
+ return !!this.$('.o-selection-focus .o-expand').length;
+ },
+});
+});
diff --git a/addons/web/static/src/js/widgets/change_password.js b/addons/web/static/src/js/widgets/change_password.js
index 658c7ee8e7d..367cf42e680 100644
--- a/addons/web/static/src/js/widgets/change_password.js
+++ b/addons/web/static/src/js/widgets/change_password.js
@@ -13,7 +13,7 @@ var ChangePassword = Widget.extend({ // FIXME
start: function() {
var self = this;
web_client.set_title(_t("Change Password"));
- var $button = self.$el.find('.oe_form_button');
+ var $button = self.$('.oe_form_button');
$button.appendTo(this.getParent().$footer);
$button.eq(1).click(function(){
self.$el.parents('.modal').modal('hide');
diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js
index a73020ce9b2..4b87d45ff4a 100644
--- a/addons/web/static/src/js/widgets/data_export.js
+++ b/addons/web/static/src/js/widgets/data_export.js
@@ -11,212 +11,326 @@ var QWeb = core.qweb;
var _t = core._t;
var DataExport = Dialog.extend({
- template: 'ExportTreeView',
+ template: 'ExportDialog',
events: {
- 'click #add_field': function () {
+ 'click .o_expand_parent': function(e) {
+ this.on_expand_action(this.records[$(e.target).closest('.o_export_tree_item').data('id')]);
+ },
+ 'click .o_export_tree_item': function(e) {
+ e.stopPropagation();
+ var $elem = $(e.currentTarget);
+
+ var row_index = $elem.prevAll('.o_export_tree_item').length;
+ var row_index_level = $elem.parents('.o_export_tree_item').length;
+
+ if(e.shiftKey && row_index_level === this.row_index_level) {
+ var minIndex = Math.min(row_index, this.row_index);
+ var maxIndex = Math.max(row_index, this.row_index);
+
+ this.$records.filter(function() { return ($elem.parent()[0] === $(this).parent()[0]); })
+ .slice(minIndex, maxIndex+1)
+ .addClass('o_selected')
+ .filter(':not(:last)')
+ .each(process_children);
+ }
+
+ this.row_index = row_index;
+ this.row_index_level = row_index_level;
+
+ if(e.ctrlKey) {
+ $elem.toggleClass('o_selected').focus();
+ } else if(e.shiftKey) {
+ $elem.addClass('o_selected').focus();
+ } else {
+ this.$(".o_selected").removeClass("o_selected")
+ $elem.addClass("o_selected").focus();
+ }
+
+ function process_children() {
+ var $this = $(this);
+ if($this.hasClass('open')) {
+ $this.children('.o_export_tree_item')
+ .addClass('o_selected')
+ .each(process_children);
+ }
+ }
+ },
+ 'dblclick .o_export_tree_item': function(e) {
+ var $elem = $(e.currentTarget);
+ $elem.removeClass('o_selected');
+ this.add_field($elem.data('id'), $elem.find('.o_tree_column').first().text());
+ },
+ 'keydown .o_export_tree_item': function(e) {
+ e.stopPropagation();
+ var $elem = $(e.currentTarget);
+ var record = this.records[$elem.data('id')];
+
+ switch(e.keyCode || e.which) {
+ case $.ui.keyCode.LEFT:
+ if ($elem.hasClass('open')) {
+ this.on_expand_action(record);
+ }
+ break;
+ case $.ui.keyCode.RIGHT:
+ if (!$elem.hasClass('open')) {
+ this.on_expand_action(record);
+ }
+ break;
+ case $.ui.keyCode.UP:
+ var $prev = $elem.prev('.o_export_tree_item');
+ if($prev.length === 1) {
+ while($prev.hasClass('open')) {
+ $prev = $prev.children('.o_export_tree_item').last();
+ }
+ } else {
+ $prev = $elem.parent('.o_export_tree_item');
+ if($prev.length === 0) {
+ break;
+ }
+ }
+
+ $elem.removeClass("o_selected").blur();
+ $prev.addClass("o_selected").focus();
+ break;
+ case $.ui.keyCode.DOWN:
+ var $next;
+ if($elem.hasClass('open')) {
+ $next = $elem.children('.o_export_tree_item').first();
+ } else {
+ $next = $elem.next('.o_export_tree_item');
+ if($next.length === 0) {
+ $next = $elem.parent('.o_export_tree_item').next('.o_export_tree_item');
+ if($next.length === 0) {
+ break;
+ }
+ }
+ }
+
+ $elem.removeClass("o_selected").blur();
+ $next.addClass("o_selected").focus();
+ break;
+ }
+ },
+
+ 'click .o_add_field': function() {
var self = this;
- this.$('#field-tree-structure tr.ui-selected')
- .removeClass('ui-selected')
- .find('a').each(function () {
- var id = $(this).attr('id').split('-')[1];
- var string = $(this).attr('string');
- self.add_field(id, string);
+ this.$('.o_field_tree_structure .o_selected')
+ .removeClass('o_selected')
+ .each(function() {
+ var $this = $(this);
+ self.add_field($this.data('id'), $this.children('.o_tree_column').text());
});
},
- 'click #remove_field': function () {
- this.$('#fields_list option:selected').remove();
+ 'click .o_remove_field': function() {
+ this.$fields_list.find('option:selected').remove();
+ },
+ 'click .o_remove_all_field': function() {
+ this.$fields_list.empty();
+ },
+ 'click .o_move_up': function() {
+ var $selected_rows = this.$fields_list.find('option:selected');
+
+ var $prev_row = $selected_rows.first().prev();
+ if($prev_row.length){
+ $prev_row.before($selected_rows.detach());
+ }
+ },
+ 'click .o_move_down': function () {
+ var $selected_rows = this.$fields_list.find('option:selected');
+
+ var $next_row = $selected_rows.last().next();
+ if($next_row.length){
+ $next_row.after($selected_rows.detach());
+ }
},
- 'click #remove_all_field': function () {
- this.$('#fields_list').empty();
+
+ 'click .o_toggle_save_list': function(e) {
+ e.preventDefault();
+
+ var $saveList = this.$(".o_save_list");
+ if($saveList.is(':empty')) {
+ $saveList.append(QWeb.render('Export.SaveList'));
+ } else {
+ if($saveList.is(':hidden')) {
+ $saveList.show();
+ $saveList.find(".o_export_list_input").val("");
+ } else {
+ $saveList.hide();
+ }
+ }
+ },
+ 'click .o_save_list > button': function(e) {
+ var $saveList = this.$(".o_save_list");
+
+ var value = $saveList.find("input").val();
+ if(!value) {
+ Dialog.alert(this, _t("Please enter save field list name"));
+ return;
+ }
+
+ var fields = this.get_fields();
+ if (fields.length === 0) {
+ return;
+ }
+
+ $saveList.hide();
+
+ var self = this;
+ this.exports.create({
+ name: value,
+ resource: this.dataset.model,
+ export_fields: _.map(fields, function (field) {
+ return [0, 0, {name: field}];
+ }),
+ }).then(function(export_list_id) {
+ if(!export_list_id) {
+ return;
+ }
+ var $select = self.$(".o_exported_lists_select");
+ if($select.length === 0 || $select.is(":hidden")) {
+ self.show_exports_list();
+ }
+ $select.append(new Option(value, export_list_id));
+ });
},
- 'click #export_new_list': 'on_show_save_list',
- 'click #move_up':'on_click_move_up',
- 'click #move_down':'on_click_move_down',
},
init: function(parent, dataset) {
- var self = this;
var options = {
+ title: _t("Export Data"),
buttons: [
- {text: _t("Close"), click: function () { self.$el.parents('.modal').modal('hide'); }},
- {text: _t("Export To File"), click: function () { self.on_click_export_data(); }}
+ {text: _t("Export To File"), click: this.export_data, classes: "btn-primary"},
+ {text: _t("Close"), close: true},
],
- close: function () { self.close();}
};
this._super(parent, options);
this.records = {};
this.dataset = dataset;
- this.exports = new data.DataSetSearch(
- this, 'ir.exports', this.dataset.get_context());
- this.set_title(_t("Export Data"));
+ this.exports = new data.DataSetSearch(this, 'ir.exports', this.dataset.get_context());
+
+ this.row_index = 0;
+ this.row_index_level = 0;
},
start: function() {
var self = this;
- this._super.apply(this, arguments);
+ var waitFor = [this._super.apply(this, arguments)];
+
+ this.$fields_list = this.$('.o_fields_list');
+ this.$import_compat_radios = this.$('.o_import_compat input');
+
+ waitFor.push(this.rpc('/web/export/formats', {}).then(do_setup_export_formats));
var got_fields = new $.Deferred();
- this.$el.find('#import_compat').change(function() {
- self.$el.find('#fields_list').empty();
- self.$el.find('#field-tree-structure').remove();
- var import_comp = self.$el.find("#import_compat").val();
+ this.$import_compat_radios.change(function(e) {
+ self.$fields_list.empty();
+ self.$('.o_field_tree_structure').remove();
+
self.rpc("/web/export/get_fields", {
model: self.dataset.model,
- import_compat: !!import_comp,
+ import_compat: !!$(e.target).val(),
}).done(function (records) {
got_fields.resolve();
self.on_show_data(records);
});
- }).change();
+ }).eq(0).change();
+ waitFor.push(got_fields);
- var got_domain = this.getParent().get_active_domain().then(function (domain) {
+ waitFor.push(this.getParent().get_active_domain().then(function (domain) {
if (domain === undefined) {
self.ids_to_export = self.getParent().get_selected_ids();
self.domain = self.dataset.domain;
- }
- else {
+ } else {
self.ids_to_export = false;
self.domain = domain;
}
self.on_show_domain();
- });
+ }));
- return $.when(
- got_fields,
- got_domain,
- this.rpc('/web/export/formats', {}).done(this.do_setup_export_formats),
- this.show_exports_list());
- },
- on_click_move_up: function () {
- var prev_row = this.$('#fields_list option:selected').first().prev();
- if(prev_row.length){
- var selected_rows = this.$('#fields_list option:selected').detach();
- prev_row.before(selected_rows);
- }
- },
- on_click_move_down: function () {
- var next_row = this.$('#fields_list option:selected').last().next();
- if(next_row.length){
- var selected_rows = this.$('#fields_list option:selected').detach();
- next_row.after(selected_rows);
+ waitFor.push(this.show_exports_list());
+
+ return $.when.apply($, waitFor);
+
+ function do_setup_export_formats(formats) {
+ var $fmts = self.$('.o_export_format');
+
+ _.each(formats, function(format, i) {
+ var $radio = $('<input/>', {type: 'radio', value: format.tag, name: 'o_export_format_name'});
+ var $label = $('<label/>', {html: format.label});
+
+ if (format.error) {
+ $radio.prop('disabled', true);
+ $label.html(_.str.sprintf("%s — %s", format.label, format.error));
+ }
+
+ $fmts.append($("<div/>").append($radio, $label));
+ });
+
+ self.$export_format_inputs = $fmts.find('input');
+ self.$export_format_inputs.first().prop('checked', true);
}
},
- do_setup_export_formats: function (formats) {
- var $fmts = this.$el.find('#export_format');
- _(formats).each(function (format) {
- var opt = new Option(format.label, format.tag);
- if (format.error) {
- opt.disabled = true;
- opt.replaceChild(
- document.createTextNode(
- _.str.sprintf("%s — %s", format.label, format.error)),
- opt.childNodes[0]);
- }
- $fmts.append(opt);
- });
- },
show_exports_list: function() {
- var self = this;
- if (self.$el.find('#saved_export_list').is(':hidden')) {
- self.$el.find('#ExistsExportList').show();
+ if (this.$('.o_exported_lists_select').is(':hidden')) {
+ this.$('.o_exported_lists').show();
return $.when();
}
+
+ var self = this;
return this.exports.read_slice(['name'], {
domain: [['resource', '=', this.dataset.model]]
- }).done(function (export_list) {
+ }).then(function (export_list) {
if (!export_list.length) {
return;
}
- self.$el.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
- self.$el.find('#saved_export_list').change(function() {
- self.$el.find('#fields_list option').remove();
- var export_id = self.$el.find('#saved_export_list option:selected').val();
- if (export_id) {
- self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id, 10)}).done(self.do_load_export_field);
+ self.$('.o_exported_lists').append(QWeb.render('Export.SavedList', {'existing_exports': export_list}));
+ self.$('.o_exported_lists_select').on('change', function() {
+ self.$fields_list.empty();
+ var export_id = self.$('.o_exported_lists_select option:selected').val();
+ if(export_id) {
+ self.rpc('/web/export/namelist', {
+ model: self.dataset.model,
+ export_id: parseInt(export_id, 10),
+ }).then(do_load_export_field);
}
});
- self.$el.find('#delete_export_list').click(function() {
- var select_exp = self.$el.find('#saved_export_list option:selected');
- if (select_exp.val()) {
+ self.$('.o_delete_exported_list').click(function() {
+ var select_exp = self.$('.o_exported_lists_select option:selected');
+ if(select_exp.val()) {
self.exports.unlink([parseInt(select_exp.val(), 10)]);
select_exp.remove();
- self.$el.find("#fields_list option").remove();
- if (self.$el.find('#saved_export_list option').length <= 1) {
- self.$el.find('#ExistsExportList').hide();
+ self.$fields_list.empty();
+ if (self.$('.o_exported_lists_select option').length <= 1) {
+ self.$('.o_exported_lists').hide();
}
}
});
});
- },
- do_load_export_field: function(field_list) {
- var export_node = this.$el.find("#fields_list");
- _(field_list).each(function (field) {
- export_node.append(new Option(field.label, field.name));
- });
- },
- on_show_save_list: function() {
- var self = this;
- var current_node = self.$el.find("#savenewlist");
- if (!(current_node.find("label")).length) {
- current_node.append(QWeb.render('ExportNewList'));
- current_node.find("#add_export_list").click(function() {
- var value = current_node.find("#savelist_name").val();
- if (value) {
- self.do_save_export_list(value);
- } else {
- alert(_t("Please enter save field list name"));
- }
+
+ function do_load_export_field(field_list) {
+ _.each(field_list, function (field) {
+ self.$fields_list.append(new Option(field.label, field.name));
});
- } else {
- if (current_node.is(':hidden')) {
- current_node.show();
- current_node.find("#savelist_name").val("");
- } else {
- current_node.hide();
- }
}
},
- do_save_export_list: function(value) {
- var self = this;
- var fields = self.get_fields();
- if (!fields.length) {
+ on_expand_action: function(record) {
+ if(!record['children']) {
return;
}
- this.exports.create({
- name: value,
- resource: this.dataset.model,
- export_fields: _(fields).map(function (field) {
- return [0, 0, {name: field}];
- })
- }).then(function (export_list_id) {
- if (!export_list_id) {
- return;
- }
- if (!self.$el.find("#saved_export_list").length || self.$el.find("#saved_export_list").is(":hidden")) {
- self.show_exports_list();
- }
- self.$el.find("#saved_export_list").append( new Option(value, export_list_id) );
- });
- this.on_show_save_list();
- },
- on_click: function(id, record) {
- var self = this;
- if (!record['children']) {
- return;
- }
- var model = record['params']['model'],
- prefix = record['params']['prefix'],
- name = record['params']['name'],
- exclude_fields = [];
- if (record['relation_field']) {
+
+ var model = record['params']['model'];
+ var prefix = record['params']['prefix'];
+ var name = record['params']['name'];
+ var exclude_fields = [];
+ if(record['relation_field']) {
exclude_fields.push(record['relation_field']);
}
- if (!record.loaded) {
- var import_comp = self.$el.find("#import_compat").val();
- self.rpc("/web/export/get_fields", {
+ if(!record.loaded) {
+ var self = this;
+ this.rpc("/web/export/get_fields", {
model: model,
prefix: prefix,
parent_name: name,
- import_compat: Boolean(import_comp),
+ import_compat: !!this.$import_compat_radios.filter(':checked').val(),
parent_field_type : record['field_type'],
exclude: exclude_fields
}).done(function(results) {
@@ -224,200 +338,90 @@ var DataExport = Dialog.extend({
self.on_show_data(results, record.id);
});
} else {
- self.showcontent(record.id);
+ this.show_content(record.id);
}
},
on_show_domain: function() {
- this.$el.find('tr').first().find('td').append(QWeb.render('ExportTreeView-Domain', {'record': this}));
+ this.$('p').first()
+ .after(QWeb.render('Export.DomainMessage', {record: this}));
},
- on_show_data: function(result, after) {
+ on_show_data: function(records, expansion) {
var self = this;
-
- if (after) {
- var current_tr = self.$el.find("tr[id='treerow-" + after + "']");
- current_tr.addClass('open');
- current_tr.find('img').attr('src','/web/static/src/img/collapse.gif');
- current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
+
+ if(expansion) {
+ this.$('.o_export_tree_item[data-id="' + expansion + '"]')
+ .addClass('open')
+ .find('.o_expand_parent')
+ .toggleClass('fa-plus fa-minus')
+ .next()
+ .after(QWeb.render('Export.TreeItems', {'fields': records}));
} else {
- self.$el.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
+ this.$('.o_left_field_panel').empty().append(
+ $("<div/>").addClass('o_field_tree_structure')
+ .append(QWeb.render('Export.TreeItems', {'fields': records}))
+ );
}
- _.each(result, function(record) {
- self.records[record.id] = record.value;
- if (record.required) {
- var required_fld = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
- required_fld.addClass("oe_export_requiredfield");
- }
- self.$el.find("img[id='parentimg-" + record.id +"']").click(function() {
- self.on_click(this.id, record);
- });
- self.$el.find("tr[id='treerow-" + record.id + "']").click(function(e) {
- if (e.shiftKey) {
- var frst_click, scnd_click = '';
- if (self.row_index === 0) {
- self.row_index = this.rowIndex;
- frst_click = self.$el.find("tr[id^='treerow-']")[self.row_index-1];
- $(frst_click).addClass("ui-selected");
- } else {
- var i;
- if (this.rowIndex >=self.row_index) {
- for (i = (self.row_index-1); i < this.rowIndex; i++) {
- scnd_click = self.$el.find("tr[id^='treerow-']")[i];
- if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
- $(scnd_click).addClass("ui-selected");
- }
- }
- } else {
- for (i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
- scnd_click = self.$el.find("tr[id^='treerow-']")[i];
- if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
- $(scnd_click).addClass("ui-selected");
- }
- }
- }
- }
- }
- self.row_index = this.rowIndex;
-
- self.$el.find("tr[id='treerow-" + record.id + "']").keyup(function() {
- self.row_index = 0;
- });
- var o2m_selection = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
- if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
- return false;
- }
- if (e.ctrlKey) {
- if ($(this).hasClass('ui-selected')) {
- $(this).removeClass('ui-selected').find('a').blur();
- } else {
- $(this).addClass('ui-selected').find('a').focus();
- }
- } else if (!e.shiftKey) {
- self.$el.find("tr.ui-selected")
- .removeClass("ui-selected").find('a').blur();
- $(this).addClass("ui-selected").find('a').focus();
- }
- return false;
- });
-
- self.$el.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
- var keyCode = e.keyCode || e.which;
- var arrow = {left: 37, up: 38, right: 39, down: 40 };
- var elem;
- switch (keyCode) {
- case arrow.left:
- if ($(this).hasClass('open')) {
- self.on_click(this.id, record);
- }
- break;
- case arrow.right:
- if (!$(this).hasClass('open')) {
- self.on_click(this.id, record);
- }
- break;
- case arrow.up:
- elem = this;
- $(elem).removeClass("ui-selected");
- while (!$(elem).prev().is(":visible")) {
- elem = $(elem).prev();
- }
- if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
- $(elem).prev().addClass("ui-selected");
- }
- $(elem).prev().find('a').focus();
- break;
- case arrow.down:
- elem = this;
- $(elem).removeClass("ui-selected");
- while(!$(elem).next().is(":visible")) {
- elem = $(elem).next();
- }
- if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
- $(elem).next().addClass("ui-selected");
- }
- $(elem).next().find('a').focus();
- break;
- }
- });
- self.$el.find("tr[id='treerow-" + record.id + "']").dblclick(function() {
- var $o2m_selection = self.$el.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
- if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
- self.add_field(record.id, $(this).find("a").attr("string"));
- }
- });
- });
- self.$el.find('#fields_list').mouseover(function(event) {
- if (event.relatedTarget) {
- if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
- var field_id = event.relatedTarget.attributes["id"]["value"];
- if (field_id && field_id.split("-")[0] === 'export') {
- if (!self.$el.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
- self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
- }
- }
- }
- }
+ _.extend(this.records, _.object(_.pluck(records, 'id'), records));
+ this.$records = this.$(".o_export_tree_item");
+ this.$records.each(function(i, el) {
+ var $elem = $(el);
+ $elem.find('.o_tree_column').first().toggleClass('o_required', !!self.records[$elem.data('id')].required);
});
},
- showcontent: function(id) {
- // show & hide the contents
- var $this = this.$el.find("tr[id='treerow-" + id + "']");
- var is_open = $this.hasClass('open');
+ show_content: function(id) {
+ var $this = this.$('.o_export_tree_item[data-id="' + id + '"]');
$this.toggleClass('open');
+ var is_open = $this.hasClass('open');
- var first_child = $this.find('img');
- if (is_open) {
- first_child.attr('src', '/web/static/src/img/expand.gif');
- } else {
- first_child.attr('src', '/web/static/src/img/collapse.gif');
- }
- var child_field = this.$el.find("tr[id^='treerow-" + id +"/']");
+ $this.children('.o_expand_parent').toggleClass('fa-minus', !!is_open).toggleClass('fa-plus', !is_open);
+
+ var $child_field = $this.find('.o_export_tree_item');
var child_len = (id.split("/")).length + 1;
- for (var i = 0; i < child_field.length; i++) {
- var $child = $(child_field[i]);
- if (is_open) {
+ for (var i = 0 ; i < $child_field.length ; i++) {
+ var $child = $child_field.eq(i);
+ if(!is_open) {
$child.hide();
- } else if (child_len == (child_field[i].id.split("/")).length) {
+ } else if(child_len === $child_field.eq(i).data('id').split("/").length) {
if ($child.hasClass('open')) {
$child.removeClass('open');
- $child.find('img').attr('src', '/web/static/src/img/expand.gif');
+ $child.children('.o_expand_parent').removeClass('fa-minus').addClass('fa-plus');
}
$child.show();
}
}
},
add_field: function(field_id, string) {
- var field_list = this.$el.find('#fields_list');
- field_id = this.records[field_id] || field_id;
- if (this.$el.find("#fields_list option[value='" + field_id + "']")
- && !this.$el.find("#fields_list option[value='" + field_id + "']").length) {
- field_list.append(new Option(string, field_id));
+ var $field_list = this.$('.o_fields_list');
+ if($field_list.find("option[value='" + field_id + "']").length === 0) {
+ $field_list.append(new Option(string, field_id));
}
},
get_fields: function() {
- var export_fields = this.$("#fields_list option").map(function() {
+ var $export_fields = this.$(".o_fields_list option").map(function() {
return $(this).val();
}).get();
- if (!export_fields.length) {
- alert(_t("Please select fields to save export list..."));
+ if($export_fields.length === 0) {
+ Dialog.alert(this, _t("Please select fields to save export list..."));
}
- return export_fields;
+ return $export_fields;
},
- on_click_export_data: function() {
+ export_data: function() {
var self = this;
- var exported_fields = this.$el.find('#fields_list option').map(function () {
- // DOM property is textContent, but IE8 only knows innerText
- return {name: self.records[this.value] || this.value,
- label: this.textContent || this.innerText};
+ var exported_fields = this.$('.o_fields_list option').map(function () {
+ return {
+ name: (self.records[this.value] || this).value,
+ label: this.textContent || this.innerText // DOM property is textContent, but IE8 only knows innerText
+ };
}).get();
if (_.isEmpty(exported_fields)) {
- alert(_t("Please select fields to export..."));
+ Dialog.alert(this, _t("Please select fields to export..."));
return;
}
exported_fields.unshift({name: 'id', label: 'External ID'});
- var export_format = this.$el.find("#export_format").val();
+ var export_format = this.$export_format_inputs.filter(':checked').val();
framework.blockUI();
this.session.get_file({
@@ -428,16 +432,14 @@ var DataExport = Dialog.extend({
ids: this.ids_to_export,
domain: this.domain,
context: this.dataset.context,
- import_compat: !!this.$el.find("#import_compat").val(),
+ import_compat: !!this.$import_compat_radios.filter(':checked').val(),
})},
complete: framework.unblockUI,
error: crash_manager.rpc_error.bind(crash_manager),
});
},
- close: function() {
- this._super();
- }
});
return DataExport;
+
});
diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js
index 74a534ec40e..d95509bb465 100644
--- a/addons/web/static/src/js/widgets/debug_manager.js
+++ b/addons/web/static/src/js/widgets/debug_manager.js
@@ -1,6 +1,8 @@
odoo.define('web.DebugManager', function (require) {
"use strict";
+var ActionManager = require('web.ActionManager');
+var common = require('web.form_common');
var core = require('web.core');
var Dialog = require('web.Dialog');
var formats = require('web.formats');
@@ -8,210 +10,691 @@ var framework = require('web.framework');
var Model = require('web.Model');
var session = require('web.session');
var SystrayMenu = require('web.SystrayMenu');
+var Tour = require('web.Tour');
var utils = require('web.utils');
var ViewManager = require('web.ViewManager');
+var WebClient = require('web.WebClient');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
-if (core.debug) {
- var DebugManager = Widget.extend({
- template: "WebClient.DebugManager",
- events: {
- "click .oe_debug_button": "render_dropdown",
- "click .js_debug_dropdown li": "on_debug_click",
- },
- start: function() {
- this._super();
- this.$dropdown = this.$(".js_debug_dropdown");
- },
- /**
- * Updates its attributes according to the inner_widget of the ActionManager
- */
- _update: function() {
- this.view_manager = odoo.__DEBUG__.services['web.web_client'].action_manager.get_inner_widget();
- if (!this.view_manager instanceof ViewManager) { return; }
- this.dataset = this.view_manager.dataset;
- this.active_view = this.view_manager.active_view;
- if (!this.active_view) { return; }
- this.view = this.active_view.controller;
- return true;
+var ADD = function (id) { return [4, id, false]; },
+ REMOVE = function (id) { return [3, id, false]; };
+/**
+ * DebugManager base + general features (applicable to any context)
+ */
+var DebugManager = Widget.extend({
+ template: "WebClient.DebugManager",
+ events: {
+ "click a[data-action]": "perform_callback",
+ "mouseover .o_debug_dropdowns > li:not(.open)": function(e) {
+ // Open other dropdowns on mouseover
+ var $opened = this.$('.o_debug_dropdowns > li.open');
+ if($opened.length) {
+ $opened.removeClass('open');
+ $(e.currentTarget).addClass('open').find('> a').focus();
+ }
},
- /**
- * Renders the DebugManager dropdown
- */
- render_dropdown: function() {
- var self = this;
+ },
+ init: function () {
+ this._super.apply(this, arguments);
+ // 15 fps, only actually call after sequences of queries
+ this._update_stats = _.throttle(
+ this._update_stats.bind(this),
+ 1000/15, {leading: false});
+ this._events = null;
+ if (document.querySelector('meta[name=debug]')) {
+ this._events = [];
+ }
+ },
+ start: function () {
+ core.bus.on('action_pushed', this, this.update);
+ core.bus.on('rpc:result', this, function (req, resp) {
+ this._debug_events(resp.debug);
+ });
+ this.on('update-stats', this, this._update_stats);
+ var init;
+ if ((init = document.querySelector('meta[name=debug]'))) {
+ this._debug_events(JSON.parse(init.getAttribute('value')));
+ }
- // Empty the previously rendered dropdown
- this.$dropdown.empty();
+ this.$dropdown = this.$(".o_debug_dropdown");
+ // falsy if can't write to user or couldn't find technical features
+ // group, otherwise features group id
+ this._features_group = null;
+ // whether group is currently enabled for current user
+ this._has_features = false;
+ // whether the current user is an administrator
+ this._is_admin = false;
+ return $.when(
+ new Model('res.users').call('check_access_rights', {operation: 'write', raise_exception: false}),
+ this.session.user_has_group('base.group_no_one'),
+ new Model('ir.model.data').call('xmlid_to_res_id', {xmlid: 'base.group_no_one'}),
+ this.session.user_has_group('base.group_system'),
+ this._super()
+ ).then(function (can_write_user, has_group_no_one, group_no_one_id, is_admin) {
+ this._features_group = can_write_user && group_no_one_id;
+ this._has_features = has_group_no_one;
+ this._is_admin = is_admin;
+ return this.update();
+ }.bind(this));
+ },
+ leave_debug_mode: function () {
+ var qs = $.deparam.querystring();
+ delete qs.debug;
+ window.location.search = '?' + $.param(qs);
+ }, /**
+ * Calls the appropriate callback when clicking on a Debug option
+ */
+ perform_callback: function (evt) {
+ evt.preventDefault();
+ var params = $(evt.target).data();
+ var callback = params.action;
- // Attempt to retrieve the inner_widget of the ActionManager
- if (!this._update()) {
- // Disable the button when not available
- console.warn("DebugManager is not available");
- return;
+ if (callback && this[callback]) {
+ // Perform the callback corresponding to the option
+ this[callback](params, evt);
+ } else {
+ console.warn("No handler for ", callback);
+ }
+ },
+
+ _debug_events: function (events) {
+ if (!this._events) { return; }
+ if (events && events.length) {
+ this._events.push(events);
+ }
+ this.trigger('update-stats', this._events);
+ },
+ requests_clear: function () {
+ if (!this._events) { return; }
+ this._events = [];
+ this.trigger('update-stats', this._events);
+ },
+ _update_stats: function (rqs) {
+ var requests = 0, rtime = 0, queries = 0, qtime = 0;
+ for(var r = 0; r < rqs.length; ++r) {
+ for (var i = 0; i < rqs[r].length; i++) {
+ var event = rqs[r][i];
+ var query_start, request_start;
+ switch (event[0]) {
+ case 'request-start':
+ request_start = event[3] * 1e3;
+ break;
+ case 'request-end':
+ ++requests;
+ rtime += (event[3] * 1e3 - request_start) | 0;
+ break;
+ case 'sql-start':
+ query_start = event[3] * 1e3;
+ break;
+ case 'sql-end':
+ ++queries;
+ qtime += (event[3] * 1e3 - query_start) | 0;
+ break;
+ }
}
+ }
+ this.$('#debugmanager_requests_stats').text(
+ _.str.sprintf(_t("%d requests (%d ms) %d queries (%d ms)"),
+ requests, rtime, queries, qtime));
+ },
+ show_timelines: function () {
+ if (this._overlay) {
+ this._overlay.destroy();
+ this._overlay = null;
+ return;
+ }
+ this._overlay = new RequestsOverlay(this);
+ this._overlay.appendTo(document.body);
+ },
- this.session.user_has_group('base.group_system').then(function(is_admin) {
- // Render the dropdown and append it
- var dropdown_content = QWeb.render('WebClient.DebugDropdown', {
- widget: self,
- active_view: self.active_view,
- view: self.view,
- action: self.view_manager.action,
- searchview: self.view_manager.searchview,
- is_admin: is_admin,
- });
- $(dropdown_content).appendTo(self.$dropdown);
- });
- },
- /**
- * Calls the appropriate callback when clicking on a Debug option
- */
- on_debug_click: function (evt) {
- evt.preventDefault();
-
- var params = $(evt.target).data();
- var callback = params.action;
-
- if (callback && this[callback]) {
- // Perform the callback corresponding to the option
- this[callback](params, evt);
- } else {
- console.warn("No debug handler for ", callback);
+ /**
+ * Update the debug manager: reinserts all "universal" controls
+ */
+ update: function () {
+ this.$dropdown
+ .empty()
+ .append(QWeb.render('WebClient.DebugManager.Global', {
+ manager: this,
+ }));
+ return $.when();
+ },
+ start_tour: function() {
+ var dialog = new Dialog(this, {
+ title: 'Tours',
+ $content: QWeb.render('WebClient.DebugManager.ToursDialog', {
+ tours: Tour.tours
+ }),
+ }).open();
+
+ dialog.$('.o_start_tour').on('click', function(e) {
+ e.preventDefault();
+ odoo.__DEBUG__.services['web.Tour'].run($(e.target).data('id'));
+ });
+ },
+ select_view: function () {
+ var self = this;
+ new common.SelectCreateDialog(this, {
+ res_model: 'ir.ui.view',
+ title: _t('Select a view'),
+ disable_multiple_selection: true,
+ on_selected: function (element_ids) {
+ new Model('ir.ui.view')
+ .query(['name', 'model', 'type'])
+ .filter([['id', '=', element_ids[0]]])
+ .first()
+ .then(function (view) {
+ self.do_action({
+ type: 'ir.actions.act_window',
+ name: view.name,
+ res_model: view.model,
+ views: [[view.id, view.type]]
+ });
+ });
}
- },
- get_metadata: function() {
- var ds = this.dataset;
- ds.call('get_metadata', [this.view.get_selected_ids()]).done(function(result) {
- new Dialog(this, {
- title: _.str.sprintf(_t("Metadata (%s)"), ds.model),
- size: 'medium',
- $content: QWeb.render('WebClient.DebugViewLog', {
- perm : result[0],
- format : formats.format_value
- })
- }).open();
- });
- },
- toggle_layout_outline: function() {
- this.view.rendering_engine.toggle_layout_debugging();
- },
- set_defaults: function() {
- this.view.open_defaults_dialog();
- },
- perform_js_tests: function() {
- this.do_action({
- name: _t("JS Tests"),
- target: 'new',
- type : 'ir.actions.act_url',
- url: '/web/tests?mod=*'
+ }).open();
+ },
+ perform_js_tests: function () {
+ this.do_action({
+ name: _t("JS Tests"),
+ target: 'new',
+ type: 'ir.actions.act_url',
+ url: '/web/tests?mod=*'
+ });
+ },
+ toggle_technical_features: function () {
+ if (!this._features_group) { return; }
+ var command = this._has_features ? REMOVE(this._features_group) : ADD(this._features_group);
+ new Model('res.users').call('write', [session.uid, {
+ groups_id: [command]
+ }]).then(function () {
+ window.location.reload();
+ });
+ },
+ split_assets: function() {
+ window.location = $.param.querystring(window.location.href, 'debug=assets');
+ },
+});
+
+/**
+ * DebugManager features depending on having an action, and possibly a model
+ * (window action)
+ */
+DebugManager.include({
+ /**
+ * Updates current action (action descriptor) on tag = action,
+ */
+ update: function (tag, descriptor) {
+ if (tag === 'action') {
+ this._action = descriptor;
+ }
+ return this._super().then(function () {
+ this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.Action', {
+ manager: this,
+ action: this._action
+ }));
+ }.bind(this));
+ },
+ edit: function (params, evt) {
+ this.do_action({
+ res_model: params.model,
+ res_id: params.id,
+ name: evt.target.text,
+ type: 'ir.actions.act_window',
+ views: [[false, 'form']],
+ view_mode: 'form',
+ target: 'new',
+ flags: {action_buttons: true, headless: true}
+ });
+ },
+ get_view_fields: function () {
+ var self = this;
+ var model = this._action.res_model;
+ new Model(model).call('fields_get', {
+ attributes: ['string', 'searchable', 'required', 'readonly', 'type', 'store', 'sortable', 'relation', 'help']
+ }).done(function (fields) {
+ new Dialog(self, {
+ title: _.str.sprintf(_t("Fields of %s"), model),
+ $content: $(QWeb.render('WebClient.DebugManager.Action.Fields', {
+ fields: fields
+ }))
+ }).open();
+ });
+ },
+ manage_filters: function () {
+ 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._action.res_model
+ }
+ });
+ },
+ edit_workflow: function () {
+ return this.do_action({
+ res_model: 'workflow',
+ name: _t('Edit Workflow'),
+ domain: [['osv', '=', this._action.res_model]],
+ views: [[false, 'list'], [false, 'form'], [false, 'diagram']],
+ type: 'ir.actions.act_window',
+ view_type: 'list',
+ view_mode: 'list'
+ });
+ },
+ translate: function() {
+ var model = this._action.res_model;
+ new Model("ir.translation")
+ .call('get_technical_translations', [model])
+ .then(this.do_action);
+ }
+});
+
+/**
+ * DebugManager features depending on having a form view or single record.
+ * These could theoretically be split, but for now they'll be considered one
+ * and the same.
+ */
+DebugManager.include({
+ start: function () {
+ this._can_edit_views = false;
+ return $.when(
+ this._super(),
+ new Model('ir.ui.view').call(
+ 'check_access_rights', {operation: 'write', raise_exception: false}
+ ).then(function (ar) {
+ this._can_edit_views = ar;
+ }.bind(this))
+ );
+ },
+ update: function (tag, descriptor, widget) {
+ switch (tag) {
+ case 'action':
+ if (!(widget instanceof ViewManager)) {
+ this._active_view = null;
+ this._view_manager = null;
+ break;
+ }
+ this._view_manager = widget;
+ widget.on('switch_mode', this, function () {
+ this.update('view', null, widget);
});
- },
- get_view_fields: function() {
- var self = this;
- self.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));
- });
+ case 'view':
+ this._active_view = widget.active_view;
+ }
+
+ return this._super(tag, descriptor).then(function () {
+ this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.View', {
+ manager: this,
+ action: this._action,
+ view: this._active_view,
+ can_edit: this._can_edit_views,
+ searchview: this._view_manager && this._view_manager.searchview,
+ }));
+ }.bind(this));
+ },
+
+ get_metadata: function() {
+ var ds = this._view_manager.dataset;
+ ds.call('get_metadata', [this._active_view.controller.get_selected_ids()]).done(function(result) {
+ new Dialog(this, {
+ title: _.str.sprintf(_t("Metadata (%s)"), ds.model),
+ size: 'medium',
+ $content: QWeb.render('WebClient.DebugViewLog', {
+ perm : result[0],
+ format : formats.format_value
+ })
+ }).open();
+ });
+ },
+ set_defaults: function() {
+ this._active_view.controller.open_defaults_dialog();
+ },
+ fvg: function() {
+ var dialog = new Dialog(this, { title: _t("Fields View Get") }).open();
+ $('<pre>').text(utils.json_node_to_xml(
+ this._active_view.controller.fields_view.arch, true)
+ ).appendTo(dialog.$el);
+ },
+ print_workflow: function() {
+ var ids = this._active_view.controller.get_selected_ids();
+ framework.blockUI();
+ var action = {
+ context: { active_ids: ids },
+ report_name: "workflow.instance.graph",
+ datas: {
+ model: this._view_manager.dataset.model,
+ id: ids[0],
+ nested: true,
+ }
+ };
+ this.session.get_file({
+ url: '/web/report',
+ data: {action: JSON.stringify(action)},
+ complete: framework.unblockUI
+ });
+ },
+});
+function make_context(width, height, fn) {
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ // make e.layerX/e.layerY imitate e.offsetX/e.offsetY.
+ canvas.style.position = 'relative';
+ var ctx = canvas.getContext('2d');
+ ctx.imageSmoothingEnabled = false;
+ ctx.mozImageSmoothingEnabled = false;
+ ctx.oImageSmoothingEnabled = false;
+ ctx.webkitImageSmoothingEnabled = false;
+ fn && fn(ctx);
+ return ctx;
+}
+var RequestsOverlay = Widget.extend({
+ template: 'WebClient.DebugManager.RequestsOverlay',
+ TRACKS: 8,
+ TRACK_WIDTH: 9,
+ events: {
+ mousemove: function (e) {
+ this.$tooltip.hide();
+ }
+ },
+ init: function () {
+ this._super.apply(this, arguments);
+ this._render = _.throttle(
+ this._render.bind(this),
+ 1000/15, {leading: false}
+ );
+ },
+ start: function () {
+ var _super = this._super();
+ this.$tooltip = this.$('div.o_debug_tooltip');
+ this.getParent().on('update-stats', this, this._render);
+ this._render();
+ return _super;
+ },
+ tooltip: function (text, start, end, x, y) {
+ // x and y are hit point with respect to the viewport. To know where
+ // this hit point is with respect to the overlay, subtract the offset
+ // between viewport and overlay, then add scroll factor of overlay
+ // (which isn't taken in account by the viewport).
+ //
+ // Normally the viewport overlay should sum offsets of all
+ // offsetParents until we reach `null` but in this case the overlay
+ // should have been added directly to the body, which should have an
+ // offset of 0.
+
+ var top = y - this.el.offsetTop + this.el.scrollTop + 1;
+ var left = x - this.el.offsetLeft + this.el.scrollLeft + 1;
+ this.$tooltip.css({top: top, left: left}).show()[0].innerHTML = ['<p>', text, ' (', (end - start), 'ms)', '</p>'].join('');
+ },
+
+ _render: function () {
+ var $summary = this.$('header'),
+ w = $summary[0].clientWidth,
+ $requests = this.$('.o_debug_requests');
+ $summary.find('canvas').attr('width', w);
+ var tracks = document.getElementById('o_debug_requests_summary');
+
+ _.invoke(this.getChildren(), 'destroy');
+
+ var requests = this.getParent()._events;
+ var bounds = this._get_bounds(requests);
+ // horizontal scaling factor for summary
+ var scale = w / (bounds.high - bounds.low);
+
+ // store end-time of "current" requests, to find out which track a
+ // request should go in, just look for the first track whose end-time
+ // is smaller than the new request's start time.
+ var track_ends = _(this.TRACKS).times(_.constant(-Infinity));
+
+ var ctx = tracks.getContext('2d');
+ ctx.lineWidth = this.TRACK_WIDTH;
+ for (var i = 0; i < requests.length; i++) {
+ var request = requests[i];
+ // FIXME: is it certain that events in the request are sorted by timestamp?
+ var rstart = Math.floor(request[0][3] * 1e3);
+ var rend = Math.ceil(request[request.length - 1][3] * 1e3);
+ // find free track for current request
+ for(var track=0; track < track_ends.length; ++track) {
+ if (track_ends[track] < rstart) { break; }
+ }
+ // FIXME: display error message of some sort? Re-render with larger area? Something?
+ if (track >= track_ends.length) {
+ console.warn("could not find an empty summary track");
+ continue;
+ }
+ // set new track end
+ track_ends[track] = rend;
+ ctx.save();
+ ctx.translate(Math.floor((rstart - bounds.low) * scale), track * (this.TRACK_WIDTH + 1));
+ this._draw_request(request, ctx, 0, scale);
+ ctx.restore();
+ new RequestDetails(this, request, scale).appendTo($requests);
+ }
+ },
+ _draw_request: function (request, to_context, step, hscale, handle_event) {
+ // have one draw surface for each event type:
+ // * no need to alter context from one event to the next, each surface
+ // gets its own color for all its lifetime
+ // * surfaces can be blended in a specified order, which means events
+ // can be drawn in any order, no need to care about z-index while
+ // serializing events to the surfaces
+ var surfaces = {
+ request: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ ctx.strokeStyle = 'blue';
+ ctx.fillStyle = '#88f';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 1;
+ }),
+ //func: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ // ctx.strokeStyle = 'gray';
+ // ctx.lineWidth = to_context.lineWidth;
+ // ctx.translate(0, initial_offset);
+ //}),
+ sql: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ ctx.strokeStyle = 'red';
+ ctx.fillStyle = '#f88';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 1;
+ }),
+ template: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ ctx.strokeStyle = 'green';
+ ctx.fillStyle = '#8f8';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 1;
+ })
+ };
+ // apply scaling manually so zooming in improves display precision
+ var stacks = {}, start = Math.floor(request[0][3] * 1e3 * hscale);
+ var event_idx = 0;
+
+ var rect_width = to_context.lineWidth;
+ for (var i = 0; i < request.length; i++) {
+ var type, m, event = request[i];
+ var tag = event[0], timestamp = Math.floor(event[3] * 1e3 * hscale) - start;
+
+ if (m = /(\w+)-start/.exec(tag)) {
+ type = m[1];
+ if (!(type in stacks)) { stacks[type] = []; }
+ handle_event && handle_event(event_idx, timestamp, event);
+ stacks[type].push({
+ timestamp: timestamp,
+ idx: event_idx++
});
- new Dialog(self, {
- title: _.str.sprintf(_t("Model %s fields"), self.dataset.model),
- $content: $root
- }).open();
- });
- },
- fvg: function() {
- var dialog = new Dialog(this, { title: _t("Fields View Get") }).open();
- $('<pre>').text(utils.json_node_to_xml(this.view.fields_view.arch, true)).appendTo(dialog.$el);
- },
- manage_filters: function() {
- 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
- }
- });
- },
- translate: function() {
- new Model("ir.translation")
- .call('get_technical_translations', [this.dataset.model])
- .then(this.do_action);
+ } else if (m = /(\w+)-end/.exec(tag)) {
+ type = m[1];
+ var stack = stacks[type];
+ var estart = stack.pop(), duration = Math.ceil(timestamp - estart.timestamp);
+ handle_event && handle_event(estart.idx, timestamp, event);
+
+ var surface = surfaces[type];
+ if (!surface) { continue; } // FIXME: support for unknown event types
+
+ var y = step * estart.idx;
+ // path rectangle for the current event on the relevant surface
+ surface.rect(estart.timestamp + 0.5, y + 0.5, duration || 1, rect_width);
+ }
+ }
+ // add each layer to the main canvas
+ var keys = ['request', /*'func', */'template', 'sql'];
+ for (var j = 0; j < keys.length; ++j) {
+ // stroke and fill all rectangles for the relevant surface/context
+ var ctx = surfaces[keys[j]];
+ ctx.fill();
+ ctx.stroke();
+ to_context.drawImage(ctx.canvas, 0, 0);
+ }
+ },
+ /**
+ * Returns first and last events in milliseconds
+ *
+ * @param requests
+ * @returns {{low: number, high: number}}
+ * @private
+ */
+ _get_bounds: function (requests) {
+ var low = +Infinity;
+ var high =-+Infinity;
+
+ for (var i = 0; i < requests.length; i++) {
+ var request = requests[i];
+ for (var j = 0; j < request.length; j++) {
+ var event = request[j];
+ var timestamp = event[3];
+ low = Math.min(low, timestamp);
+ high = Math.max(high, timestamp);
+ }
+ }
+ return {low: Math.floor(low * 1e3), high: Math.ceil(high * 1e3)};
+ }
+});
+var RequestDetails = Widget.extend({
+ events: {
+ click: function () {
+ this._open = !this._open;
+ this.render();
},
- edit: function(params, evt) {
- this.do_action({
- res_model : params.model,
- res_id : params.id,
- name: evt.target.text,
- type : 'ir.actions.act_window',
- view_type : 'form',
- view_mode : 'form',
- views : [[false, 'form']],
- target : 'new',
- flags : {
- action_buttons : true,
- headless: true,
+ 'mousemove canvas': function (e) {
+ e.stopPropagation();
+ var y = e.y || e.offsetY || e.layerY;
+ if (!y) { return; }
+ var event = this._payloads[Math.floor(y / this._REQ_HEIGHT)];
+ if (!event) { return; }
+
+ this.getParent().tooltip(event.payload, event.start, event.stop, e.clientX, e.clientY);
+ }
+ },
+ init: function (parent, request, scale) {
+ this._super.apply(this, arguments);
+ this._request = request;
+ this._open = false;
+ this._scale = scale;
+ this._REQ_HEIGHT = 20;
+ },
+ start: function () {
+ this.el.style.borderBottom = '1px solid black';
+ this.render();
+ return this._super();
+ },
+ render: function () {
+ var request_cell_height = this._REQ_HEIGHT, TITLE_WIDTH = 200;
+ var request = this._request;
+ var req_start = request[0][3] * 1e3;
+ var req_duration = request[request.length - 1][3] * 1e3 - req_start;
+ var height = request_cell_height * (this._open ? request.length / 2 : 1);
+ var cell_center = request_cell_height / 2;
+ var ctx = make_context(210 + Math.ceil(req_duration * this._scale), height, function (ctx) {
+ ctx.lineWidth = cell_center;
+ });
+ this.$el.empty().append(ctx.canvas);
+ var payloads = this._payloads = [];
+
+ // lazy version: if the render is single-line (!this._open), the extra
+ // content will be discarded when the text canvas gets pasted onto the
+ // main canvas. An improvement would be to not do text rendering
+ // beyond the first event for "closed" requests events… then again
+ // that makes for more regular rendering profile?
+ var text_ctx = make_context(TITLE_WIDTH, height, function (ctx) {
+ ctx.font = '12px sans-serif';
+ ctx.textAlign = 'right';
+ ctx.textBaseline = 'middle';
+ ctx.translate(0, cell_center);
+ });
+
+ ctx.save();
+ ctx.translate(TITLE_WIDTH + 10, ((request_cell_height/4)|0));
+
+ this.getParent()._draw_request(request, ctx, this._open ? request_cell_height : 0, this._scale, function (idx, timestamp, event) {
+ if (/-start$/g.test(event[0])) {
+ payloads.push({
+ payload: event[2],
+ start: timestamp,
+ stop: null
+ });
+
+ // we want ~200px wide, assume the average character is at
+ // least 4px wide => there can be *at most* 49 characters
+ var title = event[2];
+ title = title.replace(/\s+$/, '');
+ title = title.length <= 50 ? title : ('…' + title.slice(-49));
+ while (text_ctx.measureText(title).width > 200) {
+ title = '…' + title.slice(2);
}
+ text_ctx.fillText(title, TITLE_WIDTH, request_cell_height * idx);
+ } else if (/-end$/g.test(event[0])) {
+ payloads[idx].stop = timestamp;
+ }
+ });
+ ctx.restore();
+ // add the text layer to the main canvas
+ ctx.drawImage(text_ctx.canvas, 0, 0);
+ }
+});
+
+if (core.debug) {
+ SystrayMenu.Items.push(DebugManager);
+
+ WebClient.include({
+ start: function() {
+ var self = this;
+ return this._super().then(function () {
+ // Override push_action so that it triggers an event each time a new action is pushed
+ // The DebugManager listens to this event to keep itself up-to-date
+ var push_action = self.action_manager.push_action;
+ self.action_manager.push_action = function(widget, descr) {
+ return push_action.apply(self.action_manager, arguments).then(function () {
+ core.bus.trigger('action_pushed', 'action', descr, widget);
+ });
+ };
});
},
- edit_workflow: function() {
- 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'
- });
- },
- print_workflow: function() {
- if (this.view.get_selected_ids && this.view.get_selected_ids().length == 1) {
- framework.blockUI();
- var action = {
- context: { active_ids: this.view.get_selected_ids() },
- report_name: "workflow.instance.graph",
- datas: {
- model: this.dataset.model,
- id: this.view.get_selected_ids()[0],
- nested: true,
- }
- };
- this.session.get_file({
- url: '/web/report',
- data: {action: JSON.stringify(action)},
- complete: framework.unblockUI
+ });
+
+ Dialog.include({
+ open: function() {
+ var self = this;
+
+ var parent = self.getParent();
+ if (parent instanceof ActionManager && parent.dialog_widget) {
+ // Instantiate the DebugManager and insert it into the DOM once dialog is opened
+ this.opened(function() {
+ self.debug_manager = new DebugManager(self);
+ var $header = self.$modal.find('.modal-header');
+ return self.debug_manager.prependTo($header).then(function() {
+ self.debug_manager.update('action', parent.dialog_widget.action, parent.dialog_widget);
+ });
});
- } else {
- this.do_warn("Warning", "No record selected.");
}
- },
- split_assets: function() {
- window.location = $.param.querystring(window.location.href, 'debug=assets');
- },
- leave_debug: function() {
- window.location.search="?";
+
+ return this._super.apply(this, arguments);
},
});
-
- SystrayMenu.Items.push(DebugManager);
-
- return DebugManager;
}
+return DebugManager;
+
});
diff --git a/addons/web/static/src/js/widgets/loading.js b/addons/web/static/src/js/widgets/loading.js
index 4ccabcc199e..645287ecf80 100644
--- a/addons/web/static/src/js/widgets/loading.js
+++ b/addons/web/static/src/js/widgets/loading.js
@@ -9,7 +9,8 @@ var Widget = require('web.Widget');
var _t = core._t;
var Loading = Widget.extend({
- template: _t("Loading"),
+ template: "Loading",
+
init: function(parent) {
this._super(parent);
this.count = 0;
diff --git a/addons/web/static/src/js/widgets/pager.js b/addons/web/static/src/js/widgets/pager.js
index 21f3957ca97..d9b37dc889a 100644
--- a/addons/web/static/src/js/widgets/pager.js
+++ b/addons/web/static/src/js/widgets/pager.js
@@ -28,6 +28,7 @@ var Pager = Widget.extend({
* @param {int} [limit] the number of elements per page
* @param {boolean} [options.can_edit] editable feature of the pager
* @param {boolean} [options.single_page_hidden] (not) to display the pager if only one page
+ * @param {function} [options.validate] callback returning a Deferred to validate changes
*/
init: function (parent, size, current_min, limit, options) {
this.state = {
@@ -43,6 +44,9 @@ var Pager = Widget.extend({
this.options = _.defaults({}, options, {
can_edit: true, // editable
single_page_hidden: false, // displayed even if there is a single page
+ validate: function() {
+ return $.Deferred().resolve();
+ },
});
this._super(parent);
},
@@ -101,7 +105,7 @@ var Pager = Widget.extend({
_edit: function() {
if (this.options.can_edit) {
var self = this;
- var $input = $('<input>').val(this.$value.html());
+ var $input = $('<input>', {type: 'text', value: this.$value.html()});
this.$value.html($input);
$input.focus();
@@ -129,23 +133,26 @@ var Pager = Widget.extend({
* @param {jQuery} [$input] the jQuery element containing the new state
*/
_save: function($input) {
- var value = $input.val().split("-");
- var min = utils.confine(parseInt(value[0], 10), 1, this.state.size);
- var max = utils.confine(parseInt(value[1], 10), 1, this.state.size);
-
- if (!isNaN(min)) {
- this.state.current_min = min;
- if (!isNaN(max)) {
- this.state.limit = utils.confine(max-min+1, 1, this.state.size);
- } else {
- // The state has been given as a single value -> set the limit to 1
- this.state.limit = 1;
+ var self = this;
+ this.options.validate().then(function() {
+ var value = $input.val().split("-");
+ var min = utils.confine(parseInt(value[0], 10), 1, self.state.size);
+ var max = utils.confine(parseInt(value[1], 10), 1, self.state.size);
+
+ if (!isNaN(min)) {
+ self.state.current_min = min;
+ if (!isNaN(max)) {
+ self.state.limit = utils.confine(max-min+1, 1, self.state.size);
+ } else {
+ // The state has been given as a single value -> set the limit to 1
+ self.state.limit = 1;
+ }
+ self.trigger('pager_changed', _.clone(self.state));
}
- this.trigger('pager_changed', _.clone(this.state));
- }
-
- // Render the pager's new state (removes the input)
- this._render();
+ }).always(function() {
+ // Render the pager's new state (removes the input)
+ self._render();
+ });
},
/**
@@ -153,23 +160,26 @@ var Pager = Widget.extend({
* @param {int} [direction] the action (previous or next) on the pager
*/
_change_selection: function (direction) {
- var size = this.state.size;
- var current_min = this.state.current_min;
- var limit = this.state.limit;
-
- // Compute the new current_min
- current_min = (current_min + limit*direction);
- if (current_min > size) {
- current_min = 1;
- } else if ((current_min < 1) && (limit === 1)) {
- current_min = size;
- } else if ((current_min < 1) && (limit > 1)) {
- current_min = size - ((size % limit) || limit) + 1;
- }
+ var self = this;
+ this.options.validate().then(function() {
+ var size = self.state.size;
+ var current_min = self.state.current_min;
+ var limit = self.state.limit;
+
+ // Compute the new current_min
+ current_min = (current_min + limit*direction);
+ if (current_min > size) {
+ current_min = 1;
+ } else if ((current_min < 1) && (limit === 1)) {
+ current_min = size;
+ } else if ((current_min < 1) && (limit > 1)) {
+ current_min = size - ((size % limit) || limit) + 1;
+ }
- this.state.current_min = current_min;
- this.trigger('pager_changed', _.clone(this.state));
- this._render();
+ self.state.current_min = current_min;
+ self.trigger('pager_changed', _.clone(self.state));
+ self._render();
+ });
},
/**
diff --git a/addons/web/static/src/js/widgets/sidebar.js b/addons/web/static/src/js/widgets/sidebar.js
index 9ea15be128b..e2222526320 100644
--- a/addons/web/static/src/js/widgets/sidebar.js
+++ b/addons/web/static/src/js/widgets/sidebar.js
@@ -6,7 +6,6 @@ var data = require('web.data');
var Dialog = require('web.Dialog');
var framework = require('web.framework');
var pyeval = require('web.pyeval');
-var session = require('web.session');
var Widget = require('web.Widget');
var QWeb = core.qweb;
@@ -37,6 +36,7 @@ var Sidebar = Widget.extend({
start: function() {
var self = this;
this._super(this);
+ this.$el.addClass('btn-group');
this.redraw();
this.$el.on('click','.dropdown-menu li a', function(event) {
var section = $(this).data('section');
@@ -53,16 +53,19 @@ var Sidebar = Widget.extend({
});
},
redraw: function() {
- var self = this;
- self.$el.html(QWeb.render('Sidebar', {widget: self}));
+ this.$el.html(QWeb.render('Sidebar', {widget: this}));
// Hides Sidebar sections when item list is empty
- this.$('.oe_form_dropdown_section').each(function() {
- $(this).toggle(!!$(this).find('li').length);
+ this.$('.o_dropdown').each(function() {
+ if (!$(this).find('li').length) {
+ $(this).hide();
+ }
});
- self.$("[title]").tooltip({
+ this.$("[title]").tooltip({
delay: { show: 500, hide: 0}
});
+ this.$('.o_sidebar_add_attachment .o_form_binary_form').change(this.on_attachment_changed);
+ this.$('.o_sidebar_delete_attachment').click(this.on_attachment_delete);
},
/**
* For each item added to the section:
@@ -104,7 +107,6 @@ var Sidebar = Widget.extend({
items[i] = {
label: items[i]['name'],
action: items[i],
- classname: 'oe_sidebar_' + type
};
}
self.add_items(type=='print' ? 'print' : 'other', items);
@@ -184,16 +186,14 @@ var Sidebar = Widget.extend({
});
this.items.files = attachments;
this.redraw();
- this.$('.oe_sidebar_add_attachment .o_form_input_file').change(this.on_attachment_changed);
- this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete);
},
on_attachment_changed: function(e) {
var $e = $(e.target);
if ($e.val() !== '') {
- this.$el.find('form.o_form_binary_form').submit();
+ this.$('form.o_form_binary_form').submit();
$e.parent().find('input[type=file]').prop('disabled', true);
$e.parent().find('button').prop('disabled', true).find('img, span').toggle();
- this.$('.oe_sidebar_add_attachment a').text(_t('Uploading...'));
+ this.$('.o_sidebar_add_attachment a').text(_t('Uploading...'));
framework.blockUI();
}
},
@@ -202,11 +202,16 @@ var Sidebar = Widget.extend({
e.stopPropagation();
var self = this;
var $e = $(e.currentTarget);
- if (confirm(_t("Do you really want to delete this attachment ?"))) {
- (new data.DataSet(this, 'ir.attachment')).unlink([parseInt($e.attr('data-id'), 10)]).done(function() {
- self.do_attachement_update(self.dataset, self.model_id);
- });
- }
+ var options = {
+ confirm_callback: function () {
+ new data.DataSet(this, 'ir.attachment')
+ .unlink([parseInt($e.attr('data-id'), 10)])
+ .done(function() {
+ self.do_attachement_update(self.dataset, self.model_id);
+ });
+ }
+ };
+ Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options);
}
});
diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js
index 2879a0a2b0d..89dc6ae29b3 100644
--- a/addons/web/static/src/js/widgets/switch_company_menu.js
+++ b/addons/web/static/src/js/widgets/switch_company_menu.js
@@ -1,45 +1,46 @@
odoo.define('web.SwitchCompanyMenu', function(require) {
- "use strict";
+"use strict";
- var Model = require('web.Model');
- var session = require('web.session');
- var SystrayMenu = require('web.SystrayMenu');
- var Widget = require('web.Widget');
-
- var SwitchCompanyMenu = Widget.extend({
- template: 'SwitchCompanyMenu',
- willStart: function() {
- if (!session.user_companies) {
- return $.Deferred().reject();
- }
- return this._super();
- },
- start: function() {
- var self = this;
- this.$el.on('click', '.dropdown-menu li a[data-menu]', _.debounce(function(ev) {
- ev.preventDefault();
- var company_id = $(ev.currentTarget).data('company-id');
- new Model('res.users').call('write', [[session.uid], {'company_id': company_id}]).then(function() {
- location.reload();
- });
- }, 1500, true));
-
- self.$('.oe_topbar_name').text(session.user_companies.current_company[1]);
+var Model = require('web.Model');
+var session = require('web.session');
+var SystrayMenu = require('web.SystrayMenu');
+var Widget = require('web.Widget');
- var companies_list = '';
- _.each(session.user_companies.allowed_companies, function(company) {
- var a = '';
- if(company[0] == session.user_companies.current_company[0]) {
- a = '<i class="fa fa-check o_current_company"></i>';
- } else {
- a = '<span class="o_company"/>';
- }
- companies_list += '<li><a href="#" data-menu="company" data-company-id="' + company[0] + '">' + a + company[1] + '</a></li>';
+var SwitchCompanyMenu = Widget.extend({
+ template: 'SwitchCompanyMenu',
+ willStart: function() {
+ if (!session.user_companies) {
+ return $.Deferred().reject();
+ }
+ return this._super();
+ },
+ start: function() {
+ var self = this;
+ this.$el.on('click', '.dropdown-menu li a[data-menu]', _.debounce(function(ev) {
+ ev.preventDefault();
+ var company_id = $(ev.currentTarget).data('company-id');
+ new Model('res.users').call('write', [[session.uid], {'company_id': company_id}]).then(function() {
+ location.reload();
});
- self.$('.dropdown-menu').html(companies_list);
- return this._super();
- },
- });
+ }, 1500, true));
+
+ self.$('.oe_topbar_name').text(session.user_companies.current_company[1]);
+
+ var companies_list = '';
+ _.each(session.user_companies.allowed_companies, function(company) {
+ var a = '';
+ if (company[0] === session.user_companies.current_company[0]) {
+ a = '<i class="fa fa-check o_current_company"></i>';
+ } else {
+ a = '<span class="o_company"/>';
+ }
+ companies_list += '<li><a href="#" data-menu="company" data-company-id="' + company[0] + '">' + a + company[1] + '</a></li>';
+ });
+ self.$('.dropdown-menu').html(companies_list);
+ return this._super();
+ },
+});
+
+SystrayMenu.Items.push(SwitchCompanyMenu);
- SystrayMenu.Items.push(SwitchCompanyMenu);
});
diff --git a/addons/web/static/src/js/widgets/user_menu.js b/addons/web/static/src/js/widgets/user_menu.js
index 31728dfa813..edeb059790a 100644
--- a/addons/web/static/src/js/widgets/user_menu.js
+++ b/addons/web/static/src/js/widgets/user_menu.js
@@ -4,7 +4,7 @@ odoo.define('web.UserMenu', function (require) {
var core = require('web.core');
var Dialog = require('web.Dialog');
var framework = require('web.framework');
-var Model = require('web.DataModel');
+var Model = require('web.Model');
var session = require('web.session');
var Widget = require('web.Widget');
@@ -13,10 +13,6 @@ var QWeb = core.qweb;
var UserMenu = Widget.extend({
template: "UserMenu",
- init: function(parent) {
- this._super(parent);
- this.update_promise = $.Deferred().resolve();
- },
start: function() {
var self = this;
this.$el.on('click', '.dropdown-menu li a[data-menu]', function(ev) {
@@ -26,32 +22,28 @@ var UserMenu = Widget.extend({
f($(this));
}
});
- this.$el.parent().show();
- return this._super.apply(this, arguments);
+ return this._super.apply(this, arguments).then(function () {
+ return self.do_update();
+ });
},
do_update: function () {
var self = this;
- var fct = function() {
- var $avatar = self.$el.find('.oe_topbar_avatar');
- $avatar.attr('src', $avatar.data('default-src'));
- if (!session.uid)
- return;
- var func = new Model("res.users").get_func("read");
- return self.alive(func(session.uid, ["name"])).then(function(res) {
- var topbar_name = res.name;
- if(session.debug)
- topbar_name = _.str.sprintf("%s (%s)", topbar_name, session.db);
- self.$el.find('.oe_topbar_name').text(topbar_name);
- if (!session.debug) {
- topbar_name = _.str.sprintf("%s (%s)", topbar_name, session.db);
- }
- var avatar_src = session.url('/web/image', {model:'res.users', field: 'image_small', id: session.uid});
- $avatar.attr('src', avatar_src);
+ var $avatar = self.$('.oe_topbar_avatar');
+ $avatar.attr('src', $avatar.data('default-src'));
+ if (!session.uid) {
+ return $.when();
+ }
+ var func = new Model("res.users").get_func("read");
+ return self.alive(func(session.uid, ["name"])).then(function(res) {
+ var topbar_name = res.name;
+ if(session.debug) {
+ topbar_name = _.str.sprintf("%s (%s)", topbar_name, session.db);
+ }
+ self.$('.oe_topbar_name').text(topbar_name);
- core.bus.trigger('resize'); // Re-trigger the reflow logic
- });
- };
- this.update_promise = this.update_promise.then(fct, fct);
+ var avatar_src = session.url('/web/image', {model:'res.users', field: 'image_small', id: session.uid});
+ $avatar.attr('src', avatar_src);
+ });
},
on_menu_documentation: function () {
window.open('https://www.odoo.com/documentation/user', '_blank');
@@ -83,36 +75,42 @@ var UserMenu = Widget.extend({
},
on_menu_settings: function() {
var self = this;
- this.getParent().clear_uncommitted_changes().then(function() {
- self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
- result.res_id = session.uid;
- self.getParent().action_manager.do_action(result);
- });
+ this.trigger_up('clear_uncommitted_changes', {
+ callback: function() {
+ self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
+ result.res_id = session.uid;
+ self.do_action(result);
+ });
+ },
});
},
on_menu_account: function() {
- this.getParent().clear_uncommitted_changes().then(function() {
- var P = new Model('ir.config_parameter');
- P.call('get_param', ['database.uuid']).then(function(dbuuid) {
- var state = {
- 'd': session.db,
- 'u': window.location.protocol + '//' + window.location.host,
- };
- var params = {
- response_type: 'token',
- client_id: dbuuid || '',
- state: JSON.stringify(state),
- scope: 'userinfo',
- };
- framework.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params));
- }).fail(function(result, ev){
- ev.preventDefault();
- framework.redirect('https://accounts.odoo.com/account');
- });
+ this.trigger_up('clear_uncommitted_changes', {
+ callback: function() {
+ var P = new Model('ir.config_parameter');
+ P.call('get_param', ['database.uuid']).then(function(dbuuid) {
+ var state = {
+ 'd': session.db,
+ 'u': window.location.protocol + '//' + window.location.host,
+ };
+ var params = {
+ response_type: 'token',
+ client_id: dbuuid || '',
+ state: JSON.stringify(state),
+ scope: 'userinfo',
+ };
+ framework.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params));
+ }).fail(function(result, ev){
+ ev.preventDefault();
+ framework.redirect('https://accounts.odoo.com/account');
+ });
+ },
});
},
on_menu_logout: function() {
- this.trigger('user_logout');
+ this.trigger_up('clear_uncommitted_changes', {
+ callback: this.do_action.bind(this, 'logout'),
+ });
},
});