luci2: Properly handle empty values in ListValue widgets
[project/luci2/ui.git] / luci2 / htdocs / luci2 / luci2.js
index f094b00..45584e7 100644 (file)
@@ -3684,6 +3684,18 @@ function LuCI2()
                        }
                }),
 
+               _render_change_indicator: function()
+               {
+                       return $('<ul />')
+                               .addClass('nav navbar-nav navbar-right')
+                               .append($('<li />')
+                                       .append($('<a />')
+                                               .attr('id', 'changes')
+                                               .attr('href', '#')
+                                               .append($('<span />')
+                                                       .addClass('label label-info'))));
+               },
+
                renderMainMenu: _luci2.rpc.declare({
                        object: 'luci2.ui',
                        method: 'menu',
@@ -3694,7 +3706,8 @@ function LuCI2()
 
                                $('#mainmenu')
                                        .empty()
-                                       .append(_luci2.globals.mainMenu.render(0, 1));
+                                       .append(_luci2.globals.mainMenu.render(0, 1))
+                                       .append(_luci2.ui._render_change_indicator());
                        }
                }),
 
@@ -3707,9 +3720,11 @@ function LuCI2()
 
                renderView: function()
                {
-                       var node = arguments[0];
-                       var name = node.view.split(/\//).join('.');
-                       var args = [ ];
+                       var node  = arguments[0];
+                       var name  = node.view.split(/\//).join('.');
+                       var cname = _luci2.toClassName(name);
+                       var views = _luci2.views || (_luci2.views = { });
+                       var args  = [ ];
 
                        for (var i = 1; i < arguments.length; i++)
                                args.push(arguments[i]);
@@ -3718,16 +3733,12 @@ function LuCI2()
                                _luci2.globals.currentView.finish();
 
                        _luci2.ui.renderViewMenu();
-
-                       if (!_luci2._views)
-                               _luci2._views = { };
-
                        _luci2.setHash('view', node.view);
 
-                       if (_luci2._views[name] instanceof _luci2.ui.view)
+                       if (views[cname] instanceof _luci2.ui.view)
                        {
-                               _luci2.globals.currentView = _luci2._views[name];
-                               return _luci2._views[name].render.apply(_luci2._views[name], args);
+                               _luci2.globals.currentView = views[cname];
+                               return views[cname].render.apply(views[cname], args);
                        }
 
                        var url = _luci2.globals.resource + '/view/' + name + '.js';
@@ -3747,13 +3758,13 @@ function LuCI2()
 
                                        var viewConstructor = eval(viewConstructorSource);
 
-                                       _luci2._views[name] = new viewConstructor({
+                                       views[cname] = new viewConstructor({
                                                name: name,
                                                acls: node.write || { }
                                        });
 
-                                       _luci2.globals.currentView = _luci2._views[name];
-                                       return _luci2._views[name].render.apply(_luci2._views[name], args);
+                                       _luci2.globals.currentView = views[cname];
+                                       return views[cname].render.apply(views[cname], args);
                                }
                                catch(e) {
                                        alert('Unable to instantiate view "%s": %s'.format(url, e));
@@ -3763,6 +3774,23 @@ function LuCI2()
                        });
                },
 
+               changeView: function()
+               {
+                       var name = _luci2.getHash('view');
+                       var node = _luci2.globals.defaultNode;
+
+                       if (name && _luci2.globals.mainMenu)
+                               node = _luci2.globals.mainMenu.getNode(name);
+
+                       if (node)
+                       {
+                               _luci2.ui.loading(true);
+                               _luci2.ui.renderView(node).then(function() {
+                                       _luci2.ui.loading(false);
+                               });
+                       }
+               },
+
                updateHostname: function()
                {
                        return _luci2.system.getBoardInfo().then(function(info) {
@@ -3788,6 +3816,7 @@ function LuCI2()
                                                switch (c[0])
                                                {
                                                case 'order':
+                                                       log.push('uci reorder %s.<ins>%s=<strong>%s</strong></ins>'.format(config, c[1], c[2]));
                                                        break;
 
                                                case 'remove':
@@ -3831,20 +3860,23 @@ function LuCI2()
 
                                if (n > 0)
                                        $('#changes')
-                                               .empty()
-                                               .show()
-                                               .append($('<a />')
-                                                       .attr('href', '#')
-                                                       .addClass('label')
-                                                       .addClass('notice')
-                                                       .text(_luci2.trcp('Pending configuration changes', '1 change', '%d changes', n).format(n))
-                                                       .click(function(ev) {
-                                                               _luci2.ui.dialog(_luci2.tr('Staged configuration changes'), html, { style: 'close' });
-                                                               ev.preventDefault();
-                                                       }));
+                                               .click(function(ev) {
+                                                       _luci2.ui.dialog(_luci2.tr('Staged configuration changes'), html, {
+                                                               style: 'confirm',
+                                                               confirm: function() {
+                                                                       _luci2.uci.apply().then(
+                                                                               function(code) { alert('Success with code ' + code); },
+                                                                               function(code) { alert('Error with code ' + code); }
+                                                                       );
+                                                               }
+                                                       });
+                                                       ev.preventDefault();
+                                               })
+                                               .children('span')
+                                                       .show()
+                                                       .text(_luci2.trcp('Pending configuration changes', '1 change', '%d changes', n).format(n));
                                else
-                                       $('#changes')
-                                               .hide();
+                                       $('#changes').children('span').hide();
                        });
                },
 
@@ -3853,13 +3885,19 @@ function LuCI2()
                        _luci2.ui.loading(true);
 
                        $.when(
+                               _luci2.session.updateACLs(),
                                _luci2.ui.updateHostname(),
                                _luci2.ui.updateChanges(),
-                               _luci2.ui.renderMainMenu()
+                               _luci2.ui.renderMainMenu(),
+                               _luci2.NetworkModel.init()
                        ).then(function() {
                                _luci2.ui.renderView(_luci2.globals.defaultNode).then(function() {
                                        _luci2.ui.loading(false);
-                               })
+                               });
+
+                               $(window).on('hashchange', function() {
+                                       _luci2.ui.changeView();
+                               });
                        });
                },
 
