Wijmo 5

FlexGrid 101

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

Getting Started

Steps for getting started with FlexGrid in JavaScript applications:

  1. Add references to Wijmo.
  2. Add markup to serve as the Wijmo control's host.
  3. Initialize the Wijmo control(s) via JavaScript.
  4. (Optional) Add some CSS to customize the FlexGrid control's appearance.

This will create a FlexGrid with default behavior, which includes automatic column generation, column sorting and reordering, editing, and clipboard support.

<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="css/bootstrap.css"/> <link rel="stylesheet" type="text/css" href="css/wijmo.css" /> <link href="css/app.css" rel="stylesheet" type="text/css" /> <script src="scripts/wijmo.js" type="text/javascript"></script> <script src="scripts/wijmo.input.js" type="text/javascript"></script> <script src="scripts/wijmo.grid.js" type="text/javascript"></script> </head> <body> <!-- this is the grid --> <div id="gsFlexGrid"></div> </body> </html>
var countries = 'US,Germany,UK,Japan,Italy,Greece'.split(','); var data = []; for (var i = 0; i < count; i++) { data.push({ id: i, country: countries[i % countries.length], date: new Date(2014, i % 12, i % 28), amount: Math.random() * 10000, active: i % 4 === 0 }); } // create the grid and give it some data var grid = new wijmo.grid.FlexGrid('#gsFlexGrid'); grid.itemsSource = data;
/* set default grid style */ .wj-flexgrid { height: 300px; background-color: white; box-shadow: 4px 4px 10px 0px rgba(50, 50, 50, 0.75); margin-bottom: 12px; }

Result (live):

Column Definitions

The Getting Started example did not define any columns, so FlexGrid generated them automatically.

This example shows how you can define the columns using the FlexGrid's initialize method and columns collection directly.

Specifying the columns allows you to choose which columns to show, and in what order. This also gives you control over each column's width, heading, formatting, alignment, and other properties.

In this case, we use star sizing to set the width of the "Country" column. This tells the column to stretch to fill the available width of the grid so there is no empty space. On the "Revenue" column, we set the format property to "n0", which results in numbers with thousand separators and no decimal digits.

<b>Initialize Method</b> <div id="cdInitMethod"></div> <b>Column Definitions</b> <div id="cdColsCollection"></div>
// create two grids and some data var fgInitMethod = new wijmo.grid.FlexGrid('#cdInitMethod'), fgColsCollection = new wijmo.grid.FlexGrid('#cdColsCollection'), cv = new wijmo.collections.CollectionView(data.getData(100)); // initialize one grid using 'initialize' method fgInitMethod.initialize({ autoGenerateColumns: false, columns: [ { header: 'Country', binding: 'country', width: '*' }, { header: 'Date', binding: 'date' }, { header: 'Revenue', binding: 'amount', format: 'n0' }, { header: 'Active', binding: 'active' }, ], itemsSource: cv }); // initialize the other grid using the columns collection fgColsCollection.autoGenerateColumns = false; fgColsCollection.itemsSource = cv; var c = new wijmo.grid.Column(); c.binding = 'country'; c.header = 'Country'; c.width = '*'; fgColsCollection.columns.push(c); c = new wijmo.grid.Column(); c.binding = 'date'; c.header = 'Date'; fgColsCollection.columns.push(c); c = new wijmo.grid.Column(); c.binding = 'amount'; c.header = 'Revenue'; c.format = 'n0'; fgColsCollection.columns.push(c); c = new wijmo.grid.Column(); c.binding = 'active'; c.header = 'Active'; fgColsCollection.columns.push(c);

Result (live):

Initialize Method
Columns Collection

Selection Modes

By default, FlexGrid allows you to select a range of cells with the mouse or keyboard, just like Excel. The selectionMode property allows you to change that so that you can select a row, a range of rows, non-contiguous rows (like in a list-box), a single cell, or disable selection altogether.

This example allows you to pick the selectionMode from a Wijmo Menu control.

<div id="smFlexGrid"></div> <select id="smMenu"> <option value="None">None</option> <option value="Cell">Cell</option> <option value="CellRange" selected>CellRange</option> <option value="Row">Row</option> <option value="RowRange">RowRange</option> <option value="ListBox">ListBox</option> </select>
// initialize grid and menu var grid = new wijmo.grid.FlexGrid('#smFlexGrid'), menu = new wijmo.input.Menu('#smMenu'), cv = new wijmo.collections.CollectionView(data.getData(100)); grid.itemsSource = cv; updateMenuHeader(); // update grid selection mode when an item is selected from the menu menu.itemClicked.addHandler(function (sender) { grid.selectionMode = sender.selectedValue; updateMenuHeader(); }); // update menu header to show current selection mode function updateMenuHeader() { menu.header = '<b>Selection Mode:</b> ' + menu.text; }

