diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-12-10 17:21:10 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-12-11 13:01:39 +0200 |
commit | 3204822db1ae50aa031446b22e7ba94845a9c7c1 (patch) | |
tree | 447063175476517338c79efc54098f5568476939 /web | |
parent | bdebf89f5d7661b0bb360b4484c85a3ddcd3e249 (diff) | |
download | aconf-3204822db1ae50aa031446b22e7ba94845a9c7c1.tar.bz2 aconf-3204822db1ae50aa031446b22e7ba94845a9c7c1.tar.xz |
web client: cleaner syntax for widget inheritance
Diffstat (limited to 'web')
-rw-r--r-- | web/client.js | 1012 |
1 files changed, 533 insertions, 479 deletions
diff --git a/web/client.js b/web/client.js index 89cb341..3497039 100644 --- a/web/client.js +++ b/web/client.js @@ -476,6 +476,26 @@ $(function() { var Widget = { + extend: function(spec) { + var res = Object.create(this); + for (key in spec) res[key] = spec[key]; + res.base = this; + return res; + }, + + super: function() { + var args = _.toArray(arguments); + var cls = args.shift(); + var key = args.shift(); + return cls.base[key].apply(this, args); + }, + + new: function(data, name, meta, level, editable, removable) { + return Object.create(this).init( + data, name, meta, level, editable, removable + ); + }, + init: function(data, name, meta, level, editable, removable) { this.data = data; this.meta = meta; @@ -557,526 +577,563 @@ $(function() { }; - var Field = Object.create(Widget); - - Field.init = function( - data, name, meta, level, editable, removable - ) { - this.name = name; - this.editable = editable && meta.editable; - - var el = _.bind(Widget.init, this)( + var Field = Widget.extend({ + init: function( data, name, meta, level, editable, removable - ); - - if (this.editable) - this.field.change(_.bind(this.validate, this)); - - return el; - }; - - Field.staticRender = function(value) { - return $("<td>").text(value); - }; + ) { + this.name = name; + this.editable = editable && meta.editable; + + var el = this.super( + Field, + "init", + data, + name, + meta, + level, + editable, + removable + ); - Field.makeEl = function() { - _.bind(Widget.makeEl, this)(); - if (!this.field) this.field = this.el; - }; + if (this.editable) + this.field.change(_.bind(this.validate, this)); + + return el; + }, - Field.createEl = function() { return $("<input>"); }; + staticRender: function(value) { + return $("<td>").text(value); + }, - Field.render = function(value, meta) { - this.field.attr({type: "text", value: value}); - }; + makeEl: function() { + this.super(Field, "makeEl"); + if (!this.field) this.field = this.el; + }, - Field.wrap = function() { - var td = $("<td>"); - this.msg = $("<div>"); - td.append(this.msg); - td.append(this.el); - return td; - }; + createEl: function() { return $("<input>"); }, - Field.validate = function() { - _.bind(Widget.validate, this)(); - if (!this.visible || !this.editable) return; + render: function(value, meta) { + this.field.attr({type: "text", value: value}); + }, - this.msg.text("[checking]"); - statusBar.setError("Validating changes", "validate"); + wrap: function() { + var td = $("<td>"); + this.msg = $("<div>"); + td.append(this.msg); + td.append(this.el); + return td; + }, - var self = this; - - this.data.set(this.name, this.get()).done(function(txnValid) { - self.msg.empty() - self.setStatus(self.data.status(self.name)); - statusBar.validationReady(txnValid); - self.el.trigger("validated"); - - }).fail(function(xhr) { - if (_.isString(xhr)) self.msg.text(xhr); - - else if (xhr.statusCode().status == 422) - self.msg.html( - _.reduce( - _.map($.parseJSON(xhr.responseText), _.escape), - function(a, b) { return a + "<br/>" + b; } - ) - ); + validate: function() { + this.super(Field, "validate"); + if (!this.visible || !this.editable) return; - else self.msg.text(formatError("Error", xhr)); - - self.setStatus("invalid"); - statusBar.validationReady(false); - }); - }; - - Field.get = function() { - return this.editable ? ( - this.field.val() || null - ) : this.data.get(this.name); - }; - + this.msg.text("[checking]"); + statusBar.setError("Validating changes", "validate"); - var ComboBox = Object.create(Field); - - ComboBox.createEl = function() { return $("<select>"); }; - - ComboBox.render = function(value, meta) { - var el = this.field; + var self = this; + + this.data.set(this.name, this.get()) + .done(function(txnValid) { + self.msg.empty() + self.setStatus(self.data.status(self.name)); + statusBar.validationReady(txnValid); + self.el.trigger("validated"); + + }) + .fail(function(xhr) { + if (_.isString(xhr)) self.msg.text(xhr); + + else if (xhr.statusCode().status == 422) + self.msg.html( + _.reduce( + _.map( + $.parseJSON(xhr.responseText), + _.escape + ), + function(a, b) { + return a + "<br/>" + b; + } + ) + ); + + else self.msg.text(formatError("Error", xhr)); + + self.setStatus("invalid"); + statusBar.validationReady(false); + }); + }, - function opt(value, ui_value, selected) { - el.append($("<option>").attr( - {value: value, selected: selected} - ).text(ui_value)); + get: function() { + return this.editable ? ( + this.field.val() || null + ) : this.data.get(this.name); } + }); - if (!meta.required) - opt("", "(none)", value == null) - _.each( - meta.choice, - function(choice) { - var selected = value == choice.value; - if (choice.enabled || selected) - opt(choice.value, choice["ui-value"], selected); - } - ); - } + var ComboBox = Field.extend({ + createEl: function() { return $("<select>"); }, - var CheckBox = Object.create(Field); - CheckBox.staticRender = function(value) { - return $("<td>").text(value ? "Yes" : "No"); - }; + render: function(value, meta) { + var el = this.field; - CheckBox.statusEl = function() { return this.el.parent(); }; + function opt(value, ui_value, selected) { + el.append($("<option>").attr( + {value: value, selected: selected} + ).text(ui_value)); + } - CheckBox.render = function(value, meta) { - this.field.attr({type: "checkbox", checked: value}); - }; + if (!meta.required) opt("", "(none)", value == null); - CheckBox.get = function() { return this.field.is(":checked"); }; + _.each( + meta.choice, + function(choice) { + var selected = value == choice.value; + if (choice.enabled || selected) + opt(choice.value, choice["ui-value"], selected); + } + ); + } + }); + var CheckBox = Field.extend({ + staticRender: function(value) { + return $("<td>").text(value ? "Yes" : "No"); + }, - var Link = Object.create(Widget); + statusEl: function() { return this.el.parent(); }, - Link.staticRender = objectRef; + render: function(value, meta) { + this.field.attr({type: "checkbox", checked: value}); + }, - Link.createEl = function() { return href(); }; + get: function() { return this.field.is(":checked"); } + }); - Link.render = function(value, meta) { - this.staticRender(value, this.el) - }; - Link.wrap = function() { return $("<td>").html(this.el); }; + var Link = Widget.extend({ + staticRender: objectRef, - Link.get = function() { return {}; }; + createEl: href, + render: function(value, meta) { + this.staticRender(value, this.el) + }, - Inline = Object.create(Link); + wrap: function() { return $("<td>").html(this.el); }, + + get: function() { return {}; } + }); - Inline.init = function( - data, name, meta, level, editable, removable - ) { - return _.bind(Link.init, this)( - data, - name, - meta, - Math.min(6, level + 1), - editable, - removable - ); - }; - Inline.showStatus = false; + var Inline = Link.extend({ + init: function( + data, name, meta, level, editable, removable + ) { + return this.super( + Inline, + "init", + data, + name, + meta, + Math.min(6, level + 1), + editable, + removable + ); + }, - Inline.requestData = function(value, meta) { - var def = $.Deferred(); - txnMgr.query(value).done(function(data) { - def.resolve(data, data.meta); - }); - return def; - }; + showStatus: false, + + requestData: function(value, meta) { + var def = $.Deferred(); + txnMgr.query(value).done(function(data) { + def.resolve(data, data.meta); + }); + return def; + }, - Inline.render = function(data, meta) { - this.renderFields(data, meta); - }; + render: function(data, meta) { + this.renderFields(data, meta); + }, - Inline.renderFields = function(data, meta) { - this.reqData = data; - var self = this; - - if (data.meta.type == "model") { - this.fields = {}; - _.each(data.meta.fields, function(field) { - self.fields[field.name] = self.renderField( - field.name, - field, - field["ui-name"], - true, - false - ); - }); + renderFields: function(data, meta) { + this.reqData = data; + var self = this; + + if (data.meta.type == "model") { + this.fields = {}; + _.each(data.meta.fields, function(field) { + self.fields[field.name] = self.renderField( + field.name, + field, + field["ui-name"], + true, + false + ); + }); + + _.each(this.fields, function(f1, name) { + _.each(self.fields, function(f2) { + if (f1 != f2) + f1.on("validated", function(event) { + f2.trigger("updated", [name]); + event.stopPropagation(); + }); + }); + }); - _.each(this.fields, function(f1, name) { - _.each(self.fields, function(f2) { - if (f1 != f2) - f1.on("validated", function(event) { - f2.trigger("updated", [name]); - event.stopPropagation(); - }); + _.each(this.fields, function(field) { + field.trigger("start"); }); - }); + } + + else _.each(data.data, function(value, name) { + if (data.meta.type == "set") name = data.data[name]; + else if (_.isArray(data.data)) name++; + self.renderCollectionMember(name, data.meta); + }); + }, - _.each(this.fields, function(field) { - field.trigger("start"); + renderField: function( + name, meta, label, editable, removable + ) { + var widget = this.widget(meta).new( + this.reqData, + name, + meta, + this.level, + editable, + removable + ); + var container = this.appendWidget(widget, label); + widget.on("setVisible", function(event, visible) { + if (visible) container.show(); + else container.hide(); + event.stopPropagation(); }); - } + return widget; + }, - else _.each(data.data, function(value, name) { - if (data.meta.type == "set") name = data.data[name]; - else if (_.isArray(data.data)) name++; - self.renderCollectionMember(name, data.meta); - }); - }; - - Inline.renderField = function( - name, meta, label, editable, removable - ) { - var widget = Object.create(this.widget(meta)).init( - this.reqData, - name, - meta, - this.level, - editable, - removable - ); - var container = this.appendWidget(widget, label); - widget.on("setVisible", function(event, visible) { - if (visible) container.show(); - else container.hide(); - event.stopPropagation(); - }); - return widget; - } + renderCollectionMember: function(name, meta) { + var set = meta.type == "set"; + this.renderField( + name, + meta.members, + meta["ui-member"] + " " + name, + !set, + !set + ).trigger("start"); + }, - Inline.renderCollectionMember = function(name, meta) { - var set = meta.type == "set"; - this.renderField( - name, - meta.members, - meta["ui-member"] + " " + name, - !set, - !set - ).trigger("start"); - }; + widget: function(meta) { return widgets[meta.widget]; }, + + wrap: function() { return this.el; }, - Inline.widget = function(meta) { return widgets[meta.widget]; }; + validate: function() { + this.super(Inline, "validate"); + + var self = this; + this.request.done(function(data, meta) { + if (self.data.match(self.meta.condition)) { + var valid = data.validate(); + self.setStatus(data.status()); + statusBar.validationReady(valid); + } + + if (self.fields) + _.each(self.fields, function(field) { + field.trigger("updated"); + }); + }); + } + }); - Inline.wrap = function() { return this.el; }; - Inline.validate = function() { - _.bind(Link.validate, this)(); + var Horizontal = Inline.extend({ + createEl: function() { + this.previous = null; + return $("<tr>").html($("<td>").prop( + "class", "placeholder" + )); + }, - var self = this; - this.request.done(function(data, meta) { - if (self.data.match(self.meta.condition)) { - var valid = data.validate(); - self.setStatus(data.status()); - statusBar.validationReady(valid); + appendWidget: function(el, label) { + if (!el.is("td")) return null; + if (this.previous) this.previous.after(el); + else { + var ph = this.el.find(".placeholder"); + ph.after(el); + ph.remove(); } - - if (self.fields) - _.each(self.fields, function(field) { - field.trigger("updated"); - }); - }); - }; - - - var Horizontal = Object.create(Inline); - - Horizontal.createEl = function() { - this.previous = null; - return $("<tr>").html($("<td>").prop( - "class", "placeholder" - )); - }; - - Horizontal.appendWidget = function(el, label) { - if (!el.is("td")) return null; - if (this.previous) this.previous.after(el); - else { - var ph = this.el.find(".placeholder"); - ph.after(el); - ph.remove(); + this.previous = el; + return el; } - this.previous = el; - return el; - }; - - - var HeaderHorizontal = Object.create(Horizontal); - - HeaderHorizontal.init = function( - data, name, meta, level, editable, removable - ) { - this.header = $("<tr>"); - var table = $("<table>").html(this.header); - table.append( - _.bind(Horizontal.init, this)( - data, name, meta, level, editable, removable - ) - ); - return table; - }; + }); - HeaderHorizontal.appendWidget = function(el, label) { - el = _.bind(Horizontal.appendWidget, this)(el, label); - if (el) this.header.append($("<th>").text(label)); - return el; - }; + var HeaderHorizontal = Horizontal.extend({ + init: function( + data, name, meta, level, editable, removable + ) { + this.header = $("<tr>"); + var table = $("<table>").html(this.header); + table.append( + this.super( + HeaderHorizontal, + "init", + data, + name, + meta, + level, + editable, + removable + ) + ); + return table; + }, + + appendWidget: function(el, label) { + el = this.super( + HeaderHorizontal, "appendWidget", el, label + ); + if (el) this.header.append($("<th>").text(label)); + return el; + } + }); - var HeaderInline = Object.create(Inline); - HeaderInline.createEl = function() { return $("<div>"); }; + var HeaderInline = Inline.extend({ + createEl: function() { return $("<div>"); }, - HeaderInline.render = function(data, meta) { - this.el.append( - $("<h" + this.level + ">").text(data.meta["ui-name"]) - ); - }; + render: function(data, meta) { + this.el.append( + $("<h" + this.level + ">").text(data.meta["ui-name"]) + ); + } + }); - var Vertical = Object.create(HeaderInline); + var Vertical = HeaderInline.extend({ + wrap: function() { return $("<div>").html(this.el); }, + + render: function(data, meta) { + this.super(Vertical, "render", data, meta); + + if (!isTreeNode(data.meta)) + return this.el.append(JSON.stringify(data)); + + this.div = $("<div>"); + this.el.append(this.div); - Vertical.wrap = function() { return $("<div>").html(this.el); }; + var self = this; + + if (data.meta.type == "model") + _.each(data.meta.actions, function(action) { + self.div.append($("<input>").attr( + {type: "submit", value: action["ui-name"]} + ).click(function() { + data.invoke(action.name).done(function() { + alert("Done"); + }).fail(function() { + alert("Failed"); + }); + })); + }); + + this.renderFields(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); + + data.set( + name, + isTreeNode(data.meta.members) ? {} : null + ).done(function(txnValid) { + self.renderCollectionMember( + name, data.meta + ); + button.prop("class", null); + statusBar.validationReady(txnValid); + }); + } - Vertical.render = function(data, meta) { - _.bind(HeaderInline.render, this)(data, meta); + 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); + self.appendRow(row); + } + else { + getter = function() { + return data.data.length + 1; + }; + insert(); + } + }); + this.el.append($("<p>").html(button)); + } + }, - if (!isTreeNode(data.meta)) - return this.el.append(JSON.stringify(data)); + appendWidget: function(el, label) { + var self = this; + el = makeRow(el); + + if (el.is("tr")) { + el.prepend($("<td>").text(label)); + this.appendRow(el); + } + else if (el.is("table")) { + var td; + el.find("tr").each(function(index, row) { + td = $("<td>"); + $(row).prepend(td); + self.appendRow(row); + }); + td.text(label); + return null; + } + else { + this.table = null; + this.div.append(el); + } + + return el; + }, - this.div = $("<div>"); - this.el.append(this.div); + appendRow: function(row) { + if (!this.table) { + this.table = $("<table>"); + this.div.append(this.table); + } + this.table.append(row); + } + }); - var self = this; - - if (data.meta.type == "model") - _.each(data.meta.actions, function(action) { - self.div.append($("<input>").attr( - {type: "submit", value: action["ui-name"]} - ).click(function() { - data.invoke(action.name).done(function() { - alert("Done"); - }).fail(function() { - alert("Failed"); - }); - })); - }); - this.renderFields(data, meta); + var CheckBoxes = HeaderInline.extend({ + showStatus: true, - if (_.contains(["collection", "list"], data.meta.type)) { - var keys = _.clone(_.keys(data.data)); + setStatus: function(status) { + this.super( + CheckBoxes, + "setStatus", + status == "invalid" ? "invalid" : null + ); + }, + + render: function(data, meta) { + this.super(CheckBoxes, "render", data, meta); - var button = $("<input>").attr( - {type: "submit", value: "Insert"} - ).click(function() { + var table = $("<table>"); + this.el.append(table); + + var self = this; + + _.each(meta.members.choice, function(choice) { + var selected = _.contains(data.data, choice.value); + if (!(choice.enabled || selected)) return; - var getter; + var cbox = $("<input>").attr({ + type: "checkbox", checked: selected + }); - function insert() { - var name = getter(); - - if (_.contains(keys, name)) { - button.prop("class", null); - return; - } - keys.push(name); - - data.set( - name, isTreeNode(data.meta.members) ? {} : null + var row = $("<tr>"); + row.append($("<td>").html(cbox)); + + var item = $("<td>"); + if (choice.ref) + item.html(objectRef(choice.ref) + .text(choice["ui-value"])); + else item.text(choice["ui-value"]); + row.append(item); + + function setRowStatus() { + setStatus(row, data.status(choice.value)); + } + setRowStatus(); + + cbox.change(function() { + ( + cbox.is(":checked") ? + data.add(choice.value) : + data.delete(choice.value) ).done(function(txnValid) { - self.renderCollectionMember(name, data.meta); - button.prop("class", null); + self.setStatus(data.status()); + setRowStatus(); statusBar.validationReady(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); - self.appendRow(row); - } - else { - getter = function() { - return data.data.length + 1; - }; - insert(); - } - }); - this.el.append($("<p>").html(button)); - } - }; - - Vertical.appendWidget = function(el, label) { - var self = this; - el = makeRow(el); + }); - if (el.is("tr")) { - el.prepend($("<td>").text(label)); - this.appendRow(el); - } - else if (el.is("table")) { - var td; - el.find("tr").each(function(index, row) { - td = $("<td>"); - $(row).prepend(td); - self.appendRow(row); + table.append(row); }); - td.text(label); - return null; - } - else { - this.table = null; - this.div.append(el); - } - - return el; - }; - - Vertical.appendRow = function(row) { - if (!this.table) { - this.table = $("<table>"); - this.div.append(this.table); } - this.table.append(row); - }; - - - var CheckBoxes = Object.create(HeaderInline); - - CheckBoxes.showStatus = true; - - CheckBoxes.setStatus = function(status) { - _.bind(HeaderInline.setStatus, this)( - status == "invalid" ? "invalid" : null - ); - } - - CheckBoxes.render = function(data, meta) { - _.bind(HeaderInline.render, this)(data, meta); - - var table = $("<table>"); - this.el.append(table); - - var self = this; - - _.each(meta.members.choice, function(choice) { - var selected = _.contains(data.data, choice.value); - if (!(choice.enabled || selected)) return; - - var cbox = $("<input>").attr({ - type: "checkbox", checked: selected - }); - - var row = $("<tr>"); - row.append($("<td>").html(cbox)); - - var item = $("<td>"); - if (choice.ref) - item.html(objectRef(choice.ref) - .text(choice["ui-value"])); - else item.text(choice["ui-value"]); - row.append(item); - - function setRowStatus() { - setStatus(row, data.status(choice.value)); - } - setRowStatus(); - - cbox.change(function() { - ( - cbox.is(":checked") ? - data.add(choice.value) : - data.delete(choice.value) - ).done(function(txnValid) { - self.setStatus(data.status()); - setRowStatus(); - statusBar.validationReady(txnValid); - }); - }); - - table.append(row); - }); - }; - + }); - var Reference = Object.create(ComboBox); - Reference.init = function( - data, name, meta, level, editable, removable - ) { - this.field = ComboBox.createEl(); - return _.bind(ComboBox.init, this)( + var Reference = ComboBox.extend({ + init: function( data, name, meta, level, editable, removable - ); - }; - - Reference.staticRender = function(value) { - return objectRef(value).text(value); - }; - - Reference.statusEl = function() { return this.el.find("select"); }; - - Reference.createEl = function() { return $("<div>"); }; - - Reference.render = function(value, meta) { - _.bind(ComboBox.render, this)(value, meta); - - this.el.html(this.field); - this.el.append(" "); - - var link = $("<div>"); - var update = _.bind(function() { - link.html(objectRef(this.get())); - }, this); - this.el.append(link); + ) { + this.field = ComboBox.createEl(); + return this.super( + Reference, + "init", + data, + name, + meta, + level, + editable, + removable + ); + }, + + staticRender: function(value) { + return objectRef(value).text(value); + }, + + statusEl: function() { return this.el.find("select"); }, - this.field.change(update); - update(); - }; + createEl: function() { return $("<div>"); }, + + render: function(value, meta) { + this.super(Reference, "render", value, meta); + + this.el.html(this.field); + this.el.append(" "); + + var link = $("<div>"); + var update = _.bind(function() { + link.html(objectRef(this.get())); + }, this); + this.el.append(link); + + this.field.change(update); + update(); + } + }); var widgets = { @@ -1090,26 +1147,28 @@ $(function() { }; - var Tabular = Object.create(Vertical); - - Tabular.render = function(data, meta) { - this.header = true; - _.bind(Vertical.render, this)(data, meta); - }; - - Tabular.widget = function(meta) { - if (!isTreeNode(meta)) return Vertical.widget(meta); - if (!this.header) return Horizontal; - this.header = false; - return HeaderHorizontal; - }; - + var Tabular = Vertical.extend({ + render: function(data, meta) { + this.header = true; + this.super(Tabular, "render", data, meta); + }, + + widget: function(meta) { + if (!isTreeNode(meta)) + return this.super(Tabular, "widget", meta); + if (!this.header) return Horizontal; + this.header = false; + return HeaderHorizontal; + } + }); - var Stacked = Object.create(Vertical); - Stacked.widget = function(meta) { - return isTreeNode(meta) ? Vertical : Vertical.widget(meta); - }; + var Stacked = Vertical.extend({ + widget: function(meta) { + return isTreeNode(meta) ? + Vertical : this.super(Stacked, "widget", meta); + } + }); var layouts = {stacked: Stacked, tabular: Tabular}; @@ -1124,19 +1183,14 @@ $(function() { data ? $.Deferred().resolve(data) : txnMgr.query(path) ).done(function(data) { var layout = data.meta.widget; - var form = Object.create(layout ? layouts[layout] : Vertical); - - form.createEl = function() { - return $("#content").empty(); - }; - form.wrap = function() { return this.el; }; - form.requestData = function(value, meta) { - return $.Deferred().resolve(data, meta); - }; - var name = split(path).pop(); - - form.init( + (layout ? layouts[layout] : Vertical).extend({ + createEl: function() { return $("#content").empty(); }, + wrap: function() { return this.el; }, + requestData: function(value, meta) { + return $.Deferred().resolve(data, meta); + } + }).new( { get: function(name) { return path; }, status: function(name) { return null; }, |