RequireJS and Backbone ajax Issue - javascript

I am new to RequireJS and Backbone and was trying to understand why the ajax (fetch) code is not working as excepted.
main.js
require.config({
shim: {
'backbone': {
deps:['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
}
},
paths: {
'jquery': 'vendor/jquery/jquery',
'underscore': 'vendor/underscore/underscore',
'backbone': 'vendor/backbone/backbone'
}
});
require(['views/appViews'], function(AppView) {
new AppView();
});
AppView.js
define(['jquery', 'underscore','backbone', '../collections/appCollections'], function($, _, Backbone, AppCollections) {
var App = Backbone.View.extend({
initialize: function() {
_.bindAll( this, "render" );
this.collection = new AppCollections;
var $this = this;
this.collection.bind("all", this.render, this);
var x = this.collection.fetch();
/*
* This was not working
this.collection.fetch({
success: function() {
$this.render();
}
});
*/
},
template: _.template( $('#tweetsTemplate').html() ),
render: function() {
console.log(this.collection.toJSON());
//$(this.el).html(this.template({ tweets: this.collection.toJSON() }));
}
});
return App;
});
AppCollections.js
define(['jquery','underscore','backbone','../models/appModels'], function($, _, Backbone, AppModel) {
var AppCollection = Backbone.Collection.extend({
model: AppModel,
url: 'http://search.twitter.com/search.json?q=dog',
parse: function ( response, xhr ) {
return response.results;
},
// Overwrite the sync method to pass over the Same Origin Policy
sync: function (method, model) {
var $this = this;
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: $this.url,
processData: false
} );
return $.ajax(params);
}
});
return AppCollection;
});
AppModel
define(['underscore', 'backbone'], function(_, Backbone) {
var AppModel = Backbone.Model.extend({});
return AppModel;
});
Problem is: the render method is not called once collection is fetched. Also no error in developer tool. So not sure where to look.
Any pointer is helpful.
Thanks
Viral

The success callback is not called because your sync method is not passing it on to ajax.
The third parameter of sync is the options object, which has the success callback in it.
sync: function (method, model, options) {
var $this = this;
var success = options.success;
options.success = function(resp) {
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: $this.url,
processData: false
}, options);
return $.ajax(params);
}
This way, ajax will properly call the success callback defined in Backbone Collection's fetch which will in turn call the success callback you passed into fetch.
Then fetch:
this.collection.fetch({
success: function() {
$this.render();
}
});
Here is fetch from Backbone source. You can see it passes the success callback to sync.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var success = options.success;
options.success = function(collection, resp, options) {
var method = options.update ? 'update' : 'reset';
collection[method](resp, options);
if (success) success(collection, resp, options);
};
return this.sync('read', this, options);
},

When you overwrite the sync method in backbone it will not trigger the events properly. Try overwriting the sync method this way
Or, you can simply make your success function look like backbones source:
success = function(resp) {
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};

Great response Paul, but just wanted to point out the following:
When attempting to retrieve the data from your ajax call by overriding fetch's success function, I had to make the following modification to your code:
sync: function (method, model, options) {
var $this = this;
var success = options.success;
options.success = function(resp) {
if (success) success(resp);
model.trigger('sync', model, resp, options);
};
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: $this.url,
processData: false
}, options);
return $.ajax(params);
}
Note the difference in the line:
if (success) success(resp);
This was needed in order to properly pass the success function the response, otherwise it was being overwritten by the model. Now, in the success function of fetch, you can output the data:
var $this = this;
this.collection.fetch({
success: function(collection, response, options){
$this.render(response);
}
});
This passes on the ajax data (response) to the render function to do what you like with. Of course, you could also manipulate the data in any which way beforehand as well.
Ideally, I'd like to be able to pass the data into the collection.models object, as Backbone does by default. I believe it has something to do with how the data is being parsed, but I haven't figured it out yet. If anyone has a solution, I'd love to hear it :)
Update:
I've managed to override the parse function and process the JSON data from my ajax call in such a way so as to stay true to the way that Backbone structures its collection object. Here's the code:
parse: function(resp){
var _resp = {};
_resp.results = [];
_.each(resp, function(model) {
_resp.results.push(model);
});
return _resp.results;
}
This creates a new object with an array of your models called results, which is then returned to your fetch function, allowing you to directly access the attributes of each model.

Related

Backbone Model : Ajax request in parse override

