Infinite scroll backbone view - javascript

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;
},
...

Related

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

Backbone JSONP call from RESTful json API not working

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.

Backbone persistant view with persistant data

I have a navigation bar that is pretty much the only piece of persistant template in my web application (in that is always there regardless of route).
In that navbar I show a Login link if the user is not logged in, and if they are I show their name, the problem I having is that I can't get the nav to keep it's state. The uses logins,
runLogin: function(e) {
e.preventDefault();
var element = $(e.currentTarget),
self = this;
$.when($.ajax({
url: 'http://app-api.dev/api/oauth/access_token?grant_type=password&client_id=1&client_secret=xyz',
method: 'POST',
data: element.serialize(),
dataType: 'json',
success: function(data) {
// Set the cookie, this will need to be sent in every singal request going foward.
$.cookie('access_token', data.access_token);
// So we have the access token we use this to populate our user details
},
error: function(response) {
// We can build a universal error view that would be quite and pretty easy to do.
}
})).done(function() {
$.ajax({
url: 'http://app-api.dev/api/users/me',
dataType: 'json',
method: 'GET',
success:function(user) {
self.model.set(user);
self.launchDashboard();
}
});
})
},
launchDashboard: function() {
localStorage.setItem('user', '');
localStorage.setItem('user', JSON.stringify(this.me));
App.Routes.Application.navigate('dashboard', { trigger: true } );
}
Basically I login, request my details from the API, and use those to set a model, the model I am setting has already been passed in to the navigation view, and has a change listener on it.
showUserDetails: function() {
this.loggedInUserMenu = new App.Views.navigationViewLoggedIn({
model : this.model
});
},
the above fires this,
App.Views.navigationViewLoggedIn = Backbone.View.extend({
el: '.navbar-right',
template: _.template( $('#tpl-navigation-logged-in').html() ),
events: {
},
initialize: function() {
this.render();
},
render: function() {
this.$('.li-login').replaceWith( this.template({
user: this.model.toJSON()
}));
}
});
Router
initialize: function() {
Pops.Models.AuthUser = new Pops.Models.User;
this.navigation_bar = new Pops.Views.navigationView({
model: Pops.Models.AuthUser
});
},
On successful login the loggedInNavigation is visisble, and I am at app-client.dev/#dashboard however if I navigate to `app-client.dev/#groups I lose the loggedInNavigation and it reverts back to just saying "login".
Why?
=== Edit I ahve attached the method that creates the main nav it is done on the initialize of the routes. The method that does the view update is all the in the previous code, login sets the model that is int he navigation view, listening for a change ===

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.

RequireJS and Backbone ajax Issue

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.

Categories

Resources