Backbone JS – Facebook News Feed Example

update: check out https://github.com/serdary/backbone-facebook-newsfeed for source code.

Backbone.js is a javascript framework and I just started using backbone along with coffeescript for my new 2 projects.

In this tutorial, I’ll develop a simple backbone javascript application that fetches logged in facebook user’s news feed and lets the user share a new status message.
I used koala gem for facebook open graph.

You can use coffeescript instead of pure javascript, I’ll probably add coffeescript version of this simple app in part 2.

Backbone is a great framework if you will create a heavy-js app. It may have some differences than traditional MVC pattern, like views can be used as controllers. But if you know MVC pattern, it won’t take too much time to learn backbone.

I assume you know the basics of rails, so I will not add rails related stuff all the time.

In short, backbone has Model, Collection, Router and View.
Model is just like rails models.
Collection provides some extra functionality to model groups like sorting and filtering.
Router acts as rails controllers and routes definitions to map urls to actions.
Views = rails views. We can (should) use a templating library something like mustache, haml or jquery templates to separate html from javascript views.

I’ll structure js codes in assets/javascript/backbone, just like:

app
–assets
—-javascripts
——backbone
——–models

———-feed.js
——–routers
———-feeds_router.js
——–views
———-feeds

————new_view.js
————show_view.js
————index_view.js
——myfeed.js
——backbone-min.js
——underscore-min.js

p.s: if you’ll use coffeescript, I strongly recommend backbone-rails gem which creates this structure (and more) for you with a simple command.

After creating this folder structure, we need to add some require and require_tree directives to application.js and myfeed.js file.
You can see all the requires directives on github, now lets start the application with a rails controller and feed model.

Lets create first controller, the main entry point of app, home controller with a simple index action and make it homepage via routes.rb file.

Now, change the index.html.erb view under home controller to call our app’s init function.

<h1>
	<a href="#">My Facebook Feeds</a>
</h1>

<div id="notice"></div>
<div id="app"></div>

<script type="text/javascript">
    $(function() {
        App.init();
    });
</script>

Model

App.Models.Feed = Backbone.Model.extend({
	defaults: {
		from_name: null,
		from_id: null,
		message: null,
		feed_type: null,
		like_count: null 
	}
});
App.Collections.Feeds = Backbone.Collection.extend({
	model: App.Models.Feed,
	url: '/feeds'
});

I just defined the default values for the feed model properties and created a feeds collection which has a model property points Feed and a url points /feeds (we’ll create a feeds controller in a moment)

Router
The only router will be feed_router for this app. We’ll add the routes to call related actions.

App.Routers.FeedsRouter = Backbone.Router.extend({
	feeds: {},
	initialize: function() {
		// create a new object to hold feeds collection
		feeds = new App.Collections.Feeds();
	},
	routes: {
		''      : 'index',
		'/index': 'index',
		'/new'  : 'newFeed',
		'/:id'  : 'show'
	},
	index: function() {
		var view = new App.Views.IndexView(feeds);
		$('#app').html(view.render().el);
	},
	show: function(id) {
		console.log('index'+id);
	},
	newFeed: function() {
		console.log('newFeed');
	}
});

So after creating this router, if you wanna try your app, you may get “Backbone.history is undefined” error on your console. I have spent 15 mins to resolve that, the reason is you don’t have any routes on your controller. (I had routes but I mistyped as ‘routers’, so that resulted an error)

View

App.Views.IndexView = Backbone.View.extend({
	initialize: function() {
		this.feeds = this.options.feeds;
	},
	
	addAll: function() {
		if (this.feeds === undefined || this.feeds.length < 1) {
    		$(this.el).find("tbody").append('no feeds yet');
    		return;
		}
		// TODO: use haml-js or jquery templates
		var view = '';
		var template = '<tr><td>[from_name]</td><td>[message]</td><td>[feed_type]</td><td>[like_count]</td><td><a href="#/[id]">Show</td></tr>';
		for (var i = 0; i < this.feeds.models.length; i++) {
			var feed = this.feeds.models[i].attributes;
			view += template.replace('[from_name]', feed.from_name).replace('[message]', feed.message)
				.replace('[feed_type]', feed.feed_type).replace('[like_count]', feed.like_count).replace('[id]', feed.id);
		}
		
    		$(this.el).find("tbody").append(view);
	},
	
	render: function() {
		var table = '<h1>Feeds</h1><a href="#/new">New Feed</a><table><tr><th>From</th><th>Message</th><th>Type</th><th>Like count</th></tr></table>';
		$(this.el).html(table);
		this.addAll();
		return this; // chain
	}
});

