Progressively Decoupled Drupal

By Jason Blanda,
A square on the left that says "FrontEnd / Drupal"  transitioning into two overlapping, but separate squares on the right. One says FrontEnd the other says Drupal with "JS Framework" written on the overlapping area

If you are a Drupal developer you know that the Views module is an essential part of site-building. Its ability to query and aggregate content to be displayed in any way you might need saves a considerable amount of time and energy.

However, even with all that power, Views has one major downfall. As the queries start to pile up, views may start to slow down. Add in relationships, exposed filters, lots of fields (and even, contextual filters!) and you’re asking for serious performance issues. 

The Project Requirements

The project requirements from the client were quite simple: create a school finder application within their website that would allow users to filter, compare and select the school they would like their child to attend. They needed specific filters for grade level, special education, language requirements, and the ability to enter their address to get the distance away from their home.

The Challenge

The most technically challenging aspect of this project was that much of this data was spread across nodes, taxonomy terms, as well as references to other terms within terms.

A simple Views implementation was not an option due to the client’s complex needs. A complex Views implementation would have introduced countless relationships with brought in latency and duplicate content issues.

Because of this, the decision was made to progressively decouple Drupal where the frontend implementation would be separate from the backend data aggregation.

What is Progressively Decoupled Drupal?

Progressively decoupled Drupal is an architectural approach that allows for site editors to leverage the administrative interfaces, content workflows, and all other traditional methodologies of creating, editing, and publishing content via Drupal. 

For frontend developers, progressively decoupled Drupal allocates a portion of the page to include the frontend framework of their choosing, such as React, Gatsby, or Angular. This process assists frontend developers by eliminating the constraints of Drupal’s templating system.

The Architecture

To put it into simple terms, we created an MVVM (Model View ViewModel) application using Knockout.js; however, this implementation is architecturally agnostic and other frameworks such as React could easily be substituted. More on why we chose Knockout.js to come.

To create our Model we chose to still lean on Views for its powerful content aggregation and advanced query systems. In this particular case, we used the View Data Export module to construct a JSON feed of all the data we would need to feed our application. 

Each school was then represented in the following way:

{
  "nid": "195",
  "title": "<a href=\"/school/alvarado-elementary-school\" title=\"opens in new window\" target=\"_blank\">Alvarado Elementary School</a>",
  "address": "625 Douglass Street, San Francisco, CA 94114",
  "type": "Early Education, Elementary School",
  "special_ed_programs": "<div class=\"item-list\"><ul><li>Resource Specialist   services</li><li>Separate class - Moderate/severe</li></ul></div>",
  ... Additional fields ...
}

To consume and display this data we chose to utilize Knockout.js. The reasons were pretty straightforward, Knockout.js is:

  1.     Open source

  2.     Written in pure JavaScript and works with any web framework

  3.     Lightweight, only 66kb minified

  4.     No dependencies

  5.     Supports all mainstream browsers (even ancient ones)

  6.     Fully documented


The Implementation

Like most JavaScript frameworks we needed an entry point for our application. The entry point tells the framework, this is where our application begins and serves as the link between our JavaScript and the HTML contents of our template. The framework will only be scoped to the code contained within the entry point. 

To do this we created a module that rendered a single page, and housed the Twig template, CSS file, Knockout.js source, and custom JavaScript files we created.
First, we defined our entry point within the Twig template file for our page:

<div id="knockout-app">
. . .
</div>

The ID for our entry point is arbitrary; however, convention suggests the above implementation.
From here we applied the bindings within our own custom JS file. The bindings are what actually link the frontend templates entry point to the JavaScript framework.

var svm = new SchoolViewModel('deferred');
ko.applyBindings(svm, document.querySelector('#knockout-app'));

We then created the SchoolViewModel that we referenced above:

function SchoolViewModel(type) {
  // Knockout Implementation here ... 
}

We were now free to utilize Knockout.js to build out the functionality of our application.

We chose to fetch the data with a simple Ajax call and the data was mapped to and stored in an array of JavaScript objects created from a model “class.”

for (var i = 0; i < $data.length; i++) {
   self.schoolsList.push(
     new School(
       $data[i].title,
       $data[i].address,
       ... additional vars ...
     )
   );
 }

 This allowed us to instantiate objects with observables.

var School = function( distance, ... additional vars) {
   ...
   self.distance = ko.observable(distance);
   ...
}

Observables in Knockout.js are objects that can listen for changes and notify subscribers that those changes have taken place. For instance, subscribers can be notified through the frontend template. This allows for two-way data binding, where the template and JavaScript code speak continuously with one another independent of page load.

Once the data was stored and manipulated where needed, displaying it was as simple as looping through the array of objects in the Twig template of the page we created from our module and defined the entry point above.

<div id="knockout-app">
  ...
  <tr data-bind="foreach: filteredSchools">
    <td tabindex="0>
      <p class="address notranslate"><span class="visually-hidden">Address:</span> <span data-bind="html: address"></span></p>
      <p class="neighborhood"><strong data-bind="text: field_neighborhood"></strong> neighborhood</p>
      <p class="distance"><strong data-bind="text: distance"></strong> miles away from your address</p>
    </td>
  </tr>
  ...
</div>

The data-bind attribute above is provided to us by Knockout.js and allows us to either assign content to a field, send data back to our ViewModel, or in the case of the tr, loop through an array of data.

This allowed us to write one block of markup and produce over 150 rows of content, making our Twig template file small and very manageable.

Filtering was done in a similar fashion; however, we utilized the two-way data binding to send information back to our ViewModel to manipulate. 

For example, a user selects a grade level, the View sends that information back to the ViewModel for manipulation through the data-bind attribute.

<select id="grade-level" class="grade-level" data-bind="options: Grades, value: filterGrade"></select>

Then in the ViewModel the selected grade is used to filter down the results.

self.filteredSchools = ko.computed(function() {
  ...
  return(
    (school.field_grades == self.filterGrade() || gradeControl == self.filterGrade()
  );
});

The filtered school list is updated and sent back to the View to be displayed without the need for a page refresh.

Conclusion

We are only scratching the surface of progressively decoupling Drupal. Substituting Views Data Export for RESTful Web Services and JSON:API in core and making the decoupled architecture more global could allow us to “fully” progressively decouple a site.

It’s important to remember that just because we can progressively decouple Drupal doesn’t always mean we should. Progressively decoupling this particular portion of the site worked perfectly for us as it provided the performance enhancements we desired, but it may be an unnecessary implementation for other features, such as a basic small business website or a simple blog.

Helping to determine those situations and provide tools to efficiently implement those solutions excites us as we push the decoupled architecture into the future.

Jason Blanda

I’m a husband, website developer, digital marketer, author, speedrunner, and bassist. I have been professionally developing and deploying websites using the Drupal content management system for almost 10 years. I run the OneStop How to Guys YouTube channel, offering a wide variety of free educational courses. You can find me from time to time speedrunning Super Mario World, making beer, or doing other random things on Twitch... come hang out!