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 e3ec4637658..997fed8747f 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -88,6 +88,8 @@ var ListRenderer = BasicRenderer.extend({ * The result of these computations is stored in the 'aggregate' key of each * column of this.columns. This will be then used by the _renderFooter * method to display the appropriate amount. + * + * @private */ _computeAggregates: function () { var self = this; @@ -105,43 +107,53 @@ var ListRenderer = BasicRenderer.extend({ return; } - _.each(this.columns, function (column) { - var attrs = column.attrs; - var field = self.state.fields[attrs.name]; - if (!field) { - return; - } - var type = field.type; - if (type !== 'integer' && type !== 'float' && type !== 'monetary') { - return; - } - var func = (attrs.sum && 'sum') || (attrs.avg && 'avg') || - (attrs.max && 'max') || (attrs.min && 'min'); - if (func) { - var count = 0; - var aggregate_value = (func === 'max') ? -Infinity : (func === 'min') ? Infinity : 0; - _.each(data, function (d) { - count += 1; - var value = (d.type === 'record') ? d.data[attrs.name] : d.aggregateValues[attrs.name]; - if (func === 'avg') { - aggregate_value += value; - } else if (func === 'sum') { - aggregate_value += value; - } else if (func === 'max') { - aggregate_value = Math.max(aggregate_value, value); - } else if (func === 'min') { - aggregate_value = Math.min(aggregate_value, value); - } - }); + _.each(this.columns, this._computeColumnAggregates.bind(this, data)); + }, + /** + * Compute the aggregate values for a given column and a set of records. + * The aggregate values are then written, if applicable, in the 'aggregate' + * key of the column object. + * + * @private + * @param {Object[]} data a list of selected/all records + * @param {Object} column + */ + _computeColumnAggregates: function (data, column) { + var attrs = column.attrs; + var field = this.state.fields[attrs.name]; + if (!field) { + return; + } + var type = field.type; + if (type !== 'integer' && type !== 'float' && type !== 'monetary') { + return; + } + var func = (attrs.sum && 'sum') || (attrs.avg && 'avg') || + (attrs.max && 'max') || (attrs.min && 'min'); + if (func) { + var count = 0; + var aggregateValue = (func === 'max') ? -Infinity : (func === 'min') ? Infinity : 0; + _.each(data, function (d) { + count += 1; + var value = (d.type === 'record') ? d.data[attrs.name] : d.aggregateValues[attrs.name]; if (func === 'avg') { - aggregate_value = aggregate_value / count; + aggregateValue += value; + } else if (func === 'sum') { + aggregateValue += value; + } else if (func === 'max') { + aggregateValue = Math.max(aggregateValue, value); + } else if (func === 'min') { + aggregateValue = Math.min(aggregateValue, value); } - column.aggregate = { - help: attrs[func], - value: aggregate_value, - }; + }); + if (func === 'avg') { + aggregateValue = aggregateValue / count; } - }); + column.aggregate = { + help: attrs[func], + value: aggregateValue, + }; + } }, /** * Each line can be decorated according to a few simple rules. The arch @@ -151,6 +163,7 @@ var ListRenderer = BasicRenderer.extend({ * concerned with the computation of the list of css classes for a given * record. * + * @private * @param {Object} record a basic model record * @returns {string[]} a list of css classes */ @@ -171,6 +184,7 @@ var ListRenderer = BasicRenderer.extend({ * When a list view is grouped, we need to display the name of each group in * the 'title' row. This is the purpose of this method. * + * @private * @param {any} value * @param {Object} field a field description * @returns {string} @@ -190,7 +204,8 @@ var ListRenderer = BasicRenderer.extend({ * one more that in non editable mode, because there may be a visible 'trash * icon'. * - * @returns {number} + * @private + * @returns {integer} */ _getNumberOfCols: function () { var n = this.columns.length; @@ -200,6 +215,7 @@ var ListRenderer = BasicRenderer.extend({ * Determine if a given cell is invisible. A cell is considered invisible * if there is an 'invisible' attr, with a matching domain. * + * @private * @param {Object} record a basic model record * @param {Object} node a node object (from the arch) * @returns {boolean} @@ -215,17 +231,18 @@ var ListRenderer = BasicRenderer.extend({ * Render a list of , with aggregates if available. It can be displayed * in the footer, or for each open groups. * - * @param {any} aggregate_values + * @private + * @param {any} aggregateValues * @returns {jQueryElement[]} a list of with the aggregate values */ - _renderAggregateCells: function (aggregate_values) { + _renderAggregateCells: function (aggregateValues) { var self = this; return _.map(this.columns, function (column) { var $cell = $(''); - if (column.attrs.name in aggregate_values) { + if (column.attrs.name in aggregateValues) { var field = self.state.fields[column.attrs.name]; - var value = aggregate_values[column.attrs.name].value; - var help = aggregate_values[column.attrs.name].help; + var value = aggregateValues[column.attrs.name].value; + var help = aggregateValues[column.attrs.name].help; var formattedValue = field_utils['format_' + field.type](value, field, {}); $cell.addClass('o_list_number').attr('title', help).html(formattedValue); } @@ -238,6 +255,7 @@ var ListRenderer = BasicRenderer.extend({ * data. The reason is that lists with 0 or 1 lines don't really look like * a table. * + * @private * @returns {jQueryElement} a jquery element */ _renderBody: function () { @@ -253,6 +271,7 @@ var ListRenderer = BasicRenderer.extend({ * node was explicitely defined with a 'widget' attribute, then we * instantiate the corresponding widget. * + * @private * @param {Object} record * @param {Object} node * @returns {jQueryElement} a element @@ -299,6 +318,7 @@ var ListRenderer = BasicRenderer.extend({ * Render a complete empty row. This is used to fill in the blanks when we * have less than 4 lines to display. * + * @private * @returns {jQueryElement} a element */ _renderEmptyRow: function () { @@ -309,6 +329,7 @@ var ListRenderer = BasicRenderer.extend({ * Render the footer. It is a with a single row, containing all * aggregates, if applicable. * + * @private * @param {boolean} isGrouped if the view is grouped, we have to add an * extra * @returns {jQueryElement} a element @@ -332,6 +353,7 @@ var ListRenderer = BasicRenderer.extend({ /** * Render the row that represent a group * + * @private * @param {Object} group * @returns {jQueryElement} a element */ @@ -368,6 +390,7 @@ var ListRenderer = BasicRenderer.extend({ * Note that each group is rendered inside a , which contains a group * row, then possibly a bunch of rows for each record. * + * @private * @returns {jQueryElement[]} a list of */ _renderGroups: function () { @@ -397,6 +420,7 @@ var ListRenderer = BasicRenderer.extend({ * Render the main header for the list view. It is basically just a * with the name of each fields * + * @private * @param {boolean} isGrouped * @returns {jQueryElement} a element */ @@ -416,6 +440,7 @@ var ListRenderer = BasicRenderer.extend({ * field, the th will be empty. Otherwise, it will contains all relevant * information for the field. * + * @private * @param {Object} node * @returns {jQueryElement} a element */ @@ -460,6 +485,7 @@ var ListRenderer = BasicRenderer.extend({ /** * Render a row, corresponding to a record. * + * @private * @param {Object} record * @returns {jQueryElement} a element */ @@ -479,11 +505,24 @@ var ListRenderer = BasicRenderer.extend({ * Render all rows. This method should only called when the view is not * grouped. * + * @private * @returns {jQueryElement[]} a list of */ _renderRows: function () { return _.map(this.state.data, this._renderRow.bind(this)); }, + /** + * A 'selector' is the small checkbox on the left of a record in a list + * view. This is rendered as an input inside a div, so we can properly + * style it. + * + * Note that it takes a tag in argument, because selectores in the header + * are renderd in a th, and those in the tbody are in a td. + * + * @private + * @param {any} tag either th or td + * @returns {jQueryElement} + */ _renderSelector: function (tag) { var $content = $('
'); return $('<' + tag + ' width="1">') @@ -491,8 +530,12 @@ var ListRenderer = BasicRenderer.extend({ .append($content); }, /** + * Main render function for the list. It is rendered as a table. For now, + * this method does not wait for the field widgets to be ready. + * * @override - * returns {Deferred} + * @private + * returns {Deferred} this deferred is resolved immediately */ _renderView: function () { var self = this; @@ -521,6 +564,13 @@ var ListRenderer = BasicRenderer.extend({ } return this._super(); }, + /** + * Whenever we change the state of the selected rows, we need to call this + * method to keep the this.selection variable in sync, and also to recompute + * the aggregates. + * + * @private + */ _updateSelection: function () { var $selectedRows = this.$('tbody .o_list_record_selector input:checked') .closest('tr'); @@ -537,6 +587,7 @@ var ListRenderer = BasicRenderer.extend({ //-------------------------------------------------------------------------- /** + * @private * @param {MouseEvent} event */ _onRowClicked: function (event) { @@ -546,6 +597,7 @@ var ListRenderer = BasicRenderer.extend({ } }, /** + * @private * @param {MouseEvent} event */ _onSelectRecord: function (event) { @@ -556,6 +608,7 @@ var ListRenderer = BasicRenderer.extend({ } }, /** + * @private * @param {MouseEvent} event */ _onSortColumn: function (event) { @@ -563,6 +616,7 @@ var ListRenderer = BasicRenderer.extend({ this.trigger_up('toggle_column_order', {id: this.state.id, name: name}); }, /** + * @private * @param {MouseEvent} event */ _onToggleGroup: function (event) { @@ -571,6 +625,13 @@ var ListRenderer = BasicRenderer.extend({ this.trigger_up('toggle_group', {group: group}); } }, + /** + * When the user clicks on the 'checkbox' on the left of a record, we need + * to toggle its status. + * + * @private + * @param {MouseEvent} event + */ _onToggleSelection: function (event) { var checked = $(event.currentTarget).prop('checked') || false; this.$('tbody .o_list_record_selector input').prop('checked', checked);