At this point, you should get the index view with an empty table and a warning, as ‘no feeds yet’.
So it’s clear that we need feeds to try index page and show page. I could add facebook open graph related codes here but it may distract the backbone subject, so I’ll just create a feeds controller and a dummy json feeds response for now.

Feeds Controller

class FeedsController < ApplicationController
  def index
    #dummy
    feeds = [{ :id => 1, :from_name => 'dummy from1', :message => 'dummy message1', :feed_type => 'dummy feed_type1',
      :like_count => 'dummy like_count 1' }, 
      { :id => 2, :from_name => 'dummy from2', :message => 'dummy message2', :feed_type => 'dummy feed_type2',
      :like_count => 'dummy like_count 2' }]
      
    render :json => feeds
  end
  def create
  end
end

Now, we should have a working feed listing page. Let’s add a separate show page for items.
Show View

App.Views.ShowView = Backbone.View.extend({
	render: function() {
		var template = '<b>From name:</b><em>[from_name]</em><b>message:</b><em>[message]</em><a href="#">home</a>';
		
		var view = template.replace('[from_name]', this.model.attributes.from_name).replace('[message]', this.model.attributes.message);
		$(this.el).html(view);
		return this;
	}
});

Router – Show Action

App.Routers.FeedsRouter = Backbone.Router.extend({
...
	show: function(id) {
		var feed = feeds.get(id);
		var view = new App.Views.ShowView({ model: feed });
		
		$('#app').html(view.render().el);
	}
...
});

Now lets implement javascript part of the create action, then we need to start coding facebook open graph stuff to finalize the app.

New View

App.Views.NewView = Backbone.View.extend({
	initialize: function() {
		this.collection = this.options.collection;
		this.model = new this.collection.model();
	}, 
	events: {
		'submit #new-feed' : 'save'
	},
	render: function() {
		var template = '<h1>New feed</h1><form id="new-feed" name="feed"><div><b> message:</b><input type="text" name="message" id="message" /></div><div><input type="submit" value="Create Feed" /></div></form><a href="#">Home</a>';
		
		$(this.el).html(template);
		return this;
	},
	save: function() {
		this.collection.create({ message: $('#message').val() }, {
			success: function(feed) {
	  			this.model = feed;
	  			//window.location.hash = '#/' + this.model.id
	  			window.location.hash = '#'
  			},
  			error: function(feed) {
  			}
  		});
  		return false;
	}
});

Router – New Action

App.Routers.FeedsRouter = Backbone.Router.extend({
...
	newFeed: function() {
		var view = new App.Views.NewView({ collection: feeds });
		
		$('#app').html(view.render().el);
	}
...
});

Connect to Facebook Open Graph
Now we need to let users connect app via their facebook account and fetch their news feed, update their status.
To provide this, I’ll use awesome koala gem.

1. create a new facebook app and get app_id, app_secret.
2. add koala gem to Gemfile
gem “koala”

3. create a new rails controller with connect_fb and fb_callback actions
rails g controller sessions connect_fb fb_callback
4. check if the user is logged in. Do this for all the actions for all controllers, so add a before_filter to ApplicationController method to main controller.

class ApplicationController < ActionController::Base
  before_filter :authorize
  protect_from_forgery
  
  protected
  def authorize
    is_user_logged_in
    # TODO: redirect to root_url if not authorized
  end
  private
  def is_user_logged_in
    @user_logged_in = session[:fb_access_token] != nil
  end
end

5. Add a facebook login link to home/index view.

..
<% unless @user_logged_in %>
	<%= link_to 'Connect Facebook', sessions_connect_fb_path, { :class => 'fb_connect_btn' } %>
