Create web applications effortlessly with Ractive.js

In the ever-evolving world of JavaScript frameworks and libraries, choosing the right foundation for your development can feel overwhelming. Once you commit to a specific framework, switching to another is no easy feat, often impossible due to time and budget constraints.

So, why Ractive.js?

Unlike tools that generate static HTML, Ractive transforms templates into dynamic blueprints for interactive applications. While some might argue that Ractive’s impact is more of a refinement than a revolution, its value remains undeniable.

Ractive’s strength lies in its ability to provide robust capabilities with a refreshingly user-friendly approach. It’s elegant, fast, unobtrusive, and lightweight.

This article will guide you through building a simple search application with Ractive, highlighting its key features and showcasing how it simplifies web app development.

Ractive.js and web apps

What is Ractive.js?

Ractive was initially designed to address the complexities of data binding. It achieves this by converting templates into a lightweight virtual representation of the DOM. This allows for efficient and intelligent updates to the real DOM whenever data changes.

However, it soon became evident that Ractive’s underlying approach and architecture could be applied to streamline other tasks. For instance, it can automatically manage tasks like reusing event handlers and unbinding them when no longer needed, eliminating the need for event delegation. This approach, similar to its data binding, prevents code bloat as the application scales.

Crucially, essential features like two-way binding, animations, and SVG support are available out of the box, with plugins readily available for custom functionality.

Unlike tools and frameworks that impose a rigid structure and new vocabulary, Ractive adapts to your workflow, seamlessly integrating with other libraries.

Our Sample App

Our example application will enable users to search a database of developers based on their skills. The app will have two views:

  • Search: Displays a searchable list of skills.
  • Results: Shows a skill view with a list of developers possessing that skill.

For each developer, we’ll display their name, photo, a short bio, and a list of skills, each linking to the respective skill view.

(Note: Links to a live demo and the source code repository are provided at the end of this article.)

To focus on the Ractive framework, we’ll make some simplifications not recommended for production:

  • Default theme: We’ll use Bootstrap with its default styling instead of custom theming.
  • Dependencies: Dependencies will be added as separate scripts defining global variables instead of using ES6 modules, CommonJS, or AMD with proper loaders for development and a build process for production.
  • Static data: We’ll use pre-scraped static data from publicly available pages.
  • No client-side routing: The URL will remain constant as we switch between views. While not ideal for SPAs, it’s acceptable for smaller interactive components. Ractive lacks a built-in router but can integrate with third-party ones, as shown in this example.
  • Templates inside script tags: While suitable for smaller applications, especially with the advantage of being processed client-side alongside server-side templates (e.g., for internationalization), larger apps benefit from pre-compiling (a.k.a., pre-parsing) templates into internal JS representations.

Getting Started

Let’s build our application the app iteratively, adding features and exploring concepts as we go.

Start by creating a folder with two files: index.html and script.js. Our application will be simple, running directly from the file:// protocol, eliminating the need for a development server (though you can use one if you prefer).

The Search Page

We’ll begin by creating the search page where users can select a skill to find matching developers.

HTML Structure

Let’s start with a basic HTML page:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
    <head>
      <title>Toptal Search</title>
        <!-- LOAD BOOTSTRAP FROM THE CDN -->
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    </head>
    <body>
      <!-- SOME BASIC STATIC CONTENT -->
      <div class="container">
        <h1>Toptal Search</h1>
      </div>
  
      <!-- LOAD THE JAVASCRIPT LIBRARIES WE NEED -->
      <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.9.3/lodash.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/ractive/0.7.3/ractive.min.js"></script>
  
      <!-- LOAD THE DATA -->
      <script src="https://rawgit.com/emirotin/toptal-blog-ractive/master/data.js"></script>
  
      <!-- LOAD OUR SCRIPT -->
      <script src="script.js"></script>
    </body>
  </html>

