diff options
Diffstat (limited to 'modules/luci-base/htdocs/luci-static/resources/ui.js')
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/ui.js | 1464 |
1 files changed, 732 insertions, 732 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js index 1580996ef4..7e6c4748b7 100644 --- a/modules/luci-base/htdocs/luci-static/resources/ui.js +++ b/modules/luci-base/htdocs/luci-static/resources/ui.js @@ -9,10 +9,10 @@ 'require uci'; 'require fs'; -var modalDiv = null, - tooltipDiv = null, - indicatorDiv = null, - tooltipTimeout = null; +let modalDiv = null; +let tooltipDiv = null; +let indicatorDiv = null; +let tooltipTimeout = null; /** * @class AbstractElement @@ -34,7 +34,7 @@ var modalDiv = null, * it in external JavaScript, use `L.require("ui").then(...)` and access the * `AbstractElement` property of the class instance value. */ -var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ { +const UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ { /** * @typedef {Object} InitOptions * @memberof LuCI.ui.AbstractElement @@ -78,7 +78,7 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * string. Complex widgets such as `DynamicList` instances may result in * an array of strings or `null` for unset values. */ - getValue: function() { + getValue() { if (dom.matches(this.node, 'select') || dom.matches(this.node, 'input')) return this.node.value; @@ -96,7 +96,7 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * Complex widgets such as `DynamicList` instances may accept string array * or `null` values. */ - setValue: function(value) { + setValue(value) { if (dom.matches(this.node, 'select') || dom.matches(this.node, 'input')) this.node.value = value; }, @@ -110,10 +110,10 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * The placeholder to set for the input element. Only applicable to text * inputs, not to radio buttons, selects or similar. */ - setPlaceholder: function(value) { - var node = this.node ? this.node.querySelector('input,textarea') : null; + setPlaceholder(value) { + const node = this.node ? this.node.querySelector('input,textarea') : null; if (node) { - switch (node.getAttribute('type') || 'text') { + switch (node.getAttribute('type') ?? 'text') { case 'password': case 'search': case 'tel': @@ -138,7 +138,7 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * value and changes it back to the original state, it is still reported * as changed. */ - isChanged: function() { + isChanged() { return (this.node ? this.node.getAttribute('data-changed') : null) == 'true'; }, @@ -151,7 +151,7 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * Returns `true` if the current input value is valid or `false` if it does * not meet the validation constraints. */ - isValid: function() { + isValid() { return (this.validState !== false); }, @@ -163,8 +163,8 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * @returns {string} * The validation error at this time */ - getValidationError: function() { - return this.validationError || ''; + getValidationError() { + return this.validationError ?? ''; }, /** @@ -177,11 +177,11 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * @instance * @memberof LuCI.ui.AbstractElement */ - triggerValidation: function() { + triggerValidation() { if (typeof(this.vfunc) != 'function') return false; - var wasValid = this.isValid(); + const wasValid = this.isValid(); this.vfunc(); @@ -212,12 +212,12 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * @param {string[]} events * The native DOM events for which event handlers should be registered. */ - registerEvents: function(targetNode, synevent, events) { - var dispatchFn = L.bind(function(ev) { + registerEvents(targetNode, synevent, events) { + const dispatchFn = L.bind(function(ev) { this.node.dispatchEvent(new CustomEvent(synevent, { bubbles: true })); }, this); - for (var i = 0; i < events.length; i++) + for (let i = 0; i < events.length; i++) targetNode.addEventListener(events[i], dispatchFn); }, @@ -237,19 +237,18 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * @param {...string} events * The DOM events for which event handlers should be registered. */ - setUpdateEvents: function(targetNode /*, ... */) { - var datatype = this.options.datatype, - optional = this.options.hasOwnProperty('optional') ? this.options.optional : true, - validate = this.options.validate, - events = this.varargs(arguments, 1); + setUpdateEvents(targetNode, ...events) { + const datatype = this.options.datatype; + const optional = this.options.hasOwnProperty('optional') ? this.options.optional : true; + const validate = this.options.validate; this.registerEvents(targetNode, 'widget-update', events); if (!datatype && !validate) return; - this.vfunc = UI.prototype.addValidator.apply(UI.prototype, [ - targetNode, datatype || 'string', + this.vfunc = UI.prototype.addValidator(...[ + targetNode, datatype ?? 'string', optional, validate ].concat(events)); @@ -282,13 +281,13 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * @param {...string} events * The DOM events for which event handlers should be registered. */ - setChangeEvents: function(targetNode /*, ... */) { - var tag_changed = L.bind(function(ev) { this.setAttribute('data-changed', true) }, this.node); + setChangeEvents(targetNode, ...events) { + const tag_changed = L.bind(function(ev) { this.setAttribute('data-changed', true) }, this.node); - for (var i = 1; i < arguments.length; i++) - targetNode.addEventListener(arguments[i], tag_changed); + for (let i = 0; i < events.length; i++) + targetNode.addEventListener(events[i], tag_changed); - this.registerEvents(targetNode, 'widget-change', this.varargs(arguments, 1)); + this.registerEvents(targetNode, 'widget-change', events); }, /** @@ -301,7 +300,7 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * Returns a DOM Node or DocumentFragment containing the rendered * widget markup. */ - render: function() {} + render() {} }); /** @@ -330,7 +329,7 @@ var UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype */ * @param {LuCI.ui.Textfield.InitOptions} [options] * Object describing the widget specific options to initialize the input. */ -var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { +const UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { /** * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions} * the following properties are recognized: @@ -354,7 +353,7 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { * Specifies the HTML `placeholder` attribute which is displayed when the * corresponding `<input>` element is empty. */ - __init__: function(value, options) { + __init__(value, options) { this.value = value; this.options = Object.assign({ optional: true, @@ -363,10 +362,10 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { }, /** @override */ - render: function() { - var frameEl = E('div', { 'id': this.options.id }); - var inputEl = E('input', { - 'id': this.options.id ? 'widget.' + this.options.id : null, + render() { + const frameEl = E('div', { 'id': this.options.id }); + const inputEl = E('input', { + 'id': this.options.id ? `widget.${this.options.id}` : null, 'name': this.options.name, 'type': 'text', 'class': this.options.password ? 'cbi-input-password' : 'cbi-input-text', @@ -374,7 +373,6 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { 'disabled': this.options.disabled ? '' : null, 'maxlength': this.options.maxlength, 'placeholder': this.options.placeholder, - 'autocomplete': this.options.password ? 'new-password' : null, 'value': this.value, }); @@ -386,14 +384,14 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { 'title': _('Reveal/hide password'), 'aria-label': _('Reveal/hide password'), 'click': function(ev) { - var e = this.previousElementSibling; + const e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'; ev.preventDefault(); } }, '∗') ])); - window.requestAnimationFrame(function() { inputEl.type = 'password' }); + window.requestAnimationFrame(() => { inputEl.type = 'password' }); } else { frameEl.appendChild(inputEl); @@ -403,8 +401,8 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { }, /** @private */ - bind: function(frameEl) { - var inputEl = frameEl.querySelector('input'); + bind(frameEl) { + const inputEl = frameEl.querySelector('input'); this.node = frameEl; @@ -417,14 +415,14 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { }, /** @override */ - getValue: function() { - var inputEl = this.node.querySelector('input'); + getValue() { + const inputEl = this.node.querySelector('input'); return inputEl.value; }, /** @override */ - setValue: function(value) { - var inputEl = this.node.querySelector('input'); + setValue(value) { + const inputEl = this.node.querySelector('input'); inputEl.value = value; } }); @@ -455,7 +453,7 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ { * @param {LuCI.ui.Textarea.InitOptions} [options] * Object describing the widget specific options to initialize the input. */ -var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { +const UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { /** * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions} * the following properties are recognized: @@ -485,7 +483,7 @@ var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { * @property {boolean} [wrap=false] * Specifies whether the HTML `wrap` attribute should be set. */ - __init__: function(value, options) { + __init__(value, options) { this.value = value; this.options = Object.assign({ optional: true, @@ -496,13 +494,13 @@ var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { }, /** @override */ - render: function() { - var style = !this.options.cols ? 'width:100%' : null, - frameEl = E('div', { 'id': this.options.id, 'style': style }), - value = (this.value != null) ? String(this.value) : ''; + render() { + const style = !this.options.cols ? 'width:100%' : null; + const frameEl = E('div', { 'id': this.options.id, 'style': style }); + const value = (this.value != null) ? String(this.value) : ''; frameEl.appendChild(E('textarea', { - 'id': this.options.id ? 'widget.' + this.options.id : null, + 'id': this.options.id ? `widget.${this.options.id}` : null, 'name': this.options.name, 'class': 'cbi-input-textarea', 'readonly': this.options.readonly ? '' : null, @@ -521,8 +519,8 @@ var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { }, /** @private */ - bind: function(frameEl) { - var inputEl = frameEl.firstElementChild; + bind(frameEl) { + const inputEl = frameEl.firstElementChild; this.node = frameEl; @@ -535,12 +533,12 @@ var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { }, /** @override */ - getValue: function() { + getValue() { return this.node.firstElementChild.value; }, /** @override */ - setValue: function(value) { + setValue(value) { this.node.firstElementChild.value = value; } }); @@ -571,7 +569,7 @@ var UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ { * @param {LuCI.ui.Checkbox.InitOptions} [options] * Object describing the widget specific options to initialize the input. */ -var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { +const UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { /** * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions} * the following properties are recognized: @@ -590,7 +588,7 @@ var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { * checkbox. This is a legacy property existing for compatibility reasons, * it is required for HTML based form submissions. */ - __init__: function(value, options) { + __init__(value, options) { this.value = value; this.options = Object.assign({ value_enabled: '1', @@ -599,9 +597,9 @@ var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { }, /** @override */ - render: function() { - var id = 'cb%08x'.format(Math.random() * 0xffffffff); - var frameEl = E('div', { + render() { + const id = 'cb%08x'.format(Math.random() * 0xffffffff); + const frameEl = E('div', { 'id': this.options.id, 'class': 'cbi-checkbox' }); @@ -620,13 +618,13 @@ var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { 'value': this.options.value_enabled, 'checked': (this.value == this.options.value_enabled) ? '' : null, 'disabled': this.options.disabled ? '' : null, - 'data-widget-id': this.options.id ? 'widget.' + this.options.id : null + 'data-widget-id': this.options.id ? `widget.${this.options.id}` : null })); frameEl.appendChild(E('label', { 'for': id })); if (this.options.tooltip != null) { - var icon = "⚠️"; + let icon = "⚠️"; if (this.options.tooltipicon != null) icon = this.options.tooltipicon; @@ -645,10 +643,10 @@ var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { }, /** @private */ - bind: function(frameEl) { + bind(frameEl) { this.node = frameEl; - var input = frameEl.querySelector('input[type="checkbox"]'); + const input = frameEl.querySelector('input[type="checkbox"]'); this.setUpdateEvents(input, 'click', 'blur'); this.setChangeEvents(input, 'change'); @@ -665,19 +663,19 @@ var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { * @returns {boolean} * Returns `true` when the checkbox is currently checked, otherwise `false`. */ - isChecked: function() { + isChecked() { return this.node.querySelector('input[type="checkbox"]').checked; }, /** @override */ - getValue: function() { + getValue() { return this.isChecked() ? this.options.value_enabled : this.options.value_disabled; }, /** @override */ - setValue: function(value) { + setValue(value) { this.node.querySelector('input[type="checkbox"]').checked = (value == this.options.value_enabled); } }); @@ -715,7 +713,7 @@ var UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ { * @param {LuCI.ui.Select.InitOptions} [options] * Object describing the widget specific options to initialize the inputs. */ -var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { +const UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { /** * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions} * the following properties are recognized: @@ -750,7 +748,7 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { * Specifies a placeholder text which is displayed when no choice is * selected yet. Only applicable to the `select` widget type. */ - __init__: function(value, choices, options) { + __init__(value, choices, options) { if (!L.isObject(choices)) choices = {}; @@ -773,9 +771,9 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { }, /** @override */ - render: function() { - var frameEl = E('div', { 'id': this.options.id }), - keys = Object.keys(this.choices); + render() { + const frameEl = E('div', { 'id': this.options.id }); + let keys = Object.keys(this.choices); if (this.options.sort === true) keys.sort(L.naturalCompare); @@ -784,7 +782,7 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { if (this.options.widget != 'radio' && this.options.widget != 'checkbox') { frameEl.appendChild(E('select', { - 'id': this.options.id ? 'widget.' + this.options.id : null, + 'id': this.options.id ? `widget.${this.options.id}` : null, 'name': this.options.name, 'size': this.options.size, 'class': 'cbi-input-select', @@ -796,28 +794,28 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { frameEl.lastChild.appendChild(E('option', { 'value': '', 'selected': (this.values.length == 0 || this.values[0] == '') ? '' : null - }, [ this.choices[''] || this.options.placeholder || _('-- Please choose --') ])); + }, [ this.choices[''] ?? this.options.placeholder ?? _('-- Please choose --') ])); - for (var i = 0; i < keys.length; i++) { + for (let i = 0; i < keys.length; i++) { if (keys[i] == null || keys[i] == '') continue; frameEl.lastChild.appendChild(E('option', { 'value': keys[i], 'selected': (this.values.indexOf(keys[i]) > -1) ? '' : null - }, [ this.choices[keys[i]] || keys[i] ])); + }, [ this.choices[keys[i]] ?? keys[i] ])); } } else { - var brEl = (this.options.orientation === 'horizontal') ? document.createTextNode(' \xa0 ') : E('br'); + const brEl = (this.options.orientation === 'horizontal') ? document.createTextNode(' \xa0 ') : E('br'); - for (var i = 0; i < keys.length; i++) { + for (let i = 0; i < keys.length; i++) { frameEl.appendChild(E('span', { 'class': 'cbi-%s'.format(this.options.multiple ? 'checkbox' : 'radio') }, [ E('input', { 'id': this.options.id ? 'widget.%s.%d'.format(this.options.id, i) : null, - 'name': this.options.id || this.options.name, + 'name': this.options.id ?? this.options.name, 'type': this.options.multiple ? 'checkbox' : 'radio', 'class': this.options.multiple ? 'cbi-input-checkbox' : 'cbi-input-radio', 'value': keys[i], @@ -829,7 +827,7 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { 'click': function(ev) { ev.currentTarget.previousElementSibling.previousElementSibling.click(); } - }, [ this.choices[keys[i]] || keys[i] ]) + }, [ this.choices[keys[i]] ?? keys[i] ]) ])); frameEl.appendChild(brEl.cloneNode()); @@ -840,7 +838,7 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { }, /** @private */ - bind: function(frameEl) { + bind(frameEl) { this.node = frameEl; if (this.options.widget != 'radio' && this.options.widget != 'checkbox') { @@ -848,8 +846,8 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { this.setChangeEvents(frameEl.firstChild, 'change'); } else { - var radioEls = frameEl.querySelectorAll('input[type="radio"]'); - for (var i = 0; i < radioEls.length; i++) { + const radioEls = frameEl.querySelectorAll('input[type="radio"]'); + for (let i = 0; i < radioEls.length; i++) { this.setUpdateEvents(radioEls[i], 'change', 'click', 'blur'); this.setChangeEvents(radioEls[i], 'change', 'click', 'blur'); } @@ -861,12 +859,12 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { }, /** @override */ - getValue: function() { + getValue() { if (this.options.widget != 'radio' && this.options.widget != 'checkbox') return this.node.firstChild.value; - var radioEls = this.node.querySelectorAll('input[type="radio"]'); - for (var i = 0; i < radioEls.length; i++) + const radioEls = this.node.querySelectorAll('input[type="radio"]'); + for (let i = 0; i < radioEls.length; i++) if (radioEls[i].checked) return radioEls[i].value; @@ -874,19 +872,19 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { }, /** @override */ - setValue: function(value) { + setValue(value) { if (this.options.widget != 'radio' && this.options.widget != 'checkbox') { if (value == null) value = ''; - for (var i = 0; i < this.node.firstChild.options.length; i++) + for (let i = 0; i < this.node.firstChild.options.length; i++) this.node.firstChild.options[i].selected = (this.node.firstChild.options[i].value == value); return; } - var radioEls = frameEl.querySelectorAll('input[type="radio"]'); - for (var i = 0; i < radioEls.length; i++) + const radioEls = frameEl.querySelectorAll('input[type="radio"]'); + for (let i = 0; i < radioEls.length; i++) radioEls[i].checked = (radioEls[i].value == value); } }); @@ -923,7 +921,7 @@ var UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ { * @param {LuCI.ui.Dropdown.InitOptions} [options] * Object describing the widget specific options to initialize the dropdown. */ -var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { +const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { /** * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions} * the following properties are recognized: @@ -1020,7 +1018,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { * compatibility reasons. It is usually better to `maxlength(N)` validation * expression. Only applicable when `create` is `true`. */ - __init__: function(value, choices, options) { + __init__(value, choices, options) { if (typeof(choices) != 'object') choices = {}; @@ -1045,8 +1043,8 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @override */ - render: function() { - var sb = E('div', { + render() { + const sb = E('div', { 'id': this.options.id, 'class': 'cbi-dropdown', 'multiple': this.options.multiple ? '' : null, @@ -1055,7 +1053,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { 'tabindex': -1 }, E('ul')); - var keys = Object.keys(this.choices); + let keys = Object.keys(this.choices); if (this.options.sort === true) keys.sort(L.naturalCompare); @@ -1063,12 +1061,12 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { keys = this.options.sort; if (this.options.create) - for (var i = 0; i < this.values.length; i++) + for (let i = 0; i < this.values.length; i++) if (!this.choices.hasOwnProperty(this.values[i])) keys.push(this.values[i]); - for (var i = 0; i < keys.length; i++) { - var label = this.choices[keys[i]]; + for (let i = 0; i < keys.length; i++) { + let label = this.choices[keys[i]]; if (dom.elem(label)) label = label.cloneNode(true); @@ -1076,20 +1074,20 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { sb.lastElementChild.appendChild(E('li', { 'data-value': keys[i], 'selected': (this.values.indexOf(keys[i]) > -1) ? '' : null - }, [ label || keys[i] ])); + }, [ label ?? keys[i] ])); } if (this.options.create) { - var createEl = E('input', { + const createEl = E('input', { 'type': 'text', 'class': 'create-item-input', 'readonly': this.options.readonly ? '' : null, 'maxlength': this.options.maxlength, - 'placeholder': this.options.custom_placeholder || this.options.placeholder + 'placeholder': this.options.custom_placeholder ?? this.options.placeholder }); if (this.options.datatype || this.options.validate) - UI.prototype.addValidator(createEl, this.options.datatype || 'string', + UI.prototype.addValidator(createEl, this.options.datatype ?? 'string', true, this.options.validate, 'blur', 'keyup'); sb.lastElementChild.appendChild(E('li', { 'data-value': '-' }, createEl)); @@ -1103,29 +1101,29 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - bind: function(sb) { - var o = this.options; + bind(sb) { + const o = this.options; o.multiple = sb.hasAttribute('multiple'); o.optional = sb.hasAttribute('optional'); - o.placeholder = sb.getAttribute('placeholder') || o.placeholder; - o.display_items = parseInt(sb.getAttribute('display-items') || o.display_items); - o.dropdown_items = parseInt(sb.getAttribute('dropdown-items') || o.dropdown_items); - o.create_query = sb.getAttribute('item-create') || o.create_query; - o.create_template = sb.getAttribute('item-template') || o.create_template; - - var ul = sb.querySelector('ul'), - more = sb.appendChild(E('span', { class: 'more', tabindex: -1 }, '···')), - open = sb.appendChild(E('span', { class: 'open', tabindex: -1 }, '▾')), - canary = sb.appendChild(E('div')), - create = sb.querySelector(this.options.create_query), - ndisplay = this.options.display_items, - n = 0; + o.placeholder = sb.getAttribute('placeholder') ?? o.placeholder; + o.display_items = parseInt(sb.getAttribute('display-items') ?? o.display_items); + o.dropdown_items = parseInt(sb.getAttribute('dropdown-items') ?? o.dropdown_items); + o.create_query = sb.getAttribute('item-create') ?? o.create_query; + o.create_template = sb.getAttribute('item-template') ?? o.create_template; + + const ul = sb.querySelector('ul'); + const more = sb.appendChild(E('span', { class: 'more', tabindex: -1 }, '···')); + const open = sb.appendChild(E('span', { class: 'open', tabindex: -1 }, '▾')); + const canary = sb.appendChild(E('div')); + const create = sb.querySelector(this.options.create_query); + let ndisplay = this.options.display_items; + let n = 0; if (this.options.multiple) { - var items = ul.querySelectorAll('li'); + let items = ul.querySelectorAll('li'); - for (var i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { this.transformItem(sb, items[i]); if (items[i].hasAttribute('selected') && ndisplay-- > 0) @@ -1134,22 +1132,22 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { } else { if (this.options.optional && !ul.querySelector('li[data-value=""]')) { - var placeholder = E('li', { placeholder: '' }, - this.options.select_placeholder || this.options.placeholder); + const placeholder = E('li', { placeholder: '' }, + this.options.select_placeholder ?? this.options.placeholder); ul.firstChild ? ul.insertBefore(placeholder, ul.firstChild) : ul.appendChild(placeholder); } - var items = ul.querySelectorAll('li'), - sel = sb.querySelectorAll('[selected]'); + let items = ul.querySelectorAll('li'); + const sel = sb.querySelectorAll('[selected]'); - sel.forEach(function(s) { + sel.forEach(s => { s.removeAttribute('selected'); }); - var s = sel[0] || items[0]; + const s = sel[0] ?? items[0]; if (s) { s.setAttribute('selected', ''); s.setAttribute('display', n++); @@ -1174,7 +1172,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { sb.removeAttribute('empty'); dom.content(more, (ndisplay == this.options.display_items) - ? (this.options.select_placeholder || this.options.placeholder) : '···'); + ? (this.options.select_placeholder ?? this.options.placeholder) : '···'); sb.addEventListener('click', this.handleClick.bind(this)); @@ -1183,7 +1181,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { sb.addEventListener('cbi-dropdown-select', this.handleDropdownSelect.bind(this)); if ('ontouchstart' in window) { - sb.addEventListener('touchstart', function(ev) { ev.stopPropagation(); }); + sb.addEventListener('touchstart', ev => ev.stopPropagation()); window.addEventListener('touchstart', this.closeAllDropdowns); } else { @@ -1201,7 +1199,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { create.addEventListener('focus', this.handleCreateFocus.bind(this)); create.addEventListener('blur', this.handleCreateBlur.bind(this)); - var li = findParent(create, 'li'); + const li = findParent(create, 'li'); li.setAttribute('unselectable', ''); li.addEventListener('click', this.handleCreateClick.bind(this)); @@ -1218,10 +1216,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - getScrollParent: function(element) { - var parent = element, - style = getComputedStyle(element), - excludeStaticParent = (style.position === 'absolute'); + getScrollParent(element) { + let parent = element; + let style = getComputedStyle(element); + const excludeStaticParent = (style.position === 'absolute'); if (style.position === 'fixed') return document.body; @@ -1240,49 +1238,49 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - openDropdown: function(sb) { - var st = window.getComputedStyle(sb, null), - ul = sb.querySelector('ul'), - li = ul.querySelectorAll('li'), - fl = findParent(sb, '.cbi-value-field'), - sel = ul.querySelector('[selected]'), - rect = sb.getBoundingClientRect(), - items = Math.min(this.options.dropdown_items, li.length), - scrollParent = this.getScrollParent(sb); - - document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) { + openDropdown(sb) { + const st = window.getComputedStyle(sb, null); + const ul = sb.querySelector('ul'); + const li = ul.querySelectorAll('li'); + const fl = findParent(sb, '.cbi-value-field'); + const sel = ul.querySelector('[selected]'); + const rect = sb.getBoundingClientRect(); + const items = Math.min(this.options.dropdown_items, li.length); + const scrollParent = this.getScrollParent(sb); + + document.querySelectorAll('.cbi-dropdown[open]').forEach(s => { s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {})); }); sb.setAttribute('open', ''); - var pv = ul.cloneNode(true); - pv.classList.add('preview'); + const pv = ul.cloneNode(true); + pv.classList.add('preview'); if (fl) fl.classList.add('cbi-dropdown-open'); if ('ontouchstart' in window) { - var vpWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0), - vpHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0), - start = null; - - ul.style.top = sb.offsetHeight + 'px'; - ul.style.left = -rect.left + 'px'; - ul.style.right = (rect.right - vpWidth) + 'px'; - ul.style.maxHeight = (vpHeight * 0.5) + 'px'; + const vpWidth = Math.max(document.documentElement.clientWidth, window.innerWidth ?? 0); + const vpHeight = Math.max(document.documentElement.clientHeight, window.innerHeight ?? 0); + let start = null; + + ul.style.top = `${sb.offsetHeight}px`; + ul.style.left = `${-rect.left}px`; + ul.style.right = `${rect.right - vpWidth}px`; + ul.style.maxHeight = `${vpHeight * 0.5}px`; ul.style.WebkitOverflowScrolling = 'touch'; - var scrollFrom = scrollParent.scrollTop, - scrollTo = scrollFrom + rect.top - vpHeight * 0.5; + const scrollFrom = scrollParent.scrollTop; + const scrollTo = scrollFrom + rect.top - vpHeight * 0.5; - var scrollStep = function(timestamp) { + const scrollStep = timestamp => { if (!start) { start = timestamp; ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0; } - var duration = Math.max(timestamp - start, 1); + const duration = Math.max(timestamp - start, 1); if (duration < 100) { scrollParent.scrollTop = scrollFrom + (scrollTo - scrollFrom) * (duration / 100); window.requestAnimationFrame(scrollStep); @@ -1298,72 +1296,72 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { ul.style.maxHeight = '1px'; ul.style.top = ul.style.bottom = ''; - window.requestAnimationFrame(function() { - var containerRect = scrollParent.getBoundingClientRect(), - itemHeight = li[Math.max(0, li.length - 2)].getBoundingClientRect().height, - fullHeight = 0, - spaceAbove = rect.top - containerRect.top, - spaceBelow = containerRect.bottom - rect.bottom; + window.requestAnimationFrame(() => { + const containerRect = scrollParent.getBoundingClientRect(); + const itemHeight = li[Math.max(0, li.length - 2)].getBoundingClientRect().height; + let fullHeight = 0; + const spaceAbove = rect.top - containerRect.top; + const spaceBelow = containerRect.bottom - rect.bottom; - for (var i = 0; i < (items == -1 ? li.length : items); i++) + for (let i = 0; i < (items == -1 ? li.length : items); i++) fullHeight += li[i].getBoundingClientRect().height; if (fullHeight <= spaceBelow) { - ul.style.top = rect.height + 'px'; - ul.style.maxHeight = spaceBelow + 'px'; + ul.style.top = `${rect.height}px`; + ul.style.maxHeight = `${spaceBelow}px`; } else if (fullHeight <= spaceAbove) { - ul.style.bottom = rect.height + 'px'; - ul.style.maxHeight = spaceAbove + 'px'; + ul.style.bottom = `${rect.height}px`; + ul.style.maxHeight = `${spaceAbove}px`; } else if (spaceBelow >= spaceAbove) { - ul.style.top = rect.height + 'px'; - ul.style.maxHeight = (spaceBelow - (spaceBelow % itemHeight)) + 'px'; + ul.style.top = `${rect.height}px`; + ul.style.maxHeight = `${spaceBelow - (spaceBelow % itemHeight)}px`; } else { - ul.style.bottom = rect.height + 'px'; - ul.style.maxHeight = (spaceAbove - (spaceAbove % itemHeight)) + 'px'; + ul.style.bottom = `${rect.height}px`; + ul.style.maxHeight = `${spaceAbove - (spaceAbove % itemHeight)}px`; } ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0; }); } - var cboxes = ul.querySelectorAll('[selected] input[type="checkbox"]'); - for (var i = 0; i < cboxes.length; i++) { + const cboxes = ul.querySelectorAll('[selected] input[type="checkbox"]'); + for (let i = 0; i < cboxes.length; i++) { cboxes[i].checked = true; cboxes[i].disabled = (cboxes.length == 1 && !this.options.optional); - }; + } ul.classList.add('dropdown'); sb.insertBefore(pv, ul.nextElementSibling); - li.forEach(function(l) { + li.forEach(l => { l.setAttribute('tabindex', 0); }); sb.lastElementChild.setAttribute('tabindex', 0); - var focusFn = L.bind(function(el) { + const focusFn = L.bind(function(el) { this.setFocus(sb, el, true); ul.removeEventListener('transitionend', focusFn); - }, this, sel || li[0]); + }, this, sel ?? li[0]); ul.addEventListener('transitionend', focusFn); }, /** @private */ - closeDropdown: function(sb, no_focus) { + closeDropdown(sb, no_focus) { if (!sb.hasAttribute('open')) return; - var pv = sb.querySelector('ul.preview'), - ul = sb.querySelector('ul.dropdown'), - li = ul.querySelectorAll('li'), - fl = findParent(sb, '.cbi-value-field'); + const pv = sb.querySelector('ul.preview'); + const ul = sb.querySelector('ul.dropdown'); + const li = ul.querySelectorAll('li'); + const fl = findParent(sb, '.cbi-value-field'); - li.forEach(function(l) { l.removeAttribute('tabindex'); }); + li.forEach(l => l.removeAttribute('tabindex')); sb.lastElementChild.removeAttribute('tabindex'); sb.removeChild(pv); @@ -1383,20 +1381,20 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - toggleItem: function(sb, li, force_state) { - var ul = li.parentNode; + toggleItem(sb, li, force_state) { + const ul = li.parentNode; if (li.hasAttribute('unselectable')) return; if (this.options.multiple) { - var cbox = li.querySelector('input[type="checkbox"]'), - items = li.parentNode.querySelectorAll('li'), - label = sb.querySelector('ul.preview'), - sel = li.parentNode.querySelectorAll('[selected]').length, - more = sb.querySelector('.more'), - ndisplay = this.options.display_items, - n = 0; + const cbox = li.querySelector('input[type="checkbox"]'); + const items = li.parentNode.querySelectorAll('li'); + const label = sb.querySelector('ul.preview'); + let sel = li.parentNode.querySelectorAll('[selected]').length; + const more = sb.querySelector('.more'); + let ndisplay = this.options.display_items; + let n = 0; if (li.hasAttribute('selected')) { if (force_state !== true) { @@ -1422,7 +1420,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { while (label && label.firstElementChild) label.removeChild(label.firstElementChild); - for (var i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { items[i].removeAttribute('display'); if (items[i].hasAttribute('selected')) { if (ndisplay-- > 0) { @@ -1430,7 +1428,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { if (label) label.appendChild(items[i].cloneNode(true)); } - var c = items[i].querySelector('input[type="checkbox"]'); + const c = items[i].querySelector('input[type="checkbox"]'); if (c) c.disabled = (sel == 1 && !this.options.optional); } @@ -1447,10 +1445,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { sb.removeAttribute('empty'); dom.content(more, (ndisplay === this.options.display_items) - ? (this.options.select_placeholder || this.options.placeholder) : '···'); + ? (this.options.select_placeholder ?? this.options.placeholder) : '···'); } else { - var sel = li.parentNode.querySelector('[selected]'); + let sel = li.parentNode.querySelector('[selected]'); if (sel) { sel.removeAttribute('display'); sel.removeAttribute('selected'); @@ -1466,9 +1464,9 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - transformItem: function(sb, li) { - var cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' })), - label = E('label'); + transformItem(sb, li) { + const cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' })); + const label = E('label'); while (li.firstChild) label.appendChild(li.firstChild); @@ -1478,21 +1476,21 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - saveValues: function(sb, ul) { - var sel = ul.querySelectorAll('li[selected]'), - div = sb.lastElementChild, - name = this.options.name, - strval = '', - values = []; + saveValues(sb, ul) { + const sel = ul.querySelectorAll('li[selected]'); + const div = sb.lastElementChild; + const name = this.options.name; + let strval = ''; + const values = []; while (div.lastElementChild) div.removeChild(div.lastElementChild); - sel.forEach(function (s) { + sel.forEach(s => { if (s.hasAttribute('placeholder')) return; - var v = { + const v = { text: s.innerText, value: s.hasAttribute('data-value') ? s.getAttribute('data-value') : s.innerText, element: s @@ -1506,10 +1504,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { values.push(v); - strval += strval.length ? ' ' + v.value : v.value; + strval += strval.length ? ` ${v.value}` : v.value; }); - var detail = { + const detail = { instance: this, element: sb }; @@ -1528,11 +1526,11 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - setValues: function(sb, values) { - var ul = sb.querySelector('ul'); + setValues(sb, values) { + const ul = sb.querySelector('ul'); if (this.options.create) { - for (var value in values) { + for (const value in values) { this.createItems(sb, value); if (!this.options.multiple) @@ -1541,9 +1539,9 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { } if (this.options.multiple) { - var lis = ul.querySelectorAll('li[data-value]'); - for (var i = 0; i < lis.length; i++) { - var value = lis[i].getAttribute('data-value'); + const lis = ul.querySelectorAll('li[data-value]'); + for (let i = 0; i < lis.length; i++) { + const value = lis[i].getAttribute('data-value'); if (values === null || !(value in values)) this.toggleItem(sb, lis[i], false); else @@ -1551,13 +1549,13 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { } } else { - var ph = ul.querySelector('li[placeholder]'); + const ph = ul.querySelector('li[placeholder]'); if (ph) this.toggleItem(sb, ph); - var lis = ul.querySelectorAll('li[data-value]'); - for (var i = 0; i < lis.length; i++) { - var value = lis[i].getAttribute('data-value'); + const lis = ul.querySelectorAll('li[data-value]'); + for (let i = 0; i < lis.length; i++) { + const value = lis[i].getAttribute('data-value'); if (values !== null && (value in values)) this.toggleItem(sb, lis[i]); } @@ -1565,11 +1563,11 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - setFocus: function(sb, elem, scroll) { + setFocus(sb, elem, scroll) { if (sb.hasAttribute('locked-in')) return; - sb.querySelectorAll('.focus').forEach(function(e) { + sb.querySelectorAll('.focus').forEach(e => { e.classList.remove('focus'); }); @@ -1582,13 +1580,13 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleMouseout: function(ev) { - var sb = ev.currentTarget; + handleMouseout(ev) { + const sb = ev.currentTarget; if (!sb.hasAttribute('open')) return; - sb.querySelectorAll('.focus').forEach(function(e) { + sb.querySelectorAll('.focus').forEach(e => { e.classList.remove('focus'); }); @@ -1596,20 +1594,20 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - createChoiceElement: function(sb, value, label) { - var tpl = sb.querySelector(this.options.create_template), - markup = null; + createChoiceElement(sb, value, label) { + const tpl = sb.querySelector(this.options.create_template); + let markup = null; if (tpl) - markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim(); + markup = (tpl.textContent ?? tpl.innerHTML ?? tpl.firstChild.data).replace(/^<!--|-->$/, '').trim(); else markup = '<li data-value="{{value}}"><span data-label-placeholder="true" /></li>'; - var new_item = E(markup.replace(/{{value}}/g, '%h'.format(value))), - placeholder = new_item.querySelector('[data-label-placeholder]'); + const new_item = E(markup.replace(/{{value}}/g, '%h'.format(value))); + const placeholder = new_item.querySelector('[data-label-placeholder]'); if (placeholder) { - var content = E('span', {}, label || this.choices[value] || [ value ]); + const content = E('span', {}, label ?? this.choices[value] ?? [ value ]); while (content.firstChild) placeholder.parentNode.insertBefore(content.firstChild, placeholder); @@ -1624,20 +1622,20 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - createItems: function(sb, value) { - var sbox = this, - val = (value || '').trim(), - ul = sb.querySelector('ul'); + createItems(sb, value) { + const sbox = this; + let val = (value ?? '').trim(); + const ul = sb.querySelector('ul'); if (!sbox.options.multiple) val = val.length ? [ val ] : []; else val = val.length ? val.split(/\s+/) : []; - val.forEach(function(item) { - var new_item = null; + val.forEach(item => { + let new_item = null; - ul.childNodes.forEach(function(li) { + ul.childNodes.forEach(li => { if (li.getAttribute && li.getAttribute('data-value') === item) new_item = li; }); @@ -1646,7 +1644,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { new_item = sbox.createChoiceElement(sb, item); if (!sbox.options.multiple) { - var old = ul.querySelector('li[created]'); + const old = ul.querySelector('li[created]'); if (old) ul.removeChild(old); @@ -1674,14 +1672,14 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { * If set to `true`, deselect and remove selected choices as well instead * of keeping them. */ - clearChoices: function(reset_value) { - var ul = this.node.querySelector('ul'), - lis = ul ? ul.querySelectorAll('li[data-value]') : [], - len = lis.length - (this.options.create ? 1 : 0), - val = reset_value ? null : this.getValue(); - - for (var i = 0; i < len; i++) { - var lival = lis[i].getAttribute('data-value'); + clearChoices(reset_value) { + const ul = this.node.querySelector('ul'); + const lis = ul ? ul.querySelectorAll('li[data-value]') : []; + const len = lis.length - (this.options.create ? 1 : 0); + const val = reset_value ? null : this.getValue(); + + for (let i = 0; i < len; i++) { + const lival = lis[i].getAttribute('data-value'); if (val == null || (!this.options.multiple && val != lival) || (this.options.multiple && val.indexOf(lival) == -1)) @@ -1709,10 +1707,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { * as label text. Choice labels may be any valid value accepted by * {@link LuCI.dom#content}. */ - addChoices: function(values, labels) { - var sb = this.node, - ul = sb.querySelector('ul'), - lis = ul ? ul.querySelectorAll('li[data-value]') : []; + addChoices(values, labels) { + const sb = this.node; + const ul = sb.querySelector('ul'); + const lis = ul ? ul.querySelectorAll('li[data-value]') : []; if (!Array.isArray(values)) values = L.toArray(values); @@ -1720,10 +1718,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { if (!L.isObject(labels)) labels = {}; - for (var i = 0; i < values.length; i++) { - var found = false; + for (let i = 0; i < values.length; i++) { + let found = false; - for (var j = 0; j < lis.length; j++) { + for (let j = 0; j < lis.length; j++) { if (lis[j].getAttribute('data-value') === values[i]) { found = true; break; @@ -1742,22 +1740,22 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { /** * Close all open dropdown widgets in the current document. */ - closeAllDropdowns: function() { - document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) { + closeAllDropdowns() { + document.querySelectorAll('.cbi-dropdown[open]').forEach(s => { s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {})); }); }, /** @private */ - handleClick: function(ev) { - var sb = ev.currentTarget; + handleClick(ev) { + const sb = ev.currentTarget; if (!sb.hasAttribute('open')) { if (!matchesElem(ev.target, 'input')) this.openDropdown(sb); } else { - var li = findParent(ev.target, 'li'); + const li = findParent(ev.target, 'li'); if (li && li.parentNode.classList.contains('dropdown')) this.toggleItem(sb, li); else if (li && li.parentNode.classList.contains('preview')) @@ -1771,9 +1769,9 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleKeydown: function(ev) { - var sb = ev.currentTarget, - ul = sb.querySelector('ul.dropdown'); + handleKeydown(ev) { + const sb = ev.currentTarget; + const ul = sb.querySelector('ul.dropdown'); if (matchesElem(ev.target, 'input')) return; @@ -1789,7 +1787,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { } } else { - var active = findParent(document.activeElement, 'li'); + const active = findParent(document.activeElement, 'li'); switch (ev.keyCode) { case 27: @@ -1839,16 +1837,16 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleDropdownClose: function(ev) { - var sb = ev.currentTarget; + handleDropdownClose(ev) { + const sb = ev.currentTarget; this.closeDropdown(sb, true); }, /** @private */ - handleDropdownSelect: function(ev) { - var sb = ev.currentTarget, - li = findParent(ev.target, 'li'); + handleDropdownSelect(ev) { + const sb = ev.currentTarget; + const li = findParent(ev.target, 'li'); if (!li) return; @@ -1858,37 +1856,37 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleMouseover: function(ev) { - var sb = ev.currentTarget; + handleMouseover(ev) { + const sb = ev.currentTarget; if (!sb.hasAttribute('open')) return; - var li = findParent(ev.target, 'li'); + const li = findParent(ev.target, 'li'); if (li && li.parentNode.classList.contains('dropdown')) this.setFocus(sb, li); }, /** @private */ - handleFocus: function(ev) { - var sb = ev.currentTarget; + handleFocus(ev) { + const sb = ev.currentTarget; - document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) { + document.querySelectorAll('.cbi-dropdown[open]').forEach(s => { if (s !== sb || sb.hasAttribute('open')) s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {})); }); }, /** @private */ - handleCanaryFocus: function(ev) { + handleCanaryFocus(ev) { this.closeDropdown(ev.currentTarget.parentNode); }, /** @private */ - handleCreateKeydown: function(ev) { - var input = ev.currentTarget, - sb = findParent(input, '.cbi-dropdown'); + handleCreateKeydown(ev) { + const input = ev.currentTarget; + const sb = findParent(input, '.cbi-dropdown'); switch (ev.keyCode) { case 13: @@ -1905,10 +1903,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleCreateFocus: function(ev) { - var input = ev.currentTarget, - cbox = findParent(input, 'li').querySelector('input[type="checkbox"]'), - sb = findParent(input, '.cbi-dropdown'); + handleCreateFocus(ev) { + const input = ev.currentTarget; + const cbox = findParent(input, 'li').querySelector('input[type="checkbox"]'); + const sb = findParent(input, '.cbi-dropdown'); if (cbox) cbox.checked = true; @@ -1917,10 +1915,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleCreateBlur: function(ev) { - var input = ev.currentTarget, - cbox = findParent(input, 'li').querySelector('input[type="checkbox"]'), - sb = findParent(input, '.cbi-dropdown'); + handleCreateBlur(ev) { + const input = ev.currentTarget; + const cbox = findParent(input, 'li').querySelector('input[type="checkbox"]'); + const sb = findParent(input, '.cbi-dropdown'); if (cbox) cbox.checked = false; @@ -1929,25 +1927,25 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @private */ - handleCreateClick: function(ev) { + handleCreateClick(ev) { ev.currentTarget.querySelector(this.options.create_query).focus(); }, /** @override */ - setValue: function(values) { + setValue(values) { if (this.options.multiple) { if (!Array.isArray(values)) values = (values != null && values != '') ? [ values ] : []; - var v = {}; + const v = {}; - for (var i = 0; i < values.length; i++) + for (let i = 0; i < values.length; i++) v[values[i]] = true; this.setValues(this.node, v); } else { - var v = {}; + const v = {}; if (values != null) { if (Array.isArray(values)) @@ -1961,12 +1959,12 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { }, /** @override */ - getValue: function() { - var div = this.node.lastElementChild, - h = div.querySelectorAll('input[type="hidden"]'), - v = []; + getValue() { + const div = this.node.lastElementChild; + const h = div.querySelectorAll('input[type="hidden"]'); + const v = []; - for (var i = 0; i < h.length; i++) + for (let i = 0; i < h.length; i++) v.push(h[i].value); return this.options.multiple ? v : v[0]; @@ -2007,7 +2005,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ { * @param {LuCI.ui.Combobox.InitOptions} [options] * Object describing the widget specific options to initialize the dropdown. */ -var UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ { +const UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ { /** * Comboboxes support the same properties as * [Dropdown.InitOptions]{@link LuCI.ui.Dropdown.InitOptions} but enforce @@ -2028,7 +2026,7 @@ var UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ { * Since Comboboxes are always optional, this property is forcibly set to * `true`. */ - __init__: function(value, choices, options) { + __init__(value, choices, options) { this.super('__init__', [ value, choices, Object.assign({ select_placeholder: _('-- Please choose --'), custom_placeholder: _('-- custom --'), @@ -2074,7 +2072,7 @@ var UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ { * @param {LuCI.ui.ComboButton.InitOptions} [options] * Object describing the widget specific options to initialize the button. */ -var UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype */ { +const UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype */ { /** * ComboButtons support the same properties as * [Dropdown.InitOptions]{@link LuCI.ui.Dropdown.InitOptions} but enforce @@ -2111,7 +2109,7 @@ var UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype * * and receive the DOM click event as first as well as the selected action * choice value as second argument. */ - __init__: function(value, choices, options) { + __init__(value, choices, options) { this.super('__init__', [ value, choices, Object.assign({ sort: true }, options, { @@ -2122,35 +2120,35 @@ var UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype * }, /** @override */ - render: function(/* ... */) { - var node = UIDropdown.prototype.render.apply(this, arguments), - val = this.getValue(); + render(...args) { + const node = UIDropdown.prototype.render.call(this, ...args); + const val = this.getValue(); if (L.isObject(this.options.classes) && this.options.classes.hasOwnProperty(val)) - node.setAttribute('class', 'cbi-dropdown ' + this.options.classes[val]); + node.setAttribute('class', `cbi-dropdown ${this.options.classes[val]}`); return node; }, /** @private */ - handleClick: function(ev) { - var sb = ev.currentTarget, - t = ev.target; + handleClick(ev, ...args) { + const sb = ev.currentTarget; + const t = ev.target; if (sb.hasAttribute('open') || dom.matches(t, '.cbi-dropdown > span.open')) - return UIDropdown.prototype.handleClick.apply(this, arguments); + return UIDropdown.prototype.handleClick.call(this, ev, ...args); if (this.options.click) return this.options.click.call(sb, ev, this.getValue()); }, /** @private */ - toggleItem: function(sb /*, ... */) { - var rv = UIDropdown.prototype.toggleItem.apply(this, arguments), - val = this.getValue(); + toggleItem(sb, ...args) { + const rv = UIDropdown.prototype.toggleItem.call(this, sb, ...args); + const val = this.getValue(); if (L.isObject(this.options.classes) && this.options.classes.hasOwnProperty(val)) - sb.setAttribute('class', 'cbi-dropdown ' + this.options.classes[val]); + sb.setAttribute('class', `cbi-dropdown ${this.options.classes[val]}`); else sb.setAttribute('class', 'cbi-dropdown'); @@ -2193,7 +2191,7 @@ var UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype * * @param {LuCI.ui.DynamicList.InitOptions} [options] * Object describing the widget specific options to initialize the dynamic list. */ -var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ { +const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ { /** * In case choices are passed to the dynamic list constructor, the widget * supports the same properties as [Dropdown.InitOptions]{@link LuCI.ui.Dropdown.InitOptions} @@ -2211,7 +2209,7 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ * predefined choice values, the dropdown must be made optional to allow * it to remain unselected. */ - __init__: function(values, choices, options) { + __init__(values, choices, options) { if (!Array.isArray(values)) values = (values != null && values != '') ? [ values ] : []; @@ -2227,8 +2225,8 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @override */ - render: function() { - var dl = E('div', { + render() { + const dl = E('div', { 'id': this.options.id, 'class': 'cbi-dynlist', 'disabled': this.options.disabled ? '' : null @@ -2238,13 +2236,13 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ if (this.options.placeholder != null) this.options.select_placeholder = this.options.placeholder; - var cbox = new UICombobox(null, this.choices, this.options); + const cbox = new UICombobox(null, this.choices, this.options); dl.lastElementChild.appendChild(cbox.render()); } else { - var inputEl = E('input', { - 'id': this.options.id ? 'widget.' + this.options.id : null, + const inputEl = E('input', { + 'id': this.options.id ? `widget.${this.options.id}` : null, 'type': 'text', 'class': 'cbi-input-text', 'placeholder': this.options.placeholder, @@ -2255,12 +2253,12 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ dl.lastElementChild.appendChild(E('div', { 'class': 'btn cbi-button cbi-button-add' }, '+')); if (this.options.datatype || this.options.validate) - UI.prototype.addValidator(inputEl, this.options.datatype || 'string', + UI.prototype.addValidator(inputEl, this.options.datatype ?? 'string', true, this.options.validate, 'blur', 'keyup'); } - for (var i = 0; i < this.values.length; i++) { - var label = this.choices ? this.choices[this.values[i]] : null; + for (let i = 0; i < this.values.length; i++) { + let label = this.choices ? this.choices[this.values[i]] : null; if (dom.elem(label)) label = label.cloneNode(true); @@ -2272,7 +2270,7 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @private */ - bind: function(dl) { + bind(dl) { dl.addEventListener('click', L.bind(this.handleClick, this)); dl.addEventListener('keydown', L.bind(this.handleKeydown, this)); dl.addEventListener('cbi-dropdown-change', L.bind(this.handleDropdownChange, this)); @@ -2288,20 +2286,21 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @private */ - addItem: function(dl, value, text, flash) { - var exists = false, - new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex': 0 }, [ - E('span', {}, [ text || value ]), - E('input', { - 'type': 'hidden', - 'name': this.options.name, - 'value': value })]); + addItem(dl, value, text, flash) { + let exists = false; + + const new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex': 0 }, [ + E('span', {}, [ text ?? value ]), + E('input', { + 'type': 'hidden', + 'name': this.options.name, + 'value': value })]); - dl.querySelectorAll('.item').forEach(function(item) { + dl.querySelectorAll('.item').forEach(item => { if (exists) return; - var hidden = item.querySelector('input[type="hidden"]'); + let hidden = item.querySelector('input[type="hidden"]'); if (hidden && hidden.parentNode !== item) hidden = null; @@ -2311,7 +2310,7 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }); if (!exists) { - var ai = dl.querySelector('.add-item'); + const ai = dl.querySelector('.add-item'); ai.parentNode.insertBefore(new_item, ai); } @@ -2327,11 +2326,11 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @private */ - removeItem: function(dl, item) { - var value = item.querySelector('input[type="hidden"]').value; - var sb = dl.querySelector('.cbi-dropdown'); + removeItem(dl, item) { + const value = item.querySelector('input[type="hidden"]').value; + const sb = dl.querySelector('.cbi-dropdown'); if (sb) - sb.querySelectorAll('ul > li').forEach(function(li) { + sb.querySelectorAll('ul > li').forEach(li => { if (li.getAttribute('data-value') === value) { if (li.hasAttribute('dynlistcustom')) li.parentNode.removeChild(li); @@ -2354,9 +2353,9 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @private */ - handleClick: function(ev) { - var dl = ev.currentTarget, - item = findParent(ev.target, '.item'); + handleClick(ev) { + const dl = ev.currentTarget; + const item = findParent(ev.target, '.item'); if (this.options.disabled) return; @@ -2365,7 +2364,7 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ this.removeItem(dl, item); } else if (matchesElem(ev.target, '.cbi-button-add')) { - var input = ev.target.previousElementSibling; + const input = ev.target.previousElementSibling; if (input.value.length && !input.classList.contains('cbi-input-invalid')) { this.addItem(dl, input.value, null, true); input.value = ''; @@ -2374,11 +2373,11 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @private */ - handleDropdownChange: function(ev) { - var dl = ev.currentTarget, - sbIn = ev.detail.instance, - sbEl = ev.detail.element, - sbVal = ev.detail.value; + handleDropdownChange(ev) { + const dl = ev.currentTarget; + const sbIn = ev.detail.instance; + const sbEl = ev.detail.element; + const sbVal = ev.detail.value; if (sbVal === null) return; @@ -2391,12 +2390,12 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ sbVal.element.setAttribute('dynlistcustom', ''); } - var label = sbVal.text; + let label = sbVal.text; if (sbVal.element) { label = E([]); - for (var i = 0; i < sbVal.element.childNodes.length; i++) + for (let i = 0; i < sbVal.element.childNodes.length; i++) label.appendChild(sbVal.element.childNodes[i].cloneNode(true)); } @@ -2404,9 +2403,9 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @private */ - handleKeydown: function(ev) { - var dl = ev.currentTarget, - item = findParent(ev.target, '.item'); + handleKeydown(ev) { + const dl = ev.currentTarget; + const item = findParent(ev.target, '.item'); if (item) { switch (ev.keyCode) { @@ -2446,34 +2445,34 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ }, /** @override */ - getValue: function() { - var items = this.node.querySelectorAll('.item > input[type="hidden"]'), - input = this.node.querySelector('.add-item > input[type="text"]'), - v = []; + getValue() { + const items = this.node.querySelectorAll('.item > input[type="hidden"]'); + const input = this.node.querySelector('.add-item > input[type="text"]'); + const v = []; - for (var i = 0; i < items.length; i++) + for (let i = 0; i < items.length; i++) v.push(items[i].value); if (input && input.value != null && input.value.match(/\S/) && input.classList.contains('cbi-input-invalid') == false && - v.filter(function(s) { return s == input.value }).length == 0) + v.filter(s => s == input.value).length == 0) v.push(input.value); return v; }, /** @override */ - setValue: function(values) { + setValue(values) { if (!Array.isArray(values)) values = (values != null && values != '') ? [ values ] : []; - var items = this.node.querySelectorAll('.item'); + const items = this.node.querySelectorAll('.item'); - for (var i = 0; i < items.length; i++) + for (let i = 0; i < items.length; i++) if (items[i].parentNode === this.node) this.removeItem(this.node, items[i]); - for (var i = 0; i < values.length; i++) + for (let i = 0; i < values.length; i++) this.addItem(this.node, values[i], this.choices ? this.choices[values[i]] : null); }, @@ -2495,8 +2494,8 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ * as label text. Choice labels may be any valid value accepted by * {@link LuCI.dom#content}. */ - addChoices: function(values, labels) { - var dl = this.node.lastElementChild.firstElementChild; + addChoices(values, labels) { + const dl = this.node.lastElementChild.firstElementChild; dom.callClassMethod(dl, 'addChoices', values, labels); }, @@ -2508,8 +2507,8 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ * @instance * @memberof LuCI.ui.DynamicList */ - clearChoices: function() { - var dl = this.node.lastElementChild.firstElementChild; + clearChoices() { + const dl = this.node.lastElementChild.firstElementChild; dom.callClassMethod(dl, 'clearChoices'); } }); @@ -2541,8 +2540,8 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ * @param {LuCI.ui.AbstractElement.InitOptions} [options] * Object describing the widget specific options to initialize the hidden input. */ -var UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ { - __init__: function(value, options) { +const UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ { + __init__(value, options) { this.value = value; this.options = Object.assign({ @@ -2550,8 +2549,8 @@ var UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ }, /** @override */ - render: function() { - var hiddenEl = E('input', { + render() { + const hiddenEl = E('input', { 'id': this.options.id, 'type': 'hidden', 'value': this.value @@ -2561,7 +2560,7 @@ var UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ }, /** @private */ - bind: function(hiddenEl) { + bind(hiddenEl) { this.node = hiddenEl; dom.bindClassInstance(hiddenEl, this); @@ -2570,12 +2569,12 @@ var UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ }, /** @override */ - getValue: function() { + getValue() { return this.node.value; }, /** @override */ - setValue: function(value) { + setValue(value) { this.node.value = value; } }); @@ -2608,7 +2607,7 @@ var UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype */ * Object describing the widget specific options to initialize the file * upload control. */ -var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { +const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { /** * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions} * the following properties are recognized: @@ -2643,7 +2642,7 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { * Whether remote directories are browsable or not solely depends on the * ACL setup for the current session. */ - __init__: function(value, options) { + __init__(value, options) { this.value = value; this.options = Object.assign({ show_hidden: false, @@ -2654,7 +2653,7 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - bind: function(browserEl) { + bind(browserEl) { this.node = browserEl; this.setUpdateEvents(browserEl, 'cbi-fileupload-select', 'cbi-fileupload-cancel'); @@ -2666,9 +2665,9 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @override */ - render: function() { + render() { return L.resolveDefault(this.value != null ? fs.stat(this.value) : null).then(L.bind(function(stat) { - var label; + let label; if (L.isObject(stat) && stat.type != 'directory') this.stat = stat; @@ -2699,15 +2698,15 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - truncatePath: function(path) { + truncatePath(path) { if (path.length > 50) - path = path.substring(0, 25) + '…' + path.substring(path.length - 25); + path = `${path.substring(0, 25)}…${path.substring(path.length - 25)}`; return path; }, /** @private */ - iconForType: function(type) { + iconForType(type) { switch (type) { case 'symlink': return E('img', { @@ -2736,7 +2735,7 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - canonicalizePath: function(path) { + canonicalizePath(path) { return path.replace(/\/{2,}/, '/') .replace(/\/\.(\/|$)/g, '/') .replace(/[^\/]+\/\.\.(\/|$)/g, '/') @@ -2744,9 +2743,9 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - splitPath: function(path) { - var croot = this.canonicalizePath(this.options.root_directory || '/'), - cpath = this.canonicalizePath(path || '/'); + splitPath(path) { + const croot = this.canonicalizePath(this.options.root_directory ?? '/'); + const cpath = this.canonicalizePath(path ?? '/'); if (cpath.length <= croot.length) return [ croot ]; @@ -2754,7 +2753,7 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { if (cpath.charAt(croot.length) != '/') return [ croot ]; - var parts = cpath.substring(croot.length + 1).split(/\//); + const parts = cpath.substring(croot.length + 1).split(/\//); parts.unshift(croot); @@ -2762,36 +2761,36 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - handleUpload: function(path, list, ev) { - var form = ev.target.parentNode, - fileinput = form.querySelector('input[type="file"]'), - nameinput = form.querySelector('input[type="text"]'), - filename = (nameinput.value != null ? nameinput.value : '').trim(); + handleUpload(path, list, ev) { + const form = ev.target.parentNode; + const fileinput = form.querySelector('input[type="file"]'); + const nameinput = form.querySelector('input[type="text"]'); + const filename = (nameinput.value != null ? nameinput.value : '').trim(); ev.preventDefault(); if (filename == '' || filename.match(/\//) || fileinput.files[0] == null) return; - var existing = list.filter(function(e) { return e.name == filename })[0]; + const existing = list.filter(e => e.name == filename)[0]; if (existing != null && existing.type == 'directory') return alert(_('A directory with the same name already exists.')); else if (existing != null && !confirm(_('Overwrite existing file "%s" ?').format(filename))) return; - var data = new FormData(); + const data = new FormData(); data.append('sessionid', L.env.sessionid); - data.append('filename', path + '/' + filename); + data.append('filename', `${path}/${filename}`); data.append('filedata', fileinput.files[0]); - return request.post(L.env.cgi_base + '/cgi-upload', data, { - progress: L.bind(function(btn, ev) { + return request.post(`${L.env.cgi_base}/cgi-upload`, data, { + progress: L.bind((btn, ev) => { btn.firstChild.data = '%.2f%%'.format((ev.loaded / ev.total) * 100); }, this, ev.target) }).then(L.bind(function(path, ev, res) { - var reply = res.json(); + const reply = res.json(); if (L.isObject(reply) && reply.failure) alert(_('Upload request failed: %s').format(reply.message)); @@ -2801,10 +2800,10 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - handleDelete: function(path, fileStat, ev) { - var parent = path.replace(/\/[^\/]+$/, '') || '/', - name = path.replace(/^.+\//, ''), - msg; + handleDelete(path, fileStat, ev) { + const parent = path.replace(/\/[^\/]+$/, '') ?? '/'; + const name = path.replace(/^.+\//, ''); + let msg; ev.preventDefault(); @@ -2814,8 +2813,8 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { msg = _('Do you really want to delete "%s" ?').format(name); if (confirm(msg)) { - var button = this.node.firstElementChild, - hidden = this.node.lastElementChild; + const button = this.node.firstElementChild; + const hidden = this.node.lastElementChild; if (path == hidden.value) { dom.content(button, _('Select file…')); @@ -2824,14 +2823,14 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { return fs.remove(path).then(L.bind(function(parent, ev) { return this.handleSelect(parent, null, ev); - }, this, parent, ev)).catch(function(err) { + }, this, parent, ev)).catch(err => { alert(_('Delete request failed: %s').format(err.message)); }); } }, /** @private */ - renderUpload: function(path, list) { + renderUpload(path, list) { if (!this.options.enable_upload) return E([]); @@ -2840,8 +2839,8 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { 'href': '#', 'class': 'btn cbi-button-positive', 'click': function(ev) { - var uploadForm = ev.target.nextElementSibling, - fileInput = uploadForm.querySelector('input[type="file"]'); + const uploadForm = ev.target.nextElementSibling; + const fileInput = uploadForm.querySelector('input[type="file"]'); ev.target.style.display = 'none'; uploadForm.style.display = ''; @@ -2853,8 +2852,8 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { 'type': 'file', 'style': 'display:none', 'change': function(ev) { - var nameinput = ev.target.parentNode.querySelector('input[type="text"]'), - uploadbtn = ev.target.parentNode.querySelector('button.cbi-button-save'); + const nameinput = ev.target.parentNode.querySelector('input[type="text"]'); + const uploadbtn = ev.target.parentNode.querySelector('button.cbi-button-save'); nameinput.value = ev.target.value.replace(/^.+[\/\\]/, ''); uploadbtn.disabled = false; @@ -2878,22 +2877,22 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - renderListing: function(container, path, list) { - var breadcrumb = E('p'), - rows = E('ul'); + renderListing(container, path, list) { + const breadcrumb = E('p'); + const rows = E('ul'); - list.sort(function(a, b) { + list.sort((a, b) => { return L.naturalCompare(a.type == 'directory', b.type == 'directory') || - L.naturalCompare(a.name, b.name); + L.naturalCompare(a.name, b.name); }); - for (var i = 0; i < list.length; i++) { + for (let i = 0; i < list.length; i++) { if (!this.options.show_hidden && list[i].name.charAt(0) == '.') continue; - var entrypath = this.canonicalizePath(path + '/' + list[i].name), - selected = (entrypath == this.node.lastElementChild.value), - mtime = new Date(list[i].mtime * 1000); + const entrypath = this.canonicalizePath(`${path}/${list[i].name}`); + const selected = (entrypath == this.node.lastElementChild.value); + const mtime = new Date(list[i].mtime * 1000); rows.appendChild(E('li', [ E('div', { 'class': 'name' }, [ @@ -2931,16 +2930,16 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { if (!rows.firstElementChild) rows.appendChild(E('em', _('No entries in this directory'))); - var dirs = this.splitPath(path), - cur = ''; + const dirs = this.splitPath(path); + let cur = ''; - for (var i = 0; i < dirs.length; i++) { - cur = cur ? cur + '/' + dirs[i] : dirs[i]; + for (let i = 0; i < dirs.length; i++) { + cur = cur ? `${cur}/${dirs[i]}` : dirs[i]; dom.append(breadcrumb, [ i ? ' » ' : '', E('a', { 'href': '#', - 'click': UI.prototype.createHandlerFn(this, 'handleSelect', cur || '/', null) + 'click': UI.prototype.createHandlerFn(this, 'handleSelect', cur ?? '/', null) }, dirs[i] != '' ? '%h'.format(dirs[i]) : E('em', '(root)')), ]); } @@ -2960,9 +2959,9 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - handleCancel: function(ev) { - var button = this.node.firstElementChild, - browser = button.nextElementSibling; + handleCancel(ev) { + const button = this.node.firstElementChild; + const browser = button.nextElementSibling; browser.classList.remove('open'); button.style.display = ''; @@ -2973,9 +2972,9 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - handleReset: function(ev) { - var button = this.node.firstElementChild, - hidden = this.node.lastElementChild; + handleReset(ev) { + const button = this.node.firstElementChild; + const hidden = this.node.lastElementChild; hidden.value = ''; dom.content(button, _('Select file…')); @@ -2984,17 +2983,17 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - handleSelect: function(path, fileStat, ev) { - var browser = dom.parent(ev.target, '.cbi-filebrowser'), - ul = browser.querySelector('ul'); + handleSelect(path, fileStat, ev) { + const browser = dom.parent(ev.target, '.cbi-filebrowser'); + const ul = browser.querySelector('ul'); if (fileStat == null) { dom.content(ul, E('em', { 'class': 'spinning' }, _('Loading directory contents…'))); L.resolveDefault(fs.list(path), []).then(L.bind(this.renderListing, this, browser, path)); } else { - var button = this.node.firstElementChild, - hidden = this.node.lastElementChild; + const button = this.node.firstElementChild; + const hidden = this.node.lastElementChild; path = this.canonicalizePath(path); @@ -3013,10 +3012,10 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @private */ - handleFileBrowser: function(ev) { - var button = ev.target, - browser = button.nextElementSibling, - path = this.stat ? this.stat.path.replace(/\/[^\/]+$/, '') : (this.options.initial_directory || this.options.root_directory); + handleFileBrowser(ev) { + const button = ev.target; + const browser = button.nextElementSibling; + let path = this.stat ? this.stat.path.replace(/\/[^\/]+$/, '') : (this.options.initial_directory ?? this.options.root_directory); if (path.indexOf(this.options.root_directory) != 0) path = this.options.root_directory; @@ -3024,7 +3023,7 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { ev.preventDefault(); return L.resolveDefault(fs.list(path), []).then(L.bind(function(button, browser, path, list) { - document.querySelectorAll('.cbi-filebrowser.open').forEach(function(browserEl) { + document.querySelectorAll('.cbi-filebrowser.open').forEach(browserEl => { dom.findClassInstance(browserEl).handleCancel(ev); }); @@ -3036,36 +3035,36 @@ var UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */ { }, /** @override */ - getValue: function() { + getValue() { return this.node.lastElementChild.value; }, /** @override */ - setValue: function(value) { + setValue(value) { this.node.lastElementChild.value = value; } }); function scrubMenu(node) { - var hasSatisfiedChild = false; + let hasSatisfiedChild = false; if (L.isObject(node.children)) { - for (var k in node.children) { - var child = scrubMenu(node.children[k]); + for (const k in node.children) { + const child = scrubMenu(node.children[k]); if (child.title && !child.firstchild_ineligible) - hasSatisfiedChild = hasSatisfiedChild || child.satisfied; + hasSatisfiedChild ||= child.satisfied; } } if (L.isObject(node.action) && - node.action.type == 'firstchild' && - hasSatisfiedChild == false) + node.action.type == 'firstchild' && + hasSatisfiedChild == false) node.satisfied = false; return node; -}; +} /** * Handle menu. @@ -3077,7 +3076,7 @@ function scrubMenu(node) { * * Handles menus. */ -var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { +const UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { /** * @typedef {Object} MenuNode * @memberof LuCI.ui.menu @@ -3096,7 +3095,7 @@ var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { * @returns {Promise<LuCI.ui.menu.MenuNode>} * Returns a promise resolving to the root element of the menu tree. */ - load: function() { + load() { if (this.menu == null) this.menu = session.getLocalData('menu'); @@ -3116,7 +3115,7 @@ var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { * Flush the internal menu cache to force loading a new structure on the * next page load. */ - flushCache: function() { + flushCache() { session.setLocalData('menu', null); }, @@ -3128,13 +3127,13 @@ var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { * @returns {LuCI.ui.menu.MenuNode[]} * Returns an array of child menu nodes. */ - getChildren: function(node) { - var children = []; + getChildren(node) { + const children = []; if (node == null) node = this.menu; - for (var k in node.children) { + for (const k in node.children) { if (!node.children.hasOwnProperty(k)) continue; @@ -3144,14 +3143,14 @@ var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { if (!node.children[k].hasOwnProperty('title')) continue; - var subnode = Object.assign(node.children[k], { name: k }); + let subnode = Object.assign(node.children[k], { name: k }); if (L.isObject(subnode.action) && subnode.action.path != null && - (subnode.action.type == 'alias' || subnode.action.type == 'rewrite')) { - var root = this.menu, - path = subnode.action.path.split('/'); + (subnode.action.type == 'alias' || subnode.action.type == 'rewrite')) { + let root = this.menu; + const path = subnode.action.path.split('/'); - for (var i = 0; root != null && i < path.length; i++) + for (let i = 0; root != null && i < path.length; i++) root = L.isObject(root.children) ? root.children[path[i]] : null; if (root) @@ -3164,9 +3163,9 @@ var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { children.push(subnode); } - return children.sort(function(a, b) { - var wA = a.order || 1000, - wB = b.order || 1000; + return children.sort((a, b) => { + const wA = a.order ?? 1000; + const wB = b.order ?? 1000; if (wA != wB) return wA - wB; @@ -3176,17 +3175,17 @@ var UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ { } }); -var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { - __init__: function(captions, options, placeholder) { +const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { + __init__(captions, options, placeholder) { if (!Array.isArray(captions)) { this.initFromMarkup(captions); return; } - var id = options.id || 'table%08x'.format(Math.random() * 0xffffffff); + const id = options.id ?? 'table%08x'.format(Math.random() * 0xffffffff); - var table = E('table', { 'id': id, 'class': 'table' }, [ + const table = E('table', { 'id': id, 'class': 'table' }, [ E('tr', { 'class': 'tr table-titles', 'click': UI.prototype.createHandlerFn(this, 'handleSort') }) ]); @@ -3194,13 +3193,13 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { this.node = table this.options = options; - var sorting = this.getActiveSortState(); + const sorting = this.getActiveSortState(); - for (var i = 0; i < captions.length; i++) { + for (let i = 0; i < captions.length; i++) { if (captions[i] == null) continue; - var th = E('th', { 'class': 'th' }, [ captions[i] ]); + const th = E('th', { 'class': 'th' }, [ captions[i] ]); if (typeof(options.captionClasses) == 'object') DOMTokenList.prototype.add.apply(th.classList, L.toArray(options.captionClasses[i])); @@ -3216,8 +3215,8 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { } if (placeholder) { - var trow = table.appendChild(E('tr', { 'class': 'tr placeholder' })), - td = trow.appendChild(E('td', { 'class': 'td' }, placeholder)); + const trow = table.appendChild(E('tr', { 'class': 'tr placeholder' })); + const td = trow.appendChild(E('td', { 'class': 'td' }, placeholder)); if (typeof(captionClasses) == 'object') DOMTokenList.prototype.add.apply(td.classList, L.toArray(captionClasses[0])); @@ -3226,19 +3225,19 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { DOMTokenList.prototype.add.apply(table.classList, L.toArray(options.classes)); }, - update: function(data, placeholder) { - var placeholder = placeholder || this.options.placeholder || _('No data', 'empty table placeholder'), - sorting = this.getActiveSortState(); + update(data, placeholderText) { + const placeholder = placeholderText ?? this.options.placeholder ?? _('No data', 'empty table placeholder'); + const sorting = this.getActiveSortState(); if (!Array.isArray(data)) return; if (sorting) { - var list = data.map(L.bind(function(row) { + const list = data.map(L.bind(function(row) { return [ this.deriveSortKey(row[sorting[0]], sorting[0]), row ]; }, this)); - list.sort(function(a, b) { + list.sort((a, b) => { return sorting[1] ? -L.naturalCompare(a[0], b[0]) : L.naturalCompare(a[0], b[0]); @@ -3246,7 +3245,7 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { data.length = 0; - list.forEach(function(item) { + list.forEach(item => { data.push(item[1]); }); } @@ -3254,22 +3253,22 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { this.data = data; this.placeholder = placeholder; - var n = 0, - rows = this.node.querySelectorAll('tr, .tr'), - trows = [], - headings = [].slice.call(this.node.firstElementChild.querySelectorAll('th, .th')), - captionClasses = this.options.captionClasses, - trTag = (rows[0] && rows[0].nodeName == 'DIV') ? 'div' : 'tr', - tdTag = (headings[0] && headings[0].nodeName == 'DIV') ? 'div' : 'td'; + let n = 0; + const rows = this.node.querySelectorAll('tr, .tr'); + const trows = []; + const headings = [].slice.call(this.node.firstElementChild.querySelectorAll('th, .th')); + const captionClasses = this.options.captionClasses; + const trTag = (rows[0] && rows[0].nodeName == 'DIV') ? 'div' : 'tr'; + const tdTag = (headings[0] && headings[0].nodeName == 'DIV') ? 'div' : 'td'; - data.forEach(function(row) { + data.forEach(row => { trows[n] = E(trTag, { 'class': 'tr' }); - for (var i = 0; i < headings.length; i++) { - var text = (headings[i].innerText || '').trim(); - var raw_val = Array.isArray(row[i]) ? row[i][0] : null; - var disp_val = Array.isArray(row[i]) ? row[i][1] : row[i]; - var td = trows[n].appendChild(E(tdTag, { + for (let i = 0; i < headings.length; i++) { + const text = (headings[i].innerText ?? '').trim(); + const raw_val = Array.isArray(row[i]) ? row[i][0] : null; + const disp_val = Array.isArray(row[i]) ? row[i][1] : row[i]; + const td = trows[n].appendChild(E(tdTag, { 'class': 'td', 'data-title': (text !== '') ? text : null, 'data-value': raw_val @@ -3285,7 +3284,7 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { trows[n].classList.add('cbi-rowstyle-%d'.format((n++ % 2) ? 2 : 1)); }); - for (var i = 0; i < n; i++) { + for (let i = 0; i < n; i++) { if (rows[i+1]) this.node.replaceChild(trows[i], rows[i+1]); else @@ -3296,8 +3295,8 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { this.node.removeChild(rows[n]); if (placeholder && this.node.firstElementChild === this.node.lastElementChild) { - var trow = this.node.appendChild(E(trTag, { 'class': 'tr placeholder' })), - td = trow.appendChild(E(tdTag, { 'class': 'td' }, placeholder)); + const trow = this.node.appendChild(E(trTag, { 'class': 'tr placeholder' })); + const td = trow.appendChild(E(tdTag, { 'class': 'td' }, placeholder)); if (typeof(captionClasses) == 'object') DOMTokenList.prototype.add.apply(td.classList, L.toArray(captionClasses[0])); @@ -3306,31 +3305,31 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { return this.node; }, - render: function() { + render() { return this.node; }, /** @private */ - initFromMarkup: function(node) { + initFromMarkup(node) { if (!dom.elem(node)) node = document.querySelector(node); if (!node) throw 'Invalid table selector'; - var options = {}, - headrow = node.querySelector('tr, .tr'); + const options = {}; + const headrow = node.querySelector('tr, .tr'); if (!headrow) return; - options.classes = [].slice.call(node.classList).filter(function(c) { return c != 'table' }); + options.classes = [].slice.call(node.classList).filter(c => c != 'table'); options.sortable = []; options.captionClasses = []; - headrow.querySelectorAll('th, .th').forEach(function(th, i) { + headrow.querySelectorAll('th, .th').forEach((th, i) => { options.sortable[i] = !th.classList.contains('cbi-section-actions'); - options.captionClasses[i] = [].slice.call(th.classList).filter(function(c) { return c != 'th' }); + options.captionClasses[i] = [].slice.call(th.classList).filter(c => c != 'th'); }); headrow.addEventListener('click', UI.prototype.createHandlerFn(this, 'handleSort')); @@ -3341,9 +3340,10 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { }, /** @private */ - deriveSortKey: function(value, index) { - var opts = this.options || {}, - hint, m; + deriveSortKey(value, index) { + const opts = this.options ?? {}; + let hint; + let m; if (opts.sortable == true || opts.sortable == null) hint = 'auto'; @@ -3354,16 +3354,17 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { if (value.hasAttribute('data-value')) value = value.getAttribute('data-value'); else - value = (value.innerText || '').trim(); + value = (value.innerText ?? '').trim(); } - switch (hint || 'auto') { + switch (hint ?? 'auto') { case true: case 'auto': m = /^([0-9a-fA-F:.]+)(?:\/([0-9a-fA-F:.]+))?$/.exec(value); if (m) { - var addr, mask; + let addr; + let mask; addr = validation.parseIPv6(m[1]); mask = m[2] ? validation.parseIPv6(m[2]) : null; @@ -3418,13 +3419,13 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { }, /** @private */ - getActiveSortState: function() { + getActiveSortState() { if (this.sortState) return this.sortState; - var page = document.body.getAttribute('data-page'), - key = page + '.' + this.id, - state = session.getLocalData('tablesort'); + const page = document.body.getAttribute('data-page'); + const key = `${page}.${this.id}`; + const state = session.getLocalData('tablesort'); if (L.isObject(state) && Array.isArray(state[key])) return state[key]; @@ -3433,15 +3434,15 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { }, /** @private */ - setActiveSortState: function(index, descending) { + setActiveSortState(index, descending) { this.sortState = [ index, descending ]; if (!this.options.id) return; - var page = document.body.getAttribute('data-page'), - key = page + '.' + this.id, - state = session.getLocalData('tablesort'); + const page = document.body.getAttribute('data-page'); + const key = `${page}.${this.id}`; + let state = session.getLocalData('tablesort'); if (!L.isObject(state)) state = {}; @@ -3452,15 +3453,15 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { }, /** @private */ - handleSort: function(ev) { + handleSort(ev) { if (!ev.target.matches('th[data-sortable-row]')) return; - var th = ev.target, - direction = (th.getAttribute('data-sort-direction') == 'asc'), - index = 0; + const th = ev.target; + const direction = (th.getAttribute('data-sort-direction') == 'asc'); + let index = 0; - this.node.firstElementChild.querySelectorAll('th').forEach(function(other_th, i) { + this.node.firstElementChild.querySelectorAll('th').forEach((other_th, i) => { if (other_th !== th) other_th.removeAttribute('data-sort-direction'); else @@ -3484,8 +3485,8 @@ var UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ { * To import the class in views, use `'require ui'`, to import it in * external JavaScript, use `L.require("ui").then(...)`. */ -var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { - __init__: function() { +const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { + __init__() { modalDiv = document.body.appendChild( dom.create('div', { id: 'modal_overlay', @@ -3548,13 +3549,11 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @returns {Node} * Returns a DOM Node representing the modal dialog element. */ - showModal: function(title, children /* , ... */) { - var dlg = modalDiv.firstElementChild; + showModal(title, children, ...classes) { + const dlg = modalDiv.firstElementChild; dlg.setAttribute('class', 'modal'); - - for (var i = 2; i < arguments.length; i++) - dlg.classList.add(arguments[i]); + dlg.classList.add(...classes); dom.content(dlg, dom.create('h4', {}, title)); dom.append(dlg, children); @@ -3576,15 +3575,15 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * will not invoke other class functions so it suitable to be used as event * handler as-is without the need to bind it first. */ - hideModal: function() { + hideModal() { document.body.classList.remove('modal-overlay-active'); modalDiv.blur(); }, /** @private */ - cancelModal: function(ev) { + cancelModal(ev) { if (ev.key == 'Escape') { - var btn = modalDiv.querySelector('.right > button, .right > .btn'); + const btn = modalDiv.querySelector('.right > button, .right > .btn'); if (btn) btn.click(); @@ -3592,8 +3591,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - showTooltip: function(ev) { - var target = findParent(ev.target, '[data-tooltip]'); + showTooltip(ev) { + const target = findParent(ev.target, '[data-tooltip]'); if (!target) return; @@ -3603,10 +3602,10 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { tooltipTimeout = null; } - var rect = target.getBoundingClientRect(), - x = rect.left + window.pageXOffset, - y = rect.top + rect.height + window.pageYOffset, - above = false; + const rect = target.getBoundingClientRect(); + const x = rect.left + window.pageXOffset; + let y = rect.top + rect.height + window.pageYOffset; + let above = false; tooltipDiv.className = 'cbi-tooltip'; tooltipDiv.innerHTML = '▲ '; @@ -3618,18 +3617,18 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { if ((y + tooltipDiv.offsetHeight) > (window.innerHeight + window.pageYOffset)) above = true; - var dropdown = target.querySelector('ul.dropdown[style]:first-child'); + const dropdown = target.querySelector('ul.dropdown[style]:first-child'); if (dropdown && dropdown.style.top) above = true; if (above) { y -= (tooltipDiv.offsetHeight + target.offsetHeight); - tooltipDiv.firstChild.data = '▼ ' + tooltipDiv.firstChild.data.substr(2); + tooltipDiv.firstChild.data = `▼ ${tooltipDiv.firstChild.data.substr(2)}`; } - tooltipDiv.style.top = y + 'px'; - tooltipDiv.style.left = x + 'px'; + tooltipDiv.style.top = `${y}px`; + tooltipDiv.style.left = `${x}px`; tooltipDiv.style.opacity = 1; tooltipDiv.dispatchEvent(new CustomEvent('tooltip-open', { @@ -3639,7 +3638,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - hideTooltip: function(ev) { + hideTooltip(ev) { if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv || tooltipDiv.contains(ev.target) || tooltipDiv.contains(ev.relatedTarget)) return; @@ -3650,7 +3649,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { } tooltipDiv.style.opacity = 0; - tooltipTimeout = window.setTimeout(function() { tooltipDiv.removeAttribute('style'); }, 250); + tooltipTimeout = window.setTimeout(() => tooltipDiv.removeAttribute('style'), 250); tooltipDiv.dispatchEvent(new CustomEvent('tooltip-close', { bubbles: true })); }, @@ -3685,13 +3684,13 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @returns {Node} * Returns a DOM Node representing the notification banner element. */ - addNotification: function(title, children /*, ... */) { - var mc = document.querySelector('#maincontent') || document.body; - var msg = E('div', { + addNotification(title, children, ...classes) { + const mc = document.querySelector('#maincontent') ?? document.body; + const msg = E('div', { 'class': 'alert-message fade-in', 'style': 'display:flex', 'transitionend': function(ev) { - var node = ev.currentTarget; + const node = ev.currentTarget; if (node.parentNode && node.classList.contains('fade-out')) node.parentNode.removeChild(node); } @@ -3714,8 +3713,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { dom.append(msg.firstElementChild, children); - for (var i = 2; i < arguments.length; i++) - msg.classList.add(arguments[i]); + msg.classList.add(...classes); mc.insertBefore(msg, mc.firstElementChild); @@ -3757,7 +3755,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * Returns `true` when the indicator has been updated or `false` when no * changes were made. */ - showIndicator: function(id, label, handler, style) { + showIndicator(id, label, handler, style) { if (indicatorDiv == null) { indicatorDiv = document.body.querySelector('#indicators'); @@ -3765,11 +3763,11 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { return false; } - var handlerFn = (typeof(handler) == 'function') ? handler : null, - indicatorElem = indicatorDiv.querySelector('span[data-indicator="%s"]'.format(id)); + const handlerFn = (typeof(handler) == 'function') ? handler : null; + let indicatorElem = indicatorDiv.querySelector('span[data-indicator="%s"]'.format(id)); if (indicatorElem == null) { - var beforeElem = null; + let beforeElem = null; for (beforeElem = indicatorDiv.firstElementChild; beforeElem != null; @@ -3805,8 +3803,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * Returns `true` when the indicator has been removed or `false` when the * requested indicator was not found. */ - hideIndicator: function(id) { - var indicatorElem = indicatorDiv ? indicatorDiv.querySelector('span[data-indicator="%s"]'.format(id)) : null; + hideIndicator(id) { + const indicatorElem = indicatorDiv ? indicatorDiv.querySelector('span[data-indicator="%s"]'.format(id)) : null; if (indicatorElem == null) return false; @@ -3848,19 +3846,19 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @returns {Node} * Returns the parent DOM node the formatted markup has been added to. */ - itemlist: function(node, items, separators) { - var children = []; + itemlist(node, items, separators) { + const children = []; if (!Array.isArray(separators)) - separators = [ separators || E('br') ]; + separators = [ separators ?? E('br') ]; - for (var i = 0; i < items.length; i += 2) { + for (let i = 0; i < items.length; i += 2) { if (items[i+1] !== null && items[i+1] !== undefined) { - var sep = separators[(i/2) % separators.length], - cld = []; + const sep = separators[(i/2) % separators.length]; + const cld = []; children.push(E('span', { class: 'nowrap' }, [ - items[i] ? E('strong', items[i] + ': ') : '', + items[i] ? E('strong', `${items[i]}: `) : '', items[i+1] ])); @@ -3891,11 +3889,13 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { */ tabs: baseclass.singleton(/* @lends LuCI.ui.tabs.prototype */ { /** @private */ - init: function() { - var groups = [], prevGroup = null, currGroup = null; + init() { + const groups = []; + let prevGroup = null; + let currGroup = null; - document.querySelectorAll('[data-tab]').forEach(function(tab) { - var parent = tab.parentNode; + document.querySelectorAll('[data-tab]').forEach(tab => { + const parent = tab.parentNode; if (dom.matches(tab, 'li') && dom.matches(parent, 'ul.cbi-tabmenu')) return; @@ -3915,7 +3915,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { groups[currGroup].push(tab); }); - for (var i = 0; i < groups.length; i++) + for (let i = 0; i < groups.length; i++) this.initTabGroup(groups[i]); document.addEventListener('dependency-update', this.updateTabs.bind(this)); @@ -3944,22 +3944,22 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * of a `querySelectorAll()` call or the `.childNodes` property of a * DOM node. */ - initTabGroup: function(panes) { + initTabGroup(panes) { if (typeof(panes) != 'object' || !('length' in panes) || panes.length === 0) return; - var menu = E('ul', { 'class': 'cbi-tabmenu' }), - group = panes[0].parentNode, - groupId = +group.getAttribute('data-tab-group'), - selected = null; + const menu = E('ul', { 'class': 'cbi-tabmenu' }); + const group = panes[0].parentNode; + const groupId = +group.getAttribute('data-tab-group'); + let selected = null; if (group.getAttribute('data-initialized') === 'true') return; - for (var i = 0, pane; pane = panes[i]; i++) { - var name = pane.getAttribute('data-tab'), - title = pane.getAttribute('data-tab-title'), - active = pane.getAttribute('data-tab-active') === 'true'; + for (let i = 0, pane; pane = panes[i]; i++) { + const name = pane.getAttribute('data-tab'); + const title = pane.getAttribute('data-tab-title'); + const active = pane.getAttribute('data-tab-active') === 'true'; menu.appendChild(E('li', { 'style': this.isEmptyPane(pane) ? 'display:none' : null, @@ -3981,7 +3981,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { selected = this.getActiveTabId(panes[0]); if (selected < 0 || selected >= panes.length || this.isEmptyPane(panes[selected])) { - for (var i = 0; i < panes.length; i++) { + for (let i = 0; i < panes.length; i++) { if (!this.isEmptyPane(panes[i])) { selected = i; break; @@ -3996,7 +3996,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { this.setActiveTabId(panes[selected], selected); } - requestAnimationFrame(L.bind(function(pane) { + requestAnimationFrame(L.bind(pane => { pane.dispatchEvent(new CustomEvent('cbi-tab-active', { detail: { tab: pane.getAttribute('data-tab') } })); @@ -4016,13 +4016,14 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @returns {boolean} * Returns `true` if the pane is empty, else `false`. */ - isEmptyPane: function(pane) { - return dom.isEmpty(pane, function(n) { return n.classList.contains('cbi-tab-descr') }); + isEmptyPane(pane) { + return dom.isEmpty(pane, n => n.classList.contains('cbi-tab-descr')); }, /** @private */ - getPathForPane: function(pane) { - var path = [], node = null; + getPathForPane(pane) { + const path = []; + let node = null; for (node = pane ? pane.parentNode : null; node != null && node.hasAttribute != null; @@ -4038,9 +4039,9 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - getActiveTabState: function() { - var page = document.body.getAttribute('data-page'), - state = session.getLocalData('tab'); + getActiveTabState() { + const page = document.body.getAttribute('data-page'); + const state = session.getLocalData('tab'); if (L.isObject(state) && state.page === page && L.isObject(state.paths)) return state; @@ -4051,15 +4052,15 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - getActiveTabId: function(pane) { - var path = this.getPathForPane(pane); - return +this.getActiveTabState().paths[path] || 0; + getActiveTabId(pane) { + const path = this.getPathForPane(pane); + return +this.getActiveTabState().paths[path] ?? 0; }, /** @private */ - setActiveTabId: function(pane, tabIndex) { - var path = this.getPathForPane(pane), - state = this.getActiveTabState(); + setActiveTabId(pane, tabIndex) { + const path = this.getPathForPane(pane); + const state = this.getActiveTabState(); state.paths[path] = tabIndex; @@ -4067,11 +4068,11 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - updateTabs: function(ev, root) { - (root || document).querySelectorAll('[data-tab-title]').forEach(L.bind(function(pane) { - var menu = pane.parentNode.previousElementSibling, - tab = menu ? menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))) : null, - n_errors = pane.querySelectorAll('.cbi-input-invalid').length; + updateTabs(ev, root) { + (root ?? document).querySelectorAll('[data-tab-title]').forEach(L.bind(function(pane) { + const menu = pane.parentNode.previousElementSibling; + const tab = menu ? menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))) : null; + const n_errors = pane.querySelectorAll('.cbi-input-invalid').length; if (!menu || !tab) return; @@ -4082,7 +4083,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { } else if (tab.style.display === 'none') { tab.style.display = ''; - requestAnimationFrame(function() { tab.classList.add('flash') }); + requestAnimationFrame(() => tab.classList.add('flash')); } if (n_errors) { @@ -4098,27 +4099,27 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - switchTab: function(ev) { - var tab = ev.target.parentNode, - name = tab.getAttribute('data-tab'), - menu = tab.parentNode, - group = menu.nextElementSibling, - groupId = +group.getAttribute('data-tab-group'), - index = 0; + switchTab(ev) { + const tab = ev.target.parentNode; + const name = tab.getAttribute('data-tab'); + const menu = tab.parentNode; + const group = menu.nextElementSibling; + const groupId = +group.getAttribute('data-tab-group'); + let index = 0; ev.preventDefault(); if (!tab.classList.contains('cbi-tab-disabled')) return; - menu.querySelectorAll('[data-tab]').forEach(function(tab) { + menu.querySelectorAll('[data-tab]').forEach(tab => { tab.classList.remove('cbi-tab'); tab.classList.remove('cbi-tab-disabled'); tab.classList.add( tab.getAttribute('data-tab') === name ? 'cbi-tab' : 'cbi-tab-disabled'); }); - group.childNodes.forEach(function(pane) { + group.childNodes.forEach(pane => { if (dom.matches(pane, '[data-tab]')) { if (pane.getAttribute('data-tab') === name) { pane.setAttribute('data-tab-active', 'true'); @@ -4163,8 +4164,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * or rejecting with an error in case the upload failed or has been * cancelled by the user. */ - uploadFile: function(path, progressStatusNode) { - return new Promise(function(resolveFn, rejectFn) { + uploadFile(path, progressStatusNode) { + return new Promise((resolveFn, rejectFn) => { UI.prototype.showModal(_('Uploading file…'), [ E('p', _('Please select the file to upload.')), E('div', { 'style': 'display:flex' }, [ @@ -4172,11 +4173,11 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { E('input', { type: 'file', style: 'display:none', - change: function(ev) { - var modal = dom.parent(ev.target, '.modal'), - body = modal.querySelector('p'), - upload = modal.querySelector('.cbi-button-action.important'), - file = ev.currentTarget.files[0]; + change(ev) { + const modal = dom.parent(ev.target, '.modal'); + const body = modal.querySelector('p'); + const upload = modal.querySelector('.cbi-button-action.important'); + const file = ev.currentTarget.files[0]; if (file == null) return; @@ -4212,27 +4213,27 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { 'class': 'btn cbi-button-action important', 'disabled': true, 'click': function(ev) { - var input = dom.parent(ev.target, '.modal').querySelector('input[type="file"]'); + const input = dom.parent(ev.target, '.modal').querySelector('input[type="file"]'); if (!input.files[0]) return; - var progress = E('div', { 'class': 'cbi-progressbar', 'title': '0%' }, E('div', { 'style': 'width:0' })); + const progress = E('div', { 'class': 'cbi-progressbar', 'title': '0%' }, E('div', { 'style': 'width:0' })); UI.prototype.showModal(_('Uploading file…'), [ progress ]); - var data = new FormData(); + const data = new FormData(); data.append('sessionid', rpc.getSessionID()); data.append('filename', path); data.append('filedata', input.files[0]); - var filename = input.files[0].name; + const filename = input.files[0].name; - request.post(L.env.cgi_base + '/cgi-upload', data, { + request.post(`${L.env.cgi_base}/cgi-upload`, data, { timeout: 0, - progress: function(pev) { - var percent = (pev.loaded / pev.total) * 100; + progress(pev) { + const percent = (pev.loaded / pev.total) * 100; if (progressStatusNode) progressStatusNode.data = '%.2f%%'.format(percent); @@ -4240,8 +4241,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { progress.setAttribute('title', '%.2f%%'.format(percent)); progress.firstElementChild.style.width = '%.2f%%'.format(percent); } - }).then(function(res) { - var reply = res.json(); + }).then(res => { + const reply = res.json(); UI.prototype.hideModal(); @@ -4253,7 +4254,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { reply.name = filename; resolveFn(reply); } - }, function(err) { + }, err => { UI.prototype.hideModal(); rejectFn(err); }); @@ -4285,11 +4286,11 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * reachable or rejecting with an `error` event in case it is not reachable * or rejecting with `null` when the connectivity check timed out. */ - pingDevice: function(proto, ipaddr) { - var target = '%s://%s%s?%s'.format(proto || 'http', ipaddr || window.location.host, L.resource('icons/loading.gif'), Math.random()); + pingDevice(proto, ipaddr) { + const target = '%s://%s%s?%s'.format(proto ?? 'http', ipaddr ?? window.location.host, L.resource('icons/loading.gif'), Math.random()); - return new Promise(function(resolveFn, rejectFn) { - var img = new Image(); + return new Promise((resolveFn, rejectFn) => { + const img = new Image(); img.onload = resolveFn; img.onerror = rejectFn; @@ -4311,19 +4312,20 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * If omitted, the current value of `window.location.host` is used by * default. */ - awaitReconnect: function(/* ... */) { - var ipaddrs = arguments.length ? arguments : [ window.location.host ]; + awaitReconnect(...hosts) { + const ipaddrs = hosts.length ? hosts : [ window.location.host ]; window.setTimeout(L.bind(function() { poll.add(L.bind(function() { - var tasks = [], reachable = false; + const tasks = []; + let reachable = false; - for (var i = 0; i < 2; i++) - for (var j = 0; j < ipaddrs.length; j++) + for (let i = 0; i < 2; i++) + for (let j = 0; j < ipaddrs.length; j++) tasks.push(this.pingDevice(i ? 'https' : 'http', ipaddrs[j]) - .then(function(ev) { reachable = ev.target.src.replace(/^(https?:\/\/[^\/]+).*$/, '$1/') }, function() {})); + .then(ev => { reachable = ev.target.src.replace(/^(https?:\/\/[^\/]+).*$/, '$1/') }, () => {})); - return Promise.all(tasks).then(function() { + return Promise.all(tasks).then(() => { if (reachable) { poll.stop(); window.location = reachable; @@ -4348,7 +4350,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * `changes` property of the class instance value. */ changes: baseclass.singleton(/* @lends LuCI.ui.changes.prototype */ { - init: function() { + init() { if (!L.env.sessionid) return; @@ -4368,7 +4370,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @param {number} n * The number of changes to indicate. */ - setIndicator: function(n) { + setIndicator(n) { if (n > 0) { UI.prototype.showIndicator('uci-changes', '%s: %d'.format(_('Unsaved Changes'), n), @@ -4390,10 +4392,10 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @param {Object<string, Array<LuCI.uci.ChangeRecord>>} changes * The UCI changeset to count. */ - renderChangeIndicator: function(changes) { - var n_changes = 0; + renderChangeIndicator(changes) { + let n_changes = 0; - for (var config in changes) + for (const config in changes) if (changes.hasOwnProperty(config)) n_changes += changes[config].length; @@ -4424,60 +4426,61 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @instance * @memberof LuCI.ui.changes */ - displayChanges: function() { - var list = E('div', { 'class': 'uci-change-list' }), - dlg = UI.prototype.showModal(_('Configuration') + ' / ' + _('Changes'), [ - E('div', { 'class': 'cbi-section' }, [ - E('strong', _('Legend:')), - E('div', { 'class': 'uci-change-legend' }, [ - E('div', { 'class': 'uci-change-legend-label' }, [ - E('ins', ' '), ' ', _('Section added') ]), - E('div', { 'class': 'uci-change-legend-label' }, [ - E('del', ' '), ' ', _('Section removed') ]), - E('div', { 'class': 'uci-change-legend-label' }, [ - E('var', {}, E('ins', ' ')), ' ', _('Option changed') ]), - E('div', { 'class': 'uci-change-legend-label' }, [ - E('var', {}, E('del', ' ')), ' ', _('Option removed') ])]), - E('br'), list, - E('div', { 'class': 'right' }, [ - E('button', { - 'class': 'btn', - 'click': UI.prototype.hideModal - }, [ _('Close') ]), ' ', - new UIComboButton('0', { - 0: [ _('Save & Apply') ], - 1: [ _('Apply unchecked') ] - }, { - classes: { - 0: 'btn cbi-button cbi-button-positive important', - 1: 'btn cbi-button cbi-button-negative important' - }, - click: L.bind(function(ev, mode) { this.apply(mode == '0') }, this) - }).render(), ' ', - E('button', { - 'class': 'cbi-button cbi-button-reset', - 'click': L.bind(this.revert, this) - }, [ _('Revert') ])])]) - ]); + displayChanges() { + const list = E('div', { 'class': 'uci-change-list' }); + + const dlg = UI.prototype.showModal(`${_('Configuration')} / ${_('Changes')}`, [ + E('div', { 'class': 'cbi-section' }, [ + E('strong', _('Legend:')), + E('div', { 'class': 'uci-change-legend' }, [ + E('div', { 'class': 'uci-change-legend-label' }, [ + E('ins', ' '), ' ', _('Section added') ]), + E('div', { 'class': 'uci-change-legend-label' }, [ + E('del', ' '), ' ', _('Section removed') ]), + E('div', { 'class': 'uci-change-legend-label' }, [ + E('var', {}, E('ins', ' ')), ' ', _('Option changed') ]), + E('div', { 'class': 'uci-change-legend-label' }, [ + E('var', {}, E('del', ' ')), ' ', _('Option removed') ])]), + E('br'), list, + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn', + 'click': UI.prototype.hideModal + }, [ _('Close') ]), ' ', + new UIComboButton('0', { + 0: [ _('Save & Apply') ], + 1: [ _('Apply unchecked') ] + }, { + classes: { + 0: 'btn cbi-button cbi-button-positive important', + 1: 'btn cbi-button cbi-button-negative important' + }, + click: L.bind(function(ev, mode) { this.apply(mode == '0') }, this) + }).render(), ' ', + E('button', { + 'class': 'cbi-button cbi-button-reset', + 'click': L.bind(this.revert, this) + }, [ _('Revert') ])])]) + ]); - for (var config in this.changes) { + for (const config in this.changes) { if (!this.changes.hasOwnProperty(config)) continue; list.appendChild(E('h5', '# /etc/config/%s'.format(config))); - for (var i = 0, added = null; i < this.changes[config].length; i++) { - var chg = this.changes[config][i], - tpl = this.changeTemplates['%s-%d'.format(chg[0], chg.length)]; + for (let i = 0, added = null; i < this.changes[config].length; i++) { + const chg = this.changes[config][i]; + const tpl = this.changeTemplates['%s-%d'.format(chg[0], chg.length)]; - list.appendChild(E(tpl.replace(/%([01234])/g, function(m0, m1) { + list.appendChild(E(tpl.replace(/%([01234])/g, (m0, m1) => { switch (+m1) { case 0: return config; case 2: if (added != null && chg[1] == added[0]) - return '@' + added[1] + '[-1]'; + return `@${added[1]}[-1]`; else return chg[1]; @@ -4499,9 +4502,9 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - displayStatus: function(type, content) { + displayStatus(type, content) { if (type) { - var message = UI.prototype.showModal('', ''); + const message = UI.prototype.showModal('', ''); message.classList.add('alert-message'); DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/)); @@ -4523,14 +4526,14 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - checkConnectivityAffected: function() { + checkConnectivityAffected() { return L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr', null, 'json')).then(L.bind(function(info) { if (L.isObject(info) && Array.isArray(info.inbound_interfaces)) { - for (var i = 0; i < info.inbound_interfaces.length; i++) { - var iif = info.inbound_interfaces[i]; + for (let i = 0; i < info.inbound_interfaces.length; i++) { + const iif = info.inbound_interfaces[i]; - for (var j = 0; this.changes && this.changes.network && j < this.changes.network.length; j++) { - var chg = this.changes.network[j]; + for (let j = 0; this.changes && this.changes.network && j < this.changes.network.length; j++) { + const chg = this.changes.network[j]; if (chg[0] == 'set' && chg[1] == iif && (chg[2] == 'proto' || chg[2] == 'ipaddr' || chg[2] == 'netmask')) return iif; @@ -4543,13 +4546,13 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - rollback: function(checked) { + rollback(checked) { if (checked) { this.displayStatus('warning spinning', E('p', _('Failed to confirm apply within %ds, waiting for rollback…') .format(L.env.apply_rollback))); - var call = function(r, data, duration) { + const call = (r, data, duration) => { if (r.status === 204) { UI.prototype.changes.displayStatus('warning', [ E('h4', _('Configuration changes have been rolled back!')), @@ -4573,8 +4576,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { return; } - var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0); - window.setTimeout(function() { + const delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0); + window.setTimeout(() => { request.request(L.url('admin/uci/confirm'), { method: 'post', timeout: L.env.apply_timeout * 1000, @@ -4594,16 +4597,16 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, /** @private */ - confirm: function(checked, deadline, override_token) { - var tt; - var ts = Date.now(); + confirm(checked, deadline, override_token) { + let tt; + let ts = Date.now(); this.displayStatus('notice'); if (override_token) this.confirm_auth = { token: override_token }; - var call = function(r, data, duration) { + const call = (r, data, duration) => { if (Date.now() >= deadline) { window.clearTimeout(tt); UI.prototype.changes.rollback(checked); @@ -4617,7 +4620,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { E('p', _('Configuration changes applied.'))); window.clearTimeout(tt); - window.setTimeout(function() { + window.setTimeout(() => { //UI.prototype.changes.displayStatus(false); window.location = window.location.href.split('#')[0]; }, L.env.apply_display * 1000); @@ -4625,8 +4628,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { return; } - var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0); - window.setTimeout(function() { + const delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0); + window.setTimeout(() => { request.request(L.url('admin/uci/confirm'), { method: 'post', timeout: L.env.apply_timeout * 1000, @@ -4635,8 +4638,8 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { }, delay); }; - var tick = function() { - var now = Date.now(); + const tick = () => { + const now = Date.now(); UI.prototype.changes.displayStatus('notice spinning', E('p', _('Applying configuration changes… %ds') @@ -4675,15 +4678,15 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * will begin to roll back the changes in order to restore the previous * settings. */ - apply: function(checked) { + apply(checked) { this.displayStatus('notice spinning', E('p', _('Starting configuration apply…'))); - (new Promise(function(resolveFn, rejectFn) { + (new Promise((resolveFn, rejectFn) => { if (!checked) return resolveFn(false); - UI.prototype.changes.checkConnectivityAffected().then(function(affected) { + UI.prototype.changes.checkConnectivityAffected().then(affected => { if (!affected) return resolveFn(true); @@ -4707,13 +4710,13 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { ]) ]); }); - })).then(function(checked) { + })).then(checked => { request.request(L.url('admin/uci', checked ? 'apply_rollback' : 'apply_unchecked'), { method: 'post', query: { sid: L.env.sessionid, token: L.env.token } - }).then(function(r) { + }).then(r => { if (r.status === (checked ? 200 : 204)) { - var tok = null; try { tok = r.json(); } catch(e) {} + let tok = null; try { tok = r.json(); } catch(e) {} if (checked && tok !== null && typeof(tok) === 'object' && typeof(tok.token) === 'string') UI.prototype.changes.confirm_auth = tok; @@ -4723,16 +4726,16 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { UI.prototype.changes.displayStatus('notice', E('p', _('There are no changes to apply'))); - window.setTimeout(function() { + window.setTimeout(() => { UI.prototype.changes.displayStatus(false); }, L.env.apply_display * 1000); } else { UI.prototype.changes.displayStatus('warning', E('p', _('Apply request failed with status <code>%h</code>') - .format(r.responseText || r.statusText || r.status))); + .format(r.responseText ?? r.statusText ?? r.status))); - window.setTimeout(function() { + window.setTimeout(() => { UI.prototype.changes.displayStatus(false); }, L.env.apply_display * 1000); } @@ -4752,14 +4755,14 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @instance * @memberof LuCI.ui.changes */ - revert: function() { + revert() { this.displayStatus('notice spinning', E('p', _('Reverting configuration…'))); request.request(L.url('admin/uci/revert'), { method: 'post', query: { sid: L.env.sessionid, token: L.env.token } - }).then(function(r) { + }).then(r => { if (r.status === 200) { document.dispatchEvent(new CustomEvent('uci-reverted')); @@ -4767,7 +4770,7 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { UI.prototype.changes.displayStatus('notice', E('p', _('Changes have been reverted.'))); - window.setTimeout(function() { + window.setTimeout(() => { //UI.prototype.changes.displayStatus(false); window.location = window.location.href.split('#')[0]; }, L.env.apply_display * 1000); @@ -4775,9 +4778,9 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { else { UI.prototype.changes.displayStatus('warning', E('p', _('Revert request failed with status <code>%h</code>') - .format(r.statusText || r.status))); + .format(r.statusText ?? r.status))); - window.setTimeout(function() { + window.setTimeout(() => { UI.prototype.changes.displayStatus(false); }, L.env.apply_display * 1000); } @@ -4820,19 +4823,18 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * * @see LuCI.validation */ - addValidator: function(field, type, optional, vfunc /*, ... */) { + addValidator(field, type, optional, vfunc, ...events) { if (type == null) return; - var events = this.varargs(arguments, 3); if (events.length == 0) events.push('blur', 'keyup'); try { - var cbiValidator = validation.create(field, type, optional, vfunc), - validatorFn = cbiValidator.validate.bind(cbiValidator); + const cbiValidator = validation.create(field, type, optional, vfunc); + const validatorFn = cbiValidator.validate.bind(cbiValidator); - for (var i = 0; i < events.length; i++) + for (let i = 0; i < events.length; i++) field.addEventListener(events[i], validatorFn); validatorFn(); @@ -4871,17 +4873,15 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * a string which could not be found in `ctx` or if `ctx[fn]` is not a * valid function value. */ - createHandlerFn: function(ctx, fn /*, ... */) { + createHandlerFn(ctx, fn, ...args) { if (typeof(fn) == 'string') fn = ctx[fn]; if (typeof(fn) != 'function') return null; - var arg_offset = arguments.length - 2; - - return Function.prototype.bind.apply(function() { - var t = arguments[arg_offset].currentTarget; + return L.bind(function() { + const t = arguments[args.length].currentTarget; t.classList.add('spinning'); t.disabled = true; @@ -4889,11 +4889,11 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { if (t.blur) t.blur(); - Promise.resolve(fn.apply(ctx, arguments)).finally(function() { + Promise.resolve(fn.apply(ctx, arguments)).finally(() => { t.classList.remove('spinning'); t.disabled = false; }); - }, this.varargs(arguments, 2, ctx)); + }, ctx, ...args); }, /** @@ -4914,15 +4914,15 @@ var UI = baseclass.extend(/** @lends LuCI.ui.prototype */ { * @returns {Promise<LuCI.view>} * Returns a promise resolving to the loaded view instance. */ - instantiateView: function(path) { - var className = 'view.%s'.format(path.replace(/\//g, '.')); + instantiateView(path) { + const className = 'view.%s'.format(path.replace(/\//g, '.')); - return L.require(className).then(function(view) { + return L.require(className).then(view => { if (!(view instanceof View)) throw new TypeError('Loaded class %s is not a descendant of View'.format(className)); return view; - }).catch(function(err) { + }).catch(err => { dom.content(document.querySelector('#view'), null); L.error(err); }); |