Emberjs: Save persistent data from remote API to local storage - javascript

I am still a little confused about the way Ember fetches data from remote API and save them in the browser.
So I have created a REST Adapter to get sets of records with an Ajax call, a serializer, and the corresponding model. Suppose they are posts, and I have successfully made a posts index page where the user can click on any post to get into the post detail page.
The Ajax call happens on the index page, and using the Ember inspector, it is clear that all the records are stored in the local store.
But when I click the "back link" which is available on every post detail page, it does redirect to '/posts/' but it seems to make the ajax call again. So all the posts are fetched from the API once again making the page much less responsive.
Here's my questions:
How does that part of Ember work and how do I make Ember simply get the records from the local store without making Ajax call again and again? (unless the user refresh the browser)
If I make a GET request to 'post/1' , no data will be available since in this route no Ajax call should be made. But how do I let the data show? Should I set up another REST adapter for individual post or is it possible to get the record from the local store if an Ajax call has been made?
Hope that makes sense and thanks in advance!
Update:
My post adapter:
App.PostAdapter = DS.RESTAdapter.extend({
findAll: function(store, type, sinceToken) {
var url = 'THE URL TO GET JSON BACK';
return $.getJSON(url).then(function(data) {
return posts;
})
}
});
My Post and Posts routes:
App.PostRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.postId);
}
})
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
}
})

Regarding your first question: It depends on the model callback of your route. If you use the all method of the store, the Ajax Request won't be made again. (But: You'd be responsible to get the data the first time around. You way want to sideload it somewhere or may want to call find if all didn't return anything. It depends on your application.
Regarding your second question: The RESTAdapter should be available for single data items as well as for lists. You can implement a model hook in the route using find. If you link-to this route with an object (instead of an ID), this hook won't be called. So the hook would only be called when needed.

Related

Backbone and pusher - what data to send?

I am working a on backbone application at the moment, that "talks" to an API. A user can edit an organisation for example, the PATCH request will go to the API and get saved to the database, on a successful the API then "talks" to Pusher via this line,
Pusherer::trigger('organisation_'.$id, 'organisation:change', json_encode(array('organisation' => $organisation)));
Basically this telling pusher to trigger an event on the organisation_21 channel, the event that has happended is the organisation:change one, and the data to send is the organisation model.
What happens then on the Backbone side is that that i bind a method on to that channel and when the event happens that method will run, and update the view for the subscribed user.
HOWEVER, the data for my organisations has gotten quite big, the JSON object is 11.8kb, pusher won't process anything more than 10kb, is there a better way to work with backbone, my api and pusher other than sending the entire model?
On suggestion I like the idea, of doing the save, and fetching the model for new data in realtime via pusher. Would that look something like this?
organisationChanged:function(){
var self = this;
this.model.get('organisations').fetch({ //send GET To /api/organisation/id
success: function(model, response) {
self.model.get('organisations').set(response);
}
});
}
Fetch the model and set the attributes returned from the server to those of the model - so far this sounds correct to me yes? The complication comes that the model also contains a couple of collections, will set work on these, or is there a better way?
The edit is the right idea, but with a little changes.
The fetch would automatically change the model. From your code, it looks like your model is a bigger model, and organization is just a sub model. If this is the case:
var organizationmodel=this.model.get('organisations') // This would get the organization model.
organizationmodel.fetch({ //send GET To /api/organisation/id
success: function(model, response) {
// the model, and the organizationmodel both actually should point to the same object
// and they are already changed based on the server returned stuff. so no need to do a set.
// if they are not, you can just set it again.
self.model.set('organizations',model);
}
});

How to make sure to add response from server to ember store

