From 798bd31d507d13d27e92d576a09dafb96cb49453 Mon Sep 17 00:00:00 2001 From: Kaarle Ritvanen Date: Fri, 17 May 2013 22:15:00 +0300 Subject: web client: basic editing functionality --- web/client.html | 1 + web/client.js | 309 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 267 insertions(+), 43 deletions(-) (limited to 'web') 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 @@ +

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 $("
").text(value); }, + init: function(value, meta) { + this.el = $("").attr({type: "text", value: value}); + }, + get: function() { return this.el.val() || null; } + } + + var ComboBox = Object.create(Field); + ComboBox.init = function(value, meta) { + var el = $("").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 = $("
"); + var update = _.bind(function() { + link.html(this.format(this.get())); + }, this); + this.cbox.el.change(update); + update(); + + this.el = $("
"); + 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($("

").text(path)); + request(url, txn).done(function(data) { + var content = $("#content"); + content.html($("

").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 = $(""); + + function renderField(url, name, value, meta, editable) { + var row = $(""); + if (name != null) row.append($("
").text(name)); + + var td = $(""); + + var msg = $("
"); + if (url in changed) msg.text("[changed]"); + td.html(msg); - var table = $(""); + if (meta.widget in widgets) { + var widget = widgets[meta.widget]; + var el; - function render(name, value, meta) { - var row = $(""); - if (name != null) row.append($("
").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 + "
" + 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.
" + ); + statusBar.append($("").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($("").attr({ + type: "submit", value: "Revert" + }).click(abortTxn)); - var td = $("
"); - if (["collection", "model", "reference", "set"] - .indexOf(meta.type) > -1) { - - var link = $("") - .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($("").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($("").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("#/"); + }); }) }) -- cgit v1.2.3