Adding Micro-Constructors to a Modular Design Pattern

Recently on JSPro I wrote about a modular design patterns, with four different member types providing a high-degree of flexibility over how a script is organised. In this follow-up article, I’ll be looking at a way of extending that pattern by adding micro-constructors, public functions that are object constructors, so you can have multiple independent instances of part of a module’s functionality. This effectively combines the power of object-orientation, with the control and flexibility of a singleton design pattern.

Sample Use Case

I was recently asked by a client to develop a widget for online surveys. The widget would have an interface that appears in the actual survey, and that’s what the survey respondent would use for answering questions. It would also have a different interface within the survey control panel, and that’s what the survey owner would use for designing questions and responses. For reasons of commercial confidentiality, I can’t go into too much detail about what the widget actual does, but suffice it to say it represented a particular programming challenge, simply because of the architectural model it had to support:

  1. There are two interfaces, and on any given page there can be any number of instances of one or the other (but not both).
  2. Each interface instance will need its own public methods – things like load and save for interacting with that instance.
  3. It will also need control functionality to manage all instances of either interface and share data between them.

So to satisfy all these requirements, I came up with this idea; but it wasn’t quite as simple as that!

The Problem With Public Constructors

Each interface needs to be able to support multiple instances, and object-orientation is the perfect solution for that because a public constructor function can be called many times. Additionally, we can create the necessary instance methods by defining them as prototypes. But all of those instances will still need to be managed by the control module, and all without unwanted public data.

An instance of a constructed object is internally referred to as this, so properties of the constructed object are defined using this.property syntax. If we want the prototyped methods to be able to access the constructor’s data, we have to define those properties with public syntax. Private variables defined in the constructor are only accessible in the constructor. And there’s the problem: if the constructor is public, then so are its properties.

So how do we implement instance-specific properties of a constructed public object, while hiding all of that data inside a private scope? It’s actually simpler than it sounds!

The Widget’s Module Structure

Let’s start with a look at the widget’s module structure, which splits the code into two separate scripts. The first script is Widget.js, which creates the root object and defines all the shared functionality, similar to the Master module example from the previous article. As well as the expected config and utility functions object, there’s also another protected object called instances, which we’ll talk about more in just a moment. To keep the code example short, the objects and functions are just empty shells, but you can grab the complete code at the end of this article.

var Widget = (function()
{
  var instances = {},
      config = {},
      utils = {
        extend : function(root, props){ ... },
        privatise : function(root, prop){ ... }
      };

  this.define = function(key, value){ ... };

  return utils.extend(this,
  {
    instances : instances,
    config    : config,
    utils     : utils
  });
})();

The second script is either DeveloperInterface.js or RespondentInterface.js, and is like the Runtime module example from the previous article. Its first job is to seal the protected members. This is where the public interface constructor is defined, a public object which also has public methods of its own. Only one of the interface scripts is required on any given page, and for this example I’m using the developer interface.

Widget = (function()
{
  var instances = this.utils.privatise(this, 'instances'),
      config = this.utils.privatise(this, 'config'),
      utils = this.utils.privatise(this, 'utils');

  this.DeveloperInterface = function()
  {
  };
  this.DeveloperInterface.prototype =
  {
    load : function(){ ... },
    save : function(){ ... }
  };

  return this;
}).apply(Widget);

Inside the Constructor

The public constructor is used to create an instance of an interface, and passes a reference key (a partial id) to the static markup it enhances.

var example = new Widget.DeveloperInterface("A1");

The key is used to get a DOM reference to the markup. Both of those values will need to be accessible from the load and save methods. Other things being equal, then we would define them as public properties:

this.DeveloperInterface = function(key)
{
  this.key = key;
  this.question = document.getElementById('Question-' + this.key);
};

But the problem now is that both those values are accessible from outside the widget, as properties of the instances example.key and example.question. What we actually want is for most of the interface’s data to be private to the widget; but we already know we can’t just define it using private variables.

So it’s unavoidable – somewhere along the line we have no choice but to create public properties. We can, however limit that data to a single reference value, and then use that value to refer to private data. This is what the instances object is for.

Using the Instances Object

Let’s define the constructor again, but this time using the instances object, referenced by the instance key:

this.DeveloperInterface = function(key)
{
  this.key = key;
  instances[this.key] =
  {
    question : document.getElementById('Question-' + this.key)
  };
};

The key is the reference value, and the only public property. The question property is now shielded inside a protected object, yet still accessible to the interface methods as instances[this.key].question. The instances object can then be extended with any number of properties, and all of them will be private to the widget yet available to the instance methods.

Hanging on by a Key

The danger with not shielding data is that it’s possible for users to inadvertently break things. For example, adding a custom property to a single instance that happens to use the same name as an existing property might cause a serious, obvious problem. Unfortunately, all too often it will only be a subtle or intermittent problem. Even worse, it could manifest as something that only happens with particular configurations, or specific browsers.

If we accept that we can’t make these instances entirely safe, we can at least ensure that any such problem is immediately obvious, by only affecting something that quickly halts the script’s execution. Our public key is like that because its loss or modification will break the master reference to all the other data an instance uses.

Maintaing Global Control

Data shielding is certainly important, but equally important is the fact that we now have a centralized reference of all the interface instances. This makes it possible to implement over-arching functionality. Functions in the interface script can iterate through all the instances, reading data from them, writing data back to them, or whatever else is needed for management and control. And because the instances object is protected, it’s also accessible to the master Widget module. From there, we can implement shared functionality that applies to instances of either interface.

But suppose we had shared functionality using delegated event-listeners – events that are bound to the whole document then filtered by target reference. It’s simple enough to identify when an event comes from inside a question element, but how do we know from there which object instance the element belongs to? To make that work, we’ll need to define an additional circular reference – a property of the question element that refers back to its owning instance.

this.DeveloperInterface = function(key)
{
  this.key = key;
  instances[this.key] =
  {
    question : document.getElementById('Question-' + this.key)
  };

  instances[this.key].question.instance = this;
};

Here’s a simple example using a global click event. The event-listener would be defined inside the master Widget module, then triggered by clicks inside the question element of any instantiated interface:

document.addEventListener('click', function(e)
{
  var target = e.target;
  do
  {
    if(typeof(target.instance) !== 'undefined')
    {
      break;
    }
  }
  while(target = target.parentNode);

  if(target)
  {
    alert(target.instance.key);
    alert(target === instances[target.instance.key].question);
  }
}, false);

You can see from those sample alerts, how we can use the instance reference to refer to the instance key, and with that, to a circular reference back to the target.

The Final Constructor Module Pattern

I’ve prepared a download file that includes all the features covered in this article. It is split into two separate files, Widget.js and DeveloperInterface.js, as described in this article:

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.