summaryrefslogtreecommitdiffstats
path: root/web/widget
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2014-02-06 23:09:13 +0200
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2014-02-14 22:35:56 +0200
commit38ac196b2f00c1e13a0928cd892384bc296c8a98 (patch)
tree473663650b5b4fd898bd9bab4c666dca6b900dd4 /web/widget
parent7d434464a843a8ca8acc903367492186a0ae13e8 (diff)
downloadacf2-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.js134
-rw-r--r--web/widget/abstract/fields.js106
-rw-r--r--web/widget/abstract/inline.js74
-rw-r--r--web/widget/checkbox.js20
-rw-r--r--web/widget/checkboxes.js78
-rw-r--r--web/widget/combobox.js42
-rw-r--r--web/widget/field.js109
-rw-r--r--web/widget/inline.js155
-rw-r--r--web/widget/link.js23
-rw-r--r--web/widget/reference.js53
-rw-r--r--web/widget/table/header.js37
-rw-r--r--web/widget/table/row.js27
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;
+ }
+ });
+});