summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/components/pager.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/components/pager.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/components/pager.js')
-rw-r--r--addons/web/static/src/js/components/pager.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/addons/web/static/src/js/components/pager.js b/addons/web/static/src/js/components/pager.js
new file mode 100644
index 00000000..2d8d402a
--- /dev/null
+++ b/addons/web/static/src/js/components/pager.js
@@ -0,0 +1,225 @@
+odoo.define('web.Pager', function (require) {
+ "use strict";
+
+ const { useAutofocus } = require('web.custom_hooks');
+
+ const { Component, hooks } = owl;
+ const { useState } = hooks;
+
+ /**
+ * Pager
+ *
+ * The pager goes from 1 to size (included).
+ * The current value is currentMinimum if limit === 1 or the interval:
+ * [currentMinimum, currentMinimum + limit[ if limit > 1].
+ * The value can be manually changed by clicking on the pager value and giving
+ * an input matching the pattern: min[,max] (in which the comma can be a dash
+ * or a semicolon).
+ * The pager also provides two buttons to quickly change the current page (next
+ * or previous).
+ * @extends Component
+ */
+ class Pager extends Component {
+ /**
+ * @param {Object} [props]
+ * @param {int} [props.size] the total number of elements
+ * @param {int} [props.currentMinimum] the first element of the current_page
+ * @param {int} [props.limit] the number of elements per page
+ * @param {boolean} [props.editable] editable feature of the pager
+ * @param {function} [props.validate] callback returning a Promise to
+ * validate changes
+ * @param {boolean} [props.withAccessKey] can be disabled, for example,
+ * for x2m widgets
+ */
+ constructor() {
+ super(...arguments);
+
+ this.state = useState({
+ disabled: false,
+ editing: false,
+ });
+
+ useAutofocus();
+ }
+
+ async willUpdateProps() {
+ this.state.editing = false;
+ this.state.disabled = false;
+ }
+
+ //---------------------------------------------------------------------
+ // Getters
+ //---------------------------------------------------------------------
+
+ /**
+ * @returns {number}
+ */
+ get maximum() {
+ return Math.min(this.props.currentMinimum + this.props.limit - 1, this.props.size);
+ }
+
+ /**
+ * @returns {boolean} true iff there is only one page
+ */
+ get singlePage() {
+ return (1 === this.props.currentMinimum) && (this.maximum === this.props.size);
+ }
+
+ /**
+ * @returns {number}
+ */
+ get value() {
+ return this.props.currentMinimum + (this.props.limit > 1 ? `-${this.maximum}` : '');
+ }
+
+ //---------------------------------------------------------------------
+ // Private
+ //---------------------------------------------------------------------
+
+ /**
+ * Update the pager's state according to a pager action
+ * @private
+ * @param {number} [direction] the action (previous or next) on the pager
+ */
+ async _changeSelection(direction) {
+ try {
+ await this.props.validate();
+ } catch (err) {
+ return;
+ }
+ const { limit, size } = this.props;
+
+ // Compute the new currentMinimum
+ let currentMinimum = (this.props.currentMinimum + limit * direction);
+ if (currentMinimum > size) {
+ currentMinimum = 1;
+ } else if ((currentMinimum < 1) && (limit === 1)) {
+ currentMinimum = size;
+ } else if ((currentMinimum < 1) && (limit > 1)) {
+ currentMinimum = size - ((size % limit) || limit) + 1;
+ }
+
+ // 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).
+ this._updateAndDisable(currentMinimum, limit);
+ }
+
+ /**
+ * Save the state from the content of the input
+ * @private
+ * @param {string} value the new raw pager value
+ * @returns {Promise}
+ */
+ async _saveValue(value) {
+ try {
+ await this.props.validate();
+ } catch (err) {
+ return;
+ }
+ const [min, max] = value.trim().split(/\s*[\-\s,;]\s*/);
+
+ let currentMinimum = Math.max(Math.min(parseInt(min, 10), this.props.size), 1);
+ let maximum = max ? Math.max(Math.min(parseInt(max, 10), this.props.size), 1) : min;
+
+ if (
+ !isNaN(currentMinimum) &&
+ !isNaN(maximum) &&
+ currentMinimum <= maximum
+ ) {
+ const limit = Math.max(maximum - currentMinimum) + 1;
+ this._updateAndDisable(currentMinimum, limit);
+ }
+ }
+
+ /**
+ * Commits the current input value. There are two scenarios:
+ * - the value is the same: the pager toggles back to readonly
+ * - the value changed: the pager is disabled to prevent furtherchanges
+ * Either way the "pager-changed" event is triggered to reload the
+ * view.
+ * @private
+ * @param {number} currentMinimum
+ * @param {number} limit
+ */
+ _updateAndDisable(currentMinimum, limit) {
+ if (
+ currentMinimum !== this.props.currentMinimum ||
+ limit !== this.props.limit
+ ) {
+ this.state.disabled = true;
+ } else {
+ // In this case we want to trigger an update, but since it will
+ // not re-render the pager (current props === next props) we
+ // have to disable the edition manually here.
+ this.state.editing = false;
+ }
+ this.trigger('pager-changed', { currentMinimum, limit });
+ }
+
+ //---------------------------------------------------------------------
+ // Handlers
+ //---------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onEdit() {
+ if (
+ this.props.editable && // editable
+ !this.state.editing && // not already editing
+ !this.state.disabled // not being changed already
+ ) {
+ this.state.editing = true;
+ }
+ }
+
+ /**
+ * @private
+ * @param {InputEvent} ev
+ */
+ _onValueChange(ev) {
+ this._saveValue(ev.currentTarget.value);
+ if (!this.state.disabled) {
+ ev.preventDefault();
+ }
+ }
+
+ /**
+ * @private
+ * @param {KeyboardEvent} ev
+ */
+ _onValueKeydown(ev) {
+ switch (ev.key) {
+ case 'Enter':
+ ev.preventDefault();
+ ev.stopPropagation();
+ this._saveValue(ev.currentTarget.value);
+ break;
+ case 'Escape':
+ ev.preventDefault();
+ ev.stopPropagation();
+ this.state.editing = false;
+ break;
+ }
+ }
+ }
+
+ Pager.defaultProps = {
+ editable: true,
+ validate: async () => { },
+ withAccessKey: true,
+ };
+ Pager.props = {
+ currentMinimum: { type: Number, optional: 1 },
+ editable: Boolean,
+ limit: { validate: l => !isNaN(l), optional: 1 },
+ size: { type: Number, optional: 1 },
+ validate: Function,
+ withAccessKey: Boolean,
+ };
+ Pager.template = 'web.Pager';
+
+ return Pager;
+});