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! :)
Related
Ok so im messing around with Backbone for the first time. I think I've pretty much covered all the basics of frontend logic, but i have never really been any good at backend logic and coding.
I'm working with wordpress and creating a theme using backbone. My understanding is as long as i set up a template page that has the correct containers that my backbone code will render views in, the fact that it's a wordpress theme instead of it's own app shouldn't really change anything on the frontend side.
I'm at the stage where i want to save a model so that i can fetch it in my routes to link to my view to render.
I'm unsure about the whole process of saving data. I know i need to give the model attribute 'urlRoot' a string but i don't know what that string should be, and what happens after that.
Can someone explain the whole process, especially in terms of how to do it with Wordpress. (i did stumble upon the WP REST API plugin that i think helps, although i don't exactly know how.)
EDIT
OK so in the end i presume my problem was something to do with authentication when trying to access the database as the textResponse was just returning the entire HTML for the current page i was on, probably due to the fact it wasn't getting through to the database and being redirected back to the page.
After googling around for a while i came across this. Rather than reinventing the wheel I installed this plugin and followed the setup instructions and low and behold it worked pretty much out of the box. If your trying to build a Backbone theme i suggest using the WP-API Client JS plugin with the WP REST API plugin. Seems to cover everything.
How to expose a WordPress blog's content through an API?
WP REST API seems like a good way to start. There are a lot of options and it exposes everything you need.
Note that it is named WordPress REST API (Version 2) in the wordpress.org plugin directory.
You can test that the plugin works by navigating to:
http://www.example.com/wp-json/wp/v2/
It should output all the information on the blog as a big JSON dump.
You can also test that it works for other endpoints, like post:
http://www.example.com/wp-json/wp/v2/posts
There's a Backbone plugin for the WP REST API that works out of the box.
How to communicate with the API?
This is a simple example using Backbone without any plugin. If you want to know how to use the plugin, see the documentation for it.
Since it offers a lot of arguments that can be passed in the URL, I made a small collection and an example of how it could be used.
var API_ROOT = '/wp-json/wp/v2/',
DEFAULT_API_ARGS = ['context' /* etc. */ ];
var WordPressCollection = Backbone.Collection.extend({
constructor: function(models, options) {
options = options || {};
this.apiArgs = _.union(DEFAULT_API_ARGS, this.apiArgs, options.apiArgs);
this.args = _.extend({}, this.args, this.getApiArgs(options));
WordPressCollection.__super__.constructor.apply(this, arguments);
},
getApiArgs: function(obj) {
return _.pick(obj, this.apiArgs);
},
fetch: function(options) {
options = options || {};
options.data = _.extend({}, this.args, this.getApiArgs(options), options.data);
return WordPressCollection.__super__.fetch.call(this, options);
},
});
And to use it:
var CommentCollection = WordPressCollection.extend({
url: API_ROOT + 'comments',
// all the arguments to look for in the passed options
apiArgs: ['page', 'per_page', 'post' /* etc. */ ],
});
var myPostComments = new CommentCollection(null, {
post: 23 // id
});
console.log(myPostComments.url());
myPostComments.fetch({ page: 2 });
The fetch should make a GET request to:
/wp-json/wp/v2/comments?post=23&page=2
And from that point, the WP REST API plugin takes control. It returns a new JSON encoded array of comment objects in the body of the response.
It should looks something like this:
Backbone automatically parses the JSON received, so you don't need to worry about that and you just have to go on and use it:
myPostComments.each(function(comment) {
console.log(comment.get('author_name'));
});
Then, saving a new comment is a matter of calling:
// check the doc for the comment object details
myPostComments.create({
post: 23,
content: "my new comment",
/* etc. */
});
And this would make a POST request to /wp-json/wp/v2/comments.
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.
I'm no sure I'm using the correct words, but I've looked at the localTodos app, and a few other online tutorials.
I'm reading in to Addy's free online book here:
http://addyosmani.github.io/backbone-fundamentals/#implementation-specifics
but right now I'm getting too much theory and just need to do a basic GET from my server and populate my Collection.
Can someone provide a hello World for a GET / synch request. All the mysql tables are set up and so is the code that provides a nice JSON stream of my table, neatly organized.
I shouldn't need to install a PHP framework as I can respond with the JSON stream just fine on my own.
I just need a starting point as I'm guessing it will be a few weeks before the book hits this if it does at all.
I tagged this PHP but I don't think it should matter, as all Backbone will see is a JSON stream.
Ok the basics are.
use "fetch" to get something from server.
use "save" to put or post something from server.
use "destroy" to delete something from server.
To perform fetch you'll need a code like this:
Inside your Model
//Coffescript
url: "pathToYourAPi/"
getAllFromServer:->
#fetch()
//Javascript
url: "pathToYourAPi/",
getAllFromServer: function() {
return this.fetch();
}
This is the simplest way to get data from server. But if you want to get an specific data from server, you maybe should pass an Id or something.
//Coffeescript
url:"/pathToYourAPi/"
setAttributes:->
#set("id": 1)
getItenFromServer:->
#fetch()
// Javascript
setAttributes: function() {
return this.set({"id": 1});
},
getItenFromServer: function() {
return this.fetch();
}
It will request to your api path passing the number 1 as "parameter" to server.
If you want to specify the data that you want to sendo to server in another way, you need pass a Object called data when you're "fetching"
example inside model.
//Coffescript
GetSomeData: ->
#fetch({ data:{ id: 1}})
//Javascript
GetSomeData: function() {
return this.fetch({data: {"id": 1}
});
I have a post about tips using backbone, unfortunately it's only available in portuguese.
try to use google to translate it.
http://www.rcarvalhojs.com/dicas/de/backbone/2014/06/04/5dicas-backbone.html.
Hope it helps.
I am using Ember data and The RESTAdapter with an extension for Django.
Here is a JSBin
Here is how our routes are set up:
App.Router.map(function() {
this.resource('locations');
this.resource('location', {path:'locations/:location_id'}, function() {
this.resource('items', function() {
this.route('create');
});
this.resource('item', { path:'item/:item_id' }, function() {
this.route('edit');
});
});
});
App.LocationsRoute = Ember.Route.extend({
model: function () {
return this.get('store').find('location');
}
});
App.ItemsRoute = Ember.Route.extend({
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
return this.modelFor('location').get('items');
}
});
Now this all works fine when we navigate to locations/1/items. The user is presented with a list of items relevant to the location with id 1.
When the user clicks one of these items it brings the url to #/locations/1/item/1 and displays the details of the item with id 1.
Now what doesnt work is this:
When I hit the back button the #/locations/1/items route loads but it does not have its data any more and no REST call to api/locations/1/items occurs. Even though the data displayed just fine when we first navigated to #/locations/1/items.
It is like Ember said "Well we already loaded that data, so we dont need to call the api again" but the data is somehow not being displayed in our template.
If I change the ItemsRoute model to this:
return this.get('store').find('item');
The scenario above works perfectly fine but the data is not based on our location.
Is there something I am missing with using this.modelFor? Or is my route set up incorrect?
Let me know if theres any extra info you need.
Update 1:
Having changed the model function to this I have discovered some new insights:
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
var location = this.modelFor('location');
var items = location.get('items');
return items;
}
Upon first load of #/locations/1/items the location variable holds the location model and the items variable holds something which has a 'content' property, 'isFulfilled: true' and some other things.
This correctly works and displays the list of items. When i click on a particular item and got to #/locations/1/items/1 then hit the back button. The breakpoint triggers and location is still correctly populating with the location model.
However the items variable seems to just be a PromiseArray, do I need to somehow wait for the promise to be fulfilled before this model hook returns? I thought Ember already did this automatically? I suppose this means that the route is loading before the promise is fulfilled and thats why there is not data displayed?
Therefore my list is not populated correctly.
I'm on a phone, so I'll update this a bit later with more, but the problem is your location resource isn't a child of locations. Becaude of that, ember says why waste time fetching that route if it isn't a part of that resource path. It only hits the location route, which I'm betting you don't have a model hook for fetching the single location (at least based on the code above).
Ok, here is the deal. I have fixed the issue but have no idea why it fixed the issue.
Without further ado:
Here is a jsbin example of the exact setup I have. The only difference between the real one and the jsbin is the fact that I am using the RestAdapter instead of the FixtureAdapter. This is not technically true because I am using the ember-django-rest-adapter which extends the REST one.
The issue described in the question does not present itself in the jsbin but the exact setup with the ember-django-rest-adapter does present the issue.
The fix was to break the cyclic relationship between User -> Site -> Items -> User
For example if I comment out the 'locations' relationship in the User model, the back button works.
Or if I comment out the 'owner' relationship to User in the Item model the back button works.
I might ask a separate question to see what the reasoning behind the problem is, although if someone can shed any light in to why this is happening I'll happily accept the answer here.
I've been trying to wrap my head around best RESTful practices while using BackboneJS. I feel like I've written myself into a bit of a knot and could use some guidance.
My scenario is this: a user wants to create a new Playlist with N items in it. The data for the N items is coming from a third-party API in bursts of 50 items. As such, I want to add a new, empty Playlist and, as the bursts of 50 come in, save the items and add to my Playlist.
This results in my Playlist model having a method, addItems, which looks like:
addItems: function (videos, callback) {
var itemsToSave = new PlaylistItems();
var self = this;
// Create a new PlaylistItem with each Video.
videos.each(function (video) {
var playlistItem = new PlaylistItem({
playlistId: self.get('id'),
video: video
});
itemsToSave.push(playlistItem);
});
itemsToSave.save({}, {
success: function () {
// OOF TERRIBLE.
self.fetch({
success: function () {
// TODO: For some reason when I call self.trigger then allPlaylists triggers fine, but if I go through fetch it doesnt trigger?
self.trigger('reset', self);
if (callback) {
callback();
}
}
});
},
error: function (error) {
console.error("There was an issue saving" + self.get('title'), error);
}
});
}
ItemsToSave is generally a Collection with 50 items in it. Since BackboneJS does not provide a Save for Collections, I wrote my own. I didn't care much for creating a Model wrapper for my Collection.
So, when I call Save, none of my items have IDs. The database assigns the IDs, but that information isn't implicitly updated by Backbone because I'm saving a Collection and not a Model. As such, once the save is successful, I call fetch on my Playlist to retrieve the updated information. This is terrible because a Playlist could have thousands of items in it -- I don't want to be fetching thousands of items every time I save multiple.
So, I'm thinking maybe I need to override the Collection's parse method and manually map the server's response back to the Collection.
This all seems... overkill/wrong. Am I doing something architecturally incorrect? How does a RESTful architecture handle such a scenario?
My opinion is do what works and feels clean enough and disregard what the RESTafarians credence might be. Bulk create, bulk update, bulk delete are real world use cases that the REST folk just close their eyes and pretend don't exist. Something along these lines sounds like a reasonable first attempt to me:
create a bulkAdd method or override add carefully if you are feeling confident
don't make models or add them to the collection yet though
do your bulk POST or whatever to get them into the database and get the assigned IDs back
then add them as models to the collection