From 96fbca76c824a00cb7e04519f5cd4baf9d64c8be Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 16 Sep 2019 07:55:45 +0200 Subject: [PATCH] luci-base: form.js: implement JSONMap Implement a new map type JSONMap which uses a JSON data structure instead of uci as configuration data backend. Signed-off-by: Jo-Philipp Wich --- .../htdocs/luci-static/resources/form.js | 209 ++++++++++++++++-- 1 file changed, 194 insertions(+), 15 deletions(-) diff --git a/modules/luci-base/htdocs/luci-static/resources/form.js b/modules/luci-base/htdocs/luci-static/resources/form.js index f0629d0ca..0630ceec8 100644 --- a/modules/luci-base/htdocs/luci-static/resources/form.js +++ b/modules/luci-base/htdocs/luci-static/resources/form.js @@ -4,6 +4,173 @@ var scope = this; +var CBIJSONConfig = Class.extend({ + __init__: function(data) { + data = Object.assign({}, data); + + this.data = {}; + + var num_sections = 0, + section_ids = []; + + for (var sectiontype in data) { + if (!data.hasOwnProperty(sectiontype)) + continue; + + if (L.isObject(data[sectiontype])) { + this.data[sectiontype] = Object.assign(data[sectiontype], { + '.anonymous': false, + '.name': sectiontype, + '.type': sectiontype + }); + + section_ids.push(sectiontype); + num_sections++; + } + else if (Array.isArray(data[sectiontype])) { + for (var i = 0, index = 0; i < data[sectiontype].length; i++) { + var item = data[sectiontype][i], + anonymous, name; + + if (!L.isObject(item)) + continue; + + if (typeof(item['.name']) == 'string') { + name = item['.name']; + anonymous = false; + } + else { + name = sectiontype + num_sections; + anonymous = true; + } + + if (!this.data.hasOwnProperty(name)) + section_ids.push(name); + + this.data[name] = Object.assign(item, { + '.index': num_sections++, + '.anonymous': anonymous, + '.name': name, + '.type': sectiontype + }); + } + } + } + + section_ids.sort(L.bind(function(a, b) { + var indexA = (this.data[a]['.index'] != null) ? +this.data[a]['.index'] : 9999, + indexB = (this.data[b]['.index'] != null) ? +this.data[b]['.index'] : 9999; + + if (indexA != indexB) + return (indexA - indexB); + + return (a > b); + }, this)); + + for (var i = 0; i < section_ids.length; i++) + this.data[section_ids[i]]['.index'] = i; + }, + + load: function() { + return Promise.resolve(this.data); + }, + + save: function() { + return Promise.resolve(); + }, + + get: function(config, section, option) { + if (section == null) + return null; + + if (option == null) + return this.data[section]; + + if (!this.data.hasOwnProperty(section)) + return null; + + var value = this.data[section][option]; + + if (Array.isArray(value)) + return value; + + if (value != null) + return String(value); + + return null; + }, + + set: function(config, section, option, value) { + if (section == null || option == null || option.charAt(0) == '.') + return; + + if (!this.data.hasOwnProperty(section)) + return; + + if (value == null) + delete this.data[section][option]; + else if (Array.isArray(value)) + this.data[section][option] = value; + else + this.data[section][option] = String(value); + }, + + unset: function(config, section, option) { + return this.set(config, section, option, null); + }, + + sections: function(config, sectiontype, callback) { + var rv = []; + + for (var section_id in this.data) + if (sectiontype == null || this.data[section_id]['.type'] == sectiontype) + rv.push(this.data[section_id]); + + rv.sort(function(a, b) { return a['.index'] - b['.index'] }); + + if (typeof(callback) == 'function') + for (var i = 0; i < rv.length; i++) + callback.call(this, rv[i], rv[i]['.name']); + + return rv; + }, + + add: function(config, sectiontype, sectionname) { + var num_sections_type = 0, next_index = 0; + + for (var name in this.data) { + num_sections_type += (this.data[name]['.type'] == sectiontype); + next_index = Math.max(next_index, this.data[name]['.index']); + } + + var section_id = sectionname || sectiontype + num_sections_type; + + if (!this.data.hasOwnProperty(section_id)) { + this.data[section_id] = { + '.name': section_id, + '.type': sectiontype, + '.anonymous': (sectionname == null), + '.index': next_index + 1 + }; + } + + return section_id; + }, + + remove: function(config, section) { + if (this.data.hasOwnProperty(section)) + delete this.data[section]; + }, + + resolveSID: function(config, section_id) { + return section_id; + }, + + move: function(config, section_id1, section_id2, after) { + return uci.move.apply(this, [config, section_id1, section_id2, after]); + } +}); + var CBINode = Class.extend({ __init__: function(title, description) { this.title = title || ''; @@ -83,6 +250,7 @@ var CBIMap = CBINode.extend({ this.config = config; this.parsechain = [ config ]; + this.data = uci; }, findElements: function(/* ... */) { @@ -118,7 +286,7 @@ var CBIMap = CBINode.extend({ }, load: function() { - return uci.load(this.parsechain || [ this.config ]) + return this.data.load(this.parsechain || [ this.config ]) .then(this.loadChildren.bind(this)); }, @@ -137,7 +305,7 @@ var CBIMap = CBINode.extend({ return this.parse() .then(cb) - .then(uci.save.bind(uci)) + .then(this.data.save.bind(this.data)) .then(this.load.bind(this)) .catch(function(e) { if (!silent) @@ -228,6 +396,16 @@ var CBIMap = CBINode.extend({ } }); +var CBIJSONMap = CBIMap.extend({ + __init__: function(data /*, ... */) { + this.super('__init__', this.varargs(arguments, 1, 'json')); + + this.config = 'json'; + this.parsechain = [ 'json' ]; + this.data = new CBIJSONConfig(data); + } +}); + var CBIAbstractSection = CBINode.extend({ __init__: function(map, sectionType /*, ... */) { this.super('__init__', this.varargs(arguments, 2)); @@ -543,7 +721,7 @@ var CBIAbstractValue = CBINode.extend({ if (section_id == null) L.error('TypeError', 'Section ID required'); - return uci.get( + return this.map.data.get( this.uciconfig || this.section.uciconfig || this.map.config, this.ucisection || section_id, this.ucioption || this.option); @@ -631,7 +809,7 @@ var CBIAbstractValue = CBINode.extend({ }, write: function(section_id, formvalue) { - return uci.set( + return this.map.data.set( this.uciconfig || this.section.uciconfig || this.map.config, this.ucisection || section_id, this.ucioption || this.option, @@ -639,7 +817,7 @@ var CBIAbstractValue = CBINode.extend({ }, remove: function(section_id) { - return uci.unset( + return this.map.data.unset( this.uciconfig || this.section.uciconfig || this.map.config, this.ucisection || section_id, this.ucioption || this.option); @@ -650,7 +828,7 @@ var CBITypedSection = CBIAbstractSection.extend({ __name__: 'CBI.TypedSection', cfgsections: function() { - return uci.sections(this.uciconfig || this.map.config, this.sectiontype) + return this.map.data.sections(this.uciconfig || this.map.config, this.sectiontype) .map(function(s) { return s['.name'] }) .filter(L.bind(this.filter, this)); }, @@ -658,14 +836,14 @@ var CBITypedSection = CBIAbstractSection.extend({ handleAdd: function(ev, name) { var config_name = this.uciconfig || this.map.config; - uci.add(config_name, this.sectiontype, name); + this.map.data.add(config_name, this.sectiontype, name); return this.map.save(null, true); }, handleRemove: function(section_id, ev) { var config_name = this.uciconfig || this.map.config; - uci.remove(config_name, section_id); + this.map.data.remove(config_name, section_id); return this.map.save(null, true); }, @@ -998,7 +1176,7 @@ var CBITableSection = CBITypedSection.extend({ 'title': btn_title || _('Delete'), 'class': 'cbi-button cbi-button-remove', 'click': L.ui.createHandlerFn(this, function(sid, ev) { - uci.remove(config_name, sid); + this.map.data.remove(config_name, sid); return this.map.save(null, true); }, section_id) }, [ btn_title || _('Delete') ]) @@ -1082,7 +1260,7 @@ var CBITableSection = CBITypedSection.extend({ sid2 = s.targetNode.getAttribute('data-sid'); s.node.parentNode.insertBefore(s.node, ref_node); - uci.move(config_name, sid1, sid2, after); + this.map.data.move(config_name, sid1, sid2, after); } scope.dragState = null; @@ -1178,7 +1356,7 @@ var CBIGridSection = CBITableSection.extend({ handleAdd: function(ev) { var config_name = this.uciconfig || this.map.config, - section_id = uci.add(config_name, this.sectiontype); + section_id = this.map.data.add(config_name, this.sectiontype); this.addedSection = section_id; return this.renderMoreOptionsModal(section_id); @@ -1193,7 +1371,7 @@ var CBIGridSection = CBITableSection.extend({ var config_name = this.uciconfig || this.map.config; if (this.addedSection != null) { - uci.remove(config_name, this.addedSection); + this.map.data.remove(config_name, this.addedSection); this.addedSection = null; } @@ -1273,7 +1451,7 @@ var CBINamedSection = CBIAbstractSection.extend({ var section_id = this.section, config_name = this.uciconfig || this.map.config; - uci.add(config_name, this.sectiontype, section_id); + this.map.data.add(config_name, this.sectiontype, section_id); return this.map.save(null, true); }, @@ -1281,7 +1459,7 @@ var CBINamedSection = CBIAbstractSection.extend({ var section_id = this.section, config_name = this.uciconfig || this.map.config; - uci.remove(config_name, section_id); + this.map.data.remove(config_name, section_id); return this.map.save(null, true); }, @@ -1337,7 +1515,7 @@ var CBINamedSection = CBIAbstractSection.extend({ section_id = this.section; return Promise.all([ - uci.get(config_name, section_id), + this.map.data.get(config_name, section_id), this.renderUCISection(section_id) ]).then(this.renderContents.bind(this)); } @@ -1737,6 +1915,7 @@ var CBISectionValue = CBIValue.extend({ return L.Class.extend({ Map: CBIMap, + JSONMap: CBIJSONMap, AbstractSection: CBIAbstractSection, AbstractValue: CBIAbstractValue, -- 2.25.1