diff --git a/luciexpress/htdocs/index.html b/luciexpress/htdocs/index.html index a3a57dfde..42fb9573b 100644 --- a/luciexpress/htdocs/index.html +++ b/luciexpress/htdocs/index.html @@ -37,6 +37,7 @@ + diff --git a/luciexpress/htdocs/js/app.js b/luciexpress/htdocs/js/app.js index de69bff72..0ba2b7e30 100644 --- a/luciexpress/htdocs/js/app.js +++ b/luciexpress/htdocs/js/app.js @@ -61,7 +61,8 @@ angular.module("luci", [ 'angularModalService', "uiSwitch", "ngAnimate", - "gettext" + "gettext", + "checklist-model" ]); angular.module("luci") diff --git a/luciexpress/htdocs/js/uci.js b/luciexpress/htdocs/js/uci.js index 24fbeebd1..472bacc1f 100644 --- a/luciexpress/htdocs/js/uci.js +++ b/luciexpress/htdocs/js/uci.js @@ -192,6 +192,9 @@ angular.module("luci") function UCIField(value, schema){ if(!schema) throw new Error("No schema specified for the field!"); this.ovalue = value; + if(value != null && value instanceof Array) { + this.ovalue = []; Object.assign(this.ovalue, value); + } this.dirty = false; this.uvalue = undefined; this.schema = schema; @@ -199,6 +202,9 @@ angular.module("luci") UCIField.prototype = { $reset: function(value){ this.ovalue = this.uvalue = value; + if(value != null && value instanceof Array) { + this.ovalue = []; Object.assign(this.ovalue, value); + } this.dirty = false; }, get value(){ @@ -497,8 +503,11 @@ angular.module("luci") next("could not commit config: "+err); }); }, function(err){ - if(err) deferred.reject(err); - else deferred.resolve(err); + // this is to always make sure that we do this outside of this code flow + setTimeout(function(){ + if(err) deferred.reject(err); + else deferred.resolve(err); + },0); }); }); return deferred.promise(); diff --git a/luciexpress/htdocs/lib/js/angular-checklist-model.js b/luciexpress/htdocs/lib/js/angular-checklist-model.js new file mode 100644 index 000000000..146fd91e8 --- /dev/null +++ b/luciexpress/htdocs/lib/js/angular-checklist-model.js @@ -0,0 +1,116 @@ +/** + * Checklist-model + * AngularJS directive for list of checkboxes + */ + +angular.module('checklist-model', []) +.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) { + // contains + function contains(arr, item, comparator) { + if (angular.isArray(arr)) { + for (var i = arr.length; i--;) { + if (comparator(arr[i], item)) { + return true; + } + } + } + return false; + } + + // add + function add(arr, item, comparator) { + arr = angular.isArray(arr) ? arr : []; + if(!contains(arr, item, comparator)) { + arr.push(item); + } + return arr; + } + + // remove + function remove(arr, item, comparator) { + if (angular.isArray(arr)) { + for (var i = arr.length; i--;) { + if (comparator(arr[i], item)) { + arr.splice(i, 1); + break; + } + } + } + return arr; + } + + // http://stackoverflow.com/a/19228302/1458162 + function postLinkFn(scope, elem, attrs) { + // compile with `ng-model` pointing to `checked` + $compile(elem)(scope); + + // getter / setter for original model + var getter = $parse(attrs.checklistModel); + var setter = getter.assign; + var checklistChange = $parse(attrs.checklistChange); + + // value added to list + var value = $parse(attrs.checklistValue)(scope.$parent); + + + var comparator = angular.equals; + + if (attrs.hasOwnProperty('checklistComparator')){ + comparator = $parse(attrs.checklistComparator)(scope.$parent); + } + + // watch UI checked change + scope.$watch('checked', function(newValue, oldValue) { + if (newValue === oldValue) { + return; + } + var current = getter(scope.$parent); + if (newValue === true) { + setter(scope.$parent, add(current, value, comparator)); + } else { + setter(scope.$parent, remove(current, value, comparator)); + } + + if (checklistChange) { + checklistChange(scope); + } + }); + + // declare one function to be used for both $watch functions + function setChecked(newArr, oldArr) { + scope.checked = contains(newArr, value, comparator); + } + + // watch original model change + // use the faster $watchCollection method if it's available + if (angular.isFunction(scope.$parent.$watchCollection)) { + scope.$parent.$watchCollection(attrs.checklistModel, setChecked); + } else { + scope.$parent.$watch(attrs.checklistModel, setChecked, true); + } + } + + return { + restrict: 'A', + priority: 1000, + terminal: true, + scope: true, + compile: function(tElement, tAttrs) { + if (tElement[0].tagName !== 'INPUT' || tAttrs.type !== 'checkbox') { + throw 'checklist-model should be applied to `input[type="checkbox"]`.'; + } + + if (!tAttrs.checklistValue) { + throw 'You should provide `checklist-value`.'; + } + + // exclude recursion + tElement.removeAttr('checklist-model'); + + // local scope var storing individual checkbox model + tElement.attr('ng-model', 'checked'); + + return postLinkFn; + } + }; +}]); diff --git a/luciexpress/htdocs/plugins/core/widgets/luci.config.js b/luciexpress/htdocs/plugins/core/widgets/luci.config.js index fe92431c1..20583037c 100644 --- a/luciexpress/htdocs/plugins/core/widgets/luci.config.js +++ b/luciexpress/htdocs/plugins/core/widgets/luci.config.js @@ -43,17 +43,20 @@ $juci.module("core") var plugin_root = $juci.module("core").plugin_root; return { template: '