diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-09-11 11:28:00 +0300 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-09-11 14:23:01 +0300 |
commit | bdd932cf8461214ddda2686aba81151c65c9092f (patch) | |
tree | ff529cfaa6fe4369a1102c06cd0ecaf4b7c637c5 | |
parent | 35d0195c76dff143eeb196b371cc06c0c24d623d (diff) | |
download | aconf-bdd932cf8461214ddda2686aba81151c65c9092f.tar.bz2 aconf-bdd932cf8461214ddda2686aba81151c65c9092f.tar.xz |
web client: inline widget for nested object views
-rw-r--r-- | acf/model/field.lua | 7 | ||||
-rw-r--r-- | acf/model/init.lua | 1 | ||||
-rw-r--r-- | web/client.js | 472 |
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); }); }); } |