diff --git a/luci-base/htdocs/luci-static/resources/form.js b/luci-base/htdocs/luci-static/resources/form.js index 58d8f7100..ab0998943 100644 --- a/luci-base/htdocs/luci-static/resources/form.js +++ b/luci-base/htdocs/luci-static/resources/form.js @@ -196,6 +196,8 @@ var CBIMap = CBINode.extend({ if (changed && (n || 0) < 10) this.checkDepends(ev, (n || 10) + 1); + + ui.tabs.updateTabs(ev, this.root); } }); @@ -1065,6 +1067,10 @@ var CBITableSection = CBITypedSection.extend({ .catch(function() {}); }, + addModalOptions: function(modalSection, section_id, ev) { + + }, + renderMoreOptionsModal: function(section_id, ev) { var parent = this.map, title = parent.title, @@ -1109,7 +1115,7 @@ var CBITableSection = CBITypedSection.extend({ } //ev.target.classList.add('spinning'); - m.render().then(L.bind(function(nodes) { + Promise.resolve(this.addModalOptions(s, section_id, ev)).then(L.bind(m.render, m)).then(L.bind(function(nodes) { //ev.target.classList.remove('spinning'); L.ui.showModal(title, [ nodes, @@ -1460,7 +1466,7 @@ var CBIListValue = CBIValue.extend({ id: this.cbid(section_id), size: this.size, sort: this.keylist, - optional: this.rmempty || this.optional, + optional: this.optional, placeholder: this.placeholder, validate: L.bind(this.validate, this, section_id) }); diff --git a/luci-base/htdocs/luci-static/resources/luci.js b/luci-base/htdocs/luci-static/resources/luci.js index 6659bc51b..66f32d722 100644 --- a/luci-base/htdocs/luci-static/resources/luci.js +++ b/luci-base/htdocs/luci-static/resources/luci.js @@ -23,6 +23,20 @@ }); } + /* Promise.finally polyfill */ + if (typeof Promise.prototype.finally !== 'function') { + Promise.prototype.finally = function(fn) { + var onFinally = function(cb) { + return Promise.resolve(fn.call(this)).then(cb); + }; + + return this.then( + function(result) { return onFinally.call(this, function() { return result }) }, + function(reason) { return onFinally.call(this, function() { return Promise.reject(reason) }) } + ); + }; + } + /* * Class declaration and inheritance helper */ @@ -1174,6 +1188,14 @@ return null; return inst[method].apply(inst, inst.varargs(arguments, 2)); + }, + + isEmpty: function(node) { + for (var child = node.firstElementChild; child != null; child = child.nextElementSibling) + if (!child.classList.contains('hidden')) + return false; + + return true; } }), diff --git a/luci-base/htdocs/luci-static/resources/ui.js b/luci-base/htdocs/luci-static/resources/ui.js index 2b4c8e033..29233dec0 100644 --- a/luci-base/htdocs/luci-static/resources/ui.js +++ b/luci-base/htdocs/luci-static/resources/ui.js @@ -213,16 +213,19 @@ var UISelect = UIElement.extend({ if (!Array.isArray(value)) value = (value != null && value != '') ? [ value ] : []; - if (!options.multi && value.length > 1) + if (!options.multiple && value.length > 1) value.length = 1; this.values = value; this.choices = choices; this.options = Object.assign({ - multi: false, + multiple: false, widget: 'select', orientation: 'horizontal' }, options); + + if (this.choices.hasOwnProperty('')) + this.options.optional = true; }, render: function() { @@ -240,10 +243,10 @@ var UISelect = UIElement.extend({ 'name': this.options.name, 'size': this.options.size, 'class': 'cbi-input-select', - 'multiple': this.options.multi ? '' : null + 'multiple': this.options.multiple ? '' : null })); - if (this.options.optional || this.choices.hasOwnProperty('')) + if (this.options.optional) frameEl.lastChild.appendChild(E('option', { 'value': '', 'selected': (this.values.length == 0 || this.values[0] == '') ? '' : null @@ -267,8 +270,8 @@ var UISelect = UIElement.extend({ E('input', { 'id': this.options.id ? 'widget.' + this.options.id : null, 'name': this.options.id || this.options.name, - 'type': this.options.multi ? 'checkbox' : 'radio', - 'class': this.options.multi ? 'cbi-input-checkbox' : 'cbi-input-radio', + 'type': this.options.multiple ? 'checkbox' : 'radio', + 'class': this.options.multiple ? 'cbi-input-checkbox' : 'cbi-input-radio', 'value': keys[i], 'checked': (this.values.indexOf(keys[i]) > -1) ? '' : null }), @@ -320,8 +323,8 @@ var UISelect = UIElement.extend({ if (value == null) value = ''; - for (var i = 0; i < this.node.options.length; i++) - this.node.options[i].selected = (this.node.options[i].value == value); + for (var i = 0; i < this.node.firstChild.options.length; i++) + this.node.firstChild.options[i].selected = (this.node.firstChild.options[i].value == value); return; } @@ -345,7 +348,7 @@ var UIDropdown = UIElement.extend({ this.choices = choices; this.options = Object.assign({ sort: true, - multi: Array.isArray(value), + multiple: Array.isArray(value), optional: true, select_placeholder: _('-- Please choose --'), custom_placeholder: _('-- custom --'), @@ -361,7 +364,7 @@ var UIDropdown = UIElement.extend({ var sb = E('div', { 'id': this.options.id, 'class': 'cbi-dropdown', - 'multiple': this.options.multi ? '' : null, + 'multiple': this.options.multiple ? '' : null, 'optional': this.options.optional ? '' : null, }, E('ul')); @@ -409,7 +412,7 @@ var UIDropdown = UIElement.extend({ bind: function(sb) { var o = this.options; - o.multi = sb.hasAttribute('multiple'); + o.multiple = sb.hasAttribute('multiple'); o.optional = sb.hasAttribute('optional'); o.placeholder = sb.getAttribute('placeholder') || o.placeholder; o.display_items = parseInt(sb.getAttribute('display-items') || o.display_items); @@ -425,7 +428,7 @@ var UIDropdown = UIElement.extend({ ndisplay = this.options.display_items, n = 0; - if (this.options.multi) { + if (this.options.multiple) { var items = ul.querySelectorAll('li'); for (var i = 0; i < items.length; i++) { @@ -657,7 +660,7 @@ var UIDropdown = UIElement.extend({ if (li.hasAttribute('unselectable')) return; - if (this.options.multi) { + if (this.options.multiple) { var cbox = li.querySelector('input[type="checkbox"]'), items = li.parentNode.querySelectorAll('li'), label = sb.querySelector('ul.preview'), @@ -780,7 +783,7 @@ var UIDropdown = UIElement.extend({ element: sb }; - if (this.options.multi) + if (this.options.multiple) detail.values = values; else detail.value = values.length ? values[0] : null; @@ -800,12 +803,12 @@ var UIDropdown = UIElement.extend({ for (var value in values) { this.createItems(sb, value); - if (!this.options.multi) + if (!this.options.multiple) break; } } - if (this.options.multi) { + if (this.options.multiple) { var lis = ul.querySelectorAll('li[data-value]'); for (var i = 0; i < lis.length; i++) { var value = lis[i].getAttribute('data-value'); @@ -857,7 +860,7 @@ var UIDropdown = UIElement.extend({ val = (value || '').trim(), ul = sb.querySelector('ul'); - if (!sbox.options.multi) + if (!sbox.options.multiple) val = val.length ? [ val ] : []; else val = val.length ? val.split(/\s+/) : []; @@ -881,7 +884,7 @@ var UIDropdown = UIElement.extend({ new_item = E(markup.replace(/{{value}}/g, '%h'.format(item))); - if (sbox.options.multi) { + if (sbox.options.multiple) { sbox.transformItem(sb, new_item); } else { @@ -1071,7 +1074,7 @@ var UIDropdown = UIElement.extend({ }, setValue: function(values) { - if (this.options.multi) { + if (this.options.multiple) { if (!Array.isArray(values)) values = (values != null && values != '') ? [ values ] : []; @@ -1104,7 +1107,7 @@ var UIDropdown = UIElement.extend({ for (var i = 0; i < h.length; i++) v.push(h[i].value); - return this.options.multi ? v : v[0]; + return this.options.multiple ? v : v[0]; } }); @@ -1116,7 +1119,7 @@ var UICombobox = UIDropdown.extend({ dropdown_items: -1, sort: true }, options, { - multi: false, + multiple: false, create: true, optional: true }) ]); @@ -1134,7 +1137,7 @@ var UIDynamicList = UIElement.extend({ this.values = values; this.choices = choices; this.options = Object.assign({}, options, { - multi: false, + multiple: false, optional: true }); }, @@ -1327,11 +1330,17 @@ var UIDynamicList = UIElement.extend({ getValue: function() { var items = this.node.querySelectorAll('.item > input[type="hidden"]'), + input = this.node.querySelector('.add-item > input[type="text"]'), v = []; for (var i = 0; i < items.length; i++) v.push(items[i].value); + if (input && input.value != null && input.value.match(/\S/) && + input.classList.contains('cbi-input-invalid') == false && + v.filter(function(s) { return s == input.value }).length == 0) + v.push(input.value); + return v; }, @@ -1580,8 +1589,14 @@ return L.Class.extend({ if (selected === null) { selected = this.getActiveTabId(groupId); - if (selected < 0 || selected >= panes.length) - selected = 0; + if (selected < 0 || selected >= panes.length || L.dom.isEmpty(panes[selected])) { + for (var i = 0; i < panes.length; i++) { + if (!L.dom.isEmpty(panes[i])) { + selected = i; + break; + } + } + } menu.childNodes[selected].classList.add('cbi-tab'); menu.childNodes[selected].classList.remove('cbi-tab-disabled'); @@ -1621,13 +1636,13 @@ return L.Class.extend({ return true; }, - updateTabs: function(ev) { - document.querySelectorAll('[data-tab-title]').forEach(function(pane) { + updateTabs: function(ev, root) { + (root || document).querySelectorAll('[data-tab-title]').forEach(function(pane) { var menu = pane.parentNode.previousElementSibling, tab = menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))), n_errors = pane.querySelectorAll('.cbi-input-invalid').length; - if (!pane.firstElementChild) { + if (L.dom.isEmpty(pane)) { tab.style.display = 'none'; tab.classList.remove('flash'); } @@ -1797,7 +1812,7 @@ return L.Class.extend({ return chg[1]; case 4: - return "'%ḧ'".format(chg[3].replace(/'/g, "'\"'\"'")); + return "'%h'".format(chg[3].replace(/'/g, "'\"'\"'")); default: return chg[m1-1]; @@ -1978,7 +1993,7 @@ return L.Class.extend({ } else { L.ui.changes.displayStatus('warning', - E('p', _('Apply request failed with status %h%>') + E('p', _('Apply request failed with status %h') .format(r.responseText || r.statusText || r.status))); window.setTimeout(function() { diff --git a/luci-base/root/www/index.html b/luci-base/root/www/index.html index e96417582..6899820a4 100644 --- a/luci-base/root/www/index.html +++ b/luci-base/root/www/index.html @@ -2,7 +2,7 @@ - +