diff options
| author | Aaron Bohy <aab@odoo.com> | 2018-10-22 11:03:17 +0000 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2018-10-23 10:56:12 +0000 |
| commit | 03fe95d6af9d96f961c47d9acfaec498966b8cfa (patch) | |
| tree | 4ae97e4007ef1642b21cdaae596201a17e3cb316 | |
| parent | 3c59add416fdba779675dc79580f6c0f56abc5a3 (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
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> |
