luci-base: form.js: allow nesting Grid and Table sections

Implement a simple view stack within the modal overlay to allow the modal
edit dialog of a table or grid section to render another table or grid
section which recursively opens another modal edit dialog.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2021-11-21 23:29:24 +01:00
parent b11a7d8e49
commit 1c798d1a08

View file

@ -2972,15 +2972,44 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
/** @private */ /** @private */
handleModalCancel: function(modalMap, ev) { handleModalCancel: function(modalMap, ev) {
return Promise.resolve(ui.hideModal()); var prevNode = this.getPreviousModalMap();
if (prevNode) {
var heading = prevNode.parentNode.querySelector('h4');
prevNode.classList.add('flash');
prevNode.classList.remove('hidden');
prevNode.parentNode.removeChild(prevNode.nextElementSibling);
heading.removeChild(heading.lastElementChild);
if (!this.getPreviousModalMap())
prevNode.parentNode
.querySelector('div.right > button')
.firstChild.data = _('Dismiss');
}
else {
ui.hideModal();
}
return Promise.resolve();
}, },
/** @private */ /** @private */
handleModalSave: function(modalMap, ev) { handleModalSave: function(modalMap, ev) {
return modalMap.save(null, true) var mapNode = this.getActiveModalMap(),
.then(L.bind(this.map.load, this.map)) activeMap = dom.findClassInstance(mapNode),
.then(L.bind(this.map.reset, this.map)) saveTasks = activeMap.save(null, true);
.then(ui.hideModal)
while (activeMap.parent) {
activeMap = activeMap.parent;
saveTasks = saveTasks
.then(L.bind(activeMap.load, activeMap))
.then(L.bind(activeMap.reset, activeMap));
}
return saveTasks
.then(L.bind(this.handleModalCancel, this, modalMap, ev))
.catch(function() {}); .catch(function() {});
}, },
@ -3013,6 +3042,19 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
}, },
/** @private */
getActiveModalMap: function() {
return document.querySelector('body.modal-overlay-active > #modal_overlay > .modal.cbi-modal > .cbi-map:not(.hidden)');
},
/** @private */
getPreviousModalMap: function() {
var mapNode = this.getActiveModalMap(),
prevNode = mapNode ? mapNode.previousElementSibling : null;
return (prevNode && prevNode.matches('.cbi-map.hidden')) ? prevNode : null;
},
/** @private */ /** @private */
renderMoreOptionsModal: function(section_id, ev) { renderMoreOptionsModal: function(section_id, ev) {
var parent = this.map, var parent = this.map,
@ -3061,20 +3103,37 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
} }
return Promise.resolve(this.addModalOptions(s, section_id, ev)).then(L.bind(m.render, m)).then(L.bind(function(nodes) { return Promise.resolve(this.addModalOptions(s, section_id, ev)).then(L.bind(m.render, m)).then(L.bind(function(nodes) {
ui.showModal(title, [ var modalMap = this.getActiveModalMap();
nodes,
E('div', { 'class': 'right' }, [ if (modalMap) {
E('button', { modalMap.parentNode
'class': 'cbi-button', .querySelector('h4')
'click': ui.createHandlerFn(this, 'handleModalCancel', m) .appendChild(E('span', title ? ' » ' + title : ''));
}, [ _('Dismiss') ]), ' ',
E('button', { modalMap.parentNode
'class': 'cbi-button cbi-button-positive important', .querySelector('div.right > button')
'click': ui.createHandlerFn(this, 'handleModalSave', m), .firstChild.data = _('Back');
'disabled': m.readonly || null
}, [ _('Save') ]) modalMap.classList.add('hidden');
]) modalMap.parentNode.insertBefore(nodes, modalMap.nextElementSibling);
], 'cbi-modal'); nodes.classList.add('flash');
}
else {
ui.showModal(title, [
nodes,
E('div', { 'class': 'right' }, [
E('button', {
'class': 'cbi-button',
'click': ui.createHandlerFn(this, 'handleModalCancel', m)
}, [ _('Dismiss') ]), ' ',
E('button', {
'class': 'cbi-button cbi-button-positive important',
'click': ui.createHandlerFn(this, 'handleModalSave', m),
'disabled': m.readonly || null
}, [ _('Save') ])
])
], 'cbi-modal');
}
}, this)).catch(L.error); }, this)).catch(L.error);
} }
}); });
@ -3155,25 +3214,33 @@ var CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.pro
/** @private */ /** @private */
handleAdd: function(ev, name) { handleAdd: function(ev, name) {
var config_name = this.uciconfig || this.map.config, var config_name = this.uciconfig || this.map.config,
section_id = this.map.data.add(config_name, this.sectiontype, name); section_id = this.map.data.add(config_name, this.sectiontype, name),
mapNode = this.getPreviousModalMap(),
prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
prevMap.addedSection = section_id;
this.addedSection = section_id;
return this.renderMoreOptionsModal(section_id); return this.renderMoreOptionsModal(section_id);
}, },
/** @private */ /** @private */
handleModalSave: function(/* ... */) { handleModalSave: function(/* ... */) {
var mapNode = this.getPreviousModalMap(),
prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
return this.super('handleModalSave', arguments) return this.super('handleModalSave', arguments)
.then(L.bind(function() { this.addedSection = null }, this)); .then(function() { delete prevMap.addedSection });
}, },
/** @private */ /** @private */
handleModalCancel: function(/* ... */) { handleModalCancel: function(/* ... */) {
var config_name = this.uciconfig || this.map.config; var config_name = this.uciconfig || this.map.config,
mapNode = this.getPreviousModalMap(),
prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
if (this.addedSection != null) { if (prevMap.addedSection != null) {
this.map.data.remove(config_name, this.addedSection); this.map.data.remove(config_name, prevMap.addedSection);
this.addedSection = null; delete prevMap.addedSection;
} }
return this.super('handleModalCancel', arguments); return this.super('handleModalCancel', arguments);