webui-aria2/js/directives/fileselect.js
2017-07-29 17:35:03 +02:00

94 lines
5.1 KiB
JavaScript

// watches changes in the file upload control (input[file]) and
// puts the files selected in an attribute
var app = angular.module("webui.directives.fileselect", ["webui.services.deps"]);
app.directive("indeterminate", [
"$parse", function syncInput (parse) {
return {
require : "ngModel",
// Restrict the directive so it can only be used as an attribute
restrict : "A",
link : function link (scope, elem, attr, ngModelCtrl) {
// 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 children = []; // cache children input
var get = function () {
return getter(scope);
};
// the internal 'indeterminate' flag on the attached dom element
var setIndeterminateState = function (newValue) {
elem.prop("indeterminate", newValue);
};
var setWithSideEffect = function (newVal) {
ngModelCtrl.$setViewValue(newVal);
ngModelCtrl.$render();
};
var passIfLeafChild = function (callback) { // ensure to execute callback when this input have 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
return function () {
if (children.length === 0) {
callback.apply(this, arguments);
}
};
};
var passThroughThisScope = function (callback) { // pass through the event from the scope where they sent
return function (event) {
if (event.targetScope !== event.currentScope) {
return callback.apply(this, arguments);
}
};
};
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"]));
ngModelCtrl.$viewChangeListeners.push(passIfNotIsLeafChild(function () {
scope.$emit("childSelectedChange");
}));
scope.$on("ParentSelectedChange", passThroughThisScope(
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
}));
// 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
} else {
// establish parent-child's relation
// listen for the child emitted token
scope.$on("i'm child input", passThroughThisScope(
function (event, child) {
children.push(child);
})
);
var updateBaseOnChildrenState = function () {
var allSelected = children.every(function (child) {
return child();
});
var anySeleted = children.some(function (child) {
return child();
});
setIndeterminateState(allSelected !== anySeleted); // if at least one is selected, but not all then set input property indeterminate to true
setWithSideEffect(allSelected);
};
// is not leaf input, Only receive child change and parent change event
ngModelCtrl.$viewChangeListeners.push(passIfLeafChild(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)));
}
}
};
}
]);