I am using ember.js for UI side development of application. For every action we are showing a modal popup and we have forms in that pop ups and then on form submission we have some respective actions which have controllers. Its working fine upto here, if we are making a web service call it is taking the server response and updating respective model js file in store.
So new requirement came that in that form if we give some value in a particular text field then a server call should go and fetch some information. Now the problem is, this value is not updating in store. The html(template) is like any html form and mouseout has some action calling on a particular field. That action is
App.MainFormSubmitActionController = App.ModalController.extend({
needs : 'application',
actions : {
actionToBeCalled : function() {
this.store.find('xyzModel');
//the above line should normally find xyzModal in store if not found then hit server and then update the store too
},
mainFormSubmitAction : function() {
//some task done here
}
}
});
the json object I am getting back from server is :
{"payload":{"xyzModel":{"gmp":25.0,"type":"someType","id":1}},"status":"SUCCESS"}
and the js file is
App.XyzModel = DS.Model.extend({
"type" : DS.attr(),
"gmp" : DS.attr()
});
The server call is happening fine, my problem is why ember store is not getting updated when I am getting a response and model not found error is not there. I google a lot but no one seems to have faced the same problem. What cud have I possibly done wrong.
Ember doesn't expect the model data to be wrapped in anything, what you can do is either change the api behaviour or you can customise your Application or Model serializer like so to extract the model and make it the top level object:
App.XyzModelSerializer = DS.RESTSerializer.extend({
extractSingle: function(store, type, payload, id) {
delete payload.payload.status;
payload = {xyzModel: payload.payload.xyzModel };
return this._super(store, type, payload, id);
}
});
there are other methods for when mutiple records are returned, see http://emberjs.com/api/data/classes/DS.RESTSerializer.html

How do I know my collection already has data using Backbone.JS?

I am developing a site using javascript framework BACKBONE.JS. In my site, There is one Category Selection drop down. Using Backbone collection fetch, I have rendered my category drop down successfully. In my header i have three horizontal menu[image shown below]. User click of the menu(Page navigation is done using backbone routers). My main content of the day will change. The user can filter the content based on the category. My category filter drop down option will not change frequently.
ALL = http://www.Site1.com
MOBILE = http://www.Site1.com/#all/mobile
DESKTOP = http://www.Site1.com/#all/desktop
My Router:
dealapp.AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"all/mobile": "mobile",
"all/descktop": "displayAllVoucher"
},
home: function () {},
mobile: function () {},
desktop: function () {}
});
Success Case
I am loading my site Using "http://www.Site1.com/". The function home will get a call and do the listed action. If i am navigating to some other tab(mobile/desktop), my category drop down displaying.[ Note : i am fetching my category from the server in the home function]
scenario
I am loading my site using "http://www.Site1.com/#all/deal" directly. In this case my category drop down is not rendering , i am getting an empty drop down. I know that i haven't added my category fetch in the other two functions mobile and desktop. If i include the category fetch in mobile and desktop function each time then my category fetch call goes to server and fetches data from server.
My doubt
How do i know if my collection already has data? I want to reuse the already downloaded data. If data not available in the local storage then i need to fetch it from the server.
You can override fetch on the collection. Fetch returns a deferred object, you can store this on the collection itself. If the deferred is null you will call the prototype fetch. The advantage is that in your code you always call fetch, and if the collection has data you return the cached data.
fetch : function(options) {
if(this.deferred){
return this.deferred;
}
this.deferred = Backbone.Collection.prototype.fetch.call(this, options);
return this.deferred;
}
This specific problem was dealt with by others and a few plugins can be found.
The one I am currently using with success is the Thorax framework that adds a few things over Backbone.
For Model and Collection they added isPopulated() and isEmpty() method as can be seen here in their documentation.
They will tell you if there is data in the collection or not. If you don't want to use the entire framework, just copying the code from their Git repository here, would do.
In a few words they solve the problem by overriding fetch to set a property called _fetched to true when the data are fetched.
Another way would be to cache the data. Most of the time this is a good idea, but this depends. In your scenario it could be a good idea to cache it in a localStorage.
A plugin I found that seems to do it's job is Backbone fetch cache.
Description:
This plugin intercepts calls to fetch and stores the results in a
cache object (Backbone.fetchCache._cache). If fetch is called with {
cache: true } in the options and the URL has already been cached the
AJAX call will be skipped.
Yet another version is mentioned in this answer: Caching collections in backbone.js?
As the answerer there said, you could do it similar to this:
var manager = (function(){
var constructors = {
'example': ExampleCollection
};
var collections = {};
return {
getCollection: function(name) {
if(!collections[name]) {
var collection = new constructors[name]();
collection.fetch();
collections[name] = collection;
}
return collections[name];
}
}
})();
Here the manager is responsible for instantiating collections and
fetching them. When you call:
var exampleCollection = manager.getCollection('example');
you get an instance of example collection with data being already
fetched. Whenever you need this collection again you can call the
method again. You will then get the exact same instance with no need
to fetch it again.

