summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2018-10-22 11:03:17 +0000
committerAaron Bohy <aab@odoo.com>2018-10-23 10:56:12 +0000
commit03fe95d6af9d96f961c47d9acfaec498966b8cfa (patch)
tree4ae97e4007ef1642b21cdaae596201a17e3cb316
parent3c59add416fdba779675dc79580f6c0f56abc5a3 (diff)
[REF] web: small refactoring pass on some web components
(mainly views and ActionManager) - add missing docstrings - fix typos - correctly order functions and imports - put functions in correct sections - stop using 'event' as argument of handlers - don't call handlers directly - remove trailing spaces/tabs - move module created in stable version to its own file - follow js guidelines in general closes odoo/odoo#28024
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js10
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js33
-rw-r--r--addons/web/static/src/js/views/abstract_controller.js108
-rw-r--r--addons/web/static/src/js/views/abstract_view.js52
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js30
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js360
-rw-r--r--addons/web/static/src/js/views/basic/basic_renderer.js42
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js94
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js17
-rw-r--r--addons/web/static/src/js/views/graph/graph_controller.js10
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js5
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js92
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js31
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js117
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js56
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_controller.js53
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_renderer.js7
-rw-r--r--addons/web/static/src/js/views/view_utils.js47
-rw-r--r--addons/web/views/webclient_templates.xml1
19 files changed, 584 insertions, 581 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index 0dae6b64808..18bbb6a3681 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -77,7 +77,7 @@ var ActionManager = Widget.extend({
/**
* Called each time the action manager is attached into the DOM.
*/
- on_attach_callback: function() {
+ on_attach_callback: function () {
this.isInDOM = true;
var currentController = this.getCurrentController();
if (currentController) {
@@ -87,7 +87,7 @@ var ActionManager = Widget.extend({
/**
* Called each time the action manager is detached from the DOM.
*/
- on_detach_callback: function() {
+ on_detach_callback: function () {
this.isInDOM = false;
var currentController = this.getCurrentController();
if (currentController) {
@@ -573,12 +573,12 @@ var ActionManager = Widget.extend({
return $.Deferred();
} else {
var w = window.open(url, '_blank');
-         if (!w || w.closed || typeof w.closed === 'undefined') {
-             var message = _t('A popup window has been blocked. You ' +
+ if (!w || w.closed || typeof w.closed === 'undefined') {
+ var message = _t('A popup window has been blocked. You ' +
'may need to change your browser settings to allow ' +
'popup windows for this page.');
this.do_warn(_t('Warning'), message, true);
-         }
+ }
}
options.on_close();
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js
index d121ec929f0..7b87cb45dd3 100644
--- a/addons/web/static/src/js/chrome/action_manager_act_window.js
+++ b/addons/web/static/src/js/chrome/action_manager_act_window.js
@@ -22,10 +22,10 @@ ActionManager.include({
env_updated: '_onEnvUpdated',
execute_action: '_onExecuteAction',
get_controller_context: '_onGetControllerContext',
+ navigation_move: '_onNavigationMove',
update_filters: '_onUpdateFilters',
search: '_onSearch',
switch_view: '_onSwitchView',
- navigation_move: '_onNavigationMove',
}),
//--------------------------------------------------------------------------
@@ -815,6 +815,22 @@ ActionManager.include({
ev.data.callback(context || {});
},
/**
+ * Called mainly from the control panel when the focus should be given to a
+ * controller
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onNavigationMove : function (ev) {
+ switch(ev.data.direction) {
+ case 'down' :
+ var currentController = this.getCurrentController().widget;
+ currentController.giveFocus();
+ ev.stopPropagation();
+ break;
+ }
+ },
+ /**
* Handles a request to add/remove search view filters.
*
* @param {OdooEvent} ev
@@ -831,21 +847,6 @@ ActionManager.include({
data.callback(addedFilters);
},
/**
- * Called mainly from the control panel when the focus should be given to a controller
- *
- * @param {OdooEvent} event
- * @private
- */
- _onNavigationMove : function(event) {
- switch(event.data.direction) {
- case 'down' :
- var currentController = this.getCurrentController().widget;
- currentController.giveFocus();
- event.stopPropagation();
- break;
- }
- },
- /**
* Called when there is a change in the search view, so the current action's
* environment needs to be updated with the new domain, context and groupby.
*
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js
index 4bcb21f7a4d..d2d22a626d1 100644
--- a/addons/web/static/src/js/views/abstract_controller.js
+++ b/addons/web/static/src/js/views/abstract_controller.js
@@ -12,12 +12,12 @@ odoo.define('web.AbstractController', function (require) {
* reading localstorage, ...) has to go through the controller.
*/
+var AbstractAction = require('web.AbstractAction');
var ajax = require('web.ajax');
var concurrency = require('web.concurrency');
var config = require('web.config');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
-var AbstractAction = require('web.AbstractAction');
var QWeb = core.qweb;
@@ -124,12 +124,6 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
this.renderer.on_detach_callback();
},
- /**
- * Gives the focus to the renderer
- */
- giveFocus:function(){
- this.renderer.giveFocus();
- },
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -181,6 +175,12 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
return this.displayName;
},
/**
+ * Gives the focus to the renderer
+ */
+ giveFocus: function() {
+ this.renderer.giveFocus();
+ },
+ /**
* The use of this method is discouraged. It is still snakecased, because
* it currently is used in many templates, but we will move to a simpler
* mechanism as soon as we can.
@@ -283,23 +283,23 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
state: state || {},
});
},
- /**
- * Renders the html provided by the route specified by the
- * bannerRoute attribute on the controller (banner_route in the template).
- * Renders it before the view output and add a css class 'o_has_banner' to it.
- * There can be only one banner displayed at a time.
- *
- * If the banner contains stylesheet links or js files, they are moved to <head>
- * (and will only be fetched once).
- *
- * Route example:
- * @http.route('/module/hello', auth='user', type='json')
- * def hello(self):
- * return {'html': '<h1>hello, world</h1>'}
- *
- * @private
- * @returns {Deferred}
- */
+ /**
+ * Renders the html provided by the route specified by the
+ * bannerRoute attribute on the controller (banner_route in the template).
+ * Renders it before the view output and add a css class 'o_has_banner' to it.
+ * There can be only one banner displayed at a time.
+ *
+ * If the banner contains stylesheet links or js files, they are moved to <head>
+ * (and will only be fetched once).
+ *
+ * Route example:
+ * @http.route('/module/hello', auth='user', type='json')
+ * def hello(self):
+ * return {'html': '<h1>hello, world</h1>'}
+ *
+ * @private
+ * @returns {Deferred}
+ */
_renderBanner: function () {
if (this.bannerRoute !== undefined) {
var self = this;
@@ -441,30 +441,6 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
//--------------------------------------------------------------------------
/**
- * When an Odoo event arrives requesting a record to be opened, this method
- * gets the res_id, and request a switch view in the appropriate mode
- *
- * Note: this method seems wrong, it relies on the model being a basic model,
- * to get the res_id. It should receive the res_id in the event data
- * @todo move this to basic controller?
- *
- * @private
- * @param {OdooEvent} event
- * @param {number} event.data.id The local model ID for the record to be
- * opened
- * @param {string} [event.data.mode='readonly']
- */
- _onOpenRecord: function (event) {
- event.stopPropagation();
- var record = this.model.get(event.data.id, {raw: true});
- this.trigger_up('switch_view', {
- view_type: 'form',
- res_id: record.res_id,
- mode: event.data.mode || 'readonly',
- model: this.modelName,
- });
- },
- /**
* When a user clicks on an <a> link with type="action", we need to actually
* do the action. This kind of links is used a lot in no-content helpers.
*
@@ -475,10 +451,10 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
* reloaded after the dialog has been closed.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onActionClicked: function (event) {
- var $target = $(event.currentTarget);
+ _onActionClicked: function (ev) {
+ var $target = $(ev.currentTarget);
var self = this;
var model = $target.data('model');
var method = $target.data('method');
@@ -503,13 +479,37 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
}
},
/**
+ * When an Odoo event arrives requesting a record to be opened, this method
+ * gets the res_id, and request a switch view in the appropriate mode
+ *
+ * Note: this method seems wrong, it relies on the model being a basic model,
+ * to get the res_id. It should receive the res_id in the event data
+ * @todo move this to basic controller?
+ *
+ * @private
+ * @param {OdooEvent} ev
+ * @param {number} ev.data.id The local model ID for the record to be
+ * opened
+ * @param {string} [ev.data.mode='readonly']
+ */
+ _onOpenRecord: function (ev) {
+ ev.stopPropagation();
+ var record = this.model.get(ev.data.id, {raw: true});
+ this.trigger_up('switch_view', {
+ view_type: 'form',
+ res_id: record.res_id,
+ mode: ev.data.mode || 'readonly',
+ model: this.modelName,
+ });
+ },
+ /**
* Intercepts the 'switch_view' event to add the controllerID into the data,
* and lets the event bubble up.
*
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onSwitchView: function (event) {
- event.data.controllerID = this.controllerID;
+ _onSwitchView: function (ev) {
+ ev.data.controllerID = this.controllerID;
},
});
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index f3277d394a3..7705ee5972e 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -308,55 +308,3 @@ var AbstractView = Class.extend({
return AbstractView;
});
-
-odoo.define('web.viewUtils', function () {
-"use strict";
-
-/**
- * FIXME: move this module to its own file in master
- */
-
-var utils = {
- /**
- * Returns the value of a group dataPoint, i.e. the value of the groupBy
- * field for the records in that group.
- *
- * @param {Object} group dataPoint of type list, corresponding to a group
- * @param {string} groupByField the name of the groupBy field
- * @returns {string | integer | false}
- */
- getGroupValue: function (group, groupByField) {
- var groupedByField = group.fields[groupByField];
- switch (groupedByField.type) {
- case 'many2one':
- return group.res_id || false;
- case 'selection':
- var descriptor = _.findWhere(groupedByField.selection, group.value);
- return descriptor && descriptor[0];
- default:
- return group.value;
- }
- },
- /**
- * States whether or not the quick create feature is available for the given
- * datapoint, depending on its groupBy field.
- *
- * @param {Object} list dataPoint of type list
- * @returns {Boolean} true iff the kanban quick create feature is available
- */
- isQuickCreateEnabled: function (list) {
- var groupByField = list.groupedBy[0] && list.groupedBy[0].split(':')[0];
- if (!groupByField) {
- return false;
- }
- var availableTypes = ['char', 'boolean', 'many2one', 'selection'];
- if (!_.contains(availableTypes, list.fields[groupByField].type)) {
- return false;
- }
- return true;
- },
-};
-
-return utils;
-
-});
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index 43d604eac0b..0aff337dcbf 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -554,16 +554,16 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
*
* @todo: rename db_id into handle
*
- * @param {OdooEvent} event
- * @param {Object} event.data
- * @param {string} [event.data.db_id] handle of the data to reload and
+ * @param {OdooEvent} ev
+ * @param {Object} ev.data
+ * @param {string} [ev.data.db_id] handle of the data to reload and
* re-render (reload the whole form by default)
- * @param {string[]} [event.data.fieldNames] list of the record's fields to
+ * @param {string[]} [ev.data.fieldNames] list of the record's fields to
* reload
*/
- _onReload: function (event) {
- event.stopPropagation(); // prevent other controllers from handling this request
- var data = event && event.data || {};
+ _onReload: function (ev) {
+ ev.stopPropagation(); // prevent other controllers from handling this request
+ var data = ev && ev.data || {};
var handle = data.db_id;
if (handle) {
// reload the relational field given its db_id
@@ -589,28 +589,28 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* performed through the sidebar.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onSidebarDataAsked: function (event) {
+ _onSidebarDataAsked: function (ev) {
var sidebarEnv = this._getSidebarEnv();
- event.data.callback(sidebarEnv);
+ ev.data.callback(sidebarEnv);
},
/**
* open the translation view for the current field
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onTranslate: function (event) {
- event.stopPropagation();
+ _onTranslate: function (ev) {
+ ev.stopPropagation();
var self = this;
- var record = this.model.get(event.data.id, {raw: true});
+ var record = this.model.get(ev.data.id, {raw: true});
this._rpc({
route: '/web/dataset/call_button',
params: {
model: 'ir.translation',
method: 'translate_fields',
- args: [record.model, record.res_id, event.data.fieldName, record.getContext()],
+ args: [record.model, record.res_id, ev.data.fieldName, record.getContext()],
}
}).then(function (result) {
self.do_action(result, {
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index de879018ad5..f8d6daf081d 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -202,6 +202,27 @@ var BasicModel = AbstractModel.extend({
});
},
/**
+ * Completes the fields and fieldsInfo of a dataPoint with the given ones.
+ * It is useful for the cases where a record element is shared between
+ * various views, such as a one2many with a tree and a form view.
+ *
+ * @param {string} recordID a valid element ID
+ * @param {Object} viewInfo
+ * @param {Object} viewInfo.fields
+ * @param {Object} viewInfo.fieldsInfo
+ */
+ addFieldsInfo: function (recordID, viewInfo) {
+ var record = this.localData[recordID];
+ record.fields = _.extend({}, record.fields, viewInfo.fields);
+ // complete the given fieldsInfo with the fields of the main view, so
+ // that those field will be reloaded if a reload is triggered by the
+ // secondary view
+ var fieldsInfo = _.mapObject(viewInfo.fieldsInfo, function (fieldsInfo) {
+ return _.defaults({}, fieldsInfo, record.fieldsInfo[record.viewType]);
+ });
+ record.fieldsInfo = _.extend({}, record.fieldsInfo, fieldsInfo);
+ },
+ /**
* Add and process default values for a given record. Those values are
* parsed and stored in the '_changes' key of the record. For relational
* fields, sub-dataPoints are created, and missing relational data is
@@ -309,108 +330,52 @@ var BasicModel = AbstractModel.extend({
});
},
/**
- * Compute the default value that the handle field should take.
- * We need to compute this in order for new lines to be added at the correct position.
+ * Returns true if a record can be abandoned.
*
- * @private
- * @param {Object} listID
- * @param {string} position
- * @return {Object} empty object if no overrie has to be done, or:
- * field: the name of the field to override,
- * value: the value to use for that field
+ * Case for not abandoning the record:
+ *
+ * 1. flagged as 'no abandon' (i.e. during a `default_get`, including any
+ * `onchange` from a `default_get`)
+ * 2. registered in a list on addition
+ *
+ * 2.1. registered as non-new addition
+ * 2.2. registered as new additon on update
+ *
+ * 3. record is not new
+ *
+ * Otherwise, the record can be abandoned.
+ *
+ * This is useful when discarding changes on this record, as it means that
+ * we must keep the record even if some fields are invalids (e.g. required
+ * field is empty).
+ *
+ * @param {string} id id for a local resource
+ * @returns {boolean}
*/
- _computeOverrideDefaultFields: function (listID, position) {
- var list = this.localData[listID];
- var handleField;
-
- // Here listID is actually just parentID, it's not yet confirmed
- // to be a list.
- // If we are not in the case that interests us,
- // listID will be undefined and this check will work.
- if (!list) {
- return {};
- }
-
- position = position || 'bottom';
-
- // Let's find if there is a field with handle.
- if (!list.fieldsInfo) {
- return {};
+ canBeAbandoned: function (id) {
+ // 1. no drop if flagged
+ if (this.localData[id]._noAbandon) {
+ return false;
}
- for (var field in list.fieldsInfo.list) {
- if (list.fieldsInfo.list[field].widget === 'handle') {
- handleField = field;
- break;
- // If there are 2 handle fields on the same list,
- // we take the first one we find.
- // And that will be alphabetically on the field name...
+ // 2. no drop in a list on "ADD in some cases
+ var record = this.localData[id];
+ var parent = this.localData[record.parentID];
+ if (parent) {
+ var entry = _.findWhere(parent._savePoint, {operation: 'ADD', id: id});
+ if (entry) {
+ // 2.1. no drop on non-new addition in list
+ if (!entry.isNew) {
+ return false;
+ }
+ // 2.2. no drop on new addition on "UPDATE"
+ var lastEntry = _.last(parent._savePoint);
+ if (lastEntry.operation === 'UPDATE' && lastEntry.id === id) {
+ return false;
+ }
}
}
-
- if (!handleField) {
- return {};
- }
-
- // We don't want to override the default value
- // if the list is not ordered by the handle field.
- var isOrderedByHandle = list.orderedBy
- && list.orderedBy.length
- && list.orderedBy[0].asc === true
- && list.orderedBy[0].name === handleField;
-
- if (!isOrderedByHandle) {
- return {};
- }
-
- // We compute the list (get) to apply the pending changes before doing our work,
- // otherwise new lines might not be taken into account.
- // We use raw: true because we only need to load the first level of relation.
- var computedList = this.get(list.id, {raw: true});
-
- // We don't need to worry about the position of a new line if the list is empty.
- if (!computedList || !computedList.data || !computedList.data.length) {
- return {};
- }
-
- // If there are less elements in the list than the limit of
- // the page then take the index of the last existing line.
-
- // If the button is at the top, we want the new element on
- // the first line of the page.
-
- // If the button is at the bottom, we want the new element
- // after the last line of the page
- // (= theorically it will be the first element of the next page).
-
- // We ignore list.offset because computedList.data
- // will only have the current page elements.
-
- var index = Math.min(
- computedList.data.length - 1,
- position !== 'top' ? list.limit - 1 : 0
- );
-
- // This positioning will almost be correct. There might just be
- // an issue if several other lines have the same handleFieldValue.
-
- // TODO ideally: if there is an element with the same handleFieldValue,
- // that one and all the following elements must be incremented
- // by 1 (at least until there is a gap in the numbering).
-
- // We don't do it now because it's not an important case.
- // However, we can for sure increment by 1 if we are on the last page.
-
- var handleFieldValue = computedList.data[index].data[handleField];
- if (position === 'top') {
- handleFieldValue--;
- } else if (list.count <= list.offset + list.limit - (list.tempLimitIncrement || 0)) {
- handleFieldValue++;
- }
-
- return {
- field: handleField,
- value: handleFieldValue,
- };
+ // 3. drop new records
+ return this.isNew(id);
},
/**
* Delete a list of records, then, if the records have a parent, reload it.
@@ -518,6 +483,20 @@ var BasicModel = AbstractModel.extend({
});
},
/**
+ * For list resources, this freezes the current records order.
+ *
+ * @param {string} listID a valid element ID of type list
+ */
+ freezeOrder: function (listID) {
+ var list = this.localData[listID];
+ if (list.type === 'record') {
+ return;
+ }
+ list = this._applyX2ManyOperations(list);
+ this._sortList(list);
+ this.localData[listID].orderedResIDs = list.res_ids;
+ },
+ /**
* The get method first argument is the handle returned by the load method.
* It is optional (the handle can be undefined). In some case, it makes
* sense to use the handle as a key, for example the BasicModel holds the
@@ -692,54 +671,6 @@ var BasicModel = AbstractModel.extend({
return _t("New");
},
/**
- * Returns true if a record can be abandoned.
- *
- * Case for not abandoning the record:
- *
- * 1. flagged as 'no abandon' (i.e. during a `default_get`, including any
- * `onchange` from a `default_get`)
- * 2. registered in a list on addition
- *
- * 2.1. registered as non-new addition
- * 2.2. registered as new additon on update
- *
- * 3. record is not new
- *
- * Otherwise, the record can be abandoned.
- *
- * This is useful when discarding changes on this record, as it means that
- * we must keep the record even if some fields are invalids (e.g. required
- * field is empty).
- *
- * @param {string} id id for a local resource
- * @returns {boolean}
- */
- canBeAbandoned: function (id) {
- // 1. no drop if flagged
- if (this.localData[id]._noAbandon) {
- return false;
- }
- // 2. no drop in a list on "ADD in some cases
- var record = this.localData[id];
- var parent = this.localData[record.parentID];
- if (parent) {
- var entry = _.findWhere(parent._savePoint, {operation: 'ADD', id: id});
- if (entry) {
- // 2.1. no drop on non-new addition in list
- if (!entry.isNew) {
- return false;
- }
- // 2.2. no drop on new addition on "UPDATE"
- var lastEntry = _.last(parent._savePoint);
- if (lastEntry.operation === 'UPDATE' && lastEntry.id === id) {
- return false;
- }
- }
- }
- // 3. drop new records
- return this.isNew(id);
- },
- /**
* Returns true if a record is dirty. A record is considered dirty if it has
* some unsaved changes, marked by the _isDirty property on the record or
* one of its subrecords.
@@ -1176,41 +1107,6 @@ var BasicModel = AbstractModel.extend({
});
},
/**
- * Completes the fields and fieldsInfo of a dataPoint with the given ones.
- * It is useful for the cases where a record element is shared between
- * various views, such as a one2many with a tree and a form view.
- *
- * @param {string} recordID a valid element ID
- * @param {Object} viewInfo
- * @param {Object} viewInfo.fields
- * @param {Object} viewInfo.fieldsInfo
- */
- addFieldsInfo: function (recordID, viewInfo) {
- var record = this.localData[recordID];
- record.fields = _.extend({}, record.fields, viewInfo.fields);
- // complete the given fieldsInfo with the fields of the main view, so
- // that those field will be reloaded if a reload is triggered by the
- // secondary view
- var fieldsInfo = _.mapObject(viewInfo.fieldsInfo, function (fieldsInfo) {
- return _.defaults({}, fieldsInfo, record.fieldsInfo[record.viewType]);
- });
- record.fieldsInfo = _.extend({}, record.fieldsInfo, fieldsInfo);
- },
- /**
- * For list resources, this freezes the current records order.
- *
- * @param {string} listID a valid element ID of type list
- */
- freezeOrder: function (listID) {
- var list = this.localData[listID];
- if (list.type === 'record') {
- return;
- }
- list = this._applyX2ManyOperations(list);
- this._sortList(list);
- this.localData[listID].orderedResIDs = list.res_ids;
- },
- /**
* Manually sets a resource as dirty. This is used to notify that a field
* has been modified, but with an invalid value. In that case, the value is
* not sent to the basic model, but the record should still be flagged as
@@ -2062,6 +1958,110 @@ var BasicModel = AbstractModel.extend({
return hasOnchange ? specs : false;
},
/**
+ * Compute the default value that the handle field should take.
+ * We need to compute this in order for new lines to be added at the correct position.
+ *
+ * @private
+ * @param {Object} listID
+ * @param {string} position
+ * @return {Object} empty object if no overrie has to be done, or:
+ * field: the name of the field to override,
+ * value: the value to use for that field
+ */
+ _computeOverrideDefaultFields: function (listID, position) {
+ var list = this.localData[listID];
+ var handleField;
+
+ // Here listID is actually just parentID, it's not yet confirmed
+ // to be a list.
+ // If we are not in the case that interests us,
+ // listID will be undefined and this check will work.
+ if (!list) {
+ return {};
+ }
+
+ position = position || 'bottom';
+
+ // Let's find if there is a field with handle.
+ if (!list.fieldsInfo) {
+ return {};
+ }
+ for (var field in list.fieldsInfo.list) {
+ if (list.fieldsInfo.list[field].widget === 'handle') {
+ handleField = field;
+ break;
+ // If there are 2 handle fields on the same list,
+ // we take the first one we find.
+ // And that will be alphabetically on the field name...
+ }
+ }
+
+ if (!handleField) {
+ return {};
+ }
+
+ // We don't want to override the default value
+ // if the list is not ordered by the handle field.
+ var isOrderedByHandle = list.orderedBy
+ && list.orderedBy.length
+ && list.orderedBy[0].asc === true
+ && list.orderedBy[0].name === handleField;
+
+ if (!isOrderedByHandle) {
+ return {};
+ }
+
+ // We compute the list (get) to apply the pending changes before doing our work,
+ // otherwise new lines might not be taken into account.
+ // We use raw: true because we only need to load the first level of relation.
+ var computedList = this.get(list.id, {raw: true});
+
+ // We don't need to worry about the position of a new line if the list is empty.
+ if (!computedList || !computedList.data || !computedList.data.length) {
+ return {};
+ }
+
+ // If there are less elements in the list than the limit of
+ // the page then take the index of the last existing line.
+
+ // If the button is at the top, we want the new element on
+ // the first line of the page.
+
+ // If the button is at the bottom, we want the new element
+ // after the last line of the page
+ // (= theorically it will be the first element of the next page).
+
+ // We ignore list.offset because computedList.data
+ // will only have the current page elements.
+
+ var index = Math.min(
+ computedList.data.length - 1,
+ position !== 'top' ? list.limit - 1 : 0
+ );
+
+ // This positioning will almost be correct. There might just be
+ // an issue if several other lines have the same handleFieldValue.
+
+ // TODO ideally: if there is an element with the same handleFieldValue,
+ // that one and all the following elements must be incremented
+ // by 1 (at least until there is a gap in the numbering).
+
+ // We don't do it now because it's not an important case.
+ // However, we can for sure increment by 1 if we are on the last page.
+
+ var handleFieldValue = computedList.data[index].data[handleField];
+ if (position === 'top') {
+ handleFieldValue--;
+ } else if (list.count <= list.offset + list.limit - (list.tempLimitIncrement || 0)) {
+ handleFieldValue++;
+ }
+
+ return {
+ field: handleField,
+ value: handleFieldValue,
+ };
+ },
+ /**
* Evaluate modifiers
*
* @private
diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js
index 33267283a15..8db200183ef 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -136,31 +136,12 @@ var BasicRenderer = AbstractRenderer.extend({
dom.setSelectionRange(field.getFocusableElement().get(0), {start: offset, end: offset});
}
},
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
- * Add a tooltip on a $node, depending on a field description
- *
- * @param {FieldWidget} widget
- * @param {$node} $node
- */
- _addFieldTooltip: function (widget, $node) {
- // optional argument $node, the jQuery element on which the tooltip
- // should be attached if not given, the tooltip is attached on the
- // widget's $el
- $node = $node.length ? $node : widget.$el;
- $node.tooltip({
- title: function () {
- return qweb.render('WidgetLabel.tooltip', {
- debug: config.debug,
- widget: widget,
- });
- }
- });
- },
- /**
* Activates the widget at the given index for the given record if possible
* or the "next" possible one. Usually, a widget can be activated if it is
* in edit mode, and if it is visible.
@@ -239,6 +220,26 @@ var BasicRenderer = AbstractRenderer.extend({
return this._activateFieldWidget(record, currentIndex, {inc:-1});
},
/**
+ * Add a tooltip on a $node, depending on a field description
+ *
+ * @param {FieldWidget} widget
+ * @param {$node} $node
+ */
+ _addFieldTooltip: function (widget, $node) {
+ // optional argument $node, the jQuery element on which the tooltip
+ // should be attached if not given, the tooltip is attached on the
+ // widget's $el
+ $node = $node.length ? $node : widget.$el;
+ $node.tooltip({
+ title: function () {
+ return qweb.render('WidgetLabel.tooltip', {
+ debug: config.debug,
+ widget: widget,
+ });
+ }
+ });
+ },
+ /**
* Does the necessary DOM updates to match the given modifiers data. The
* modifiers data is supposed to contain the properly evaluated modifiers
* associated to the given records and elements.
@@ -685,7 +686,6 @@ var BasicRenderer = AbstractRenderer.extend({
return $el;
},
-
/**
* Rerenders a given widget and make sure the associated data which
* referenced the old one is updated.
diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index 7586dcc4bab..7587e52fcd0 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -2,9 +2,9 @@ odoo.define('web.FormController', function (require) {
"use strict";
var BasicController = require('web.BasicController');
-var dialogs = require('web.view_dialogs');
var core = require('web.core');
var Dialog = require('web.Dialog');
+var dialogs = require('web.view_dialogs');
var Sidebar = require('web.Sidebar');
var _t = core._t;
@@ -40,12 +40,20 @@ var FormController = BasicController.extend({
this.hasSidebar = params.hasSidebar;
this.toolbarActions = params.toolbarActions || {};
},
+ /**
+ * Called each time the form view is attached into the DOM
+ *
+ * @todo convert to new style
+ */
+ on_attach_callback: function () {
+ this._super.apply(this, arguments);
+ this.autofocus();
+ },
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
-
/**
* Calls autofocus on the renderer
*/
@@ -107,15 +115,6 @@ var FormController = BasicController.extend({
return this.model.getName(this.handle);
},
/**
- * Called each time the form view is attached into the DOM
- *
- * @todo convert to new style
- */
- on_attach_callback: function () {
- this._super.apply(this, arguments);
- this.autofocus();
- },
- /**
* Render buttons for the control panel. The form view can be rendered in
* a dialog, and in that case, if we have buttons defined in the footer, we
* have to use them instead of the standard buttons.
@@ -249,6 +248,7 @@ var FormController = BasicController.extend({
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
+
/**
* Assign on the buttons save and discard additionnal behavior to facilitate
* the work of the users doing input only using the keyboard
@@ -259,18 +259,18 @@ var FormController = BasicController.extend({
*/
_assignSaveCancelKeyboardBehavior: function ($saveCancelButtonContainer) {
var self = this;
- $saveCancelButtonContainer.children().on('keydown', function(e) {
+ $saveCancelButtonContainer.children().on('keydown', function (e) {
switch(e.which) {
case $.ui.keyCode.ENTER:
e.preventDefault();
- self.saveRecord.apply(self);
+ self.saveRecord();
break;
case $.ui.keyCode.ESCAPE:
e.preventDefault();
- self._discardChanges.apply(self);
+ self._discardChanges();
break;
case $.ui.keyCode.TAB:
- if (!e.shiftKey && e.target.classList.contains("btn-primary")) {
+ if (!e.shiftKey && e.target.classList.contains('btn-primary')) {
$saveCancelButtonContainer.tooltip('show');
e.preventDefault();
}
@@ -435,12 +435,12 @@ var FormController = BasicController.extend({
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onButtonClicked: function (event) {
+ _onButtonClicked: function (ev) {
// stop the event's propagation as a form controller might have other
// form controllers in its descendants (e.g. in a FormViewDialog)
- event.stopPropagation();
+ ev.stopPropagation();
var self = this;
var def;
@@ -453,11 +453,11 @@ var FormController = BasicController.extend({
// we need to reget the record to make sure we have changes made
// by the basic model, such as the new res_id, if the record is
// new.
- var record = self.model.get(event.data.record.id);
+ var record = self.model.get(ev.data.record.id);
return self._callButtonAction(attrs, record);
});
}
- var attrs = event.data.attrs;
+ var attrs = ev.data.attrs;
if (attrs.confirm) {
var d = $.Deferred();
Dialog.confirm(this, attrs.confirm, {
@@ -467,7 +467,7 @@ var FormController = BasicController.extend({
});
def = d.promise();
} else if (attrs.special === 'cancel') {
- def = this._callButtonAction(attrs, event.data.record);
+ def = this._callButtonAction(attrs, ev.data.record);
} else if (!attrs.special || attrs.special === 'save') {
// save the record but don't switch to readonly mode
def = saveAndExecuteAction();
@@ -503,16 +503,16 @@ var FormController = BasicController.extend({
/**
* Destroy subdialog widgets after an action is finished.
*
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
* @private
*/
- _onDoAction: function (event) {
- var self=this;
+ _onDoAction: function (ev) {
+ var self = this;
// A priori, different widgets could write on the "on_success" key.
// Below we ensure that all the actions required by those widgets
// are executed in a suitable order before every cycle of destruction.
- var callback = event.data.on_success || function () {};
- event.data.on_success = function () {
+ var callback = ev.data.on_success || function () {};
+ ev.data.on_success = function () {
callback();
function isDialog (widget) {
return (widget instanceof Dialog);
@@ -589,11 +589,11 @@ var FormController = BasicController.extend({
* with the one of the form view.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onOpenOne2ManyRecord: function (event) {
- event.stopPropagation();
- var data = event.data;
+ _onOpenOne2ManyRecord: function (ev) {
+ ev.stopPropagation();
+ var data = ev.data;
var record;
if (data.id) {
record = this.model.get(data.id, {raw: true});
@@ -613,29 +613,29 @@ var FormController = BasicController.extend({
res_id: record && record.res_id,
res_model: data.field.relation,
shouldSaveLocally: true,
- title: (record ? _t("Open: ") : _t("Create ")) + (event.target.string || data.field.string),
+ title: (record ? _t("Open: ") : _t("Create ")) + (ev.target.string || data.field.string),
}).open();
},
/**
* Open an existing record in a form view dialog
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onOpenRecord: function (event) {
- event.stopPropagation();
+ _onOpenRecord: function (ev) {
+ ev.stopPropagation();
var self = this;
- var record = this.model.get(event.data.id, {raw: true});
+ var record = this.model.get(ev.data.id, {raw: true});
new dialogs.FormViewDialog(self, {
- context: event.data.context,
- fields_view: event.data.fields_view,
- on_saved: event.data.on_saved,
- on_remove: event.data.on_remove,
- readonly: event.data.readonly,
- deletable: event.data.deletable,
+ context: ev.data.context,
+ fields_view: ev.data.fields_view,
+ on_saved: ev.data.on_saved,
+ on_remove: ev.data.on_remove,
+ readonly: ev.data.readonly,
+ deletable: ev.data.deletable,
res_id: record.res_id,
res_model: record.model,
- title: _t("Open: ") + event.data.string,
+ title: _t("Open: ") + ev.data.string,
}).open();
},
/**
@@ -681,13 +681,13 @@ var FormController = BasicController.extend({
* in a x2many list view
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onToggleColumnOrder: function (event) {
- event.stopPropagation();
+ _onToggleColumnOrder: function (ev) {
+ ev.stopPropagation();
var self = this;
- this.model.setSort(event.data.id, event.data.name).then(function () {
- var field = event.data.field;
+ this.model.setSort(ev.data.id, ev.data.name).then(function () {
+ var field = ev.data.field;
var state = self.model.get(self.handle);
self.renderer.confirmChange(state, state.id, [field]);
});
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index c7a32b72ba9..1da3fb97607 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -974,9 +974,14 @@ var FormRenderer = BasicRenderer.extend({
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
- _onActivateNextWidget: function (e) {
- e.stopPropagation();
- var index = this.allFieldWidgets[this.state.id].indexOf(e.data.target);
+
+ /**
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onActivateNextWidget: function (ev) {
+ ev.stopPropagation();
+ var index = this.allFieldWidgets[this.state.id].indexOf(ev.data.target);
this._activateNextFieldWidget(this.state, index);
},
/**
@@ -1011,9 +1016,9 @@ var FormRenderer = BasicRenderer.extend({
* @private
* @param {MouseEvent} ev
*/
- _onTranslate: function (event) {
- event.preventDefault();
- this.trigger_up('translate', {fieldName: event.target.name, id: this.state.id});
+ _onTranslate: function (ev) {
+ ev.preventDefault();
+ this.trigger_up('translate', {fieldName: ev.target.name, id: this.state.id});
},
});
diff --git a/addons/web/static/src/js/views/graph/graph_controller.js b/addons/web/static/src/js/views/graph/graph_controller.js
index 48f15de8902..09992695506 100644
--- a/addons/web/static/src/js/views/graph/graph_controller.js
+++ b/addons/web/static/src/js/views/graph/graph_controller.js
@@ -175,17 +175,17 @@ var GraphController = AbstractController.extend(GroupByMenuInterfaceMixin,{
* Do what need to be done when a button from the control panel is clicked.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onButtonClick: function (event) {
- var $target = $(event.target);
+ _onButtonClick: function (ev) {
+ var $target = $(ev.target);
var parent;
var field;
if ($target.hasClass('o_graph_button')) {
this._setMode($target.data('mode'));
} else if ($target.parents('.o_graph_measures_list').length) {
- event.preventDefault();
- event.stopPropagation();
+ ev.preventDefault();
+ ev.stopPropagation();
field = $target.data('field');
this._setMeasure(field);
}
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index 0517e14c876..e68a4446ba5 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -68,6 +68,11 @@ return AbstractRenderer.extend({
this._super.apply(this, arguments);
this.isInDOM = false;
},
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
/**
* @override
* @param {Object} state
diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js
index e195b33068c..0c26e38c18b 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -206,11 +206,11 @@ var KanbanController = BasicController.extend({
* a group and to update the renderer
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onAddColumn: function (event) {
+ _onAddColumn: function (ev) {
var self = this;
- this.model.createGroup(event.data.value, this.handle).then(function () {
+ this.model.createGroup(ev.data.value, this.handle).then(function () {
var state = self.model.get(self.handle, {raw: true});
var ids = _.pluck(state.data, 'res_id').filter(_.isNumber);
return self._resequenceColumns(ids);
@@ -223,15 +223,15 @@ var KanbanController = BasicController.extend({
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onAddRecordToColumn: function (event) {
+ _onAddRecordToColumn: function (ev) {
var self = this;
- var record = event.data.record;
- var column = event.target;
+ var record = ev.data.record;
+ var column = ev.target;
this.alive(this.model.moveRecord(record.db_id, column.db_id, this.handle))
.then(function (column_db_ids) {
- return self._resequenceRecords(column.db_id, event.data.ids)
+ return self._resequenceRecords(column.db_id, ev.data.ids)
.then(function () {
_.each(column_db_ids, function (db_id) {
var data = self.model.get(db_id);
@@ -242,12 +242,12 @@ var KanbanController = BasicController.extend({
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onButtonClicked: function (event) {
- event.stopPropagation();
- var attrs = event.data.attrs;
- var record = event.data.record;
+ _onButtonClicked: function (ev) {
+ ev.stopPropagation();
+ var attrs = ev.data.attrs;
+ var record = ev.data.record;
if (attrs.context) {
attrs.context = new Context(attrs.context)
.set_eval_context({
@@ -264,7 +264,7 @@ var KanbanController = BasicController.extend({
model: record.model,
resIDs: record.res_ids,
},
- on_closed: this._reloadAfterButtonClick.bind(this, event.target, event.data),
+ on_closed: this._reloadAfterButtonClick.bind(this, ev.target, ev.data),
});
},
/**
@@ -294,28 +294,28 @@ var KanbanController = BasicController.extend({
* Moves the focus from the controller buttons to the first kanban record
*
* @private
- * @param {jQueryEvent} e
+ * @param {jQueryEvent} ev
*/
- _onButtonsKeyDown: function (e) {
- switch(e.keyCode) {
+ _onButtonsKeyDown: function (ev) {
+ switch(ev.keyCode) {
case $.ui.keyCode.DOWN:
this.$('.o_kanban_record:first').focus();
}
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onColumnResequence: function (event) {
- this._resequenceRecords(event.target.db_id, event.data.ids);
+ _onColumnResequence: function (ev) {
+ this._resequenceRecords(ev.target.db_id, ev.data.ids);
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onDeleteColumn: function (event) {
+ _onDeleteColumn: function (ev) {
var self = this;
- var column = event.target;
+ var column = ev.target;
var state = this.model.get(this.handle, {raw: true});
var relatedModelName = state.fields[state.groupedBy[0]].relation;
this.model
@@ -334,26 +334,26 @@ var KanbanController = BasicController.extend({
* lazy loaded)
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onLoadColumnRecords: function (event) {
+ _onLoadColumnRecords: function (ev) {
var self = this;
- this.model.loadColumnRecords(event.data.columnID).then(function (dbID) {
+ this.model.loadColumnRecords(ev.data.columnID).then(function (dbID) {
var data = self.model.get(dbID);
self.renderer.updateColumn(dbID, data);
self._updateEnv();
- if (event.data.onSuccess) {
- event.data.onSuccess();
+ if (ev.data.onSuccess) {
+ ev.data.onSuccess();
}
});
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onLoadMore: function (event) {
+ _onLoadMore: function (ev) {
var self = this;
- var column = event.target;
+ var column = ev.target;
this.model.loadMore(column.db_id).then(function (db_id) {
var data = self.model.get(db_id);
self.renderer.updateColumn(db_id, data);
@@ -416,34 +416,34 @@ var KanbanController = BasicController.extend({
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onRecordDelete: function (event) {
- this._deleteRecords([event.data.id]);
+ _onRecordDelete: function (ev) {
+ this._deleteRecords([ev.data.id]);
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onResequenceColumn: function (event) {
+ _onResequenceColumn: function (ev) {
var self = this;
- this._resequenceColumns(event.data.ids).then(function () {
+ this._resequenceColumns(ev.data.ids).then(function () {
self._updateEnv();
});
},
/**
* @private
- * @param {OdooEvent} event
- * @param {boolean} [event.data.openQuickCreate=false] if true, opens the
+ * @param {OdooEvent} ev
+ * @param {boolean} [ev.data.openQuickCreate=false] if true, opens the
* QuickCreate in the toggled column (it assumes that we are opening it)
*/
- _onToggleColumn: function (event) {
+ _onToggleColumn: function (ev) {
var self = this;
- var column = event.target;
+ var column = ev.target;
this.model.toggleGroup(column.db_id).then(function (db_id) {
var data = self.model.get(db_id);
var options = {
- openQuickCreate: !!event.data.openQuickCreate,
+ openQuickCreate: !!ev.data.openQuickCreate,
};
self.renderer.updateColumn(db_id, data, options);
self._updateEnv();
@@ -464,12 +464,12 @@ var KanbanController = BasicController.extend({
* Allow the user to archive/restore all the records of a column.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onToggleActiveRecords: function (event) {
+ _onToggleActiveRecords: function (ev) {
var self = this;
- var active = !event.data.archive;
- var column = event.target;
+ var active = !ev.data.archive;
+ var column = ev.target;
var recordIds = _.pluck(column.records, 'db_id');
if (recordIds.length) {
this.model
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js
index 9356311d51c..ca5882c965d 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -2,10 +2,10 @@ odoo.define('web.KanbanRenderer', function (require) {
"use strict";
var BasicRenderer = require('web.BasicRenderer');
+var ColumnQuickCreate = require('web.kanban_column_quick_create');
var core = require('web.core');
var KanbanColumn = require('web.KanbanColumn');
var KanbanRecord = require('web.KanbanRecord');
-var ColumnQuickCreate = require('web.kanban_column_quick_create');
var QWeb = require('web.QWeb');
var session = require('web.session');
var utils = require('web.utils');
@@ -151,7 +151,10 @@ var KanbanRenderer = BasicRenderer.extend({
addQuickCreate: function () {
return this.widgets[0].addQuickCreate();
},
- giveFocus:function() {
+ /**
+ * Focuses the first kanban record
+ */
+ giveFocus: function () {
this.$('.o_kanban_record:first').focus();
},
/**
@@ -249,21 +252,22 @@ var KanbanRenderer = BasicRenderer.extend({
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
+
/**
- * @private
- * @param {DOMElement} currentColumn
- */
- _focusOnNextCard: function(currentCardElement) {
+ * @private
+ * @param {DOMElement} currentColumn
+ */
+ _focusOnNextCard: function (currentCardElement) {
var nextCard = currentCardElement.nextElementSibling;
if (nextCard) {
nextCard.focus();
}
},
/**
- * @private
- * @param {DOMElement} currentColumn
- */
- _focusOnPreviousCard: function(currentCardElement) {
+ * @private
+ * @param {DOMElement} currentColumn
+ */
+ _focusOnPreviousCard: function (currentCardElement) {
var previousCard = currentCardElement.previousElementSibling;
if (previousCard && previousCard.classList.contains("o_kanban_record")) { //previous element might be column title
previousCard.focus();
@@ -500,6 +504,7 @@ var KanbanRenderer = BasicRenderer.extend({
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
+
/**
* Closes the opened quick create widgets in columns
*
@@ -513,10 +518,10 @@ var KanbanRenderer = BasicRenderer.extend({
},
/**
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onQuickCreateColumnUpdated: function (event) {
- event.stopPropagation();
+ _onQuickCreateColumnUpdated: function (ev) {
+ ev.stopPropagation();
this._toggleNoContentHelper();
},
/**
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index 3691903c079..612cc1eee02 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -18,6 +18,11 @@ var _t = core._t;
var qweb = core.qweb;
var ListController = BasicController.extend({
+ /**
+ * This key contains the name of the buttons template to render on top of
+ * the list view. It can be overridden to add buttons in specific child views.
+ */
+ buttons_template: 'ListView.buttons',
custom_events: _.extend({}, BasicController.prototype.custom_events, {
add_record: '_onAddRecord',
button_clicked: '_onButtonClicked',
@@ -105,11 +110,6 @@ var ListController = BasicController.extend({
});
},
/**
- * This key contains the name of the buttons template to render on top of
- * the form view. It can be overridden to add buttons in specific child views.
- */
- buttons_template: 'ListView.buttons',
- /**
* Display and bind all buttons in the control panel
*
* Note: clicking on the "Save" button does nothing special. Indeed, all
@@ -154,13 +154,13 @@ var ListController = BasicController.extend({
label: _t("Archive"),
callback: function () {
Dialog.confirm(self, _t("Are you sure that you want to archive all the selected records?"), {
- confirm_callback: self._onToggleArchiveState.bind(self, true),
+ confirm_callback: self._toggleArchiveState.bind(self, true),
});
}
});
other.push({
label: _t("Unarchive"),
- callback: this._onToggleArchiveState.bind(this, false)
+ callback: this._toggleArchiveState.bind(this, false)
});
}
if (this.is_action_enabled('delete')) {
@@ -351,6 +351,15 @@ var ListController = BasicController.extend({
}
},
/**
+ * Called when clicking on 'Archive' or 'Unarchive' in the sidebar.
+ *
+ * @private
+ * @param {boolean} archive
+ */
+ _toggleArchiveState: function (archive) {
+ this._archive(this.selectedRecords, archive);
+ },
+ /**
* Display the sidebar (the 'action' menu in the control panel) if we have
* some selected records.
*/
@@ -387,25 +396,25 @@ var ListController = BasicController.extend({
* Add a record to the list
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onAddRecord: function (event) {
- event.stopPropagation();
+ _onAddRecord: function (ev) {
+ ev.stopPropagation();
if (this.activeActions.create) {
this._addRecord();
- } else if (event.data.onFail) {
- event.data.onFail();
+ } else if (ev.data.onFail) {
+ ev.data.onFail();
}
},
/**
* Handles a click on a button by performing its action.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onButtonClicked: function (event) {
- event.stopPropagation();
- this._callButtonAction(event.data.attrs, event.data.record);
+ _onButtonClicked: function (ev) {
+ ev.stopPropagation();
+ this._callButtonAction(ev.data.attrs, ev.data.record);
},
/**
* When the user clicks on the 'create' button, two things can happen. We
@@ -413,14 +422,14 @@ var ListController = BasicController.extend({
* mode, or we can edit inline.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onCreateRecord: function (event) {
+ _onCreateRecord: function (ev) {
// we prevent the event propagation because we don't want this event to
// trigger a click on the main bus, which would be then caught by the
// list editable renderer and would unselect the newly created row
- if (event) {
- event.stopPropagation();
+ if (ev) {
+ ev.stopPropagation();
}
var state = this.model.get(this.handle, {raw: true});
if (this.editable && !state.groupedBy.length) {
@@ -476,35 +485,23 @@ var ListController = BasicController.extend({
new DataExport(this, record, defaultExportFields).open();
},
/**
- * Called when the renderer displays an editable row and the user tries to
- * leave it -> Saves the record associated to that line.
- *
- * @param {OdooEvent} ev
- */
- _onSaveLine: function (ev) {
- var recordID = ev.data.recordID;
- this.saveRecord(recordID)
- .done(ev.data.onSuccess)
- .fail(ev.data.onFailure);
- },
- /**
* Force a resequence of the records curently on this page.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onResequence: function (event) {
+ _onResequence: function (ev) {
var self = this;
this.trigger_up('mutexify', {
action: function () {
var state = self.model.get(self.handle);
- var resIDs = _.map(event.data.rowIDs, function(rowID) {
+ var resIDs = _.map(ev.data.rowIDs, function(rowID) {
return _.findWhere(state.data, {id: rowID}).res_id;
});
var options = {
- offset: event.data.offset,
- field: event.data.handleField,
+ offset: ev.data.offset,
+ field: ev.data.handleField,
};
return self.model.resequence(self.modelName, resIDs, self.handle, options).then(function () {
self._updateEnv();
@@ -515,31 +512,27 @@ var ListController = BasicController.extend({
});
},
/**
- * When the current selection changes (by clicking on the checkboxes on the
- * left), we need to display (or hide) the 'sidebar'.
+ * Called when the renderer displays an editable row and the user tries to
+ * leave it -> Saves the record associated to that line.
*
- * @private
- * @param {OdooEvent} event
- */
- _onSelectionChanged: function (event) {
- this.selectedRecords = event.data.selection;
- this._toggleSidebar();
- },
- /**
- * @override
+ * @param {OdooEvent} ev
*/
- _onSidebarDataAsked: function (event) {
- var env = this._getSidebarEnv();
- event.data.callback(env);
+ _onSaveLine: function (ev) {
+ var recordID = ev.data.recordID;
+ this.saveRecord(recordID)
+ .done(ev.data.onSuccess)
+ .fail(ev.data.onFailure);
},
/**
- * Called when clicking on 'Archive' or 'Unarchive' in the sidebar.
+ * When the current selection changes (by clicking on the checkboxes on the
+ * left), we need to display (or hide) the 'sidebar'.
*
* @private
- * @param {boolean} archive
+ * @param {OdooEvent} ev
*/
- _onToggleArchiveState: function (archive) {
- this._archive(this.selectedRecords, archive);
+ _onSelectionChanged: function (ev) {
+ this.selectedRecords = ev.data.selection;
+ this._toggleSidebar();
},
/**
* When the user clicks on one of the sortable column headers, we need to
@@ -547,16 +540,16 @@ var ListController = BasicController.extend({
* rerender the view.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onToggleColumnOrder: function (event) {
- event.stopPropagation();
+ _onToggleColumnOrder: function (ev) {
+ ev.stopPropagation();
var data = this.model.get(this.handle);
if (!data.groupedBy) {
this.pager.updateState({current_min: 1});
}
var self = this;
- this.model.setSort(data.id, event.data.name).then(function () {
+ this.model.setSort(data.id, ev.data.name).then(function () {
self.update({});
});
},
@@ -566,11 +559,11 @@ var ListController = BasicController.extend({
* renderer.
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
*/
- _onToggleGroup: function (event) {
+ _onToggleGroup: function (ev) {
this.model
- .toggleGroup(event.data.group.id)
+ .toggleGroup(ev.data.group.id)
.then(this.update.bind(this, {}, {keepSelection: true, reload: false}));
},
});
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index b343f044bc2..87542170dd4 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -739,24 +739,24 @@ var ListRenderer = BasicRenderer.extend({
* a cell using the keyboard, if he presses enter, enter the model represented by the line
*
* @private
- * @param {KeyboardEvent} e
+ * @param {KeyboardEvent} ev
*/
- _onKeyDown: function (e) {
+ _onKeyDown: function (ev) {
if (!this.editable) {
- switch (e.which) {
+ switch (ev.which) {
case $.ui.keyCode.DOWN:
- $(e.currentTarget).next().find('input').focus();
- e.preventDefault();
+ $(ev.currentTarget).next().find('input').focus();
+ ev.preventDefault();
break;
case $.ui.keyCode.UP:
- $(e.currentTarget).prev().find('input').focus();
- e.preventDefault();
+ $(ev.currentTarget).prev().find('input').focus();
+ ev.preventDefault();
break;
case $.ui.keyCode.ENTER:
- e.preventDefault();
- var id = $(e.currentTarget).data('id');
+ ev.preventDefault();
+ var id = $(ev.currentTarget).data('id');
if (id) {
- this.trigger_up('open_record', { id: id, target: e.target });
+ this.trigger_up('open_record', { id: id, target: ev.target });
}
break;
}
@@ -764,43 +764,43 @@ var ListRenderer = BasicRenderer.extend({
},
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onRowClicked: function (event) {
+ _onRowClicked: function (ev) {
// The special_click property explicitely allow events to bubble all
// the way up to bootstrap's level rather than being stopped earlier.
- if (!$(event.target).prop('special_click')) {
- var id = $(event.currentTarget).data('id');
+ if (!$(ev.target).prop('special_click')) {
+ var id = $(ev.currentTarget).data('id');
if (id) {
- this.trigger_up('open_record', { id: id, target: event.target });
+ this.trigger_up('open_record', { id: id, target: ev.target });
}
}
},
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onSelectRecord: function (event) {
- event.stopPropagation();
+ _onSelectRecord: function (ev) {
+ ev.stopPropagation();
this._updateSelection();
- if (!$(event.currentTarget).find('input').prop('checked')) {
+ if (!$(ev.currentTarget).find('input').prop('checked')) {
this.$('thead .o_list_record_selector input').prop('checked', false);
}
},
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onSortColumn: function (event) {
- var name = $(event.currentTarget).data('name');
+ _onSortColumn: function (ev) {
+ var name = $(ev.currentTarget).data('name');
this.trigger_up('toggle_column_order', { id: this.state.id, name: name });
},
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onToggleGroup: function (event) {
- var group = $(event.currentTarget).data('group');
+ _onToggleGroup: function (ev) {
+ var group = $(ev.currentTarget).data('group');
if (group.count) {
this.trigger_up('toggle_group', { group: group });
}
@@ -810,10 +810,10 @@ var ListRenderer = BasicRenderer.extend({
* to toggle its status.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onToggleSelection: function (event) {
- var checked = $(event.currentTarget).prop('checked') || false;
+ _onToggleSelection: function (ev) {
+ var checked = $(ev.currentTarget).prop('checked') || false;
this.$('tbody .o_list_record_selector input:not(":disabled")').prop('checked', checked);
this._updateSelection();
},
diff --git a/addons/web/static/src/js/views/pivot/pivot_controller.js b/addons/web/static/src/js/views/pivot/pivot_controller.js
index 48b5fb033fa..9dfe5dc11b5 100644
--- a/addons/web/static/src/js/views/pivot/pivot_controller.js
+++ b/addons/web/static/src/js/views/pivot/pivot_controller.js
@@ -208,10 +208,10 @@ var PivotController = AbstractController.extend({
* current measures, or a request to flip/expand/download data.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onButtonClick: function (event) {
- var $target = $(event.target);
+ _onButtonClick: function (ev) {
+ var $target = $(ev.target);
if ($target.hasClass('o_pivot_flip_button')) {
this.model.flip();
this.update({}, {reload: false});
@@ -223,8 +223,8 @@ var PivotController = AbstractController.extend({
}
if ($target.parents('.o_pivot_measures_list').length) {
var field = $target.data('field');
- event.preventDefault();
- event.stopPropagation();
+ ev.preventDefault();
+ ev.stopPropagation();
this.model
.toggleMeasure(field)
.then(this.update.bind(this, {}, {reload: false}));
@@ -239,10 +239,10 @@ var PivotController = AbstractController.extend({
* corresponding record.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onCellClick: function (event) {
- var $target = $(event.currentTarget);
+ _onCellClick: function (ev) {
+ var $target = $(ev.currentTarget);
if ($target.hasClass('o_pivot_header_cell_closed') ||
$target.hasClass('o_pivot_header_cell_opened') ||
$target.hasClass('o_empty') ||
@@ -281,10 +281,10 @@ var PivotController = AbstractController.extend({
* clicked header, if a corresponging groupby has already been selected.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onClosedHeaderClick: function (event) {
- var $target = $(event.target);
+ _onClosedHeaderClick: function (ev) {
+ var $target = $(ev.target);
var id = $target.data('id');
var header = this.model.getHeader(id);
var groupbys = header.root.groupbys;
@@ -297,22 +297,22 @@ var PivotController = AbstractController.extend({
this.lastHeaderSelected = id;
var position = $target.position();
var top = position.top + $target.height();
- var left = position.left + event.offsetX;
+ var left = position.left + ev.offsetX;
this._renderFieldSelection(top, left);
- event.stopPropagation();
+ ev.stopPropagation();
}
},
/**
* This handler is called when the user selects a field in the dropdown menu
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onFieldMenuSelection: function (event) {
- event.preventDefault();
- var $target = $(event.target);
+ _onFieldMenuSelection: function (ev) {
+ ev.preventDefault();
+ var $target = $(ev.target);
if ($target.hasClass('disabled')) {
- event.stopPropagation();
+ ev.stopPropagation();
return;
}
var field = $target.data('field');
@@ -329,10 +329,10 @@ var PivotController = AbstractController.extend({
* If the user clicks on a measure row, we can perform an in-memory sort
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onMeasureRowClick: function (event) {
- var $target = $(event.target);
+ _onMeasureRowClick: function (ev) {
+ var $target = $(ev.target);
var col_id = $target.data('id');
var measure = $target.data('measure');
var isAscending = $target.hasClass('o_pivot_measure_row_sorted_asc');
@@ -344,16 +344,15 @@ var PivotController = AbstractController.extend({
* happens, we want to close the header, then redisplay the view.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onOpenHeaderClick: function (event) {
- event.preventDefault();
- event.stopImmediatePropagation();
- var headerID = $(event.target).data('id');
+ _onOpenHeaderClick: function (ev) {
+ ev.preventDefault();
+ ev.stopImmediatePropagation();
+ var headerID = $(ev.target).data('id');
this.model.closeHeader(headerID);
this.update({}, {reload: false});
},
-
});
return PivotController;
diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js
index 68f2f4bf86d..a68d91330a9 100644
--- a/addons/web/static/src/js/views/pivot/pivot_renderer.js
+++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js
@@ -280,13 +280,12 @@ var PivotRenderer = AbstractRenderer.extend({
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onTdHover: function (event) {
+ _onTdHover: function (ev) {
var $td = $(event.target);
$td.closest('table').find('col:eq(' + $td.index()+')').toggleClass('hover');
- }
-
+ },
});
return PivotRenderer;
diff --git a/addons/web/static/src/js/views/view_utils.js b/addons/web/static/src/js/views/view_utils.js
new file mode 100644
index 00000000000..57ae9547d2f
--- /dev/null
+++ b/addons/web/static/src/js/views/view_utils.js
@@ -0,0 +1,47 @@
+odoo.define('web.viewUtils', function () {
+"use strict";
+
+var utils = {
+ /**
+ * Returns the value of a group dataPoint, i.e. the value of the groupBy
+ * field for the records in that group.
+ *
+ * @param {Object} group dataPoint of type list, corresponding to a group
+ * @param {string} groupByField the name of the groupBy field
+ * @returns {string | integer | false}
+ */
+ getGroupValue: function (group, groupByField) {
+ var groupedByField = group.fields[groupByField];
+ switch (groupedByField.type) {
+ case 'many2one':
+ return group.res_id || false;
+ case 'selection':
+ var descriptor = _.findWhere(groupedByField.selection, group.value);
+ return descriptor && descriptor[0];
+ default:
+ return group.value;
+ }
+ },
+ /**
+ * States whether or not the quick create feature is available for the given
+ * datapoint, depending on its groupBy field.
+ *
+ * @param {Object} list dataPoint of type list
+ * @returns {Boolean} true iff the kanban quick create feature is available
+ */
+ isQuickCreateEnabled: function (list) {
+ var groupByField = list.groupedBy[0] && list.groupedBy[0].split(':')[0];
+ if (!groupByField) {
+ return false;
+ }
+ var availableTypes = ['char', 'boolean', 'many2one', 'selection'];
+ if (!_.contains(availableTypes, list.fields[groupByField].type)) {
+ return false;
+ }
+ return true;
+ },
+};
+
+return utils;
+
+});
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 0ffced2d895..e9d03097403 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -273,6 +273,7 @@
<script type="text/javascript" src="/web/static/src/js/views/standalone_field_manager_mixin.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/view_registry.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/view_dialogs.js"></script>
+ <script type="text/javascript" src="/web/static/src/js/views/view_utils.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/form/form_renderer.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/form/form_controller.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/form/form_view.js"></script>