The Builder Pattern keeps your code DRY.

Last time, I went on a little rant against object literals, laying out a case that they are enemies of the DRY (Don’t Repeat Yourself) Principle. My example was the UI Grid‘s gridOptions object, which is often provided as an object literal:

$scope.gridOptions = {
  enableSorting: true,
  columnDefs: [
    { field: 'name' },
    { field: 'company'},
  ]
}

If your application has more than one grid, and you code an object literal for each one, then you have no DRY place to express the standard ways you want grids to behave. A better practice is to write a function that creates your standard grid options.

But your grids will differ from each other in some respects. Some may be multi-select and others single-select. Certainly they will contain different columns. So how can you keep your code DRY in the face of such variability?

One answer is to use the Builder Pattern. The example here will focus on building the gridOptions object, but you’ll find this pattern is useful whenever you want to set up many properties of an object before you use it.

With the Builder Pattern, you use a builder object to specify what you want and then, as a final step, ask the builder to deliver the specified object. Often, a fluent interface is natural:

var gridOptions = new GridOptionsBuilder()
  .multiSelect()
  .data(myData)
  .startColumnDef('name').cellClass('nameClass').endColumnDef()
  .startColumnDef('company').cellClass('companyClass').endColumnDef()
  .yieldGridOptions();

Now to unpack some of the key calls:

multiSelect() not only enables the user to select more than one row; it is also the DRY place to specify how you want multi-selection to work. I’ll show the specifics in a moment.

The call to yieldGridOptions() is always the final step of using the GridOptionsBuilder. It returns the actual gridOptions object that will govern your grid.

I’ve built up each column starting with a call to startColumnDef and finishing with endColumnDef. The first call actually returns a ColumnBuilder that is internal to the GridOptionsBuilder class. If you prefer, you could expose ColumnBuilder as an independent class.

Obviously, a full implementation would offer many more functions, but if you start with even this simple an implementation you can add to the builder as needed.

The complete listing will show how it’s all put together. I’ll keep this post DRY by not repeating what you can read in the comments.

function GridOptionsBuilder() {
  'use strict';
  
  var gridSelf = this;
  
  // Start with standard options for a single-select grid.
  var gridOptions = {
    enableSorting: true,
    enableColumnMenus: true,
    enableGridMenu: true,
    multiSelect: false,
    columnDefs: [],
    data: []
  };
  
  // Enable multiple rows to be selected.
  // A more elaborate implementation might take a Boolean
  // parameter to enable *or* disable multi-selection.
  this.multiSelect = function() {
    gridOptions.multiSelect = true;

    // Require Ctrl or Shift to multi-select
    gridOptions.modifierKeysToMultiSelect = true;

    // Return the builder to provide a fluent interface.
    return gridSelf;
  };
  
  // Set the data that the grid will display.
  this.data = function(d) {
    gridOptions.data = d;
    return gridSelf;	
  };
  
  // Always the final call. Returns the built-up options.
  this.yieldGridOptions = function() {
    return gridOptions;
  }
  
  // Internal function/class to build a column.
  function ColumnBuilder(columnName) {
    var colSelf = this,
    columnDef = { 
      name: columnName 
    };
	
    this.cellClass = function(classOrFunction) {
      columnDef.cellClass = classOrFunction;
      // Return the column builder to enable a fluent interface
      return colSelf;
    };
	
    // Always the final call to column-building.
    // Saves the column definition and reverts the
    // fluent interface to the GridOptionsBuilder.
    this.endColumnDef = function() {
      gridOptions.columnDefs.push(columnDef);
      return gridSelf;
    };
  }
  
  // Switches the GridOptionsBuilder's fluent interface to
  // a new instance of ColumnBuilder.
  this.startColumnDef = function(columnName) {
    return new ColumnBuilder(columnName);
  }  
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s