RSS

Basic Node.js and Backbone.js application II

15 abr

backbone logo

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.

Tweegle homepage

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:

Project files

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);

Although you may find more code in GitHub, the above code is all you need to run a server using Express.js. Previously, to do anything you should import the required libraries. In the code we see that we are going to need express and sys. In fact, the sys library isn’t required in the code because nothing is printed to the console.
One thing you must do when you need a specific library in your local project is download it before. With Node.js we have a Node.js package manager so called npm. Npm is quite similar to other package manager such as rvm for Ruby environment or Maven for the JEE environment. You can find how download npm clicking the links. After that, if you wish to install a module in your local environment you need to type in your console:

npm [-g to install global] install node_module
After that we simply create a server from express and define a method get that will handle with the requests that are sent to our root URL. This method will reply with a HTML5 page called index. As long as we are going to  use Jade, we need to add a couple of line more to our code that set Jade as our template engine. Besides that we need to specify from where our Jade files are sent.

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’

Tweegle

You could be interested in reading the other posts:

 
3 comentarios

Publicado por en 15 abril, 2012 en web

 

Etiquetas: , , , , , , , , , , , , ,

3 Respuestas a “Basic Node.js and Backbone.js application II

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

 
Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 69 seguidores

%d personas les gusta esto: