In the previous post I defined the frameworks and libraries that I’m going to use in order to build a simple web application with Node.js and Backbone.js. This application will have a text field where we’ll introduce a word and we get back the lastest tweets regarding with that word. Besides that, it’ll show the places in the world where that tweets were sent. But first, take a look at application’s home page.
Show me the code!
First, you can find the code in my GitHub account. So if you want to clone it and make your own changes, you are free to do it.
Second, we are going to show how it is structured the code. It’s not the better way to define a application with Backbone.js but on the other hand it will help us to understand the basics of Node.js and Backbone.js. You can the files of this project in the following screen caption:
To explain the project we are going to enumerate the required steps in order to develop our application. We can summarize these steps as follows:
- Create a server.
- Reply to a request to a specified URL with a certain page where our javascript code with Backbone.js will be sent.
- Every event that may occur in the client (the web browser) will be handled by our Backbone.js code.
Create a server.
As we said previously, we are going to use Express.js to do that. Express.js is a web framework based on Sinatra that allows us to create a web server easily.
var express = require('express'), sys = require("sys"); var app = express.createServer(); app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.get('/', function(req,res){ res.render('index'); }); var port = (process.env.PORT || 3000); app.listen(port,null);
npm [-g to install global] install node_module
Reply to the client.
We have seen that the index file will be sent to the client (web browser). The index.jade file looks like that.
append head script(src="/js/App.js") link(rel='stylesheet/less', href='/stylesheets/styles.less') link(rel='stylesheet', href='/stylesheets/jquery-ui-1.8.18.custom.css') link(rel='stylesheet', href='http://code.leafletjs.com/leaflet-0.3.1/leaflet.css') script(src="/js/less-1.3.0.min.js") script(src="/js/jquery-ui-1.8.18.custom.min.js") script(src="http://code.leafletjs.com/leaflet-0.3.1/leaflet.js") script(src="http://tile.cloudmade.com/wml/latest/web-maps-lite.js") body.radial-gradient h1.shadow Tweegle div.center input(type="text", placeholder="Please, write a word")#search button#add.ui-button.ui-widget.ui-state-default.ui-corner-all.ui-button-text-only span.ui-button-text Search tweets div#spinner img(src='../images/ajax-loader.gif') ul#tweets #map
This is a HTML5 that uses some all the libraries we have explained before. Syntactically it’s quite similar to a HTML5 standard page. The good thing about it, it’s that it will not be rendered in the server-side but in the client-server so that you separate the server-side and the client-side. You can use your javascript to define the values that will populate this file. Besides that we may use a CSS preprocessor to define our styles. To do that we use Less. Our CSS definition with Less is as follows:
@blue_twitter: #C0DEED; @deepblue_twitter: #8EC1DA; @red: #F00000; @blue: #00000F; @pastel_yellow: #F7E967; @pastel_green: #A9CF54; .border-radius(@values) { -webkit-border-radius: @values; -moz-border-radius: @values; border-radius: @values; } .border(@borderWidth: 2px) { border: @borderWidth solid #F00; padding: 3+4px; } body { width: 960px; height: auto; margin: 0 auto; } li { list-style-type: none; font-color: mixin(@red, @blue); height: 50px; } input { .border-radius(10px); color: #ccc; font-size: 1.5em; width: 300px; height: 40px; padding: 3px; } #overlay { position: absolute; width: 100% ; height: 100px; top: 0; left: 0; color: #fff; background-color: #000; display: none; text-align: center; } #overlay a { font-size: 130% ; font-weight: bold; } .shadow { font-size: 3em; text-align: center; color: #666; text-shadow: 1px 1px 5px rgba(0,0,0,0.8); } .radial-gradient { background-image: -webkit-gradient(radial,center center, 1, center center, 900, from(@blue_twitter), to(@deepblue_twitter)); } .ui-progressbar-value { background-image: url(../images/ajax-loader.gif); } .center { width: 960px; margin: 0 auto; text-align: center; } .tweeter_photo { float: left; padding: 1px; } #map { height: 300px; width: 500px; margin: 0 auto; }
The good thing about a CSS preprocessor it that you can add some salt to your CSS. You might want to define CSS variables, create functions or use mixins within your CSS files.
If we notice taking a look at our index.jade file there isn’t any link to Backbone.js, Underscore or JQuery libraries. So how it must suppose we use those libraries. Well, our index.jade inherits from a custom layout through the append head instruction. Our custom layout code defines a simple HTML5 page and import some libraries to it:
!!! 5 html head script(src='https://ajax.googleapis.com/../jquery/1.7.1/jquery.min.js') script(src='http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js') script(src='http://documentcloud.github.com/underscore/underscore.js') script(src='http://documentcloud.github.com/backbone/backbone.js') title My example page body block content #container!= body
Our Backbone code.
Our Backbone.js code is saved in the static/js folder within the App.js file. As I said previously the Backbone.js code isn’t the best Backbone.js code you might find out there, but you may find suitable to understand how Backbone.js works somehow.
$(function() { var Tweet = Backbone.Model.extend(); var Tweets = Backbone.Collection.extend({ model: Tweet, url: 'http://search.twitter.com/search.json?q=Node.js&rpp=8&callback=?', parse: function(response) { //console.log('parsing tweets...') console.log(response.results); return response.results; } }); var _Location = Backbone.Collection.extend({ model: Loc, url: 'http://where.yahooapis.com/v1/places.q("default_string")?format=json&appid=[yahoo_api]', parse: function(response) { //console.log('parsing location ...') return response.places.place; } }); var User = Backbone.Collection.extend({ model: User, url: 'https://api.twitter.com/1/users/show.json?user_id=007&callback=?', parse: function(response) { //console.log('parsing user ...'); return response; } }); var PageView = Backbone.View.extend({ el: $('body'), events: { 'click button#add': 'doSearch' }, initialize: function() { _.bindAll(this, 'render', 'addItem'); this.tweets = new Tweets(); _this = this; this.tweets.bind('reset', function(collection) { _this.$('#tweets').empty(); collection.each(function(tweet) { console.log(tweet); _this.addItem(tweet); }); $('#spinner').hide(); }); this.map = new L.Map('map'); var cloudmade = new L.TileLayer('http://{s}.tile.cloudmade.com/[cloudmade _api]/997/256/{z}/{x}/{y}.png'); var center = new L.LatLng(25,0); this.map.setView(center, 1).addLayer(cloudmade); this.render(); }, doSearch: function() { $('#spinner').show(); var subject = $('#search').val() || 'Node.js'; this.tweets.url = 'http://search.twitter.com/search.json?q=' + subject + '&rpp=8&callback=?'; this.tweets.fetch(); }, render: function() { $('#spinner').hide(); return this; }, addItem: function(item) { _this = this; var user_id = item.get("from_user_id"); this.user = new User(); this.user.url = 'https://api.twitter.com/1/users/show.json?user_id=' + u ser_id + '&callback=?' this.user.fetch(); this.user.bind('reset',function(collection){ collection.each(function(value) { var location = value.get("location"); this.loc = new _Location(); this.loc.url = 'http://where.yahooapis.com/v1/places.q('+ location + ')?format=json&appid=[yahoo_api]', this.loc.fetch(); this.loc.bind('reset', function(collection) { collection.each(function(value) { var centroid = value.get("centroid") var circleLocation = new L.LatLng(centroid.latitude, centroid.long itude), circleOptions = { color: 'red', fillColor: '#f03', fillOpacity: 0.2 }; var circle = new L.Circle(circleLocation, 1500, circleOptions); _this.map.addLayer(circle); }); }); }); }); var img = "<img class='tweeter_photo' src='" + item.get('profile_image_url') + "' />" $('ul', this.el).append("<li>" + img + "<b>" + item.get('from_user_name') + "</b>:<br /> " + item.get('text') + "</li>"); } }); var pageView = new PageView(); });
In our code we have:
- A Tweet Model. This model defines a Tweet class. If you notice, this model isn’t define any property as name, photo or location for it. We can do that overriding the defaults property.
- Tweets Collection. It defines a collection of tweets. To that we must define its model as a tweet model. Besides that we are going to indicate the url where we are getting our tweets. In our case is a REST services provided by the Twitter API. There is a parse function that return the Tweets collections.
- Location Collection. It defines a collection of locations. It will call the Yahoo Geolocation API to get the information it needs. Although I’ve created this as a collection it should be more correct a simple model because we only need a location.
- User Collection. It defines a collection of users. As Tweets Collection does, it’ll call the Twitter API for it. It returns us a collection of User information from Twitter. In this code we only need a Twitter user info. Unfortunately a client can’t send more than 150 request per hour. So, it might be that no information was added to the map and you see any point in it. 😦
- A PageView view. Our view doesn’t (or shouldn’t) know anything about the structure of our page, that’s something that will be define by our Jade engine. Instead of that it will get the information from the RESTservices and it will sent that information to our engine template. Our PageView has defined 6 properties:
- el. It links this property with a DOM element. If it’s not defined it’ll take a div element by default. In our case we link the el element to the body element within our HTML5 page.
- events. It defines how it will react our view when a specific event is triggered and the method that handles with that event. We define that when we click our Search button a doSearch method will be triggered.
- initialize. Here we can define and bind events to those methods that will handle them. In our case we bind our tweets collection to a reset event. The reset event will be triggered after we call to the fetch method. Besides that we create a map and call the render function. The render method will create the application’s home page we saw at the beginning of this post.
- doSearch. The doSearch property will fetch a collection of Tweets. This fetch will trigger a reset event that will be intercepted by the method defined in the initialize property. For each tweet we call the addItem property to add it to our page.
- render. It will render the page.
- addItem. It will add the tweet information to our page. At the same time it will call the Yahoo Geoplanet API and Twitter API to get more information about the location and the user who wrote that tweet. Besides that it will add a point in the View’s map for each tweet.
Try it.
You can donwload the code and try it launching the code with:
node app.js
You can visit this page and give a try. As default it searches for ‘Node.js’
You could be interested in reading the other posts:
- Basic Node.js and Backbone.js application I
- Basic Node.js and Backbone.js application II
- Basic Node.js and Backbone.js application III. Cloud deployment to Cloud Foundry, Heroku and AWS
- Basic Node.js and Backbone.js application IV. Redis and Socket.io support
Hansie
7 febrero, 2014 at 1:49 pm
Hi
Very nice tutorials.
Unfortunately i am getting errors as in the demo link itself:
1. Uncaught ReferenceError: io is not defined in my chrome browser
2. {“errors”: [{“message”: “The Twitter REST API v1 is no longer active. Please migrate to API v1.1. https://dev.twitter.com/docs/api/1.1/overview.”, “code”: 68}]}
Can you please assist me with this
Thanks in Advance
hop2croft
16 enero, 2016 at 7:38 pm
Has been almost a couple of years, but those errors seem related to deprecated dependencies in the project/api