Pagination by token in sever side and ember.js

I have in the frontend an ember.js application with ember-data to abstract the models and in server side I have a REST Api. When I need a list of the items in the server I do a GET request for /items in the case that the list have more than 10 items I receive a json just like that:
{
nextPageToken:"token",
items:[
...
]
}
The nextPageToken is a token to be used to get the items in the next page, in this particular case making another GET request with the page token included: /items?pageToken=token.
I don't know the best way that I can use this tokens to create a page showing the items with a link to the next and the previous pages (storing the token from the previous page) with ember and ember-data.
UPDATE
After some research I found a solution to navigate to next page extending the extractMeta function in the RESTSerializer to store the nextPageToken as metadata.
App.ApplicationSerializer = DS.RESTSerializer.extend({
//...
// Extract the pageToken and store it as metadata from the desired type.
extractMeta: function(store, type, payload) {
store.metaForType(type, { nextPageToken: payload.nextPageToken });
}
}
And then in the controller from my page create a property based in the stored page token.
App.ItemsController = Ember.ArrayController.extend({
//...
// Get the page token store as metadata from the item model.
nextPageToken: function(){
var meta = this.store.metadataFor("item");
return meta.nextPageToken;
}.property('item')
}
Now it's possible to use the page token in your template to create a link to the next page.
With that, part of the problem is solved, but I still need to find a solution to navigate to previous pages.

Postponing search until template load

I want to make a request to a server to get a bunch of news articles based off of what the user clicks on (recent, trending, etc). I'd like to be able to load the page first and show a loading bar while I wait for the response from the API. What I have, and although it works and returns the JSON data I need, will wait until the response comes back from the server before loading anything. This is all it is so far:
What I want to achieve is the following: Load up an empty array of objects, and then make API calls to articles incrementally (let's say grab all the articles from this hour, then last hour, then the hour before, and so on) and whenever I retrive articles populate the view (I'm assuming I can just inject them into a controller somehow) however I'm getting lost on the Emberisms on how to add to that array of objects. I think I need to make an ArrayController and then create a model in said array, then call a function to add to it, but as I said I'm lost as to how to add items into that controller on the fly
App = Ember.Application.create();
App.Router.map(function() {
this.resource('today');
});
App.TodayRoute = Ember.Route.extend({
model: function() {
return $.getJSON('/today');
}
});
To elaborate a bit on the reasoning for my question - I'm confused on how to approach this with ember. I'm familiar on how to do something like this using jquery, however I'm trying to learn the framework and am having a little bit of trouble originally knowing what the division of labor is between the two. I know the actual AJAX requests should be jquery, but I want to do as much as possible in Ember. If this however is something that should be done by jquery though, then that is fine as well!
The getJSON method looks to have a callback function on success you could use, something like this maybe?
App.TodayRoute = Ember.Route.extend({
model: function() {
$.getJSON('/today', function(data) {
// hide loading here
/ data is the JSON returned
});
}
});
Check out this link http://api.jquery.com/jQuery.getJSON/
I found the solution - and like everything with ember, it was very simple. I used some blank fixture data, then defined my controller as below.
App.ArticleRoute = Ember.Route.extend({
model: function() {
return App.Article.find();
}
})
Then upon pageload I call a method focused around
App.Article.createRecord({
// Attributes, pulling from the returned JSON
})
Simple! :)

Categories

Resources