HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Knockout.js binding 2D table with rowspan

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withknockoutbindingtablerowspan

Problem

The app I'm working on should allows it's users to create tables. I have a view in which users are able to create a table. They should be able to define here the table's columns. The columns that the user adds will be part from some predefined types of columns: BusinessFields, SourceKeys, SourceAttrs,...; At the same time columns should have some other proprieties that define the value they will contain: minlength, minValue, defaultValue,...

These tables have to have a format(at least a column for the SourceKeys and TargetKeys categories), but this format is not so restrictive. The image will help to describe the point:

I have made it work as you see in the image, but I don't like how I've made the bindings. My code design also reduces the flexibility (For example: It would be much more difficult to add a click binding for a row).

JavaScript:

```
//column definition
function Column(columnName, minLength, maxLength, minValue, maxValue, reg_ex, role, order, defaultValue) {
var self = {};
self.name = ko.observable(columnName || 'new column');
self.minLength = ko.observable(minLength || null);
self.maxLength = ko.observable(maxLength || null);
self.minValue = ko.observable(minValue || null);
self.maxValue = ko.observable(maxValue || null);
self.reg_exp = ko.observable(reg_ex || null);
self.role = ko.observable(role ? role : 1);
self.order = ko.observable(order || null);
self.defaultValue = ko.observable(defaultValue || null);
return self;
}

//table definition
function Table() {
var self = {};
self.tableName = ko.observable('New table');
self.tableDescription = ko.observable('description');
self.businessFields = ko.observableArray([]);
self.sourceKeys = ko.observableArray([]);
self.sourceAttr = ko.observableArray([]);
self.targetKeys = ko.observableArray([]);
self.targetAttr = ko.observableArray([]);
self.attrFields = ko.observableArray([]);
self.technicalFields = ko.observableArray

Solution

I would just add a columnGroup attribute to Column and then do a groupBy-type operation on that attribute. That would reduce your Table view-model to just a collection of Rows, which would be nice. Here's what I'm thinking for Column

function Column(columnName, columnGroup /* ... all your other attributes ... */) {
    var self = {};
    self.name = ko.observable(columnName || 'new column');
    self.groupName = ko.observable(columnGroup || 'Misc.');
    /* all your other boilerplate setting */
    return self;
}

// returns a group object, which has a name and an array of items.
// this will only be created once in the groups array.
function ensureGroup (groups, groupName) {
    var foundGroup;
    groups.some(function (group) {
        if (group.name === groupName) {
            return foundGroup = group;
        }
    });
    if (!foundGroup) {
        foundGroup = {
            name: groupName,
            items: []
        };
        groups.push(foundGroup);
    }
    return foundGroup;
}

function Table() {
    var self = {};
    self.tableName = ko.observable('New table');
    self.tableDescription = ko.observable('description');
    self.columns = ko.observableArray([]);
    self.columnGroups = ko.computed(function () {
        var groups = self.columns().reduce(function (groups, column) {
            var group = ensureGroup(groups, column.fieldGroup();
            group.items.push(column);
        }, []);
        return groups;
    });
    return self;
}


You then just have to iterate over table.columnGroups in your template. Of course, this code is untested, and there are certainly ways to clean it up more, but I wanted to give you an approach to work from.

Code Snippets

function Column(columnName, columnGroup /* ... all your other attributes ... */) {
    var self = {};
    self.name = ko.observable(columnName || 'new column');
    self.groupName = ko.observable(columnGroup || 'Misc.');
    /* all your other boilerplate setting */
    return self;
}

// returns a group object, which has a name and an array of items.
// this will only be created once in the groups array.
function ensureGroup (groups, groupName) {
    var foundGroup;
    groups.some(function (group) {
        if (group.name === groupName) {
            return foundGroup = group;
        }
    });
    if (!foundGroup) {
        foundGroup = {
            name: groupName,
            items: []
        };
        groups.push(foundGroup);
    }
    return foundGroup;
}

function Table() {
    var self = {};
    self.tableName = ko.observable('New table');
    self.tableDescription = ko.observable('description');
    self.columns = ko.observableArray([]);
    self.columnGroups = ko.computed(function () {
        var groups = self.columns().reduce(function (groups, column) {
            var group = ensureGroup(groups, column.fieldGroup();
            group.items.push(column);
        }, []);
        return groups;
    });
    return self;
}

Context

StackExchange Code Review Q#40276, answer score: 5

Revisions (0)

No revisions yet.