JavaScript and CSS (Cascading Style Sheets) have evolved over the years from being nice embellishments on a web page to critical aspects of a web application’s user interface. It should be no surprise that Rails, by convention over configuration, makes including modern JavaScript and CSS both easy to incorporate into your web application and flexible to modify for advanced use cases.
Rails 6 introduces the inclusion of the webpacker gem by default. The webpacker gem (with its default configuration) causes your javascript to be preprocessed and bundled with the popular JavaScript bundler webpack. Though webpack is capable of also handling CSS, images, fonts, and more, at this point Rails’ default configuration only uses webpack for JavaScript. To keep things simple, we’ll stick with only using webpack for JavaScript in this book.
For CSS, images, and fonts, Rails still uses the Asset Pipeline, a component of Rails which handles preprocessing and bundling.
Why do we need our JavaScript, CSS, and other assets to be preprocessed and bundled? What does that even mean? We’ll give a brief overview of some of the benefits before we apply our knowledge to the blog application we’re building.
If you need to get the code at the exact point where you finished Chapter 8, download the source code zip file from the book’s page on www.apress.com and extract it onto your computer.
Benefits of Preprocessing Assets
Why bother preprocessing and bundling your assets? We’ve been serving JavaScript, CSS, images, and more on our websites for years just fine, right? In the last several years, JavaScript and CSS have exploded in new features and capabilities and have quickly become integral parts of our web applications, whereas previously they might have just been a nice enhancement.
As our web applications now include more JavaScript and CSS than they used to, we must be concerned with how quickly our users can download our assets. Traditional approaches to optimizing the sizes of our assets required tedious work, or custom scripts.
Also, in recent years, JavaScript and CSS have spawned new languages—such as TypeScript and SASS—which seek to add features that make authoring JavaScript and CSS easier and more featureful. But browsers need JavaScript and CSS, not TypeScript or SASS. Wouldn’t it be nice to choose to author our JavaScript and CSS in the language we desire and have it converted automatically to what the browser needs?
In the next few sections, we’ll discuss some of these benefits of preprocessing assets in more detail.
Asset Concatenation and Compression
Applications that have a lot of JavaScript and CSS can have hundreds of individual .js and .css files. If a browser has to download all of these files, it causes a lot of overhead just starting and stopping the transfer of files. The Asset Pipeline concatenates both your JavaScript and CSS into files so that a browser only has to download one or two files instead of hundreds. It can also minify and compress the files. This removes things like comments, whitespace, and long variable names from the final output. The final product is functionally equivalent, but usually much smaller. Both of these features combine to make web applications load much faster and are transparent to the user.
Secondary Languages
Browsers have very strong support for both JavaScript and CSS, but if you want to use another language on the frontend or even if you use newer JavaScript features that aren’t available in slightly older browsers, you’d be out of luck. The browser would at best ignore it and at worse throw errors all over the screen. webpack and the Asset Pipeline allow you to use other languages that compile down to code that browsers understand. For example, webpack (with babel) allows you to write modern JavaScript—ES6—which is then transpiled (converted) into older JavaScript which more browsers can understand. The Asset Pipeline allows you to create your app’s styles in the SASS language and converts it into standard CSS which browsers understand.
Detailed description of ES6 and SASS is out of the scope of this book, but you should know what they are if you encounter them. For more information on ES6, visit https://developer.mozilla.org/en-US/docs/Web/JavaScript, and for more information on SASS, visit https://sass-lang.com/.
Asset Locations
Locations for Assets
Preprocessor | File Location | Description |
---|---|---|
Asset Pipeline | app/assets | This is for assets that are owned by the application. You can include images, style sheets, and JavaScript. |
Asset Pipeline | lib/assets | This location is for assets that are shared across applications but are owned by you. These assets don’t really fit into the scope of this specific application but are used by it. |
Asset Pipeline | vendor/assets | This location is for assets that are from an outside vendor, like JavaScript or CSS frameworks. |
webpack | app/javascript/packs/ | This location is where you create packs—JavaScript files that import other Javascript files, meant to be served as a bundle. By default, application.js is installed. You can add to it or create a separate pack when you want a substantially different group of JavaScript files (e.g., admin.js). |
webpack | app/javascript | This location is where you add smaller JavaScript files which will be imported by pack files, as described in the preceding text. |
In general, webpack and the Asset Pipeline stay out of the way, but they empower you to do impressive things with your assets with the default configuration and can be configured to do even more. For more information on the Asset Pipeline, visit https://guides.rubyonrails.org/asset_pipeline.html. For more information on Webpacker, see https://github.com/rails/webpacker.
Turbolinks
Since version 4, Rails has included the Turbolinks gem by default. This gem (and the accompanying JavaScript) aims to speed up your application by using Ajax to request pages instead of the more traditional page requests. It tracks files that are commonly shared across requests, like JavaScript and style sheets, and only reloads the information that changes. It attaches itself to links on your page instead of making those requests the traditional way. It makes an Ajax request and replaces the body tag of your document. Turbolinks also keeps track of the URL and manages the back and forward buttons. It’s designed to be transparent to both users and developers.
Turbolinks is turned on by default since Rails 4. It is included in the default JavaScript pack. If you needed to remove Turbolinks for some reason, you could do so, but we’ll leave it on for our blog application we’re building.
Rails link_to Helper with a No-Turbolinks Attribute Attached
Some JavaScript libraries aren’t compatible with Turbolinks. Listing these is out of the scope of this book, but you can find more information at https://github.com/turbolinks/turbolinks. If you continue to have problems, you can always disable Turbolinks.
Let’s Build Something!
We’ve talked about the features of Rails that support JavaScript and CSS, but let’s actually put JavaScript to work. We’ve added our style sheets in Chapter 8, but this chapter will focus on making our application use Ajax to load and submit forms.
Ajax and Rails
Ajax is a combination of technologies centered around the XMLHttpRequest object, a JavaScript API originally developed by Microsoft but now supported in all modern browsers. Of course, you could interface with the XMLHttpRequest API directly, but it wouldn’t be fun. A far better idea is to use one of several libraries that abstracts the low-level details and makes cross-browser support possible.
Rails makes Ajax easier for web developers to use. Toward that end, it implements a set of conventions that enable you to implement even the most advanced techniques with relative ease.
Most of the Ajax features you implement in Rails applications are coded using JavaScript; so familiarity with JavaScript code always helps and is pretty important for today’s web developers.
JavaScript and the DOM
The Document Object Model (DOM) provides a way to interact programmatically with a web page in your browser with JavaScript. Using the DOM, you can add, update, and remove elements from the web page without having to ask the server for a new page.
In the past, different browsers did not provide consistent APIs for interacting with the DOM. Developers were forced to write different JavaScript for different browsers. Eventually, JavaScript frameworks like jQuery emerged to simplify the process of writing code compatible with different browsers.
However, things have changed considerably. Different browsers now provide a more consistent interface (not perfectly consistent, but better!). So while tools like jQuery were considered essential in the not-so-distant past, developers no longer need such frameworks to achieve cross-browser compatibility.
Wikipedia defines DOM as follows: “The Document Object Model (DOM) is a cross-platform and language-independent convention for representing and interacting with objects in HTML, XHTML, and XML documents” (https://en.wikipedia.org/wiki/Document_Object_Model).
Working with the DOM is a deep subject; we’ll only scratch the surface in this book. But we’ll learn enough to add some nice touches to our application. See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model for more information.
Selecting Elements from the DOM
Function | Description |
---|---|
document.querySelector('#article_123') | Returns the element matching the given ID article_123. |
document.querySelectorAll('.comment') | Returns a list of elements with the class name comment. |
document.querySelectorAll('div.article') | Returns a list of div elements with the class name article. |
Table 9-2 used some of the most commonly used CSS selectors. For a complete list, see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors. Also, notice that when we expected a unique element (e.g., with a given id), we used querySelector, whereas when we expected any number of elements, we used querySelectorAll.
Moving to Practice
Now that you know what Ajax is, how it works, and how to select elements from the DOM, we can apply some of this knowledge to enhance the usability of our application. Mainly, one would use Ajax in their application when they think a snappier interaction is possible and recommended. Let’s begin Ajaxifying our blog application in the article page.
Not All Users Comment
If you look at the article page, you quickly notice that every time users read a post, they’re presented with a form for entering comments. Although reader participation is paramount, most users are only interested in reading the content. You can modify the article page to not load the comment form automatically; instead, it will load the form only after a user clicks the new comment link.
Loading a Template via Ajax
The Article Partial in app/views/articles/show.html.erb: https://gist.github.com/nicedawg/5e13311132f323e425be2488c7b2f5d4
The template hasn’t changed a lot: you no longer directly render the comment form, and you add a link called new comment. The new link still uses the well-known link_to helper to generate a link; however, you pass in the remote: true option, which tells Rails that you want the request triggered by this link to hit the server using Ajax.
There are a couple of things to note in the use of link_to in Listing 9-2. First, you send the request to a URL that already exists; the new_article_comment_path route identifies a path to a new comment. Second, you use the id: 'new_comment_link' option to give the rendered HTML element an ID that you can refer to later.
On the server side, you don’t need to make any changes to the comments controller. As currently implemented, you don’t explicitly implement a new action; the default behavior in this case is to render the new partial template in app/views/comments/_new.html.erb. But that file doesn’t exist, and that isn’t really what we want. We want to execute some JavaScript in this case—not just receive some HTML. Instead, we want a separate JavaScript template to be used as a response for this action.
Responding to Requests with JavaScript
The .js.erb New Comment Template in app/views/comments/new.js.erb: https://gist.github.com/nicedawg/944c9b741caa5437101687103e292a94
DOM Element Methods for Inserting HTML into a Page
Method | Description |
---|---|
insertAdjacentHTML(position, text) | Inserts the provided text adjacent to the current element, according to position, which can be ‘beforebegin’, ‘afterbegin’, ‘beforeend’, or ‘afterend’ |
insertAdjacentElement(position, element) | Inserts the provided element adjacent to the current element, according to the provided position, as in the preceding text |
insertAdjacentText(position, text) | Inserts the provided text adjacent to the current element, according to the provided position, as in the preceding text. This is recommended when you expect the content to be plain text. |
Going back to Listing 9-3, the last line hides the new_comment_element, which contains the link to add a new comment, by setting its style’s display attribute to “none.” Because you already have the comment form in your page, it makes little sense to keep that link around.
In a similar fashion, you can display a hidden element by setting its style.display attribute to “block,” “inline,” or other values, depending on the element’s intended usage.
Making a Grand Entrance
In the previous section, you added an element to the screen via Ajax—the comment form. It’s a pretty big form. It’s a very obvious inclusion on the page and your users won’t miss it; however, sometimes you may want to add just an extra link or highlight some text on a page. To help draw attention to the new content, let’s have it fade in.
The Updated New Comment Template in app/views/comments/new.js.erb: https://gist.github.com/nicedawg/1a94805ef8141b48b16a0b2faf8d659b
We added a few lines that could use some explanation. First, we select the form we just added and store it in the variable comment_form. Then, we immediately set the form’s opacity to 0, to make it completely transparent. Then, we use the setTimeout method to delay the execution of the next steps by 10 milliseconds. (Apparently, newly added content needs a few milliseconds before they’re ready to consistently work with CSS transitions.) Lastly, we tell the element that any future changes to its opacity attribute should gradually take place over 1 second, and then we set its opacity to 1—full visibility—and the element begins to fade in.
Arguably, there are better ways of making an element fade in. Perhaps we should have written some CSS rules to handle the transition in combination with JavaScript and made a more reusable solution for fading the element in. We also should make the JavaScript code we added more robust—but for now, this is the simplest way to get what we want, and that’s okay!
Open your browser at any article page and look at the shiny effect that is being applied.
You very likely want to learn more about all the various style properties you can change with CSS and JS. For more info, see https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style.
Using Ajax for Forms
Another user interaction improvement is to not refresh the page after a user adds a new record. In quite a few applications, users may be required to enter a considerable amount of data in forms; so this technique is important to grasp.
The Updated Comment Form in app/views/comments/_new.html.erb: https://gist.github.com/nicedawg/5dd35d1922270369b41f52815b57b224
The Updated Comments Controller in app/controllers/comments_controller.rb: https://gist.github.com/nicedawg/db9226972a7cbc652513d3e657b959b7
The main method in this code is the respond_to helper . By using respond_to, you can have some code in the format.html block that’s called when you receive a regular request and some code in the format.js block that’s called when a JavaScript request is received. Hang on! There is no code in format.js! When no code is added to a format block, Rails looks for a template named after the view, just like regular views, which means it looks for create.js.erb. When a submitted comment fails validation, you also want to warn the user by displaying error messages; for that, you use format.js { render :fail_create } to render a template named fail_create.js.erb.
The Template in app/views/comments/create.js.erb: https://gist.github.com/nicedawg/cae8f8be678155859b6730b144738599
The Template in app/views/comments/fail_create.js.erb: https://gist.github.com/nicedawg/0cb4848e5553d60b366d0ecd8f2a1d4d
In the create.js.erb template , you run a couple of JavaScript commands. First, you render the template for a new comment—using render @comment—and insert that HTML at the bottom of the comments div, similar to what we’ve done before. The document.querySelector("#main form").reset(); line is a simple call to reset all the elements of the new comment form, which is blank and ready to accept another comment from your user.
Give it a try: point your browser to an existing article, for example, http://localhost:3000/articles/2, and enter a few—or lots of—comments. As you can see, you can interact with the page in a much more efficient way: there’s no need to wait until a full page reload happens.
Deleting Records with Ajax
To complete the “making things snappy” section, you may want to delete some of the comments that are added by users. You can combine the techniques you’ve learned in this chapter to let users delete comments without delay.
The Template in app/views/comments/_comment.html.erb: https://gist.github.com/nicedawg/3710992814a2d9328dd86dd8cd081df5
The Comments Controller in app/controllers/comments_controller.rb: https://gist.github.com/nicedawg/0d6b6c3907175ed645cd6c1ebb2965c4
The app/views/comments/destroy.js.erb File: https://gist.github.com/nicedawg/b30f595ec078927ff93ab37f3bb94f14
In the preceding JavaScript, we select the comments container and then call removeChild, passing it the comment element we wish to delete. Removing an element seems a little complicated. Most modern browsers support simply calling .remove() on the element you want to remove, but IE doesn’t support that feature. We could have added a polyfill—a JavaScript library which adds specific features to browsers which don’t implement them—but that’s out of the scope of this book.
Open your browser to an article page—make sure you are logged in as the article owner—with some comments you want to delete or add lots of spam-like comments. See how quickly you can get rid of comments now? It’s a lot better than waiting for page reloads.
Summary
To be sure, JavaScript is a large topic. Entire books, conferences, and technology are devoted to the language, so it goes without saying that this chapter only scratches the surface. Still, in short order, you’ve learned the basics of implementing Ajax in Rails applications, and you know where to go when you need to dig deeper.
You learned how to make remote Ajax calls using the remote: true option for links and forms. You also used a simple visual effect to show new elements on the page, thanks to JavaScript’s ability to interact with the DOM.
Finally, you learned about using JavaScript templates—which have the .js.erb extension—to produce responses to Ajax requests using JavaScript code. At this stage, you have a solid grasp of the Action Pack side of web development with Rails.