Backbone fetch and working with the jqXHR object - javascript

I'm working on a new application using Backbone and don't have the backend APIs written yet so I am trying work with a JSON data file local to my project. I set its location as the urlRoot and am able to fetch it and receive the jqXHR object back. But, I am not sure how to interact with the responseText (assuming that's what I need based on the console.log output of the object).
This is the closest question I've found to mine but it wasn't closed with a final answer: backbone model is only returning and object, or JSON {readyState : 1}
var JobListings = Backbone.Model.extend({
urlRoot: 'scripts/app/data/listings.json'
});
// Instantiate the model
var jobListings = new JobListings();
console.log(jobListings.fetch()); // Returns jqXHR object
console.log(jobListings.attributes); // Returns empty object
How do I get at my JSON data? Also, it should be in a model and not a collection right? I am a bit fuzzy as to the role of collections based on other developers use of them. I thought models contain data and collections were sets of models.
My goal was to build two models for the data. One takes in the dirty JSON that needs to be cleaned up and the second being my outputted clean data for use by the application. Any help is much appreciated.
EDIT:
A snippet of my JSON... I'm still not having luck on how to get at my data. I'm sure I don't have to get to a view to see my data before hand.
[
{
"jobId": "1",
"applyUrl": "http://google.com",
"title": "President of the World",
"trackingCode": "1",
"jobDescription": "Stuff",
"requiredSkills": "Stuff",
"requiredExperience": [],
"postingDate": "2013-07-12T11:07:50Z",
"jobLocation": {
"countryCode": "US",
"region": "California",
"municipality": "Santa Monica"
},
"category": "Life",
"businessUnit": [],
"positionType": "Full-Time"
}
]

'Backbone.Model' contains data
'Backbone.Collection' were set of models. Like you can define it as in below:
var Library = Backbone.Collection.extend({
model: Book
});
You normally won't work with jqXHR directly.
Once data is fetched, you can access it "field by field" (or attribute-by-attribute) using get operation:
note.get("title")
You can edit the data using the set operation:
note.set({title: "March 20", content: "In his eyes she eclipses..."});
book.set("title", "A Scandal in Bohemia");
You can return a copy of the data (called attributes) using toJSON operation
Also Backbone will track if data is dirty or not by using hasChanged

Looking at your code, JobListing will be a model with job listing data. JobListings would be a collection of all JobListing models:
var JobListing = Backbone.Model.extend();
var JobListings = Backbone.Collection.extend({,
model: JobListing,
url: 'scripts/app/data/listings.json',
parse: function(data) {
// Not sure about your json structure, but you could do the following
// Do your cleanup here
return data.joblistings;
}
});
// Instantiate the collection
var jobListings = new JobListings();
var req = jobListings.fetch();
req.done(function() {
// contains all job listings
console.log(jobListings.models);
});

Related

How to get single book details from server using backbone model?

I am creating simple book library using backbone.js. Functionalities are very simple.
1.) Get All Books
2.) Get Particular Book (from server)
3.) Display Books
from the collection i can get all the books. But how can I get single book from server using Models ? I need to make ajax call to server to get the book details. since the details may update pretty soon (I don't want to get it from all books collection)
So far I created Views, Model, Collections as follows
var Book: Backbone.Model.extend({
defaults:{
name: "",
id: ""
}
});
var Library: Backbone.Collection.extend({
model: Book,
url: function(){
return "/books";
},
fetchBooks: function(){
fetch({
success: success_callback,
error: error_callback
});
},
fetchBook: function(bookId){
//how to get single book ?
}
});
How do I get single item from the server using my existing models and collections ?
If you need to use your Model outside the scope of a collection, you need to specify the urlRoot property:
var Book: Backbone.Model.extend({
defaults: {
name: ""
// ** See Note on Defaults below ...
},
urlRoot: "/books"
});
After that you can initialize the Model with a given ID and fetch it:
fetchBook: function(bookId){
var book = new Book({ id: bookId });
book.fetch({
success: function() {
console.log('Received book: ' + book.toJSON());
}
});
}
This will result in a GET request to `/books/{id}'. Assuming the server returns JSON, all your results will be populated as attributes on the book instance.
** NOTE on Defaults: You definitely shouldn't have an id default to an empty string, as Backbone uses the presence or absence of an id to determine if a model has been saved on the server or not. For instance if you ever use model.save(...), Backbone will issue a POST if the id does not exist, or a PUT if the id does exist.

Parse Backbone Collection as Object, not Array

I implementing nested comments using Backbone and Rails. My current implementation on the server side involves comment models that store the parent_comment_id (assuming they have one). When the app requests the comments for a given entity, I return a JSON object where the keys are the parent_comment_ids and the values are arrays of comments that have that a parent comment with that id. E.g.:
{
"" : [{id: 1, content: "I'm an unnested comment, parent_comment_id: ""}],
1 : [{id: 2, content: "I am nested under the comment with an id of 1", parent_comment_id: 1}, etc.],
...
}
The problem that I am currently having is that the Backbone collection corresponding to Comments returns an Array of length 1, where the sole element is the comments hash.
How can I override the parsing of the JSON response by the Comments collection so that the returned response is the JSON response hash and not an array?
You can override collection.parse method
After some further consideration, I decided that the best course of action was to offload the construction of the comments hash to the client side. By doing this, I can continue to parse collections as Arrays of objects as intended and then build the hash by calling the following method on the collection:
Jot.Collections.Comments = Backbone.Collection.extend({
...
commentsByParent: function() {
var commentsHash = {},
key;
this.models.forEach(function(c) {
key = c.get('parent_comment_id');
commentsHash[key] = commentsHash[key] || [];
commentsHash[key].push(c);
});
return commentsHash;
}
});

Backbone Collection: How to fetch a specific object from API to place in model

I am using a collection to fetch data from an API to place this into a model.
I do receive all data from the API, however the data is returned in an object and I only need a part of this object.
Let me clarify this with an example of the returned JSON:
{
"all-users":
[
{"username":"poekoe","lastsyncdate":"1376496898"},
{"username":"plaap","lastsyncdate":"1376494547"}
],
"total-users": "10",
"selected": 2
}
This is the JSON returned, however I only want need the 'all-users' array to be placed in my model.
At this point I am fetching data like this:
var userCollection = new UserCollection;
userCollection.fetch({
data: {
"search": "p",
"session: login.session
}
});
What can I do here to only use the 'all-users' array to be placed in the model?
Is there something in the Fetch what I can do? or should I alter the Model to only use the all-users array?
Thanks in advance!
You can override the parse method of the collection:
var UserCollection = new Backbone.Collection.extend({
parse: function(data){
return data["all-users"];
}
});
So your Collection will only consist of what the parse method returns, in this case the all-users array from your response json.

backbone.js change url parameter of a Model and fetch does not update data fetched

I have the following Model:
window.MyModel = Backbone.Model.extend({
initialize: function(props){
this.url = props.url;
}
parse: function(){
// #override- parsing data fetched from URL
}
});
// instantiate
var mod = new MyModel({url: 'some/url/here'});
I use this global variable 'mod' to fetch some data into this model from backend.
// fetch
mod.fetch({
success: function(){ ...},
error: ...
});
All above works well....
My Issue: I want to reuse this model by changing resetting the url and call fetch but it does not update the url somehow. I have tried the following:
mod.fetch({
data: {url:'/some/other/url'},
postData: true,
success: function(){ //process data},
error: ...
});
mod.set({url: '/some/other/url'});
// called fetch() without data: and postData: attributes as mentioned in previous
How do I set the url for my model so that I could call fetch() and it fetches data from updated url? Am I missing something. Thanks for any pointers..
UPDATE 1: Basically, I am unable to get updated values if I did
model.set({url: 'new value'});
followed by
model.fetch();
'model' is a global variable. Creating a fresh instance of 'model' works:
model = new Model({url:'/some/other/url'});
model.fetch();
however, works as required. Does this mean that a model instance is permanently attached to a url and it cannot be reset?
ANSWER TO MY QUESTION in UPDATE 1 Model instance is not permanently attached to a url. It can be reset dynamically. Please read through #tkone's thorough explanation and then #fguillens' solution for a better understanding.
After have understood the #tkone 's explanation...
If you still want to have a dynamic Model.url you always can delay its construction to run time, try this:
window.MyModel = Backbone.Model.extend({
url: function(){
return this.instanceUrl;
},
initialize: function(props){
this.instanceUrl = props.url;
}
}
Well the answer here is that you want to do:
mod.url = '/some/other/url'
The URL isn't part of the instance of the model itself, but rather an attribute of the MyModel object that you're creating your model instance from. Therefore, you'd just set it like it was an normal JavaScript object property. set is used only when the data you're setting (or conversely getting with get) is actually an attribute of the data you want to send/receive from the server.
But why you're changing the URL is the question we should be asking. The idea behind Backbone's model/collection system is that you speak to a REST endpoint and each model has a corresponding endpoint.
Like you've got a blog and that blog has an "entry" object which is available at:
/rest/entry/
And you've got a Backbone model for Entry:
Entry = Backbone.Model.extend({urlBase: '/rest/entry'});
Now when you save or fetch Backbone knows how this works.
So like you're making a new model:
e = new Entry();
e.set({title: "my blog rulez", body: "this is the best blog evar!!!!1!!"});
e.save();
This would then make Backbone do an HTTP POST request to /rest/entry with the body:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
(When you do your mod.set({url: '/some/other/url'}); you're actually adding a field called url to the dataset, so the server would send "url": "/some/other/url" as part of that JSON POST body above:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!",
"url": "/some/other/url"
}
The server would then respond with an HTTP 200 (or 201) response with the same model, only with, like, say, and ID attached:
{
"id": 1,
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
And that's not what you're looking for, right?)
Now you've got this model and it's got an ID. This means if you change it:
e.set('title', 'my blog is actually just ok');
e.save()
Backbone now makes an HTTP PUT request on /rest/entry/1 to update the resource on the server.
The server sees that you're talking about ID 1 on the /rest/entry/ endpoint, so knows to update an existing record (and send back an HTTP 200).
TL;DR
Don't change the URL, Backbone will. Make a new model for a new piece of data.
model.urlRoot = "/your/url"
OR
model.urlRoot = function(){ return "/your/url"; }
OR
model.url = "/your/url"
OR
model.url = function(){ return "/your/url"; }
Default 'url' property of a Backbone.Model object is as below. Backbone.js doc says:
Default URL for the model's representation on the server -- if you're using Backbone's restful methods, override this to change the endpoint that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
Clearly, It first gets the value of urlRoot, if not available, it will look at the url of the collection to which model belongs. By defining urlRoot instead of url has an advantage of falling back to collection url in case urlRoot is null.
You can set url as option in fetch function, like this:
var mod = new MyModel();
mod.fetch({
url: '/some/other/url',
data: {}
});

Backbone relational lazy loading

I'm using Backbone with my RESTful JSON API in order to create an app that works with posts and their comments. I've been trying to get Backbone Relational to work, but run into problems with my Lazy loading.
I load up a list of posts, without the related comments. On click of an post in the list, I open up a view that fetches the full Post, comments included, and renders this.
I've got 2 Backbone.RelationModels, Posts and Comments. The post relation to the comment is setup as folows:`
relations: [{
type: Backbone.HasMany,
key: 'comments',
relatedModel: 'Comment',
includeInJSON: true, // don't include it in the exporting json
collectionType: 'Comments'
}]
Now the problem I'm facing is that the relationships are initialized as soon as I retrieve my list, that do not contain the comments yet. I load up the full data later, by fetching the model by it's URI. However, the relationships don't seem to reinitialise, calling Posts.get(1).get('comments') returns a Comments collection that is empty!
Does anyone know how I could best tackle this problem? The data is there, it just seems the collection of the comments doesn't gets updated.
Edit: I can make the Post model bind it's change:comments to itself, which updates the collection. However, I can't seem to find a solid way to get the original comments' JSON, since this.get('comments') returns the Comments collection.
Note: In my collection, I do parse the JSON to make it work with my API, with the following code:
parse: function(response) {
var response_array = [];
_.each(response, function(item) {
response_array.push(item);
});
return response_array;
}
This is because the JSON returned by my API returns an object with indexed keys (associative array) instead of a native JSON array.
{
"id" : "1",
"title" : "post title",
"comments" : {
"2" : {
"id" : "2",
"description": "this should solve it"
},
"6" : {
"id" : "6",
"description": "this should solve it"
}
}
}
Thanks a bunch! Please ask any questions, I'm sure I've been vague somewhere!
The Backbone Relational model doesn't parse collections other then arrays, the JSON from my question didn't work. I changed the backend to return the comments in a proper array
{
"id" : "1",
"title" : "post title",
"comments" : [
{
"id" : "2",
"description": "this should solve it"
},
{
"id" : "6",
"description": "this should solve it"
}]
}
}
The RelationalModel doesn't respect the parse function that Backbone provides to parse your JSON before it moves on. With the backend returning "proper" JSON, the lazy loading works without any extra code.
You can also use the initialize method on your comment model, to simulate the parse method and define attributes with custom values like this (CoffeeScript):
initialize: (obj) ->
#attributes = obj.customKey

Categories

Resources