239 lines
6.4 KiB
JavaScript
239 lines
6.4 KiB
JavaScript
|
//i18n
|
||
|
//
|
||
|
function _t(s) {
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
var i18n = {
|
||
|
// format('hello, {0}', 'rambo') -> "hello, rambo"
|
||
|
format: function (str) {
|
||
|
for(var i = 1; i < arguments.length; ++i) {
|
||
|
var attrs = arguments[i];
|
||
|
for(var attr in attrs) {
|
||
|
str = str.replace(RegExp('\\{' + attr + '\\}', 'g'), attrs[attr]);
|
||
|
}
|
||
|
}
|
||
|
return str;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
(function() {
|
||
|
|
||
|
// helper functions needed from backbone (they are not exported)
|
||
|
var getValue = function(object, prop, method) {
|
||
|
if (!(object && object[prop])) return null;
|
||
|
return _.isFunction(object[prop]) ? object[prop](method) : object[prop];
|
||
|
};
|
||
|
|
||
|
// Throw an error when a URL is needed, and none is supplied.
|
||
|
var urlError = function() {
|
||
|
throw new Error('A "url" property or function must be specified');
|
||
|
};
|
||
|
|
||
|
|
||
|
// backbone.sync replacement to control url prefix
|
||
|
Backbone.originalSync = Backbone.sync;
|
||
|
Backbone.sync = function(method, model, options) {
|
||
|
var url = options.url || getValue(model, 'url', method) || urlError();
|
||
|
// prefix if http is not present
|
||
|
var absoluteUrl = url.indexOf('http') === 0 || url.indexOf("//") === 0;
|
||
|
if (!absoluteUrl) {
|
||
|
options.url = cdb.config.prefixUrl() + url;
|
||
|
} else {
|
||
|
options.url = url;
|
||
|
}
|
||
|
if (method !== 'read') {
|
||
|
// remove everything related
|
||
|
if (model.surrogateKeys) {
|
||
|
Backbone.cachedSync.invalidateSurrogateKeys(getValue(model, 'surrogateKeys'));
|
||
|
}
|
||
|
}
|
||
|
return Backbone.originalSync(method, model, options);
|
||
|
};
|
||
|
|
||
|
Backbone.currentSync = Backbone.sync;
|
||
|
Backbone.withCORS = function(method, model, options) {
|
||
|
if (!options) {
|
||
|
options = {};
|
||
|
}
|
||
|
|
||
|
if (!options.crossDomain) {
|
||
|
options.crossDomain = true;
|
||
|
}
|
||
|
|
||
|
if (!options.xhrFields) {
|
||
|
options.xhrFields = { withCredentials: true };
|
||
|
}
|
||
|
|
||
|
return Backbone.currentSync(method, model, options);
|
||
|
};
|
||
|
|
||
|
// this method returns a cached version of backbone sync
|
||
|
// take a look at https://github.com/teambox/backbone.memoized_sync/blob/master/backbone.memoized_sync.js
|
||
|
// this is the same concept but implemented as a wrapper for ``Backbone.sync``
|
||
|
// usage:
|
||
|
// initialize: function() {
|
||
|
// this.sync = Backbone.cachedSync(this.user_name);
|
||
|
// }
|
||
|
Backbone.cachedSync = function(namespace, sync) {
|
||
|
|
||
|
if (!namespace) {
|
||
|
throw new Error("cachedSync needs a namespace as argument");
|
||
|
}
|
||
|
|
||
|
var surrogateKey = namespace;
|
||
|
var session = window.user_data && window.user_data.username;
|
||
|
// no user session, no cache
|
||
|
// there should be a session to have cache so we avoid
|
||
|
// cache collision for someone with more than one account
|
||
|
if (session) {
|
||
|
namespace += "-" + session;
|
||
|
} else {
|
||
|
return Backbone.sync;
|
||
|
}
|
||
|
|
||
|
var namespaceKey = "cdb-cache/" + namespace;
|
||
|
|
||
|
// saves all the localstore references to the namespace
|
||
|
// inside localstore. It allows to remove all the references
|
||
|
// at a time
|
||
|
var index = {
|
||
|
|
||
|
// return a list of references for the namespace
|
||
|
_keys: function() {
|
||
|
return JSON.parse(localStorage.getItem(namespaceKey) || '{}');
|
||
|
},
|
||
|
|
||
|
// add a new reference for the namespace
|
||
|
add: function(key) {
|
||
|
var keys = this._keys();
|
||
|
keys[key] = +new Date();
|
||
|
localStorage.setItem(namespaceKey, JSON.stringify(keys));
|
||
|
},
|
||
|
|
||
|
// remove all the references for the namespace
|
||
|
invalidate: function() {
|
||
|
var keys = this._keys();
|
||
|
_.each(keys, function(v, k) {
|
||
|
localStorage.removeItem(k);
|
||
|
});
|
||
|
localStorage.removeItem(namespaceKey);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// localstore-like cache wrapper
|
||
|
var cache = {
|
||
|
|
||
|
setItem: function(key, value) {
|
||
|
localStorage.setItem(key, value);
|
||
|
index.add(key);
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
// this is async in case the data needs to be compressed
|
||
|
getItem: function(key, callback) {
|
||
|
var val = localStorage.getItem(key);
|
||
|
_.defer(function() {
|
||
|
callback(val);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
removeItem: function(key) {
|
||
|
localStorage.removeItem(key);
|
||
|
index.invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var cached = function(method, model, options) {
|
||
|
var url = options.url || getValue(model, 'url') || urlError();
|
||
|
var key = namespaceKey + "/" + url;
|
||
|
|
||
|
if (method === 'read') {
|
||
|
var success = options.success;
|
||
|
var cachedValue = null;
|
||
|
|
||
|
options.success = function(resp, status, xhr) {
|
||
|
// if cached value is ok
|
||
|
if (cachedValue && xhr.responseText === cachedValue) {
|
||
|
return;
|
||
|
}
|
||
|
cache.setItem(key, xhr.responseText);
|
||
|
success(resp, status, xhr);
|
||
|
}
|
||
|
|
||
|
cache.getItem(key, function(val) {
|
||
|
cachedValue = val;
|
||
|
if (val) {
|
||
|
success(JSON.parse(val), "success");
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
cache.removeItem(key);
|
||
|
}
|
||
|
return (sync || Backbone.sync)(method, model, options);
|
||
|
}
|
||
|
|
||
|
// create a public function to invalidate all the namespace
|
||
|
// items
|
||
|
cached.invalidate = function() {
|
||
|
index.invalidate();
|
||
|
}
|
||
|
|
||
|
// for testing and debugging porpuposes
|
||
|
cached.cache = cache;
|
||
|
|
||
|
// have a global namespace -> sync function in order to avoid invalidation
|
||
|
Backbone.cachedSync.surrogateKeys[surrogateKey] = cached;
|
||
|
|
||
|
return cached;
|
||
|
}
|
||
|
|
||
|
Backbone.cachedSync.surrogateKeys = {};
|
||
|
|
||
|
Backbone.cachedSync.invalidateSurrogateKeys = function(keys) {
|
||
|
_.each(keys, function(k) {
|
||
|
var s = Backbone.cachedSync.surrogateKeys[k];
|
||
|
if (s) {
|
||
|
s.invalidate();
|
||
|
} else {
|
||
|
cdb.log.debug("surrogate key not found: " + k);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
})();
|
||
|
|
||
|
Backbone.syncAbort = function() {
|
||
|
var self = arguments[1];
|
||
|
if (self._xhr) {
|
||
|
self._xhr.abort();
|
||
|
}
|
||
|
self._xhr = Backbone.sync.apply(this, arguments);
|
||
|
self._xhr.always(function() { self._xhr = null; });
|
||
|
return self._xhr;
|
||
|
};
|
||
|
|
||
|
Backbone.delayedSaveSync = function(sync, delay) {
|
||
|
var dsync = _.debounce(sync, delay);
|
||
|
return function(method, model, options) {
|
||
|
if (method === 'create' || method === 'update') {
|
||
|
return dsync(method, model, options);
|
||
|
} else {
|
||
|
return sync(method, model, options);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Backbone.saveAbort = function() {
|
||
|
var self = this;
|
||
|
if (this._saving && this._xhr) {
|
||
|
this._xhr.abort();
|
||
|
}
|
||
|
this._saving = true;
|
||
|
var xhr = Backbone.Model.prototype.save.apply(this, arguments);
|
||
|
this._xhr = xhr;
|
||
|
xhr.always(function() { self._saving = false; });
|
||
|
return xhr;
|
||
|
};
|