luci2: Properly handle empty values in ListValue widgets
[project/luci2/ui.git] / luci2 / htdocs / luci2 / luci2.js
index fd2c671..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());
                        }
                }),
 
@@ -3847,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();
                        });
                },
 
@@ -4950,6 +4966,7 @@ function LuCI2()
                        this.instance = { };
                        this.dependencies = [ ];
                        this.rdependency = { };
+                       this.events = { };
 
                        this.options = _luci2.defaults(options, {
                                placeholder: '',
@@ -5005,6 +5022,11 @@ function LuCI2()
                        return i.top;
                },
 
+               active: function(sid)
+               {
+                       return (this.instance[sid] && !this.instance[sid].disabled);
+               },
+
                ucipath: function(sid)
                {
                        return {
@@ -5144,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;
 
@@ -5159,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;
@@ -5173,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);
@@ -5212,7 +5240,7 @@ function LuCI2()
                        return (i.disabled || i.error.text() == '');
                },
 
-               depends: function(d, v)
+               depends: function(d, v, add)
                {
                        var dep;
 
@@ -5257,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;
                },
@@ -5287,7 +5319,7 @@ function LuCI2()
                                                        break;
                                                }
                                        }
-                                       else if (typeof(cmp) == 'string')
+                                       else if (typeof(cmp) == 'string' || typeof(cmp) == 'number')
                                        {
                                                if (val != cmp)
                                                {
@@ -5332,6 +5364,12 @@ function LuCI2()
                        }
 
                        return false;
+               },
+
+               on: function(evname, evfunc)
+               {
+                       this.events[evname] = evfunc;
+                       return this;
                }
        });
 
@@ -5451,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 --'))
@@ -5474,6 +5512,9 @@ function LuCI2()
                        if (!this.choices)
                                this.choices = [ ];
 
+                       if (k == '')
+                               this.has_empty = true;
+
                        this.choices.push([k, v || k]);
                        return this;
                }
@@ -5548,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)
                        {
@@ -5561,6 +5599,8 @@ function LuCI2()
                        {
                                ev.data.input.val(ev.data.select.val());
                        }
+
+                       ev.stopPropagation();
                },
 
                _blur: function(ev)
@@ -5571,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 --'))
@@ -5601,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)
@@ -5648,6 +5688,9 @@ function LuCI2()
                        if (!this.choices)
                                this.choices = [ ];
 
+                       if (k == '')
+                               this.has_empty = true;
+
                        this.choices.push([k, v || k]);
                        return this;
                },
@@ -6141,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++)
@@ -6206,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++)
@@ -6215,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);
                }
        });
 
@@ -6238,7 +6304,6 @@ function LuCI2()
                        this.options  = options;
                        this.tabs     = [ ];
                        this.fields   = { };
-                       this.error_count  = 0;
                        this.active_panel = 0;
                        this.active_tab   = { };
                },
@@ -7027,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 />');
@@ -7042,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;
                },
@@ -7213,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);
+                       });
+               }
+       });
 };