The Foundation
The foundation of a good
porcelain Ajax framework is component refresh, i.e., refreshing an area of the page with new/updated information. Component refresh is equivalent to reloading the page, but provides a better user experience since it happens asynchronously, and reduces the amount of work the server has to do. Because we are requesting a block of HTML from the server, we don't have to write any additional JS to update the page.
jQuery.load does all the heavy lifting. With component refresh it becomes trivial to Ajax enable pagination, sorting, filtering, lazy loading, tabs and many other actions for a better user experience and better performance. In this post, we'll learn how to create a porcelain Ajax framework with component refresh using Grails.
The App
Porcelain frameworks are usually built around an application. For our purposes, lets say we have a simple app for creating, listing, viewing and editing products. The products have a name, price, and description. Additionally, each product can be viewed in four different colors. In our app, the only difference between colors will be the image that is displayed for the product, but the different colors could easily be different tabs of product information, or different configurations of the product.
All our work will be on the product list, which will look like this:
|
our made up products list |
The Project
We start with a standard Grails project and add a Product domain class and generate the controller and views. How to do all that with Grails is out of scope for this discussion, but it's pretty easy.
By default, Grails lists products in a table. Typically products are listed as "cards" to give them more room, so we'll enhance the markup slightly:
At this point we haven't written a line of Ajax code, we're just creating a simple app that will work even with JS disabled. In anticipation of adding Ajax functionality though, we should break the list page up into the components that we plan to refresh. To do this, we'll make use of the
g:include tag, which allows us to inline another controller action in our page. We'll create two new controller actions and adjust our templates accordingly:
Notice the original list code has moved into renderList and we return productIds instead of the actual product object.
And we replace the list in list.gsp with g:include controller="product" action="renderList". renderList.gsp is just:
and renderProduct.gsp is the product div above.
For the colors pages, we're going to reuse renderProduct:
and that's it. We now have all our functionality setup and it all works even with JavaScript disabled. Time for some progressive enhancement.
The Framework
The core of our component refresh implementation is going to be
g:include. Since g:include is plumbing, we need to ensure that our porcelain covers it very thinly to make future extension easy. One option would be to use Groovy meta-programming magic to intercept calls to the built in Grails taglib where g:include is. This has the advantage of being transparent to the user. We don't have to worry about using the right tag, and if we already have a lot of existing code using g:include, this would be the way to go. However, it has the disadvantage of being complicated. If we're starting from scratch, it's much simpler to define our own tag. So enter the FrameworkTaglib:
Instead of g:include, we will use ajax:include. The new tag does two thing. First, it wraps the content of the component in a div with the class "ajax-component" so that out framework JavaScript will know that it's a component. Secondly, it sets the id of that div to the controller action and optional id for the include. That will allow us to call the correct action from our Ajax requests.
The second part of our framework is framework.js:
refresh
is also a very thin wrapper, this time around
jQuery.load
. It has the nice feature of allowing us to use any element inside a component as a representative of the component. Also, we don't have to pass a URL, and can pass a data string or object, just like
jQuery.load
.
With these two components, our framework is ready! Our first task will be to Ajax enable the color links so we can view the different colors without leaving the list:
With our framework code in place, it couldn't be simpler. We strip the query string off of the link and pass it as data to refresh and we're done. The color links now refresh the individual products in place, for a greatly improved user experience. Also, notice we use
jQuery.delegate so that when the component is refreshed we don't have to reattach event handlers.
So that was easy, but what about pagination? Surely it will be a bit more work to Ajax enable pagination.
Or maybe it's the exact same code. It looks like progressively enhancing links in this way might be something to add to framework.js. Notice that we can refresh the entire list as a component, but also have nested components that can be refreshed individually.
In very little code, we've built a framework that has allowed us to progressively Ajax enhance both pagination and content toggle in about seven lines of JS. But lets take an objective look at what this framework does well and what might be problematic.
You can download the complete project to this point
from GitHub.
The Good
There is a lot to like about the framework as it stands.
- Very simple usage.
- We take advantage of progressive enhancement. Normal links become refreshes very easily.
- The whole framework is less than 50 lines of code at this point.
- Components can be nested inside other components.
The Bad
Using this code as is on a serious project is probably not the best idea for a few reasons:
- All components are wrapped as divs. That means refreshing something like a table row is impossible.
- In framework.js we assume that the default URL mapping of "/$controller/$action?/$id?" is used, which may not be the case, in which case refresh will fail.
- We have to pass all the parameters to refresh. For just progressively enhancing links, this isn't such a big deal since the link should have all the parameters, but as we get into more advanced features, it will become a pain point.
- Several features of g:include will not work. In particular, the model and params attributes will not be present in a refresh. For simple cases, that's not a problem, but a seriously application will quickly feel the pain of such limitations.
- Two nitpicks
- We use the namespace "ajax" for the taglib, which is not likely to be unique. Another poorly written plugin could cause problems.
- We use the id attribute to store the controller action information. A class might be more appropriate, if a little harder to parse.
The Conclusion
The core idea of a good component refresh is being able to pair together the view (GSP) and the code that generates the model (the controller action). This pairing allows us to treat each component as it's own entity, making refresh and other advanced features possible. Grails and jQuery provided us with some very nice pipes to start with, so creating the component refresh porcelain was perhaps easier than it would normally be, but I think the simplicity helps the underlying concepts to show more easily.
Next time we will build multi-component refresh and try to fix some of the problems with our framework. It's not good from much beyond basic enhancement at this point, but we're off to a good start.
Remember, you can download the complete project to this point
from GitHub.