FlexGrid 101 (Vue)

Getting Started

Steps for getting started with FlexGrid in Vue applications:

  1. Add references to Vue, Wijmo, and Wijmo's Vue interop module.
  2. Create a Vue object and give it a host element.
  3. Add FlexGrid controls to Vue's host element and bind them to the data.
  4. Add some CSS to customize the grid'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> <!-- Vue --> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script> <!-- Wijmo --> <link href="http://cdn.wijmo.com/5.latest/styles/wijmo.min.css" rel="stylesheet"/> <script src="http://cdn.wijmo.com/5.latest/controls/wijmo.min.js"></script> <script src="http://cdn.wijmo.com/5.latest/controls/wijmo.grid.min.js"></script> <!-- Wijmo/Vue interop --> <script src="http://cdn.wijmo.com/5.latest/interop/vue/wijmo.vue.min.js"></script> <!-- app scripts and styles --> <link href="styles/app.css" rel="stylesheet" /> <script src="scripts/app.js"></script> </head> <body> <div id="app" class="container"> <wj-flex-grid :items-source="data"> </wj-flex-grid> </div> </body> </html>
onload = function () { // app view var app = new Vue({ el: '#app', data: { data: new wijmo.collections.CollectionView(getData(100)), selectionMode: 'CellRange', ... }, methods: { ... } }); }

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 columns using HTML markup. You can also do this in code, but using markup allows you to have more separation between the controller and the view.

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.

<wj-flex-grid :items-source="data"> <wj-flex-grid-column header="Country" binding="country" width="*"></wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"></wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0"></wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active"></wj-flex-grid-column> </wj-flex-grid>
// no code required

Result (live):

Selection Modes

By default, FlexGrid allows you to select a range of cells with the mouse or keyboard, just like Microsoft 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.

<wj-flex-grid :items-source="data" :selection-mode="selMode"> </wj-flex-grid> <dl class="dl-horizontal"> <dt>SelectionMode</dt> <dd> <wj-combo-box :items-source="selModes" :text.sync="selMode"> </wj-combo-box> </dd> </dl>

Result (live):

SelectionMode

Cell Freezing

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

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

<wj-flex-grid control="frozenFlex" :items-source="data" :frozen-rows="2" :frozen-columns="2"> </wj-flex-grid> <button class="btn btn-default" @click="toggleFreeze"> {​{ frozenFlex.frozenRows == 0 ? 'Freeze' : 'Unfreeze'}} </button>
// Vue application var app = new Vue({ el: '#app', data: { frozenCount: 2, ... }, methods: { toggleFreeze: function () { this.frozenCount = this.frozenCount == 0 ? 2 : 0; } } });

Result (live):

Editing

FlexGrid has built-in support for fast, in-cell editing like you find in Microsoft 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, 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.

<wj-flex-grid :items-source="data"> <wj-flex-grid-column header="ID" binding="id" :width="50" :is-read-only="true"></wj-flex-grid-column> <wj-flex-grid-column header="Country" binding="country" width="*"></wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"></wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0"></wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active"></wj-flex-grid-column> </wj-flex-grid>
// no code required

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 the 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.

<wj-flex-grid :items-source="cvGroup"> <wj-flex-grid-column header="Country" binding="country" width="*"></wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"></wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" aggregate="Sum"></wj-flex-grid-column> </wj-flex-grid> <dl class="dl-horizontal"> <dt>Group By</dt> <dd> <wj-combo-box :items-source="groupBy" display-member-path="header" :selected-index-changed="updateGroups"> </wj-combo-box> </dd> </dl>
// Vue application var app = new Vue({ el: '#app', data: { cvGroup: new wijmo.collections.CollectionView(getData(100)), groupBy: new wijmo.collections.CollectionView([ { value: '', header: '(no grouping)' }, { value: 'country', header: 'Country' }, { value: 'amount', header: 'Revenue' }, { value: 'date', header: 'Date' }, { value: 'country,date', header: 'Country and Date' }, { value: 'country,amount', header: 'Country and Revenue' }, { value: 'country,date,amount', header: 'Country, Date, and Revenue' }, ]), ... }, methods: { updateGroups: function() { var cv = this.cvGroup; cv.groupDescriptions.clear(); if (this.groupBy.currentItem) { var groupNames = this.groupBy.currentItem.value.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); } } } } } });

Result (live):

Group By

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.

<wj-flex-grid :items-source="cvFilter"> </wj-flex-grid> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-filter"> </span> </span> <input type="text" class="form-control" placeholder="filter" v-model="filterText" /> </div>
// Vue application var app = new Vue({ el: '#app', data: { cvFilter: new wijmo.collections.CollectionView(getData(100)), filterText: '', filterRx: null, ... }, methods: { filter: function (item) { return this.filterRx == null || this.filterRx.test(item.country); } }, created: function () { // attach filter function to CollectionView this.cvFilter.filter = this.filter; // when filter text changes, // update filter regex (case-insensitive) and refresh CollectionView this.$watch('filterText', function () { this.filterRx = this.filterText ? new RegExp(this.filterText, 'i') : null; this.cvFilter.refresh(); }); } });

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 in the button click directives. Note that we use the pageIndex and pageCount properties to show the current page and total number of pages.

<wj-flex-grid :items-source="cvPaging" style="height:auto"> </wj-flex-grid> <div class="btn-group"> <button type="button" class="btn btn-default" @click="cvPaging.moveToFirstPage()" :disabled="cvPaging.pageIndex <= 0"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" @click="cvPaging.moveToPreviousPage()" :disabled="cvPaging.pageIndex <= 0"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn btn-default" disabled style="width:100px"> {{cvPaging.pageIndex + 1 | number}} / {{cvPaging.pageCount | number}} </button> <button type="button" class="btn btn-default" @click="cvPaging.moveToNextPage()" :disabled="cvPaging.pageIndex >= cvPaging.pageCount - 1"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" @click="cvPaging.moveToLastPage()" :disabled="cvPaging.pageIndex >= cvPaging.pageCount - 1"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div>
// no code required

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.

The sample below shows details for the item that is currently selected on the grid. Notice how the detail section uses the "wj-filter" filter to format dates and numbers using given format strings, which reflect the current culture.

<wj-flex-grid :items-source="cvFilter" :is-read-only="true"> <wj-flex-grid-column header="Country" binding="country" width="*"></wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"></wj-flex-grid-column> </wj-flex-grid> <dl class="dl-horizontal"> <dt>ID</dt> <dd>{​{ cvFilter.currentItem.id }}</dd> <dt>Country</dt> <dd>{​{ cvFilter.currentItem.country }}</dd> <dt>Date</dt> <dd>{​{ cvFilter.currentItem.date | wj-format 'd' }}</dd> <dt>Revenue</dt> <dd>{​{ cvFilter.currentItem.amount | wj-format 'c' }}</dd> </dl>
// no code required

Result (live):

ID
{{ cvFilter.currentItem.id }}
Country
{{ cvFilter.currentItem.country }}
Date
{{ cvFilter.currentItem.date | wj-format 'd' }}
Revenue
{{ cvFilter.currentItem.amount | wj-format 'c' }}

Custom Cells

FlexGrid has a formatItem event that gives you complete control over the contents of the cells. The event handler can get all the information it needs from the grid, and then modify the cell element accordingly.

The example below uses the formatItem event to add a flag to the contents of the 'Country' column:

<wj-flex-grid :items-source="data" :format-item="formatItem" > </wj-flex-grid>
// Vue application var app = new Vue({ el: '#app', data: { data: new wijmo.collections.CollectionView(getData(100)), ... }, methods: { formatCountry: function (s, e) { var flex = s; if (e.panel == flex.cells && flex.columns[e.col].binding == 'country') { e.cell.innerHTML = wijmo.format( '<img src="resources/{country}.png"> {country}', flex.rows[e.row].dataItem); } } }, });

Result (live):

Conditional Styling

The formatItem event can also be used to provide conditional formatting for cells.

This example has a formatItem event handler that changes the foreground color of the cell element depending on the amount it contains.

<wj-flex-grid :items-source="data" :format-item="formatAmount" > </wj-flex-grid>
// Vue application var app = new Vue({ el: '#app', data: { data: new wijmo.collections.CollectionView(getData(100)), ... }, methods: { formatAmount: function (s, e) { // format cells in the "cells" panel only (not in headers) if (e.panel == s.cells) { // start with default color var color = ''; // customize color based on amount if (s.columns[e.col].binding == 'amount') { var amount = s.rows[e.row].dataItem.amount; color = amount < 500 ? 'darkred' : amount < 2500 ? 'black' : 'darkgreen'; } // always set the color, since cells are recycled e.cell.style.color = color; } } } });

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 the 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 grids that have the "custom-flex-grid" class.

We also customize the appearance of the glyphs used to show the column sorting direction and the outline nodes in grouped grids. To see the custom glyphs, click a column header cell.

<wj-flex-grid class="custom-flex-grid" :items-source="data"> </wj-flex-grid>
/* create a 'custom-flex-grid' theme for the FlexGrid */ .custom-flex-grid .wj-header.wj-cell { background-color: #000; color: #fff; font-weight: bold; border-right: solid 1px #404040; border-bottom: solid 1px #404040; } .custom-flex-grid .wj-cell { border: none; background-color: #fff; } .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: #222222; color: #fff; } /* override the glyphs used to show sorting and grouping */ .custom-flex-grid .wj-glyph-up { background-image:url('../resources/ascending.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; opacity: 1; } .custom-flex-grid .wj-glyph-down { background-image:url('../resources/descending.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; opacity: 1; } .custom-flex-grid .wj-glyph-right { background-image:url('../resources/collapsed.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; } .custom-flex-grid .wj-glyph-down-right { background-image:url('../resources/expanded.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; }

Result (live):

Trees and Hierarchical Data

In addition to grouping, FlexGrid supports hierarchical data, that is the 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.

<wj-flex-grid class="custom-flex-grid" :items-source="treeData" child-items-path="items" allow-resizing="None" selection-mode="ListBox" headers-visibility="None"> <wj-flex-grid-column binding="name" width="*"></wj-flex-grid-column> <wj-flex-grid-column binding="length" :width="80" align="center"></wj-flex-grid-column> </wj-flex-grid>
// Vue application var app = new Vue({ el: '#app', data: { treeData: getTreeData(), ... }, ... }); // generate some hierarchical data function getTreeData() { return [ { name: '\u266B Adriane Simione', items: [ { name: '\u266A Intelligible Sky', items: [ { name: 'Theories', length: '2:02' }, { name: 'Giant Eyes', length: '3:29' }, { name: 'Jovian Moons', length: '1:02' }, { name: 'Open Minds', length: '2:41' }, { name: 'Spacetronic Eyes', length: '3:41' }] } ] }, { name: '\u266B Amy Winehouse', items: [ { name: '\u266A Back to Black', items: [ { name: 'Addicted', length: '1:34' }, { name: 'He Can Only Hold Her', length: '2:22' }, { name: 'Some Unholy War', length: '2:21' }, { name: 'Wake Up Alone', length: '3:43' }, { name: 'Tears Dry On Their Own', length: '1:25' }] }, { name: '\u266A Live in Paradiso', items: [ { name: "You Know That I'm No Good", length: '2:32' }, { name: 'Wake Up Alone', length: '1:04' }, { name: 'Valerie', length: '1:22' }, { name: 'Tears Dry On Their Own', length: '3:15' }, { name: 'Rehab', length: '3:40' }] }] }, ... }

Result (live):

Sorting Trees

By default, sorting a grid that contains hierarchical data only sorts the top-level items. This is because the CollectionView does not know about the data hierarchy, since the childItemsPath property belongs to the grid and not to the underlying CollectionView.

If you do want to sort some or all of the grid's child items, you should handle the grid's sortedColumn event to enumerate the items and perform additional sorting on the child items yourself.

This example shows how to do this, assuming you want the child items sorted in the same order as the top-level items. In this scenario, you can call the sort method on the child items array using the CollectionView's _compareItems method to compare the items. This is the same method that the CollectionView uses internally.

<wj-flex-grid class="custom-flex-grid" :items-source="treeData" child-items-path="items" selection-mode="ListBox" headers-visibility="Column" :sorted-column="sortedColumn"> <wj-flex-grid-column binding="name" width="*"></wj-flex-grid-column> <wj-flex-grid-column binding="length" :width="80" align="center"></wj-flex-grid-column> </wj-flex-grid>
// Vue application var app = new Vue({ el: '#app', data: { treeData: getTreeData(), ... }, methods: { sortedColumn: function (s, e) { // sort top-level items var view = s.collectionView; if (view && s.childItemsPath) { for (var i = 0; i < view.items.length; i++) { sortItem(view.items[i], view, s.childItemsPath); } view.refresh(); } // sort child items function sortItem(item, view, childItemsPath) { var children = item[childItemsPath]; if (children && wijmo.isArray(children)) { children.sort(view._compareItems()); for (var i = 0; i < children.length; i++) { sortItem(children[i], view, childItemsPath); } } } } } });

Result (live):

Handling null values

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

You can change this behavior by 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 the 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 in FlexGrid (null values are 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 the others. You can delete the content that is not required, by entering an empty string or simply by pressing the delete key.

<wj-flex-grid :items-source="data"> <wj-flex-grid-column header="Country" binding="country" width="*" :is-required="true"></wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date" :is-required="false"></wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" :is-required="false"></wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active" :is-required="false"></wj-flex-grid-column> </wj-flex-grid>
// no code required

Result (live):