@name alone does not provide a linkable symbol.
@member and @memberof do.
Signed-off-by: Paul Donald <redacted>
http://www.apache.org/licenses/LICENSE-2.0
*/
+/**
+ * CBI (Configuration Bindings Interface) helper utilities and DOM helpers.
+ *
+ * Provides initialization for CBI UI elements, dependency handling,
+ * validation wiring and miscellaneous helpers used by LuCI forms. Functions
+ * defined here are registered as global `window.*` symbols.
+ * @module LuCI.cbi
+ */
var cbi_d = [];
var cbi_strings = { path: {}, label: {} };
+/**
+ * Read signed 8-bit integer from a byte array at the given offset.
+ * @param {Array<number>} bytes - Byte array.
+ * @param {number} off - Offset into the array.
+ * @returns {number} Signed 8-bit value (returned as unsigned number).
+ */
function s8(bytes, off) {
var n = bytes[off];
return (n > 0x7F) ? (n - 256) >>> 0 : n;
}
+/**
+ * Read unsigned 16-bit little-endian integer from a byte array at offset.
+ * @param {Array<number>} bytes - Byte array.
+ * @param {number} off - Offset into the array.
+ * @returns {number} Unsigned 16-bit integer.
+ */
function u16(bytes, off) {
return ((bytes[off + 1] << 8) + bytes[off]) >>> 0;
}
+/**
+ * Compute a stable 32-bit-ish string hash used for translation keys.
+ * Encodes UTF-8 surrogate pairs and mixes bytes into a hex hash string.
+ * @param {string|null} s - Input string.
+ * @returns {string|null} Hex hash string or null for empty input.
+ */
function sfh(s) {
if (s === null || s.length === 0)
return null;
var plural_function = null;
+/**
+ * Trim whitespace and normalise internal whitespace sequences to single spaces.
+ * @param {*} s - Value to convert to string and trim.
+ * @returns {string} Trimmed and normalised string.
+ */
function trimws(s) {
return String(s).trim().replace(/[ \t\n]+/g, ' ');
}
+/**
+ * Lookup a translated string for the given message and optional context.
+ * Falls back to the source string when no translation found.
+ * @param {string} s - Source string.
+ * @param {string} [c] - Optional translation context.
+ * @returns {string} Translated string or original.
+ */
function _(s, c) {
var k = (c != null ? trimws(c) + '\u0001' : '') + trimws(s);
return (window.TR && TR[sfh(k)]) || s;
}
+/**
+ * Plural-aware translation lookup.
+ * @param {number} n - Quantity to evaluate plural form.
+ * @param {string} s - Singular string.
+ * @param {string} p - Plural string.
+ * @param {string} [c] - Optional context.
+ * @returns {string} Translated plural form or source string.
+ */
function N_(n, s, p, c) {
if (plural_function == null && window.TR)
plural_function = new Function('n', (TR['00000000'] || 'plural=(n != 1);') + 'return +plural');
}
+/**
+ * Register a dependency entry for a field.
+ * @param {HTMLElement|string} field - Field element or its id.
+ * @param {Object} dep - Dependency specification object.
+ * @param {number} index - Order index of the dependent node.
+ */
function cbi_d_add(field, dep, index) {
var obj = (typeof(field) === 'string') ? document.getElementById(field) : field;
if (obj) {
}
}
+/**
+ * Check whether an input/select identified by target matches the given reference value.
+ * @param {string} target - Element id or name to query.
+ * @param {string} ref - Reference value to compare with.
+ * @returns {boolean} True if the current value matches ref.
+ */
function cbi_d_checkvalue(target, ref) {
var value = null,
query = 'input[id="'+target+'"], input[name="'+target+'"], ' +
return (((value !== null) ? value : "") == ref);
}
+/**
+ * Evaluate a list of dependency descriptors and return whether any match.
+ * @param {Array<Object>} deps - Array of dependency objects to evaluate.
+ * @returns {boolean} True when dependencies indicate the element should be shown.
+ */
function cbi_d_check(deps) {
var reverse;
var def = false;
return def;
}
+/**
+ * Update DOM nodes based on registered dependencies, showing or hiding
+ * nodes and restoring their order when dependency state changes.
+ */
function cbi_d_update() {
var state = false;
for (var i=0; i<cbi_d.length; i++) {
parent.dispatchEvent(new CustomEvent('dependency-update', { bubbles: true }));
}
+/**
+ * Initialize CBI widgets and wire up dependency and validation handlers.
+ * Walks the DOM looking for CBI-specific data attributes and replaces
+ * placeholders with interactive widgets.
+ */
function cbi_init() {
var nodes;
Promise.all(tasks).then(cbi_d_update);
}
+/**
+ * Run all validators associated with a form and optionally show an error.
+ * @param {HTMLFormElement} form - Form element containing validators.
+ * @param {string} [errmsg] - Message to show when validation fails.
+ * @returns {boolean} True when form is valid.
+ */
function cbi_validate_form(form, errmsg)
{
/* if triggered by a section removal or addition, don't validate */
return true;
}
+/**
+ * Enable/disable a named-section add button depending on input value.
+ * @param {HTMLInputElement} input - Input that contains the new section name.
+ */
function cbi_validate_named_section_add(input)
{
var button = input.parentNode.parentNode.querySelector('.cbi-button-add');
button.disabled = input.value === '';
}
+/**
+ * Trigger a delayed form validation (used to allow UI state to settle).
+ * @param {HTMLFormElement} form - Form to validate after a short delay.
+ * @returns {boolean} Always returns true.
+ */
function cbi_validate_reset(form)
{
window.setTimeout(
return true;
}
+/**
+ * Attach a validator to a field and wire validation events.
+ * @param {HTMLElement|string} cbid - Element or element id to validate.
+ * @param {boolean} optional - Whether an empty value is allowed.
+ * @param {string} type - Validator type expression (passed to L.validation).
+ */
function cbi_validate_field(cbid, optional, type)
{
var field = isElem(cbid) ? cbid : document.getElementById(cbid);
}
}
+/**
+ * Move a table row up or down within a section and update the storage field.
+ * @param {HTMLElement} elem - Element inside the row that triggers the swap.
+ * @param {boolean} up - If true, move the row up; otherwise move down.
+ * @param {string} store - ID of the hidden input used to store the order.
+ * @returns {boolean} Always returns false to cancel default action.
+ */
function cbi_row_swap(elem, up, store)
{
var tr = findParent(elem.parentNode, '.cbi-section-table-row');
return false;
}
+/**
+ * Mark the last visible value container child with class `cbi-value-last`.
+ * @param {HTMLElement} container - Parent container element.
+ */
function cbi_tag_last(container)
{
var last;
last.classList.add('cbi-value-last');
}
+/**
+ * Submit a form, optionally adding a hidden input to pass a name/value pair.
+ * @param {HTMLElement} elem - Element inside the form or an element with a form.
+ * @param {string} [name] - Name of hidden input to include, if any.
+ * @param {string} [value] - Value for the hidden input (defaults to '1').
+ * @param {string} [action] - Optional form action URL override.
+ * @returns {boolean} True on successful submit, false when no form found.
+ */
function cbi_submit(elem, name, value, action)
{
var form = elem.form || findParent(elem, 'form');
return true;
}
+/**
+ * @external String
+ */
+
+/**
+ * Format a string using positional arguments.
+ * @function format
+ * @memberof external:String.prototype
+ * @param {...string} args
+ * @returns {string}
+ */
String.prototype.format = function()
{
if (!RegExp)
var html_esc = [/&/g, '&', /"/g, '"', /'/g, ''', /</g, '<', />/g, '>'];
var quot_esc = [/"/g, '"', /'/g, '''];
+ /**
+ * Escape a string.
+ * @private
+ * @function esc
+ * @param {string} s
+ * @param {string} r
+ * @returns {string}
+ */
function esc(s, r) {
var t = typeof(s);
return out + str;
}
+/**
+ * Format a string using positional arguments.
+ * @function nobr
+ * @memberof external:String.prototype
+ * @param {...string} args
+ * @returns {string}
+ */
String.prototype.nobr = function()
{
return this.replace(/[\s\n]+/g, ' ');
}
+/**
+ * Format a string using positional arguments.
+ * @function format
+ * @memberof external:String
+ * @param {...string} args
+ * @returns {string}
+ */
String.format = function()
{
var a = [ ];
return ''.format.apply(arguments[0], a);
}
+/**
+ * Format a string using positional arguments.
+ * @function nobr
+ * @memberof external:String
+ * @param {...string} args
+ * @returns {string}
+ */
String.nobr = function()
{
var a = [ ];
}
+/**
+ * Return the element for input which may be an element or an id.
+ * @param {Element|string} e - Element or id.
+ * @returns {HTMLElement|null}
+ */
function isElem(e) { return L.dom.elem(e) }
+
+/**
+ * Parse an HTML string into a DOM element.
+ * @param {string} s - HTML string.
+ * @returns {HTMLElement} Parsed DOM element.
+ */
function toElem(s) { return L.dom.parse(s) }
+
+/**
+ * Test whether node matches a CSS selector.
+ * @param {Node} node - Node to test.
+ * @param {string} selector - CSS selector.
+ * @returns {boolean}
+ */
function matchesElem(node, selector) { return L.dom.matches(node, selector) }
+
+/**
+ * Find the parent matching selector from node upwards.
+ * @param {Node} node - Starting node.
+ * @param {string} selector - CSS selector to match ancestor.
+ * @returns {HTMLElement|null}
+ */
function findParent(node, selector) { return L.dom.parent(node, selector) }
+
+/**
+ * Create DOM elements using {@link L.dom.create} helper (convenience wrapper).
+ * @returns {HTMLElement}
+ */
function E() { return L.dom.create.apply(L.dom, arguments) }
+/**
+ * Initialize a dropdown element into an {@link L.ui.Dropdown} instance and bind it.
+ * If already bound, this is a no-op.
+ * @param {HTMLElement} sb - The select element to convert.
+ * @returns {L.ui.Dropdown|undefined} Dropdown instance or undefined when already bound.
+ */
function cbi_dropdown_init(sb) {
if (sb && L.dom.findClassInstance(sb) instanceof L.ui.Dropdown)
return;
return dl.bind(sb);
}
+/**
+ * Update or initialize a table UI widget with new data.
+ * @param {HTMLElement|string} table - Table element or selector.
+ * @param {...Node[]} data - Data to update the table with.
+ * @param {string} [placeholder] - Placeholder text when empty.
+ */
function cbi_update_table(table, data, placeholder) {
var target = isElem(table) ? table : document.querySelector(table);
t.update(data, placeholder);
}
+/**
+ * Show a modal dialog with the given title and children content.
+ * @deprecated
+ * @param {string} title - Title of the modal.
+ * @param {HTMLElement|Array<HTMLElement>} children - Content of the modal.
+ * @returns {Promise} Promise that resolves when modal shown or when closed depending on L.showModal.
+ */
function showModal(title, children)
{
return L.showModal(title, children);
}
+/**
+ * Hide any currently shown modal dialog.
+ * @deprecated
+ * @returns {*} Return value forwarded from L.hideModal.
+ */
function hideModal()
{
return L.hideModal();
'require tools.prng as random';
+/**
+ * @namespace LuCI
+ */
+
+/**
+ * @namespace LuCI.firewall
+ * @memberof LuCI
+ */
+
+/**
+ * Load the firewall configuration.
+ * @returns {Promise}
+ */
function initFirewallState() {
return L.resolveDefault(uci.load('firewall'));
}
+/**
+ * Parse an enum value.
+ * @param {?string} s
+ * @param {Array<string>} values
+ * @returns {string}
+ */
function parseEnum(s, values) {
if (s == null)
return null;
return null;
}
+/**
+ * Parse a policy value, or defaultValue if not found.
+ * @param {?string} s
+ * @param {Array<string>} [defaultValue=['DROP', 'REJECT', 'ACCEPT']]
+ * @returns {?string}
+ */
function parsePolicy(s, defaultValue) {
return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue);
}
var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule;
+/**
+ * Look up a firewall zone.
+ * @param {?string} name
+ * @returns {?Zone}
+ */
function lookupZone(name) {
var z = uci.get('firewall', name);
return null;
}
+/**
+ * Generate a colour for a name.
+ * @param {?string} forName
+ * @returns {string}
+ */
function getColorForName(forName) {
if (forName == null)
return '#eeeeee';
}
});
+/**
+ * @namespace LuCI.form
+ * @memberof LuCI
+ */
+
/**
* @class AbstractElement
* @memberof LuCI.form
L.error('InternalError', 'Not implemented');
},
- /** @private */
+ /**
+ * @private
+ * @param {...*} args
+ * @returns {Promise}
+ */
loadChildren(...args) /* ... */{
const tasks = [];
return Promise.all(tasks);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} tab_name
+ * @param {...*} args
+ * @returns {Promise}
+ */
renderChildren(tab_name, ...args) {
const tasks = [];
let index = 0;
*
* In all other cases, `null` is returned.
*
- * @param {string} property
+ * @param {string} attr (property)
* The name of the element property to use.
*
- * @param {...*} fmt_args
+ * @param {...string} args (fmt_args)
* Extra values to format the title string with.
*
* @returns {string|null}
});
/**
- * @constructor Map
+ * @class Map
* @memberof LuCI.form
* @augments LuCI.form.AbstractElement
- *
* @classdesc
- *
* The `Map` class represents one complete form. A form usually maps one UCI
* configuration file and is divided into multiple sections containing multiple
* fields each.
* uci configuration upon loading and mark the form readonly if no write
* permissions are granted.
*
- * @name LuCI.form.Map.prototype#readonly
- * @type boolean
+ * @memberof LuCI.form.Map.prototype
+ * @member {boolean} readonly
*/
/**
* nodes while `map.findElements('type', 'text')` would find any DOM node
* with a `type="text"` attribute.
*
+ * @param {...*} args argument array
+ *
* @param {string} selector_or_attrname
* If invoked with only one parameter, this argument is a
* `querySelectorAll()` compatible selector expression. If invoked with
* node while `map.findElement('type', 'text')` would find the first DOM
* node with a `type="text"` attribute.
*
+ * @param {...*} args argument array
+ *
* @param {string} selector_or_attrname
* If invoked with only one parameter, this argument is a `querySelector()`
* compatible selector expression. If invoked with two parameters, this
* to present configuration sections in different ways. Refer to the
* documentation of the different section classes for details.
*
- * @param {LuCI.form.AbstractSection} sectionclass
+ * @param {LuCI.form.AbstractSection} cbiClass (sectionclass)
* The section class to use for rendering the configuration section.
* Note that this value must be the class itself, not a class instance
* obtained from calling `new`. It must also be a class derived from
- * `LuCI.form.AbstractSection`.
+ * {@link LuCI.form.AbstractSection AbstractSection}.
*
- * @param {...string} classargs
+ * @param {...string} args (classargs)
* Additional arguments which are passed as-is to the constructor of the
* given section class. Refer to the class specific constructor
* documentation for details.
*/
section(cbiClass, ...args) {
if (!CBIAbstractSection.isSubclass(cbiClass))
- L.error('TypeError', 'Class must be a descendent of CBIAbstractSection');
+ L.error('TypeError', 'Class must be a descendant of CBIAbstractSection');
const obj = cbiClass.instantiate([this, ...args]);
this.append(obj);
* This function parses the current form, saves the resulting UCI changes,
* reloads the UCI configuration data and redraws the form elements.
*
- * @param {function} [cb]
+ * @param {function()} [cb]
* An optional callback function that is invoked after the form is parsed
* but before the changed UCI data is saved. This is useful to perform
* additional data manipulation steps before saving the changes.
return this.load().then(this.renderContents.bind(this));
},
- /** @private */
+ /**
+ * @private
+ * @returns {Promise}
+ */
renderContents() {
const mapEl = (this.root ??= E('div', {
'id': 'cbi-%s'.format(this.config),
return (inst instanceof CBIAbstractValue) ? [ inst, sid ] : null;
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {number} n
+ */
checkDepends(ev, n) {
let changed = false;
ui.tabs.updateTabs(ev, this.root);
},
- /** @private */
+ /**
+ * @private
+ * @param {string[]} depends
+ * @param {string} config_name
+ * @param {string} section_id
+ * @returns {boolean}
+ */
isDependencySatisfied(depends, config_name, section_id) {
let def = false;
});
/**
- * @constructor JSONMap
+ * @class JSONMap
* @memberof LuCI.form
* @augments LuCI.form.Map
*
* @classdesc
*
* The `AbstractSection` class serves as an abstract base for the different form
- * section styles implemented by `LuCI.form`. It provides the common logic for
+ * section styles implemented by {@link LuCI.form}. It provides the common logic for
* enumerating underlying configuration section instances, for registering
* form options and for handling tabs in order to segment child options.
*
*
* If this section is not nested, the property is `null`.
*
- * @name LuCI.form.AbstractSection.prototype#parentoption
- * @type LuCI.form.AbstractValue
+ * @memberof LuCI.form.AbstractSection.prototype
+ * @member parentoption
+ * @type {LuCI.form.AbstractValue}
* @readonly
*/
* the form section element.
*
* The default implementation always returns `true`. User code or
- * classes extending `AbstractSection` may override this function with
- * custom implementations.
+ * classes extending {@link LuCI.form.AbstractSection AbstractSection} may
+ * override this function with custom implementations.
*
* @abstract
* @param {string} section_id
* The option class to use for rendering the configuration option. Note
* that this value must be the class itself, not a class instance obtained
* from calling `new`. It must also be a class derived from
- * [LuCI.form.AbstractSection]{@link LuCI.form.AbstractSection}.
+ * {@link LuCI.form.AbstractSection AbstractSection}.
*
- * @param {...*} classargs
+ * @param {object} cbiClass (classargs)
* Additional arguments which are passed as-is to the constructor of the
* given option class. Refer to the class specific constructor
* documentation for details.
*
+ * @param {...*} args argument array
+ *
* @throws {TypeError}
* Throws a `TypeError` exception in case the passed class value is not a
- * descendant of `AbstractValue`.
+ * descendant of {@link LuCI.form.AbstractValue AbstractValue}.
*
* @returns {LuCI.form.AbstractValue}
* Returns the instantiated option class instance.
* The option class to use for rendering the configuration option. Note
* that this value must be the class itself, not a class instance obtained
* from calling `new`. It must also be a class derived from
- * [LuCI.form.AbstractSection]{@link LuCI.form.AbstractSection}.
+ * {@link LuCI.form.AbstractSection AbstractSection}.
*
- * @param {...*} classargs
+ * @param {...*} args (classargs)
* Additional arguments which are passed as-is to the constructor of the
* given option class. Refer to the class specific constructor
* documentation for details.
*
* @throws {TypeError}
* Throws a `TypeError` exception in case the passed class value is not a
- * descendant of `AbstractValue`.
+ * descendant of {@link LuCI.form.AbstractValue AbstractValue}.
*
* @returns {LuCI.form.AbstractValue}
* Returns the instantiated option class instance.
return rv;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {Promise}
+ */
renderUCISection(section_id) {
const renderTasks = [];
.then(this.renderTabContainers.bind(this, section_id));
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {Node[]} nodes
+ * @returns {Node[]}
+ */
renderTabContainers(section_id, nodes) {
const config_name = this.uciconfig ?? this.map.config;
const containerEls = E([]);
return containerEls;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} tab_name
+ * @param {string} section_id
+ * @returns {Node[]}
+ */
renderOptions(tab_name, section_id) {
const in_table = (this instanceof CBITableSection);
return this.renderChildren(tab_name, section_id, in_table).then((nodes) => {
});
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {number} n
+ * @returns {boolean}
+ */
checkDepends(ev, n) {
let changed = false;
const sids = this.cfgsections();
}
});
-
+/**
+ * Determines equality of two provided parameters. Can be arrays or objects.
+ * @function
+ * @param {*} x
+ * @param {*} y
+ * @returns {boolean}
+ */
function isEqual(x, y) {
if (typeof(y) == 'object' && y instanceof RegExp)
return (x == null) ? false : y.test(x);
return true;
};
+/**
+ * Determines containment of two provided parameters. Can be arrays or objects.
+ * @function
+ * @param {*} x
+ * @param {*} y
+ * @returns {boolean}
+ */
function isContained(x, y) {
if (Array.isArray(x)) {
for (let i = 0; i < x.length; i++)
* the form when the option element is disabled due to unsatisfied
* dependency constraints.
*
- * @name LuCI.form.AbstractValue.prototype#rmempty
- * @type boolean
+ * @member {boolean} rmempty
+ * @memberof LuCI.form.AbstractValue.prototype
* @default true
*/
* otherwise the option element is marked invalid when no value is entered
* or selected by the user.
*
- * @name LuCI.form.AbstractValue.prototype#optional
- * @type boolean
+ * @member {boolean} optional
+ * @memberof LuCI.form.AbstractValue.prototype
* @default false
*/
* is to remove the values of all options whose dependencies are not
* fulfilled.
*
- * @name LuCI.form.AbstractValue.prototype#retain
- * @type boolean
+ * @member {boolean} retain
+ * @memberof LuCI.form.AbstractValue.prototype
* @default false
*/
/**
* Sets a default value to use when the underlying UCI option is not set.
*
- * @name LuCI.form.AbstractValue.prototype#default
- * @type *
+ * @member {*} default
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
* If the user entered input does not match the datatype validation, the
* option element is marked as invalid.
*
- * @name LuCI.form.AbstractValue.prototype#datatype
- * @type string
+ * @member {string} datatype
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
* If the user entered input does not pass the validation function, the
* option element is marked as invalid.
*
- * @name LuCI.form.AbstractValue.prototype#validate
- * @type function
+ * @member {function()} validate
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
*
* The default of null means inherit from the parent form.
*
- * @name LuCI.form.AbstractValue.prototype#uciconfig
- * @type string
+ * @member {string} uciconfig
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
*
* The default of null means inherit from the parent section.
*
- * @name LuCI.form.AbstractValue.prototype#ucisection
- * @type string
+ * @member {string} ucisection
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
*
* The default of null means use the option element name.
*
- * @name LuCI.form.AbstractValue.prototype#ucioption
- * @type string
+ * @member {string} ucioption
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
/**
* Mark the grid section option element as editable.
*
- * Options which are displayed in the table portion of a `GridSection`
+ * Options which are displayed in the table portion of a {@link LuCI.form.GridSection GridSection}
* instance are rendered as readonly text by default. By setting the
* `editable` property of a child option element to `true`, that element
* is rendered as a full input widget within its cell instead of a text only
* This property has no effect on options that are not children of grid
* section elements.
*
- * @name LuCI.form.AbstractValue.prototype#editable
- * @type boolean
+ * @member {boolean} editable
+ * @memberof LuCI.form.AbstractValue.prototype
* @default false
*/
* This property has no effect on options that are not children of grid
* section elements.
*
- * @name LuCI.form.AbstractValue.prototype#modalonly
- * @type boolean
+ * @member {boolean} modalonly
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
* meaning its contents cannot be changed and the widget cannot be
* interacted with.
*
- * @name LuCI.form.AbstractValue.prototype#readonly
- * @type boolean
+ * @member {boolean} readonly
+ * @memberof LuCI.form.AbstractValue.prototype
* @default false
*/
* This property has no effect on options that are not children of grid or
* table section elements.
*
- * @name LuCI.form.AbstractValue.prototype#width
- * @type number|string
+ * @member {number|string} width
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
* first and the underlying configuration section ID as well as the input
* value as second and third argument respectively.
*
- * @name LuCI.form.AbstractValue.prototype#onchange
- * @type function
+ * @member {function()} onchange
+ * @memberof LuCI.form.AbstractValue.prototype
* @default null
*/
this.deps.push(deps);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {string[]} deplist
+ * @returns {string[]}
+ */
transformDepList(section_id, deplist) {
const list = deplist ?? this.deps;
const deps = [];
return deps;
},
- /** @private */
+ /**
+ * @private
+ * @returns {object} choices
+ */
transformChoices() {
if (!Array.isArray(this.keylist) || this.keylist.length == 0)
return null;
return choices;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {boolean}
+ */
checkDepends(section_id) {
const config_name = this.uciconfig ?? this.section.uciconfig ?? this.map.config;
const active = this.map.isDependencySatisfied(this.deps, config_name, section_id);
return active;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ */
updateDefaultValue(section_id) {
if (!L.isObject(this.defaults))
return;
*
* The default implementation of this method reads and returns the
* underlying UCI option value (or the related JavaScript property for
- * `JSONMap` instances). It may be overridden by user code to load data
- * from non-standard sources.
+ * {@link LuCI.form.JSONMap JSONMap} instances). It may be overridden by
+ * user code to load data from non-standard sources.
*
* @param {string} section_id
* The configuration section ID
* @throws {TypeError}
* Throws a `TypeError` exception when no `section_id` was specified.
*
- * @return {LuCI.ui.AbstractElement|null}
+ * @returns {LuCI.ui.AbstractElement|null}
* Returns the `LuCI.ui` element instance or `null` in case the form
* option implementation does not use `LuCI.ui` widgets.
*/
* @param {string} section_id
* The configuration section ID
*
+ * @param {string} set_value
+ * The value to assign
+ *
* @throws {TypeError}
* Throws a `TypeError` exception when no `section_id` was specified.
*
* @param {string} section_id
* The configuration section ID
*
- * @returns {function}
+ * @returns {function()}
* Returns a bound validator function suitable for passing to UI widgets.
* If this.validate is an array, returns a wrapper that calls each validator
* serially. Otherwise returns the bound validate method.
return (field != null && !field.classList.contains('hidden'));
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {boolean} active
+ * @returns {boolean}
+ */
setActive(section_id, active) {
const field = this.map.findElement('data-field', this.cbid(section_id));
return false;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {boolean}
+ */
triggerValidation(section_id) {
const elem = this.getUIElement(section_id);
return elem ? elem.triggerValidation() : true;
*
* The default implementation simply sets the given input value in the
* UCI configuration (or the associated JavaScript object property in
- * case of `JSONMap` forms). It may be overridden by user code to
- * implement alternative save logic, e.g. to transform the input value
- * before it is written.
+ * case of {@link LuCI.form.JSONMap JSONMap} forms). It may be overridden
+ * by user code to implement alternative save logic, e.g. to transform the
+ * input value before it is written.
*
* @param {string} section_id
* The configuration section ID
*
* @param {string|string[]} formvalue
* The input value to write.
+ *
+ * @returns {null}
*/
write(section_id, formvalue) {
return this.map.data.set(
*
* The default implementation simply removes the associated option from the
* UCI configuration (or the associated JavaScript object property in
- * case of `JSONMap` forms). It may be overridden by user code to
- * implement alternative removal logic, e.g. to retain the original value.
+ * case of {@link LuCI.form.JSONMap JSONMap} forms). It may be overridden
+ * by user code to implement alternative removal logic, e.g. to retain the
+ * original value.
*
* @param {string} section_id
* The configuration section ID
* section widget, otherwise only pre-existing sections may be edited.
* The default is `false`.
*
- * @name LuCI.form.TypedSection.prototype#addremove
- * @type boolean
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member addremove
+ * @type {boolean}
* @default false
*/
* not be rendered in the UI. The default is false, meaning that the
* title is rendered.
*
- * @name LuCI.form.TypedSection.prototype#hidetitle
- * @type boolean
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member hidetitle
+ * @type {boolean}
* @default false
*/
* rendered without a title element and that no name is required when adding
* new sections. The default is `false`.
*
- * @name LuCI.form.TypedSection.prototype#anonymous
- * @type boolean
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member anonymous
+ * @type {boolean}
* @default false
*/
* at the top of the form section element, allowing the user to switch
* among instances. The default is `false`.
*
- * @name LuCI.form.TypedSection.prototype#tabbed
- * @type boolean
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member tabbed
+ * @type {boolean}
* @default false
*/
* is used as a caption, after converting it to a string. If this property
* is not set, the default is `Add`.
*
- * @name LuCI.form.TypedSection.prototype#addbtntitle
- * @type string|function
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member addbtntitle
+ * @type {string|function()}
* @default null
*/
* is used as a caption, after converting it to a string. If this property
* is not set, the default is `Delete`.
*
- * @name LuCI.form.TypedSection.prototype#delbtntitle
- * @type string|function
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member delbtntitle
+ * @type {string|function()}
* @default null
*/
* By setting this property, a deviating configuration may be specified.
* The default of `null` means inherit from the parent form.
*
- * @name LuCI.form.TypedSection.prototype#uciconfig
- * @type string
+ * @memberof LuCI.form.TypedSection.prototype
+ * @member uciconfig
+ * @type {string}
* @default null
*/
.filter(L.bind(this.filter, this));
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {string} name
+ * @returns {null}
+ */
handleAdd(ev, name) {
const config_name = this.uciconfig ?? this.map.config;
return this.map.save(null, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {Event} ev
+ * @returns {null}
+ */
handleRemove(section_id, ev) {
const config_name = this.uciconfig ?? this.map.config;
return this.map.save(null, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} extra_class
+ * @returns {Node}
+ */
renderSectionAdd(extra_class) {
if (!this.addremove)
return E([]);
return createEl;
},
- /** @private */
+ /**
+ * @private
+ * @returns {Node}
+ */
renderSectionPlaceholder() {
return E('em', _('This section contains no values yet'));
},
- /** @private */
+ /**
+ * @private
+ * @param {string[]} cfgsections
+ * @param {Node[]} nodes
+ * @returns {Node[]}
+ */
renderContents(cfgsections, nodes) {
const section_id = null;
const config_name = this.uciconfig ?? this.map.config;
* If this property is not set, the default is the name of the underlying
* UCI configuration section.
*
- * @name LuCI.form.TableSection.prototype#sectiontitle
- * @type string|function
+ * @memberof LuCI.form.TableSection.prototype
+ * @member sectiontitle
+ * @type {string|function()}
* @default null
*/
* If this property is not set, the default is the name of the underlying
* UCI configuration section.
*
- * @name LuCI.form.TableSection.prototype#modaltitle
- * @type string|function
+ * @memberof LuCI.form.TableSection.prototype
+ * @member modaltitle
+ * @type {string|function()}
* @default null
*/
* Set a custom text for the actions column header row when actions buttons
* are present.
*
- * @name LuCI.form.TableSection.prototype#actionstitle
- * @type string|function
+ * @memberof LuCI.form.TableSection.prototype
+ * @member actionstitle
+ * @type {string|function()}
* @default null
*/
* opening a modal dialog presenting all options elements in `NamedSection`
* style when clicked.
*
- * @name LuCI.form.TableSection.prototype#max_cols
- * @type number
+ * @memberof LuCI.form.TableSection.prototype
+ * @member max_cols
+ * @type {number}
* @default null
*/
* classes are added to the table row elements. Not all LuCI themes
* implement these row style classes. The default is `false`.
*
- * @name LuCI.form.TableSection.prototype#rowcolors
- * @type boolean
+ * @memberof LuCI.form.TableSection.prototype
+ * @member rowcolors
+ * @type {boolean}
* @default false
*/
* the user to clone section instances mapped by the section form element.
* The default is `false`.
*
- * @name LuCI.form.TypedSection.prototype#cloneable
- * @type boolean
+ * @memberof LuCI.form.TableSection.prototype
+ * @member cloneable
+ * @type {boolean}
* @default false
*/
* is used as a caption, after converting it to a string. If this property
* is not set, the default is `Clone`.
*
- * @name LuCI.form.TypedSection.prototype#clonebtntitle
- * @type string|function
+ * @memberof LuCI.form.TableSection.prototype
+ * @member clonebtntitle
+ * @type {string|function()}
* @default null
*/
* handler on the rendered edit button, receiving the section instance
* name as the first and the DOM click event as the second argument.
*
- * @name LuCI.form.TableSection.prototype#extedit
- * @type string|function
+ * @memberof LuCI.form.TableSection.prototype
+ * @member extedit
+ * @type {string|function()}
* @default null
*/
* column entries on narrow displays which might fold the columns over
* multiple lines.
*
- * @name LuCI.form.TableSection.prototype#filterrow
- * @type boolean
+ * @memberof LuCI.form.TableSection.prototype
+ * @member filterrow
+ * @type {boolean}
* @default null
*/
*
* The default implementation returns an empty node.
*
- * @name LuCI.form.TableSection.prototype#footer
- * @type string[]|function
- * @default E([])
+ * @memberof LuCI.form.TableSection.prototype
+ * @member footer
+ * @type {string[]|function()}
+ * @default `E([])`
*/
/**
* the user to reorder the section instances mapped by the section form
* element.
*
- * @name LuCI.form.TableSection.prototype#sortable
- * @type boolean
+ * @memberof LuCI.form.TableSection.prototype
+ * @member sortable
+ * @type {boolean}
* @default false
*/
* not be displayed. By default, the row of descriptions is automatically displayed
* when at least one option has a description.
*
- * @name LuCI.form.TableSection.prototype#nodescriptions
- * @type boolean
+ * @memberof LuCI.form.TableSection.prototype
+ * @member nodescriptions
+ * @type {boolean}
* @default false
*/
* invoked.
*
* @override
- * @throws Throws an exception when invoked.
+ * @throws {string} Throws an exception when invoked.
*/
tab() {
throw 'Tabs are not supported by TableSection';
/**
* Clone the section_id, putting the clone immediately after if put_next
* is true. Optionally supply a name for the new section_id.
+ *
+ * @private
+ * @param {string} section_id
+ * @param {boolean} put_next
+ * @param {string} name
+ * @returns {null}
*/
- /** @private */
handleClone(section_id, put_next, name) {
let config_name = this.uciconfig || this.map.config;
return this.map.save(null, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {string[]} cfgsections
+ * @param {Node[]} nodes
+ * @returns {Node}
+ */
renderContents(cfgsections, nodes) {
const section_id = null;
const config_name = this.uciconfig ?? this.map.config;
return sectionEl;
},
- /** @private */
+ /**
+ * @private
+ * @param {boolean} has_action
+ * @returns {Node[]}
+ */
renderHeaderRows(has_action) {
let has_titles = false;
let has_descriptions = false;
return trEls;
},
- /** @private */
+ /**
+ * @private
+ * @param {boolean} has_action
+ * @returns {Node}
+ */
renderFooterRows(has_action) {
if (this.footer == null)
return E([]);
* (e.g., due to filtering). Measures the widest actions cell and applies
* a fixed width to header/filter/footer/action cells. Stores measured width
* in dataset so filtering won't collapse the column if all rows are hidden.
+ *
+ * @private
+ * @param {Node} tableEl
*/
stabilizeActionColumnWidth(tableEl) {
if (!tableEl || !tableEl.querySelector) return;
}
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {string} more_label
+ * @param {Node} trEl
+ * @returns {Node}
+ */
renderRowActions(section_id, more_label, trEl) {
const config_name = this.uciconfig ?? this.map.config;
return tdEl;
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleDragInit(ev) {
scope.dragState = { node: ev.target };
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {Node} trEl
+ * @returns {boolean}
+ */
handleDragStart(ev, trEl) {
// Only allow drag from the handle
if (!ev.target || !ev.target.classList || !ev.target.classList.contains('drag-handle')) {
ev.target.style.opacity = 0.4;
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {boolean}
+ */
handleDragOver(ev) {
if (scope.dragState === null ) return;
const n = scope.dragState.targetNode;
return false;
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {null}
+ */
handleDragEnter(ev) {
if (scope.dragState === null ) return;
scope.dragState.rect = ev.currentTarget.getBoundingClientRect();
scope.dragState.targetNode = ev.currentTarget;
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleDragLeave(ev) {
ev.currentTarget.classList.remove('drag-over-above');
ev.currentTarget.classList.remove('drag-over-below');
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {Node} trEl
+ * @returns {boolean}
+ */
handleDragEnd(ev, trEl) {
let n;
if (trEl) {
});
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {boolean|null}
+ */
handleDrop(ev) {
const s = scope.dragState;
if (!s) return;
return false;
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} node
+ * @returns {number[]}
+ */
determineBackgroundColor(node) {
let r = 255;
let g = 255;
return [ r, g, b ];
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {null}
+ */
handleTouchMove(ev) {
if (!ev.target.classList.contains('drag-handle'))
return;
window.requestAnimationFrame(() => { htmlElem.scrollTop += 30 });
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {null}
+ */
handleTouchEnd(ev) {
const rowElem = dom.parent(ev.target, '.tr');
const htmlElem = document.querySelector('html');
document.body.removeChild(dragHandle);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} modalMap
+ * @param {Event} ev
+ * @returns {Promise}
+ */
handleModalCancel(modalMap, ev) {
const prevNode = this.getPreviousModalMap();
let resetTasks = Promise.resolve();
return resetTasks;
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} modalMap
+ * @param {Event} ev
+ * @returns {Promise[]}
+ */
handleModalSave(modalMap, ev) {
const mapNode = this.getActiveModalMap();
let activeMap = dom.findClassInstance(mapNode);
.catch(() => {});
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {null}
+ */
handleSort(ev) {
if (!ev.target.matches('th[data-sortable-row]'))
return;
},
- /** @private */
+ /**
+ * @private
+ * @returns {Node[]}
+ */
getActiveModalMap() {
return document.querySelector('body.modal-overlay-active > #modal_overlay > .modal.cbi-modal > .cbi-map:not(.hidden)');
},
- /** @private */
+ /**
+ * @private
+ * @returns {Node[]|null}
+ */
getPreviousModalMap() {
const mapNode = this.getActiveModalMap();
const prevNode = mapNode ? mapNode.previousElementSibling : null;
return (prevNode && prevNode.matches('.cbi-map.hidden')) ? prevNode : null;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} src_section
+ * @param {string} dest_section
+ */
cloneOptions(src_section, dest_section) {
for (let i = 0; i < src_section.children.length; i++) {
const o1 = src_section.children[i];
}
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {Event} ev
+ * @returns {Promise}
+ */
renderMoreOptionsModal(section_id, ev) {
const parent = this.map;
const sref = parent.data.get(parent.config, section_id);
CBIAbstractSection.prototype.tab.call(this, name, title, description);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {string} name
+ * @returns {Promise}
+ */
handleAdd(ev, name) {
const config_name = this.uciconfig ?? this.map.config;
const section_id = this.map.data.add(config_name, this.sectiontype, name);
return this.renderMoreOptionsModal(section_id);
},
- /** @private */
+ /**
+ * @private
+ * @param {...*} args
+ * @returns {*}
+ */
handleModalSave(...args) /* ... */{
const mapNode = this.getPreviousModalMap();
const prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
return this.super('handleModalSave', args);
},
- /** @private */
+ /**
+ * @private
+ * @param {*} modalMap
+ * @param {Event} ev
+ * @param {boolean} isSaving
+ * @returns {*}
+ */
handleModalCancel(modalMap, ev, isSaving) {
const config_name = this.uciconfig ?? this.map.config;
const mapNode = this.getPreviousModalMap();
return this.super('handleModalCancel', arguments);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {*}
+ */
renderUCISection(section_id) {
return this.renderOptions(null, section_id);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} tab_name
+ * @param {string} section_id
+ * @param {string} in_table
+ * @returns {Promise[]}
+ */
renderChildren(tab_name, section_id, in_table) {
const tasks = [];
let index = 0;
return Promise.all(tasks);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {string} opt
+ * @returns {Node}
+ */
renderTextValue(section_id, opt) {
const title = this.stripTags(opt.title).trim();
const descr = this.stripTags(opt.description).trim();
}, (value != null) ? value : E('em', _('none')));
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {Node[]}
+ */
renderHeaderRows(section_id) {
return this.super('renderHeaderRows', [ true ]);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {Node[]}
+ */
renderRowActions(section_id) {
return this.super('renderRowActions', [ section_id, _('Edit') ]);
},
* configuration instance from the form section widget, otherwise only a
* pre-existing section may be edited. The default is `false`.
*
- * @name LuCI.form.NamedSection.prototype#addremove
- * @type boolean
+ * @memberof LuCI.form.NamedSection
+ * @member addremove
+ * @type {boolean}
* @default false
*/
* not be rendered in the UI. The default is false, meaning that the
* title is rendered.
*
- * @name LuCI.form.NamedSection.prototype#hidetitle
- * @type boolean
+ * @memberof LuCI.form.NamedSection
+ * @member hidetitle
+ * @type {boolean}
* @default false
*/
* By setting this property, a deviating configuration may be specified.
* The default of `null` means inherit from the parent form.
*
- * @name LuCI.form.NamedSection.prototype#uciconfig
- * @type string
+ * @memberof LuCI.form.NamedSection
+ * @member uciconfig
+ * @type {string}
* @default null
*/
return [ this.section ];
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {null}
+ */
handleAdd(ev) {
const section_id = this.section;
const config_name = this.uciconfig ?? this.map.config;
return this.map.save(null, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {null}
+ */
handleRemove(ev) {
const section_id = this.section;
const config_name = this.uciconfig ?? this.map.config;
return this.map.save(null, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {string[]} data
+ * @returns {Node}
+ */
renderContents(data) {
const ucidata = data[0];
const nodes = data[1];
* If set to `true`, the field is rendered as a password input, otherwise
* as a plain text input.
*
- * @name LuCI.form.Value.prototype#password
- * @type boolean
+ * @memberof LuCI.form.Value.prototype
+ * @member password
+ * @type {boolean}
* @default false
*/
/**
* Set a placeholder string to use when the input field is empty.
*
- * @name LuCI.form.Value.prototype#placeholder
- * @type string
+ * @memberof LuCI.form.Value.prototype
+ * @member placeholder
+ * @type {string}
* @default null
*/
.then(this.renderFrame.bind(this, section_id, in_table, option_index));
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {object} state
+ * @param {Event} ev
+ * @returns {null}
+ */
handleValueChange(section_id, state, ev) {
if (typeof(this.onchange) != 'function')
return;
this.onchange.call(this, ev, section_id, value);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {string} in_table
+ * @param {number} option_index
+ * @param {Node[]} nodes
+ * @returns {Node}
+ */
renderFrame(section_id, in_table, option_index, nodes) {
const config_name = this.uciconfig ?? this.section.uciconfig ?? this.map.config;
const depend_list = this.transformDepList(section_id);
return optionEl;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const value = (cfgvalue != null) ? cfgvalue : this.default;
const choices = this.transformChoices();
* Default is `null`. If `true`, the underlying form value will
* not be checked for duplication.
*
- * @name LuCI.form.DynamicList.prototype#allowduplicates
- * @type boolean
+ * @memberof LuCI.form.DynamicList.prototype
+ * @member allowduplicates
+ * @type {boolean}
* @default null
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const value = (cfgvalue != null) ? cfgvalue : this.default;
const choices = this.transformChoices();
/**
* Set the size attribute of the underlying HTML select element.
*
- * @name LuCI.form.ListValue.prototype#size
- * @type number
+ * @memberof LuCI.form.ListValue.prototype
+ * @member size
+ * @type {number}
* @default null
*/
* select element is rendered, otherwise a collection of `radio`
* elements is used.
*
- * @name LuCI.form.ListValue.prototype#widget
- * @type string
+ * @memberof LuCI.form.ListValue.prototype
+ * @member widget
+ * @type {string}
* @default select
*/
* May be one of `horizontal` or `vertical`. Only applies to non-select
* widget types.
*
- * @name LuCI.form.ListValue.prototype#orientation
- * @type string
+ * @memberof LuCI.form.ListValue.prototype
+ * @member orientation
+ * @type {string}
* @default horizontal
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const choices = this.transformChoices();
const widget = new ui.Select((cfgvalue != null) ? cfgvalue : this.default, choices, {
* @param {string} [description]
* The description text of the option element.
*/
-const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.ListValue.prototype */ {
+const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.RichListValue.prototype */ {
__name__: 'CBI.RichListValue',
__init__() {
* May be one of `horizontal` or `vertical`. Only applies to non-select
* widget types.
*
- * @name LuCI.form.RichListValue.prototype#orientation
- * @type string
+ * @memberof LuCI.form.RichListValue.prototype
+ * @member orientation
+ * @type {string}
* @default horizontal
*/
/**
* Set the size attribute of the underlying HTML select element.
*
- * @name LuCI.form.RichListValue.prototype#size
- * @type number
+ * @memberof LuCI.form.RichListValue.prototype
+ * @member size
+ * @type {number}
* @default null
*/
* select element is rendered, otherwise a collection of `radio`
* elements is used.
*
- * @name LuCI.form.RichListValue.prototype#widget
- * @type string
+ * @memberof LuCI.form.RichListValue.prototype
+ * @member widget
+ * @type {string}
* @default select
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const choices = this.transformChoices();
const widget = new ui.Dropdown((cfgvalue != null) ? cfgvalue : this.default, choices, {
* which prompts the user to select a predefined choice, or to enter a
* custom value.
*
- * @param {string} key
+ * @param {string} value
* The choice value to add.
*
- * @param {Node|string} val
+ * @param {Node|string} title
* The caption for the choice value. May be a DOM node, a document fragment
* or a plain text string. If omitted, the `key` value is used as caption.
*
/**
* Minimum value the slider can represent.
- * @name LuCI.form.RangeSliderValue.prototype#min
- * @type number
+ * @memberof LuCI.form.RangeSliderValue.prototype
+ * @member min
+ * @type {number}
* @default 0
*/
/**
* Maximum value the slider can represent.
- * @name LuCI.form.RangeSliderValue.prototype#max
- * @type number
+ * @memberof LuCI.form.RangeSliderValue.prototype
+ * @member max
+ * @type {number}
* @default 100
*/
/**
* Step size for each tick of the slider, or the special value "any" when
* handling arbitrary precision floating point numbers.
- * @name LuCI.form.RangeSliderValue.prototype#step
- * @type string
+ * @memberof LuCI.form.RangeSliderValue.prototype
+ * @member step
+ * @type {string}
* @default 1
*/
* Set the default value for the slider. The default value is elided during
* save: meaning, a currently chosen value which matches the default is
* not saved.
- * @name LuCI.form.RangeSliderValue.prototype#default
- * @type string
+ * @memberof LuCI.form.RangeSliderValue.prototype
+ * @member default
+ * @type {string}
* @default null
*/
* is more meaningful than the currently chosen value. The calculated value
* is displayed below the slider.
*
- * @name LuCI.form.RangeSliderValue.prototype#calculate
- * @type function
+ * @memberof LuCI.form.RangeSliderValue.prototype
+ * @member calculate
+ * @type {function()}
* @default null
*/
*
* Suffix a unit string to the calculated value, e.g. 'seconds' or 'dBm'.
*
- * @name LuCI.form.RangeSliderValue.prototype#calcunits
- * @type string
+ * @memberof LuCI.form.RangeSliderValue.prototype
+ * @member calcunits
+ * @type {string}
* @default null
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const slider = new ui.RangeSlider((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id),
/**
* Sets the input value to use for the checkbox checked state.
*
- * @name LuCI.form.Flag.prototype#enabled
- * @type string
+ * @memberof LuCI.form.Flag.prototype
+ * @member enabled
+ * @type {string}
* @default 1
*/
/**
* Sets the input value to use for the checkbox unchecked state.
*
- * @name LuCI.form.Flag.prototype#disabled
- * @type string
+ * @memberof LuCI.form.Flag.prototype
+ * @member disabled
+ * @type {string}
* @default 0
*/
* value will be shown as a tooltip. If the return value of the function
* is `null` no tooltip will be set.
*
- * @name LuCI.form.Flag.prototype#tooltip
- * @type string|function
+ * @memberof LuCI.form.Flag.prototype
+ * @member {string|function()} tooltip
* @default null
*/
* If set, this icon will be shown for the default one.
* This could also be a png icon from the resources directory.
*
- * @name LuCI.form.Flag.prototype#tooltipicon
- * @type string
+ * @memberof LuCI.form.Flag.prototype
+ * @member tooltipicon
+ * @type {string}
* @default 'ℹ️';
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
let tooltip = null;
/**
* Allows custom value entry in addition to those already specified.
*
- * @name LuCI.form.MultiValue.prototype#create
- * @type boolean
+ * @memberof LuCI.form.MultiValue.prototype
+ * @member create
+ * @type {boolean}
* @default null
*/
* property of the underlying dropdown widget. If omitted, the value of
* the `size` property is used or `3` when `size` is also unspecified.
*
- * @name LuCI.form.MultiValue.prototype#display_size
- * @type number
+ * @memberof LuCI.form.MultiValue.prototype
+ * @member display_size
+ * @type {number}
* @default null
*/
* property of the underlying dropdown widget. If omitted, the value of
* the `size` property is used or `-1` when `size` is also unspecified.
*
- * @name LuCI.form.MultiValue.prototype#dropdown_size
- * @type number
+ * @memberof LuCI.form.MultiValue.prototype
+ * @member dropdown_size
+ * @type {number}
* @default null
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const value = (cfgvalue != null) ? cfgvalue : this.default;
const choices = this.transformChoices();
* Enforces the use of a monospace font for the textarea contents when set
* to `true`.
*
- * @name LuCI.form.TextValue.prototype#monospace
- * @type boolean
+ * @memberof LuCI.form.TextValue.prototype
+ * @member monospace
+ * @type {boolean}
* @default false
*/
* Allows specifying the [cols]{@link LuCI.ui.Textarea.InitOptions}
* property of the underlying textarea widget.
*
- * @name LuCI.form.TextValue.prototype#cols
- * @type number
+ * @memberof LuCI.form.TextValue.prototype
+ * @member cols
+ * @type {number}
* @default null
*/
* Allows specifying the [rows]{@link LuCI.ui.Textarea.InitOptions}
* property of the underlying textarea widget.
*
- * @name LuCI.form.TextValue.prototype#rows
- * @type number
+ * @memberof LuCI.form.TextValue.prototype
+ * @member rows
+ * @type {number}
* @default null
*/
* Allows specifying the [wrap]{@link LuCI.ui.Textarea.InitOptions}
* property of the underlying textarea widget.
*
- * @name LuCI.form.TextValue.prototype#wrap
- * @type number
+ * @memberof LuCI.form.TextValue.prototype
+ * @member wrap
+ * @type {number}
* @default null
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const value = (cfgvalue != null) ? cfgvalue : this.default;
* By setting this property, the dummy value text is wrapped in an `<a>`
* element with the property value used as `href` attribute.
*
- * @name LuCI.form.DummyValue.prototype#href
- * @type string
+ * @memberof LuCI.form.DummyValue.prototype
+ * @member href
+ * @type {string}
* @default null
*/
* text. In some cases, HTML content may need to be interpreted and
* rendered as-is. When set to `true`, HTML escaping is disabled.
*
- * @name LuCI.form.DummyValue.prototype#rawhtml
- * @type boolean
+ * @memberof LuCI.form.DummyValue.prototype
+ * @member rawhtml
+ * @type {boolean}
* @default null
*/
*
* By default, the value is displayed.
*
- * @name LuCI.form.DummyValue.prototype#hidden
- * @type boolean
+ * @memberof LuCI.form.DummyValue.prototype
+ * @member hidden
+ * @type {boolean}
* @default null
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const value = (cfgvalue != null) ? cfgvalue : this.default;
const hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) });
*
* The default of `null` means the option title is used as caption.
*
- * @name LuCI.form.Button.prototype#inputtitle
- * @type string|function
+ * @memberof LuCI.form.Button.prototype
+ * @member inputtitle
+ * @type {string|function()}
* @default null
*/
*
* The default of `null` means a neutral button styling is used.
*
- * @name LuCI.form.Button.prototype#inputstyle
- * @type string
+ * @memberof LuCI.form.Button.prototype
+ * @member inputstyle
+ * @type {string}
* @default null
*/
* DOM click element as the first and the underlying configuration section ID
* as the second argument.
*
- * @name LuCI.form.Button.prototype#onclick
- * @type function
+ * @memberof LuCI.form.Button.prototype
+ * @member onclick
+ * @type {function()}
* @default null
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const value = (cfgvalue != null) ? cfgvalue : this.default;
const hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) });
const CBIHiddenValue = CBIValue.extend(/** @lends LuCI.form.HiddenValue.prototype */ {
__name__: 'CBI.HiddenValue',
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const widget = new ui.Hiddenfield((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id)
* Render the widget in browser mode initially instead of a button
* to 'Select File...'.
*
- * @name LuCI.form.FileUpload.prototype#browser
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member browser
+ * @type {boolean}
* @default false
*/
*
* The default of `false` means hidden files are not displayed.
*
- * @name LuCI.form.FileUpload.prototype#show_hidden
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member show_hidden
+ * @type {boolean}
* @default false
*/
*
* The default of `true` means file upload functionality is displayed.
*
- * @name LuCI.form.FileUpload.prototype#enable_upload
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member enable_upload
+ * @type {boolean}
* @default true
*/
*
* The default of `false` means the directory create button is hidden.
*
- * @name LuCI.form.FileUpload.prototype#directory_create
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member directory_create
+ * @type {boolean}
* @default false
*/
*
* The default is `false`.
*
- * @name LuCI.form.FileUpload.prototype#directory_select
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member directory_select
+ * @type {boolean}
* @default false
*/
*
* The default is `true`, means file removal buttons are displayed.
*
- * @name LuCI.form.FileUpload.prototype#enable_remove
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member enable_remove
+ * @type {boolean}
* @default true
*/
/**
* Toggle download file functionality.
*
- * @name LuCI.form.FileUpload.prototype#enable_download
- * @type boolean
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member enable_download
+ * @type {boolean}
* @default false
*/
*
* The default is `/etc/luci-uploads`.
*
- * @name LuCI.form.FileUpload.prototype#root_directory
- * @type string
+ * @memberof LuCI.form.FileUpload.prototype
+ * @member root_directory
+ * @type {string}
* @default /etc/luci-uploads
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id),
* Render the widget in browser mode initially instead of a button
* to 'Select Directory...'.
*
- * @name LuCI.form.DirectoryPicker.prototype#browser
- * @type boolean
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member browser
+ * @type {boolean}
* @default false
*/
*
* The default of `false` means the directory create button is hidden.
*
- * @name LuCI.form.DirectoryPicker.prototype#directory_create
- * @type boolean
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member directory_create
+ * @type {boolean}
* @default false
*/
/**
* Toggle download file functionality.
*
- * @name LuCI.form.DirectoryPicker.prototype#enable_download
- * @type boolean
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member enable_download
+ * @type {boolean}
* @default false
*/
*
* The default is `false`, means file removal buttons are not displayed.
*
- * @name LuCI.form.DirectoryPicker.prototype#enable_remove
- * @type boolean
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member enable_remove
+ * @type {boolean}
* @default false
*/
*
* The default of `false` means file upload functionality is disabled.
*
- * @name LuCI.form.DirectoryPicker.prototype#enable_upload
- * @type boolean
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member enable_upload
+ * @type {boolean}
* @default false
*/
*
* The default is `/tmp`.
*
- * @name LuCI.form.DirectoryPicker.prototype#root_directory
- * @type string
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member root_directory
+ * @type {string}
* @default /tmp
*/
*
* The default of `true` means hidden files are displayed.
*
- * @name LuCI.form.DirectoryPicker.prototype#show_hidden
- * @type boolean
+ * @memberof LuCI.form.DirectoryPicker.prototype
+ * @member {boolean} show_hidden
* @default true
*/
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
const browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id),
this.super('__init__', [ map, section, option ]);
if (!CBIAbstractSection.isSubclass(cbiClass))
- throw 'Sub section must be a descendent of CBIAbstractSection';
+ throw 'Sub section must be a descendant of CBIAbstractSection';
this.subsection = cbiClass.instantiate([ this.map, ...args ]);
this.subsection.parentoption = this;
*
* This property holds a reference to the instantiated nested section.
*
- * @name LuCI.form.SectionValue.prototype#subsection
- * @type LuCI.form.AbstractSection
+ * @memberof LuCI.form.SectionValue
+ * @member subsection
+ * @type {LuCI.form.AbstractSection}
* @readonly
*/
return this.subsection.parse(section_id);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @param {number} option_index
+ * @param {string} cfgvalue
+ * @returns {Node}
+ */
renderWidget(section_id, option_index, cfgvalue) {
return this.subsection.render(section_id);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} section_id
+ * @returns {null}
+ */
checkDepends(section_id) {
this.subsection.checkDepends(section_id);
return CBIValue.prototype.checkDepends.apply(this, [ section_id ]);
* [render()]{@link LuCI.form.Map#render} is invoked on the instance to
* assemble the HTML markup and insert it into the DOM.
*
- * Example:
+ * @example
*
- * <pre>
* 'use strict';
* 'require form';
*
* let m, s, o;
*
- * m = new form.Map('example', 'Example form',
- * 'This is an example form mapping the contents of /etc/config/example');
+ * m = new form.Map('example', _('Example form'),
+ * _('This is an example form mapping the contents of /etc/config/example'));
*
- * s = m.section(form.NamedSection, 'first_section', 'example', 'The first section',
- * 'This sections maps "config example first_section" of /etc/config/example');
+ * s = m.section(form.NamedSection, 'first_section', 'example', _('The first section'),
+ * _('This sections maps "config example first_section" of /etc/config/example'));
*
- * o = s.option(form.Flag, 'some_bool', 'A checkbox option');
+ * o = s.option(form.Flag, 'some_bool', _('A checkbox option'));
*
- * o = s.option(form.ListValue, 'some_choice', 'A select element');
- * o.value('choice1', 'The first choice');
- * o.value('choice2', 'The second choice');
+ * o = s.option(form.ListValue, 'some_choice', _('A select element'));
+ * o.value('choice1', _('The first choice'));
+ * o.value('choice2', _('The second choice'));
*
* m.render().then((node) => {
* document.body.appendChild(node);
* });
- * </pre>
*/
return baseclass.extend(/** @lends LuCI.form.prototype */ {
Map: CBIMap,
'require request';
'require baseclass';
+/**
+ * @namespace LuCI.fs
+ * @memberof LuCI
+ */
+
/**
* @typedef {Object} FileStatEntry
* @memberof LuCI.fs
-
+ *
* @property {string} name - Name of the directory entry
* @property {string} type - Type of the entry, one of `block`, `char`, `directory`, `fifo`, `symlink`, `file`, `socket` or `unknown`
* @property {number} size - Size in bytes
'UnsupportedError'
];
+/**
+ * Handle an RPC reply.
+ * @param {object} expect
+ * @param {number} rc
+ * @throws {Error}
+ * @returns {number}
+ */
function handleRpcReply(expect, rc) {
if (typeof(rc) == 'number' && rc != 0) {
var e = new Error(rpc.getStatusText(rc)); e.name = rpcErrors[rc] || 'Error';
return rc;
}
+/**
+ * Handle a CGI-IO reply.
+ * @param {object} res
+ * @throws {Error}
+ * @returns {string}
+ */
function handleCgiIoReply(res) {
if (!res.ok || res.status != 200) {
var e = new Error(res.statusText);
* Whether to include stderr output in command output. This is usually
* not needed but can be useful to execute a command and get full output.
*
- * @param {function} [responseProgress=null]
+ * @param {function()} [responseProgress=null]
* An optional request callback function which receives ProgressEvent
* instances as sole argument during the HTTP response transfer. This is
* usually not needed but can be useful to receive realtime command
/**
- * @class LuCI
- * @classdesc
*
* This is the LuCI base class. It is automatically instantiated and
* accessible using the global `L` variable.
- *
- * @param {Object} env
- * The environment settings to use for the LuCI runtime.
+ * @class LuCI
+ * @classdesc
+ * @property {object} env The environment settings to use for the LuCI runtime.
+ * @param {Window} window - The browser global `window` object.
+ * @param {Document} document - The DOM `document` root for the current page.
+ * @param {undefined} undefined - Local `undefined` slot (prevents shadowing and
+ * ensures `undefined` is the real undefined value).
*/
((window, document, undefined) => {
const toCamelCase = s => s.replace(/(?:^|[\. -])(.)/g, (m0, m1) => m1.toUpperCase());
/**
- * @class baseclass
+ * @class
+ * @name LuCI.baseclass
* @hideconstructor
- * @memberof LuCI
* @classdesc
*
* `LuCI.baseclass` is the abstract base class all LuCI classes inherit from.
* An object describing the properties to add to the new
* subclass.
*
+ * @this {LuCI.baseclass}
* @returns {LuCI.baseclass}
* Returns a new LuCI.baseclass subclassed from this class, extended
* by the given properties and with its prototype set to this base
/**
* Extends this base class with the properties described in
* `properties`, instantiates the resulting subclass using
- * the additional optional arguments passed to this function
+ * the given arguments passed to this function
* and returns the resulting subclassed Class instance.
*
* This function serves as a convenience shortcut for
* An object describing the properties to add to the new
* subclass.
*
- * @param {...*} [new_args]
- * Specifies arguments to be passed to the subclass constructor
- * as-is in order to instantiate the new subclass.
+ * @param {...*} new_args
+ * Arguments forwarded to the constructor of the generated subclass.
*
* @returns {LuCI.baseclass}
* Returns a new LuCI.baseclass instance extended by the given
*
* @memberof LuCI.baseclass
*
- * @param {Array<*>} params
+ * @param {Array<*>} args
* An array of arbitrary values which will be passed as arguments
* to the constructor function.
*
* @returns {boolean}
* Returns `true` when the given `classValue` is a subclass of this
* class or `false` if the given value is not a valid class or not
- * a subclass of this class'.
+ * a subclass of this class.
*/
isSubclass(classValue) {
return (typeof(classValue) == 'function' && classValue.prototype instanceof this);
* `offset` and prepend any further given optional parameters to
* the beginning of the resulting array copy.
*
- * @memberof LuCI.baseclass
+ * @memberof LuCI.baseclass.prototype
* @instance
*
* @param {Array<*>} args
* Calls the `key()` method with parameters `arg1` and `arg2`
* when found within one of the parent classes.
*
- * @memberof LuCI.baseclass
+ * @memberof LuCI.baseclass.prototype
* @instance
*
* @param {string} key
* The name of the superclass member to retrieve.
*
- * @param {Array<*>} [callArgs]
- * An optional array of function call parameters to use. When
- * this parameter is specified, the found member value is called
- * as a function using the values of this array as arguments.
+ * @param {...*|Array<*>} [callArgs]
+ * Arguments to pass when invoking the superclass method. May be
+ * either an argument array or variadic arguments.
*
* @throws {ReferenceError}
* Throws a `ReferenceError` when `callArgs` are specified and
/**
* Returns a string representation of this class.
*
+ * @memberof LuCI.baseclass.prototype
+ * @instance
+ *
* @returns {string}
* Returns a string representation of this class containing the
* constructor functions `displayName` and describing the class
const requestQueue = [];
+ /**
+ * Check whether a Request.options object is eligible to be queued for RPC
+ * batching.
+ *
+ * A request is considered queueable when all of the following hold:
+ * - `classes.rpc` is available
+ * - HTTP method is `POST` and `content` is an object
+ * - `nobatch` is not explicitly `true`
+ * - the request URL starts with the RPC base URL
+ *
+ * @private
+ * @param {object} opt - Options object passed to `Request.request()`
+ * @returns {boolean} `true` if the request may be queued for batching,
+ * otherwise `false`
+ */
function isQueueableRequest(opt) {
if (!classes.rpc)
return false;
return (rpcBaseURL != null && opt.url.indexOf(rpcBaseURL) == 0);
}
+ /**
+ * Send all queued RPC requests in a single batched call and dispatch
+ * individual callbacks for each original request.
+ *
+ * Behaviour:
+ * - aggregates queued requests' `content` into a single array payload
+ * - sends the batch to `rpcBaseURL` with `nobatch: true`
+ * - on success: clones and forwards per-request replies to the
+ * originally provided resolve callbacks, or rejects when no
+ * related reply is available
+ * - on failure: invokes each queued request's reject callback
+ *
+ * @private
+ * @returns {void}
+ */
function flushRequestQueue() {
if (!requestQueue.length)
return;
* @property {Object<string, string>} [header]
* Specifies HTTP headers to set for the request.
*
- * @property {function} [progress]
+ * @property {function()} [progress]
* An optional request callback function which receives ProgressEvent
* instances as sole argument during the HTTP request transfer.
*
- * @property {function} [responseProgress]
+ * @property {function()} [responseProgress]
* An optional request callback function which receives ProgressEvent
* instances as sole argument during the HTTP response transfer.
*/
});
},
+ /**
+ * Handle XHR readyState changes for an in-flight request and resolve or
+ * reject the originating promise.
+ *
+ * @instance
+ * @memberof LuCI.request
+ * @param {function(LuCI.response)} resolveFn
+ * Callback invoked on success with the constructed {@link LuCI.response}.
+ *
+ * @param {function(Error)} rejectFn
+ * Callback invoked on failure or abort with an `Error` instance.
+ *
+ * @param {Event} [ev]
+ * The XHR `readystatechange` event (optional).
+ *
+ * @returns {void}
+ * No return value; the function resolves or rejects the supplied callbacks.
+ */
handleReadyStateChange(resolveFn, rejectFn, ev) {
const xhr = this.xhr, duration = Date.now() - this.start;
* @param {LuCI.request.RequestOptions} [options]
* Additional options to configure the request.
*
- * @param {LuCI.request.poll~callbackFn} [callback]
+ * @param {LuCI.request.poll.callbackFn} [callback]
* {@link LuCI.request.poll~callbackFn Callback} function to
* invoke for each HTTP reply.
*
* @throws {TypeError}
* Throws `TypeError` when an invalid interval was passed.
*
- * @returns {function}
+ * @returns {function()}
* Returns the internally created poll function.
*/
add(interval, url, options, callback) {
*
* @instance
* @memberof LuCI.request.poll
- * @param {function} entry
+ * @param {function()} entry
* The poll function returned by {@link LuCI.request.poll#add add()}.
*
* @returns {boolean}
remove(entry) { return Poll.remove(entry) },
/**
- * Alias for {@link LuCI.poll.start LuCI.poll.start()}.
- *
- * @instance
- * @memberof LuCI.request.poll
- */
+ * Alias for {@link LuCI.poll.start LuCI.poll.start()}.
+ *
+ * @instance
+ * @memberof LuCI.request.poll
+ * @returns {boolean}
+ */
start() { return Poll.start() },
/**
- * Alias for {@link LuCI.poll.stop LuCI.poll.stop()}.
- *
- * @instance
- * @memberof LuCI.request.poll
- */
+ * Alias for {@link LuCI.poll.stop LuCI.poll.stop()}.
+ *
+ * @instance
+ * @memberof LuCI.request.poll
+ * @returns {boolean}
+ */
stop() { return Poll.stop() },
/**
- * Alias for {@link LuCI.poll.active LuCI.poll.active()}.
- *
- * @instance
- * @memberof LuCI.request.poll
- */
+ * Alias for {@link LuCI.poll.active LuCI.poll.active()}.
+ *
+ * @instance
+ * @memberof LuCI.request.poll
+ * @returns {boolean}
+ */
active() { return Poll.active() }
}
});
*
* @instance
* @memberof LuCI.poll
- * @param {function} fn
+ * @param {function()} fn
* The function to invoke on each poll interval.
*
* @param {number} interval
*
* @instance
* @memberof LuCI.poll
- * @param {function} fn
+ * @param {function()} fn
* The function to remove.
*
* @throws {TypeError}
* When `val` is of any other type, it will be added as an attribute
* to the given `node` as-is, with the underlying `setAttribute()`
* call implicitly turning it into a string.
+ * @returns {null}
*/
attr(node, key, val) {
if (!this.elem(node))
*
* @instance
* @memberof LuCI.dom
- * @param {*} html
+ * @param {string} html
* Describes the node to create.
*
* When the value of `html` is of type array, a `DocumentFragment`
* @param {string} method
* The name of the method to invoke on the found class instance.
*
- * @param {...*} params
+ * @param {...*} args
* Additional arguments to pass to the invoked method as-is.
*
* @returns {*|null}
* @param {Node} node
* The DOM `Node` instance to test.
*
- * @param {LuCI.dom~ignoreCallbackFn} [ignoreFn]
+ * @param {LuCI.dom.ignoreCallbackFn} [ignoreFn]
* Specifies an optional function which is invoked for each child
* node to decide whether the child node should be ignored or not.
*
* @memberof LuCI.view
* @param {Event} ev
* The DOM event that triggered the function.
+ * @param {number} mode
+ * Whether to apply the changes checked.
*
* @returns {*|Promise<*>}
* Any return values of this function are discarded, but
* @instance
* @memberof LuCI
*
- * @param {function} fn
+ * @param {function()} fn
* The function to bind.
*
* @param {*} self
* Zero or more variable arguments which are bound to the function
* as parameters.
*
- * @returns {function}
+ * @returns {function()}
* Returns the bound function.
*/
bind(fn, self, ...args) {
* be replaced by spaces and joined with the runtime-determined
* base URL of LuCI.js to form an absolute URL to load the class
* file from.
- *
+ * @param {string[]} [from=[]]
+ * Optional dependency chain used during dependency resolution. This
+ * array contains the sequence of class names already being resolved
+ * (the caller stack). It is used to detect circular dependencies —
+ * if `name` appears in `from` a `DependencyError` is thrown.
* @throws {DependencyError}
* Throws a `DependencyError` when the class to load includes
* circular dependencies.
* such as `sae` or `11w` support. The `subfeature` argument can
* be used to query these.
*
- * @return {boolean|null}
+ * @returns {boolean|null}
* Return `true` if the queried feature (and sub-feature) is available
* or `false` if the requested feature isn't present or known.
* Return `null` when a sub-feature was queried for a feature which
},
/* private */
- setupDOM(res) {
- const domEv = res[0], uiClass = res[1], rpcClass = res[2], formClass = res[3], rpcBaseURL = res[4];
+ setupDOM([domEv, uiClass, rpcClass, formClass, rpcBaseURL]) {
rpcClass.setBaseURL(rpcBaseURL);
* @param {...string} [parts]
* An array of parts to join into a path.
*
- * @return {string}
+ * @returns {string}
* Return the joined path.
*/
fspath() /* ... */{
* An array of parts to join into a URL path. Parts may contain
* slashes and any of the other characters mentioned above.
*
- * @return {string}
+ * @returns {string}
* Return the joined URL path.
*/
path(prefix = '', parts) {
* An array of parts to join into a URL path. Parts may contain
* slashes and any of the other characters mentioned above.
*
- * @return {string}
+ * @returns {string}
* Returns the resulting URL path.
*/
url() {
* An array of parts to join into a URL path. Parts may contain
* slashes and any of the other characters mentioned above.
*
- * @return {string}
+ * @returns {string}
* Returns the resulting URL path.
*/
resource() {
* An array of parts to join into a URL path. Parts may contain
* slashes and any of the other characters mentioned above.
*
- * @return {string}
+ * @returns {string}
* Returns the resulting URL path.
*/
media() {
* @instance
* @memberof LuCI
*
- * @return {string}
+ * @returns {string}
* Returns the URL path to the current view.
*/
location() {
* @param {*} [val]
* The value to test
*
- * @return {boolean}
+ * @returns {boolean}
* Returns `true` if the given value is of a type object and
* not `null`, else returns `false`.
*/
* @param {*} [val]
* The value to test
*
- * @return {boolean}
+ * @returns {boolean}
* Returns `true` if the given value is a function arguments object,
* else returns `false`.
*/
* lexicographic sorting with a sorting suitable for IP/MAC style
* addresses or numeric values respectively.
*
- * @return {string[]}
+ * @returns {string[]}
* Returns an array containing the sorted keys of the given object.
*/
sortedKeys(obj, key, sortmode) {
* This function is meant to be used as a comparator function for
* Array.sort().
*
- * @type {function}
+ * @type {function()}
*
* @param {*} a
* The first value
* @param {*} b
* The second value.
*
- * @return {number}
+ * @returns {number}
* Returns -1 if the first value is smaller than the second one.
* Returns 0 if both values are equal.
* Returns 1 if the first value is larger than the second one.
* @param {*} val
* The input value to sort (and convert to an array if needed).
*
- * @return {Array<*>}
+ * @returns {Array<*>}
* Returns the resulting, numerically sorted array.
*/
sortedArray(val) {
* @param {*} val
* The value to convert into an array.
*
- * @return {Array<*>}
+ * @returns {Array<*>}
* Returns the resulting array.
*/
toArray(val) {
* @param {LuCI.requestCallbackFn} cb
* The callback function to invoke when the request finishes.
*
- * @return {Promise<null>}
+ * @returns {Promise<null>}
* Returns a promise resolving to `null` when concluded.
*/
get(url, args, cb) {
* @param {LuCI.requestCallbackFn} cb
* The callback function to invoke when the request finishes.
*
- * @return {Promise<null>}
+ * @returns {Promise<null>}
* Returns a promise resolving to `null` when concluded.
*/
post(url, args, cb) {
* an argument `token` with the current value of `LuCI.env.token` by
* default, regardless of the parameters specified with `args`.
*
- * @return {function}
+ * @returns {function()}
* Returns the internally created function that has been passed to
* {@link LuCI.request.poll#add Request.poll.add()}. This value can
* be passed to {@link LuCI.poll.remove Poll.remove()} to remove the
/**
* Check whether a view has sufficient permissions.
*
- * @return {boolean|null}
+ * @returns {boolean|null}
* Returns `null` if the current session has no permission at all to
* load resources required by the view. Returns `false` if readonly
* permissions are granted or `true` if at least one required ACL
* @instance
* @memberof LuCI
*
- * @param {function} entry
+ * @param {function()} entry
* The polling function to remove.
*
- * @return {boolean}
+ * @returns {boolean}
* Returns `true` when the function has been removed or `false` if
* it could not be found.
*/
* @instance
* @memberof LuCI
*
- * @return {boolean}
+ * @returns {boolean}
* Returns `true` when the polling loop has been stopped or `false`
* when it didn't run to begin with.
*/
* @instance
* @memberof LuCI
*
- * @return {boolean}
+ * @returns {boolean}
* Returns `true` when the polling loop has been started or `false`
* when it was already running.
*/
* @param {number} [timeout]
* Request timeout to use
*
- * @return {Promise<null>}
+ * @returns {Promise<null>}
*/
get(url, data, callback, timeout) {
this.active = true;
* @param {number} [timeout]
* Request timeout to use
*
- * @return {Promise<null>}
+ * @returns {Promise<null>}
*/
post(url, data, callback, timeout) {
this.active = true;
var Hosts, Network, Protocol, Device, WifiDevice, WifiNetwork, WifiVlan;
+/**
+ * @namespace LuCI.network
+ * @memberof LuCI
+ */
+
/**
* @class network
* @memberof LuCI
/**
* Converts the given prefix size in bits to a netmask.
*
- * @method
+ * @function
*
* @param {number} bits
* The prefix size in bits.
/**
* Converts the given netmask to a prefix size in bits.
*
- * @method
+ * @function
*
* @param {string} netmask
* The netmask to convert into a bits count.
* into a human readable string such as `mixed WPA/WPA2 PSK (TKIP, CCMP)`
* or `WPA3 SAE (CCMP)`.
*
- * @method
+ * @function
*
* @param {LuCI.network.WifiEncryption} encryption
* The wireless encryption entry to convert.
* with the given methods and returns the resulting subclass value.
*
* This function internally calls
- * {@link LuCI.Class.extend Class.extend()} on the `Network.Protocol`
+ * {@link LuCI.baseclass.extend baseclass.extend()} on the `Network.Protocol`
* base class.
*
* @param {string} protoname
*
* @param {Object<string, *>} methods
* The member methods and values of the new `Protocol` subclass to
- * be passed to {@link LuCI.Class.extend Class.extend()}.
+ * be passed to {@link LuCI.baseclass.extend baseclass.extend()}.
*
* @returns {LuCI.network.Protocol}
* Returns the new `Protocol` subclass.
* @param {null|string|string[]} val
* The value to set or `null` to remove the given option from the
* configuration.
+ * @returns {null}
*/
set: function(opt, val) {
return uci.set('network', this.sid, opt, val);
/**
* Get the name of this network protocol class.
*
- * This function will be overwritten by subclasses created by
+ * This function will be overridden by subclasses created by
* {@link LuCI.network#registerProtocol Network.registerProtocol()}.
*
* @abstract
* Return a human readable description for the protocol, such as
* `Static address` or `DHCP client`.
*
- * This function should be overwritten by subclasses.
+ * This function should be overridden by subclasses.
*
* @abstract
* @returns {string}
* package is available via the system default package manager. This is used
* when a config refers to a protocol handler which is not yet installed.
*
- * This function should be overwritten by protocol specific subclasses.
+ * This function should be overridden by protocol specific subclasses.
*
* @abstract
*
/**
* Check function for the protocol handler if a new interface is creatable.
*
- * This function should be overwritten by protocol specific subclasses.
+ * This function should be overridden by protocol specific subclasses.
*
* @abstract
*
* network device on startup, examples for non-virtual protocols are
* `dhcp` or `static` which apply IP configuration to existing interfaces.
*
- * This function should be overwritten by subclasses.
+ * This function should be overridden by subclasses.
*
* @returns {boolean}
* Returns a boolean indicating whether the underlying protocol spawns
* from the configuration and is responsible for performing any required
* cleanup tasks, such as unsetting uci entries in related configurations.
*
- * It should be overwritten by protocol specific subclasses.
+ * It should be overridden by protocol specific subclasses.
*
* @abstract
*
* @param {null|string|string[]} value
* The value to set or `null` to remove the given option from the
* configuration.
+ * @returns {null}
*/
set: function(opt, value) {
return uci.set('wireless', this.sid, opt, value);
* @param {null|string|string[]} value
* The value to set or `null` to remove the given option from the
* configuration.
+ * @returns {null}
*/
set: function(opt, value) {
return uci.set('wireless', this.sid, opt, value);
* @property {boolean} tdls
* Specifies whether TDLS is active.
*
- * @property {number} [mesh llid]
+ * @property {number} [mesh_llid]
* The mesh LLID, may be `0` or absent if not applicable or supported
* by the driver.
*
- * @property {number} [mesh plid]
+ * @property {number} [mesh_plid]
* The mesh PLID, may be `0` or absent if not applicable or supported
* by the driver.
*
- * @property {string} [mesh plink]
+ * @property {string} [mesh_plink]
* The mesh peer link state description, may be an empty string (`''`)
* or absent if not applicable or supported by the driver.
*
* - `BLOCKED`
* - `UNKNOWN`
*
- * @property {number} [mesh local PS]
+ * @property {number} [mesh_local_PS]
* The local power-save mode for the peer link, may be an empty
* string (`''`) or absent if not applicable or supported by
* the driver.
* - `DEEP SLEEP`
* - `UNKNOWN`
*
- * @property {number} [mesh peer PS]
+ * @property {number} [mesh_peer_PS]
* The remote power-save mode for the peer link, may be an empty
* string (`''`) or absent if not applicable or supported by
* the driver.
* - `DEEP SLEEP`
* - `UNKNOWN`
*
- * @property {number} [mesh non-peer PS]
+ * @property {number} [mesh_non_peer_PS]
* The power-save mode for all non-peer neighbours, may be an empty
* string (`''`) or absent if not applicable or supported by the driver.
*
* Calculate the current signal.
*
* @deprecated
+ * @param {number} signal
+ * @param {number} noise
* @returns {number}
* Returns the calculated signal level, which is the difference between
* noise and signal (SNR), divided by 5.
'require network';
'require validation';
+/**
+ * Determine whether a provided value is a CIDR format IP string.
+ * @param {string} value the IP string to test.
+ * @returns {boolean}
+ */
function isCIDR(value) {
return Array.isArray(value) || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/(\d{1,2}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.test(value);
}
+/**
+ * Calculate the broadcast IP for a given IP.
+ * @param {Node} s the ui element to test.
+ * @param {boolean} use_cfgvalue whether to use the config or form value.
+ * @returns {string}
+ */
function calculateBroadcast(s, use_cfgvalue) {
const readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue';
const addropt = s.children.find(o => o.option == 'ipaddr');
return bc.join('.');
}
+/**
+ * Validate a broadcast IP for a section value.
+ * @param {string} section_id
+ * @param {string} value
+ * @returns {boolean}
+ */
function validateBroadcast(section_id, value) {
var opt = this.map.lookupOption('broadcast', section_id),
node = opt ? this.map.findElement('id', opt[0].cbid(section_id)) : null,
let rpcBaseURL = L.url('admin/ubus');
const rpcInterceptorFns = [];
+/**
+ * @namespace LuCI.rpc
+ * @memberof LuCI
+ */
+
/**
* @class rpc
* @memberof LuCI
* - `list("objname", ...)`
* Returns method signatures for each given `ubus` object name.
*
- * @param {...string} [objectNames]
+ * @param {...string} [args] (objectNames)
* If any object names are given, this function will return the method
* signatures of each given object.
*
*
* Extraneous parameters passed to the generated function will not be
* sent to the remote procedure but are passed to the
- * {@link LuCI.rpc~filterFn filter function} if one is specified.
+ * {@link LuCI.rpc.filterFn filter function} if one is specified.
*
* Examples:
* - `params: [ "foo", "bar" ]` -
* `success` or if `reply.success` is not a boolean value, `false` will
* be returned as default instead.
*
- * @property {LuCI.rpc~filterFn} [filter]
+ * @property {LuCI.rpc.filterFn} [filter]
* Specifies an optional filter function which is invoked to transform the
* received reply data before it is returned to the caller.
*
* The filter function is invoked to transform a received `ubus` RPC call
* reply before returning it to the caller.
*
- * @callback LuCI.rpc~filterFn
+ * @callback LuCI.rpc.filterFn
*
* @param {*} data
* The received `ubus` reply data or a subset of it as described in the
* All extraneous arguments passed to the RPC method exceeding the number
* of arguments describes in the RPC call declaration.
*
- * @return {*}
+ * @returns {*}
* The return value of the filter function will be returned to the caller
* of the RPC method as-is.
*/
* using the arguments passed to it as arguments and return a promise
* resolving to the received reply values.
*
- * @callback LuCI.rpc~invokeFn
+ * @callback LuCI.rpc.invokeFn
*
* @param {...*} params
* The parameters to pass to the remote procedure call. The given
* `params` declaration are passed as private extra arguments to the
* declared filter function.
*
- * @return {Promise<*>}
+ * @returns {Promise<*>}
* Returns a promise resolving to the result data of the remote `ubus`
* RPC method invocation, optionally substituted and filtered according
* to the `expect` and `filter` declarations.
* If any object names are given, this function will return the method
* signatures of each given object.
*
- * @returns {LuCI.rpc~invokeFn}
+ * @returns {LuCI.rpc.invokeFn}
* Returns a new function implementing the method call described in
* `options`.
*/
* declaration object, allowing access to internals of the invocation
* function such as `filter`, `expect` or `params` values.
*
- * @return {Promise<*>|*}
+ * @returns {Promise<*>|*}
* Interceptor functions may return a promise to defer response
* processing until some delayed work completed. Any values the returned
* promise resolves to are ignored.
/**
* Registers a new interceptor function.
*
- * @param {LuCI.rpc~interceptorFn} interceptorFn
+ * @param {LuCI.rpc.interceptorFn} interceptorFn
* The interceptor function to register.
*
- * @returns {LuCI.rpc~interceptorFn}
+ * @returns {LuCI.rpc.interceptorFn}
* Returns the given function value.
*/
addInterceptor(interceptorFn) {
/**
* Removes a registered interceptor function.
*
- * @param {LuCI.rpc~interceptorFn} interceptorFn
+ * @param {LuCI.rpc.interceptorFn} interceptorFn
* The interceptor function to remove.
*
* @returns {boolean}
var s = [0x0000, 0x0000, 0x0000, 0x0000];
+/**
+ * Multiply two 64-bit values represented as arrays of four 16-bit words.
+ *
+ * Arrays use little-endian word order (least-significant 16-bit word first).
+ * The result is truncated to the lower 64 bits and returned as a 4-element
+ * array of 16-bit words.
+ *
+ * @param {Array<number>} a - Multiplicand (4 × 16-bit words, little-endian)
+ * @param {Array<number>} b - Multiplier (4 × 16-bit words, little-endian)
+ * @returns {Array<number>} Product as 4 × 16-bit words (little-endian)
+ */
function mul(a, b) {
var r = [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000];
return r;
}
+/**
+ * Add a small integer to a 64-bit value represented as four 16-bit words.
+ *
+ * Treats `a` as a little-endian 64-bit value (4 × 16-bit words). Adds the
+ * integer `n` to the least-significant word and propagates carry across
+ * subsequent 16-bit words. The result is truncated to 64 bits and returned
+ * as a 4-element array of 16-bit words (little-endian).
+ *
+ * @param {Array<number>} a - Addend as 4 × 16-bit words (little-endian)
+ * @param {number} n - Value to add (integer carry)
+ * @returns {Array<number>} Sum as 4 × 16-bit words (little-endian)
+ */
function add(a, n) {
var r = [0x0000, 0x0000, 0x0000, 0x0000],
k = n;
return r;
}
+/**
+ * Shift a 64-bit value (4 × 16-bit words, little-endian) right by `n` bits.
+ *
+ * The input array is treated as little-endian 16-bit words. Bits shifted out
+ * on the right are discarded; the returned array contains the lower 64-bit
+ * result after the logical right shift.
+ *
+ * @param {Array<number>} a - Source value as 4 × 16-bit words (little-endian)
+ * @param {number} n - Number of bits to shift right (non-negative integer)
+ * @returns {Array<number>} Shifted value as 4 × 16-bit words (little-endian)
+ */
function shr(a, n) {
var r = [a[0], a[1], a[2], a[3], 0x0000],
i = 4,
}
return L.Class.extend({
+ /**
+ * Seed the PRNG state.
+ *
+ * The seed is treated as a 32-bit integer; the lower 16 bits are stored
+ * in `s[0]`, the upper 16 bits in `s[1]`. `s[2]` and `s[3]` are zeroed.
+ *
+ * @param {number} n - Seed value (32-bit integer)
+ * @returns {void}
+ */
seed: function(n) {
n = (n - 1)|0;
s[0] = n & 0xffff;
s[3] = 0;
},
+ /**
+ * Produce the next PRNG 32-bit integer.
+ *
+ * Advances the internal state and returns a 32-bit pseudo-random integer
+ * derived from the current state.
+ *
+ * @returns {number} 32-bit pseudo-random integer (JS number)
+ */
int: function() {
s = mul(s, [0x7f2d, 0x4c95, 0xf42d, 0x5851]);
s = add(s, 1);
return (r[1] << 16) | r[0];
},
+ /**
+ * Return a pseudo-random value.
+ *
+ * Overloads:
+ * - get() -> number in [0, 1]
+ * - get(upper) -> integer in [1, upper]
+ * - get(lower, upper) -> integer in [lower, upper]
+ *
+ * @param {number} [lower=0] - Lower bound (when two args supplied)
+ * @param {number} [upper=0] - Upper bound (when one or two args supplied)
+ * @returns {number} Random value (float in [0,1] or integer in requested range)
+ */
get: function() {
var r = (this.int() % 0x7fffffff) / 0x7fffffff, l, u;
return Math.floor(r * (u - l + 1)) + l;
},
+ /**
+ * Derive a deterministic hex color from an input string.
+ *
+ * The color is produced by seeding the PRNG from a string-derived
+ * hash and producing RGB components. Returns a `#rrggbb` hex string.
+ *
+ * @param {string} string - Input string used to derive the color
+ * @returns {string} Hex color string in `#rrggbb` format
+ */
derive_color: function(string) {
this.seed(parseInt(sfh(string), 16));
'class': 'cbi-input',
});
+ /**
+ * Update the form when log filter parameters change
+ */
function handleLogFilterChange() {
self.logFacilityFilter = facilitySelect.value;
self.invertLogFacilitySearch = facilityInvert.checked;
'require firewall';
'require fs';
+
+/**
+ * Get users found in `/etc/passwd`.
+ * @returns {string[]}
+ */
function getUsers() {
return fs.lines('/etc/passwd').then(function(lines) {
return lines.map(function(line) { return line.split(/:/)[0] });
});
}
+/**
+ * Get users found in `/etc/group`.
+ * @returns {string[]}
+ */
function getGroups() {
return fs.lines('/etc/group').then(function(lines) {
return lines.map(function(line) { return line.split(/:/)[0] });
});
}
+/**
+ * Get bridge devices or Layer 3 devices of a network object.
+ * @param {object} network
+ * @returns {string[]}
+ */
function getDevices(network) {
if (network.isBridge()) {
var devices = network.getDevices();
'require rpc';
'require baseclass';
+/**
+ * Determine whether an object is empty.
+ * @param {object} object object to enumerate.
+ * @param {string} ignore property to ignore.
+ * @returns {boolean}
+ */
function isEmpty(object, ignore) {
for (const property in object)
if (object.hasOwnProperty(property) && property != ignore)
return true;
}
+/**
+ * @namespace LuCI.uci
+ * @memberof LuCI
+ */
+
/**
* @class uci
* @memberof LuCI
}
},
+ /* eslint-disable jsdoc/check-property-names */
/**
* A section object represents the options and their corresponding values
* enclosed within a configuration section, as well as some additional
* @typedef {Object<string, boolean|number|string|string[]>} SectionObject
* @memberof LuCI.uci
*
- * @property {boolean} .anonymous
+ * @property {boolean} ".anonymous"
* The `.anonymous` property specifies whether the configuration is
* anonymous (`true`) or named (`false`).
*
- * @property {number} .index
+ * @property {number} ".index"
* The `.index` property specifies the sort order of the section.
*
- * @property {string} .name
+ * @property {string} ".name"
* The `.name` property holds the name of the section object. It may be
* either an anonymous ID in the form `cfgXXXXXX` or `newXXXXXX` with `X`
* being a hexadecimal digit or a string holding the name of the section.
*
- * @property {string} .type
+ * @property {string} ".type"
* The `.type` property contains the type of the corresponding uci
* section.
*
* either contain a string value or an array of strings, in case the
* underlying option is an UCI list.
*/
+ /* eslint-enable jsdoc/check-property-names */
/**
* The sections callback is invoked for each section found within
* the given configuration and receives the section object and its
* associated name as arguments.
*
- * @callback LuCI.uci~sectionsFn
+ * @callback LuCI.uci.sections
*
* @param {LuCI.uci.SectionObject} section
* The section object.
* Enumerate only sections of the given type. If omitted, enumerate
* all sections.
*
- * @param {LuCI.uci~sectionsFn} [cb]
+ * @param {LuCI.uci.sections} [cb]
* An optional callback to invoke for each enumerated section.
*
* @returns {Array<LuCI.uci.SectionObject>}
*
* @param {string} opt
* The name of the option to remove.
+ * @returns {null}
*/
unset(conf, sid, opt) {
return this.set(conf, sid, opt, null);
* @param {string} conf
* The name of the configuration to read.
*
- * @param {string} sid
- * The name or ID of the section to read.
+ * @param {string} type
+ * The section type to read.
*
* @param {string} [opt]
* The option name from which to read the value. If the option
* The option value to set. If the value is `null` or an empty string,
* the option will be removed, otherwise it will be set or overwritten
* with the given value.
+ * @returns {null}
*/
set_first(conf, type, opt, val) {
let sid = null;
*
* @param {string} opt
* The option name to set the value for.
+ *
+ * @returns {null}
*/
unset_first(conf, type, opt) {
return this.set_first(conf, type, opt, null);
* @typedef {string[]} ChangeRecord
* @memberof LuCI.uci
*
- * @property {string} 0
+ * @property {string} operation
* The operation name - may be one of `add`, `set`, `remove`, `order`,
* `list-add`, `list-del` or `rename`.
*
- * @property {string} 1
+ * @property {string} section_id
* The section ID targeted by the operation.
*
- * @property {string} 2
+ * @property {string} parameter
* The meaning of the third element depends on the operation.
* - For `add` it is type of the section that has been added
* - For `set` it either is the option name if a fourth element exists,
* a section has been renamed to if the change entry only contains
* three elements.
*
- * @property {string} 4
+ * @property {string=} value
* The meaning of the fourth element depends on the operation.
* - For `set` it is the value an option has been set to.
* - For `list-add` it is the new value that has been added to a
/**
* Fetches uncommitted UCI changes from the remote `ubus` RPC api.
*
- * @method
+ * @function
* @returns {Promise<Object<string, Array<LuCI.uci.ChangeRecord>>>}
* Returns a promise resolving to an object containing the configuration
* names as keys and arrays of related change records as values.
let indicatorDiv = null;
let tooltipTimeout = null;
+/**
+ * @namespace LuCI.ui
+ * @memberof LuCI
+ */
+
/**
* @class AbstractElement
* @memberof LuCI.ui
* It defaults to `string` which will allow any value.
* See {@link LuCI.validation} for details on the expression format.
*
- * @property {function|function[]} [validator]
+ * @property {function()|Array<function()>} [validator]
* Specifies one or more custom validator functions which are invoked after
* the standard validation constraints are checked. Each function should
* return `true` to accept the given input value. When multiple functions
*
* @instance
* @memberof LuCI.ui.AbstractElement
+ * @returns {boolean}
*/
triggerValidation() {
if (typeof(this.vfunc) != 'function')
/**
* Render the widget, set up event listeners and return resulting markup.
- *
+ * @abstract
* @instance
* @memberof LuCI.ui.AbstractElement
*
/**
* Instantiate a text input widget.
*
- * @constructor Textfield
+ * @class Textfield
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.Textfield
+ * @param {string} value
+ * @param {object} options
*
* @property {boolean} [password=false]
* Specifies whether the input should be rendered as concealed password field.
return this.bind(frameEl);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} frameEl
+ * @returns {Node}
+ */
bind(frameEl) {
const inputEl = frameEl.querySelector('input');
/**
* Instantiate a textarea widget.
*
- * @constructor Textarea
+ * @class Textarea
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.Textarea
+ * @param {string} value
+ * @param {object} options
*
* @property {boolean} [readonly=false]
* Specifies whether the input widget should be rendered readonly.
return this.bind(frameEl);
},
- /** @private */
+
+ /**
+ * @private
+ * @param {Node} frameEl
+ * @returns {Node}
+ */
bind(frameEl) {
const inputEl = frameEl.firstElementChild;
/**
* Instantiate a checkbox widget.
*
- * @constructor Checkbox
+ * @class Checkbox
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.Checkbox
+ * @param {string} value
+ * @param {object} options
*
* @property {string} [value_enabled=1]
* Specifies the value corresponding to a checked checkbox.
return this.bind(frameEl);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} frameEl
+ * @returns {Node}
+ */
bind(frameEl) {
this.node = frameEl;
/**
* Instantiate a select dropdown or checkbox/radiobutton group.
*
- * @constructor Select
+ * @class Select
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.Select
+ * @param {string} value
+ * @param {object} choices
+ * @param {object} options
*
* @property {boolean} [multiple=false]
* Specifies whether multiple choice values may be selected.
return this.bind(frameEl);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} frameEl
+ * @returns {Node}
+ */
bind(frameEl) {
this.node = frameEl;
/**
* Instantiate a rich dropdown choice widget.
*
- * @constructor Dropdown
+ * @class Dropdown
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.Dropdown
+ * @param {string} value
+ * @param {object} choices
+ * @param {object} options
*
* @property {boolean} [optional=true]
* Specifies whether the dropdown selection is optional. In contrast to
* which is used to enter custom choice values. This should not normally
* be used except by widgets derived from the Dropdown class.
*
- * @property {string} [create_template=script[type="item-template"]]
+ * @property {string} [create_template="script[type='item-template']"]
* Specifies a CSS selector expression used to find an HTML element
* serving as template for newly added custom choice values.
*
return this.bind(sb);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @returns {Node}
+ */
bind(sb) {
const o = this.options;
return sb;
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} element
+ * @returns {document}
+ */
getScrollParent(element) {
let parent = element;
let style = getComputedStyle(element);
return document.body;
},
- /** @private */
+
+ /**
+ * @private
+ * @param {Node} sb
+ */
openDropdown(sb) {
const st = window.getComputedStyle(sb, null);
const ul = sb.querySelector('ul');
ul.addEventListener('transitionend', focusFn);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {boolean} no_focus
+ */
closeDropdown(sb, no_focus) {
if (!sb.hasAttribute('open'))
return;
this.saveValues(sb, ul);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {Node} li list item
+ * @param {boolean} force_state
+ */
toggleItem(sb, li, force_state) {
const ul = li.parentNode;
this.saveValues(sb, ul);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {Node} li list item
+ */
transformItem(sb, li) {
const cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' }));
const label = E('label');
li.appendChild(label);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {Node} ul unordered list
+ */
saveValues(sb, ul) {
const sel = ul.querySelectorAll('li[selected]');
const div = sb.lastElementChild;
}));
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {string[]} values
+ */
setValues(sb, values) {
const ul = sb.querySelector('ul');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {Node} elem
+ * @param {boolean} scroll
+ */
setFocus(sb, elem, scroll) {
if (sb.hasAttribute('locked-in'))
return;
elem.focus();
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleMouseout(ev) {
const sb = ev.currentTarget;
sb.querySelector('ul.dropdown').focus();
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {string} value
+ * @param {string} label
+ * @returns {Node}
+ */
createChoiceElement(sb, value, label) {
const tpl = sb.querySelector(this.options.create_template);
let markup = null;
return new_item;
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {string} value
+ */
createItems(sb, value) {
const sbox = this;
let val = (value ?? '').trim();
});
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleClick(ev) {
const sb = ev.currentTarget;
ev.stopPropagation();
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleKeydown(ev) {
const sb = ev.currentTarget;
const ul = sb.querySelector('ul.dropdown');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleDropdownClose(ev) {
const sb = ev.currentTarget;
this.closeDropdown(sb, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleDropdownSelect(ev) {
const sb = ev.currentTarget;
const li = findParent(ev.target, 'li');
this.closeDropdown(sb, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleMouseover(ev) {
const sb = ev.currentTarget;
this.setFocus(sb, li);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleFocus(ev) {
const sb = ev.currentTarget;
});
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleCanaryFocus(ev) {
this.closeDropdown(ev.currentTarget.parentNode);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleCreateKeydown(ev) {
const input = ev.currentTarget;
const li = findParent(input, 'li');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleCreateFocus(ev) {
const input = ev.currentTarget;
const li = findParent(input, 'li');
this.setFocus(sb, li, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleCreateBlur(ev) {
const input = ev.currentTarget;
const cbox = findParent(input, 'li').querySelector('input[type="checkbox"]');
sb.removeAttribute('locked-in');
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleCreateClick(ev) {
ev.currentTarget.querySelector(this.options.create_query).focus();
},
/**
* Instantiate a rich dropdown choice widget allowing custom values.
*
- * @constructor Combobox
+ * @class Combobox
* @memberof LuCI.ui
* @augments LuCI.ui.Dropdown
*
*
* @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
* @memberof LuCI.ui.Combobox
+ * @param {string} value
+ * @param {object} choices
+ * @param {object} options
*
- * @property {boolean} multiple=false
+ * @property {boolean} [multiple=false]
* Since Comboboxes never allow selecting multiple values, this property
* is forcibly set to `false`.
*
- * @property {boolean} create=true
+ * @property {boolean} [create=true]
* Since Comboboxes always allow custom choice values, this property is
* forcibly set to `true`.
*
- * @property {boolean} optional=true
+ * @property {boolean} [optional=true]
* Since Comboboxes are always optional, this property is forcibly set to
* `true`.
*/
/**
* Instantiate a combo button widget offering multiple action choices.
*
- * @constructor ComboButton
+ * @class ComboButton
* @memberof LuCI.ui
* @augments LuCI.ui.Dropdown
*
*
* @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
* @memberof LuCI.ui.ComboButton
+ * @param {string} value
+ * @param {object} choices
+ * @param {object} options
*
- * @property {boolean} multiple=false
+ * @property {boolean} [multiple=false]
* Since ComboButtons never allow selecting multiple actions, this property
* is forcibly set to `false`.
*
- * @property {boolean} create=false
+ * @property {boolean} [create=false]
* Since ComboButtons never allow creating custom choices, this property
* is forcibly set to `false`.
*
- * @property {boolean} optional=false
+ * @property {boolean} [optional=false]
* Since ComboButtons must always select one action, this property is
* forcibly set to `false`.
*
* This is useful to apply different button styles, such as colors, to the
* combined button depending on the selected action.
*
- * @property {function} [click]
+ * @property {function()} [click]
* Specifies a handler function to invoke when the user clicks the button.
* This function will be called with the button DOM node as `this` context
* and receive the DOM click event as first as well as the selected action
return node;
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {...*} args
+ * @returns {null}
+ */
handleClick(ev, ...args) {
const sb = ev.currentTarget;
const t = ev.target;
return this.options.click.call(sb, ev, this.getValue());
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} sb
+ * @param {...*} args
+ * @returns {*}
+ */
toggleItem(sb, ...args) {
const rv = UIDropdown.prototype.toggleItem.call(this, sb, ...args);
const val = this.getValue();
/**
* Instantiate a dynamic list widget.
*
- * @constructor DynamicList
+ * @class DynamicList
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
* @memberof LuCI.ui.DynamicList
+ * @param {string} values
+ * @param {object} choices
+ * @param {object} options
*
- * @property {boolean} multiple=false
+ * @property {boolean} [multiple=false]
* Since dynamic lists never allow selecting multiple choices when adding
* another list item, this property is forcibly set to `false`.
*
- * @property {boolean} optional=true
+ * @property {boolean} [optional=true]
* Since dynamic lists use an embedded dropdown to present a list of
* predefined choice values, the dropdown must be made optional to allow
* it to remain unselected.
return this.bind(dl);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} dl
+ */
initDragAndDrop(dl) {
let draggedItem = null;
let placeholder = null;
});
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} dl
+ * @returns {Node}
+ */
bind(dl) {
dl.addEventListener('click', L.bind(this.handleClick, this));
dl.addEventListener('keydown', L.bind(this.handleKeydown, this));
return dl;
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} dl
+ * @param {string} value
+ * @param {string} text
+ * @param {boolean} flash
+ */
addItem(dl, value, text, flash) {
let exists = false;
this.dispatchCbiDynlistChange(dl,value);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} dl
+ * @param {string} value
+ */
dispatchCbiDynlistChange(dl,value) {
dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
bubbles: true,
}));
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} dl
+ * @param {Node} item
+ */
removeItem(dl, item) {
const value = item.querySelector('input[type="hidden"]').value;
const sb = dl.querySelector('.cbi-dropdown');
this.dispatchCbiDynlistChange(dl, value);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleClick(ev) {
const dl = ev.currentTarget;
const item = findParent(ev.target, '.item');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleDropdownChange(ev) {
const dl = ev.currentTarget;
const sbIn = ev.detail.instance;
this.addItem(dl, sbVal.value, label, true);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleKeydown(ev) {
const dl = ev.currentTarget;
const item = findParent(ev.target, '.item');
/**
* Instantiate a range slider widget.
*
- * @constructor RangeSlider
+ * @class RangeSlider
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
* @param {LuCI.ui.RangeSlider.InitOptions} [options]
* Object describing the widget specific options to initialize the range slider.
*/
-const UIRangeSlider = UIElement.extend({
+const UIRangeSlider = UIElement.extend( /** @lends LuCI.ui.RangeSlider.prototype */ {
/**
* In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
* the following properties are recognized:
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.RangeSlider
+ * @param {string} value
+ * @param {object} options
*
- * @property {int} [min=1]
+ * @property {number} [min=1]
* Specifies the minimum value of the range.
*
- * @property {int} [max=100]
+ * @property {number} [max=100]
* Specifies the maximum value of the range.
*
* @property {string} [step=1]
* Specifies the step value of the range slider handle. Use "any" for
* arbitrary precision floating point numbers.
*
- * @param {function} [calculate=null]
+ * @param {function()} [calculate=null]
* A function to invoke when the slider is adjusted by the user. The function
* performs a calculation on the selected value to produce a new value.
*
*
* @instance
* @memberof LuCI.ui.RangeSlider
+ * @returns {number}
*/
getCalculatedValue() {
return this.calculatedvalue;
/**
* Instantiate a hidden input field widget.
*
- * @constructor Hiddenfield
+ * @class Hiddenfield
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
return this.bind(hiddenEl);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} hiddenEl
+ * @returns {Node} hiddenEl
+ */
bind(hiddenEl) {
this.node = hiddenEl;
/**
* Instantiate a file upload widget.
*
- * @constructor FileUpload
+ * @class FileUpload
* @memberof LuCI.ui
* @augments LuCI.ui.AbstractElement
*
*
* @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
* @memberof LuCI.ui.FileUpload
+ * @param {string} value
+ * @param {object} options
*
* @property {boolean} [browser=false]
* Use a file browser mode.
}, options);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} browserEl
+ * @returns {Node} hiddenEl
+ */
bind(browserEl) {
this.node = browserEl;
return renderFileBrowser
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @returns {string}
+ */
truncatePath(path) {
if (path.length > 50)
path = `${path.substring(0, 25)}…${path.substring(path.length - 25)}`;
return path;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} type
+ * @returns {Node}
+ */
iconForType(type) {
switch (type) {
case 'symlink':
}
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @returns {string}
+ */
canonicalizePath(path) {
return path.replace(/\/{2,}/g, '/') // Collapse multiple slashes
.replace(/\/\.(\/|$)/g, '/') // Remove `/.`
.replace(/\/$/g, (m, o, s) => s.length > 1 ? '' : '/'); // Remove trailing `/` only if not root
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @returns {string[]}
+ */
splitPath(path) {
const croot = this.canonicalizePath(this.options.root_directory ?? '/');
const cpath = this.canonicalizePath(path ?? '/');
return parts;
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @param {Event} ev
+ */
handleCreateDirectory(path, ev) {
const container = E('div', { 'class': 'uci-dialog' });
]);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @param {object[]} list
+ * @param {Event} ev
+ * @returns {Promise}
+ */
handleUpload(path, list, ev) {
const form = ev.target.parentNode;
const fileinput = form.querySelector('input[type="file"]');
}, this, path, ev));
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @param {object} fileStat
+ * @param {Event} ev
+ * @returns {Promise}
+ */
handleDelete(path, fileStat, ev) {
const parent = path.replace(/\/[^\/]+$/, '') ?? '/';
const name = path.replace(/^.+\//, '');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @param {object[]} list
+ * @returns {Promise}
+ */
renderUpload(path, list) {
if (!this.options.enable_upload)
return E([]);
]);
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} container
+ * @param {string} path
+ * @param {object[]} list
+ */
renderListing(container, path, list) {
const breadcrumb = E('p');
const rows = E('ul');
]);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleCancel(ev) {
const button = this.node.firstElementChild;
const browser = button.nextElementSibling;
ev.preventDefault();
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleReset(ev) {
const button = this.node.firstElementChild;
const hidden = this.node.lastElementChild;
this.handleCancel(ev);
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @param {object} fileStat
+ * @param {Event} ev
+ */
handleDownload(path, fileStat, ev) {
fs.read_direct(path, 'blob').then((blob) => {
const url = window.URL.createObjectURL(blob);
});
},
- /** @private */
+ /**
+ * @private
+ * @param {string} path
+ * @param {object} fileStat
+ * @param {Event} ev
+ */
handleSelect(path, fileStat, ev) {
const browser = dom.parent(ev.target, '.cbi-filebrowser');
const ul = browser.querySelector('ul');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @returns {Promise}
+ */
handleFileBrowser(ev) {
const button = ev.target;
const browser = button.nextElementSibling;
}
});
-
+/**
+ * Erase the menu node
+ * @param {Node} node
+ * @returns {Node}
+ */
function scrubMenu(node) {
let hasSatisfiedChild = false;
/**
* Handle menu.
*
- * @constructor menu
+ * @class menu
* @memberof LuCI.ui
*
* @classdesc
/**
* @typedef {Object} MenuNode
* @memberof LuCI.ui.menu
-
- * @property {string} name - The internal name of the node, as used in the URL
+ * @property {string} name - The internal name of the node, as used in the
+ * URL.
* @property {number} order - The sort index of the menu node
- * @property {string} [title] - The title of the menu node, `null` if the node should be hidden
- * @property {satisfied} boolean - Boolean indicating whether the menu entries dependencies are satisfied
- * @property {readonly} [boolean] - Boolean indicating whether the menu entries underlying ACLs are readonly
- * @property {LuCI.ui.menu.MenuNode[]} [children] - Array of child menu nodes.
+ * @property {string} [title] - The title of the menu node, `null` if the
+ * node should be hidden.
+ * @property {boolean} satisfied - Boolean indicating whether the menu
+ * entries dependencies are satisfied.
+ * @property {boolean} [readonly] - Boolean indicating whether the menu
+ * entries underlying ACLs are readonly.
+ * @property {LuCI.ui.menu.MenuNode[]} [children] - Array of child menu
+ * nodes.
*/
/**
return this.node;
},
- /** @private */
+ /**
+ * @private
+ * @param {Node} node
+ */
initFromMarkup(node) {
if (!dom.elem(node))
node = document.querySelector(node);
this.options = options;
},
- /** @private */
- deriveSortKey(value, index) {
+ /**
+ * @private
+ * @param {string} value
+ * @param {number} index
+ * @returns {string}
+ */
+ deriveSortKey(value, index) {
const opts = this.options ?? {};
let hint;
let m;
}
},
- /** @private */
+ /**
+ * @private
+ * @returns {?string}
+ */
getActiveSortState() {
if (this.sortState)
return this.sortState;
return null;
},
- /** @private */
+ /**
+ * @private
+ * @param {number} index
+ * @param {boolean} descending
+ */
setActiveSortState(index, descending) {
this.sortState = [ index, descending ];
session.setLocalData('tablesort', state);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
handleSort(ev) {
if (!ev.target.matches('th[data-sortable-row]'))
return;
modalDiv.blur();
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
cancelModal(ev) {
if (ev.key == 'Escape') {
const btn = modalDiv.querySelector('.right > button, .right > .btn, .button-row > .btn');
}
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
showTooltip(ev) {
const target = findParent(ev.target, '[data-tooltip]');
}));
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
hideTooltip(ev) {
if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv ||
tooltipDiv.contains(ev.target) || tooltipDiv.contains(ev.relatedTarget))
* to the `dom.content()` function - refer to its documentation for
* applicable values.
*
- * @param {int} [timeout]
+ * @param {number} [timeout]
* A millisecond value after which the notification will disappear
* automatically. If omitted, the notification will remain until it receives
* the click event.
addTimeLimitedNotification(title, children, timeout, ...classes) {
const msg = this.addNotification(title, children, ...classes);
+ /**
+ * Set node parameters to fade the notification out after timeout.
+ * @param {Element} element
+ */
function fadeOutNotification(element) {
if (element) {
element.classList.add('fade-out');
* @param {string} label
* The text to display in the indicator label.
*
- * @param {function} [handler]
+ * @param {function()} [handler]
* A handler function to invoke when the indicator label is clicked/touched
* by the user. If omitted, the indicator is not clickable/touchable.
*
return dom.isEmpty(pane, n => n.classList.contains('cbi-tab-descr'));
},
- /** @private */
+ /**
+ * @private
+ * @param {object} pane
+ * @returns {string}
+ */
getPathForPane(pane) {
const path = [];
let node = null;
return path.join('/');
},
- /** @private */
+ /**
+ * @private
+ * @returns {object}
+ */
getActiveTabState() {
const page = document.body.getAttribute('data-page');
const state = session.getLocalData('tab');
return { page: page, paths: {} };
},
- /** @private */
+ /**
+ * @private
+ * @param {object} pane
+ * @returns {string|0}
+ */
getActiveTabId(pane) {
const path = this.getPathForPane(pane);
return +this.getActiveTabState().paths[path] ?? 0;
},
- /** @private */
+ /**
+ * @private
+ * @param {object} pane
+ * @param {number} tabIndex
+ * @returns {object}
+ */
setActiveTabId(pane, tabIndex) {
const path = this.getPathForPane(pane);
const state = this.getActiveTabState();
return session.setLocalData('tab', state);
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ * @param {document} root
+ */
updateTabs(ev, root) {
(root ?? document).querySelectorAll('[data-tab-title]').forEach(L.bind((pane) => {
const menu = pane.parentNode.previousElementSibling;
}, this));
},
- /** @private */
+ /**
+ * @private
+ * @param {Event} ev
+ */
switchTab(ev) {
const tab = ev.target.parentNode;
const name = tab.getAttribute('data-tab');
/**
* @typedef {Object} FileUploadReply
* @memberof LuCI.ui
-
* @property {string} name - Name of the uploaded file without directory components
* @property {number} size - Size of the uploaded file in bytes
* @property {string} checksum - The MD5 checksum of the received file data
dom.content(body, [
E('ul', {}, [
- E('li', {}, [ '%s: %s'.format(_('Name'), file.name.replace(/^.*[\\\/]/, '')) ]),
+ E('li', {}, [ '%s: %s'.format(_('Name'), file.name.replace(/^.*[\\/]/, '')) ]),
E('li', {}, [ '%s: %1024mB'.format(_('Size'), file.size) ])
])
]);
dlg.classList.add('uci-dialog');
},
- /** @private */
+ /**
+ * @private
+ * @param {string} type
+ * @param {string} content
+ */
displayStatus(type, content) {
if (type) {
const message = UI.prototype.showModal('', '');
}
},
- /** @private */
+ /**
+ * @private
+ * @returns {Promise}
+ */
checkConnectivityAffected() {
return L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr', null, 'json')).then(L.bind((info) => {
if (L.isObject(info) && Array.isArray(info.inbound_interfaces)) {
}, this));
},
- /** @private */
+ /**
+ * @private
+ * @param {boolean} checked
+ */
rollback(checked) {
if (checked) {
this.displayStatus('warning spinning',
}
},
- /** @private */
+ /**
+ * @private
+ * @param {boolean} checked
+ * @param {number} deadline
+ * @param {string} override_token
+ */
confirm(checked, deadline, override_token) {
let tt;
let ts = Date.now();
* @param {boolean} [checked=false]
* Whether to perform a checked (`true`) configuration apply or an
* unchecked (`false`) one.
-
+ *
* In case of a checked apply, the configuration changes must be
* confirmed within a specific time interval, otherwise the device
* will begin to roll back the changes in order to restore the previous
* If an input element is not marked optional it must not be empty,
* otherwise it will be marked as invalid.
*
- * @param {function|function[]} [vfunc]
+ * @param {function()|Array<function()>} [vfunc]
* Specifies a custom validation function or an array of validation functions
* which are invoked after the other validation constraints are applied. Each
* function must return `true` to accept the passed value. When multiple
* validation. If omitted, the `keyup` and `blur` events are bound by
* default.
*
- * @returns {function}
+ * @returns {function()}
* Returns the compiled validator function which can be used to trigger
* field validation manually or to bind it to further events.
*
* @param {*} ctx
* The `this` context to use for the wrapped function.
*
- * @param {function|string} fn
+ * @param {function()|string} fn
* Specifies the function to wrap. In case of a function value, the
* function is used as-is. If a string is specified instead, it is looked
* up in `ctx` to obtain the function to wrap. In both cases the bound
* function will be invoked with `ctx` as `this` context
*
- * @param {...*} extra_args
+ * @param {...*} args
* Any further parameter as passed as-is to the bound event handler
* function in the same order as passed to `createHandlerFn()`.
*
- * @returns {function|null}
+ * @returns {?function()}
* Returns the pre-bound handler function which is suitable to be passed
* to `addEventListener()`. Returns `null` if the given `fn` argument is
* a string which could not be found in `ctx` or if `ctx[fn]` is not a