I'm working on a fairly new project where I'd like to share some config items used by a JavaScript plugin. Specifically, the project is using jQuery dataTables and I want to find a good way of sharing config items.
At the moment, the code I've been given is fairly simple. It is roughly equivalent to:
// It might not be super-essential to read this
function TableSuperClass(tableElement, moreOptions) {
var options = {
pagination: true, // turn on pagination
iDisplayLength : 50, // pages are fifty rows long
lengthChange: false, // stop users changing length of pages
searching : false // disables searching
};
jQuery.extend(options, moreOptions); // merge moreOptions into options
jQuery(tableElement).DataTable(options); // we create a table with the extended options
// This is a super-handy method we'd like to use everywhere
this.repopulate = function usefulRepopulateFunction() {...};
}
function ObjectOwningTable() {
// this object does other things too
this.table = TableSuperClass({
// various column mappings for a particular table type and
// what to order the table on
columns : [...],
// implements row highlighting, partly based on column mappings
fnRowCallback : function() {...}
});
}
function AnotherObjectOwningTable() {
// this object does other things too
this.table = TableSuperClass({
// various column mappings for a different type of table, switched
// based on some logic inside this class
columns : someTest ? [...] : [...],
// implements ordering a different way
aDesc : [...] // aDesc is a legacy API call that lets you specify the col to order on
});
}
The problem is that I now have another sort of table. It is used in a print view. It has the same column mappings as ObjectOwningTable, it needs the super-convenient repopulate method and disabled searching from TableSuperClass, but it doesn't have pagination, as a printout has to show the whole data set.
I can think of two basic solutions:
- Solve the problem with inheritance. Make TableSuperClass provides the handy repopulate interface and disabled searching, then a subclass with pagination, then call that in ObjectOwningTable and AnotherObjectOwningTable. For my print table, I call TableSuperClass with the same column mappings as used in ObjectOwningTable (maybe I extract them to a global constant or something).
- Solve the problem with composition. I have a single DynamicTable that provides just the repopulate interface, then pass in a list of plain objects that are applied to the options in order. These plain objects all live on an enum somewhere, so I can see how they might clash as I write them, and allow me to mix-and-match functionality further down the line.
So, at the consumer level, the first solution would end up looking like
function ObjectOwningTable() {
this.table = PagedTable(FOO_REPORT_COLUMN_MAPPINGS);
}
function AnotherObjectOwningTable() {
this.table = PagedTable(someTest? BAR_COLUMN_MAPPINGS : BAZ_COLUMN_MAPPINGS);
}
function PrintObjectOwningTable() {
this.table = TableSuperClass(FOO_REPORT_COLUMN_MAPPINGS);
}
Whereas the latter would look like
function ObjectOwningTable() {
this.table = DynamicTable([
DATA_TABLES_OPTIONS.pagingFixedAtFiftyRows,
DATA_TABLES_OPTIONS.disableSearching,
DATA_TABLES_OPTIONS.fooColumnsAndHighlighting
]);
}
function AnotherObjectOwningTable() {
var columnMappings = someTest ? DATA_TABLES_OPTIONS.barColumnMappings : DATA_TABLES_OPTIONS.bazColumnMappings;
this.table = DynamicTable([
DATA_TABLES_OPTIONS.pagingFixedAtFiftyRows,
DATA_TABLES_OPTIONS.disableSearching,
columnMappings
]);
}
function PrintObjectOwningTable() {
this.table = DynamicTable([
DATA_TABLES_OPTIONS.disableSearching,
DATA_TABLES_OPTIONS.fooColumnsAndHighlighting,
DATA_TABLES_OPTIONS.disableSortingControls
]);
}
I'm not sure what to choose. The inheritance approach feels very problematic. It seems very inflexible, and I can see a lot of ways for future developers to 'trap' functionality in particular class taxonomies. I also think that long inheritance chains are harder to reason about than flat structures. But the compositional approach is a bit more verbose, and I think my co-workers might object to the repetition. Yet I think that mixing and matching is really handy.
There is a third option, which is to create a subclass of DynamicTable that binds the pagingFixedAtFiftyRows and disableSearching options, and is then called by the objects that use it, passing in their peculiar column mappings. But I wonder if this makes things more complicated, using both subclasses and mixins. I also wonder if it creates the temptation for other developers to add functionality to the subclass when they should really try and create mixins.
What would you recommend?