Result (live):

Editing

FlexGrid has built-in support for fast, in-cell editing like you find in Excel. There is no need to add extra columns with Edit buttons that switch between display and edit modes.

Users can start editing by typing into any cell. This puts the cell in quick-edit mode. In this mode, pressing a cursor key finishes the editing and moves the selection to a different cell.

Another way to start editing is by pressing F2 or by clicking a cell twice. This puts the cell in full-edit mode. In this mode, pressing a cursor key moves the caret within the cell text. To finish editing and move to another cell, the user must press the Enter, Tab, or Escape key.

Data is automatically coerced to the proper type when editing finishes. If the user enters invalid data, the edit is cancelled and the original data remains in place.

You can disable editing at the grid, column, or row levels using the isReadOnly property of the grid, column, or row objects. In this example, we make the ID column read-only.

<div id="eFlexGrid"></div>
// create and initialize grid (editing is enabled by default) var grid = new wijmo.grid.FlexGrid('#eFlexGrid', { autoGenerateColumns: false, columns: [ { header: 'ID', binding: 'id', width: '*', isReadOnly: true }, // cannot edit { header: 'Country', binding: 'country' }, { header: 'Date', binding: 'date' }, { header: 'Revenue', binding: 'amount', format: 'n0' }, { header: 'Active', binding: 'active' }, ], itemsSource: data.getData(100) });

Result (live):

Grouping

FlexGrid supports grouping through the ICollectionView interface, which is identical to the one in .NET. To enable grouping, add one or more GroupDescription objects to the CollectionView.groupDescriptions property, and ensure that the grid's showGroups property is set to true (the default value).

GroupDescription objects are flexible, allowing you to group data based on value or on grouping functions. The example below groups dates by year; amounts by range returning three ranges: over 5,000, 500 to 5,000, and under 500; and anything else by value. Use the menu to see the effects of each grouping.

Notice that the "Revenue" column displays the totals in the group rows. We do this by setting the column's aggregate property to "Sum." The aggregate is automatically updated when you edit the values in the column.

<div id="gFlexGrid"></div> <select id="gMenu"> <option value="" selected>(no grouping)</option> <option value="country">Country</option> <option value="amount">Revenue</option> <option value="date">Date</option> <option value="country,date">Country and Date</option> <option value="country,amount">Country and Revenue</option> <option value="country,date,amount">Country, Date, and Revenue</option> </select>
// initialize grid and menu var grid = new wijmo.grid.FlexGrid('#gFlexGrid'), menu = new wijmo.input.Menu('#gMenu'), cv = new wijmo.collections.CollectionView(data.getData(100)); grid.initialize({ autoGenerateColumns: false, columns: [ { header: 'Country', binding: 'country', width: '*' }, { header: 'Date', binding: 'date' }, { header: 'Revenue', binding: 'amount', format: 'n0' } ], itemsSource: cv }); updateMenuHeader(); // handle the Menu control's selectedIndexChanged event menu.itemClicked.addHandler(function (sender) { var groupBy = sender.selectedValue; cv.groupDescriptions.clear(); if (groupBy) { var groupNames = groupBy.split(','); for (var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; if (groupName == 'date') { // group dates by year var groupDesc = new wijmo.collections.PropertyGroupDescription(groupName, function (item, prop) { return item.date.getFullYear(); }); cv.groupDescriptions.push(groupDesc); } else if (groupName == 'amount') { // group amounts in ranges var groupDesc = new wijmo.collections.PropertyGroupDescription(groupName, function (item, prop) { return item.amount >= 5000 ? '> 5,000' : item.amount >= 500 ? '500 to 5,000' : '< 500'; }); cv.groupDescriptions.push(groupDesc); } else { // group everything else by value var groupDesc = new wijmo.collections.PropertyGroupDescription(groupName); cv.groupDescriptions.push(groupDesc); } } } updateMenuHeader(); }); // show currently selected item function updateMenuHeader() { menu.header = '<b>Group By:</b> ' + menu.text; }

Result (live):

Filtering

The FlexGrid supports filtering through the ICollectionView interface, which is identical to the one in .NET. To enable filtering, set the CollectionView.filter property to a function that determines which objects to include in the view.

In this example, we create a filter for the country, and get the filter value from the input control.

<div id="fFlexGrid"></div> <div class="input-group"> <span class="input-group-addon"> <i class="glyphicon glyphicon-filter"></i> </span> <input id="fFilter" type="text" class="form-control" placeholder="Filter by Country..." /> </div>
// create grid, some data var grid = new wijmo.grid.FlexGrid('#fFlexGrid'), cv = new wijmo.collections.CollectionView(data.getData(100)), filterEl = document.getElementById('fFilter'), filterText = ''; // populate the grid with data grid.itemsSource = cv; // update grid when filter changes filterEl.addEventListener('input', function () { filterText = this.value.toLowerCase(); cv.refresh(); }); // CollectionView filter cv.filter = function (item) { return !filterText || item.country.toLowerCase().indexOf(filterText) > -1; };

