diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-02-06 23:09:13 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-02-14 22:35:56 +0200 |
commit | 38ac196b2f00c1e13a0928cd892384bc296c8a98 (patch) | |
tree | 473663650b5b4fd898bd9bab4c666dca6b900dd4 /web/widget | |
parent | 7d434464a843a8ca8acc903367492186a0ae13e8 (diff) | |
download | acf2-38ac196b2f00c1e13a0928cd892384bc296c8a98.tar.bz2 acf2-38ac196b2f00c1e13a0928cd892384bc296c8a98.tar.xz |
web client: extract each widget to a dedicated file
Diffstat (limited to 'web/widget')
-rw-r--r-- | web/widget/abstract/base.js | 134 | ||||
-rw-r--r-- | web/widget/abstract/fields.js | 106 | ||||
-rw-r--r-- | web/widget/abstract/inline.js | 74 | ||||
-rw-r--r-- | web/widget/checkbox.js | 20 | ||||
-rw-r--r-- | web/widget/checkboxes.js | 78 | ||||
-rw-r--r-- | web/widget/combobox.js | 42 | ||||
-rw-r--r-- | web/widget/field.js | 109 | ||||
-rw-r--r-- | web/widget/inline.js | 155 | ||||
-rw-r--r-- | web/widget/link.js | 23 | ||||
-rw-r--r-- | web/widget/reference.js | 53 | ||||
-rw-r--r-- | web/widget/table/header.js | 37 | ||||
-rw-r--r-- | web/widget/table/row.js | 27 |
12 files changed, 858 insertions, 0 deletions
diff --git a/web/widget/abstract/base.js b/web/widget/abstract/base.js new file mode 100644 index 0000000..4a49e0b --- /dev/null +++ b/web/widget/abstract/base.js @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define(["acf2/dom", "jquery", "underscore"], function(dom, $, _) { + return { + 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.name = name; + this.meta = meta; + this.level = level; + + var value = data.get(name); + var status = data.status(name); + + if (!editable || !meta.editable) { + var el = this.staticRender(value, meta); + if (el) { + dom.setStatus(el, status); + return el; + } + } + + this.makeEl(); + this.dynamic = meta.dynamic; + + var self = this; + + var request; + function handleRequest(req) { + request = req; + req.done(function(value, meta) { + if (req != request) return; + self.render(value, meta); + self.setStatus(status); + }); + } + + this.wrapped = this.wrap(); + this.visible = true; + + if (removable) { + var link = dom.href().click(function() { + data.delete(name).done(function(txnValid) { + $("#content").trigger("reload", [txnValid]); + }) + }).text("Delete"); + this.wrapped = dom.makeRow(this.wrapped); + if (this.wrapped.is("tr")) link = $("<td>").html(link); + this.wrapped.append(link); + } + + handleRequest(this.requestData(value, meta)); + + function validate() { + request.done(function(value) { + self.validate(value); + }); + } + + this.wrapped.on("start", function(event) { + if (data.status(name) == "invalid") validate(); + else self.setVisible(); + event.stopPropagation(); + }); + + this.wrapped.on("updated", function(event, field) { + if (self.dynamic) handleRequest(self.refreshData()); + if (!field || + self.dynamic || + (meta.condition && field in meta.condition)) + validate(); + event.stopPropagation(); + }); + + return this.wrapped; + }, + + makeEl: function() { this.el = this.createEl(); }, + + requestData: function(value, meta) { + return $.Deferred().resolve(value, meta); + }, + + refreshData: function() { + var def = $.Deferred(); + var self = this; + this.data.metaRequest(this.name).done(function(data) { + def.resolve(self.get(), data); + }); + return def; + }, + + wrap: function() { return this.el; }, + + showStatus: true, + + setStatus: function(status) { + if (this.el && this.showStatus) + dom.setStatus(this.statusEl(), status); + }, + + statusEl: function() { return this.el; }, + + setVisible: function() { + this.visible = this.data.match(this.meta.condition); + if (this.wrapped) + this.wrapped.trigger("setVisible", [this.visible]); + }, + + validate: function(value) { this.setVisible(); } + }; +}); diff --git a/web/widget/abstract/fields.js b/web/widget/abstract/fields.js new file mode 100644 index 0000000..73111ce --- /dev/null +++ b/web/widget/abstract/fields.js @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + [ + "acf2/widget/abstract/inline", + "jquery", + "underscore", + "acf2/widget/checkbox", + "acf2/widget/checkboxes", + "acf2/widget/combobox", + "acf2/widget/field", + "acf2/widget/inline", + "acf2/widget/reference" + ], + function(Base, $, _) { + var Class = Base.extend({ + render: function(data, meta) { + this.super(Class, "render", data, meta); + + this.appendAboveFields(data, meta); + + this.reqData = data; + var self = this; + + if (meta.type == "model") { + this.fields = {}; + _.each(meta.fields, function(field) { + if (field.visible) + 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(field) { + field.trigger("start"); + }); + } + + else _.each(data.data, function(value, name) { + if (meta.type == "set") name = data.data[name]; + else if (_.isArray(data.data)) name++; + self.renderCollectionMember(name, meta); + }); + + this.appendBelowFields(data, meta); + }, + + appendAboveFields: function(data, meta) {}, + appendBelowFields: function(data, meta) {}, + + 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; + }, + + renderCollectionMember: function(name, meta) { + var set = meta.type == "set"; + this.renderField( + name, + meta.members, + meta["ui-member"] + " " + name, + !set, + !set && _.contains(meta.removable, name) + ).trigger("start"); + }, + + widget: function(meta) { + return require("acf2/widget/" + meta.widget); + } + }); + + return Class; + } +); diff --git a/web/widget/abstract/inline.js b/web/widget/abstract/inline.js new file mode 100644 index 0000000..af7d110 --- /dev/null +++ b/web/widget/abstract/inline.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + ["acf2/statusbar", "acf2/widget/link", "jquery", "underscore"], + function(statusBar, Base, $, _) { + var Class = Base.extend({ + init: function( + data, name, meta, level, editable, removable + ) { + this.txnMgr = data.txnMgr; + return this.super( + Class, + "init", + data, + name, + meta, + Math.min(6, level + 1), + editable, + removable + ); + }, + + staticRender: function(value, meta) { return null; }, + + createEl: function() { return $("<div>"); }, + + showStatus: false, + + requestData: function(value, meta) { + this.path = value; + return this.refreshData(); + }, + + refreshData: function() { + var def = $.Deferred(); + this.txnMgr.query(this.path).done(function(data) { + def.resolve(data, data.meta); + }); + return def; + }, + + showHeading: true, + + render: function(data, meta) { + if (this.showHeading) + this.el.html( + $("<h" + this.level + ">").text(meta["ui-name"]) + ); + }, + + wrap: function() { return this.el; }, + + validate: function(data) { + this.super(Class, "validate", data); + + if (this.data.match(this.meta.condition)) { + var valid = data.validate(); + this.setStatus(data.status()); + statusBar.validationReady(valid); + } + + if (this.fields) + _.each(this.fields, function(field) { + field.trigger("updated"); + }); + } + }); + + return Class; + } +); diff --git a/web/widget/checkbox.js b/web/widget/checkbox.js new file mode 100644 index 0000000..543433c --- /dev/null +++ b/web/widget/checkbox.js @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define(["acf2/widget/field", "jquery"], function(Base, $) { + return Base.extend({ + staticRender: function(value, meta) { + return $("<td>").text(value ? "Yes" : "No"); + }, + + statusEl: function() { return this.el.parent(); }, + + render: function(value, meta) { + this.field.attr({type: "checkbox", checked: value}); + }, + + get: function() { return this.field.is(":checked"); } + }); +}); diff --git a/web/widget/checkboxes.js b/web/widget/checkboxes.js new file mode 100644 index 0000000..a34aced --- /dev/null +++ b/web/widget/checkboxes.js @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + [ + "acf2/dom", + "acf2/statusbar", + "acf2/widget/abstract/inline", + "jquery", + "underscore" + ], + function(dom, statusBar, Base, $, _) { + var Class = Base.extend({ + showStatus: true, + + setStatus: function(status) { + this.super( + Class, + "setStatus", + status == "invalid" ? "invalid" : null + ); + }, + + render: function(data, meta) { + this.dynamic = meta.members.dynamic; + + this.super(Class, "render", data, meta); + + var table = $("<tbody>"); + this.el.append($("<table>").html(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(dom.objectRef(choice.ref) + .text(choice["ui-value"])); + else item.text(choice["ui-value"]); + row.append(item); + + function setRowStatus() { + dom.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); + }); + } + }); + + return Class; + } +); diff --git a/web/widget/combobox.js b/web/widget/combobox.js new file mode 100644 index 0000000..72761ee --- /dev/null +++ b/web/widget/combobox.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define(["acf2/widget/field", "jquery", "underscore"], function(Base, $, _) { + var Class = Base.extend({ + createEl: function() { return $("<select>"); }, + + staticRender: function(value, meta) { + return this.super( + Class, + "staticRender", + _.findWhere(meta.choice, {value: value})["ui-value"], + meta + ); + }, + + render: function(value, meta) { + var el = this.field.empty(); + + 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( + meta.choice, + function(choice) { + var selected = value == choice.value; + if (choice.enabled || selected) + opt(choice.value, choice["ui-value"], selected); + } + ); + } + }); + + return Class; +}); diff --git a/web/widget/field.js b/web/widget/field.js new file mode 100644 index 0000000..e4524e1 --- /dev/null +++ b/web/widget/field.js @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + [ + "acf2/error", + "acf2/statusbar", + "acf2/widget/abstract/base", + "jquery", + "underscore" + ], + function(formatError, statusBar, Base, $, _) { + var Class = Base.extend({ + init: function( + data, name, meta, level, editable, removable + ) { + this.editable = editable && meta.editable; + + var el = this.super( + Class, + "init", + data, + name, + meta, + level, + editable, + removable + ); + + if (this.editable) + this.field.change(_.bind(this.validateChange, this)); + + return el; + }, + + staticRender: function(value, meta) { + return $("<td>").text(value); + }, + + makeEl: function() { + this.super(Class, "makeEl"); + if (!this.field) this.field = this.el; + }, + + createEl: function() { return $("<input>"); }, + + render: function(value, meta) { + this.field.attr({type: "text", value: value}); + }, + + wrap: function() { + var td = $("<td>"); + this.msg = $("<div>"); + td.append(this.msg); + td.append(this.el); + return td; + }, + + validate: function(value) { + this.super(Class, "validate", value); + if (!this.visible || !this.editable) return; + this.validateChange(); + }, + + validateChange: function() { + this.msg.text("[checking]"); + statusBar.setError("Validating changes", "validate"); + + 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); + }); + }, + + get: function() { + return this.editable ? + (this.field.val() || null) : this.data.get(this.name); + } + }); + + return Class; + } +); diff --git a/web/widget/inline.js b/web/widget/inline.js new file mode 100644 index 0000000..b354f5c --- /dev/null +++ b/web/widget/inline.js @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + [ + "acf2/dom", + "acf2/statusbar", + "acf2/type", + "acf2/widget/abstract/fields", + "jquery", + "underscore", + "jquery-ui/sortable" + ], + function(dom, statusBar, type, Base, $, _) { + return Base.extend({ + wrap: function() { return $("<div>").html(this.el); }, + + appendAboveFields: function(data, meta) { + this.div = $("<div>"); + this.el.append(this.div); + + var self = this; + + if (meta.type == "model") + _.each(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"); }); + })); + }); + }, + + appendBelowFields: function(data, meta) { + if (meta.editable && + _.contains(["collection", "list"], meta.type)) { + if (!this.table) this.makeSortable(this.div); + + var self = this; + 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, type.isTreeNode(meta.members) ? {} : null + ).done(function(txnValid) { + if (_.isObject(meta.removable)) + meta.removable = []; + meta.removable.push(name); + + self.renderCollectionMember(name, meta); + button.prop("class", null); + statusBar.validationReady(txnValid); + }); + } + + button.prop("class", "hidden"); + + if (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)); + } + }, + + appendWidget: function(el, label) { + var self = this; + el = dom.makeRow(el); + + if (el.is("tr")) { + el.prepend($("<td>").text(label)); + this.appendRow(el); + } + else { + if (el.is("table")) { + this.makeSortable(el.find("tbody")); + var td; + el.find("tr").each(function(index, row) { + td = $("<td>"); + $(row).prepend(td); + }); + td.text(label); + this.table = el; + } + else this.table = null; + this.div.append(el); + } + + return el; + }, + + appendRow: function(row) { + if (!this.table) { + this.table = this.makeSortable($("<tbody>")); + this.div.append($("<table>").html(this.table)); + } + this.table.append(row); + }, + + makeSortable: function(el) { + var data = this.reqData; + + if (data.meta.type == "list") + el.sortable({ + start: function(event, ui) { + $(":focus").change(); + ui.item.data("index", ui.item.index()); + }, + stop: function(event, ui) { + var oldIndex = ui.item.data("index") + 1; + var newIndex = ui.item.index() + 1; + if (newIndex != oldIndex) + data.move(oldIndex, newIndex) + .done(function(txnValid) { + $("#content").trigger("reload", [txnValid]); + }); + } + }); + return el; + } + }); + } +); diff --git a/web/widget/link.js b/web/widget/link.js new file mode 100644 index 0000000..f6539c6 --- /dev/null +++ b/web/widget/link.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + ["acf2/dom", "acf2/widget/abstract/base", "jquery"], + function(dom, Base, $) { + return Base.extend({ + staticRender: function(value, meta) { + return $("<td>").html(dom.objectRef(value)); + }, + + createEl: dom.href, + + render: function(value, meta) { dom.objectRef(value, this.el) }, + + wrap: function() { return $("<td>").html(this.el); }, + + get: function() { return {}; } + }); + } +); diff --git a/web/widget/reference.js b/web/widget/reference.js new file mode 100644 index 0000000..4413cc9 --- /dev/null +++ b/web/widget/reference.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + ["acf2/dom", "acf2/widget/combobox", "jquery", "underscore"], + function(dom, Base, $, _) { + var Class = Base.extend({ + init: function( + data, name, meta, level, editable, removable + ) { + this.field = Base.createEl(); + return this.super( + Class, + "init", + data, + name, + meta, + level, + editable, + removable + ); + }, + + staticRender: function(value, meta) { + return $("<td>").html(dom.objectRef(value).text(value)); + }, + + statusEl: function() { return this.el.find("select"); }, + + createEl: function() { return $("<div>"); }, + + render: function(value, meta) { + this.super(Class, "render", value, meta); + + this.el.html(this.field); + this.el.append(" "); + + var link = $("<div>"); + var update = _.bind(function() { + link.html(dom.objectRef(this.get())); + }, this); + this.el.append(link); + + this.field.change(update); + update(); + } + }); + + return Class; + } +); diff --git a/web/widget/table/header.js b/web/widget/table/header.js new file mode 100644 index 0000000..0dce26e --- /dev/null +++ b/web/widget/table/header.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define(["acf2/widget/table/row", "jquery"], function(Base, $) { + var Class = Base.extend({ + init: function( + data, name, meta, level, editable, removable + ) { + this.header = $("<tr>"); + var table = $("<table>"); + table.append($("<thead>").html(this.header)); + table.append($("<tbody>").html( + this.super( + Class, + "init", + data, + name, + meta, + level, + editable, + removable + ) + )); + return table; + }, + + appendWidget: function(el, label) { + el = this.super(Class, "appendWidget", el, label); + if (el) this.header.append($("<th>").text(label)); + return el; + } + }); + + return Class; +}); diff --git a/web/widget/table/row.js b/web/widget/table/row.js new file mode 100644 index 0000000..23aadc3 --- /dev/null +++ b/web/widget/table/row.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define(["acf2/widget/abstract/fields", "jquery"], function(Base, $) { + return Base.extend({ + createEl: function() { + this.previous = null; + return $("<tr>").html($("<td>").prop("class", "placeholder")); + }, + + showHeading: false, + + 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; + } + }); +}); |