}
});
+/**
+ * @class RangeSliderValue
+ * @memberof LuCI.form
+ * @augments LuCI.form.Value
+ * @hideconstructor
+ * @classdesc
+ *
+ * The `RangeSliderValue` class implements a range slider input using
+ * {@link LuCI.ui.RangeSlider}. It is useful in cases where a value shall fall
+ * within a predetermined range. This helps omit various error checks for such
+ * values. The currently chosen value is displayed to the side of the slider.
+ *
+ * @param {LuCI.form.Map|LuCI.form.JSONMap} form
+ * The configuration form this section is added to. It is automatically passed
+ * by [option()]{@link LuCI.form.AbstractSection#option} or
+ * [taboption()]{@link LuCI.form.AbstractSection#taboption} when adding the
+ * option to the section.
+ *
+ * @param {LuCI.form.AbstractSection} section
+ * The configuration section this option is added to. It is automatically passed
+ * by [option()]{@link LuCI.form.AbstractSection#option} or
+ * [taboption()]{@link LuCI.form.AbstractSection#taboption} when adding the
+ * option to the section.
+ *
+ * @param {string} option
+ * The name of the UCI option to map.
+ *
+ * @param {string} [title]
+ * The title caption of the option element.
+ *
+ * @param {string} [description]
+ * The description text of the option element.
+ */
+const CBIRangeSliderValue = CBIValue.extend(/** @lends LuCI.form.RangeSliderValue.prototype */ {
+ __name__: 'CBI.RangeSliderValue',
+
+ /**
+ * Minimum value the slider can represent.
+ * @name LuCI.form.RangeSliderValue.prototype#min
+ * @type number
+ * @default 0
+ */
+
+ /**
+ * Maximum value the slider can represent.
+ * @name LuCI.form.RangeSliderValue.prototype#max
+ * @type number
+ * @default 100
+ */
+
+ /**
+ * Step size for each tick of the slider, or the special value "any" when
+ * handling arbitrary precision floating point numbers.
+ * @name LuCI.form.RangeSliderValue.prototype#step
+ * @type string
+ * @default 1
+ */
+
+ /**
+ * Set the default value for the slider. The default value is elided during
+ * save: meaning, a currently chosen value which matches the default is
+ * not saved.
+ * @name LuCI.form.RangeSliderValue.prototype#default
+ * @type string
+ * @default null
+ */
+
+ /**
+ * Override the calculate action.
+ *
+ * When this property is set to a function, it is invoked when the slider
+ * is adjusted. This might be useful to calculate and display a result which
+ * is more meaningful than the currently chosen value. The calculated value
+ * is displayed below the slider.
+ *
+ * @name LuCI.form.RangeSliderValue.prototype#calculate
+ * @type function
+ * @default null
+ */
+
+ /**
+ * Define the units of the calculated value.
+ *
+ * Suffix a unit string to the calculated value, e.g. 'seconds' or 'dBm'.
+ *
+ * @name LuCI.form.RangeSliderValue.prototype#calcunits
+ * @type string
+ * @default null
+ */
+
+ /**
+ * Whether to use the calculated result of the chosen value instead of the
+ * chosen value: the result of the calculation returned by the
+ * <code>calculate</code> function on the chosen value
+ * is written to the configuration instead of the chosen value. The
+ * <code>calcunits</code> displayed units are not included.
+ *
+ * Note: Implementers of the <code>calculate</code> function shall be
+ * mindful that it may be possible to return a NaN value which is seldom a
+ * sensible input for the underlying daemon or system. Verification of any
+ * calculated value is an exercise left to the implementer.
+ *
+ * @name LuCI.form.RangeSliderValue.prototype#usecalc
+ * @type boolean
+ * @default false
+ */
+
+ /** @private */
+ renderWidget(section_id, option_index, cfgvalue) {
+ const slider = new ui.RangeSlider((cfgvalue != null) ? cfgvalue : this.default, {
+ id: this.cbid(section_id),
+ name: this.cbid(section_id),
+ optional: this.optional,
+ min: this.min,
+ max: this.max,
+ step: this.step,
+ calculate: this.calculate,
+ calcunits: this.calcunits,
+ usecalc: this.usecalc,
+ disabled: this.readonly || this.disabled,
+ datatype: this.datatype,
+ validate: this.validate,
+ });
+
+ this.widget = slider;
+
+ return slider.render();
+ },
+
+ /**
+ * Query the current form input value.
+ *
+ * @param {string} section_id
+ * The configuration section ID
+ *
+ * @returns {*}
+ * Returns the current input value.
+ */
+ formvalue(section_id) {
+ const elem = this.getUIElement(section_id);
+ if (!elem) return null;
+ let val = (this.usecalc && (typeof this.calculate === 'function'))
+ ? elem.getCalculatedValue()
+ : elem.getValue();
+ val = val?.toString();
+ return (val === this.default?.toString()) ? null : val;
+ }
+});
+
/**
* @class FlagValue
* @memberof LuCI.form
DynamicList: CBIDynamicList,
ListValue: CBIListValue,
RichListValue: CBIRichListValue,
+ RangeSliderValue: CBIRangeSliderValue,
Flag: CBIFlagValue,
MultiValue: CBIMultiValue,
TextValue: CBITextValue,
}
});
+/**
+ * Instantiate a range slider widget.
+ *
+ * @constructor Slider
+ * @memberof LuCI.ui
+ * @augments LuCI.ui.AbstractElement
+ *
+ * @classdesc
+ *
+ * The `RangeSlider` class implements a widget which allows the user to set a
+ * value from a predefined range.
+ *
+ * UI widget instances are usually not supposed to be created by view code
+ * directly. Instead they're implicitly created by `LuCI.form` when
+ * instantiating CBI forms.
+ *
+ * This class is automatically instantiated as part of `LuCI.ui`. To use it
+ * in views, use `'require ui'` and refer to `ui.Slider`. To import it in
+ * external JavaScript, use `L.require("ui").then(...)` and access the
+ * `Slider` property of the class instance value.
+ *
+ * @param {string|string[]} [value=null]
+ * ...
+ *
+ */
+const UIRangeSlider = UIElement.extend({
+ __init__(value, options) {
+ this.value = value;
+ this.options = Object.assign({
+ optional: true,
+ min: 0,
+ max: 100,
+ step: 1,
+ calculate: null,
+ calcunits: null,
+ usecalc: false,
+ disabled: false,
+ }, options);
+ },
+
+ /** @override */
+ render() {
+ this.sliderEl = E('input', {
+ 'type': 'range',
+ 'id': this.options.id,
+ 'min': this.options.min,
+ 'max': this.options.max,
+ 'step': this.options.step || 'any',
+ 'value': this.value,
+ 'disabled': this.options.disabled ? '' : null
+ });
+
+ this.calculatedvalue = (typeof this.options.calculate === 'function')
+ ? this.options.calculate(this.value)
+ : null;
+
+ this.calcEl = E('output', { 'class': 'cbi-range-slider-calc' }, this.calculatedvalue);
+
+ this.calcunitsEl = E('span', { 'class': 'cbi-range-slider-calc-units' },
+ this.options.calcunits
+ ? ' ' + this.options.calcunits
+ : ''
+ );
+
+ const container = E('div', { 'class': 'cbi-range-slider' }, [
+ this.sliderEl,
+ this.valueEl = E('output', { 'for': this.options.id, 'class': 'cbi-range-slider-value' }, this.value),
+ this.calculatedvalue ? E('br') : null,
+ this.calculatedvalue ? this.calcEl : null,
+ this.calculatedvalue ? this.calcunitsEl : null,
+ ].filter(Boolean));
+
+ this.node = container;
+
+ this.setUpdateEvents(this.sliderEl, 'input', 'blur');
+ this.setChangeEvents(this.sliderEl, 'change');
+
+ this.sliderEl.addEventListener('input', () => {
+ const val = this.sliderEl.value;
+ this.valueEl.textContent = val;
+
+ if (typeof this.options.calculate === 'function') {
+ // update the stored calculated value, and the displayed values
+ this.calculatedvalue = this.options.calculate(val);
+ this.calcEl.textContent = this.calculatedvalue;
+ }
+
+ this.node.setAttribute('data-changed', true);
+ });
+
+ dom.bindClassInstance(container, this);
+
+ return container;
+ },
+
+ /** @override */
+ getValue() {
+ return this.sliderEl.value;
+ },
+
+ /** @private */
+ getCalculatedValue() {
+ return this.calculatedvalue;
+ },
+
+ /** @override */
+ setValue(value) {
+ this.sliderEl.value = value;
+ this.valueEl.textContent = value;
+
+ if (typeof this.options.calculate === 'function') {
+ this.calculatedvalue = this.options.calculate(value);
+ this.calcEl.textContent = this.calculatedvalue;
+ }
+ }
+});
+
/**
* Instantiate a hidden input field widget.
*
Select: UISelect,
Dropdown: UIDropdown,
DynamicList: UIDynamicList,
+ RangeSlider: UIRangeSlider,
Combobox: UICombobox,
ComboButton: UIComboButton,
Hiddenfield: UIHiddenfield,