As Ruby developers, most of us spend our time building websites, and whether we like it or not we have to write and maintain a lot of
HTML, CSS and JavaScript.
Rails tells us a lot about how to structure our back-end code, but very little about how to structure our front-end code. Beyond providing a few simple helper methods it's more or less silent on the subject.
Just because Rails doesn't make these decisions for us doesn't mean they don't need to be made.
JavaScript & Rails
Hooray for Rails 3!
JavaScript helpers are now unobtrusive and framework agnostic.
Unobtrusive
JavaScript lives in .js files, not in .html files and the application still works if the .js files aren't loaded.
Framework agnostic
We don't have to use Prototype, but can replace it with the best framework for this particular team or project.
The link has a real href, so it will work without JavaScript.
The Ruby code we write to generate the link is much closer to the way we would create a non-JavaScript link (helps us think in the right way).
All the JavaScript is contained in rails.js and can be easily tweaked or replaced entirely without monkey patching Rails helpers.
There's no mention of anything specific to Prototype, jQuery or any other JavaScript framework.
Be careful though, you can still create links that make a POST request and only work with JavaScript.
Eventually, the helpers aren't enough
Although the helpers are now very good, they still only provide very basic functionality.
As an app grows in size and complexity we are likely to want more JavaScript functionality than the Rails helpers can provide.
Usually we would create a new file in public/javascripts and just start hacking, but there are better ways.
If we write all our JavaScript as methods on the window object, we'll end up in a mess.
Using objects to group related code and immediately called functions to create a private variable scope helps avoid conflicts.
Initialising the right scripts
Lots of JavaScript files means lots of HTTP requests, which is slow.
Running lots of unneeded JavaScript setup on every page can also be slow, e.g. why initialise inline form validation on a page with no forms?
It is good to serve all of the JavaScript as a single file to reduce HTTP requests and benefit from caching, but be selective about which scripts we run.
This doesn't mean we should write the JavaScript in one big file (more on this later).
Ask the HTML what it needs
<body class='with-js-image-gallery'>
Because the purpose of the JavaScript is to enhance an HTML document, it makes sense to ask the document what it has that needs enhancing.
Class names on the body keeps everything in one place.
You could also use HTML5 data- attributes to be consistent with Rails 3.
We have to write defensive init functions so we don't enhance the same bit of markup twice and potentially break our pages, e.g. if the JavaScript adds some markup to the page we don't want it to be added twice.
Adding a class to enhanced markup also gives us a convenient way of targeting CSS rules which are only appropriate when the JavaScript enhancements have been applied (it's good to keep style rules out of JavaScript, in the same way that it's good to keep JavaScript and CSS out of HTML).
Note that if you always use the same class name (e.g. enhanced) you can't initialise two features on the same element (thanks to the person who pointed this out in the Q&A at RubyConfUA).
Rails to the rescue
We've talked about adding classes to the body, and setting custom HTTP headers.
Why not roll all of this into some handy Rails helpers?
Requesting JavaScript
def use_javascript_for(feature)
@js_features ||= []
@js_features << feature.to_s.downcase if request and request.xhr?
header = @js_features.uniq.join(' ')
response.headers['X-With-JavaScript'] = header
end
end
Calling use_javascript_for in our views allows bits of HTML to request specific JavaScript enhancment.
Generating the HTTP headers and body classes in one place means we can load render a partial as part of a view or load it via XHR and the JavaScript will just work.
Small, carefully named files are easier to maintain.
Make use of CSS's specificity rules: you can override what went before by being more specific.
Serving front-end code from Rails
Breaking down our front-end code into small, manageable files is a good thing, but making lots of HTTP requests is inefficient.
Rails provides some basic functionality, e.g. javascript_include_tag and stylesheet_link_tag. Both of these can combine files via :cache => true.
What if we want to do more than just combine our CSS and JavaScript files?
The future's bright
public is for designers, those messy kids, app is for the high-level programmer stuff that
we care about. That's not really a good way of going about it … and we've been forgetting about stylesheets and
JavaScript and ways we can optimise that for a long time. Let's not do that anymore.
We can do additional processing on the assets, e.g. minification.
We can use ERB and helper methods to generate some of the CSS or JavaScript, e.g. for CSS sprites, as mentioned in DHH's RubyConf presentation.
The front end code can easily access server-side configuration, like the routes or the current Rails environment.
ERB in JavaScript
jQuery('<a></a>').text('More');
could be rewritten as
jQuery('<a></a>').text(<%= I18n.t('more').to_json %>);
We can pass data from our Rails controllers directly into our JavaScript or CSS.
When adding data to JavaScript we can use Rails' to_json method to handle quoting, escaping and object literals, because JSON is a subset of JavaScript.
The future is now!
Rails 3.1 may not be out yet, but a lot of the tools we need to manage are JavaScript and CSS better already exist.
Controllers, routes, helpers, ERB and to_json are all available now in Rails 2 and 3.0.
There are already some gems and plugins to help make this stuff easier.