angular-cached-resource
Version:
An AngularJS module to interact with RESTful resources, even when browser is offline
542 lines (481 loc) • 18.2 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// Generated by CoffeeScript 1.7.1
var LOCAL_STORAGE_PREFIX;
LOCAL_STORAGE_PREFIX = 'cachedResource://';
module.exports = window.localStorage != null ? {
getItem: function(key, fallback) {
var item;
item = localStorage.getItem("" + LOCAL_STORAGE_PREFIX + key);
if (item != null) {
return angular.fromJson(item);
} else {
return fallback;
}
},
setItem: function(key, value) {
localStorage.setItem("" + LOCAL_STORAGE_PREFIX + key, angular.toJson(value));
return value;
}
} : {
getItem: function(key, fallback) {
return fallback;
},
setItem: function(key, value) {
return value;
}
};
},{}],2:[function(require,module,exports){
// Generated by CoffeeScript 1.7.1
var CachedResourceManager, ResourceWriteQueue;
ResourceWriteQueue = require('./resource_write_queue');
CachedResourceManager = (function() {
function CachedResourceManager($timeout) {
this.$timeout = $timeout;
this.queuesByKey = {};
}
CachedResourceManager.prototype.add = function(CachedResource) {
return this.queuesByKey[CachedResource.$key] = new ResourceWriteQueue(CachedResource, this.$timeout);
};
CachedResourceManager.prototype.getQueue = function(CachedResource) {
return this.queuesByKey[CachedResource.$key];
};
CachedResourceManager.prototype.flushQueues = function() {
var key, queue, _ref, _results;
_ref = this.queuesByKey;
_results = [];
for (key in _ref) {
queue = _ref[key];
_results.push(queue.flush());
}
return _results;
};
return CachedResourceManager;
})();
module.exports = CachedResourceManager;
},{"./resource_write_queue":6}],3:[function(require,module,exports){
// Generated by CoffeeScript 1.7.1
var CachedResourceManager, DEFAULT_ACTIONS, ResourceCacheArrayEntry, ResourceCacheEntry, app, resourceManagerListener;
DEFAULT_ACTIONS = {
get: {
method: 'GET'
},
query: {
method: 'GET',
isArray: true
},
save: {
method: 'POST'
},
remove: {
method: 'DELETE'
},
"delete": {
method: 'DELETE'
}
};
ResourceCacheEntry = require('./resource_cache_entry');
ResourceCacheArrayEntry = require('./resource_cache_array_entry');
CachedResourceManager = require('./cached_resource_manager');
resourceManagerListener = null;
app = angular.module('ngCachedResource', ['ngResource']);
app.factory('$cachedResource', [
'$resource', '$timeout', '$q', '$log', function($resource, $timeout, $q, $log) {
var readArrayCache, readCache, resourceManager, writeCache;
resourceManager = new CachedResourceManager($timeout);
if (resourceManagerListener) {
document.removeEventListener('online', resourceManagerListener);
}
resourceManagerListener = function(event) {
return resourceManager.flushQueues();
};
document.addEventListener('online', resourceManagerListener);
readArrayCache = function(name, CachedResource, boundParams) {
return function(parameters) {
var cacheArrayEntry, cacheInstanceEntry, cacheInstanceParams, deferred, resource, _i, _len, _ref;
resource = CachedResource.$resource[name].apply(CachedResource.$resource, arguments);
resource.$httpPromise = resource.$promise;
if (angular.isFunction(parameters)) {
parameters = {};
}
if (parameters == null) {
parameters = {};
}
cacheArrayEntry = new ResourceCacheArrayEntry(CachedResource.$key, parameters);
resource.$httpPromise.then(function(response) {
return cacheArrayEntry.set(response.map(function(instance) {
var attribute, cacheInstanceEntry, cacheInstanceParams, param;
cacheInstanceParams = {};
for (attribute in boundParams) {
param = boundParams[attribute];
if (typeof instance[attribute] !== "object" && typeof instance[attribute] !== "function") {
cacheInstanceParams[param] = instance[attribute];
}
}
if (Object.keys(cacheInstanceParams).length === 0) {
$log.error("instance " + instance + " doesn't have any boundParams. Please, make sure you specified them in your resource's initialization, f.e. `{id: \"@id\"}`, or it won't be cached.");
} else {
cacheInstanceEntry = new ResourceCacheEntry(CachedResource.$key, cacheInstanceParams);
cacheInstanceEntry.set(instance, false);
}
return cacheInstanceParams;
}));
});
if (cacheArrayEntry.value) {
_ref = cacheArrayEntry.value;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
cacheInstanceParams = _ref[_i];
cacheInstanceEntry = new ResourceCacheEntry(CachedResource.$key, cacheInstanceParams);
resource.push(new CachedResource(cacheInstanceEntry.value));
}
deferred = $q.defer();
resource.$promise = deferred.promise;
deferred.resolve(resource);
}
return resource;
};
};
readCache = function(name, CachedResource) {
return function() {
var args, cacheDeferred, cacheEntry, error, httpDeferred, instance, params, readHttp, success;
args = Array.prototype.slice.call(arguments);
params = angular.isObject(args[0]) ? args.shift() : {};
success = args[0], error = args[1];
cacheDeferred = $q.defer();
if (angular.isFunction(success)) {
cacheDeferred.promise.then(success);
}
if (angular.isFunction(error)) {
cacheDeferred.promise["catch"](error);
}
httpDeferred = $q.defer();
instance = new CachedResource({
$promise: cacheDeferred.promise,
$httpPromise: httpDeferred.promise
});
cacheEntry = new ResourceCacheEntry(CachedResource.$key, params);
readHttp = function() {
var resource;
resource = CachedResource.$resource[name].call(CachedResource.$resource, params);
resource.$promise.then(function(response) {
angular.extend(instance, response);
if (!cacheEntry.value) {
cacheDeferred.resolve(instance);
}
httpDeferred.resolve(instance);
return cacheEntry.set(response, false);
});
return resource.$promise["catch"](function(error) {
if (!cacheEntry.value) {
cacheDeferred.reject(error);
}
return httpDeferred.reject(error);
});
};
if (cacheEntry.dirty) {
resourceManager.getQueue(CachedResource).processResource(params, readHttp);
} else {
readHttp();
}
if (cacheEntry.value) {
angular.extend(instance, cacheEntry.value);
cacheDeferred.resolve(instance);
}
return instance;
};
};
writeCache = function(action, CachedResource) {
return function() {
var args, cacheEntry, deferred, error, instanceMethod, params, postData, queue, queueDeferred, resource, success;
instanceMethod = this instanceof CachedResource;
args = Array.prototype.slice.call(arguments);
params = !instanceMethod && angular.isObject(args[1]) ? args.shift() : instanceMethod && angular.isObject(args[0]) ? args.shift() : {};
postData = instanceMethod ? this : args.shift();
success = args[0], error = args[1];
resource = this || new CachedResource();
resource.$resolved = false;
deferred = $q.defer();
resource.$promise = deferred.promise;
if (angular.isFunction(success)) {
deferred.promise.then(success);
}
if (angular.isFunction(error)) {
deferred.promise["catch"](error);
}
cacheEntry = new ResourceCacheEntry(CachedResource.$key, params);
if (!angular.equals(cacheEntry.data, postData)) {
cacheEntry.set(postData, true);
}
queueDeferred = $q.defer();
queueDeferred.promise.then(function(value) {
angular.extend(resource, value);
resource.$resolved = true;
return deferred.resolve(resource);
});
queueDeferred.promise["catch"](deferred.reject);
queue = resourceManager.getQueue(CachedResource);
queue.enqueue(params, action, queueDeferred);
queue.flush();
return resource;
};
};
return function() {
var $key, CachedResource, Resource, actions, arg, args, boundParams, handler, name, param, paramDefault, paramDefaults, params, url, _ref;
args = Array.prototype.slice.call(arguments);
$key = args.shift();
url = args.shift();
while (args.length) {
arg = args.pop();
if (angular.isObject(arg[Object.keys(arg)[0]])) {
actions = arg;
} else {
paramDefaults = arg;
}
}
actions = angular.extend({}, DEFAULT_ACTIONS, actions);
if (paramDefaults == null) {
paramDefaults = {};
}
boundParams = {};
for (param in paramDefaults) {
paramDefault = paramDefaults[param];
if (paramDefault[0] === '@') {
boundParams[paramDefault.substr(1)] = param;
}
}
Resource = $resource.call(null, url, paramDefaults, actions);
CachedResource = (function() {
function CachedResource(attrs) {
angular.extend(this, attrs);
}
CachedResource.$resource = Resource;
CachedResource.$key = $key;
return CachedResource;
})();
for (name in actions) {
params = actions[name];
handler = params.method === 'GET' && params.isArray ? readArrayCache(name, CachedResource, boundParams) : params.method === 'GET' ? readCache(name, CachedResource) : (_ref = params.method) === 'POST' || _ref === 'PUT' || _ref === 'DELETE' ? writeCache(name, CachedResource) : void 0;
CachedResource[name] = handler;
if (params.method !== 'GET') {
CachedResource.prototype["$" + name] = handler;
}
}
resourceManager.add(CachedResource);
resourceManager.flushQueues();
return CachedResource;
};
}
]);
if (typeof module !== "undefined" && module !== null) {
module.exports = app;
}
},{"./cached_resource_manager":2,"./resource_cache_array_entry":4,"./resource_cache_entry":5}],4:[function(require,module,exports){
// Generated by CoffeeScript 1.7.1
var ResourceCacheArrayEntry, ResourceCacheEntry,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
ResourceCacheEntry = require('./resource_cache_entry');
ResourceCacheArrayEntry = (function(_super) {
__extends(ResourceCacheArrayEntry, _super);
function ResourceCacheArrayEntry() {
return ResourceCacheArrayEntry.__super__.constructor.apply(this, arguments);
}
ResourceCacheArrayEntry.prototype.defaultValue = [];
ResourceCacheArrayEntry.prototype.setKey = function(key) {
return this.key = "" + key + "/array";
};
return ResourceCacheArrayEntry;
})(ResourceCacheEntry);
module.exports = ResourceCacheArrayEntry;
},{"./resource_cache_entry":5}],5:[function(require,module,exports){
// Generated by CoffeeScript 1.7.1
var Cache, ResourceCacheEntry;
Cache = require('./cache');
ResourceCacheEntry = (function() {
ResourceCacheEntry.prototype.defaultValue = {};
function ResourceCacheEntry(resourceKey, params) {
var param, paramKeys, _ref;
this.setKey(resourceKey);
paramKeys = angular.isObject(params) ? Object.keys(params).sort() : [];
if (paramKeys.length) {
this.key += '?' + ((function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = paramKeys.length; _i < _len; _i++) {
param = paramKeys[_i];
_results.push("" + param + "=" + params[param]);
}
return _results;
})()).join('&');
}
_ref = Cache.getItem(this.key, this.defaultValue), this.value = _ref.value, this.dirty = _ref.dirty;
}
ResourceCacheEntry.prototype.setKey = function(key) {
this.key = key;
};
ResourceCacheEntry.prototype.set = function(value, dirty) {
this.value = value;
this.dirty = dirty;
return this._update();
};
ResourceCacheEntry.prototype._update = function() {
return Cache.setItem(this.key, {
value: this.value,
dirty: this.dirty
});
};
return ResourceCacheEntry;
})();
module.exports = ResourceCacheEntry;
},{"./cache":1}],6:[function(require,module,exports){
// Generated by CoffeeScript 1.7.1
var CACHE_RETRY_TIMEOUT, Cache, ResourceCacheEntry, ResourceWriteQueue;
CACHE_RETRY_TIMEOUT = 60000;
ResourceCacheEntry = require('./resource_cache_entry');
Cache = require('./cache');
ResourceWriteQueue = (function() {
function ResourceWriteQueue(CachedResource, $timeout) {
this.CachedResource = CachedResource;
this.$timeout = $timeout;
this.key = "" + this.CachedResource.$key + "/write";
this.queue = Cache.getItem(this.key, []);
}
ResourceWriteQueue.prototype.enqueue = function(params, action, deferred) {
var entry;
entry = this.findEntry({
params: params,
action: action
});
if (entry == null) {
this.queue.push({
params: params,
action: action,
deferred: deferred
});
return this._update();
} else {
entry.deferred.$promise.then(function(response) {
return deferred.resolve(response);
});
return entry.deferred.$promise["catch"](function(error) {
return deferred.reject(error);
});
}
};
ResourceWriteQueue.prototype.findEntry = function(_arg) {
var action, entry, params, _i, _len, _ref;
action = _arg.action, params = _arg.params;
_ref = this.queue;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
if (action === entry.action && angular.equals(params, entry.params)) {
return entry;
}
}
};
ResourceWriteQueue.prototype.removeEntry = function(_arg) {
var action, entry, newQueue, params, _i, _len, _ref;
action = _arg.action, params = _arg.params;
newQueue = [];
_ref = this.queue;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
if (!(action === entry.action && angular.equals(params, entry.params))) {
newQueue.push(entry);
}
}
this.queue = newQueue;
if (this.queue.length === 0 && this.timeoutPromise) {
this.$timeout.cancel(this.timeoutPromise);
delete this.timeoutPromise;
}
return this._update();
};
ResourceWriteQueue.prototype.flush = function() {
var entry, _i, _len, _ref, _results;
this._setFlushTimeout();
_ref = this.queue;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
_results.push(this._processEntry(entry));
}
return _results;
};
ResourceWriteQueue.prototype.processResource = function(params, done) {
var entry, notDone, _i, _len, _ref, _results;
notDone = true;
_ref = this._entriesForResource(params);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
_results.push(this._processEntry(entry, (function(_this) {
return function() {
if (notDone && _this._entriesForResource(params).length === 0) {
notDone = false;
return done();
}
};
})(this)));
}
return _results;
};
ResourceWriteQueue.prototype._entriesForResource = function(params) {
var entry, _i, _len, _ref, _results;
_ref = this.queue;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
if (angular.equals(params, entry.params)) {
_results.push(entry);
}
}
return _results;
};
ResourceWriteQueue.prototype._processEntry = function(entry, done) {
var cacheEntry, onFailure, onSuccess;
cacheEntry = new ResourceCacheEntry(this.CachedResource.$key, entry.params);
onSuccess = (function(_this) {
return function(value) {
var _ref;
_this.removeEntry(entry);
if ((_ref = entry.deferred) != null) {
_ref.resolve(value);
}
if (angular.isFunction(done)) {
return done();
}
};
})(this);
onFailure = (function(_this) {
return function(error) {
var _ref;
return (_ref = entry.deferred) != null ? _ref.reject(error) : void 0;
};
})(this);
return this.CachedResource.$resource[entry.action](entry.params, cacheEntry.value, onSuccess, onFailure);
};
ResourceWriteQueue.prototype._setFlushTimeout = function() {
if (this.queue.length > 0 && !this.timeoutPromise) {
this.timeoutPromise = this.$timeout(angular.bind(this, this.flush), CACHE_RETRY_TIMEOUT);
return this.timeoutPromise.then((function(_this) {
return function() {
delete _this.timeoutPromise;
return _this._setFlushTimeout();
};
})(this));
}
};
ResourceWriteQueue.prototype._update = function() {
var savableQueue;
savableQueue = this.queue.map(function(entry) {
return {
params: entry.params,
action: entry.action
};
});
return Cache.setItem(this.key, savableQueue);
};
return ResourceWriteQueue;
})();
module.exports = ResourceWriteQueue;
},{"./cache":1,"./resource_cache_entry":5}]},{},[3])