summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-09-11 11:28:00 +0300
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-09-11 14:23:01 +0300
commitbdd932cf8461214ddda2686aba81151c65c9092f (patch)
treeff529cfaa6fe4369a1102c06cd0ecaf4b7c637c5
parent35d0195c76dff143eeb196b371cc06c0c24d623d (diff)
downloadaconf-bdd932cf8461214ddda2686aba81151c65c9092f.tar.bz2
aconf-bdd932cf8461214ddda2686aba81151c65c9092f.tar.xz
web client: inline widget for nested object views
-rw-r--r--acf/model/field.lua7
-rw-r--r--acf/model/init.lua1
-rw-r--r--web/client.js472
3 files changed, 266 insertions, 214 deletions
diff --git a/acf/model/field.lua b/acf/model/field.lua
index 362c7ab..707cde3 100644
--- a/acf/model/field.lua
+++ b/acf/model/field.lua
@@ -173,6 +173,11 @@ end
M.TreeNode = class(M.Field)
+function M.TreeNode:init(params)
+ if not params.widget then params.widget = 'link' end
+ super(self, M.TreeNode):init(params)
+end
+
function M.TreeNode:topology(context)
local res = super(self, M.TreeNode):topology(context)
res[1].type = 'table'
@@ -224,9 +229,7 @@ function M.Model:init(params)
assert(self.model)
self.itype = self.model
-
self.dtype = 'model'
- self.widget = self.dtype
end
diff --git a/acf/model/init.lua b/acf/model/init.lua
index 0254fce..2a80b1a 100644
--- a/acf/model/init.lua
+++ b/acf/model/init.lua
@@ -153,7 +153,6 @@ function M.Collection:init(params, itype)
}
self.dtype = 'collection'
- self.widget = self.dtype
end
function M.Collection:auto_ui_name(name)
diff --git a/web/client.js b/web/client.js
index 0bed053..4305c4b 100644
--- a/web/client.js
+++ b/web/client.js
@@ -39,10 +39,6 @@ $(function() {
);
}
- function href() {
- return $("<a>").attr({href: "javascript:void(0);"});
- }
-
var txnMgr = (function(token) {
var txn, changed, invalid;
@@ -227,7 +223,7 @@ $(function() {
for (var i = name; i < length; i++) {
var opath = join(path, i + 1);
var npath = join(path, i);
-
+
_.each(
[changed, invalid],
function(map) {
@@ -297,45 +293,66 @@ $(function() {
+ function href() {
+ return $("<a>").attr({href: "javascript:void(0);"});
+ }
+
var Field = {
- format: function(value, status, label) {
- var el = this.staticRender(value);
+ format: function(value, status, label, level) {
+ var el = this.staticRender(value, level);
this.setElStatus(el, status);
if (label) return this.wrap(el, label).row;
return el;
},
- staticRender: function(value) { return $("<div>").text(value); },
+
+ staticRender: function(value, level) {
+ return $("<div>").text(value);
+ },
+
setElStatus: function(el, status) { el.prop("class", status); },
- wrap: function(el, label) {
+
+ wrap: function(el, label, remove) {
var row = $("<tr>");
row.append($("<td>").text(label));
+
var td = $("<td>");
var msg = $("<div>");
td.append(msg);
td.append(el);
row.append(td);
+
+ if (remove)
+ row.append(
+ $("<td>").html(href().click(remove).text("Delete"))
+ );
+
return {row: row, msg: msg};
},
- init: function(value, meta, callback, label) {
- this.el = this.render(value, meta);
- this.el.change(callback);
+
+ init: function(value, meta, update, remove, label, level) {
+ this.el = this.render(value, meta, level);
+ this.el.change(update);
if (!label) return this.el;
- this.els = this.wrap(this.el, label);
+ this.els = this.wrap(this.el, label, remove);
return this.els.row;
},
- render: function(value, meta) {
+
+ render: function(value, meta, level) {
return $("<input>").attr({type: "text", value: value});
},
+
setMessage: function(msg, html) {
if (html) this.els.msg.html(msg);
else this.els.msg.text(msg);
},
+
setStatus: function(status) { this.setElStatus(this.el, status); },
+
get: function() { return this.el.val() || null; }
}
var ComboBox = Object.create(Field);
- ComboBox.render = function(value, meta) {
+ ComboBox.render = function(value, meta, level) {
var el = $("<select>");
function opt(value, ui_value, selected) {
@@ -358,19 +375,19 @@ $(function() {
}
var CheckBox = Object.create(Field);
- CheckBox.staticRender = function(value) {
+ CheckBox.staticRender = function(value, level) {
return $("<div>").text(value ? "Yes" : "No");
};
CheckBox.setElStatus = function(el, status) {
Field.setElStatus(el.parent(), status);
};
- CheckBox.render = function(value, meta) {
+ CheckBox.render = function(value, meta, level) {
return $("<input>").attr({type: "checkbox", checked: value});
};
CheckBox.get = function() { return this.el.is(":checked"); };
- var Path = Object.create(Field);
- Path.staticRender = function(value) {
+ var Link = Object.create(Field);
+ Link.staticRender = function(value, level) {
var el = href();
if (value) {
el.click(function() {
@@ -379,22 +396,36 @@ $(function() {
}
return el;
};
- Path.render = function(value, meta) {
- return this.staticRender(value);
+ Link.render = function(value, meta, level) {
+ return this.staticRender(value, level);
};
- Path.get = function() { return {}; };
+ Link.get = function() { return {}; };
- var Reference = Object.create(Path);
- Reference.staticRender = function(value) {
- return Path.staticRender(value).text(value);
+ Inline = Object.create(Link);
+ Inline.staticRender = function(value, level) {
+ var el = $("<div>");
+ var obj = $("<div>");
+ fetchAndRender(value, obj, level == 6 ? 6 : level + 1);
+ el.append(obj);
+ return el;
+ };
+ Inline.wrap = function(el, label, remove) {
+ if (remove) el.append(href().click(remove).text("Delete"));
+ return {row: el};
+ };
+ Inline.setStatus = function(status) {};
+
+ var Reference = Object.create(Link);
+ Reference.staticRender = function(value, level) {
+ return Link.staticRender(value, level).text(value);
};
Reference.setElStatus = function(el, status) {
ComboBox.setElStatus(el.find("select"), status);
};
- Reference.render = function(value, meta) {
+ Reference.render = function(value, meta, level) {
var link = $("<div>");
var update = _.bind(function() {
- link.html(Path.staticRender(this.get()));
+ link.html(Link.staticRender(this.get(), level));
}, this);
this.cbox = Object.create(ComboBox);
@@ -413,10 +444,10 @@ $(function() {
var widgets = {
boolean: CheckBox,
- collection: Path,
combobox: ComboBox,
field: Field,
- model: Path,
+ inline: Inline,
+ link: Link,
reference: Reference
}
@@ -440,6 +471,202 @@ $(function() {
}
+ function renderObject(path, data, target, level) {
+ target = target || $("#content");
+ level = level || 1;
+
+ target.html($("<h" + level + ">").text(data.meta["ui-name"]));
+
+ if (!isTreeNode(data.meta))
+ return target.append(JSON.stringify(data));
+
+ function validated(txnValid) {
+ if (txnValid)
+ setStatus(
+ "changed",
+ "You have uncommitted changes",
+ true
+ );
+ else setErrorStatus("Some values need checking");
+ }
+
+ var div = $("<div>");
+ target.append(div);
+
+ var table;
+ function appendRow(row) {
+ if (!table) {
+ table = $("<table>");
+ div.append(table);
+ }
+ table.append(row);
+ }
+
+ function renderField(
+ name, value, meta, label, editable, removable
+ ) {
+
+ var status = data.status(name);
+
+ if (!(meta.widget in widgets))
+ return $("<tr>").html($("<td>").text(value));
+
+ var widget = widgets[meta.widget];
+ if (!editable)
+ return widget.format(value, status, label, level);
+
+ widget = Object.create(widget);
+
+ function change() {
+ if (isTreeNode(meta)) return;
+
+ widget.setMessage("[checking]");
+ if ($("#status").prop("class") != "invalid")
+ statusBar.text("Validating changes");
+
+ data.set(name, widget.get()).done(function(txnValid) {
+ widget.setMessage("");
+ widget.setStatus(data.status(name));
+ validated(txnValid);
+
+ }).fail(function(xhr) {
+ if (_.isString(xhr)) widget.setMessage(xhr);
+
+ else if (xhr.statusCode().status == 422)
+ widget.setMessage(
+ _.reduce(
+ _.map(
+ $.parseJSON(xhr.responseText), _.escape
+ ),
+ function(a, b) {
+ return a + "<br/>" + b;
+ }
+ ),
+ true
+ );
+
+ else widget.setMessage(formatError("Error", xhr));
+
+ widget.setStatus("invalid");
+ validated(false);
+ });
+ }
+
+ var el = widget.init(
+ value,
+ meta,
+ change,
+ removable ? function() {
+ data.delete(name).done(function(txnValid) {
+ validated(txnValid)
+ fetchAndRender(path);
+ });
+ } : null,
+ label,
+ level
+ );
+
+ if (status == "invalid") change();
+
+ widget.setStatus(status);
+
+ if (el.is("tr")) appendRow(el);
+ else {
+ table = null;
+ div.append(el);
+ }
+ }
+
+ function renderCollectionMember(name, value, meta) {
+ var set = meta.type == "set";
+ renderField(
+ name,
+ value,
+ meta.members,
+ meta["ui-member"] + " " + name,
+ !set,
+ !set
+ );
+ }
+
+ if (data.meta.type == "model")
+ _.each(data.meta.fields, function(field) {
+ renderField(
+ field.name,
+ data.get(field.name),
+ field,
+ field["ui-name"],
+ true,
+ false
+ );
+ });
+
+ else _.each(data.data, function(value, name) {
+ if (_.isArray(data.data)) name++;
+ renderCollectionMember(name, data.get(name), data.meta);
+ });
+
+ if (_.contains(["collection", "list"], data.meta.type)) {
+ var keys = _.clone(_.keys(data.data));
+
+ var button = $("<input>").attr(
+ {type: "submit", value: "Insert"}
+ ).click(function() {
+
+ var getter;
+
+ function insert() {
+ var name = getter();
+
+ if (_.contains(keys, name)) {
+ button.prop("class", null);
+ return;
+ }
+ keys.push(name);
+
+ var tn = isTreeNode(data.meta.members);
+ data.set(
+ name, tn ? {} : null
+ ).done(function(txnValid) {
+ renderCollectionMember(
+ name,
+ tn ? join(path, name) : null,
+ data.meta
+ );
+ button.prop("class", null);
+ validated(txnValid);
+ });
+ }
+
+ button.prop("class", "hidden");
+
+ if (data.meta.type == "collection") {
+ var field = $("<input>").attr({type: "text"});
+ var row = $("<tr>").html($("<td>").html(field));
+ getter = function() {
+ var res = field.val();
+ row.remove();
+ return res;
+ }
+ field.change(insert);
+ appendRow(row);
+ }
+ else {
+ getter = function() { return data.data.length + 1; };
+ insert();
+ }
+ });
+ target.append($("<p>").html(button));
+ }
+ }
+
+ function fetchAndRender(path, target, level) {
+ txnMgr.query(path).done(function(data) {
+ renderObject(path, data, target, level);
+ });
+ }
+
+
function render() {
var path = $.param.fragment();
@@ -461,7 +688,7 @@ $(function() {
_.each(data.meta.fields, function(field) {
var el = $("<li>");
- var link = Path.format(data.get(field.name));
+ var link = Link.format(data.get(field.name));
link.text(field["ui-name"]);
el.prop("class", data.status(field.name));
if (field.name == current)
@@ -475,183 +702,6 @@ $(function() {
return def;
}
-
-
- var content = $("#content").empty();
-
- function renderContent(path, data) {
- content.html($("<h1>").text(data.meta["ui-name"]));
-
- if (!isTreeNode(data.meta)) {
- content.append(JSON.stringify(data));
- return;
- }
-
-
- function validated(txnValid) {
- if (txnValid)
- setStatus(
- "changed",
- "You have uncommitted changes",
- true
- );
- else setErrorStatus("Some values need checking");
- }
-
-
- var table = $("<table>");
-
- function renderField(name, value, meta, label, editable) {
- var status = data.status(name);
-
- if (!(meta.widget in widgets))
- return $("<tr>").html($("<td>").text(value));
-
- var widget = widgets[meta.widget];
- if (!editable) return widget.format(value, status, label);
-
- widget = Object.create(widget);
-
- function change() {
- widget.setMessage("[checking]");
- if ($("#status").prop("class") != "invalid")
- statusBar.text("Validating changes");
-
- data.set(name, widget.get()).done(function(txnValid) {
- widget.setMessage("");
- widget.setStatus(data.status(name));
- validated(txnValid);
-
- }).fail(function(xhr) {
- if (_.isString(xhr)) widget.setMessage(xhr);
-
- else if (xhr.statusCode().status == 422)
- widget.setMessage(
- _.reduce(
- _.map(
- $.parseJSON(xhr.responseText),
- _.escape
- ),
- function(a, b) {
- return a + "<br/>" + b;
- }
- ),
- true
- );
-
- else widget.setMessage(formatError("Error", xhr));
-
- widget.setStatus("invalid");
- validated(false);
- });
- }
-
- var el = widget.init(value, meta, change, label);
-
- if (status == "invalid" && !isTreeNode(meta))
- change();
-
- widget.setStatus(status);
-
- table.append(el);
- return el;
- }
-
- function renderCollectionMember(name, value, meta) {
- var row = renderField(
- name,
- value,
- meta.members,
- meta["ui-member"] + " " + name,
- meta.type != "set"
- );
- row.append(href().click(function() {
- data.delete(name).done(function(txnValid) {
- validated(txnValid)
- renderObj(path);
- });
- }).text("Delete"));
- }
-
- if (data.meta.type == "model")
- _.each(data.meta.fields, function(field) {
- renderField(
- field.name,
- data.get(field.name),
- field,
- field["ui-name"],
- true
- );
- });
-
- else _.each(data.data, function(value, name) {
- if (_.isArray(data.data)) name++;
- renderCollectionMember(name, data.get(name), data.meta);
- });
-
- content.append(table);
-
- if (_.contains(["collection", "list"], data.meta.type)) {
- var keys = _.clone(_.keys(data.data));
-
- var button = $("<input>").attr(
- {type: "submit", value: "Insert"}
- ).click(function() {
-
- var getter;
-
- function insert() {
- var name = getter();
-
- if (_.contains(keys, name)) {
- button.prop("class", null);
- return;
- }
- keys.push(name);
-
- var tn = isTreeNode(data.meta.members);
- data.set(
- name, tn ? {} : null
- ).done(function(txnValid) {
- renderCollectionMember(
- name,
- tn ? join(path, name) : null,
- data.meta
- );
- button.prop("class", null);
- validated(txnValid);
- });
- }
-
- button.prop("class", "hidden");
-
- if (data.meta.type == "collection") {
- var field = $("<input>").attr({type: "text"});
- var row = $("<tr>").html($("<td>").html(field));
- getter = function() {
- var res = field.val();
- row.remove();
- return res;
- }
- field.change(insert);
- table.append(row);
- }
- else {
- getter = function() {
- return data.data.length + 1;
- };
- insert();
- }
- });
- content.append(button);
- }
- }
-
- function renderObj(path) {
- txnMgr.query(path).done(function(data) {
- renderContent(path, data);
- });
- }
txnMgr.start().done(function() {
@@ -661,14 +711,14 @@ $(function() {
if (path == "/") return;
var topLevel = comps.length == 1;
-
+
renderMenu(tabs, "/" + comps[0], comps[1], true)
.done(function(first) {
- renderObj(topLevel ? join(path, first) : path);
+ fetchAndRender(topLevel ? join(path, first) : path);
})
.fail(function(data) {
- if (topLevel) renderContent(path, data)
- else renderObj(path);
+ if (topLevel) renderObject(path, data);
+ else fetchAndRender(path);
});
});
}