diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-02-06 22:27:20 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-02-12 12:38:31 +0200 |
commit | 1dc9c3a59137b1cb9a38669938ba865154100c64 (patch) | |
tree | 28599445ef4a199ecde352afa495c4a918dd4641 | |
parent | 8857a1753ba94c490211b06531995b6ad88506a6 (diff) | |
download | acf2-1dc9c3a59137b1cb9a38669938ba865154100c64.tar.bz2 acf2-1dc9c3a59137b1cb9a38669938ba865154100c64.tar.xz |
web client: transaction module
-rw-r--r-- | web/client.js | 424 | ||||
-rw-r--r-- | web/transaction.js | 416 |
2 files changed, 419 insertions, 421 deletions
diff --git a/web/client.js b/web/client.js index 81ef869..d289890 100644 --- a/web/client.js +++ b/web/client.js @@ -21,6 +21,7 @@ require( [ "acf2/path", "acf2/statusbar", + "acf2/transaction", "acf2/type", "jquery", "underscore", @@ -28,7 +29,7 @@ require( "jquery-blockui", "jquery-ui/sortable" ], - function(pth, statusBar, type, $, _) { + function(pth, statusBar, txnMgr, type, $, _) { $("#login").submit(function() { $.ajax("/login", { @@ -38,429 +39,10 @@ require( }) }).done(function(data, status, xhr) { - 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; - if (options.data != undefined) - options.data = JSON.stringify(options.data); - return $.ajax(url, options); - } - - function abort() { - request("/transaction", {type: "DELETE"}); - reset(); - } - - function objRequest(path, options) { - return request("/config" + path, options); - } - - function exclusive(task) { - $.blockUI(); - - var def = $.Deferred(); - function resolve(txnValid) { def.resolve(txnValid); } - function reject() { def.reject(); } - - var tasks = _.filter( - _.pluck(_.values(invalid), 1), function(d) { - return d && d.state() == "pending"; - } - ); - - if (tasks.length) - tasks[0].always(function() { - exclusive(task).done(resolve).fail(reject); - }); - else task().always($.unblockUI).done(resolve).fail(reject); - - return def; - } - - function query(path) { - var def = $.Deferred(); - - objRequest(path).done(function(data) { - if (type.isTreeNode(data.meta) && !_.size(data.data)) - data.data = type.isList(data.meta) ? [] : {}; - - function index(name) { - return _.isArray(data.data) ? name - 1 : name; - } - - data.get = function(name, valid) { - var p = pth.join(path, name); - if (!valid && p in invalid) return invalid[p][0]; - - if (data.meta.type == "set") { - return _.contains( - data.data, name - ) ? name : null; - } - - return data.data[index(name)]; - }; - - data.metaRequest = function(name) { - return request("/meta" + pth.join(path, name)); - }; - - data.match = function(filter) { - if (!filter) return true; - return _.every(_.map(filter, function(values, key) { - return _.contains(values, data.data[key]); - })); - }; - - data.status = function(name) { - var p = name ? pth.join(path, name) : path; - function scan(objs) { - return _.size(_.filter( - _.keys(objs), function(obj) { - return pth.isSubordinate(obj, p); - } - )); - } - - if (scan(invalid)) return "invalid"; - if (scan(changed)) return "changed"; - return null; - }; - - data.validate = function() { - var valid = true; - if (data.meta.required) { - valid = _.size(data.data); - if (valid) delete invalid[path]; - else invalid[path] = [path]; - } - return valid && isValid(); - }; - - data.set = function(name, newValue) { - var def = $.Deferred(); - function reject(xhr) { def.reject(xhr); } - - var mpath = pth.join(path, name); - - var value = data.get(name, true); - if (value == undefined) value = null; - - var tn = _.isObject(newValue); - var npv = tn ? mpath : newValue; - - function ignore(path) { - _.each(_.keys(invalid), function(p) { - if (pth.isSubordinate(p, path)) - delete invalid[p]; - }); - } - - function resolve() { - if (mpath in invalid && - invalid[mpath][1] == def) { - - var del = invalid[mpath][0] == null; - - delete invalid[mpath]; - if (del) ignore(mpath); - } - - def.resolve(isValid()); - } - - function validate() { - var del = newValue == null; - var set = data.meta.type == "set"; - - var options; - if (!del) - options = { - type: set ? "POST" : "PUT", - data: 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( - set && !del ? path : 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 (pth.isSubordinate(p, mpath, true)) - delete changed[p]; - } - ); - - if (data.meta.type == "list" && del) - data.data.splice(name - 1, 1); - else if (!set) data.data[index(name)] = npv; - else if (del) - data.data.splice( - data.data.indexOf(name), 1 - ); - else data.data.push(name); - - data.validate(); - - if (data.meta.type == "model") - _.each( - data.meta.fields, function(field) { - if (field.condition && - field.condition[name] && - !_.contains( - field.condition[name], - newValue - )) - ignore( - pth.join(path, field.name) - ); - } - ); - - if (tn && !set) - query(mpath).done(function(data) { - - if (mpath in invalid) { - if (data.meta.type == "model") - _.each( - data.meta.fields, - function(field) { - var mmpath = pth.join( - mpath, - field.name - ); - if (field.required && - data.match( - field.condition - ) && - !(mmpath in invalid) && - (type.isCollection( - field - ) || data.get( - field.name - ) == null)) { - invalid[ - mmpath - ] = [ - null - ]; - } - }); - - else if (type.isCollection(data.meta) && - data.meta.required) - invalid[mpath] = [mpath]; - } - - 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.add = function(name) { - return data.set(name, name); - }; - - function adjustListIndex(oldIndex, newIndex) { - var opath = pth.join(path, oldIndex); - var npath = pth.join(path, newIndex); - _.each( - [changed, invalid], - function(map) { - _.each( - _.keys(map), - function(p) { - if (pth.isSubordinate( - p, opath - )) { - map[npath + - p.substring( - opath.length - )] = map[p]; - delete map[p]; - } - }); - } - ); - } - - function adjustListIndices(start, end) { - var offset = start < end ? 1 : -1; - for (var i = start; i != end; i += offset) - adjustListIndex(i + offset, i); - } - - function _delete(name) { - var def = $.Deferred(); - var length = data.data.length; - - data.set(name, null).done(function(txnValid) { - if (type.isTreeNode(data.meta) && - data.meta.type != "set") { - - delete changed[pth.join(path, name)]; - changed[path] = path; - - if (data.meta.type == "list") - adjustListIndices(name, length); - } - def.resolve(txnValid); - - }).fail(function() { def.reject(); }); - - return def; - } - - data.delete = function(name) { - return exclusive(function() { - return _delete(name); - }); - }; - - data.move = function(oldIndex, newIndex) { - if (oldIndex == newIndex) - return $.Deferred().resolve(isValid()); - - var value = data.get(oldIndex); - var length = data.data.length; - - return exclusive(function() { - var def = $.Deferred(); - function reject() { def.reject(); } - - if (oldIndex > newIndex) oldIndex++; - else newIndex++; - - objRequest(path, {type: "POST", data: { - index: newIndex, - data: type.isTreeNode(data.meta.members) ? - pth.join(path, oldIndex) : value - }}).done(function() { - - data.data.splice(newIndex - 1, 0, value); - - adjustListIndices(length + 1, newIndex); - adjustListIndex(oldIndex, newIndex); - - data.delete(oldIndex) - .done(function(txnValid) { - def.resolve(txnValid); - }) - .fail(reject); - - }).fail(reject); - - return def; - }); - }; - - data.invoke = function(name) { - return objRequest(pth.join(path, name), {type: "POST"}); - }; - - 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"}); - } - }; - })( + txnMgr = txnMgr( xhr.getResponseHeader("X-ACF-Auth-Token"), xhr.getResponseHeader("X-ACF-Save-Required") == "1" ); - - function formatError(msg, xhr) { msg += " " + xhr.statusCode().status; diff --git a/web/transaction.js b/web/transaction.js new file mode 100644 index 0000000..9a9368b --- /dev/null +++ b/web/transaction.js @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2012-2014 Kaarle Ritvanen + * See LICENSE file for license details + */ + +define( + ["acf2/path", "acf2/type", "jquery", "underscore", "jquery-blockui"], + function(pth, type, $, _) { + return 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; + if (options.data != undefined) + options.data = JSON.stringify(options.data); + return $.ajax(url, options); + } + + function abort() { + request("/transaction", {type: "DELETE"}); + reset(); + } + + function objRequest(path, options) { + return request("/config" + path, options); + } + + function exclusive(task) { + $.blockUI(); + + var def = $.Deferred(); + function resolve(txnValid) { def.resolve(txnValid); } + function reject() { def.reject(); } + + var tasks = _.filter( + _.pluck(_.values(invalid), 1), function(d) { + return d && d.state() == "pending"; + } + ); + + if (tasks.length) + tasks[0].always(function() { + exclusive(task).done(resolve).fail(reject); + }); + else task().always($.unblockUI).done(resolve).fail(reject); + + return def; + } + + function query(path) { + var def = $.Deferred(); + + objRequest(path).done(function(data) { + if (type.isTreeNode(data.meta) && !_.size(data.data)) + data.data = type.isList(data.meta) ? [] : {}; + + function index(name) { + return _.isArray(data.data) ? name - 1 : name; + } + + data.get = function(name, valid) { + var p = pth.join(path, name); + if (!valid && p in invalid) return invalid[p][0]; + + if (data.meta.type == "set") + return _.contains(data.data, name) ? name : null; + + return data.data[index(name)]; + }; + + data.metaRequest = function(name) { + return request("/meta" + pth.join(path, name)); + }; + + data.match = function(filter) { + if (!filter) return true; + return _.every(_.map(filter, function(values, key) { + return _.contains(values, data.data[key]); + })); + }; + + data.status = function(name) { + var p = name ? pth.join(path, name) : path; + function scan(objs) { + return _.size(_.filter( + _.keys(objs), function(obj) { + return pth.isSubordinate(obj, p); + } + )); + } + + if (scan(invalid)) return "invalid"; + if (scan(changed)) return "changed"; + return null; + }; + + data.validate = function() { + var valid = true; + if (data.meta.required) { + valid = _.size(data.data); + if (valid) delete invalid[path]; + else invalid[path] = [path]; + } + return valid && isValid(); + }; + + data.set = function(name, newValue) { + var def = $.Deferred(); + function reject(xhr) { def.reject(xhr); } + + var mpath = pth.join(path, name); + + var value = data.get(name, true); + if (value == undefined) value = null; + + var tn = _.isObject(newValue); + var npv = tn ? mpath : newValue; + + function ignore(path) { + _.each(_.keys(invalid), function(p) { + if (pth.isSubordinate(p, path)) + delete invalid[p]; + }); + } + + function resolve() { + if (mpath in invalid && + invalid[mpath][1] == def) { + + var del = invalid[mpath][0] == null; + + delete invalid[mpath]; + if (del) ignore(mpath); + } + + def.resolve(isValid()); + } + + function validate() { + var del = newValue == null; + var set = data.meta.type == "set"; + + var options; + if (!del) + options = { + type: set ? "POST" : "PUT", data: 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( + set && !del ? path : 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 (pth.isSubordinate( + p, mpath, true + )) + delete changed[p]; + } + ); + + if (data.meta.type == "list" && del) + data.data.splice(name - 1, 1); + else if (!set) data.data[index(name)] = npv; + else if (del) + data.data.splice( + data.data.indexOf(name), 1 + ); + else data.data.push(name); + + data.validate(); + + if (data.meta.type == "model") + _.each( + data.meta.fields, function(field) { + if (field.condition && + field.condition[name] && + !_.contains( + field.condition[name], + newValue + )) + ignore( + pth.join(path, field.name) + ); + } + ); + + if (tn && !set) + query(mpath).done(function(data) { + + if (mpath in invalid) { + if (data.meta.type == "model") + _.each( + data.meta.fields, + function(field) { + var mmpath = pth.join( + mpath, + field.name + ); + if (field.required && + data.match( + field.condition + ) && + !(mmpath in invalid) && + (type.isCollection( + field + ) || data.get( + field.name + ) == null)) { + invalid[mmpath] = [ + null + ]; + } + }); + + else if ( + type.isCollection(data.meta) && + data.meta.required + ) + invalid[mpath] = [mpath]; + } + + 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.add = function(name) { + return data.set(name, name); + }; + + function adjustListIndex(oldIndex, newIndex) { + var opath = pth.join(path, oldIndex); + var npath = pth.join(path, newIndex); + _.each( + [changed, invalid], + function(map) { + _.each( + _.keys(map), + function(p) { + if (pth.isSubordinate( + p, opath + )) { + map[npath + p.substring( + opath.length + )] = map[p]; + delete map[p]; + } + }); + } + ); + } + + function adjustListIndices(start, end) { + var offset = start < end ? 1 : -1; + for (var i = start; i != end; i += offset) + adjustListIndex(i + offset, i); + } + + function _delete(name) { + var def = $.Deferred(); + var length = data.data.length; + + data.set(name, null).done(function(txnValid) { + if (type.isTreeNode(data.meta) && + data.meta.type != "set") { + + delete changed[pth.join(path, name)]; + changed[path] = path; + + if (data.meta.type == "list") + adjustListIndices(name, length); + } + def.resolve(txnValid); + + }).fail(function() { def.reject(); }); + + return def; + } + + data.delete = function(name) { + return exclusive(function() { return _delete(name); }); + }; + + data.move = function(oldIndex, newIndex) { + if (oldIndex == newIndex) + return $.Deferred().resolve(isValid()); + + var value = data.get(oldIndex); + var length = data.data.length; + + return exclusive(function() { + var def = $.Deferred(); + function reject() { def.reject(); } + + if (oldIndex > newIndex) oldIndex++; + else newIndex++; + + objRequest(path, {type: "POST", data: { + index: newIndex, + data: type.isTreeNode(data.meta.members) ? + pth.join(path, oldIndex) : value + }}).done(function() { + + data.data.splice(newIndex - 1, 0, value); + + adjustListIndices(length + 1, newIndex); + adjustListIndex(oldIndex, newIndex); + + data.delete(oldIndex) + .done(function(txnValid) { + def.resolve(txnValid); + }) + .fail(reject); + + }).fail(reject); + + return def; + }); + }; + + data.invoke = function(name) { + return objRequest(pth.join(path, name), {type: "POST"}); + }; + + 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"}); + } + }; + } + } +); |