angular.js/src/DataStore.js
2010-01-11 17:32:33 -08:00

334 lines
No EOL
9.6 KiB
JavaScript

function DataStore(post, users, anchor) {
this.post = post;
this.users = users;
this._cache = {$collections:[]};
this.anchor = anchor;
this.bulkRequest = [];
};
DataStore.NullEntity = extend(function(){}, {
'all': function(){return [];},
'query': function(){return [];},
'load': function(){return {};},
'title': undefined
});
DataStore.prototype = {
cache: function(document) {
if (! document instanceof Model) {
throw "Parameter must be an instance of Entity! " + toJson(document);
}
var key = document.$entity + '/' + document.$id;
var cachedDocument = this._cache[key];
if (cachedDocument) {
Model.copyDirectFields(document, cachedDocument);
} else {
this._cache[key] = document;
cachedDocument = document;
}
return cachedDocument;
},
load: function(instance, id, callback, failure) {
if (id && id !== '*') {
var self = this;
this._jsonRequest(["GET", instance.$entity + "/" + id], function(response) {
instance.$loadFrom(response);
instance.$migrate();
var clone = instance.$$entity(instance);
self.cache(clone);
(callback||noop)(instance);
}, failure);
}
return instance;
},
loadMany: function(entity, ids, callback) {
var self=this;
var list = [];
var callbackCount = 0;
foreach(ids, function(id){
list.push(self.load(entity(), id, function(){
callbackCount++;
if (callbackCount == ids.length) {
(callback||noop)(list);
}
}));
});
return list;
},
loadOrCreate: function(instance, id, callback) {
var self=this;
return this.load(instance, id, callback, function(response){
if (response.$status_code == 404) {
instance.$id = id;
(callback||noop)(instance);
} else {
throw response;
}
});
},
loadAll: function(entity, callback) {
var self = this;
var list = [];
list.$$accept = function(doc){
return doc.$entity == entity.title;
};
this._cache.$collections.push(list);
this._jsonRequest(["GET", entity.title], function(response) {
var rows = response;
for ( var i = 0; i < rows.length; i++) {
var document = entity();
document.$loadFrom(rows[i]);
list.push(self.cache(document));
}
(callback||noop)(list);
});
return list;
},
save: function(document, callback) {
var self = this;
var data = {};
document.$saveTo(data);
this._jsonRequest(["POST", "", data], function(response) {
document.$loadFrom(response);
var cachedDoc = self.cache(document);
_.each(self._cache.$collections, function(collection){
if (collection.$$accept(document)) {
angular['Array']['includeIf'](collection, cachedDoc, true);
}
});
if (document.$$anchor) {
self.anchor[document.$$anchor] = document.$id;
}
if (callback)
callback(document);
});
},
remove: function(document, callback) {
var self = this;
var data = {};
document.$saveTo(data);
this._jsonRequest(["DELETE", "", data], function(response) {
delete self._cache[document.$entity + '/' + document.$id];
_.each(self._cache.$collections, function(collection){
for ( var i = 0; i < collection.length; i++) {
var item = collection[i];
if (item.$id == document.$id) {
collection.splice(i, 1);
}
}
});
(callback||noop)(response);
});
},
_jsonRequest: function(request, callback, failure) {
request.$$callback = callback;
request.$$failure = failure||function(response){
throw response;
};
this.bulkRequest.push(request);
},
flush: function() {
if (this.bulkRequest.length === 0) return;
var self = this;
var bulkRequest = this.bulkRequest;
this.bulkRequest = [];
log('REQUEST:', bulkRequest);
function callback(code, bulkResponse){
log('RESPONSE[' + code + ']: ', bulkResponse);
if(bulkResponse.$status_code == 401) {
self.users.login(function(){
self.post(bulkRequest, callback);
});
} else if(bulkResponse.$status_code) {
alert(toJson(bulkResponse));
} else {
for ( var i = 0; i < bulkResponse.length; i++) {
var response = bulkResponse[i];
var request = bulkRequest[i];
var responseCode = response.$status_code;
if(responseCode) {
if(responseCode == 403) {
self.users.notAuthorized();
} else {
request.$$failure(response);
}
} else {
request.$$callback(response);
}
}
}
}
this.post(bulkRequest, callback);
},
saveScope: function(scope, callback) {
var saveCounter = 1;
function onSaveDone() {
saveCounter--;
if (saveCounter === 0 && callback)
callback();
}
for(var key in scope) {
var item = scope[key];
if (item && item.$save == Model.prototype.$save) {
saveCounter++;
item.$save(onSaveDone);
}
}
onSaveDone();
},
query: function(type, query, arg, callback){
var self = this;
var queryList = [];
queryList.$$accept = function(doc){
return false;
};
this._cache.$collections.push(queryList);
var request = type.title + '/' + query + '=' + arg;
this._jsonRequest(["GET", request], function(response){
var list = response;
for(var i = 0; i < list.length; i++) {
var document = new type().$loadFrom(list[i]);
queryList.push(self.cache(document));
}
if (callback)
callback(queryList);
});
return queryList;
},
entities: function(callback) {
var entities = [];
var self = this;
this._jsonRequest(["GET", "$entities"], function(response) {
for (var entityName in response) {
entities.push(self.entity(entityName));
}
entities.sort(function(a,b){return a.title > b.title ? 1 : -1;});
if (callback) callback(entities);
});
return entities;
},
documentCountsByUser: function(){
var counts = {};
var self = this;
self.post([["GET", "$users"]], function(code, response){
foreach(response[0], function(value, key){
counts[key] = value;
});
});
return counts;
},
userDocumentIdsByEntity: function(user){
var ids = {};
var self = this;
self.post([["GET", "$users/" + user]], function(code, response){
foreach(response[0], function(value, key){
ids[key] = value;
});
});
return ids;
},
entity: function(name, defaults){
if (!name) {
return DataStore.NullEntity;
}
var self = this;
var entity = extend(function(initialState){
return new Model(entity, initialState);
}, {
// entity.name does not work as name seems to be reserved for functions
'title': name,
'$$factory': true,
'datastore': this,
'defaults': defaults || {},
'load': function(id, callback){
return self.load(entity(), id, callback);
},
'loadMany': function(ids, callback){
return self.loadMany(entity, ids, callback);
},
'loadOrCreate': function(id, callback){
return self.loadOrCreate(entity(), id, callback);
},
'all': function(callback){
return self.loadAll(entity, callback);
},
'query': function(query, queryArgs, callback){
return self.query(entity, query, queryArgs, callback);
},
'properties': function(callback) {
self._jsonRequest(["GET", name + "/$properties"], callback);
}
});
return entity;
},
join: function(join){
function fn(){
throw "Joined entities can not be instantiated into a document.";
};
function base(name){return name ? name.substring(0, name.indexOf('.')) : undefined;}
function next(name){return name.substring(name.indexOf('.') + 1);}
var joinOrder = _(join).chain().
map(function($, name){
return name;}).
sortBy(function(name){
var path = [];
do {
if (_(path).include(name)) throw "Infinite loop in join: " + path.join(" -> ");
path.push(name);
if (!join[name]) throw _("Named entity '<%=name%>' is undefined.").template({name:name});
name = base(join[name].on);
} while(name);
return path.length;
}).
value();
if (_(joinOrder).select(function($){return join[$].on;}).length != joinOrder.length - 1)
throw "Exactly one entity needs to be primary.";
fn['query'] = function(exp, value) {
var joinedResult = [];
var baseName = base(exp);
if (baseName != joinOrder[0]) throw _("Named entity '<%=name%>' is not a primary entity.").template({name:baseName});
var Entity = join[baseName].join;
var joinIndex = 1;
Entity['query'](next(exp), value, function(result){
var nextJoinName = joinOrder[joinIndex++];
var nextJoin = join[nextJoinName];
var nextJoinOn = nextJoin.on;
var joinIds = {};
_(result).each(function(doc){
var row = {};
joinedResult.push(row);
row[baseName] = doc;
var id = Scope.getter(row, nextJoinOn);
joinIds[id] = id;
});
nextJoin.join.loadMany(_.toArray(joinIds), function(result){
var byId = {};
_(result).each(function(doc){
byId[doc.$id] = doc;
});
_(joinedResult).each(function(row){
var id = Scope.getter(row, nextJoinOn);
row[nextJoinName] = byId[id];
});
});
});
return joinedResult;
};
return fn;
}
};