aboutsummaryrefslogtreecommitdiffstats
path: root/src/moveToAngularCom
diff options
context:
space:
mode:
Diffstat (limited to 'src/moveToAngularCom')
-rw-r--r--src/moveToAngularCom/ControlBar.js72
-rw-r--r--src/moveToAngularCom/DataStore.js330
-rw-r--r--src/moveToAngularCom/Model.js65
-rw-r--r--src/moveToAngularCom/Server.js68
-rw-r--r--src/moveToAngularCom/Users.js35
5 files changed, 570 insertions, 0 deletions
diff --git a/src/moveToAngularCom/ControlBar.js b/src/moveToAngularCom/ControlBar.js
new file mode 100644
index 00000000..685beeb2
--- /dev/null
+++ b/src/moveToAngularCom/ControlBar.js
@@ -0,0 +1,72 @@
+function ControlBar(document, serverUrl, database) {
+ this._document = document;
+ this.serverUrl = serverUrl;
+ this.database = database;
+ this._window = window;
+ this.callbacks = [];
+};
+
+ControlBar.HTML =
+ '<div>' +
+ '<div class="ui-widget-overlay"></div>' +
+ '<div id="ng-login" ng-non-bindable="true">' +
+ '<div class="ng-login-container"></div>' +
+ '</div>' +
+ '</div>';
+
+
+ControlBar.FORBIDEN =
+ '<div ng-non-bindable="true" title="Permission Error:">' +
+ 'Sorry, you do not have permission for this!'+
+ '</div>';
+
+ControlBar.prototype = {
+ bind: function () {
+ },
+
+ login: function (loginSubmitFn) {
+ this.callbacks.push(loginSubmitFn);
+ if (this.callbacks.length == 1) {
+ this.doTemplate("/user_session/new.mini?database="+encodeURIComponent(this.database)+"&return_url=" + encodeURIComponent(this.urlWithoutAnchor()));
+ }
+ },
+
+ logout: function (loginSubmitFn) {
+ this.callbacks.push(loginSubmitFn);
+ if (this.callbacks.length == 1) {
+ this.doTemplate("/user_session/do_destroy.mini");
+ }
+ },
+
+ urlWithoutAnchor: function (path) {
+ return this._window['location']['href'].split("#")[0];
+ },
+
+ doTemplate: function (path) {
+ var self = this;
+ var id = new Date().getTime();
+ var url = this.urlWithoutAnchor() + "#$iframe_notify=" + id;
+ var iframeHeight = 330;
+ var loginView = jQuery('<div style="overflow:hidden; padding:2px 0 0 0;"><iframe name="'+ url +'" src="'+this.serverUrl + path + '" width="500" height="'+ iframeHeight +'"/></div>');
+ this._document.append(loginView);
+ loginView['dialog']({
+ 'height':iframeHeight + 33, 'width':500,
+ 'resizable': false, 'modal':true,
+ 'title': 'Authentication: <a href="http://www.getangular.com"><tt>&lt;angular/&gt;</tt></a>'
+ });
+ angularCallbacks["_iframe_notify_" + id] = function() {
+ loginView['dialog']("destroy");
+ loginView['remove']();
+ foreach(self.callbacks, function(callback){
+ callback();
+ });
+ self.callbacks = [];
+ };
+ },
+
+ notAuthorized: function () {
+ if (this.forbidenView) return;
+ this.forbidenView = jQuery(ControlBar.FORBIDEN);
+ this.forbidenView.dialog({bgiframe:true, height:70, modal:true});
+ }
+}; \ No newline at end of file
diff --git a/src/moveToAngularCom/DataStore.js b/src/moveToAngularCom/DataStore.js
new file mode 100644
index 00000000..70bcc623
--- /dev/null
+++ b/src/moveToAngularCom/DataStore.js
@@ -0,0 +1,330 @@
+function DataStore(post, users, anchor) {
+ this.post = post;
+ this.users = users;
+ this._cache_collections = [];
+ this._cache = {'$collections':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.datastore === this) {
+ 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)) {
+ angularArray['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;
+ foreach(list, function(item){
+ var document = type()['$loadFrom'](item);
+ queryList.push(self.cache(document));
+ });
+ (callback||noop)(queryList);
+ });
+ return queryList;
+ },
+
+ entities: function(callback) {
+ var entities = [];
+ var self = this;
+ this._jsonRequest(["GET", "$entities"], function(response) {
+ foreach(response, function(value, entityName){
+ entities.push(self.entity(entityName));
+ });
+ entities.sort(function(a,b){return a.title > b.title ? 1 : -1;});
+ (callback||noop)(entities);
+ });
+ return entities;
+ },
+
+ documentCountsByUser: function(){
+ var counts = {};
+ var self = this;
+ self.post([["GET", "$users"]], function(code, response){
+ extend(counts, response[0]);
+ });
+ return counts;
+ },
+
+ userDocumentIdsByEntity: function(user){
+ var ids = {};
+ var self = this;
+ self.post([["GET", "$users/" + user]], function(code, response){
+ extend(ids, response[0]);
+ });
+ 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, //private, obfuscate
+ '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;
+ }
+};
diff --git a/src/moveToAngularCom/Model.js b/src/moveToAngularCom/Model.js
new file mode 100644
index 00000000..b09efd0e
--- /dev/null
+++ b/src/moveToAngularCom/Model.js
@@ -0,0 +1,65 @@
+// Single $ is special and does not get searched
+// Double $$ is special an is client only (does not get sent to server)
+
+function Model(entity, initial) {
+ this['$$entity'] = entity;
+ this['$loadFrom'](initial||{});
+ this['$entity'] = entity['title'];
+ this['$migrate']();
+};
+
+Model.copyDirectFields = function(src, dst) {
+ if (src === dst || !src || !dst) return;
+ var isDataField = function(src, dst, field) {
+ return (field.substring(0,2) !== '$$') &&
+ (typeof src[field] !== 'function') &&
+ (typeof dst[field] !== 'function');
+ };
+ for (var field in dst) {
+ if (isDataField(src, dst, field))
+ delete dst[field];
+ }
+ for (field in src) {
+ if (isDataField(src, dst, field))
+ dst[field] = src[field];
+ }
+};
+
+extend(Model.prototype, {
+ '$migrate': function() {
+ merge(this['$$entity']['defaults'], this);
+ return this;
+ },
+
+ '$merge': function(other) {
+ merge(other, this);
+ return this;
+ },
+
+ '$save': function(callback) {
+ this['$$entity'].datastore.save(this, callback === true ? undefined : callback);
+ if (callback === true) this['$$entity'].datastore.flush();
+ return this;
+ },
+
+ '$delete': function(callback) {
+ this['$$entity'].datastore.remove(this, callback === true ? undefined : callback);
+ if (callback === true) this['$$entity'].datastore.flush();
+ return this;
+ },
+
+ '$loadById': function(id, callback) {
+ this['$$entity'].datastore.load(this, id, callback);
+ return this;
+ },
+
+ '$loadFrom': function(other) {
+ Model.copyDirectFields(other, this);
+ return this;
+ },
+
+ '$saveTo': function(other) {
+ Model.copyDirectFields(this, other);
+ return this;
+ }
+}); \ No newline at end of file
diff --git a/src/moveToAngularCom/Server.js b/src/moveToAngularCom/Server.js
new file mode 100644
index 00000000..5c4ec3c6
--- /dev/null
+++ b/src/moveToAngularCom/Server.js
@@ -0,0 +1,68 @@
+function Server(url, getScript) {
+ this.url = url;
+ this.nextId = 0;
+ this.getScript = getScript;
+ this.uuid = "_" + ("" + Math.random()).substr(2) + "_";
+ this.maxSize = 1800;
+};
+
+Server.prototype = {
+ base64url: function(txt) {
+ return Base64.encode(txt);
+ },
+
+ request: function(method, url, request, callback) {
+ var requestId = this.uuid + (this.nextId++);
+ var payload = this.base64url(toJson({'u':url, 'm':method, 'p':request}));
+ var totalPockets = Math.ceil(payload.length / this.maxSize);
+ var baseUrl = this.url + "/$/" + requestId + "/" + totalPockets + "/";
+ angularCallbacks[requestId] = function(response) {
+ delete angularCallbacks[requestId];
+ callback(200, response);
+ };
+ for ( var pocketNo = 0; pocketNo < totalPockets; pocketNo++) {
+ var pocket = payload.substr(pocketNo * this.maxSize, this.maxSize);
+ this.getScript(baseUrl + (pocketNo+1) + "?h=" + pocket, noop);
+ }
+ }
+};
+
+function FrameServer(frame) {
+ this.frame = frame;
+};
+FrameServer.PREFIX = "$DATASET:";
+
+FrameServer.prototype = {
+ read:function(){
+ this.data = fromJson(this.frame.name.substr(FrameServer.PREFIX.length));
+ },
+ write:function(){
+ this.frame.name = FrameServer.PREFIX + toJson(this.data);
+ },
+ request: function(method, url, request, callback) {
+ //alert(method + " " + url + " " + toJson(request) + " " + toJson(callback));
+ }
+};
+
+
+function VisualServer(delegate, status, update) {
+ this.delegate = delegate;
+ this.update = update;
+ this.status = status;
+};
+
+VisualServer.prototype = {
+ request:function(method, url, request, callback) {
+ var self = this;
+ this.status.beginRequest(request);
+ this.delegate.request(method, url, request, function() {
+ self.status.endRequest();
+ try {
+ callback.apply(this, arguments);
+ } catch (e) {
+ alert(toJson(e));
+ }
+ self.update();
+ });
+ }
+};
diff --git a/src/moveToAngularCom/Users.js b/src/moveToAngularCom/Users.js
new file mode 100644
index 00000000..fb5845d3
--- /dev/null
+++ b/src/moveToAngularCom/Users.js
@@ -0,0 +1,35 @@
+function Users(server, controlBar) {
+ this.server = server;
+ this.controlBar = controlBar;
+};
+
+extend(Users.prototype, {
+ 'fetchCurrentUser':function(callback) {
+ var self = this;
+ this.server.request("GET", "/account.json", {}, function(code, response){
+ self['current'] = response['user'];
+ callback(response['user']);
+ });
+ },
+
+ 'logout': function(callback) {
+ var self = this;
+ this.controlBar.logout(function(){
+ delete self['current'];
+ (callback||noop)();
+ });
+ },
+
+ 'login': function(callback) {
+ var self = this;
+ this.controlBar.login(function(){
+ self['fetchCurrentUser'](function(){
+ (callback||noop)();
+ });
+ });
+ },
+
+ 'notAuthorized': function(){
+ this.controlBar.notAuthorized();
+ }
+});