odoo.define('web.Pager', function (require) { "use strict"; var utils = require('web.utils'); var Widget = require('web.Widget'); var direction = { previous: -1, next: 1, }; var Pager = Widget.extend({ template: "Pager", events: { 'click .o_pager_next': '_onNext', 'click .o_pager_previous': '_onPrevious', 'click .o_pager_value': '_onEdit', }, /** * The pager goes from 1 to size (included). * The current value is current_min if limit === 1 * or the interval [current_min, current_min + limit[ if limit > 1 * * @param {Widget} [parent] the parent widget * @param {int} [size] the total number of elements * @param {int} [current_min] the first element of the current_page * @param {int} [limit] the number of elements per page * @param {boolean} [options.can_edit] editable feature of the pager * @param {boolean} [options.single_page_hidden] (not) to display the pager * if only one page * @param {function} [options.validate] callback returning a Promise to * validate changes */ init: function (parent, size, current_min, limit, options) { this.state = { size: size, current_min: current_min, limit: limit, }; Object.defineProperty(this.state, 'current_max', { get: function() { return Math.min(this.current_min + this.limit - 1, this.size); } }); this.options = _.defaults({}, options, { can_edit: true, // editable single_page_hidden: false, // displayed even if there is a single page validate: function() { return Promise.resolve(); }, withAccessKey: true, // can be disabled, for example, for x2m widgets }); this._super(parent); }, /** * Renders the pager * * @returns {Promise} */ start: function () { this.$value = this.$('.o_pager_value'); this.$limit = this.$('.o_pager_limit'); this._render(); return this._super(); }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- /** * Disables the pager's arrows and the edition */ disable: function () { this.disabled = true; this._updateArrows(); }, /** * Enables the pager's arrows and the edition */ enable: function () { this.disabled = false; this._updateArrows(); }, /** * Executes the next action on the pager */ next: function () { this._changeSelection(direction.next); }, /** * Executes the previous action on the pager */ previous: function () { this._changeSelection(direction.previous); }, /** * Sets the state of the pager and renders it * @param {Object} [state] the values to update (size, current_min and limit) * @param {Object} [options] * @param {boolean} [options.notifyChange] set to true to make the pager * notify the environment that its state changed */ updateState: function (state, options) { _.extend(this.state, state); this._render(); if (options && options.notifyChange) { this.trigger('pager_changed', _.clone(this.state)); } }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * Private function that updates the pager's state according to a pager action * * @param {int} [direction] the action (previous or next) on the pager */ _changeSelection: function (direction) { var self = this; this.options.validate().then(function() { var size = self.state.size; var current_min = self.state.current_min; var limit = self.state.limit; // Compute the new current_min current_min = (current_min + limit*direction); if (current_min > size) { current_min = 1; } else if ((current_min < 1) && (limit === 1)) { current_min = size; } else if ((current_min < 1) && (limit > 1)) { current_min = size - ((size % limit) || limit) + 1; } self.state.current_min = current_min; // The re-rendering of the pager must be done before the trigger of // event 'pager_changed' as the rendering may enable the pager // (and a common use is to disable the pager when this event is // triggered, and to re-enable it when the data have been reloaded) self._render(); self.trigger('pager_changed', _.clone(self.state)); }); }, /** * Private function that displays an input to edit the pager's state */ _edit: function () { if (this.options.can_edit) { var self = this; var $input = $('', {class: 'o_input', type: 'text', value: this.$value.html()}); this.$value.html($input); $input.focus(); // Event handlers $input.click(function(ev) { ev.stopPropagation(); // ignore clicks on the input }); $input.blur(function(ev) { self._save($(ev.target)); // save the state when leaving the input }); $input.on('keydown', function (ev) { ev.stopPropagation(); if (ev.which === $.ui.keyCode.ENTER) { self._save($(ev.target)); // save on enter } else if (ev.which === $.ui.keyCode.ESCAPE) { self._render(); // leave on escape } }); } }, /** * Private function that renders the pager's state */ _render: function () { var size = this.state.size; var current_min = this.state.current_min; var current_max = this.state.current_max; if (size === 0 || (this.options.single_page_hidden && this._singlePage())) { this.do_hide(); } else { this.do_show(); this._updateArrows(); var value = "" + current_min; if (this.state.limit > 1) { value += "-" + current_max; } this.$value.html(value); this.$limit.html(size); } }, /** * Private function that saves the state from the content of the input * * @param {jQuery} [$input] the jQuery element containing the new state */ _save: function ($input) { var self = this; this.options.validate().then(function() { var value = $input.val().split("-"); var min = utils.confine(parseInt(value[0], 10), 1, self.state.size); var max = utils.confine(parseInt(value[1], 10), 1, self.state.size); if (!isNaN(min)) { self.state.current_min = min; if (!isNaN(max)) { self.state.limit = utils.confine(max-min+1, 1, self.state.size); } else { // The state has been given as a single value -> set the limit to 1 self.state.limit = 1; } self.trigger('pager_changed', _.clone(self.state)); } // Render the pager's new state (removes the input) self._render(); }); }, /** * @private * @returns {boolean} true iff there is only one page */ _singlePage: function () { var state = this.state; return (1 === state.current_min) && (state.current_max === state.size); }, /** * Updates the arrows' disable attribute: true iff the pager is disabed or * if there is only one page * * @private */ _updateArrows: function () { var disabled = this.disabled || this._singlePage(); this.$('button').prop('disabled', disabled); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * @private * @param {MouseEvent} event */ _onEdit: function (event) { event.stopPropagation(); if (!this.disabled) { this._edit(); } }, /** * @private * @param {MouseEvent} event */ _onNext: function (event) { event.stopPropagation(); this.next(); }, /** * @private * @param {MouseEvent} event */ _onPrevious: function (event) { event.stopPropagation(); this.previous(); }, }); return Pager; });