This is a straightforward HTML5 document. It loads Bootstrap from the CDN, Lodash (for data manipulation), and Ractive.js.

Ractive doesn’t require dedicating the entire page to a SPA; thus, we can have static content. Here, it’s a container element and the page title.

Finally, we load the pre-prepared demo data and the script containing our application logic.

With the HTML skeleton in place, let’s add functionality.

Skill List

One of Ractive’s strengths is its focus on the desired HTML output, allowing developers to concentrate on the code necessary to achieve it.

Let’s create a skill list as our initial view. This involves:

  • Adding an HTML element to display the list.
  • Adding a template snippet to our HTML.
  • Writing JavaScript to provide data to the template, rendering it within the added HTML element.

Here’s the modified HTML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  <div class="container">
    <h1>Toptal Search</h1>
    <div id="root"></div> <!-- THIS IS THE NEW HTML ELEMENT -->
  </div>
  
  <!-- THIS IS THE SMALL SNIPPET OF TEMPLATE CODE -->
  <script type="text/html" id="tpl-app">
    <div class="row">
      {{#each skills}}
      <span class="col-xs-3">
        <a href="#" class="label label-primary">{{this}}</a>
      </span>
      {{/each}}
    </div>
  </script>

Ractive has no strict conventions for designating the HTML element to receive the displayed data; using an ID is the simplest approach. I typically use the “root” ID for this. We’ll see its usage during Ractive initialization shortly. For those interested, there are other ways to specify the root element.

The unusual type="text/html" script element is a clever way to load a chunk of HTML without parsing or rendering it, as browsers ignore scripts with unknown types. The script’s content is a Mustache/Handlebars-like template (Ractive also supports some extensions).

We write the template code assuming access to a “skills” array. The {{#each}} mustache directive directive defines iteration. Inside, the current element is accessible as “this.” Assuming “skills” holds an array of strings, we render it using an interpolation mustache {{this}}.

Now, let’s look at the JavaScript that connects the data to the template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  (function () {
    var skills = DB.skills, developers = DB.developers;
    var app = new Ractive({
      el: '#root',
      template: '#tpl-app',
      data: {
        skills: _.keys(DB.skills)
      }
    });
  }());

In just ten lines:

  1. We “retrieve” data from our “database.”
  2. Instantiate a new Ractive app.
  3. Instruct it to render inside the element with id="root".
  4. Tell it to fetch the template from the script element with id="tpl-app" (there are other ways for this).
  5. Pass initial data (or “scope” in Angular terms), which we’ll learn to change dynamically.
  6. Use lodash’s “keys” method to get skill names, which are object keys in our “DB.”

This script tells the framework what to do, leaving the “how” to the template. I find this approach intuitive and elegant.

Now, let’s add more functionality.

Our search page needs a search field. As the user types, we’ll dynamically filter the skill list to display matching skills (case-insensitive).

Following Ractive’s approach, we start with the template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  <div class="container">
    <h1>Toptal Search</h1>
    <div id="root"></div>
  </div>
  <!-- THIS IS THE SMALL SNIPPET OF TEMPLATE CODE -->
  <script type="text/html" id="tpl-app">
    <!-- HERE’S OUR SEARCH BOX -->
    <div class="row">
      <form class="form-horizontal col-xs-6 col-xs-offset-6">
      <input type="search" class="form-control"
          value="{{ skillFilter }}"
          placeholder="Type part of the skill name here">
      </form>
    </div>
    <hr>
    <!-- NOW INSTEAD OF DISPLAYING ALL SKILLS, WE INVOKE A
         TO-BE-CREATED JAVASCRIPT skills() FUNCTION THAT WILL
         FILTER THE SKILL LIST DOWN TO THOSE THAT MATCH THE
         TEXT ENTERED BY THE USER -->
    <div class="row">
      {{#each skills()}}
      <span class="col-xs-3">
        <a href="#" class="label label-primary">{{this}}</a>
      </span>
      {{/each}}
    </div>
  </script>

There are several important additions:

First, we added a <div> for our search box. We’ll connect this input to a variable using Ractive’s two-way binding feature. This allows retrieving input values without manual DOM manipulation. The interpolation mustache value="{{ skillFilter }}" binds the “skillFilter” variable to the input’s value attribute. Ractive automatically updates this variable by monitoring the input.

Second, as the comment indicates, instead of displaying all skills, we’ll use a JavaScript function skills() to filter the skill list, returning only those matching the user’s input:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  // store skill list in a variable outside of Ractive scope
  var skillNames = _.keys(DB.skills);
  
  var app = new Ractive({
    el: '#root',
    template: '#tpl-app',
    data: {
      // initializing the context variable is not strictly
      // required, but it is generally considered good practice
      skillFilter: null,
  
      // Define the skills() function right in our data object.
      // Function is available to our template where we call it.
      skills: function() {
        // Get the skillFilter variable from the Ractive instance
        // (available as this).
        // NOTE WELL: Our use of a getter here tells Ractive that
        // our function has a *dependency* on the skillFilter
        // value, so this is significant.
        var skillFilter = this.get('skillFilter');
        if (!skillFilter) {
          return skillNames;
        }
        skillFilter = new RegExp(_.escapeRegExp(skillFilter), 'i')
        return _.filter(skillNames, function(skill) {
          return skill.match(skillFilter);
        });
      }
    }
  });

While clean and straightforward, you might wonder about performance implications. Calling a function on every input seems inefficient. However, Ractive only re-renders necessary template parts (and calls their functions) when their dependencies (variables) change.

For the curious, a more elegant approach using a computed property exists, which I encourage you to explore.

The Results Page

With a functional skill list, let’s create the results view, displaying developers matching the selected skill.

Switching Between Views

We’ll implement two distinct views: one for the skill list and another for the developer results based on the selected skill.

First, we need to hide the skill list and display the selected skill as the heading when the user clicks a skill. Conversely, the skill view should allow returning to the skill list.

Here’s the first step:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  <script type="text/html" id="tpl-app">
    <!-- PARTIAL IS A NEW CONCEPT HERE -->
    {{#partial skillsList}}
      <div class="row">
        <form class="form-horizontal col-xs-6 col-xs-offset-6">
          <input type="search" class="form-control" value="{{ skillFilter }}"
                   placeholder="Type part of the skill name here">
        </form>
      </div>
      <hr>
      <div class="row">
        {{#each skills()}}
        <span class="col-xs-3">
          <!-- MAKE OUR SKILLS CLICKABLE, USING PROXY EVENTS -->
          <a href="#" class="label label-primary"
             on-click="select-skill:{{this}}">{{this}}</a>
        </span>
        {{/each}}
      </div>
    {{/partial}}
  
    {{#partial skillView}}
      <h2>
        <!-- DISPLAY SELECTED SKILL AS HEADING ON THE PAGE -->
        {{ currentSkill }}
        <!-- CLOSE BUTTON TAKES USER BACK TO SKILLS LIST -->
        <button type="button" class="close pull-right"
          on-click="deselect-skill">&times; CLOSE</button>
      </h2>
    {{/partial}}
  
    <!-- PARTIALS ARE NOT IN THE VIEW UNTIL WE EXPLICITLY
         INCLUDE THEM, SO INCLUDE THE PARTIAL RELEVANT TO THE
         CURRENT VIEW. -->
    {{#if currentSkill}}
      {{> skillView}}
    {{else}}
      {{> skillsList}}
    {{/if}}
  </script>

We’ve made several changes:

  • We’ve moved the existing list view into a partial. Partials are reusable template chunks we’ll include elsewhere.
  • To make skills clickable, we use proxy events. This lets us react to physical events (like “on-click”) and proxy them to logical events (like “select-skill”) with arguments (in this case, the skill name).
  • _ (Note: An alternative syntax, method calls, achieves the same.)_
  • We introduce a variable, currentSkill, to store the selected skill name (or an empty string if none).
  • We define another partial to display the currentSkill and a “CLOSE” link to deselect the skill.

In the JavaScript, we subscribe to the “select-skill” and “deselect-skill” events, updating currentSkill (and skillFilter):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  var app = new Ractive({
    el: '#root',
    template: '#tpl-app',
    data: {
      skillFilter: null,
      currentSkill: null,    // INITIALIZE currentSkill TO null
  
      // skills function remains unchanged
      skills: function() {
        var skillFilter = this.get('skillFilter');
        if (!skillFilter) {
          return skillNames;
        }
        skillFilter = new RegExp(_.escapeRegExp(skillFilter), 'i')
        return _.filter(skillNames, function(skill) {
          return skill.match(skillFilter);
        });
      }
    }
  });
  
  // SUBSCRIBE TO LOGICAL EVENT select-skill
  app.on('select-skill', function(event, skill) {
    this.set({
      // SET currentSkill TO THE SKILL SELECTED BY THE USER
      currentSkill: skill,
      // RESET THE SEARCH FILTER
      skillFilter: null
    });
  });
  
  // SUBSCRIBE TO LOGICAL EVENT deselect-skill
  app.on('deselect-skill', function(event) {
    this.set('currentSkill', null);    // CLEAR currentSkill
  });

Listing Developers

With the skill view prepared, let’s populate it with a list of matching developers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  {{#partial skillView}}
    <h2>
      {{ currentSkill }}
      <button type="button" class="close pull-right" 
              on-click="deselect-skill">&times; CLOSE</button>
    </h2>
  
    {{#each skillDevelopers(currentSkill)}}
      <div class="panel panel-default">
        <div class="panel-body">
          {{ this.name }}
        </div>
      </div>
    {{/each}}
  {{/partial}}

We’ve added an iteration section to the skillView partial, iterating over the result of a new skillDevelopers function. This function will return an array of developers matching the selected skill. For each developer, we render a panel displaying their name. (While I could use the implicit form {{name}} and let Ractive traverse the context chain, I prefer being explicit. More on contexts and references can be found in Ractive’s documentation documentation.)

Here’s the implementation of skillDevelopers():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  skillDevelopers: function(skill) {
    // GET THE SKILL OBJECT FROM THE “DB”
    skill = skills[skill];
    // SAFETY CHECK, RETURN EARLY IN CASE OF UNKNOWN SKILL NAME
    if (!skill) {
      return;
    }
    // MAP THE DEVELOPER’S IDs (SLUGS) TO THE
    // ACTUAL DEVELOPER DETAIL OBJECTS
    return _.map(skill.developers, function(slug) {
      return developers[slug];
    });
  }

Enhancing Developer Entries

Let’s add more details and a photo to each developer’s entry:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  {{#partial skillView}}
    <h2>
      {{ currentSkill }}
      <button type="button" class="close pull-right" 
              on-click="deselect-skill">&times; CLOSE</button>
    </h2>
  
    {{#each skillDevelopers(currentSkill)}}
      <div class="panel panel-default">
        <div class="panel-body media">
          <div class="media-left">
            <!-- ADD THE PHOTO -->
            <img class="media-object img-circle"
                 width="64" height="64"
                 src="{{ this.photo }}" alt="{{ this.name }}">
          </div>
          <div class="media-body">
            <!-- MAKE THE DEVELOPER’S NAME A HYPERLINK TO
                 THEIR PROFILE -->
            <a class="h4 media-heading" href="{{ this.url }}"
               target="_blank">
              {{ this.name }}</a>
            <!-- ADD MORE DETAILS (FROM THEIR PROFILE) -->
            <p>{{ this.desc }}</p>
          </div>
        </div>
      </div>
    {{/each}}
  {{/partial}}

This utilizes Bootstrap’s features without introducing new Ractive concepts.

Displaying Clickable Developer Skills

Finally, let’s display each developer’s skills as clickable links leading to their respective skill views.

This functionality mirrors our existing clickable skill list. To avoid redundancy, we’ll leverage partials for reusability.

(Note: Ractive offers a more advanced approach for reusable view components called components, which we won’t cover here for simplicity.)

Here’s how we achieve this using partials (without writing additional JavaScript!):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  <!-- MAKE THE CLICKABLE SKILL LINK INTO ITS OWN “skill” PARTIAL -->
  {{#partial skill}}
    <a href="#" class="label label-primary"
       on-click="select-skill:{{this}}">{{this}}</a>
  {{/partial}}
  
  {{#partial skillsList}}
    <div class="row">
      <form class="form-horizontal col-xs-6 col-xs-offset-6">
        <input type="search" class="form-control"
               value="{{ skillFilter }}"
               placeholder="Type part of the skill name here">
      </form>
    </div>
    <hr>
    <div class="row">
      {{#each skills()}}
        <!-- USE THE NEW “skill” PARTIAL -->
        <span class="col-xs-3">{{> skill}}</span>
      {{/each}}
    </div>
  {{/partial}}
  
  {{#partial skillView}}
    <h2>
      {{ currentSkill }}
      <button type="button" class="close pull-right"
              on-click="deselect-skill">&times; CLOSE</button>
    </h2>
  
    {{#each skillDevelopers(currentSkill)}}
      <div class="panel panel-default">
        <div class="panel-body media">
          <div class="media-left">
            <img class="media-object img-circle"
                 width="64" height="64"
                 src="{{ this.photo }}" alt="{{ this.name }}">
          </div>
          <div class="media-body">
            <a class="h4 media-heading" href="{{ this.url }}"
               target="_blank">{{ this.name }}</a>
            <p>{{ this.desc }}</p>
            <p>
              <!-- ITERATE OVER THE DEVELOPER’S SKILLS -->
              {{#each this.skills}}
                <!-- REUSE THE NEW “skill” PARTIAL TO DISPLAY EACH
                     DEVELOPER SKILL AS A CLICKABLE LINK -->
                {{> skill}}&nbsp;
              {{/each}}
            </p>
          </div>
        </div>
      </div>
    {{/each}}
  {{/partial}}

Since the developer object from our “DB” already contains the skills list, we only need to modify the template:

  • Move the skill label rendering to a separate partial.
  • Reuse this partial in both the main skill list and the developer’s skill iteration.

This provides clickable skill labels throughout our app, leading to the relevant skill view.

Adding a Preloader

Our app is functional, but we’ll add a preloading animation while resources load, enhancing the user experience.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  <div class="container">
    <h1>Toptal Search</h1>
    <div id="root">
      <div class="progress">
        <div class="progress-bar progress-bar-striped active"
             style="width: 100%">
          Loading...
        </div>
      </div>
    </div>
  </div>

The trick is simple:

  • Add content (a Bootstrap progress bar in this case, but it could be any loading indicator) directly within the “root” element.
  • This content is displayed immediately as it doesn’t depend on JavaScript.
  • Once the Ractive app initializes, it overwrites the “root” element’s content with the rendered template, effectively removing the preloader.

This achieves a smooth loading experience with minimal code.

Conclusion

We’ve built a fairly comprehensive application with minimal code: a searchable, dynamically updated skill list, navigation to skill views, and developer listings with clickable skills. All of this is achieved in under 80 lines of HTML and 40 lines of JavaScript. This demonstrates the power, elegance, and simplicity of Ractive.

You can experience the working application here and access the complete source code here.

This article only scratches the surface of Ractive’s capabilities. I encourage you to dive into the Ractive’s 60 second setup and explore its full potential.

Licensed under CC BY-NC-SA 4.0