@@ -4073,10 +4111,7 @@ function LuCI2()
 
                _onclick: function(ev)
                {
-                       _luci2.ui.loading(true);
-                       _luci2.ui.renderView(ev.data).then(function() {
-                               _luci2.ui.loading(false);
-                       });
+                       _luci2.setHash('view', ev.data);
 
                        ev.preventDefault();
                        this.blur();
@@ -4129,7 +4164,7 @@ function LuCI2()
                                }
                                else
                                {
-                                       item.find('a').click(nodes[i], this._onclick);
+                                       item.find('a').click(nodes[i].view, this._onclick);
                                }
                        }
 
@@ -4931,6 +4966,7 @@ function LuCI2()
                        this.instance = { };
                        this.dependencies = [ ];
                        this.rdependency = { };
+                       this.events = { };
 
                        this.options = _luci2.defaults(options, {
                                placeholder: '',
@@ -4986,6 +5022,11 @@ function LuCI2()
                        return i.top;
                },
 
+               active: function(sid)
+               {
+                       return (this.instance[sid] && !this.instance[sid].disabled);
+               },
+
                ucipath: function(sid)
                {
                        return {
@@ -5125,14 +5166,30 @@ function LuCI2()
                        }
 
                        if (rv)
+                       {
                                for (var field in d.self.rdependency)
                                        d.self.rdependency[field].toggle(d.sid);
 
+                               d.self.section.tabtoggle(d.sid);
+                       }
+
                        return rv;
                },
 
                validator: function(sid, elem, multi)
                {
+                       var evdata = {
+                               self:   this,
+                               sid:    sid,
+                               elem:   elem,
+                               multi:  multi,
+                               inst:   this.instance[sid],
+                               opt:    this.options.optional
+                       };
+
+                       for (var evname in this.events)
+                               elem.on(evname, evdata, this.events[evname]);
+
                        if (typeof(this.options.datatype) == 'undefined' && $.isEmptyObject(this.rdependency))
                                return elem;
 
@@ -5140,13 +5197,13 @@ function LuCI2()
                        if (typeof(this.options.datatype) == 'string')
                        {
                                try {
-                                       vstack = _luci2.cbi.validation.compile(this.options.datatype);
+                                       evdata.vstack = _luci2.cbi.validation.compile(this.options.datatype);
                                } catch(e) { };
                        }
                        else if (typeof(this.options.datatype) == 'function')
                        {
                                var vfunc = this.options.datatype;
-                               vstack = [ function(elem) {
+                               evdata.vstack = [ function(elem) {
                                        var rv = vfunc(this, elem);
                                        if (rv !== true)
                                                validation.message = rv;
@@ -5154,16 +5211,6 @@ function LuCI2()
                                }, [ elem ] ];
                        }
 
-                       var evdata = {
-                               self:   this,
-                               sid:    sid,
-                               elem:   elem,
-                               multi:  multi,
-                               vstack: vstack,
-                               inst:   this.instance[sid],
-                               opt:    this.options.optional
-                       };
-
                        if (elem.prop('tagName') == 'SELECT')
                        {
                                elem.change(evdata, this._ev_validate);
@@ -5193,7 +5240,7 @@ function LuCI2()
                        return (i.disabled || i.error.text() == '');
                },
 
-               depends: function(d, v)
+               depends: function(d, v, add)
                {
                        var dep;
 
@@ -5238,7 +5285,11 @@ function LuCI2()
                        if ($.isEmptyObject(dep))
                                return this;
 
-                       this.dependencies.push(dep);
+                       if (!add || !this.dependencies.length)
+                               this.dependencies.push(dep);
+                       else
+                               for (var i = 0; i < this.dependencies.length; i++)
+                                       $.extend(this.dependencies[i], dep);
 
                        return this;
                },
@@ -5268,7 +5319,7 @@ function LuCI2()
                                                        break;
                                                }
                                        }
-                                       else if (typeof(cmp) == 'string')
+                                       else if (typeof(cmp) == 'string' || typeof(cmp) == 'number')
                                        {
                                                if (val != cmp)
                                                {
@@ -5313,6 +5364,12 @@ function LuCI2()
                        }
 
                        return false;
+               },
+
+               on: function(evname, evfunc)
+               {
+                       this.events[evname] = evfunc;
+                       return this;
                }
        });
 
