summaryrefslogtreecommitdiffstats
path: root/web/node.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/node.js')
-rw-r--r--web/node.js310
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;
+ });
+ }
+ }
+ };
+});