luci/modules/luci-base/htdocs/luci-static/resources/form.js
Jo-Philipp Wich dc2b38cb6f luci-base: {ui,form}.js: allow passing additional CSS classes to modals
Add the ability to pass additional CSS classes to modal dialogs and
make use of this facility in form.js to annotate CBI map modals.

This can be used later by themes to apply additional CSS rules.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
2019-07-07 15:36:26 +02:00

1676 lines
42 KiB
JavaScript

'use strict';
'require ui';
'require uci';
var scope = this;
var CBINode = Class.extend({
__init__: function(title, description) {
this.title = title || '';
this.description = description || '';
this.children = [];
},
append: function(obj) {
this.children.push(obj);
},
parse: function() {
var args = arguments;
this.children.forEach(function(child) {
child.parse.apply(child, args);
});
},
render: function() {
L.error('InternalError', 'Not implemented');
},
loadChildren: function(/* ... */) {
var tasks = [];
if (Array.isArray(this.children))
for (var i = 0; i < this.children.length; i++)
if (!this.children[i].disable)
tasks.push(this.children[i].load.apply(this.children[i], arguments));
return Promise.all(tasks);
},
renderChildren: function(tab_name /*, ... */) {
var tasks = [],
index = 0;
if (Array.isArray(this.children))
for (var i = 0; i < this.children.length; i++)
if (tab_name === null || this.children[i].tab === tab_name)
if (!this.children[i].disable)
tasks.push(this.children[i].render.apply(
this.children[i], this.varargs(arguments, 1, index++)));
return Promise.all(tasks);
},
stripTags: function(s) {
if (!s.match(/[<>]/))
return s;
var x = E('div', {}, s);
return x.textContent || x.innerText || '';
}
});
var CBIMap = CBINode.extend({
__init__: function(config /*, ... */) {
this.super('__init__', this.varargs(arguments, 1));
this.config = config;
this.parsechain = [ config ];
},
findElements: function(/* ... */) {
var q = null;
if (arguments.length == 1)
q = arguments[0];
else if (arguments.length == 2)
q = '[%s="%s"]'.format(arguments[0], arguments[1]);
else
L.error('InternalError', 'Expecting one or two arguments to findElements()');
return this.root.querySelectorAll(q);
},
findElement: function(/* ... */) {
var res = this.findElements.apply(this, arguments);
return res.length ? res[0] : null;
},
chain: function(config) {
if (this.parsechain.indexOf(config) == -1)
this.parsechain.push(config);
},
section: function(cbiClass /*, ... */) {
if (!CBIAbstractSection.isSubclass(cbiClass))
L.error('TypeError', 'Class must be a descendent of CBIAbstractSection');
var obj = cbiClass.instantiate(this.varargs(arguments, 1, this));
this.append(obj);
return obj;
},
load: function() {
return uci.load(this.parsechain || [ this.config ])
.then(this.loadChildren.bind(this));
},
parse: function() {
var tasks = [];
if (Array.isArray(this.children))
for (var i = 0; i < this.children.length; i++)
tasks.push(this.children[i].parse());
return Promise.all(tasks);
},
save: function() {
this.checkDepends();
return this.parse()
.then(uci.save.bind(uci))
.then(this.load.bind(this))
.then(this.renderContents.bind(this))
.catch(function(e) {
alert('Cannot save due to invalid values')
return Promise.reject();
});
},
reset: function() {
return this.renderContents();
},
render: function() {
return this.load().then(this.renderContents.bind(this));
},
renderContents: function() {
var mapEl = this.root || (this.root = E('div', {
'id': 'cbi-%s'.format(this.config),
'class': 'cbi-map',
'cbi-dependency-check': L.bind(this.checkDepends, this)
}));
L.dom.bindClassInstance(mapEl, this);
return this.renderChildren(null).then(L.bind(function(nodes) {
var initialRender = !mapEl.firstChild;
L.dom.content(mapEl, null);
if (this.title != null && this.title != '')
mapEl.appendChild(E('h2', { 'name': 'content' }, this.title));
if (this.description != null && this.description != '')
mapEl.appendChild(E('div', { 'class': 'cbi-map-descr' }, this.description));
L.dom.append(mapEl, nodes);
if (!initialRender) {
mapEl.classList.remove('flash');
window.setTimeout(function() {
mapEl.classList.add('flash');
}, 1);
}
this.checkDepends();
return mapEl;
}, this));
},
lookupOption: function(name, section_id) {
var id, elem, sid, inst;
if (name.indexOf('.') > -1)
id = 'cbid.%s'.format(name);
else
id = 'cbid.%s.%s.%s'.format(this.config, section_id, name);
elem = this.findElement('data-field', id);
sid = elem ? id.split(/\./)[2] : null;
inst = elem ? L.dom.findClassInstance(elem) : null;
return (inst instanceof CBIAbstractValue) ? [ inst, sid ] : null;
},
checkDepends: function(ev, n) {
var changed = false;
for (var i = 0, s = this.children[0]; (s = this.children[i]) != null; i++)
if (s.checkDepends(ev, n))
changed = true;
if (changed && (n || 0) < 10)
this.checkDepends(ev, (n || 10) + 1);
}
});
var CBIAbstractSection = CBINode.extend({
__init__: function(map, sectionType /*, ... */) {
this.super('__init__', this.varargs(arguments, 2));
this.sectiontype = sectionType;
this.map = map;
this.config = map.config;
this.optional = true;
this.addremove = false;
this.dynamic = false;
},
cfgsections: function() {
L.error('InternalError', 'Not implemented');
},
filter: function(section_id) {
return true;
},
load: function() {
var section_ids = this.cfgsections(),
tasks = [];
if (Array.isArray(this.children))
for (var i = 0; i < section_ids.length; i++)
tasks.push(this.loadChildren(section_ids[i])
.then(Function.prototype.bind.call(function(section_id, set_values) {
for (var i = 0; i < set_values.length; i++)
this.children[i].cfgvalue(section_id, set_values[i]);
}, this, section_ids[i])));
return Promise.all(tasks);
},
parse: function() {
var section_ids = this.cfgsections(),
tasks = [];
if (Array.isArray(this.children))
for (var i = 0; i < section_ids.length; i++)
for (var j = 0; j < this.children.length; j++)
tasks.push(this.children[j].parse(section_ids[i]));
return Promise.all(tasks);
},
tab: function(name, title, description) {
if (this.tabs && this.tabs[name])
throw 'Tab already declared';
var entry = {
name: name,
title: title,
description: description,
children: []
};
this.tabs = this.tabs || [];
this.tabs.push(entry);
this.tabs[name] = entry;
this.tab_names = this.tab_names || [];
this.tab_names.push(name);
},
option: function(cbiClass /*, ... */) {
if (!CBIAbstractValue.isSubclass(cbiClass))
throw L.error('TypeError', 'Class must be a descendent of CBIAbstractValue');
var obj = cbiClass.instantiate(this.varargs(arguments, 1, this.map, this));
this.append(obj);
return obj;
},
taboption: function(tabName /*, ... */) {
if (!this.tabs || !this.tabs[tabName])
throw L.error('ReferenceError', 'Associated tab not declared');
var obj = this.option.apply(this, this.varargs(arguments, 1));
obj.tab = tabName;
this.tabs[tabName].children.push(obj);
return obj;
},
renderUCISection: function(section_id) {
var renderTasks = [];
if (!this.tabs)
return this.renderOptions(null, section_id);
for (var i = 0; i < this.tab_names.length; i++)
renderTasks.push(this.renderOptions(this.tab_names[i], section_id));
return Promise.all(renderTasks)
.then(this.renderTabContainers.bind(this, section_id));
},
renderTabContainers: function(section_id, nodes) {
var config_name = this.uciconfig || this.map.config,
containerEls = E([]);
for (var i = 0; i < nodes.length; i++) {
var tab_name = this.tab_names[i],
tab_data = this.tabs[tab_name],
containerEl = E('div', {
'id': 'container.%s.%s.%s'.format(config_name, section_id, tab_name),
'data-tab': tab_name,
'data-tab-title': tab_data.title,
'data-tab-active': tab_name === this.selected_tab
});
if (tab_data.description != null && tab_data.description != '')
containerEl.appendChild(
E('div', { 'class': 'cbi-tab-descr' }, tab_data.description));
containerEl.appendChild(nodes[i]);
containerEls.appendChild(containerEl);
}
return containerEls;
},
renderOptions: function(tab_name, section_id) {
var in_table = (this instanceof CBITableSection);
return this.renderChildren(tab_name, section_id, in_table).then(function(nodes) {
var optionEls = E([]);
for (var i = 0; i < nodes.length; i++)
optionEls.appendChild(nodes[i]);
return optionEls;
});
},
checkDepends: function(ev, n) {
var changed = false,
sids = this.cfgsections();
for (var i = 0, sid = sids[0]; (sid = sids[i]) != null; i++) {
for (var j = 0, o = this.children[0]; (o = this.children[j]) != null; j++) {
var isActive = o.isActive(sid),
isSatisified = o.checkDepends(sid);
if (isActive != isSatisified) {
o.setActive(sid, !isActive);
changed = true;
}
if (!n && isActive)
o.triggerValidation(sid);
}
}
return changed;
}
});
var isEqual = function(x, y) {
if (x != null && y != null && typeof(x) != typeof(y))
return false;
if ((x == null && y != null) || (x != null && y == null))
return false;
if (Array.isArray(x)) {
if (x.length != y.length)
return false;
for (var i = 0; i < x.length; i++)
if (!isEqual(x[i], y[i]))
return false;
}
else if (typeof(x) == 'object') {
for (var k in x) {
if (x.hasOwnProperty(k) && !y.hasOwnProperty(k))
return false;
if (!isEqual(x[k], y[k]))
return false;
}
for (var k in y)
if (y.hasOwnProperty(k) && !x.hasOwnProperty(k))
return false;
}
else if (x != y) {
return false;
}
return true;
};
var CBIAbstractValue = CBINode.extend({
__init__: function(map, section, option /*, ... */) {
this.super('__init__', this.varargs(arguments, 3));
this.section = section;
this.option = option;
this.map = map;
this.config = map.config;
this.deps = [];
this.initial = {};
this.rmempty = true;
this.default = null;
this.size = null;
this.optional = false;
},
depends: function(field, value) {
var deps;
if (typeof(field) === 'string')
deps = {}, deps[field] = value;
else
deps = field;
this.deps.push(deps);
},
transformDepList: function(section_id, deplist) {
var list = deplist || this.deps,
deps = [];
if (Array.isArray(list)) {
for (var i = 0; i < list.length; i++) {
var dep = {};
for (var k in list[i]) {
if (list[i].hasOwnProperty(k)) {
if (k.charAt(0) === '!')
dep[k] = list[i][k];
else if (k.indexOf('.') !== -1)
dep['cbid.%s'.format(k)] = list[i][k];
else
dep['cbid.%s.%s.%s'.format(this.config, this.ucisection || section_id, k)] = list[i][k];
}
}
for (var k in dep) {
if (dep.hasOwnProperty(k)) {
deps.push(dep);
break;
}
}
}
}
return deps;
},
transformChoices: function() {
if (!Array.isArray(this.keylist) || this.keylist.length == 0)
return null;
var choices = {};
for (var i = 0; i < this.keylist.length; i++)
choices[this.keylist[i]] = this.vallist[i];
return choices;
},
checkDepends: function(section_id) {
var def = false;
if (!Array.isArray(this.deps) || !this.deps.length)
return true;
for (var i = 0; i < this.deps.length; i++) {
var istat = true,
reverse = false;
for (var dep in this.deps[i]) {
if (dep == '!reverse') {
reverse = true;
}
else if (dep == '!default') {
def = true;
istat = false;
}
else {
var res = this.map.lookupOption(dep, section_id),
val = res ? res[0].formvalue(res[1]) : null;
istat = (istat && isEqual(val, this.deps[i][dep]));
}
}
if (istat ^ reverse)
return true;
}
return def;
},
cbid: function(section_id) {
if (section_id == null)
L.error('TypeError', 'Section ID required');
return 'cbid.%s.%s.%s'.format(this.map.config, section_id, this.option);
},
load: function(section_id) {
if (section_id == null)
L.error('TypeError', 'Section ID required');
return uci.get(
this.uciconfig || this.map.config,
this.ucisection || section_id,
this.ucioption || this.option);
},
cfgvalue: function(section_id, set_value) {
if (section_id == null)
L.error('TypeError', 'Section ID required');
if (arguments.length == 2) {
this.data = this.data || {};
this.data[section_id] = set_value;
}
return this.data ? this.data[section_id] : null;
},
formvalue: function(section_id) {
var node = this.map.findElement('id', this.cbid(section_id));
return node ? L.dom.callClassMethod(node, 'getValue') : null;
},
textvalue: function(section_id) {
var cval = this.cfgvalue(section_id);
if (cval == null)
cval = this.default;
return (cval != null) ? '%h'.format(cval) : null;
},
validate: function(section_id, value) {
return true;
},
isValid: function(section_id) {
var node = this.map.findElement('id', this.cbid(section_id));
return node ? L.dom.callClassMethod(node, 'isValid') : true;
},
isActive: function(section_id) {
var field = this.map.findElement('data-field', this.cbid(section_id));
return (field != null && !field.classList.contains('hidden'));
},
setActive: function(section_id, active) {
var field = this.map.findElement('data-field', this.cbid(section_id));
if (field && field.classList.contains('hidden') == active) {
field.classList[active ? 'remove' : 'add']('hidden');
return true;
}
return false;
},
triggerValidation: function(section_id) {
var node = this.map.findElement('id', this.cbid(section_id));
return node ? L.dom.callClassMethod(node, 'triggerValidation') : true;
},
parse: function(section_id) {
var active = this.isActive(section_id),
cval = this.cfgvalue(section_id),
fval = active ? this.formvalue(section_id) : null;
if (active && !this.isValid(section_id))
return Promise.reject();
if (fval != '' && fval != null) {
if (this.forcewrite || !isEqual(cval, fval))
return Promise.resolve(this.write(section_id, fval));
}
else {
if (this.rmempty || this.optional) {
return Promise.resolve(this.remove(section_id));
}
else if (!isEqual(cval, fval)) {
console.log('This should have been catched by isValid()');
return Promise.reject();
}
}
return Promise.resolve();
},
write: function(section_id, formvalue) {
return uci.set(
this.uciconfig || this.map.config,
this.ucisection || section_id,
this.ucioption || this.option,
formvalue);
},
remove: function(section_id) {
return uci.unset(
this.uciconfig || this.map.config,
this.ucisection || section_id,
this.ucioption || this.option);
}
});
var CBITypedSection = CBIAbstractSection.extend({
__name__: 'CBI.TypedSection',
cfgsections: function() {
return uci.sections(this.uciconfig || this.map.config, this.sectiontype)
.map(function(s) { return s['.name'] })
.filter(L.bind(this.filter, this));
},
handleAdd: function(ev, name) {
var config_name = this.uciconfig || this.map.config;
uci.add(config_name, this.sectiontype, name);
this.map.save();
},
handleRemove: function(section_id, ev) {
var config_name = this.uciconfig || this.map.config;
uci.remove(config_name, section_id);
this.map.save();
},
renderSectionAdd: function(extra_class) {
if (!this.addremove)
return E([]);
var createEl = E('div', { 'class': 'cbi-section-create' }),
config_name = this.uciconfig || this.map.config;
if (extra_class != null)
createEl.classList.add(extra_class);
if (this.anonymous) {
createEl.appendChild(E('input', {
'type': 'submit',
'class': 'cbi-button cbi-button-add',
'value': _('Add'),
'title': _('Add'),
'click': L.bind(this.handleAdd, this)
}));
}
else {
var nameEl = E('input', {
'type': 'text',
'class': 'cbi-section-create-name'
});
L.dom.append(createEl, [
E('div', {}, nameEl),
E('input', {
'class': 'cbi-button cbi-button-add',
'type': 'submit',
'value': _('Add'),
'title': _('Add'),
'click': L.bind(function(ev) {
if (nameEl.classList.contains('cbi-input-invalid'))
return;
this.handleAdd(ev, nameEl.value);
}, this)
})
]);
ui.addValidator(nameEl, 'uciname', true, 'blur', 'keyup');
}
return createEl;
},
renderContents: function(cfgsections, nodes) {
var section_id = null,
config_name = this.uciconfig || this.map.config,
sectionEl = E('div', {
'id': 'cbi-%s-%s'.format(config_name, this.sectiontype),
'class': 'cbi-section'
});
if (this.title != null && this.title != '')
sectionEl.appendChild(E('legend', {}, this.title));
if (this.description != null && this.description != '')
sectionEl.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description));
for (var i = 0; i < nodes.length; i++) {
if (this.addremove) {
sectionEl.appendChild(
E('div', { 'class': 'cbi-section-remove right' },
E('input', {
'type': 'submit',
'class': 'cbi-button',
'name': 'cbi.rts.%s.%s'.format(config_name, cfgsections[i]),
'value': _('Delete'),
'data-section-id': cfgsections[i],
'click': L.bind(this.handleRemove, this, cfgsections[i])
})));
}
if (!this.anonymous)
sectionEl.appendChild(E('h3', cfgsections[i].toUpperCase()));
sectionEl.appendChild(E('div', {
'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
'class': this.tabs
? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node'
}, nodes[i]));
if (this.tabs)
ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
}
if (nodes.length == 0)
L.dom.append(sectionEl, [
E('em', _('This section contains no values yet')),
E('br'), E('br')
]);
sectionEl.appendChild(this.renderSectionAdd());
L.dom.bindClassInstance(sectionEl, this);
return sectionEl;
},
render: function() {
var cfgsections = this.cfgsections(),
renderTasks = [];
for (var i = 0; i < cfgsections.length; i++)
renderTasks.push(this.renderUCISection(cfgsections[i]));
return Promise.all(renderTasks).then(this.renderContents.bind(this, cfgsections));
}
});
var CBITableSection = CBITypedSection.extend({
__name__: 'CBI.TableSection',
tab: function() {
throw 'Tabs are not supported by TableSection';
},
renderContents: function(cfgsections, nodes) {
var section_id = null,
config_name = this.uciconfig || this.map.config,
max_cols = isNaN(this.max_cols) ? this.children.length : this.max_cols,
has_more = max_cols < this.children.length,
sectionEl = E('div', {
'id': 'cbi-%s-%s'.format(config_name, this.sectiontype),
'class': 'cbi-section cbi-tblsection'
}),
tableEl = E('div', {
'class': 'table cbi-section-table'
});
if (this.title != null && this.title != '')
sectionEl.appendChild(E('h3', {}, this.title));
if (this.description != null && this.description != '')
sectionEl.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description));
tableEl.appendChild(this.renderHeaderRows(max_cols));
for (var i = 0; i < nodes.length; i++) {
var sectionname = this.stripTags((typeof(this.sectiontitle) == 'function')
? String(this.sectiontitle(cfgsections[i]) || '') : cfgsections[i]).trim();
var trEl = E('div', {
'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
'class': 'tr cbi-section-table-row',
'data-sid': cfgsections[i],
'draggable': this.sortable ? true : null,
'mousedown': this.sortable ? L.bind(this.handleDragInit, this) : null,
'dragstart': this.sortable ? L.bind(this.handleDragStart, this) : null,
'dragover': this.sortable ? L.bind(this.handleDragOver, this) : null,
'dragenter': this.sortable ? L.bind(this.handleDragEnter, this) : null,
'dragleave': this.sortable ? L.bind(this.handleDragLeave, this) : null,
'dragend': this.sortable ? L.bind(this.handleDragEnd, this) : null,
'drop': this.sortable ? L.bind(this.handleDrop, this) : null,
'data-title': (sectionname && (!this.anonymous || this.sectiontitle)) ? sectionname : null
});
if (this.extedit || this.rowcolors)
trEl.classList.add(!(tableEl.childNodes.length % 2)
? 'cbi-rowstyle-1' : 'cbi-rowstyle-2');
for (var j = 0; j < max_cols && nodes[i].firstChild; j++)
trEl.appendChild(nodes[i].firstChild);
trEl.appendChild(this.renderRowActions(cfgsections[i], has_more ? _('More…') : null));
tableEl.appendChild(trEl);
}
if (nodes.length == 0)
tableEl.appendChild(E('div', { 'class': 'tr cbi-section-table-row placeholder' },
E('div', { 'class': 'td' },
E('em', {}, _('This section contains no values yet')))));
sectionEl.appendChild(tableEl);
sectionEl.appendChild(this.renderSectionAdd('cbi-tblsection-create'));
L.dom.bindClassInstance(sectionEl, this);
return sectionEl;
},
renderHeaderRows: function(max_cols) {
var has_titles = false,
has_descriptions = false,
anon_class = (!this.anonymous || this.sectiontitle) ? 'named' : 'anonymous',
trEls = E([]);
for (var i = 0, opt; i < max_cols && (opt = this.children[i]) != null; i++) {
if (opt.optional || opt.modalonly)
continue;
has_titles = has_titles || !!opt.title;
has_descriptions = has_descriptions || !!opt.description;
}
if (has_titles) {
var trEl = E('div', {
'class': 'tr cbi-section-table-titles ' + anon_class,
'data-title': (!this.anonymous || this.sectiontitle) ? _('Name') : null
});
for (var i = 0, opt; i < max_cols && (opt = this.children[i]) != null; i++) {
if (opt.optional || opt.modalonly)
continue;
trEl.appendChild(E('div', {
'class': 'th cbi-section-table-cell',
'data-type': opt.__name__
}));
if (opt.width != null)
trEl.lastElementChild.style.width =
(typeof(opt.width) == 'number') ? opt.width+'px' : opt.width;
if (opt.titleref)
trEl.lastElementChild.appendChild(E('a', {
'href': opt.titleref,
'class': 'cbi-title-ref',
'title': this.titledesc || _('Go to relevant configuration page')
}, opt.title));
else
L.dom.content(trEl.lastElementChild, opt.title);
}
if (this.sortable || this.extedit || this.addremove || has_more)
trEl.appendChild(E('div', {
'class': 'th cbi-section-table-cell cbi-section-actions'
}));
trEls.appendChild(trEl);
}
if (has_descriptions) {
var trEl = E('div', {
'class': 'tr cbi-section-table-descr ' + anon_class
});
for (var i = 0, opt; i < max_cols && (opt = this.children[i]) != null; i++) {
if (opt.optional || opt.modalonly)
continue;
trEl.appendChild(E('div', {
'class': 'th cbi-section-table-cell',
'data-type': opt.__name__
}, opt.description));
if (opt.width != null)
trEl.lastElementChild.style.width =
(typeof(opt.width) == 'number') ? opt.width+'px' : opt.width;
}
if (this.sortable || this.extedit || this.addremove || has_more)
trEl.appendChild(E('div', {
'class': 'th cbi-section-table-cell cbi-section-actions'
}));
trEls.appendChild(trEl);
}
return trEls;
},
renderRowActions: function(section_id, more_label) {
var config_name = this.uciconfig || this.map.config;
if (!this.sortable && !this.extedit && !this.addremove && !more_label)
return E([]);
var tdEl = E('div', {
'class': 'td cbi-section-table-cell nowrap cbi-section-actions'
}, E('div'));
if (this.sortable) {
L.dom.append(tdEl.lastElementChild, [
E('div', {
'title': _('Drag to reorder'),
'class': 'cbi-button drag-handle center',
'style': 'cursor:move'
}, '☰')
]);
}
if (this.extedit) {
var evFn = null;
if (typeof(this.extedit) == 'function')
evFn = L.bind(this.extedit, this);
else if (typeof(this.extedit) == 'string')
evFn = L.bind(function(sid, ev) {
location.href = this.extedit.format(sid);
}, this, section_id);
L.dom.append(tdEl.lastElementChild,
E('input', {
'type': 'button',
'value': _('Edit'),
'title': _('Edit'),
'class': 'cbi-button cbi-button-edit',
'click': evFn
})
);
}
if (more_label) {
L.dom.append(tdEl.lastElementChild,
E('input', {
'type': 'button',
'value': more_label,
'title': more_label,
'class': 'cbi-button cbi-button-edit',
'click': L.bind(this.renderMoreOptionsModal, this, section_id)
})
);
}
if (this.addremove) {
L.dom.append(tdEl.lastElementChild,
E('input', {
'type': 'submit',
'value': _('Delete'),
'title': _('Delete'),
'class': 'cbi-button cbi-button-remove',
'click': L.bind(function(sid, ev) {
uci.remove(config_name, sid);
this.map.save();
}, this, section_id)
})
);
}
return tdEl;
},
handleDragInit: function(ev) {
scope.dragState = { node: ev.target };
},
handleDragStart: function(ev) {
if (!scope.dragState || !scope.dragState.node.classList.contains('drag-handle')) {
scope.dragState = null;
ev.preventDefault();
return false;
}
scope.dragState.node = L.dom.parent(scope.dragState.node, '.tr');
ev.dataTransfer.setData('text', 'drag');
ev.target.style.opacity = 0.4;
},
handleDragOver: function(ev) {
var n = scope.dragState.targetNode,
r = scope.dragState.rect,
t = r.top + r.height / 2;
if (ev.clientY <= t) {
n.classList.remove('drag-over-below');
n.classList.add('drag-over-above');
}
else {
n.classList.remove('drag-over-above');
n.classList.add('drag-over-below');
}
ev.dataTransfer.dropEffect = 'move';
ev.preventDefault();
return false;
},
handleDragEnter: function(ev) {
scope.dragState.rect = ev.currentTarget.getBoundingClientRect();
scope.dragState.targetNode = ev.currentTarget;
},
handleDragLeave: function(ev) {
ev.currentTarget.classList.remove('drag-over-above');
ev.currentTarget.classList.remove('drag-over-below');
},
handleDragEnd: function(ev) {
var n = ev.target;
n.style.opacity = '';
n.classList.add('flash');
n.parentNode.querySelectorAll('.drag-over-above, .drag-over-below')
.forEach(function(tr) {
tr.classList.remove('drag-over-above');
tr.classList.remove('drag-over-below');
});
},
handleDrop: function(ev) {
var s = scope.dragState;
if (s.node && s.targetNode) {
var config_name = this.uciconfig || this.map.config,
ref_node = s.targetNode,
after = false;
if (ref_node.classList.contains('drag-over-below')) {
ref_node = ref_node.nextElementSibling;
after = true;
}
var sid1 = s.node.getAttribute('data-sid'),
sid2 = s.targetNode.getAttribute('data-sid');
s.node.parentNode.insertBefore(s.node, ref_node);
uci.move(config_name, sid1, sid2, after);
}
scope.dragState = null;
ev.target.style.opacity = '';
ev.stopPropagation();
ev.preventDefault();
return false;
},
handleModalCancel: function(modalMap, ev) {
return Promise.resolve(L.ui.hideModal());
},
handleModalSave: function(modalMap, ev) {
return modalMap.save()
.then(L.bind(this.map.reset, this.map))
.then(L.ui.hideModal)
.catch(function() {});
},
renderMoreOptionsModal: function(section_id, ev) {
var parent = this.map,
title = parent.title,
name = null,
m = new CBIMap(this.map.config, null, null),
s = m.section(CBINamedSection, section_id, this.sectiontype);
s.tabs = this.tabs;
s.tab_names = this.tab_names;
if (typeof(this.sectiontitle) == 'function')
name = this.stripTags(String(this.sectiontitle(section_id) || ''));
else if (!this.anonymous)
name = section_id;
if (name)
title += ' - ' + name;
for (var i = 0; i < this.children.length; i++) {
var o1 = this.children[i];
if (o1.modalonly === false)
continue;
var o2 = s.option(o1.constructor, o1.option, o1.title, o1.description);
for (var k in o1) {
if (!o1.hasOwnProperty(k))
continue;
switch (k) {
case 'map':
case 'section':
case 'option':
case 'title':
case 'description':
continue;
default:
o2[k] = o1[k];
}
}
}
//ev.target.classList.add('spinning');
m.render().then(L.bind(function(nodes) {
//ev.target.classList.remove('spinning');
L.ui.showModal(title, [
nodes,
E('div', { 'class': 'right' }, [
E('input', {
'type': 'button',
'class': 'btn',
'click': L.bind(this.handleModalCancel, this, m),
'value': _('Dismiss')
}), ' ',
E('input', {
'type': 'button',
'class': 'cbi-button cbi-button-positive important',
'click': L.bind(this.handleModalSave, this, m),
'value': _('Save')
})
])
], 'cbi-modal');
}, this)).catch(L.error);
}
});
var CBIGridSection = CBITableSection.extend({
tab: function(name, title, description) {
CBIAbstractSection.prototype.tab.call(this, name, title, description);
},
handleAdd: function(ev) {
var config_name = this.uciconfig || this.map.config,
section_id = uci.add(config_name, this.sectiontype);
this.addedSection = section_id;
this.renderMoreOptionsModal(section_id);
},
handleModalSave: function(/* ... */) {
return this.super('handleModalSave', arguments)
.then(L.bind(function() { this.addedSection = null }, this));
},
handleModalCancel: function(/* ... */) {
var config_name = this.uciconfig || this.map.config;
if (this.addedSection != null) {
uci.remove(config_name, this.addedSection);
this.addedSection = null;
}
return this.super('handleModalCancel', arguments);
},
renderUCISection: function(section_id) {
return this.renderOptions(null, section_id);
},
renderChildren: function(tab_name, section_id, in_table) {
var tasks = [], index = 0;
for (var i = 0, opt; (opt = this.children[i]) != null; i++) {
if (opt.disable || opt.modalonly)
continue;
if (opt.editable)
tasks.push(opt.render(index++, section_id, in_table));
else
tasks.push(this.renderTextValue(section_id, opt));
}
return Promise.all(tasks);
},
renderTextValue: function(section_id, opt) {
var title = this.stripTags(opt.title).trim(),
descr = this.stripTags(opt.description).trim(),
value = opt.textvalue(section_id);
return E('div', {
'class': 'td cbi-value-field',
'data-title': (title != '') ? title : opt.option,
'data-description': (descr != '') ? descr : null,
'data-name': opt.option,
'data-type': opt.typename || opt.__name__
}, (value != null) ? value : E('em', _('none')));
},
renderRowActions: function(section_id) {
return this.super('renderRowActions', [ section_id, _('Edit') ]);
},
parse: function() {
var section_ids = this.cfgsections(),
tasks = [];
if (Array.isArray(this.children)) {
for (var i = 0; i < section_ids.length; i++) {
for (var j = 0; j < this.children.length; j++) {
if (!this.children[j].editable || this.children[j].modalonly)
continue;
tasks.push(this.children[j].parse(section_ids[i]));
}
}
}
return Promise.all(tasks);
}
});
var CBINamedSection = CBIAbstractSection.extend({
__name__: 'CBI.NamedSection',
__init__: function(map, section_id /*, ... */) {
this.super('__init__', this.varargs(arguments, 2, map));
this.section = section_id;
},
cfgsections: function() {
return [ this.section ];
},
handleAdd: function(ev) {
var section_id = this.section,
config_name = this.uciconfig || this.map.config;
uci.add(config_name, this.sectiontype, section_id);
this.map.save();
},
handleRemove: function(ev) {
var section_id = this.section,
config_name = this.uciconfig || this.map.config;
uci.remove(config_name, section_id);
this.map.save();
},
renderContents: function(data) {
var ucidata = data[0], nodes = data[1],
section_id = this.section,
config_name = this.uciconfig || this.map.config,
sectionEl = E('div', {
'id': ucidata ? null : 'cbi-%s-%s'.format(config_name, section_id),
'class': 'cbi-section'
});
if (typeof(this.title) === 'string' && this.title !== '')
sectionEl.appendChild(E('legend', {}, this.title));
if (typeof(this.description) === 'string' && this.description !== '')
sectionEl.appendChild(E('div', { 'class': 'cbi-section-descr' }, this.description));
if (ucidata) {
if (this.addremove) {
sectionEl.appendChild(
E('div', { 'class': 'cbi-section-remove right' },
E('input', {
'type': 'submit',
'class': 'cbi-button',
'value': _('Delete'),
'click': L.bind(this.handleRemove, this)
})));
}
sectionEl.appendChild(E('div', {
'id': 'cbi-%s-%s'.format(config_name, section_id),
'class': this.tabs
? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node'
}, nodes));
if (this.tabs)
ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
}
else if (this.addremove) {
sectionEl.appendChild(
E('input', {
'type': 'submit',
'class': 'cbi-button cbi-button-add',
'value': _('Add'),
'click': L.bind(this.handleAdd, this)
}));
}
L.dom.bindClassInstance(sectionEl, this);
return sectionEl;
},
render: function() {
var config_name = this.uciconfig || this.map.config,
section_id = this.section;
return Promise.all([
uci.get(config_name, section_id),
this.renderUCISection(section_id)
]).then(this.renderContents.bind(this));
}
});
var CBIValue = CBIAbstractValue.extend({
__name__: 'CBI.Value',
value: function(key, val) {
this.keylist = this.keylist || [];
this.keylist.push(String(key));
this.vallist = this.vallist || [];
this.vallist.push(String(val != null ? val : key));
},
render: function(option_index, section_id, in_table) {
return Promise.resolve(this.cfgvalue(section_id))
.then(this.renderWidget.bind(this, section_id, option_index))
.then(this.renderFrame.bind(this, section_id, in_table, option_index));
},
renderFrame: function(section_id, in_table, option_index, nodes) {
var config_name = this.uciconfig || this.map.config,
depend_list = this.transformDepList(section_id),
optionEl;
if (in_table) {
optionEl = E('div', {
'class': 'td cbi-value-field',
'data-title': this.stripTags(this.title).trim(),
'data-description': this.stripTags(this.description).trim(),
'data-name': this.option,
'data-type': this.typename || (this.template ? this.template.replace(/^.+\//, '') : null) || this.__name__
}, E('div', {
'id': 'cbi-%s-%s-%s'.format(config_name, section_id, this.option),
'data-index': option_index,
'data-depends': depend_list,
'data-field': this.cbid(section_id)
}));
}
else {
optionEl = E('div', {
'class': 'cbi-value',
'id': 'cbi-%s-%s-%s'.format(config_name, section_id, this.option),
'data-index': option_index,
'data-depends': depend_list,
'data-field': this.cbid(section_id),
'data-name': this.option,
'data-type': this.typename || (this.template ? this.template.replace(/^.+\//, '') : null) || this.__name__
});
if (this.last_child)
optionEl.classList.add('cbi-value-last');
if (typeof(this.title) === 'string' && this.title !== '') {
optionEl.appendChild(E('label', {
'class': 'cbi-value-title',
'for': 'cbid.%s.%s.%s'.format(config_name, section_id, this.option)
},
this.titleref ? E('a', {
'class': 'cbi-title-ref',
'href': this.titleref,
'title': this.titledesc || _('Go to relevant configuration page')
}, this.title) : this.title));
optionEl.appendChild(E('div', { 'class': 'cbi-value-field' }));
}
}
if (nodes)
(optionEl.lastChild || optionEl).appendChild(nodes);
if (!in_table && typeof(this.description) === 'string' && this.description !== '')
L.dom.append(optionEl.lastChild || optionEl,
E('div', { 'class': 'cbi-value-description' }, this.description));
if (depend_list && depend_list.length)
optionEl.classList.add('hidden');
optionEl.addEventListener('widget-change',
L.bind(this.map.checkDepends, this.map));
L.dom.bindClassInstance(optionEl, this);
return optionEl;
},
renderWidget: function(section_id, option_index, cfgvalue) {
var value = (cfgvalue != null) ? cfgvalue : this.default,
choices = this.transformChoices(),
widget;
if (choices) {
var placeholder = (this.optional || this.rmempty)
? E('em', _('unspecified')) : _('-- Please choose --');
widget = new ui.Combobox(Array.isArray(value) ? value.join(' ') : value, choices, {
id: this.cbid(section_id),
sort: this.keylist,
optional: this.optional || this.rmempty,
datatype: this.datatype,
select_placeholder: this.placeholder || placeholder,
validate: L.bind(this.validate, this, section_id)
});
}
else {
widget = new ui.Textfield(Array.isArray(value) ? value.join(' ') : value, {
id: this.cbid(section_id),
password: this.password,
optional: this.optional || this.rmempty,
datatype: this.datatype,
placeholder: this.placeholder,
validate: L.bind(this.validate, this, section_id)
});
}
return widget.render();
}
});
var CBIDynamicList = CBIValue.extend({
__name__: 'CBI.DynamicList',
renderWidget: function(section_id, option_index, cfgvalue) {
var value = (cfgvalue != null) ? cfgvalue : this.default,
choices = this.transformChoices(),
items = null;
if (Array.isArray(value))
items = value;
else if (value != null)
items = String(value).trim().split(/\s+/);
var widget = new ui.DynamicList(items, choices, {
id: this.cbid(section_id),
sort: this.keylist,
optional: this.optional || this.rmempty,
datatype: this.datatype,
placeholder: this.placeholder,
validate: L.bind(this.validate, this, section_id)
});
return widget.render();
},
});
var CBIListValue = CBIValue.extend({
__name__: 'CBI.ListValue',
__init__: function() {
this.super('__init__', arguments);
this.widget = 'select';
this.deplist = [];
},
renderWidget: function(section_id, option_index, cfgvalue) {
var choices = this.transformChoices();
var widget = new ui.Select((cfgvalue != null) ? cfgvalue : this.default, choices, {
id: this.cbid(section_id),
size: this.size,
sort: this.keylist,
optional: this.rmempty || this.optional,
validate: L.bind(this.validate, this, section_id)
});
return widget.render();
},
});
var CBIFlagValue = CBIValue.extend({
__name__: 'CBI.FlagValue',
__init__: function() {
this.super('__init__', arguments);
this.enabled = '1';
this.disabled = '0';
this.default = this.disabled;
},
renderWidget: function(section_id, option_index, cfgvalue) {
var widget = new ui.Checkbox((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id),
value_enabled: this.enabled,
value_disabled: this.disabled,
validate: L.bind(this.validate, this, section_id)
});
return widget.render();
},
formvalue: function(section_id) {
var node = this.map.findElement('id', this.cbid(section_id)),
checked = node ? L.dom.callClassMethod(node, 'isChecked') : false;
return checked ? this.enabled : this.disabled;
},
textvalue: function(section_id) {
var cval = this.cfgvalue(section_id);
if (cval == null)
cval = this.default;
return (cval == this.enabled) ? _('Yes') : _('No');
},
parse: function(section_id) {
if (this.isActive(section_id)) {
var fval = this.formvalue(section_id);
if (!this.isValid(section_id))
return Promise.reject();
if (fval == this.default && (this.optional || this.rmempty))
return Promise.resolve(this.remove(section_id));
else
return Promise.resolve(this.write(section_id, fval));
}
else {
return Promise.resolve(this.remove(section_id));
}
},
});
var CBIMultiValue = CBIDynamicList.extend({
__name__: 'CBI.MultiValue',
__init__: function() {
this.super('__init__', arguments);
this.placeholder = _('-- Please choose --');
},
renderWidget: function(section_id, option_index, cfgvalue) {
var value = (cfgvalue != null) ? cfgvalue : this.default,
choices = this.transformChoices();
if (!Array.isArray(value))
value = String(value).split(/\s+/);
var widget = new ui.Dropdown(value, choices, {
id: this.cbid(section_id),
sort: this.keylist,
multiple: true,
optional: this.optional || this.rmempty,
select_placeholder: this.placeholder,
display_items: this.display_size || this.size || 3,
dropdown_items: this.dropdown_size || this.size || -1,
validate: L.bind(this.validate, this, section_id)
});
return widget.render();
},
});
var CBIDummyValue = CBIValue.extend({
__name__: 'CBI.DummyValue',
renderWidget: function(section_id, option_index, cfgvalue) {
var value = (cfgvalue != null) ? cfgvalue : this.default,
hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) }),
outputEl = E('div');
if (this.href)
outputEl.appendChild(E('a', { 'href': this.href }));
L.dom.append(outputEl.lastChild || outputEl,
this.rawhtml ? value : [ value ]);
return E([
outputEl,
hiddenEl.render()
]);
},
});
var CBIButtonValue = CBIValue.extend({
__name__: 'CBI.ButtonValue',
renderWidget: function(section_id, option_index, cfgvalue) {
var value = (cfgvalue != null) ? cfgvalue : this.default;
if (value !== false)
return E([
E('input', {
'type': 'hidden',
'id': this.cbid(section_id)
}),
E('input', {
'class': 'cbi-button cbi-button-%s'.format(this.inputstyle || 'button'),
'type': 'submit',
//'id': this.cbid(section_id),
//'name': this.cbid(section_id),
'value': this.inputtitle || this.title,
'click': L.bind(function(ev) {
ev.target.previousElementSibling.value = ev.target.value;
this.map.save();
}, this)
})
]);
else
return document.createTextNode(' - ');
}
});
var CBIHiddenValue = CBIValue.extend({
__name__: 'CBI.HiddenValue',
renderWidget: function(section_id, option_index, cfgvalue) {
var widget = new ui.Hiddenfield((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id)
});
return widget.render();
}
});
var CBISectionValue = CBIValue.extend({
__name__: 'CBI.ContainerValue',
__init__: function(map, section, option, cbiClass /*, ... */) {
this.super('__init__', [map, section, option]);
if (!CBIAbstractSection.isSubclass(cbiClass))
throw 'Sub section must be a descendent of CBIAbstractSection';
this.subsection = cbiClass.instantiate(this.varargs(arguments, 4, this.map));
},
load: function(section_id) {
return this.subsection.load();
},
parse: function(section_id) {
return this.subsection.parse();
},
renderWidget: function(section_id, option_index, cfgvalue) {
return this.subsection.render();
},
checkDepends: function(section_id) {
this.subsection.checkDepends();
return this.super('checkDepends');
},
write: function() {},
remove: function() {},
cfgvalue: function() { return null },
formvalue: function() { return null }
});
return L.Class.extend({
Map: CBIMap,
AbstractSection: CBIAbstractSection,
AbstractValue: CBIAbstractValue,
TypedSection: CBITypedSection,
TableSection: CBITableSection,
GridSection: CBIGridSection,
NamedSection: CBINamedSection,
Value: CBIValue,
DynamicList: CBIDynamicList,
ListValue: CBIListValue,
Flag: CBIFlagValue,
MultiValue: CBIMultiValue,
DummyValue: CBIDummyValue,
Button: CBIButtonValue,
HiddenValue: CBIHiddenValue,
SectionValue: CBISectionValue
});