<% end %>
..

6. Add routes for session controller to routes.rb file

Then the only thing left is creating callback action and redirecting user to facebook with a callback url. So here it is:

class SessionsController < ApplicationController
  def connect_fb
    app_id = '' # Your app_id
    app_secret = '' # Your app_secret
    session[:oauth] = Koala::Facebook::OAuth.new(app_id, app_secret, sessions_fb_callback_url)
    
    redirect_to session[:oauth].url_for_oauth_code(:permissions => "publish_stream,read_stream")
  end
  def fb_callback
    if params[:code].blank?
      redirect_to root_url, :notice => 'You should allow app to connect your Facebook account'
      return
    end
    session[:fb_access_token] = session[:oauth].get_access_token(params[:code])
    redirect_to root_url, :notice => 'FB Connected!'
  end
end

I've added below code piece to application.js, it is a facebook connect hotfix. When facebook call-backs your app, it adds an hash and some chars, so we need to remove it to start our js app.
$(function() {
// fb connect redirect hotfix
if (window.location.hash === '#_=_') {
window.location.hash = '';
}
});

Now our app can connect to facebook, so we can actually fetch user's news feed and post user's status messages to facebook open graph API.

FeedsController - working with real facebook news feed data

class FeedsController < ApplicationController
  def index
    unless session[:fb_access_token]
      render :json => {}
      return
    end
    
    begin
      graph = Koala::Facebook::API.new(session[:fb_access_token])
      result = graph.get_connections('me', 'home')
      feeds = create_feed_objects(result)
    rescue Exception => ex
      session[:fb_access_token] = nil
      logger.error "Exception - #{ex}"
    end
    
    render :json => feeds
  end

  private
  def create_feed_objects(result)
    feeds = []
    result.each do |feed|
      from_name = feed['from'].is_a?(Hash) ? feed['from']['name'] : ''
      from_id = feed['from'].is_a?(Hash) ? feed['from']['id'] : ''
      like_count = feed['likes'].is_a?(Hash) ? feed['likes']['count'] : 0
      message = (feed['type'] == 'link' or feed['message'].blank?) ? feed['story'] : feed['message']
      
      feeds.push({ :id => feed['id'], :from_name => from_name, :from_id => from_id, :like_count => like_count, 
        :feed_type => feed['type'], :message => message })
    end
    feeds
  end
end

FeedsController - post status messages to facebook open graph

class FeedsController < ApplicationController
...
  def create
    return unless session[:fb_access_token]
    
    begin
      graph = Koala::Facebook::API.new(session[:fb_access_token])
      obj_id = graph.put_wall_post(params[:feed][:message])
      
      #feed = create_feed_objects(graph.get_object(obj_id))[0]
      feed = {}
    rescue Exception => ex
      session[:fb_access_token] = nil
      logger.error "Exception - #{ex}"
    end
    
    render :json => feed
  end
...

So with these latest changes, a simple backbone javascript mvc app is ready.
It displays the news feed of logged in user, post new status messages as logged in user, by using facebook open graph API.

koala gem is used for facebook graph calls and authentication, backbone framework is used to organize the js code.

Please check the source code of backbone news feed on github, and let me know your thoughts!

 

Tags: , , , , , , ,

Comments: 5

Leave a reply »

 
  • cool, is there any demo available for the same.

    Regards

     
     
     
    • sorry, no demo available.

       
  • Mark L

    Cloned the app and set my app_id and app_secret in app/controllers/sessions_controller.rb. When I click the facebook connect link I get the error:

    {
    “error”: {
    “message”: “Invalid redirect_uri: Given URL is not allowed by the Application configuration.”,
    “type”: “OAuthException”,
    “code”: 191
    }
    }

    Any ideas?

     
     
     
    • It seems like you haven’t configured your test facebook app urls. Can you check them and let me know?

       
  • naemi varna

    Hey there, You’ve done a fantastic job. I’ll certainly digg it and in my view suggest to my friends. I am confident they’ll be benefited from this site.

     
     
     
  • Leave a Reply
     
    Your gravatar
    Your Name