diff options
Diffstat (limited to 'web/node.js')
-rw-r--r-- | web/node.js | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/web/node.js b/web/node.js new file mode 100644 index 0000000..22e4a38 --- /dev/null +++ b/web/node.js @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2012-2015 Kaarle Ritvanen + * See LICENSE file for license details + */ + +angular.module("aconf").directive("aconfFields", function() { + return { + restrict: "E", + scope: {node: "=", value: "=path"}, + templateUrl: "directive/fields.html", + link: function(scope) { + scope.layout = scope.node && scope.node.meta.widget || "default"; + } + } +}).directive("aconfInline", function() { + return { + restrict: "E", + scope: true, + transclude: true, + template: '<div class="aconf-node"></div>', + link: function(scope, element, attrs, ctrl, transclude) { + scope.$watch("node", function(node) { + if (node) + transclude(scope, function(clone) { + element.children().append(clone); + }); + }); + } + }; +}).directive("aconfNode", function() { + return { + restrict: "C", + require: ["?aconfField", "?^^aconfNode", "^^aconfLayout"], + link: { + pre: function(scope, element, attrs, ctrl) { + var field = ctrl[0]; + var parent = ctrl[1]; + var layout = ctrl[2]; + + var fscope = field ? field.scope : scope; + + if (parent) { + scope.level = parent.level + 1; + layout.query(fscope.value).then(function(node) { + scope.node = node; + fscope.node = node; + }); + } + else scope.level = 1; + + scope.$watch("node", function(node) { + if (!node) return; + + scope.checkValidity = function(initial) { + var valid = node.validate(); + fscope.invalid = !node.isNodeValid(); + if (!(initial && valid)) layout.validationReady(valid); + }; + + scope.checkValidity(true); + }); + } + }, + controller: function($scope) { + Object.defineProperty(this, "level", {get: function() { + return $scope.level; + }}); + Object.defineProperty(this, "data", {get: function() { + return $scope.node; + }}); + this.updated = function(field) { + $scope.checkValidity(); + $scope.$broadcast("updated", field); + }; + } + }; +}).directive("aconfHeader", function() { + return { + restrict: "E", + link: function(scope, element) { + element.html( + $("<h" + Math.min(6, scope.level) + ">") + .text(scope.node.meta["ui-name"]) + ); + scope.$watch("invalid", function(invalid) { + if (invalid) element.addClass("invalid"); + else element.removeClass("invalid"); + }); + } + }; +}).directive("aconfCheckboxes", function() { + return { + restrict: "E", + scope: true, + templateUrl: "directive/checkboxes.html", + link: function(scope) { + scope.choices = _.filter( + scope.node.meta.members.choice, + function(ch) { + ch.checked = _.contains(scope.node.data, ch.value); + return ch.enabled || ch.checked; + } + ); + } + }; +}).directive("aconfSetCheckbox", function() { + return { + restrict: "E", + scope: {choice: "="}, + templateUrl: "directive/set-checkbox.html", + link: function(scope) { + scope.name = scope.choice.value; + scope.value = scope.choice.checked; + scope.set = true; + } + }; +}).directive("aconfModel", function() { + return { + restrict: "E", + scope: true, + transclude: true, + template: '<div class="aconf-model-fields"></div>', + link: { + pre: function(scope, element, attrs, ctrl, transclude) { + scope.isRelevant = function(field) { + return scope.node.match(field.condition); + }; + + scope.$on("updated", function(event, updatedField) { + _.each(scope.fields, function(field, i, fields) { + if (field.name == updatedField || + !scope.isRelevant(field)) + return; + + if (field.dynamic) + scope.node.metaRequest(field.name).success( + function(data) { fields[i] = data; } + ); + + if (field.dynamic || + (field.condition && + updatedField in field.condition)) + fields[i] = _.clone(field); + }); + }); + + transclude(scope, function(clone) { + element.children().append(clone); + }); + } + } + }; +}).directive("aconfModelFields", function(aconfType) { + return { + restrict: "C", + require: "?aconfField", + link: function(scope, element, attrs, ctrl) { + if (ctrl) scope = ctrl.scope; + scope.$watch("node", function(node) { + if (!node) return; + + scope.fields = _.where(node.meta.fields, {visible: true}); + scope.columns = _.filter(scope.fields, function(field) { + return !(field.condition || aconfType.isCollection(field)); + }); + scope.$emit("columns", _.pluck(scope.columns, "ui-name")); + + scope.invoke = function(action) { + node.invoke(action).then(function() { + alert("Done"); + }, function() { alert("Fail"); }); + }; + }); + } + }; +}).directive( + "aconfCollection", function($modal, $q, aconfErrorFormat, aconfType) { + return { + restrict: "E", + scope: true, + templateUrl: "directive/collection.html", + require: "^^aconfLayout", + link: function(scope, element, attrs, ctrl) { + var node = scope.node; + var meta = node.meta; + + scope.$watch("node.data", function(data) { + if (!aconfType.isCollection(meta)) return; + + scope.fields = _.map(data, function(value, name) { + if (meta.type == "set") name = data[name]; + else if (_.isArray(data)) name++; + var set = meta.type == "set"; + return { + name: name, + label: set ? null : meta["ui-member"] + " " + name, + editable: meta.editable && !set, + removable: _.contains(meta.removable, name), + meta: meta.members + }; + }); + }, true); + + if (!meta.editable) return; + + var list = meta.type == "list"; + var set = meta.type == "set"; + + function insert(name) { + return $q(function(resolve, reject) { + var task; + if (aconfType.isTreeNode(meta.members)) + task = node.set(name, {}); + else if (set) task = node.add(name); + + if (task) + task.then(resolve, function(resp) { + node.delete(name); + reject(resp); + }); + + else node.set(name, null).catch(function(resp) { + resolve(false); + }); + }); + } + + if (list) { + scope.$watch( + "!node.data.length || node.get(node.data.length, true) != null", + function(saved) { + scope.insert = saved && function() { + ctrl.updateRequest(insert(node.data.length + 1)); + }; + } + ); + + scope.sortable = { + start: function(event, ui) { + var index = ui.item.index(); + + if (!node.isSubtreeValid()) { + ui.item.sortable.cancel(); + index = -1; + } + + ui.item.data("index", index); + }, + stop: function(event, ui) { + var oldIndex = ui.item.data("index") + 1; + var newIndex = ui.item.index() + 1; + if (oldIndex && newIndex != oldIndex) + ctrl.updateRequest( + node.move(oldIndex, newIndex) + ); + } + }; + + return; + } + + scope.insert = function() { + $modal.open({ + backdrop: false, + templateUrl: "insert.html", + controller: function($scope, $modalInstance) { + $scope.submit = function() { + var name = $scope.value; + + if (set ? _.contains(node.data, name) : + name in node.data) { + ctrl.validationReady( + "Already exists: " + name + ); + return; + } + + insert(name).then(function(txnValid) { + $modalInstance.close(); + ctrl.validationReady(txnValid); + }, function(resp) { + $scope.error = aconfErrorFormat(resp); + }); + }; + + $scope.close = function() { + $modalInstance.dismiss(); + }; + } + }).rendered.then(function($element) { + $(".modal-body input[type=text]").focus(); + }); + }; + } + }; + } +).directive("aconfTabularLayout", function() { + return { + restrict: "E", + scope: true, + templateUrl: "directive/tabular-layout.html", + link: { + pre: function(scope) { + scope.$on("columns", function(event, columns) { + scope.columns = columns; + }); + } + } + }; +}); |