Posted over 3 years ago. Visible to the public.

Unobtrusive JavaScript, AJAX, Reusable components


  • Learn about "AJAX" and how it improved classical web development:
    • Without AJAX we could only replace entire screens. Web applications were a series of screens, like a slide show.
    • With AJAX we can replace fragments without swapping out the entire screen.
    • By only updating fragments we keep transient state like scroll positions, unsaved form values, DOM that was changed by JavaScripts.
  • Learn about "Unobtrusive JavaScript" and "Progressive Enhancement". These two terms are often used interchangeably. What are some advantages of these techniques? How does it contrast with more modern techniques like Vue.js or React?
  • What is the $.unobtrusive helper you find in many of our projects? How does it differ from jQuery's $(function() { ... })?


Understanding $.unobtrusive

Note the difference between

$.unobtrusive(function() { $('.spoilers').each(hideSpoilers); });


$.unobtrusive(function() { $(this).find('.spoilers').each(hideSpoilers); });

Which one is correct? Why?


  • Add a "plot summary" to your Movie records.
  • Plot summaries can be spoilers. Write some JavaScript for your movie detail screen that hides the summary and shows a "Show spoilers" link. When clicked the full content is shown.
  • Implement this using unobtrusive JavaScript. In your HTML, only add a class spoiler to the <div> containing the plot summary, the rest should be done all on the client.
  • Now add a new field "Secret salary" to Actor records. Can you use the same "spoiler" JavaScript to hide the salary on the actor's show view?


Implement a find-as-you-type search box over the movies list in MovieDB. The list of movies should update while the user is typing in the search query.

Note how this time, you have multiple elements that interact with each other:

  • The <input> for the search query
  • The list of search results that needs to be updated

Now add a similar find-as-you-type search box over the actors list in MovieDB. Can you find a way to re-use the same JavaScript for both lists? For this your JavaScript must be abstract enough to not contain screen-specific references (such as selectors or URLs).

Dual-pane layout

Step 1: Search component without side effects

Change the movie index so the screen is divided into two vertical panes. Each pane should show a full list of movies and a find-as-you-type box to filter the list by query:

+-----------------------------------------+ | MOVIEDB | +--------------------+--------------------+ | | | | MOVIES | MOVIES | | [ Search… ] | [ Search… ] | | | | | - Foo movie | - Foo movie | | - Bar movie | - Bar movie | | - Baz movie | - Baz movie | | | | +--------------------+--------------------+

The search box above each pane should only filter the pane below. Actions in one pane should never affect the other pane. So this state should be possible:

+-----------------------------------------+ | MOVIEDB | +--------------------+--------------------+ | | | | MOVIES | MOVIES | | [ Foo ] | [ Ba ] | | | | | - Foo movie | - Bar movie | | | - Baz movie | | | | | | | +--------------------+--------------------+

Does your existing JavaScript already work that way?

If a search in one pane accidentially affects the other pane, you might need to find a more elegant way how the participating elements. Try this structure in your HTML:

<form class="searchable" action="/movies"> <input class="searchable--input"> <div class="searchable--results"> </form>

Use it with this

$.onubtrusive(function() { $(this).find('.searchable').each(function() { let $searchable = $(this) let $input = $searchable.find('.searchable--input') let $results = $searchable.find('.searchable--results') $input.on('input', function() { // react to user typing }); }); });

Note how now all the changes made by your activation callback only affects a single .searchable and its descendant elements. No .searchable affects another .searchable.

Step 2: AJAX navigation

Add some JavaScript so that when I click on a movie from the list of movies, the show view is fetched via AJAX and replaces the list in its pane. The other pane will be unaffected. So this state should be possible:

+-----------------------------------------+ | MOVIEDB | +--------------------+--------------------+ | | | | FOO MOVIE (2007) | MOVIES | | | [ Bar ] | | Sci-Fi thriller by | | | Danny Boyle, | - Bar movie | | adapted from a | - Baz movie | | screenplay by ... | | | | | +--------------------+--------------------+

Hint: Change the behavior of all links (<a> elements with a class pane-link).

Does your "Show spoilers" JavaScript still work? If not, fix it using $.unobtrusive.

Now use the same pane view for your actors index. The same JavaScript should serve all views that involve panes.

Convert existing JavaScripts

  • Your project probably has some JavaScript snippets from earlier lessons, such as the movies counter.
  • Refactor every piece of JavaScript so it activates selectors with the $.unobtrusive helper.
  • Also refactor your JavaScript so you have a single file per component. Each file should be named after the selector it activates:
// in /app/assets/javascripts/components/foo.js $.onubtrusive(function() { $(this).find('.foo').each(function() { let $foo = $(this); // activate JavaScript on $foo }); }); // in /app/assets/javascripts/components/bar.js $.onubtrusive(function() { $(this).find('.bar').each(function() { let $bar = $(this); // activate JavaScript on $bar }); }); // in /app/assets/javascripts/components/foo.js $.onubtrusive(function() { $(this).find('.baz').each(function() { let $baz = $(this); // activate JavaScript on $baz }); });

Owner of this card:

Henning Koch
Last edit:
2 months ago
by Henning Koch
Posted by Henning Koch to makandra Curriculum
This website uses cookies to improve usability and analyze traffic.
Accept or learn more