LocalStorage and URL in one Backbone collection - javascript

I have a collection, which fetches data from URL.
BarCollection = Backbone.Collection.extend({
model: BarModel,
url: // Some URL
});
But the problem is that I want to fetch data to this collection not only from URL, but also from local storage. I wish I could do something like that:
BarCollection = Backbone.Collection.extend({
model: BarModel,
url: // Some URL,
localStorage: new Backbone.LocalStorage('bars')
});
But .fetch() method cannot get data both from url and local storage.
Simple workaround is to create two different collections: one for URL and one for local storage. And after fetching just merge them.
BarCollection = Backbone.Collection.extend({
model: BarModel,
url: // Some URL
});
LocalBarCollection = Backbone.Collection.extend({
model: BarModel,
localStorage: new Backbone.LocalStorage('local-contributors')
});
I wonder if there is a more beautiful way of doing that.

To enable any collection or model to synchronize from both the localStorage and a server, Backbone's sync function can be overridden:
Backbone.sync = (function(sync) {
return function(method, model, options) {
options = options || {};
var key = _.result(model, 'localStorage'),
response;
// if the localStorage property exist on the model/collection
// first try to sync with the localStorage
if (key) {
switch (method) {
case 'create':
case 'update':
var data = model.toJSON(),
text = JSON.stringify(data);
localStorage.setItem(key, text);
break;
case 'delete':
localStorage.removeItem(key);
break;
case 'read':
response = JSON.parse(localStorage.getItem(key));
if (response) model.set(response, { parse: true });
break;
}
}
// then, always sync with the server as it normally would
return sync.apply(this, arguments);
};
})(Backbone.sync);
This way, if a model or a collection as a localStorage property, it'll first sync with the localStorage, then it'll make the original sync.
Example model and collection:
var BarModel = Backbone.Model.extend({
urlRoot: 'some/url',
localStorage: function() {
return 'bars-' + this.id;
},
});
var BarCollection = Backbone.Collection.extend({
model: BarModel,
url: '/some/url',
localStorage: 'bars',
});

Related

Why are these records not stored in cache?

I would like to cache my records once they are received, but I can't figure out how. According to the Documentation you can just call this.store.push('model', record), but it doesn't seem to work. Ember requests the data from the server with each call of the route, I would like to do this only once and use the local store after it is fetched from the server.
If I try to debug it as suggested by the Documentation, i get that there is no cache:
Pd.__container__.lookup('store:main').recordCache
// --> undefined
This is my route (where I try to cache it):
Pd.ProductsRoute = Ember.Route.extend({
model: function () {
var promise = this.store.find('product');
var that = this;
promise.then(function(value) {
// Caching supposed to happen here
value.content.forEach(function(product){
that.store.push('product', product);
});
}, function(reason) {
// on rejection
});
return promise;
}
});
And this the according Adapter (seems to work fine):
Pd.ProductAdapter = DS.RESTAdapter.extend({
primaryKey: 'nid', // DOES NOT WORK BUT I CAN LIVE WITH THAT (SEE WORKAROUND)
findAll: function(store, type) {
var url = 'ws/rest/products';
return new Ember.RSVP.Promise(function(resolve, reject) {
jQuery.getJSON(url).then(function(data) {
Ember.Logger.debug("Received Products:"); // TRIGGERS EVERY TIME!
var srcPattern = /src=["']([^'"]+)/;
data.forEach(function(product){
product.id = product.nid;
product.field_image = srcPattern.exec(product.field_image)[1];
});
Ember.Logger.debug(data);
Ember.run(null, resolve, {product: data});
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
Ember.run(null, reject, jqXHR);
});
});
}
});
this.store.find('type') will always make a call to the server for records. If you only want to make a call to the server once do it in the ApplicationRoute and then instead of using find use the all filter inside of the route that's hit multiple times.
Pd.ApplicationRoute = Em.Route.extend({
model: function(params){
return Em.RSVP.hash({
product: this.store.find('product'),
somethingElse: otherPromise
})
}
});
Pd.ProductRoute = Em.Route.extend({
model: function(params){
return this.store.all('product');
}
});
If you just want to prep the store with your products, you don't even need to return it, or use it in the app route
Pd.ApplicationRoute = Em.Route.extend({
model: function(params){
this.store.find('product');
return {foo:'bar'}; // or return nothing, it doesn't matter
}
});
Lazy loading the models
App.ProductRoute = Ember.Route.extend({
hasPreLoaded: false,
model: function() {
if(this.get('hasPreLoaded')){
return this.store.all('product');
} else {
this.toggleProperty('hasPreLoaded');
return this.store.find('product');
}
}
});
Example
http://emberjs.jsbin.com/OxIDiVU/482/edit
You don't define the primary key on the adapter, it goes on the serializer
Pd.ProductSerializer = DS.RESTSerializer.extend({
primaryKey: 'nid'
});
The cache no longer lives there, it lives in this.store.typeMapFor(Pd.Product) or this.store.typeMaps.
The site is still referencing an older version of ember data until ember data 1.0 is released, I'll assume you're using 1.0 beta version. This document is more up to date https://github.com/emberjs/data/blob/master/TRANSITION.md

