diff options
Diffstat (limited to 'web/transaction.js')
-rw-r--r-- | web/transaction.js | 723 |
1 files changed, 376 insertions, 347 deletions
diff --git a/web/transaction.js b/web/transaction.js index 3d4a953..fc00550 100644 --- a/web/transaction.js +++ b/web/transaction.js @@ -3,9 +3,9 @@ * See LICENSE file for license details */ -define( - ["aconf/blocking", "aconf/path", "aconf/type", "jquery", "underscore"], - function(blocking, pth, type, $, _) { +angular.module("aconf").factory( + "aconfTxn", + function($http, $q, aconfExclusive, aconfPath, aconfType) { return function(token, saveRequired) { var txnMgr = {}; @@ -22,15 +22,16 @@ define( function request(url, options) { options = options || {}; + options.url = url; options.headers = {"X-AConf-Auth-Token": token}; if (txn) options.headers["X-AConf-Transaction-ID"] = txn; - if (options.data != undefined) + if (options.data != null) options.data = JSON.stringify(options.data); - return $.ajax(encodeURI(url), options); + return $http(options); } txnMgr.abort = function() { - request("/transaction", {type: "DELETE"}); + request("/transaction", {method: "DELETE"}); reset(); } @@ -39,408 +40,436 @@ define( } function exclusive(task) { - blocking.enable(); - - var def = $.Deferred(); - function resolve(txnValid) { def.resolve(txnValid); } - function reject() { def.reject(); } + function attempt() { + return $q(function(resolve, reject) { + function postpone() { + attempt().then(resolve, reject); + } - var tasks = _.filter( - _.pluck(_.values(invalid), 1), function(d) { - return d && d.state() == "pending"; - } - ); + var tasks = _.filter( + _.pluck(_.values(invalid), 1), + function(task) { return task; } + ); - if (tasks.length) - tasks[0].always(function() { - exclusive(task).done(resolve).fail(reject); + if (tasks.length) tasks[0].then(postpone, postpone); + else task().then(resolve, reject); }); - else task().always(blocking.disable).done(resolve).fail(reject); + } - return def; + return aconfExclusive(attempt); } txnMgr.query = function(path) { - var def = $.Deferred(); - - objRequest(path).done(function(data) { - data.txnMgr = txnMgr; - - if (type.isTreeNode(data.meta) && !_.size(data.data)) - data.data = type.isList(data.meta) ? [] : {}; - - if (!_.isArray(data.meta.removable)) - data.meta.removable = []; - - function index(name) { - return _.isArray(data.data) ? name - 1 : name; - } - - function ensureKeyPresence(name) { - var key = index(name); - if (!(key in data.data)) data.data[key] = null; - if (data.data[key] == null) - data.meta.removable.push(name); - } - - function findSubordinateKeys(objmap, path) { - return _.filter(_.keys(objmap), function(p) { - return pth.isSubordinate(p, path); - }); - } - - function invalidSubordinates() { - return findSubordinateKeys(invalid, path); - } - - var set = data.meta.type == "set"; - - if (type.isCollection(data.meta) && !set) { - var level = pth.split(path).length; - _.each(invalidSubordinates(), function(p) { - ensureKeyPresence(pth.split(p)[level]); - }); - } - - 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(objmap) { - return _.size(findSubordinateKeys(objmap, p)); - } - - if (scan(invalid)) return "invalid"; - if (scan(changed)) return "changed"; - return null; - }; - - data.isSubtreeValid = function() { - return !_.size(invalidSubordinates(invalid, path)); - }; - - 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(); - }; + return $q(function(resolve, reject) { + objRequest(path).success(function(data) { + data.txnMgr = txnMgr; - data.set = function(name, newValue) { - var def = $.Deferred(); - function reject(xhr) { def.reject(xhr); } + if (aconfType.isTreeNode(data.meta) && + !_.size(data.data)) + data.data = aconfType.isList(data.meta) ? [] : {}; - var mpath = pth.join(path, name); + if (!_.isArray(data.meta.removable)) + data.meta.removable = []; - ensureKeyPresence(name); - var value = data.get(name, true); - - var empty = newValue === null && - type.isCollection(data.meta); - if (newValue === undefined) newValue = null; - var tn = _.isObject(newValue); - var npv = tn ? mpath : newValue; - - var prevTask = mpath in invalid && - invalid[mpath][1] || $.Deferred().resolve(); + function index(name) { + return _.isArray(data.data) ? name - 1 : name; + } - invalid[mpath] = [npv, def]; + function ensureKeyPresence(name) { + var key = index(name); + if (!(key in data.data)) data.data[key] = null; + if (data.data[key] == null) + data.meta.removable.push(name); + } - function ignore(path) { - _.each(_.keys(invalid), function(p) { - if (pth.isSubordinate(p, path)) - delete invalid[p]; + function findSubordinateKeys(objmap, path) { + return _.filter(_.keys(objmap), function(p) { + return aconfPath.isSubordinate(p, path); }); } - function resolve() { - if (mpath in invalid && - invalid[mpath][1] == def) { - - var del = invalid[mpath][0] == null; + function invalidSubordinates() { + return findSubordinateKeys(invalid, path); + } - delete invalid[mpath]; - if (del) ignore(mpath); - } + var set = data.meta.type == "set"; - def.resolve(isValid()); + if (aconfType.isCollection(data.meta) && !set) { + var level = aconfPath.split(path).length; + _.each(invalidSubordinates(), function(p) { + ensureKeyPresence(aconfPath.split(p)[level]); + }); } - prevTask.always(function() { - if (empty) { - def.reject("Value not set"); - return; + data.get = function(name, valid) { + var p = aconfPath.join(path, name); + if (!valid && p in invalid) return invalid[p][0]; + + if (data.meta.type == "set") + return _.contains(data.data, name) ? + name : null; + + var res = data.data[index(name)]; + return res === undefined ? null : res; + }; + + data.metaRequest = function(name) { + return request("/meta" + aconfPath.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 = aconfPath.join(path, name); + function scan(objmap) { + return _.size(findSubordinateKeys(objmap, p)); } - var del = newValue == null; - - 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; + if (scan(invalid)) return "invalid"; + if (scan(changed)) return "changed"; + return null; + }; + + data.isNodeValid = function() { + return !(path in invalid); + }; + + data.isSubtreeValid = function() { + return !_.size(invalidSubordinates(invalid, path)); + }; + + 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 = $q.defer(); + function reject(resp) { + if (mpath in invalid && + invalid[mpath][1] == def.promise) + invalid[mpath][1] = null; + def.reject(resp); } - - 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 = _.without(data.data, name); - else data.data.push(name); + var mpath = aconfPath.join(path, name); - data.validate(); + ensureKeyPresence(name); + var value = data.get(name, true); - 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) - txnMgr.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]; - } + var empty = newValue === null && + aconfType.isCollection(data.meta); + if (newValue === undefined) newValue = null; + var tn = _.isObject(newValue); + var npv = tn ? mpath : newValue; - resolve(); - }).fail(reject); - - else resolve(); - - }).fail(reject); - }); - - 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]; - } - }); + var prevTask = mpath in invalid && + invalid[mpath][1] || $q.when(); + + invalid[mpath] = [npv, def.promise]; + + function ignore(path) { + _.each(_.keys(invalid), function(p) { + if (aconfPath.isSubordinate(p, path)) + delete invalid[p]; + }); } - ); - } - function adjustListIndices(start, end) { - var offset = start < end ? 1 : -1; - for (var i = start; i != end; i += offset) - adjustListIndex(i + offset, i); - } + function resolve() { + if (mpath in invalid && + invalid[mpath][1] == def.promise) { - function _delete(name) { - var def = $.Deferred(); - var length = data.data.length; + var del = invalid[mpath][0] == null; - data.set(name).done(function(txnValid) { - if (type.isTreeNode(data.meta) && - data.meta.type != "set") { + delete invalid[mpath]; + if (del) ignore(mpath); + } - delete changed[pth.join(path, name)]; - changed[path] = path; + def.resolve(isValid()); + } - if (data.meta.type == "list") - adjustListIndices(name, length); + function newTask() { + if (empty) { + reject("Value not set"); + return; + } + + var del = newValue == null; + + var options; + if (!del) + options = { + method: set ? "POST" : "PUT", + data: newValue + }; + else if (data.get(name, true) != null) + options = {method: "DELETE"}; + + if (!options) { + if (data.meta.type == "model" && + _.findWhere( + data.meta.fields, {name: name} + ).required) + reject("Required value not set"); + else resolve(); + return; + } + + objRequest( + set && !del ? path : mpath, options + ).then(function() { + if (!(mpath in changed)) + changed[mpath] = value; + if (!tn && newValue == changed[mpath]) + delete changed[mpath]; + + if (del) + _.each( + _.keys(changed), + function(p) { + if (aconfPath.isSubordinate( + p, mpath, true + )) + delete changed[p]; + } + ); + + if (!set) data.data[index(name)] = npv; + else if (del) + data.data = _.without(data.data, name); + 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( + aconfPath.join( + path, field.name + ) + ); + } + ); + + if (tn && !set) + txnMgr.query(mpath).then(function(data) { + + if (mpath in invalid) { + if (data.meta.type == "model") + _.each( + data.meta.fields, + function(field) { + var mmpath = aconfPath.join( + mpath, + field.name + ); + if (field.required && + data.match( + field.condition + ) && + !(mmpath in invalid) && + (aconfType.isCollection( + field + ) || data.get( + field.name + ) == null)) { + invalid[mmpath] = [ + null + ]; + } + }); + + else if (aconfType.isCollection(meta) && + data.meta.required) + invalid[mpath] = [mpath]; + } + + resolve(); + }, reject); + + else resolve(); + + }, reject); } - def.resolve(txnValid); - }).fail(function() { def.reject(); }); + prevTask.then(newTask, newTask); - return def; - } + return def.promise; + }; - data.delete = function(name) { - return exclusive(function() { return _delete(name); }); - }; + data.add = function(name) { + return data.set(name, name); + }; - data.move = function(oldIndex, newIndex) { - if (oldIndex == newIndex) - return $.Deferred().resolve(isValid()); + function adjustListIndex(oldIndex, newIndex) { + var opath = aconfPath.join(path, oldIndex); + var npath = aconfPath.join(path, newIndex); + _.each( + [changed, invalid], + function(map) { + _.each( + _.keys(map), + function(p) { + if (aconfPath.isSubordinate( + p, opath + )) { + map[npath + p.substring( + opath.length + )] = map[p]; + delete map[p]; + } + }); + } + ); + } - var value = data.get(oldIndex); - var length = data.data.length; + function adjustListIndices(start, end) { + var offset = start < end ? 1 : -1; + for (var i = start; i != end; i += offset) + adjustListIndex(i + offset, i); + } - return exclusive(function() { - var def = $.Deferred(); - function reject() { def.reject(); } + function _delete(name) { + return $q(function(resolve, reject) { + var length = data.data.length; - if (oldIndex > newIndex) oldIndex++; - else newIndex++; + data.set(name).then(function(txnValid) { + if (aconfType.isTreeNode(data.meta) && + data.meta.type != "set") { - objRequest(path, {type: "POST", data: { - index: newIndex, - data: type.isTreeNode(data.meta.members) ? - pth.join(path, oldIndex) : value - }}).done(function() { + var mpath = aconfPath.join(path, name); + var key = index(name); - data.data.splice(newIndex - 1, 0, value); + delete changed[mpath]; + changed[path] = path; - adjustListIndices(length + 1, newIndex); - adjustListIndex(oldIndex, newIndex); + if (data.meta.type == "list") { + if (aconfType.isTreeNode( + data.meta.members + )) { + data.data[key] = mpath; + data.data.pop(); + } + else data.data.splice(name - 1, 1); - _delete(oldIndex) - .done(function(txnValid) { - def.resolve(txnValid); - }) - .fail(reject); + adjustListIndices(name, length); + } + else delete data.data[key]; + } - }).fail(reject); + resolve(txnValid); + }, reject); + }); + } - return def; - }); - }; + data.delete = function(name) { + return exclusive(function() { + return _delete(name); + }); + }; + + data.move = function(oldIndex, newIndex) { + if (oldIndex == newIndex) + return $q.when(isValid()); + + var value = data.get(oldIndex); + var length = data.data.length; + + return exclusive(function() { + return $q(function(resolve, reject) { + if (oldIndex > newIndex) oldIndex++; + else newIndex++; + + objRequest(path, {method: "POST", data: { + index: newIndex, + data: aconfType.isTreeNode( + data.meta.members + ) ? aconfPath.join( + path, oldIndex + ) : value + }}).then(function() { + + if (aconfType.isTreeNode( + data.meta.members + )) + data.data.push(value); + else data.data.splice( + newIndex - 1, 0, value + ); + + adjustListIndices(length + 1, newIndex); + adjustListIndex(oldIndex, newIndex); + + _delete(oldIndex).then(resolve, reject); + + }, reject); + }); + }); + }; - data.invoke = function(name) { - return objRequest(pth.join(path, name), {type: "POST"}); - }; + data.invoke = function(name) { + return objRequest( + aconfPath.join(path, name), {method: "POST"} + ); + }; - def.resolve(data); - }).fail(function() { def.reject(); }); + resolve(data); + }).error(reject); + }); + }; - return def; - } + txnMgr.isPristine = function() { return !(_.size(changed)); }; txnMgr.start = function() { - var def = $.Deferred(); - if (txn && isValid() && !(_.size(changed))) txnMgr.abort(); - - if (txn) def.resolve(); + return $q(function(resolve, reject) { + if (txn && isValid() && txnMgr.isPristine()) txnMgr.abort(); - else request("/transaction", {type: "POST"}) - .done(function(data, status, xhr) { - txn = xhr.getResponseHeader("X-AConf-Transaction-ID"); - def.resolve(); - }) - .fail(function() { def.reject(); }); + if (txn) resolve(); - return def; + else request("/transaction", {method: "POST"}) + .success(function(data, status, headers) { + txn = headers("X-AConf-Transaction-ID"); + resolve(); + }).error(reject); + }); }; txnMgr.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; + return $q(function(resolve, reject) { + request("/transaction", {method: "PUT"}).then(function() { + reset(); + if (saveRequired) + request("/save", {method: "POST"}) + .then(resolve, reject); + else resolve(); + }, reject); + }); }; txnMgr.logout = function() { - return request("/login", {type: "DELETE"}); + return request("/login", {method: "DELETE"}); }; return txnMgr; - } + }; } ); |