can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
209 lines (206 loc) • 6.25 kB
JavaScript
steal('can/model', 'can/util/object', function () {
//!steal-remove-start
can.dev.warn("can/model/cached is a deprecated plugin and will be removed in a future release.");
//!steal-remove-end
// Base model to handle reading / writing to local storage
can.Model('can.Model.Cached', {
setup: function () {
can.Model.setup.apply(this, arguments);
// setup data
if (typeof window.localStorage !== 'undefined') {
this._cached = JSON.parse(window.localStorage.getItem(this.cachedKey())) || {};
} else {
this._cached = {};
}
},
cachedKey: function () {
return 'cached' + this._shortName;
},
cacheClear: function () {
window.localStorage.removeItem(this.cachedKey());
this._cached = {};
},
cacheItems: function (items) {
var data = this._cached,
id = this.id;
can.each(items, function (item) {
var idVal = item[id],
obj = data[idVal];
if (obj) {
can.extend(obj, item);
} else {
data[idVal] = item;
}
});
window.localStorage.setItem(this.cachedKey(), JSON.stringify(data));
},
findAllCached: function (params) {
// remove anything not filtering ....
// - sorting, grouping, limit, and offset
var list = [],
data = this._cached,
item;
for (var id in data) {
item = data[id];
if (this.filter(item, params) !== false) {
list.push(item);
}
}
// do sorting / grouping
list = this.pagination(this.sort(list, params), params);
// take limit and offset ...
return list;
},
pagination: function (items, params) {
var offset = parseInt(params.offset, 10) || 0,
limit = parseInt(params.limit, 10) || items.length - offset;
return items.slice(offset, offset + limit);
},
/**
* Sorts the object in place
*
* By default uses an order property in the param
* @param {Object} items
*/
sort: function (items, params) {
can.each((params.order || [])
.slice(0)
.reverse(), function (name, i) {
var split = name.split(' ');
items = items.sort(function (a, b) {
if (split[1].toUpperCase() !== 'ASC') {
if (a[split[0]] < b[split[0]]) {
return 1;
} else if (a[split[0]] === b[split[0]]) {
return 0;
} else {
return -1;
}
} else {
if (a[split[0]] < b[split[0]]) {
return -1;
} else if (a[split[0]] === b[split[0]]) {
return 0;
} else {
return 1;
}
}
});
});
return items;
},
/**
* Called with the item and the current params.
* Should return __false__ if the item should be filtered out of the result.
*
* By default this goes through each param in params and see if it matches the
* same property in item (if item has the property defined).
* @param {Object} item
* @param {Object} params
*/
filter: function (item, params) {
// go through each param in params
var param, paramValue;
for (param in params) {
paramValue = params[param];
// in fixtures we ignore null, I don't want to now
if (paramValue !== undefined && item[param] !== undefined && !this._compare(param, item[param], paramValue)) {
return false;
}
}
},
compare: {},
_compare: function (prop, itemData, paramData) {
return can.Object.same(itemData, paramData, this.compare[prop]);
},
makeFindAll: function (findAll) {
return function (params, success, error) {
var def = new can.Deferred(),
// make the ajax request right away
findAllDeferred = findAll(params),
data = this.findAllCached(params);
def.then(success, error);
if (data.length) {
var list = this.models(data);
findAllDeferred.then(can.proxy(function (json) {
this.cacheItems(json);
list.attr(json, true); // TODO: update cached instances
}, this), function () {
can.trigger(list, 'error', arguments);
});
def.resolve(list);
} else {
findAllDeferred.then(can.proxy(function (data) {
// Create our model instance
var list = this.models(data);
// Save the data to local storage
this.cacheItems(data);
// Resolve the deferred with our instance
def.resolve(list);
}, this), function (data) {
def.reject(data);
});
}
return def;
};
},
makeFindOne: function (findOne) {
return function (params, success, error) {
var def = new can.Deferred(),
// Make the ajax request right away
findOneDeferred = findOne(params),
// grab instance from cached data
data = this._cached[params[this.id]];
// or try to load it
data = data || this.findAllCached(params)[0];
// Bind success and error callbacks to the deferred
def.then(success, error);
// If we had existing local storage data...
if (data) {
// Create our model instance
var instance = this.model(data);
findOneDeferred.then(function (json) {
// Update the instance when the ajax respone returns
instance.updated(json);
}, function (data) {
can.trigger(instance, 'error', data);
});
// Resolve the deferred with our instance
def.resolve(instance); // Otherwise hand off the deferred to the ajax request
} else {
findOneDeferred.then(can.proxy(function (data) {
// Save the data to local storage
this.cacheItems([data]);
// Create our model instance
var instance = this.model(data);
// Resolve the deferred with our instance
def.resolve(instance);
}, this), function (data) {
def.reject(data);
});
}
return def;
};
}
}, {
updated: function (attrs) {
// Save the model to local storage
this.constructor.cacheItems([this.attr()]);
// Update our model
can.Model.prototype.updated.apply(this, arguments);
},
created: function (attrs) {
// Save the model to local storage
this.constructor.cacheItems([this.attr()]);
// Update our model
can.Model.prototype.created.apply(this, arguments);
},
destroyed: function (attrs) {
// Save the model to local storage
delete this.constructor._cached[this[this.constructor.id]];
// Update our model
can.Model.prototype.destroyed.apply(this, arguments);
}
});
return can.Model.Cached;
});