A config file to hold all REST apis

I have lot of Backbone models like these:
var register = Backbone.model.extend({
url: http://....../register/
});
var login = Backbone.model.extend({
url: http://....../login/
});
My question is:
Can i have a separate file to hold all urls? Like a file which holds all the urls and I can request with a variable name as needed? Is it a bad approach? I want to be able to change these apis without going to model file(s) individually.
you can have a file, for instance, appUrl like this:
app.urls = {
registerUrl: "/register",
loginUrl: "/login"
}
and your model:
var register = Backbone.model.extend({
url: function(){
return app.urls['registerUrl']
}
});
One way to approach this is to pass in the URL in the instantiation of the model, rather than declaring explicit models for each. That would look something like:
var BaseModel = Backbone.Model.extend({
// My shared model properties
});
var myNewModel = new BaseModel(null, {
url: '/my-url-for-new-model'
});
Alternately, you can have the url parameter be a function, and then return the appropriate URL. That could be something like this:
var urls = {
register: '/some-url/register/',
login: '/some-url/login'
};
var BaseModel = Backbone.Model.extend({
initialize: function(data, config) {
this.modelType = config.type;
}
url: function() {
return urls[this.modelType];
}
});
var loginModel = new BaseModel(null, {
type: login
});

Ember data doesn't append parameters to the query

