How we structure JavaScript and CSS in Ruby on Rails

At Planet, we do a lot of work for startups at all stages of product lifecycle. When we jump into a project after it has already been created, we are often surprised at the lack of organization, flexibility, and modularity of Ruby on Rails projects.

You have the asset pipeline, treat it as your friend. It is okay to separate files.

Remember the old days of a single stylesheet for a project? That was ridiculously stupid. One file, a number of frontend devs, equals disaster. But in Rails projects we now have the asset pipeline to create different manifests or sets of stylesheets. I’ll go through stylesheets first and then tie everything into javascript architecture.

There are multiple types of stylesheets you need to consider.
1. Chrome and layout stylesheets. These include header, footer, logo, and other universal layout components.
2. Reusable patterns. Similar to bootstrap, your forms, lists, content boxes and more all go here.
3. Page-specific styles. Nearly every page has some special style and all those should be scoped to your page here.
4. Third party. Bootstrap, clearfix, modal, and other styles from projects that aren’t your own go here.
5. Browsers. I keep browser specific styles here. In other words, Internet Explorer.

Screenshot of stylesheets directory

Set page-specific IDs to the body tag to scope your pages

1. Create helpers in application_helper.rb

  def page_id
    if id = content_for(:body_id) and id.present?
      return id
    else
      base = controller.class.to_s.gsub("Controller", '').underscore.gsub("/", '_')
      return "#{base}-#{controller.action_name}"
    end
  end
  
  def page_class
    controller.class.to_s.gsub("Controller", '').underscore.gsub("/", '_')+" "+content_for(:page_class)
  end

2. Set page ID and class in application.html.erb

<body id="<%= page_id %>" class="<%= page_class %>">

3. Now the ID for a body will be controller-view. For example, on Recognize the homepage ID is “home-index”.

4. Structure your stylesheet page styles by controller and have each file be a view.
Screenshot of folder structure

If you have controller-wide styles then create a file called the controller. For instance home can be: home/home.sass, home/index.sass, home/tour.sass, etc.

Home.sass will look like…

.home
  p
    font-weight: 600

The home index stylesheet will look like…

#home-index
  p
   font-weight: 100

Now everything is scoped and isolated. If you change something in the home-index you are 100% sure it will only affect that page. The result is far lower bugs and more confidence for developers to create new features.

Some follow the paradigm if giving the view and controller class names on the body.

.home.index
 p
  font-weight: 100

The problem with this is will never do styles specific to index across all controllers so why would I want a class of index?

Regardless, the reason why I do #controller-view is for JavaScript, and it a good time to get into that.

Make your JavaScript also scoped by controller-view (the page).

Now that we have our page ID of controller-view, we use that for the key of a page-specific javascript object. The object ends up looking like this:

JavaScript page object

You can create another layer of architecture to not have all the pages loaded when not in use. But removing the pages object all together only lowered memory by 2mb, so insignificant.

Create page-specific JavaScripts and run it on page load or view change

To start the page-specific JavaScript, use the following snippet in a init.js file.

 var dataScript = document.body.getAttribute("id");
 window.R = window.R || {};

 if (R.pages && R.pages[dataScript]) {
  R.currentPage = new R.pages[dataScript]();
 }

If there is a page-specific JavaScript, it will run it, if not, then it won’t. Simple!

Here’s what a page-specific JavaScript file looks like.

window.R = window.R || {};
window.R.pages = window.R.pages || {};

window.R.pages["home-index"] = function() {
 alert("Dude you are so on the home index!");
};

By declaring a function as pages[“home-index”], it is only executed if you are actually on the home-index. Perfect!

But what about JavaScript that is used across all views in a controller? The solution is to create an abstract class, or a base class, for the controller and have all the views extend that class. (I know JavaScript doesn’t have classes, but this is the best way to describe it.)

window.R = window.R || {};
window.R.pages = window.R.pages || {};