@@ -5432,7 +5489,7 @@ function LuCI2()
                        var s = $('<select />')
                                .addClass('form-control');
 
-                       if (this.options.optional)
+                       if (this.options.optional && !this.has_empty)
                                $('<option />')
                                        .attr('value', '')
                                        .text(_luci2.tr('-- Please choose --'))
@@ -5455,6 +5512,9 @@ function LuCI2()
                        if (!this.choices)
                                this.choices = [ ];
 
+                       if (k == '')
+                               this.has_empty = true;
+
                        this.choices.push([k, v || k]);
                        return this;
                }
@@ -5529,10 +5589,7 @@ function LuCI2()
                        {
                                ev.data.select.hide();
                                ev.data.input.show().focus();
-
-                               var v = ev.data.input.val();
-                               ev.data.input.val(' ');
-                               ev.data.input.val(v);
+                               ev.data.input.val('');
                        }
                        else if (self.options.optional && s.selectedIndex == 0)
                        {
@@ -5542,6 +5599,8 @@ function LuCI2()
                        {
                                ev.data.input.val(ev.data.select.val());
                        }
+
+                       ev.stopPropagation();
                },
 
                _blur: function(ev)
@@ -5552,7 +5611,7 @@ function LuCI2()
 
                        ev.data.select.empty();
 
-                       if (self.options.optional)
+                       if (self.options.optional && !self.has_empty)
                                $('<option />')
                                        .attr('value', '')
                                        .text(_luci2.tr('-- please choose --'))
@@ -5582,7 +5641,7 @@ function LuCI2()
                                .appendTo(ev.data.select);
 
                        ev.data.input.hide();
-                       ev.data.select.val(val).show().focus();
+                       ev.data.select.val(val).show().blur();
                },
 
                _enter: function(ev)
@@ -5629,6 +5688,9 @@ function LuCI2()
                        if (!this.choices)
                                this.choices = [ ];
 
+                       if (k == '')
+                               this.has_empty = true;
+
                        this.choices.push([k, v || k]);
                        return this;
                },
@@ -6122,6 +6184,30 @@ function LuCI2()
                        return w;
                },
 
