menu

Decl

Decl provides a declarative interface to mutation observers and helps mitigate some of the performance issues and browser differences that come up when working with the vanilla DOM API.

It has three main ideas: matchers, rules, and scopes.

A matcher is a condition or predicate. There are two types of matchers: element matchers and event matchers. The simplest element matcher is a CSS selector. Likewise, the simplest event matcher is just the name of an event.

A rule is a combination of a matcher and some type-specific action that should occur when there is a new match. Rules come in four flavors: select, when, match, and event. For example, select rules invoke their callback with a new scope when a decedent matches the element matcher. Similarly, an event rule invokes a callback when an event matches the event matcher.

Finally, a scope is the combination of an element and rules for that element. It provides an interface for creating new rules. By default, the global Decl behaves like a scope for the root of the document.

Here is how you could create a simple accordion effect with Decl and the plain DOM API:

Decl.select('.expandable-row', function(scope, expandableRow) {
  var expandableRowclassList = expandableRow.classList;

  scope.when(':not(.expanded)', function(scope) {
    scope.on('click', '.expandable-row-handle', function() {
      // This function is invoked whenever an element with the expandable-row that is not expanded is clicked.
      expandableRowclassList.add('expanded');
    });
  });

  scope.when('.expanded', function(scope) {
    scope.on('click', '.expandable-row-handle', function() {
      // This function is invoked whenever an element with the expandable-row that is expanded is clicked.
      expandableRowclassList.remove('expanded');
    });
  });
});

Of course, this isn't very exciting since this could easily be accomplished with jQuery's on or by manually adding a single event listener to the document object and checking the event target (indeed, this is what both jQuery and Decl are doing internally). Where the observer architecture of Decl shines is with match rules.

Here is how you could cause all selects with the selectize class nested within n2-styles to be automatically initialized regardless of when or how they come to be on the page:

Decl.select('.n2-styles', function(scope) {
  scope.select('select.selectize', function(scope) {
    scope.match(function(_, selectElement) {
      // This function is invoked whenever a select element with the selectize class nested within an element
      // with the n2-styles class comes into the DOM tree.
      $(selectElement).selectize();
    });
  });
});

For a more thorough explanation of Decl and how it works, see the documenation on Github.