Object literals are enemies of the DRY Principle.

In the last post, I explored three ways that the DRY Principle (Don’t Repeat Yourself) means more than just “don’t repeat blocks of code.” In this post, I’ll show a fourth way.

I suggest that object literals are possibly the most frequent violators of the DRY Principle. This is because they are so natural in JavaScript, so easy to create, and so pervasive in tutorials and API documentation. Do not let these innocent-looking constructs take you in!

At work, we use Angular’s UI Grid component to display tabular data. You can control the many options of UI Grid using an object literal like this one, similar to what you’ll find in UI Grid’s Tutorial.

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

Having done that, you can create an HTML DIV with the special attribute ui-grid="gridOptions" and you’ll get the grid below. I’ve clicked the nifty column menu on the first column so you can see what you get for free with UI Grid. Not only can the user sort the grid by a column, but he can hide the column completely.

ColumnMenusTrue

It’s the easiest thing in the world to create a literal like that for each grid in the application.

Now the application goes to usability testing. It is soon discovered that once a column is hidden, it is impossible to bring it back.

doh

To remedy this, you have two choices:

You can prevent the user from getting into trouble by disabling column menus:

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

Or, you can give the user a way to get out of trouble. A grid menu will let him choose which columns to display:

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

…so that he sees this:

GridMenuTrue

Of course, you want your application to be consistent so, whichever way you go, you must change each grid’s object literal. Worse than that, as other developers add grids to the application, they must be aware of this issue and know which choice you made. What do you think the chances of that are? (Hint: not good.)

The remedy is to create your grid options in one DRY place. Perhaps you could write a function like this one:

function createStandardGridOptions() {
    return {
      enableSorting: true,
      enableColumnMenus: true,
      enableGridMenu: true,
    }
  };

So now you can do this:

$scope.gridOptions = createStandardGridOptions();

// Now you can add to the standard set-up:
$scope.gridOptions.columnDefs = [
  { field: 'name' },
  { field: 'company'}
];

You might say, “It’s a little awkward to have to create the standard options and then, as a separate step, add more. In this case we added only columnDefs, but in other cases we might add much more. Wouldn’t it be nice to pass in an object literal so there would be only one step?”

function createStandardGridOptions(moreOptions) {
  var gridOptions = {
    enableSorting: true,
    enableColumnMenus: true,
    enableGridMenu: true,
  };
  var opt;
  if (moreOptions) {
    for (opt in moreOptions) {
      gridOptions[opt] = moreOptions[opt];
    }
  }
  return gridOptions;
};

So now you only have to do this:

$scope.gridOptions = createStandardGridOptions( { columnDefs: [
  { field: 'name' },
  { field: 'company'},
] } );

Easier and more elegant, right?

No, NO, NO!!! In the next post, I’ll show the DRY way to handle this sort of situation.

One comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.