Ember data just doesn't append parameters to the query. I have a tags route like this
example
App.TagsRoute = Ember.Route.extend({
model: function(params) {
var tag = params.tag_name;
var entries = this.store.find('entry', {tags: tag});
return entries;
}
});
but this keeps making the same request as this.store.find('entry').
i'm doing it wrong?
Edit:
My router looks like this:
App.Router.map(function(){
this.resource('entries', function(){
this.resource('entry', { path: '/entry/:entry_id/:entry_title' });
});
this.route('tags', { path: '/t/:tag_name' });
});
when i request (for example) localhost:8888/#/t/tag
the value of params.tag_name is 'tag'
edit2:
My REST adapter
App.ApplicationAdapter = DS.RESTAdapter.extend({
bulkCommit: false,
buildURL: function(record, suffix) {
var s = this._super(record, suffix);
return s + ".json";
},
findQuery: function(store, type, query) {
var url = this.buildURL(type.typeKey),
proc = 'GET',
obj = { data: query },
theFinalQuery = url + "?" + $.param(query);
console.log(url); // this is the base url
console.log(proc); // this is the procedure
console.log(obj); // an object sent down to attach to the ajax request
console.log(theFinalQuery); // what the query looks like
// use the default rest adapter implementation
return this._super(store, type, query);
}
});
edit3:
making some changes to my TagsRoute object i get the next output:
App.TagsRoute = Ember.Route.extend({
model: function(params) {
var tag = params.tag_name;
var query = {tags: tag};
console.log(query);
var entries = this.store.find('entry', query);
return entries;
}
});
console output when i request localhost:8888/#/t/tag
Object {tags: "tag"}
(host url) + api/v1/entries.json
GET
Object {data: Object}
(host url) + api/v1/entries.json?tags=tag
Class {type: function, query: Object, store: Class, isLoaded: true, meta: Object…}
Ember data is attaching GET parameters. i think my error maybe is the requested url, it should be something like this
(host url) + api/v1/tags/:tag_name.json
instead of
(host url) + api/v1/entries.json?tags=:tag_name
SOLUTION
the build of ember-data (ember-data 1.0.0-beta.3-16-g2205566) was broken. When i changed the script src to builds.emberjs.com.s3.amazonaws.com/canary/daily/20131018/ember-data.js everything worked perfectly.
the proper way to add GET parameters is:
var query = {param: value};
var array = this.store.find('model', query);
thanks for your help
You are doing everything correct, are you sure the request being sent back to the server doesn't contain the query?
The full query isn't created until JQuery makes the call.
Did you look at the network tab in chrome (or whatever browser you are using) to see what it's sending back.
Watch the console in the jsbin below, it shows what happens when you use find with an object (for querying):
App.MyAdapter = DS.RESTAdapter.extend({
findQuery: function(store, type, query) {
var url = this.buildURL(type.typeKey),
proc = 'GET',
obj = { data: query },
theFinalQuery = url + "?" + $.param(query);
console.log(url); // this is the base url
console.log(proc); // this is the procedure
console.log(obj); // an object sent down to attach to the ajax request
console.log(theFinalQuery); // what the query looks like
// use the default rest adapter implementation
return this._super(store, type, query);
},
});
http://emberjs.jsbin.com/AyaVakE/1/edit
Additional questions:
hitting: http://localhost:8888/#/t/tag fires the tags route, sending in 'tag' to the tag_name. Your model hook is correct. Where are you seeing that the request is the same?
Additionally, you mentioned store.find('entries) and store.find('entry'). I know they handle pluralization of most things, but you should make sure those end up being the same endpoint /api/entries.
The RESTAdapter sends your query object to the jQuery.ajax() method by tacking it onto the data property of the settings object. (See here for info on what is done with the settings object.)
It looks like maybe the tag name is not defined. Try to make sure that the tag_name parameter is properly coming from your route.

Backbone.js - Saving data to server

I'm trying to sync my Backbone.js app to the server.
I'll note that I overrided my sync in the collection to use jsonp:
window.Project = Backbone.Model.extend({
initialize:function () {
},
urlRoot:"http://cshosting.webfactional.com/api/v1/projects",
defaults:{
"id":null,
"completion_state":"0",
"last_update_datetime":"0"
}
});
window.ProjectList = Backbone.Collection.extend({
model: Project,
url:"http://cshosting.webfactional.com/api/v1/projects/",
sync: function(method, model, options) {
options.dataType = 'jsonp';
options.url="http://cshosting.webfactional.com/api/v1/projects/? format=jsonp&callback=moshe";
//options.contentType='application/json-p';
options.error=this.errorr;
return Backbone.sync(method, model, options);
},
parse: function(response) {
return response.objects;
}
});
The problem is, while "fetch" is working just fine, when I try to "create" a new model to the collection - I get this network error:
Your application is not using your jsonp configuration in the Collection.create() This is because the Collection delegates in the Model.save() to create elements.
Check the Collection.create() code.
You should override the Project.sync() as well.

Nested Model in Backbone.js

I want to map JSON having hierarchical structure onto Model. I can map the data at a top hierarchy onto Model. However, I can't map it onto Model which nested the element which I nested.
JSON
{
"attr1":"data1",
"chi1": {
"attr1":"chi1_data"
},
"list1":[
{"name":"name1"},
{"name":"name2"}
]
}
JavaScript
var Child2 = Backbone.Model.extend({
fun1:function() {
alert("this is Child2");
}
});
var List1 = Backbone.Collection.extend({
url: "list1",
model: Child2,
fun1:function() {
alert("this is List1");
}
});
var Child1 = Backbone.Model.extend({
});
var Root1 = Backbone.Model.extend({
url: "sample.json",
defaults : {
list1 : new List1,
chi1 : new Child1,
}
});
var View1 = Backbone.View.extend({
el: "#friends",
events: {
"click button": "sample"
},
initialize: function() {
this.root1 = new Root1();
},
sample: function() {
this.root1.fetch({
success: function(model) {
// this is success
alert(model.get("attr1"));
// this is error
alert(model.get("list1").fun1());
// this is error too.
model.get("list1").each(function(attr) {
alert(attr.fun1());
});
},
error: function(model, res) {
alert("error: " + res.status);
}
});
},
});
You might want to take a look at this plugin.
http://documentup.com/afeld/backbone-nested/
Might not be exactly what you want, but it could at least point you in the right direction.
The other thing you can do is override the parse method on your model...
parse: function(resp){
// And setup the model using the raw resp
// The resp data is your json from the server and will
// be used to setup the model. So overriding parse, you can
// setup the model exactly they way you want.
return resp;
}
thank you jcreamer.
backbone-nested plugin seems to be different from what I want to do.
I can realize the nest of the model. In using parse function.
// it is able to get "chi1_data"
new Child2(JSON.parse(JSON.stringify(resp["chi1"]))).get("attr1")
// it is able to get "name2"
new Child2(JSON.parse(JSON.stringify(new List1(JSON.parse(JSON.stringify(resp["list1"]))).get(2)))).get("name")
I found Backbone-relational plug in. I will try this
https://github.com/PaulUithol/Backbone-relational

Categories

Resources