I have a scenario where a fetch() call of a model will return data from which a property will need be passed to another API and return type from that API will be the actually required data.
var Issue = Backbone.Model.extend({
urlRoot: 'https://api.github.com/repos/ibrahim-islam/ibrahim-islam.github.io/issues',
parse: function(response, options){
var markdown = new Markdown({ text : response.body });
markdown.fetch({
contentType: 'application/json',
type: 'POST',
data: JSON.stringify( markdown.toJSON() ),
success: function(data){
response.body = data;
}
});
return response;
}
});
var Markdown = Backbone.Model.extend({
defaults:{
'text': '',
'mode' : 'markdown'
},
url: 'https://api.github.com/markdown'
});
So, when an Issue will be fetched:
var issue = new Issue({id: 1});
issue.fetch().then(function(){
//do stuff
});
It will have a property of body containing markdown syntax text which in turn I need to pass to another API and get the that response which will be passed down to view.
As can be seen from above, I tried overriding parse but its return type has to be an object and fetch will be async so what can I do here to make this work?
NOTE: I know aggregating the data in server and then receiving it will be best idea but that is not possible atm.
You could override the sync method in your Issue model to chain your requests.
var Issue = Backbone.Model.extend({
urlRoot: 'https://api.github.com/repos/ibrahim-islam/ibrahim-islam.github.io/issues',
sync: function(method, model, options) {
if (method !== 'read')
return Backbone.sync.apply(this, arguments);
// first request
var xhr = Backbone.ajax({
type: 'GET',
dataType: 'json',
url: _.result(model, 'url')
});
// second request
return xhr.then(function (resp1) {
var markdown = new Markdown({text : resp1.body || 'body'});
var data = markdown.toJSON();
// the callback to fill your model, will call parse
var success = options.success;
return Backbone.ajax({
url: _.result(markdown, 'url'),
dataType: 'html',
contentType: 'application/json',
type: 'POST',
data: data
}).then(function(resp2) {
// sets the data you need from the response
var resp = _.extend({}, resp1, {
body: resp2
});
// fills the model and triggers the sync event
success(resp);
// transformed value returned by the promise
return resp;
});
});
}
});
The options hash passed to Model.sync contains the callbacks to model.parse, you can use it to set the attributes on your model when you're satisfied with your data.
And a demo http://jsfiddle.net/puwueqe3/5/
I think you would have to override the model's fetch to get this to work
Consider what the default fetch looks like:
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
},
(github)
That implementation would not support an async version of model.parse, but since you create a model class using .extend you can override this with your own implementation so lets look at what it does. It takes an options objects, creates a success callback and then delegates to Backbone.Sync.
It's that callback that calls parse and that's what needs to be made to support async.
The quickest, dirtiest way to do this is probably to just copy and modify the existing default fetch.
var MyModel = Backbone.Model.extend({
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
function parser(resp, options, cb) {
...do your async request stuff and call cb with the result when done...
}
parser(resp, options, function(result) {
if (!model.set(result, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
});
};
wrapError(this, options);
return this.sync('read', this, options);
}
});
This is just an example of how you might try to solve this. I've not tested it and it might not work but I don't see any immediately obvious reasons why it shouldn't.

Backbone.js custom model request

Is there any way to create your own ajax method to make a POST request instead of using save ?
Save not only fires the AJAX request but triggers events and validation, so I wouldn't write my "own" save method.
However, you can always write a model function like:
var SomeModel = Backbone.Model.extend({
urlRoot: "/some/url",
altSave: function () {
$.post(this.urlRoot, {
/*assemble your post data*/
}, function (response) {
});
}
});
var s = new SomeModel();
s.altSave();
calling s.altSave(); will fire a POST request.
That's right and it works but I think this is a better solution:
'use strict';
define([
'underscore',
'backbone',
'config/appConfig'
],function(_,Backbone,Config){
var StatsModel = Backbone.Model.extend({
urlRoot: Config.urlStats,
url: function() {
var url = this.urlRoot + "/resource";
return url;
},
defaults: {
metricID: '',
groupByID: ''
},
requestStats: function(opts) {
var url = this.url(),
options = {
url: url,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(this.attributes)
};
_.extend(options, opts);
return (this.sync || Backbone.sync).call(this, null, this, options);
}
});
return StatsModel;
});
Thank you very much

using backbone with third party api