Result (live):

Paging

The FlexGrid 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 on each page, and provide a UI for navigating the pages.

In this example, we use JavaScript to show 10 items per page. We add navigation buttons, and call IPagedCollectionView methods when a button is clicked. Note that we use the pageIndex and pageCount properties to show the current page and total number of pages.

<div id="pFlexGrid" style="height:auto"></div> <div class="btn-group" id="pPager"> <button type="button" class="btn btn-default" data-action="fast-backward" id="pfb"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" data-action="step-backward" id="psb"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn btn-default" disabled style="width:100px" data-action="none" id="pn"></button> <button type="button" class="btn btn-default" data-action="step-forward" id="psf"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" data-action="fast-forward" id="pff"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div>
// create a CollectionView, set the page size to 10, initialize pager var cv = new wijmo.collections.CollectionView(data.getData(100)), pagerButtons = Array.prototype.slice.call(document.querySelectorAll('#pPager button')); cv.pageSize = 10; // set collectionView's pageSize updatePager(); // show the data in a grid var grid = new wijmo.grid.FlexGrid('#pFlexGrid'); grid.itemsSource = cv; // update pager when user clicks a button pagerButtons.forEach(function(el) { el.addEventListener('click', function () { updatePager(this.getAttribute('data-action')); }); }); // disable/enable buttons and update display text for pager function updatePager(action) { // get buttons by id var display = document.getElementById('pn'), fb = document.getElementById('pfb'), sb = document.getElementById('psb'), sf = document.getElementById('psf'), ff = document.getElementById('pff'), enableBackwards = false, enableForwards = false; // handle pager operation based on button's attribute switch (action) { case 'fast-backward': cv.moveToFirstPage(); break; case 'step-backward': cv.moveToPreviousPage(); break; case 'step-forward': cv.moveToNextPage(); break; case 'fast-forward': cv.moveToLastPage(); break; } // update the pager text display.innerHTML = (cv.pageIndex + 1) + ' / ' + (cv.pageCount); // determine which pager buttons to enable/disable enableBackwards = cv.pageIndex <= 0; enableForwards = cv.pageIndex >= cv.pageCount - 1; // enable/disable pager buttons fb.disabled = enableBackwards; sb.disabled = enableBackwards; sf.disabled = enableForwards; ff.disabled = enableForwards; }

Result (live):

Master-Detail

The ICollectionView interface has built-in support for currency, which enables you to implement master-detail scenarios with FlexGrid. You can refer to the currentItem and use it as a binding source for any elements on the page.

Note that you have to update the details view when the current item changes. To do that, attach a handler to the ICollectionView.currentChanged event and update the details view as needed.

<div id="mdFlexGrid"></div> <dl class="dl-horizontal"> <dt>ID</dt> <dd id="mdCurId"></dd> <dt>Country</dt> <dd id="mdCurCountry"></dd> <dt>Date</dt> <dd id="mdCurDate"></dd> <dt>Revenue</dt> <dd id="mdCurRevenue"></dd> <dt>Active</dt> <dd id="mdCurActive"></dd> </dl>
// create a CollectionView to keep track of selection var cv = new wijmo.collections.CollectionView(data.getData(100)); // initialize details pane updateDetails(); // update details when current item changes cv.currentChanged.addHandler(function (sender, args) { updateDetails(); }); // create a grid to show/edit the data var grid = new wijmo.grid.FlexGrid('#mdFlexGrid', { autoGenerateColumns: false, columns: [ { header: 'Country', binding: 'country', width: '*' }, { header: 'Date', binding: 'date' } ], itemsSource: cv }); // update the details when the CollectionView's currentItem changes function updateDetails() { var item = cv.currentItem; document.getElementById('mdCurId').innerHTML = item.id; document.getElementById('mdCurCountry').innerHTML = item.country; document.getElementById('mdCurDate').innerHTML = wijmo.Globalize.format(item.date, 'd'); document.getElementById('mdCurRevenue').innerHTML = wijmo.Globalize.format(item.amount, 'c'); document.getElementById('mdCurActive').innerHTML = item.active; }

Result (live):

ID
Country
Date
Revenue
Active

Conditional Styling

FlexGrid has an itemFormatter property that gives you complete control over the contents of the cells.

This example uses a JavaScript function to create value ranges that return named colors. We then call this function in the FlexGrid's itemFormatter and pass the cell's data in order to conditionally set the cell's foreground color.

<div id="csFlexGrid"></div>
// create grid, some data var grid = new wijmo.grid.FlexGrid('#csFlexGrid'), cv = new wijmo.collections.CollectionView(data.getData(100)); // initialize grid grid.initialize({ autoGenerateColumns: false, columns: [ { header: 'Country', binding: 'country', width: '*', isContentHtml: true, isReadOnly: true }, { header: 'Date', binding: 'date' }, { header: 'Revenue', binding: 'amount', format: 'n0' }, { header: 'Active', binding: 'active' }, ], itemsSource: cv, itemFormatter: function (panel, r, c, cell) { // we are only interested in regular (scrollable) cells if (wijmo.grid.CellType.Cell === panel.cellType) { // compute the cell color // (for all columns, since cells may be recycled) var color = ''; if (panel.columns[c].binding == 'amount') { var cellData = panel.getCellData(r, c); color = getAmountColor(cellData); } // always set the color cell.style.color = color; } } }); // get the color used to display an amount function getAmountColor(amount) { return amount < 500 ? 'red' : amount < 2500 ? 'black' : 'green'; }

Result (live):

Themes

The appearance of the FlexGrid is defined in CSS. In addition to the default theme, we include about a dozen professionally designed themes that customize the appearance of all Wijmo controls to achieve a consistent, attractive look.

You can customize the appearance of the grid using CSS. To do this, copy CSS rules from the default theme to a new CSS file and modify the style attributes you want to change.

In this example, we add a "custom-flex-grid" class to the grid element and define some CSS rules to create a simple "black and white, no borders" theme for any grids that have the "custom-flex-grid" class.

<div id="tFlexGrid" class="custom-flex-grid"></div>
var grid = new wijmo.grid.FlexGrid('#tFlexGrid'); grid.itemsSource = data.getData(100);
.custom-flex-grid .wj-header.wj-cell { color: #fff; background-color: #000; border-bottom: solid 1px #404040; border-right: solid 1px #404040; font-weight: bold; } .custom-flex-grid .wj-cell { background-color: #fff; border: none; } .custom-flex-grid .wj-alt:not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #fff; } .custom-flex-grid .wj-state-selected { background: #000; color: #fff; } .custom-flex-grid .wj-state-multi-selected { background: #222; color: #fff; }

Result (live):

Trees and Hierarchical Data

In addition to grouping, FlexGrid supports hierarchical data, that is, data with items that have lists of subitems. This type of hierarchical structure is very common, and is usually displayed in a tree-view control.

To use FlexGrid with hierarchical data sources, set the childItemsPath property to the name of the data element that contains the child elements. The grid automatically scans the data and builds the tree for you.

<div id="tvFlexGrid" class="custom-flex-grid"></div>
// create the grid var grid = new wijmo.grid.FlexGrid('#tvFlexGrid'); // populate the grid and set childItemsPath to show data hierarchically grid.childItemsPath = 'items'; // initialize the grid to show hierarchical data grid.initialize({ autoGenerateColumns: false, columns: [ { binding: 'name', width: '*' }, { binding: 'length', width: 80, align: 'center' } ], itemsSource: data.treeData, // hierarchical data childItemsPath: 'items', // set hierarchy path allowResizing: wijmo.grid.AllowResizing.None, // disable resizing headersVisibility: wijmo.grid.HeadersVisibility.None, // hide headers selectionMode: wijmo.grid.SelectionMode.ListBox // use ListBox selection });
.custom-flex-grid .wj-header.wj-cell { color: #fff; background-color: #000; border-bottom: solid 1px #404040; border-right: solid 1px #404040; font-weight: bold; } .custom-flex-grid .wj-cell { background-color: #fff; border: none; } .custom-flex-grid .wj-alt:not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #fff; } .custom-flex-grid .wj-state-selected { background: #000; color: #fff; } .custom-flex-grid .wj-state-multi-selected { background: #222; color: #fff; }

Result (live):

Handling null values

By default, FlexGrid allows you to enter empty values in columns of type string, and will not allow empty/null values in columns of any other type.

You can change this behavior using the isRequired property on grid columns. If you set the isRequired property to false, the grid will allow you to enter empty values in that column, regardless of type. Conversely, if you set the isRequired property to true, the grid will not allow empty values even in string columns.

Setting isRequired to null reverts to the default behavior (nulls allowed only in string columns).

The grid below reverts the default behavior. It sets isRequired to true for the first column and to false for all others. You can delete content that is not required by entering an empty string or simply by pressing the delete key.

<div id="nvGrid"></div>
// create a grid and define the columns new wijmo.grid.FlexGrid('#nvGrid', { autoGenerateColumns: false, itemsSource: data.getData(100), columns: [ { header: 'Country', binding: 'country', width: '*', is-required: true }, { header: 'Date', binding: 'date', is-required: false }, { header: 'Revenue', binding: 'amount', format: 'n0', is-required: false }, { header: 'Active', binding: 'active', is-required: false } ] });

Result (live):