/*
* Copyright (c) 2012-2013 Kaarle Ritvanen
* See LICENSE file for license details
*/
$(function() {
$("#login").submit(function() {
var statusBar = (function() {
function set(status, msg, mode) {
$("#status").prop("class", status);
$("#status p").text(msg);
$("#logout").prop("class", mode ? "hidden" : null);
$("#status div").prop("class", mode == "txn" ? null : "hidden");
$("#commit").prop("disabled", status == "invalid");
}
function setError(msg, mode) { set("invalid", msg, mode); };
return {
reset: function() { set(null, ""); },
setError: setError,
validationReady: function(txnValid) {
if (txnValid)
set("changed", "You have uncommitted changes", "txn");
else setError("Some values need checking", "txn");
}
}
})();
$.ajax("/login", {
type: "POST",
data: JSON.stringify({
username: $("#username").val(), password: $("#password").val()
})
}).done(function(data, status, xhr) {
function split(path) {
var res = [];
while (path && path != "/") {
var comp = path.match(/^\/([^\\\/]|\\.)+/)[0];
res.push(comp.substring(1));
path = path.substring(comp.length);
}
return res;
}
function join() {
var arg = _.toArray(arguments);
if (arg.length == 1) return arg[0];
var path = arg.shift();
var name = arg.shift();
if (_.isString(name)) {
name = name.replace(/([\\\/])/g, "\\$1");
if (!isNaN(Number(name))) name = "\\" + name;
}
arg.unshift((path == "/" ? "" : path) + "/" + name);
return join.apply(undefined, arg);
}
function isRealSubordinate(p1, p2) {
return !p1.indexOf(p2 + "/");
}
function isSubordinate(p1, p2) {
return p1 == p2 || isRealSubordinate(p1, p2);
}
function isTreeNode(meta) {
return _.contains(
["collection", "list", "model", "set"], meta.type
);
}
var txnMgr = (function(token, saveRequired) {
var txn, changed, invalid;
function reset() {
txn = null;
changed = {};
invalid = {};
}
reset();
function isValid() { return !(_.size(invalid)); }
function request(url, options) {
options = options || {};
options.headers = {"X-ACF-Auth-Token": token};
if (txn) options.headers["X-ACF-Transaction-ID"] = txn;
return $.ajax(url, options);
}
function abort() {
request("/transaction", {type: "DELETE"});
reset();
}
function objRequest(path, options) {
return request("/config" + path, options);
}
function query(path) {
var def = $.Deferred();
objRequest(path).done(function(data) {
function index(name) {
return _.isArray(data.data) ? name - 1 : name;
}
data.get = function(name, valid) {
var p = join(path, name);
return (!valid && p in invalid) ?
invalid[p][0] : data.data[index(name)];
};
data.status = function(name) {
var p = join(path, name);
function scan(objs) {
return _.size(_.filter(
_.keys(objs), function(obj) {
return isSubordinate(obj, p);
}
));
}
if (scan(invalid)) return "invalid";
if (scan(changed)) return "changed";
return null;
}
data.set = function(name, newValue) {
var def = $.Deferred();
function reject(xhr) { def.reject(xhr); }
var mpath = join(path, name);
var value = data.get(name, true);
if (value == undefined) value = null;
var tn = _.isObject(newValue);
var npv = tn ? mpath : newValue;
function resolve() {
if (mpath in invalid &&
invalid[mpath][1] == def) {
var del = invalid[mpath][0] == null;
delete invalid[mpath];
if (del)
_.each(
_.keys(invalid),
function(p) {
if (isRealSubordinate(p, mpath))
delete invalid[p];
}
);
}
def.resolve(isValid());
}
function validate() {
var options;
if (newValue != null)
options = {
type: "PUT",
data: JSON.stringify(newValue)
};
else if (data.get(name, true) != null)
options = {type: "DELETE"};
if (!options) {
if (data.meta.type == "model" &&
_.findWhere(
data.meta.fields, {name: name}
).required)
def.reject("Required value not set");
else resolve();
return;
}
objRequest(mpath, options).done(function() {
if (!(mpath in changed))
changed[mpath] = value;
if (!tn && newValue == changed[mpath])
delete changed[mpath];
if (npv == null)
_.each(
_.keys(changed),
function(p) {
if (isRealSubordinate(p, mpath))
delete changed[p];
}
);
data.data[index(name)] = npv;
if (tn) query(mpath).done(function(data) {
if (mpath in invalid &&
data.meta.type == "model")
_.each(
data.meta.fields,
function(field) {
var mmpath = join(
mpath, field.name
);
if (field.required &&
!(mmpath in invalid) &&
data.get(
field.name
) == null)
invalid[mmpath] = [
null
];
});
resolve();
}).fail(reject);
else resolve();
}).fail(reject);
}
var prevTask;
if (mpath in invalid) prevTask = invalid[mpath][1];
invalid[mpath] = [npv, def];
if (prevTask) prevTask.always(validate);
else validate();
return def;
};
data.delete = function(name) {
var def = $.Deferred();
function resolve(txnValid) { def.resolve(txnValid); }
function reject() { def.reject(); }
var tasks = _.filter(
_.pluck(_.values(invalid), 1),
function(d) { return d.state() == "pending"; }
);
if (tasks.length)
tasks[0].always(function() {
data.delete(name).done(resolve).fail(reject);
});
else {
var length = data.data.length;
data.set(name, null).done(function(txnValid) {
if (isTreeNode(data.meta)) {
delete changed[join(path, name)];
changed[path] = path;
if (data.meta.type == "list")
for (var i = name; i < length; i++) {
var opath = join(path, i + 1);
var npath = join(path, i);
_.each(
[changed, invalid],
function(map) {
_.each(
_.keys(map),
function(p) {
if (isSubordinate(
p, opath
)) {
map[npath +
p.substring(
opath.length
)] = map[p];
delete map[p];
}
});
}
);
}
}
resolve(txnValid);
}).fail(reject);
}
return def;
};
def.resolve(data);
}).fail(function() { def.reject(); });
return def;
}
return {
start: function() {
var def = $.Deferred();
if (txn && isValid() && !(_.size(changed))) abort();
if (txn)
def.resolve();
else request("/transaction", {type: "POST"})
.done(function(data, status, xhr) {
txn = xhr.getResponseHeader(
"X-ACF-Transaction-ID"
);
def.resolve();
})
.fail(function() { def.reject(); });
return def;
},
commit: function() {
var def = $.Deferred();
function reject(xhr) { def.reject(xhr); }
request(
"/transaction", {type: "PUT"}
).done(function() {
reset();
if (saveRequired)
request(
"/save", {type: "POST"}
).done(function() {
def.resolve();
}).fail(reject);
else def.resolve();
}).fail(reject);
return def;
},
abort: abort,
query: query,
logout: function() {
return request("/login", {type: "DELETE"});
}
};
})(
xhr.getResponseHeader("X-ACF-Auth-Token"),
xhr.getResponseHeader("X-ACF-Save-Required") == "1"
);
function formatError(msg, xhr) {
msg += " " + xhr.statusCode().status;
if (xhr.responseText) msg += ': ' + xhr.responseText;
return msg;
}
function href() {
return $("").attr({href: "javascript:void(0);"});
}
var Widget = {
init: function(
data, name, meta, label, level, editable, removable
) {
var value = data.get(name);
if (!editable) {
var el = this.staticRender(value, level);
this.setElStatus(el, status);
if (label) return this.attachLabel(el, label);
return el;
}
this.el = this.render(value, meta, level);
var el = this.attachLabel(this.wrap(), label);
if (removable) {
var link = href().click(function() {
data.delete(name).done(function(txnValid) {
statusBar.validationReady(txnValid)
fetchAndRender($.param.fragment());
})
}).text("Delete");
if (el.is("tr")) link = $("
").html(link);
el.append(link);
}
this.setStatus(data.status(name));
return el;
},
setElStatus: function(el, status) {
el.prop("class", status);
},
attachLabel: function(el, label) {
var row = $(" | ");
row.append($("").text(label));
row.append(el);
return row;
},
wrap: function() { return this.el; },
setStatus: function(status) {
this.setElStatus(this.el, status);
}
}
var Field = Object.create(Widget);
Field.init = function(
data, name, meta, label, level, editable, removable
) {
var el = _.bind(Widget.init, this)(
data, name, meta, label, level, editable, removable
);
if (editable) {
if (!this.field) this.field = this.el;
var self = this;
function change() {
self.msg.text("[checking]");
statusBar.setError("Validating changes", "validate");
data.set(name, self.get()).done(function(txnValid) {
self.msg.empty()
self.setStatus(data.status(name));
statusBar.validationReady(txnValid);
}).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 + " " + b;
}
)
);
else self.msg.text(formatError("Error", xhr));
self.setStatus("invalid");
statusBar.validationReady(false);
});
}
this.field.change(change);
if (data.status(name) == "invalid") change();
}
return el;
};
Field.staticRender = function(value, level) {
return $("").text(value);
};
Field.render = function(value, meta, level) {
return $(" ").attr({type: "text", value: value});
};
Field.wrap = function() {
var td = $(" ");
this.msg = $("");
td.append(this.msg);
td.append(this.el);
return td;
};
Field.get = function() { return this.field.val() || null; };
var ComboBox = Object.create(Field);
ComboBox.render = function(value, meta, level) {
var el = $(" | |