I'm trying to use backbone to grab hold of an instagram feed. This doesn't require authenticating the user, it is pulling a public feed available through:
https://api.instagram.com/v1/users/<user_id>/media/recent/?client_id=<client_id>
I've gotten as far as outputting the JSON response into the console, but I'm unable to make it display on my page.
In the code below, I use fetchData to grab the feed, and I'd like to eventually get it to a point where render outputs everything stylized on #social. However, despite setting the feed property to the JSON response, render still returns an empty object. console.log in fetchData however displays the proper information.
var social = {}
social.Instagram = Backbone.Model.extend();
social.InstagramFeed = Backbone.Collection.extend({
model: social.Instagram,
url: 'https://api.instagram.com/v1/users/<user_id>/media/recent/?client_id=<client_id>',
parse: function(response) {
return response.results;
},
sync: function(method, model, options) {
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: this.url,
processData: false
}, options);
return $.ajax(params);
}
});
social.InstagramView = Backbone.View.extend({
el: '#social',
feed: {},
initialize: function() {
this.collection = new social.InstagramFeed();
this.fetchData();
this.render();
},
render: function() {
console.log(this.feed);
},
fetchData: function() {
this.collection.fetch({
success: function(collection, response) {
// console.log(response);
feed = response;
// console.log(this.feed);
},
error: function() {
console.log("failed to find instagram feed...");
}
});
}
});
social.instagramview = new social.InstagramView;
I've tried to output the information using just the fetchData function however this.el.append(response) results in a notice saying that el is undefined.
Your render method is called before the fetching has completed. You should bind to the sync event of the collection and call render in the event handler.
social.InstagramView = Backbone.View.extend({
el: '#social',
feed: {},
initialize: function() {
this.collection = new social.InstagramFeed();
this.fetchData();
this.collection.on('sync', function(){
this.render();
}, this);
// this.render();
},
...
})
Quoting Backbone.js documentation : sync event is fired :
when a model or collection has been successfully synced with the server.

how to pass get this for the collection from the fetch:success event?

Is there a way how to refer to owner-collection from the success-event.
example, i am using this where i want to refer to the collection:
var col = Backbone.Collection.extend({
model: MobileService,
url: 'file.json',
initialize: function(){
this.fetch({
success: function(){
this.trigger('fetched');
},
...
From the fine manual:
fetch collection.fetch([options])
[...] The options hash takes success and error callbacks which will both be passed (collection, response, options) as arguments.
So you can use:
this.fetch({
success: function(collection) {
collection.trigger('fetched');
}
});
There's always:
initialize: function(){
var self = this;
self.fetch({
success: function(){
self.trigger('fetched');
},

Infinite scroll backbone view

I would like to have an infinite/endless scroll data rendering from a JSON feed. I am interested in accomplishing something similar to Pinterest or Google Reader using Backbone/Underscore/jQuery.
How do I apply the infiniScroll.js module to my backbone view? The goal is to fetch and append the next page's ("page" URL parameter) tweets when you scroll near the end of the page. Problem: when reaching the bottom of page, same JSON page feed is fetched. How to change the page parameter in the URL to be &page=2, etc.
Demo: http://dl.dropbox.com/u/19974044/test.html OR http://jsfiddle.net/k4rPP/3/
// Define the model
Tweet = Backbone.Model.extend();
// Define the collection
Tweets = Backbone.Collection.extend({
model: Tweet,
// Url to request when fetch() is called
url: 'https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&trim_user=false&count=10&screen_name=cnn&page=1&callback=?',
parse: function (response) {
return response;
},
// Overwrite the sync method to pass over the Same Origin Policy
sync: function (method, model, options) {
var that = this;
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: that.url,
processData: false
}, options);
return $.ajax(params);
}
});
// Define the View
TweetsView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'render');
// create a collection
this.collection = new Tweets;
// Fetch the collection and call render() method
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
// infiniScroll.js integration
this.infiniScroll = new Backbone.InfiniScroll(this.collection, {success: this.appendRender, param:'page', includePage:true});
},
// Use an extern template
template: _.template($('#tweetsTemplate').html()),
render: function () {
// Fill the html with the template and the collection
$(this.el).html(this.template({
tweets: this.collection.toJSON()
}));
}
});
var app = new TweetsView({
// define the el where the view will render
el: $('body')
});​
The url attribute can be specified as a function rather than a string. So you could replace it with something like this:
...
currentPage: 0,
url: function() {
this.currentPage++;
return 'https://path.to.url/?page=' + this.currentPage;
},
...

Categories

Resources