Friday, October 11, 2013

Bookmarkable ExtJS application with BackboneJS


The JavaScript world has a lot of good frameworks. BackboneJS and ExtJS are examples of it. They are well known players in this field. Both are elegant and fun to work with, but only BackboneJS is open source and free. ExtJS gives nice widgets that developers can use as simple components out of the box. Both have their own pros and cons. This post isn't about which one is the ultimate JavaScript framework. This post only tries to explain how to use features of both in the same project. It shows how these two guys can play nicely together. As gift, it also shows another example of JavaEE application with groovy powered by Apache TomEE.

Browser bookmarks are useful. Users expect to save the state of a web application by simply copying the application URL. Unfortunately, this feature is not easy to implement on Single Page Applications. In this kind of application, we don't have real page transitions. Due to the fact that the HTML is built by the JavaScript framework directly on the browser, the framework needs to access the browser address bar and figure out how to represent that URL in HTML code. It needs to match what is defined in the address bar and the actual state of the application.

ExtJS provides the Ext.History object. It gives the ability of using hash based history. It means that the address bar will have a hash value (everything after the “#” character) that is not handled by the server. BackboneJS also supports hash fragments, but it can use the Html5 history if available. It means that BackboneJS can use cleaner URL addresses. Our application will be an ExtJS MVC application that uses BackboneJS to handle the URL in the address bar.

Lets take a look in our application. It's based on the Feed Viewer example available on the official ExtJS examples pageThe welcome page shows an empty page with two buttons: add and remove.


Click on "Add Feed" and add a new RSS feed. There are some feed links available at the yahoo developer page (http://developer.yahoo.com/rss/).



Now select the new feed. 


Did you notice that the URL is updated as soon as you select the feed? Now double-click one of the RSS items.  


The URL was again updated. Copy the address of this page and paste it in another browser, or simply hit F5.


What did just happen?

Our server returns the "index.jsp" as result to any request. It means that if you try to access something like http://localhost:8080/rssreader/i-dont-know-what-to-do the server will return the "index.jsp" page on its initial state.


After the initial page load, the framework takes over and checks what is in the URL string and executes the function mapped to that particular URL. This is the definition of our current Router:

 var Router = Backbone.Router.extend({  
         routes: {  
           '': 'showChannel',  
           '/': 'showChannel',  
           'channel/:channel/': 'showChannel',  
           'channel/:channel': 'showChannel',  
           'channel/:channel/:item': 'showChannel',  
           'channel/:channel/:item/': 'showChannel'  
         },  
         showChannel: function (channel, item) {  
           var param = {  
             channel: channel,  
             channelItem: item  
           };  
           me.fireEvent('show-channel', param);  
         }  
 });  

Yeah. It is ridiculously simple. The router maps URLs to functions. In the case above, all the URLs point to the same "showChannel" function. ":channel" and ":item" are path parameters. In case we have an URL like http://192.168.1.126:8080/rssreader/channel/365/647, the selected channel ID is "365" and the selected item ID is "647".

How to map all the requests to "index.jsp"? We need to change the "web.xml". We want the server to properly map our JavaScript, Images and CSS resources; but everything else should return "index.jsp".

First map "/*" to "index.jsp"...

  <servlet>  
   <servlet-name>index</servlet-name>  
   <jsp-file>/index.jsp</jsp-file>  
  </servlet>  
  <servlet-mapping>  
   <servlet-name>index</servlet-name>  
   <url-pattern>/*</url-pattern>  
  </servlet-mapping>  

Now every request points to "index.jsp". It is time to map your static resources to the "default" servlet...

  <servlet-mapping>  
   <servlet-name>default</servlet-name>  
   <url-pattern>/app/*</url-pattern>  
  </servlet-mapping>  

Note that our static resources are all in "/app/". When the server sees the rule above, it will return a resource instead of  the "index.jsp" file. For example, http://192.168.1.126:8080/rssreader/app/js/app.js gives the source code of the "app.js" file.


As usual, this sample implements a JavaEE application that runs on Apache TomEE. The source code is available on github.

ExtJS Histoty object -> http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.util.History
BackboneJS Router -> http://backbonejs.org/#Router (-> Read it. It shows more examples of route strings.)
Sample source code -> https://github.com/tveronezi/rssreader
Apache TomEE -> http://tomee.apache.org/

Please mind the ExtJS licensing rules. For short: