Wijmo

MultiRow 101

This page shows how to get started with Wijmo's MultiRow control.

Getting Started

The MultiRow control extends conventional grid layouts by using multiple rows to represent each data item.

The MultiRow control allows users to see and edit data in a tabular form, just like other conventional grids. But, MultiRow is different from these grids in a way that it allows you to bind each data item to multiple rows, creating form-like interfaces that can display a large number of columns with minimal horizontal scrolling.

The MultiRow control extends the FlexGrid control, so if you know how to use FlexGrid, you will be able to use MultiRow in no time. The main new property is layoutDefinition, which takes an object that describes the layout of the grid rows and cells.

The MultiRow control is not a simple replacement for conventional grids; it is a specialized tool that fits some particular scenarios really well.

To use the MultiRow control in your applications, add references to the wijmo, wijmo.grid, and wijmo.grid.multirow modules, then instantiate MultiRow controls by giving them host element on the page, or add wj-multi-row directives if you are using AngularJS:

HTML
<!DOCTYPE html> <html> <head> <link href="styles/vendor/wijmo.css" rel="stylesheet" /> <script src="scripts/vendor/wijmo.min.js"></script> <script src="scripts/vendor/wijmo.grid.min.js"></script> <script src="scripts/vendor/wijmo.grid.multirow.min.js"></script> <head> <body> <!-- using AngularJS --> <wj-multi-row items-source="data" layout-definition="layoutDef"> </wj-multi-row> <!-- using pure JS --> <div id="multirow"></div> </body> </html>
JS
onload = function() { // create and initialize the MultiRow using pure JavaScript var theMultiRow = new wijmo.grid.multirow.MultiRow('#multirow', { itemsSource: getData(), columnLayout: getColumnLayout() }); }

MultiRow Layout Definition

The MultiRow control uses multiple rows to display each record. The record layout is defined by the layoutDefinition property.

The layoutDefinition property contains an array of cell group objects with the following properties:

The example below illustrates the usage of the layoutDefiniton property:

Now, let's look at the same data shown in a MultiRow control with different layouts. Use the combo box to select a layout definition and how the grid groups the data in different ways. Notice that the MultiRow control provides all the usual grid features, including editing, sorting, filtering, and column sizing:

HTML
<wj-multi-row items-source="orders" layout-definition="layoutDefs.currentItem.def"> </wj-multi-row> <wj-combo-box items-source="layoutDefs" display-member-path="name" selected-index-changed="layoutDefChanged(s,e)"> </wj-combo-box> <p> {​{ layoutDefs.currentItem.description }}</p>
JS
// sample layout definitions $scope.layoutDefs = new wijmo.collections.CollectionView([ { name: 'Traditional', description: 'Traditional grid view, with one row per record. The user must scroll horizontally to see the whole record.', def: [ { cells: [{ binding: 'id', header:'ID' }]}, { cells: [{ binding: 'date', header:'Ordered' }]}, { cells: [{ binding: 'shippedDate', header:'Shipped' }]}, // ... one group per column ... ] }, { name: 'Compact', description: 'This view uses two rows per record. The layout is divided into three column groups: order, customer, and shipper', def: [ { header: 'Order', colspan: 2, cells: [ { binding: 'id', header: 'ID' }, { binding: 'date', header: 'Ordered' }, { binding: 'amount', header: 'Amount' }, { binding: 'shippedDate', header: 'Shipped' } ] }, // ... two more groups for customers and shippers ... ] }, { name: 'Detailed', description: 'This view uses three rows per record. The layout is divided into three column groups: order, customer, and shipper', def: [ { header: 'Order', colspan: 2, cells: [ { binding: 'id', header: 'ID', colspan: 2 }, { binding: 'amount', header: 'Amount', colspan: 2 }, { binding: 'date', header: 'Ordered' }, { binding: 'shippedDate', header: 'Shipped' } ] }, // ... two more groups for customers and shippers ... ] } ]);

Result (live):

{{ layoutDefs.currentItem.description }}

The layoutDefinition property specifies the layout of the cells in the grid. It contains an array of cell group objects. Each cell group specifies how many columns the group should span, and the cells that make up each group.

The image below illustrates how a cell group is interpreted and turned into a grid layout:

cell group

The group spans three grid columns. It contains six cells with different spans. When generating the layout, the grid fits as many cells as possible in each row, and wraps to the next row when the group span is reached. The last cell in each row is automatically expanded to fill colspan of the group. The process is similar to wrapping of text to create a paragraph.

The same process is applied to every group in the layoutDefinition object.

Collapsible Column Headers

By default, the MultiRow control creates column headers that span multiple rows and shows the header for each cell defined in the layoutDefinition.

These cell-specific column headers may be used to sort or filter the data as you would do in a conventional grid.

In some cases, you may want to collapse the column headers to a single line, showing only the group names rather than individual cells. This saves space at the expense of having individual cell headers. To collapse the column headers, set the collapsedHeaders property to true. In these scenarios, remember to set the header property on the groups in order to avoid empty column headers.

HTML
<wj-multi-row items-source="orders" layout-definition="ldThreeLines" collapsed-headers="{​{ true }}" show-header-collapse-button="{​{ true }}" control="hdrGrid"> </wj-multi-row> <label> <input type="checkbox" ng-model="hdrGrid.collapsedHeaders"> CollapsedHeaders </label> <label> <input type="checkbox" ng-model="hdrGrid.showHeaderCollapseButton"> Show Header Collapse Button </label>
JS
// no code required

Result (live):


Fully Expanded Column Headers

Setting the collapsedHeaders property to true causes the grid to show a single row of column headers containing the group names. Setting it to false cause it to show a group of rows with the same layout as the data, and column names in the cells.

If you want to display the group and the column names, set the collapsedHeaders property to null instead of true or false.

If you choose to show group and column headers, you may also want to use CSS to differentiate their appearance. This can be done easily using the wj-group-header class added to the group headers.

The sample below demonstrates:

HTML
<wj-multi-row items-source="orders" layout-definition="ldThreeLines" initialized="initExpandAll(s, e)" class="expanded-groups"> </wj-multi-row>
JS
// setting collapsedHeaders to null shows // both group and column headers $scope.initExpandAll = function (s, e) { s.collapsedHeaders = null; }
CSS
/* custom styling for group headers */ .expanded-groups .wj-cell.wj-group-header { background-color: #abcfc7; }

Result (live):

Styling Records, Groups, and Cells

In most of the applications, you would want to show where each record and group starts or ends. The MultiRow control enables this by adding CSS class names to cell elements in the first and last row/column of each group. The class names are wj-record-start, wj-record-end, wj-group-start, and wj-group-end.

The example below shows how you can use these class names in CSS rules to customize the appearance of the record and group delimiters. It also shows how you can use the standard cssClass property to customize the appearance of specific cells within a group:

HTML
<wj-multi-row class="multirow-css" items-source="orders" layout-definition="ldThreeLines"> </wj-multi-row>
CSS
/* custom styling for a MultiRow */ .multirow-css .wj-cell.wj-record-end:not(.wj-header) { border-bottom-color: #8fabff; /* blue lines between records */ } .multirow-css .wj-cell.wj-group-end { border-right-color: #bc5505; /* brown lines between groups */ } .multirow-css .wj-cell.id { color: #c0c0c0; } .multirow-css .wj-cell.amount { color: #014701; font-weight: bold; } .multirow-css .wj-cell.email { color: #0010c0; text-decoration: underline; }

Result (live):

Grouping

The MultiRow control supports CollectionView-based grouping just like FlexGrid. To use grouping, create a CollectionView based on the raw data and add one or more GroupDescription objects to the GroupDescriptions array of the collection.

HTML
<wj-multi-row items-source="groupedOrders" layout-definition="ldThreeLines" group-header-format="City: <b>{value} </b>({count:n0} items)" control="groupingGrid"> </wj-multi-row> <label> <input type="checkbox" ng-model="groupingGrid.showGroups"> Show Groups </label> <br /> <button class="btn" ng-click="groupingGrid.collapseGroupsToLevel(0)"> Collapse All </button> <button class="btn" ng-click="groupingGrid.collapseGroupsToLevel(10)"> Expand All </button>
JS
// expose grouped orders to the controller $scope.groupedOrders = new wijmo.collections.CollectionView(orders, { groupDescriptions: [ 'customer.city', ], newItemCreator: function () { return { // add empty customer and shipper objects to new orders customer: {}, shipper: {} } }, });

Result (live):


Filtering

The MultiRow control supports filtering just like FlexGrid.

Filtering is provided by the wijmo.grid.filter.FlexGridFilter class. To add filtering UI to MultiRow, create a FlexGridFilter and pass MultiRow as a parameter in the constructor.

If you are using AngularJS, you can also add a filter to the grid by embedding a wj-flex-grid-filter directive as a child of the grid's directive.

HTML
<wj-multi-row items-source="orders" layout-definition="ldThreeLines" initialized="initFlexFilter(s, e)"> </wj-multi-row>
JS
// add a filter to the MultiRow $scope.initFlexFilter = function (s, e) { var filter = new wijmo.grid.filter.FlexGridFilter(s); }

Result (live):

Row and Column Freezing

The MultiRow control allows you to freeze rows and columns so they remain in view as the user scrolls the grid. Frozen cells can be edited and selected as regular cells, exactly as in Excel and in the FlexGrid control.

This example allows you to toggle whether the first group of rows and columns should be frozen.

HTML
<wj-multi-row items-source="orders" layout-definition="ldTwoLines" control="frozenGrid"> </wj-multi-row> <button class="btn btn-default" ng-click="toggleFreeze(2, 2)"> {​{ frozenGrid.frozenRows == 0 ? 'Freeze' : 'Unfreeze' }} </button>
JS
// toggle frozen rows/columns $scope.toggleFreeze = function (rows, cols) { var flex = $scope.frozenGrid; if (flex) { flex.frozenColumns = flex.frozenColumns ? 0 : cols; flex.frozenRows = flex.frozenRows ? 0 : rows; } }
CSS
/* custom styling for frozen cells */ .wj-cell.wj-frozen:not(.wj-header):not(.wj-group):not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #f3fbcc; }

Result (live):

Paging

The MultiRow control supports paging through the IPagedCollectionView interface, which is nearly identical to the one in .NET. To enable paging, set the IPagedCollectionView.pageSize property to the number of items you want to display on each page, and provide a UI for navigating the pages.

In this example, we use JavaScript to show four items per page. We add navigation buttons, and call IPagedCollectionView method in the button click directives. We use the pageIndex and pageCount properties to show the current page and total number of pages.

HTML
<wj-multi-row items-source="pagedOrders" layout-definition="ldThreeLines"> </wj-multi-row> <div class="btn-group"> <button type="button" class="btn" ng-click="pagedOrders.moveToFirstPage()"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn" ng-click="pagedOrders.moveToPreviousPage()"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn" disabled style="width:100px"> {{ pagedOrders.pageIndex + 1 | number }} / {{ pagedOrders.pageCount | number }} </button> <button type="button" class="btn" ng-click="pagedOrders.moveToNextPage()"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn" ng-click="pagedOrders.moveToLastPage()"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div>
JS
// expose paged orders to the controller $scope.pagedOrders = new wijmo.collections.CollectionView(orders, { pageSize: 4 });

Result (live):

Adding and Deleting Records

The MultiRow control supports the allowAddNew and allowDelete properties provided by the FlexGrid control.

Setting the allowAddNew property to true causes the grid to display a set of 'new row template' rows at the bottom of the grid. When user starts editing a cell in the new row template, a new item is added to the source collection. When user finishes editing the new item by moving the selection to another row or the focus to another control, the new item is committed.

Setting the allowDelete property to true causes the grid to monitor key presses and to delete the current row if user presses the 'Delete' key while an entire row is selected.

HTML
<wj-multi-row items-source="addNewOrders" layout-definition="ldThreeLines" show-groups="false" allow-add-new="true" allow-delete="true" control="addNewGrid"> </wj-multi-row> <label> <input type="checkbox" ng-model="addNewGrid.allowAddNew"> Allow Add New </label> <br/> <label> <input type="checkbox" ng-model="addNewGrid.allowDelete"> Allow Delete </label>
JS
// create 'addNewOrders' collection, start with last item selected $scope.addNewOrders = new wijmo.collections.CollectionView(orders); $scope.addNewOrders.moveCurrentToLast();
CSS
/* custom styling for new row templates */ .wj-cell.wj-new:not(.wj-header):not(.wj-group):not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #f3fbcc; }

Result (live):