window.R.pages["home-index"] = (function($, window, undefined) {

  var Index = function() {
    Index.superclass.constructor.apply(this, arguments);
  };
  
  R.utils.inherits(Index, R.pages.home);

  return Index;
})(jQuery, window);

Here I extend the R.pages.home for the index. I never directly instantiate window.R.pages.home, it is executed via its views – index.js.

A screenshot of the JavaScript folder.
Screenshot of page JavaScript

What about AMD?

The one thing that isn’t awesome about this is it isn’t using an AMD pattern. To use AMD (such as require.js) with the asset pipeline in Rails would need a system that loops over all the JavaScript view files and create manifests that can be compiled by Rails. Another approach is using an additional build with a frontend JavaScript minification and combining library.

Isolating assets lowers your chances of bugs

By keeping everything scoped and organized not only do future developers have an easier time getting started, but it also reduces bugs, which also helps future developers. One of the biggest fears of new devs is breaking existing functionality. By knowing what you are doing only affects one page or widget, then the dev can be confident they can innovate rather than regress.

What about JavaScript that is across controllers?

Just like for the stylesheets, create a patterns/ directory that contains all the reusable JavaScript functionality.

This architecture described above has been used by our team for a couple of years now and we are super happy with it and I hope you are too!

Increase website speed by lazy loading images

Website Speed
Increase your website’s speed by lazy loading background images. Only load images above the fold, and let the rest lazy load.

The problem: Too many images and website is designed responsively

By only loading the top images of your website, you can increase your website’s speed. This is especially true when you have a long page of graphics.

Most image lazy load solutions only work for <img> tags. But what if you want to lazy load images in a responsive designed website? In responsive design we use multiple images for the same graphic. We do this because different graphics are loaded for different screen widths, hence the responsive component.

The solution: Load background images when the user scrolls

Load below-the-fold images when the user scrolls to increase website speed optimization.

Lazy load background images by simply adding a CSS Class to the body tag after the user starts to scroll. That’s it! Now your site is optimized for speed by only loading the images the user sees on load.

Responsive lazy load library

I wrote a simple JavaScript library to handle this. It is called Zoink and you can find it on our github page.

The reason why this is so important is because normal websites load all the images on the site. Absolutely no reason to load images that aren’t seen by the user. I recommend showing the first two main images on your site, and load the rest of your CSS with Zoink.

Case study: Recognize

On Recognize’s homepage, we use a lot of large images. On page load on a desktop browser Recognize loads 400k of images. Most of the bulk is due to the first two images we load.

But when the user scrolls the page we load the reset of the images. A total of 858k of images are loaded. We are saving the user from waiting for all those images to load when they may not ever scroll to them.

Lazy loading background images improves performance on mobile, tablet, and desktop. Stop loading images your users may not ever see.

How to load JavaScript

When learning something new we sometimes don’t know where to start. For JavaScript, what is the step before hello world? How do load JavaScript in the first place?

How to load JavaScript with the <script> tag

<script type="text/javascript" src="path/to/AwesomeJavaScript.js"></script>

Break loading JavasScript down:

<script> is the tag for loading scripts. You can load different types of scripts.

Because of different types of scripts, in HTML4 and earlier it is required to specify the type. In the case of JavaScript it is “text/javascript”.

But as of HTML5 the type attribute is not required and can be omitted.

The src attribute specifies the file. The path can be relative or absolute.

Relative paths are file paths from the point of view of the file loading it. If the HTML file is in /files/ and the JavaScript is at the root /, then the src would be src=”../file.js” for relative.

The absolute path, using the previous example, would be src=”/file.js”.

How to load a JavaScript file dynamically in JavaScript

// Create the script element
var script = document.createElement("script");
// Add type attribute
script.setAttribute("type", "text/javascript");
// Add the file path to the source attribute
script.setAttribute("src", "/path/to/awesomeJavaScript.js");

// Now it is time to load the file and at this point you'll see the file loaded in your network tab in the web development toolbar
document.body.appendChild(script);