Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Cache factory service #306

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ANGULAR = [
'src/filters.js',
'src/formatters.js',
'src/validators.js',
'src/service/cacheFactory.js',
'src/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
Expand Down
8 changes: 2 additions & 6 deletions src/Browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ var XHR = window.XMLHttpRequest || function () {
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
var XHR_HEADERS = {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json, text/plain, */*",
"X-Requested-With": "XMLHttpRequest"
};


/**
* @private
Expand Down Expand Up @@ -103,7 +99,7 @@ function Browser(window, document, body, XHR, $log) {
} else {
var xhr = new XHR();
xhr.open(method, url, true);
forEach(extend(XHR_HEADERS, headers || {}), function(value, key){
forEach(headers, function(value, key){
if (value) xhr.setRequestHeader(key, value);
});
xhr.onreadystatechange = function() {
Expand Down
1 change: 1 addition & 0 deletions src/angular-bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@

// Extension points

'service/cacheFactory.js',
'service/cookieStore.js',
'service/cookies.js',
'service/defer.js',
Expand Down
151 changes: 151 additions & 0 deletions src/service/cacheFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$cacheFactory
*
* @description
* Factory that constructs cache objects.
*
*
* @param {string} cacheId Name or id of the newly created cache.
* @param {object=} options Options object that specifies the cache behavior. Properties:
*
* - `{number=}` `capacity` — turns the cache into LRU cache.
*
* @returns {object} Newly created cache object with the following set of methods:
*
* - `{string}` `id()` — Returns id or name of the cache.
* - `{number}` `size()` — Returns number of items currently in the cache
* - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache
* - `{(*}} `get({string} key) — Returns cached value for `key` or undefined for cache miss.
* - `{void}` `remove{string} key) — Removes a key-value pair from the cache.
* - `{void}` `removeAll() — Removes all cached values.
*
*/
angularServiceInject('$cacheFactory', function() {

var caches = {};

function cacheFactory(cacheId, options) {
if (cacheId in caches) {
throw Error('cacheId ' + cacheId + ' taken');
}

var size = 0,
stats = extend({}, options, {id: cacheId}),
data = {},
capacity = (options && options.capacity) || Number.MAX_VALUE,
lruHash = {},
freshEnd = null,
staleEnd = null;

return caches[cacheId] = {

put: function(key, value) {
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});

refresh(lruEntry);

if (isUndefined(value)) return;
if (!(key in data)) size++;
data[key] = value;

if (size > capacity) {
this.remove(staleEnd.key);
}
},


get: function(key) {
var lruEntry = lruHash[key];

if (!lruEntry) return;

refresh(lruEntry);

return data[key];
},


remove: function(key) {
var lruEntry = lruHash[key];

if (lruEntry == freshEnd) freshEnd = lruEntry.p;
if (lruEntry == staleEnd) staleEnd = lruEntry.n;
link(lruEntry.n,lruEntry.p);

delete lruHash[key];
delete data[key];
size--;
},


removeAll: function() {
data = {};
size = 0;
lruHash = {};
freshEnd = staleEnd = null;
},


destroy: function() {
data = null;
stats = null;
lruHash = null;
delete caches[cacheId];
},


info: function() {
return extend({}, stats, {size: size});
}
}


/**
* makes the `entry` the freshEnd of the LRU linked list
*/
function refresh(entry) {
if (entry != freshEnd) {
if (!staleEnd) {
staleEnd = entry;
} else if (staleEnd == entry) {
staleEnd = entry.n;
}

link(entry.n, entry.p);
link(entry, freshEnd);
freshEnd = entry;
freshEnd.n = null;
}
}


/**
* bydirectionally links two entries of the LRU linked list
*/
function link(nextEntry, prevEntry) {
if (nextEntry != prevEntry) {
if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
}
}
}


cacheFactory.info = function() {
var info = {};
forEach(caches, function(cache, cacheId) {
info[cacheId] = cache.info();
});
return info;
}


cacheFactory.get = function(cacheId) {
return caches[cacheId];
}


return cacheFactory;
});
43 changes: 34 additions & 9 deletions src/service/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
</doc:example>
*/
angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
return function(method, url, post, callback){
function xhr(method, url, post, callback){
if (isFunction(post)) {
callback = post;
post = null;
Expand All @@ -137,12 +137,6 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){

$browser.xhr(method, url, post, function(code, response){
try {
if (isString(response)) {
if (response.match(/^\)\]\}',\n/)) response=response.substr(6);
if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
response = fromJson(response, true);
}
}
if (200 <= code && code < 300) {
callback(code, response);
} else {
Expand All @@ -155,8 +149,39 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
} finally {
$updateView();
}
}, {
}, extend({}, xhr.defaults.request.headers, {
'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']
});
}));
};


xhr.defaults = {
request:{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json, text/plain, */*",
"X-Requested-With": "XMLHttpRequest"
},
filters: [] //xsrf
},

response: {
filters: [jsonSec, jsonDeser]
}
}


function jsonSec(response) {
if (response.match(/^\)\]\}',\n/)) return response.substr(6);
}

function jsonDeser(response) {
if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
return fromJson(response, true);
}
}


return xhr;

}, ['$browser', '$xhr.error', '$log', '$updateView']);
27 changes: 19 additions & 8 deletions src/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -1059,9 +1059,12 @@ angularWidget('ng:view', function(element) {

if (!element[0]['ng:compiled']) {
element[0]['ng:compiled'] = true;
return injectService(['$xhr.cache', '$route'], function($xhr, $route, element){
var parentScope = this,
childScope;
return injectService(['$xhr', '$route', '$cacheFactory', '$defer'],
function($xhr, $route, $cacheFactory, $defer, element){
var templateCache = $cacheFactory('ng:view'),
parentScope = this,
childScope,
template;

$route.onChange(function(){
var src;
Expand All @@ -1072,11 +1075,19 @@ angularWidget('ng:view', function(element) {
}

if (src) {
//xhr's callback must be async, see commit history for more info
$xhr('GET', src, function(code, response){
element.html(response);
compiler.compile(element)(childScope);
});
if (template = templateCache.get(src)) {
//this must be async see 9bd2c396 for more info
$defer(function() {
element.html(template);
compiler.compile(element)(childScope);
});
} else {
$xhr('GET', src, function(code, response){
templateCache.put(src, response);
element.html(response);
compiler.compile(element)(childScope);
});
}
} else {
element.html('');
}
Expand Down
7 changes: 1 addition & 6 deletions test/BrowserSpecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,7 @@ describe('browser', function(){
expect(xhr.method).toEqual('METHOD');
expect(xhr.url).toEqual('URL');
expect(xhr.post).toEqual('POST');
expect(xhr.headers).toEqual({
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json, text/plain, */*",
"X-Requested-With": "XMLHttpRequest",
"X-header":"value"
});
expect(xhr.headers).toEqual({"X-header":"value"});

xhr.status = 202;
xhr.responseText = 'RESPONSE';
Expand Down
Loading