summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-05-17 22:15:00 +0300
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-05-17 22:15:00 +0300
commit798bd31d507d13d27e92d576a09dafb96cb49453 (patch)
tree4d554c0f10fdb6386023809700e1cfd6c722234d /web
parent983b8d436c6449569243c4edb0268df0add8df28 (diff)
downloadaconf-798bd31d507d13d27e92d576a09dafb96cb49453.tar.bz2
aconf-798bd31d507d13d27e92d576a09dafb96cb49453.tar.xz
web client: basic editing functionality
Diffstat (limited to 'web')
-rw-r--r--web/client.html1
-rw-r--r--web/client.js309
2 files changed, 267 insertions, 43 deletions
diff --git a/web/client.html b/web/client.html
index 6417715..55ff445 100644
--- a/web/client.html
+++ b/web/client.html
@@ -14,6 +14,7 @@
<script type="text/javascript" src="client.js"></script>
</head>
<body>
+ <p id="status"></p>
<div id="content"></div>
</body>
</html>
diff --git a/web/client.js b/web/client.js
index 3a6a277..c788984 100644
--- a/web/client.js
+++ b/web/client.js
@@ -11,61 +11,284 @@ $(function() {
var token = xhr.getResponseHeader("X-ACF-Auth-Token");
- $(window).bind("hashchange", function() {
+ function request(url, txn, options) {
+ options = options || {};
+ options.headers = {"X-ACF-Auth-Token": token};
+ if (txn) options.headers["X-ACF-Transaction-ID"] = txn;
+ return $.ajax(url, options);
+ }
+
+ var txn, changed, invalid;
+
+ function startTxn() {
+ var def = $.Deferred();
+ request("/", null, {type: "POST"})
+ .done(function(data, status, xhr) {
+ txn = xhr.getResponseHeader("X-ACF-Transaction-ID");
+ changed = {};
+ invalid = {};
+ $("#status").empty();
+ def.resolve();
+ });
+ return def;
+ }
+
+
+ var Field = {
+ format: function(value) { return $("<div>").text(value); },
+ init: function(value, meta) {
+ this.el = $("<input>").attr({type: "text", value: value});
+ },
+ get: function() { return this.el.val() || null; }
+ }
+
+ var ComboBox = Object.create(Field);
+ ComboBox.init = function(value, meta) {
+ var el = $("<select>");
+ this.el = el;
+
+ function opt(value, ui_value, selected) {
+ el.append($("<option>").attr(
+ {value: value, selected: selected}
+ ).text(ui_value));
+ }
+
+ if (!meta.required)
+ opt("", "(none)", value == null)
+
+ _.each(
+ _.zip(meta.choice, meta["ui-choice"]),
+ function(choice) {
+ opt(choice[0], choice[1], value == choice[0]);
+ }
+ );
+ }
+
+ var Path = {
+ format: function(value) {
+ var el = $("<a>");
+ if (value) {
+ el.attr({href: "javascript:void(0);"}).click(function() {
+ $.bbq.pushState("#" + value);
+ }).text(value);
+ }
+ return el;
+ },
+ init: function(value, meta) { this.el = this.format(value); }
+ }
+
+ widgets = {
+ boolean: {
+ format: function(value) { return value ? "Yes" : "No"; },
+ init: function(value, meta) {
+ this.el = $("<input>").attr(
+ {type: "checkbox", checked: value}
+ );
+ },
+ get: function() { return this.el.is(":checked"); }
+ },
+ collection: Path,
+ combobox: ComboBox,
+ field: Field,
+ model: Path,
+ reference: {
+ format: Path.format,
+ init: function(value, meta) {
+ this.cbox = Object.create(ComboBox)
+ this.cbox.init(value, meta);
+
+ var link = $("<div>");
+ var update = _.bind(function() {
+ link.html(this.format(this.get()));
+ }, this);
+ this.cbox.el.change(update);
+ update();
+
+ this.el = $("<div>");
+ this.el.append(this.cbox.el);
+ this.el.append(" ");
+ this.el.append(link);
+ },
+ get: function() { return this.cbox.get(); }
+ }
+ }
+
+
+ function render() {
var path = $.param.fragment();
+ var url = "/config" + path;
- $.ajax("/config" + path, {headers: {"X-ACF-Auth-Token": token}})
- .done(function(data) {
- var content = $("#content");
- content.html($("<h1>").text(path));
+ request(url, txn).done(function(data) {
+ var content = $("#content");
+ content.html($("<h1>").text(path));
+
+ if (!_.contains([
+ "collection", "model", "set"
+ ], data.meta.type)) {
+ content.append(JSON.stringify(data));
+ return;
+ }
- if (["collection", "model", "set"]
- .indexOf(data.meta.type) == -1) {
- content.append(JSON.stringify(data));
- return;
- }
+ var table = $("<table>");
+
+ function renderField(url, name, value, meta, editable) {
+ var row = $("<tr>");
+ if (name != null) row.append($("<td>").text(name));
+
+ var td = $("<td>");
+
+ var msg = $("<div>");
+ if (url in changed) msg.text("[changed]");
+ td.html(msg);
- var table = $("<table>");
+ if (meta.widget in widgets) {
+ var widget = widgets[meta.widget];
+ var el;
- function render(name, value, meta) {
- var row = $("<tr>");
- if (name != null) row.append($("<td>").text(name));
+ if (editable) {
+ var widget = Object.create(widget);
+ widget.init(
+ url in invalid ? invalid[url] : value, meta
+ );
+ el = widget.el;
+ el.change(function() {
+ var options;
+ var newValue = widget.get();
+ if (newValue == null)
+ options = {type: "DELETE"}
+ else options = {
+ type: "PUT",
+ data: JSON.stringify(newValue),
+ statusCode: {
+ 422: function(xhr) {
+ msg.html(_.reduce(
+ _.map(
+ $.parseJSON(
+ xhr.responseText
+ ),
+ _.escape
+ ),
+ function(a, b) {
+ return a + "<br/>" + b;
+ }
+ ));
+ }
+ }
+ }
+ var task = request(url, txn, options);
+
+ if (!(url in changed)) changed[url] = value;
+ if (newValue == changed[url])
+ delete changed[url];
+ invalid[url] = newValue;
+
+ msg.text("[checking]");
+
+ function showError(el, msg, xhr) {
+ msg += " " + xhr.statusCode().status;
+ if (xhr.responseText)
+ msg += ': ' + xhr.responseText;
+ el.text(msg);
+ }
+
+ function newTxn() {
+ return startTxn().done(render);
+ }
+
+ function abortTxn() {
+ request("/", txn, {type: "DELETE"});
+ return newTxn();
+ }
+
+ var statusBar = $("#status");
+ statusBar.html(
+ "You have uncommitted changes.<br/>"
+ );
+ statusBar.append($("<input>").attr({
+ id: "commit",
+ type: "submit",
+ value: "Commit",
+ disabled: true
+ }).click(function() {
+ request("/", txn, {type: "PUT"})
+ .done(newTxn)
+ .fail(function(xhr) {
+ abortTxn().done(function() {
+ showError(
+ statusBar,
+ "Commit failed",
+ xhr
+ );
+ });
+ });
+ }));
+ statusBar.append($("<input>").attr({
+ type: "submit", value: "Revert"
+ }).click(abortTxn));
- var td = $("<td>");
- if (["collection", "model", "reference", "set"]
- .indexOf(meta.type) > -1) {
-
- var link = $("<a>")
- .attr({href: "javascript:void(0);"})
- .click(function() {
- $.bbq.pushState("#" + value);
+ task.done(function() {
+ if (url in changed) msg.text("[changed]");
+ else msg.empty();
+
+ delete invalid[url];
+ if (!(_.size(invalid)))
+ $("#commit").prop("disabled", false);
+
+ }).fail(function(xhr) {
+ showError(msg, "Error", xhr);
});
- link.text(value);
- td.html(link);
+ });
+
+ if (url in invalid) el.trigger("change");
}
- else td.text(value);
- row.append(td);
-
- row.append($("<td>").text(JSON.stringify(meta)));
-
- table.append(row);
- }
- if (data.meta.type == "model")
- _.each(data.meta.fields, function(field) {
- render(
- field["ui-name"], data.data[field.name], field
- );
- });
+ else el = widget.format(value);
- else _.each(data.data, function(value) {
- render(null, value, data.meta.members);
- });
+ td.append(el);
+ }
+
+ else td.text(value);
+
+ row.append(td);
+ row.append($("<td>").text(JSON.stringify(meta)));
+ table.append(row);
+ }
+
+ if (data.meta.type == "model")
+ _.each(data.meta.fields, function(field) {
+ var name = field.name;
+ if (_.isString(name)) {
+ name = name.replace(/([\\\/])/g, "\\$1");
+ if (!isNaN(Number(name))) name = "\\" + name;
+ }
- content.append(table);
+ renderField(
+ url + "/" + name,
+ field["ui-name"],
+ data.data[field.name],
+ field,
+ true
+ );
+ });
+
+ else _.each(data.data, function(value) {
+ var i = 1;
+ renderField(
+ url + "/" + i++,
+ null,
+ value,
+ data.meta.members,
+ data.meta.type != "set"
+ );
});
- });
+
+ content.append(table);
+ });
+ }
- $.bbq.pushState("#/");
+ startTxn().done(function() {
+ $(window).bind("hashchange", render);
+ $.bbq.pushState("#/");
+ });
})
})