+               tabtoggle: function(sid)
+               {
+                       for (var i = 0; i < this.tabs.length; i++)
+                       {
+                               var tab = this.tabs[i];
+                               var elem = $('#' + this.id('nodetab', sid, tab.id));
+                               var empty = true;
+
+                               for (var j = 0; j < tab.fields.length; j++)
+                               {
+                                       if (tab.fields[j].active(sid))
+                                       {
+                                               empty = false;
+                                               break;
+                                       }
+                               }
+
+                               if (empty && elem.is(':visible'))
+                                       elem.fadeOut();
+                               else if (!empty)
+                                       elem.fadeIn();
+                       }
+               },
+
                ucipackages: function(pkg)
                {
                        for (var i = 0; i < this.tabs.length; i++)
@@ -6187,8 +6273,7 @@ function LuCI2()
 
                validate: function()
                {
-                       this.error_count = 0;
-
+                       var errors = 0;
                        var as = this.sections();
 
                        for (var i = 0; i < as.length; i++)
@@ -6196,19 +6281,19 @@ function LuCI2()
                                var invals = this.validate_section(as[i]['.name']);
 
                                if (invals > 0)
-                                       this.error_count += invals;
+                                       errors += invals;
                        }
 
                        var badge = $('#' + this.id('sectiontab')).children('span:first');
 
-                       if (this.error_count > 0)
+                       if (errors > 0)
                                badge.show()
-                                       .text(this.error_count)
-                                       .attr('title', _luci2.trp('1 Error', '%d Errors', this.error_count).format(this.error_count));
+                                       .text(errors)
+                                       .attr('title', _luci2.trp('1 Error', '%d Errors', errors).format(errors));
                        else
                                badge.hide();
 
-                       return (this.error_count == 0);
+                       return (errors == 0);
                }
        });
 
@@ -6219,7 +6304,6 @@ function LuCI2()
                        this.options  = options;
                        this.tabs     = [ ];
                        this.fields   = { };
-                       this.error_count  = 0;
                        this.active_panel = 0;
                        this.active_tab   = { };
                },
@@ -6852,6 +6936,16 @@ function LuCI2()
                }
        });
 
+       this.cbi.SingleSection = this.cbi.NamedSection.extend({
+               render: function()
+               {
+                       this.instance = { };
+                       this.instance[this.uci_type] = { tabs: [ ] };
+
+                       return this._render_section_body(this.uci_type, 0);
+               }
+       });
+
        this.cbi.DummySection = this.cbi.TypedSection.extend({
                sections: function(cb)
                {
@@ -6998,6 +7092,20 @@ function LuCI2()
                        return body;
                },
 
+               _render_footer: function()
+               {
+                       return $('<div />')
+                               .addClass('panel panel-default panel-body text-right')
+                               .append($('<div />')
+                                       .addClass('btn-group')
+                                       .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
+                                               .click({ self: this }, function(ev) {  }))
+                                       .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
+                                               .click({ self: this }, function(ev) { ev.data.self.send(); }))
+                                       .append(_luci2.ui.button(_luci2.tr('Reset'), 'default')
+                                               .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); })));
+               },
+
                render: function()
                {
                        var map = $('<form />');
@@ -7013,18 +7121,7 @@ function LuCI2()
                        map.append(this._render_body());
 
                        if (this.options.pageaction !== false)
-                       {
-                               map.append($('<div />')
-                                       .addClass('panel panel-default panel-body text-right')
-                                       .append($('<div />')
-                                               .addClass('btn-group')
-                                               .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
-                                                       .click({ self: this }, function(ev) {  }))
-                                               .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
-                                                       .click({ self: this }, function(ev) { ev.data.self.send(); }))
-                                               .append(_luci2.ui.button(_luci2.tr('Reset'), 'default')
-                                                       .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); }))));
-                       }
+                               map.append(this._render_footer());
 
                        return map;
                },
@@ -7184,4 +7281,55 @@ function LuCI2()
                        });
                }
        });
+
+       this.cbi.Modal = this.cbi.Map.extend({
+               _render_footer: function()
+               {
+                       return $('<div />')
+                               .addClass('btn-group')
+                               .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
+                                       .click({ self: this }, function(ev) {  }))
+                               .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
+                                       .click({ self: this }, function(ev) { ev.data.self.send(); }))
+                               .append(_luci2.ui.button(_luci2.tr('Cancel'), 'default')
+                                       .click({ self: this }, function(ev) { _luci2.ui.dialog(false); }));
+               },
+
+               render: function()
+               {
+                       var modal = _luci2.ui.dialog(this.label('caption'), null, { wide: true });
+                       var map = $('<form />');
+
+                       var desc = this.label('description');
+                       if (desc)
+                               map.append($('<p />').text(desc));
+
+                       map.append(this._render_body());
+
+                       modal.find('.modal-body').append(map);
+                       modal.find('.modal-footer').append(this._render_footer());
+
+                       return modal;
+               },
+
+               redraw: function()
+               {
+                       this.render();
+                       this.finish();
+               },
+
+               show: function()
+               {
+                       var self = this;
+
+                       _luci2.ui.loading(true);
+
+                       return self.load().then(function() {
+                               self.render();
+                               self.finish();
+
+                               _luci2.ui.loading(false);
+                       });
+               }
+       });
 };