By using ajax I can access the XHR Object simply making:
$.ajax().fail(function (XHR) {
// some code
});
When saving a backbone model:
var MyView = Backbone.View.extend({
saveModel: function () {
this.myModel.save({
error: this.onError
});
}
onError: function (xhr) {
// how to access xhr?
}
});
How should I get XHR when I save a backbone.model onError server event?.
When you call any of the functions that go through Backbone.Sync, Backbone returns a reference to the XHR:
var MyModel = Backbone.Model.extend({
url: "/some/path/that/is/an/error/"
});
var myModel = new MyModel();
xhr = myModel.save( {} , {
error: function(model, response) {
console.log(xhr);
}
});
Also, note that Model.save() takes 2 arguments - properties to change before saving, and the options hash as a second argument.
Here's a jsFiddle example: http://jsfiddle.net/edwardmsmith/8AVjy/7/
Post Comment:
I've never really needed to do it, but this is what I'd probably do:
var MyModel = Backbone.Model.extend({
url: "/some/path/that/is/an/error/"
});
var MyView = Backbone.View.extend({
saveModel: function () {
that = this;
xhr = this.model.save({}, {
error: function (model, resp) {
that.onError(xhr);
}
});
},
onError: function (xhr) {
// how to access xhr?
console.log(xhr);
}
});
var myModel = new MyModel();
var myView = new MyView({model: myModel});
myView.saveModel();
An updated jsFiddle for this: http://jsfiddle.net/edwardmsmith/8AVjy/14/
Related
I have a RESTful json API, which I need to access in my front-end Backbone site.
So, I did this:
/* Goal collection */
var GoalCollection = Backbone.Collection.extend({
model: GoalModel,
url: "http://staging.api.hiwarren.com:8080/api/v1/goals/?callback=?",
sync: function(method, collection, options) {
options.dataType = "jsonp";
// options.timeout = 10000;
return Backbone.sync(method, collection, options);
},
parse: function(response) {
return response.results;
}
});
/* View for the goal collection */
var GoalCollectionView = Backbone.View.extend({
tagName: 'ul',
initialize: function(callback){
var that = this;
_.bindAll(this, 'render');
that.collection = new GoalCollection();
that.collection.bind('reset', this.render)
that.collection.fetch({
dataType: 'jsonp',
success: function(collection, response){
that.render();
if(callback) callback(that);
},
error: function(collection, response){
throw new Error("Goal fetch error - " + response.statusText);
}
});
},
render: function(){
this.collection.each(function(goal){
var goalView = new GoalView({ model: goal });
this.$el.append(goalView.render().el);
}, this);
return this;
}
});
I am trying to use JSONP, because it is a different domain. I've followed answers to questions similar to this, as you can see in my code, but it doesn't work.
Instead, I get this error message:
Uncaught Error: Goal fetch error - load
Backbone.View.extend.initialize.that.collection.fetch.error
options.errorjquery.js:3094 jQuery.Callbacks.fire
jQuery.Callbacks.self.fireWithjquery.js:8261 done
jQuery.ajaxTransport.send.jQuery.prop.on.callback
jQuery.event.dispatchjquery.js:4116 jQuery.event.add.elemData.handle
What am I doing wrong? How can I make this work?
Are you sure that the domain you are trying to access has support for jsonp? Some sites only enable the json format.
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.
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.
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;
},
...
Hi Im having problems with javascript! i have main.js and Model.js. Model.js is a javascript oop class in need to access its functions in main.js how do i do that? I keep getting an error that Model is not defined. Are there tools needed for this to work or something is wrong in the code?
Model.js
Model = {};
Model.init = function() {
alert("model");
}
Model.getList = function(){
var list;
$.ajax(
{
url:'???',
type: 'GET',
dataType: 'json',
success: function(data)
{
list=data;
}
error: function(data)
{
alert("error");
}
});
return list;
}
main.js
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
var testins=new Model();
var list=Model.getList();
alert("result: "+testins);
}
I really could use some help.
so I tried MrCode approach and for experimental reasons put the code in one file because main.js still could not access the Model.js file.
main.js
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
alert("aaa"); //first
var testins=new Model();
var list=testins.getList();
alert("result: "+testins); // third
alert("list"+list); //fourth
}
function Model()
{
this.init = function()
{
alert("Model");
}
this.getList = function()
{
var list;
$.ajax(
{
url:'??',
type: 'GET',
dataType: 'json',
success: function(data)
{
list=data;
alert("success"+list); //fifth
},
error: function(data)
{
alert("error");
}
});
alert("success"+list); //second
return(list);
}
}
but following the alerts i see the that the $.ajax part is done last.
Do
function Model() { // this is the "constructor"
}
And replace
Model.init = function() {
by
Model.prototype.init = function() { // provide the function to all instances
(and the same for getList)
This will enable
you to call new Model()
the init function to be inherited by the objects you create with new Model().
Use it like this :
var testins=new Model(); // create an instance
var list=testins.getList(); // call the instance method
You may be interested by this MDN document about prototype and inheritance.
function Model()
{
// define public methods
this.init = function()
{
alert("Model");
}
this.getList = function()
{
var list;
$.ajax(
{
url:'???',
type: 'GET',
dataType: 'json',
success: function(data)
{
list=data;
}
error: function(data)
{
alert("error");
}
});
return list;
}
}
var testins = new Model(); // create an instance of Model()
var list = testins.getList(); // call its method