luci-base: form.js: implement JSONMap
authorJo-Philipp Wich <jo@mein.io>
Mon, 16 Sep 2019 05:55:45 +0000 (07:55 +0200)
committerJo-Philipp Wich <jo@mein.io>
Mon, 16 Sep 2019 05:55:45 +0000 (07:55 +0200)
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 <jo@mein.io>
modules/luci-base/htdocs/luci-static/resources/form.js

index f0629d0ca798a6014abb27ec429942b2c304a696..0630ceec86ae955c91c5da136795bdf00ac642a0 100644 (file)
@@ -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,