diff --git a/index.html b/index.html index c58e77e..8eeefda 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,7 @@ - + diff --git a/js/directives/fileselect.js b/js/directives/fileselect.js index 6700691..d1f5f61 100644 --- a/js/directives/fileselect.js +++ b/js/directives/fileselect.js @@ -12,8 +12,10 @@ app.directive("indeterminate", [ // Whenever the bound value of the attribute changes we update // use upward emit notification for change to prevent the performance penalty bring by $scope.$watch var getter = parse(attr["ngModel"]); - var setter = getter.assign; + // var setter = getter.assign; var children = []; // cache children input + var cacheSelectedSubInputNumber = 0; + var cacheNoSelectedSubInputNumber = 0; var get = function () { return getter(scope); }; @@ -21,18 +23,18 @@ app.directive("indeterminate", [ var setIndeterminateState = function (newValue) { elem.prop("indeterminate", newValue); }; - var setWithSideEffect = function (newVal) { + var setModelValueWithSideEffect = function (newVal) { // will cause to emit corresponding events ngModelCtrl.$setViewValue(newVal); ngModelCtrl.$render(); }; - var passIfLeafChild = function (callback) { // ensure to execute callback when this input have one or more subinputs + var passIfIsLeafChild = function (callback) { // ensure to execute callback only when this input has one or more subinputs return function () { if (children.length > 0) { callback.apply(this, arguments); } }; }; - var passIfNotIsLeafChild = function (callback) { // ensure to execute callback when this input havent subinput + var passIfNotIsLeafChild = function (callback) { // ensure to execute callback only when this input hasn't subinput return function () { if (children.length === 0) { callback.apply(this, arguments); @@ -46,19 +48,27 @@ app.directive("indeterminate", [ } }; }; + var catchEventOnlyOnce = function (callback) { // only fire once, and stop event's propagation + return function (event) { + callback.apply(this, arguments); + return event.stopPropagation(); + }; + }; + if (attr["indeterminate"] && parse(attr["indeterminate"]).constant) { + setIndeterminateState(scope.$eval(attr["indeterminate"])); // set to default value (set in template) + } if (attr["indeterminate"] && parse(attr["indeterminate"]).constant && !scope.$eval(attr["indeterminate"])) { - // is leaf input, Only receive parent change and emit child change event - setIndeterminateState(scope.$eval(attr["indeterminate"])); + // when this input wont have subinput, they will only receive parent change and emit child change event ngModelCtrl.$viewChangeListeners.push(passIfNotIsLeafChild(function () { - scope.$emit("childSelectedChange"); + scope.$emit("childSelectedChange", get()); })); - scope.$on("ParentSelectedChange", passThroughThisScope( + scope.$on("ParentSelectedChange", passThroughThisScope(passIfNotIsLeafChild( function (event, newVal) { - setWithSideEffect(newVal); // set value to parent's value; this will cause listener to emit childChange event; this won't be a infinite loop - })); + setModelValueWithSideEffect(newVal); // set value to parent's value; this will cause listener to emit childChange event; this won't be a infinite loop + }))); // init first time and only once scope.$emit("i'm child input", get); - scope.$emit("childSelectedChange"); // force emitted, and force the parent change their state base on children at first time + scope.$emit("childSelectedChange", get()); // force emitted, and force the parent change their state base on children at first time } else { // establish parent-child's relation // listen for the child emitted token @@ -67,25 +77,42 @@ app.directive("indeterminate", [ children.push(child); }) ); - var updateBaseOnChildrenState = function () { - var allSelected = children.every(function (child) { - return child(); - }); - var anySeleted = children.some(function (child) { - return child(); - }); + var updateBaseOnChildrenState = function (event, newChildValue) { + if ((cacheSelectedSubInputNumber + cacheNoSelectedSubInputNumber) !== children.length) { + cacheSelectedSubInputNumber = 0; + cacheNoSelectedSubInputNumber = 0; + for (var i = 0; i < children.length; i++) { + if (children[i]()) { + cacheSelectedSubInputNumber += 1; + } else { + cacheNoSelectedSubInputNumber += 1; + } + } + } else { + // no need for recalculated children state + // just make a few change to cache value + if (newChildValue) { + cacheSelectedSubInputNumber++; + cacheNoSelectedSubInputNumber--; + } else { + cacheSelectedSubInputNumber--; + cacheNoSelectedSubInputNumber++; + } + } + var allSelected = (cacheNoSelectedSubInputNumber === 0); + var anySeleted = (cacheSelectedSubInputNumber > 0); setIndeterminateState(allSelected !== anySeleted); // if at least one is selected, but not all then set input property indeterminate to true - setWithSideEffect(allSelected); + setModelValueWithSideEffect(allSelected); }; // is not leaf input, Only receive child change and parent change event - ngModelCtrl.$viewChangeListeners.push(passIfLeafChild(function () { + ngModelCtrl.$viewChangeListeners.push(passIfIsLeafChild(function () { // emit when property indeterminate is set to false, prevent recursively emitting event from parent to children, children to parent if (!elem.prop("indeterminate")) { scope.$broadcast("ParentSelectedChange", get()); } })); // reset input state base on children inputs - scope.$on("childSelectedChange", passThroughThisScope(passIfLeafChild(updateBaseOnChildrenState))); + scope.$on("childSelectedChange", passThroughThisScope(passIfIsLeafChild(updateBaseOnChildrenState))); } } };