CollectionView (React)

The CollectionView class is the main data management class used with Wijmo controls. This sample shows how ICollectionView works. It creates a CollectionView object based on an array with 500 items. The CollectionView is configured to show pages with 10 items each by default. The collection is shown in an HTML table that you can filter, sort, and group, using the controls at the top of each column.

This example uses React.

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './app.css'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import * as wijmo from '@grapecity/wijmo'; import * as wjInput from '@grapecity/wijmo.react.input'; import { getData } from './data'; import { CollectionViewPager } from './collection-view-pager'; import { CollectionViewNavigator } from './collection-view-navigator'; class App extends React.Component { constructor(props) { super(props); this._filter = { id: '', country: '', color: '', minAmount: '' }; this.doFilter = () => { if (this._timeOut) { clearTimeout(this._timeOut); } // this._timeOut = setTimeout(() => { this._timeOut = null; this._cv.refresh(); }, 250); }; this._cv = new wijmo.CollectionView(getData(500), { pageSize: 10, filter: this._filterFn.bind(this), newItemCreator: () => { var newItem = getData(1)[0]; newItem.id = -1; return newItem; } }); this._cv.collectionChanged.addHandler(() => { let gl = this._cv.items; if (this._cv.groups && this._cv.groups.length > 0) { gl = []; this._cv.groups.forEach(group => this._addGroup(gl, group)); } this.setState({ groupedList: gl }); }); this._cv.currentChanged.addHandler(() => this.forceUpdate()); this.state = { isEditing: false, groupedList: this._cv.items }; } render() { return <div className="container-fluid"> <div className="row"> <div className="col-md-6"> <h4>Current Item</h4> <dl className="dl-horizontal"> <dt>ID</dt> <dd> <input type="text" className="form-control" value={this._cv.currentItem ? this._cv.currentItem.id : ''} onChange={e => this.updateCurrentItem(e.target, 'id')} disabled={!this.state.isEditing}/> </dd> <dt>Country</dt> <dd> <input type="text" className="form-control" value={this._cv.currentItem ? this._cv.currentItem.country : ''} onChange={e => this.updateCurrentItem(e.target, 'country')} disabled={!this.state.isEditing}/> </dd> <dt>Color</dt> <dd> <input type="text" className="form-control" value={this._cv.currentItem ? this._cv.currentItem.color : ''} onChange={e => this.updateCurrentItem(e.target, 'color')} disabled={!this.state.isEditing}/> </dd> <dt>Amount</dt> <dd> <input type="number" className="form-control" value={this._cv.currentItem ? this._cv.currentItem.amount : ''} onChange={e => this.updateCurrentItem(e.target, 'amount')} disabled={!this.state.isEditing}/> </dd> <dt></dt> <dd> <div className="btn-group data-btn-group"> <button onClick={this.edit.bind(this)} style={{ display: !this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Edit</button> <button onClick={this.add.bind(this)} style={{ display: !this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Add</button> <button onClick={this.deleteItem.bind(this)} style={{ display: !this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Delete</button> <button onClick={this.commit.bind(this)} style={{ display: this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Commit</button> <button onClick={this.cancel.bind(this)} style={{ display: this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Cancel</button> </div> </dd> </dl> </div> <div className="col-md-6"> <h4>Navigation</h4> <dl> <dt>items</dt> <dd> <div id="navigator"></div> </dd> <dt>pages</dt> <dd> <div id="pager"></div> </dd> </dl> <wjInput.Menu header='Page Size' value={this._cv.pageSize} itemClicked={this._pageSizeChanged.bind(this)}> <wjInput.MenuItem value={0}>No Paging</wjInput.MenuItem> <wjInput.MenuItem value={10}>10</wjInput.MenuItem> <wjInput.MenuItem value={15}>15</wjInput.MenuItem> <wjInput.MenuItem value={30}>30</wjInput.MenuItem> <wjInput.MenuItem value={50}>50</wjInput.MenuItem> </wjInput.Menu> </div> </div> <table className="table table-condensed table-bordered"> <thead> <tr className="active"> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='ID' value={this._filter.id} itemClicked={this._idChanged.bind(this)} style={{ display: "block" }}> <wjInput.MenuItem value=''>(All)</wjInput.MenuItem> <wjInput.MenuItem value='odd'>Odd</wjInput.MenuItem> <wjInput.MenuItem value='even'>Even</wjInput.MenuItem> </wjInput.Menu> <button className="btn btn-default" onClick={e => this.toggleSort('id')}>{this.getSort('id')}</button> </div> </th> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='Country' value={this._filter.country} itemClicked={this._countryChanged.bind(this)} style={{ display: "block" }}> <wjInput.MenuItem value="">(All)</wjInput.MenuItem> <wjInput.MenuItem value="US">US</wjInput.MenuItem> <wjInput.MenuItem value="Germany">Germany</wjInput.MenuItem> <wjInput.MenuItem value="UK">UK</wjInput.MenuItem> <wjInput.MenuItem value="Japan">Japan</wjInput.MenuItem> <wjInput.MenuItem value="Italy">Italy</wjInput.MenuItem> <wjInput.MenuItem value="Greece">Greece</wjInput.MenuItem> <wjInput.MenuItem value="France">France</wjInput.MenuItem> </wjInput.Menu> <button className="btn btn-default" onClick={e => this.toggleSort('country')}>{this.getSort('country')}</button> <button className="btn btn-default" onClick={e => this.toggleGroup('country')}>{this.getGroup('country')}</button> </div> </th> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='Color' value={this._filter.color} itemClicked={this._colorChanged.bind(this)} style={{ display: "block" }}> <wjInput.MenuItem value="">(All)</wjInput.MenuItem> <wjInput.MenuItem value="Black">Black</wjInput.MenuItem> <wjInput.MenuItem value="White">White</wjInput.MenuItem> <wjInput.MenuItem value="Red">Red</wjInput.MenuItem> <wjInput.MenuItem value="Green">Green</wjInput.MenuItem> <wjInput.MenuItem value="Blue">Blue</wjInput.MenuItem> <wjInput.MenuItem value="Yellow">Yellow</wjInput.MenuItem> <wjInput.MenuItem value="Brown">Brown</wjInput.MenuItem> <wjInput.MenuItem value="Orange">Orange</wjInput.MenuItem> </wjInput.Menu> <button className="btn btn-default" onClick={e => this.toggleSort('color')}>{this.getSort('color')}</button> <button className="btn btn-default" onClick={e => this.toggleGroup('color')}>{this.getGroup('color')}</button> </div> </th> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='Amount' value={this._filter.minAmount} itemClicked={this._amountChanged.bind(this)} style={{ display: "block" }}> <wjInput.MenuItem value="">(All)</wjInput.MenuItem> <wjInput.MenuItem value={0}>> 0</wjInput.MenuItem> <wjInput.MenuItem value={500}>> 500</wjInput.MenuItem> <wjInput.MenuItem value={1000}>> 1000</wjInput.MenuItem> </wjInput.Menu> <button className="btn btn-default" onClick={e => this.toggleSort('amount')}>{this.getSort('amount')}</button> <button className="btn btn-default" onClick={e => this.toggleGroup('amount')}>{this.getGroup('amount')}</button> </div> </th> </tr> </thead> <tbody> {this.state.groupedList.map((item, index) => { return <tr key={index} className={item == this._cv.currentItem ? 'success' : ''} onClick={e => this.moveCurrentTo(item)}> <td style={{ display: this.isGroup(item) ? '' : 'none' }} colSpan={4} className="active"> <span style={{ display: 'inline-block', width: (item.level * 25) + 'px' }}></span> <b>{item.name}</b> ( items) </td> <td style={{ display: this.isGroup(item) ? 'none' : '' }} className="text-center">{item.id}</td> <td style={{ display: this.isGroup(item) ? 'none' : '' }} className="text-center">{item.country}</td> <td style={{ display: this.isGroup(item) ? 'none' : '' }} className="text-center">{item.color}</td> <td style={{ display: this.isGroup(item) ? 'none' : '' }} className="text-center">{item.amount}</td> </tr>; })} </tbody> </table> </div>; } componentDidMount() { new CollectionViewNavigator("#navigator", this._cv); new CollectionViewPager("#pager", this._cv); //this.setState({ groupedList: this._cv.items }); } _pageSizeChanged(sender) { if (sender.selectedItem) { this._cv.pageSize = sender.selectedValue; } } _idChanged(sender) { if (sender.selectedItem) { this._filter.id = sender.selectedValue; this.doFilter(); } } _countryChanged(sender) { if (sender.selectedItem) { this._filter.country = sender.selectedValue; this.doFilter(); } } _colorChanged(sender) { if (sender.selectedItem) { this._filter.color = sender.selectedValue; this.doFilter(); } } _amountChanged(sender) { if (sender.selectedItem) { this._filter.minAmount = sender.selectedValue; this.doFilter(); } } // IEditableCollectionView commands isEditing() { this.setState({ isEditing: this._cv.isEditingItem || this._cv.isAddingNew }); } edit() { this._cv.editItem(this._cv.currentItem); this.isEditing(); } add() { this._cv.addNew(); this.isEditing(); } deleteItem() { this._cv.remove(this._cv.currentItem); this.isEditing(); } commit() { this._cv.commitEdit(); this._cv.commitNew(); this.isEditing(); } cancel() { this._cv.cancelEdit(); this._cv.cancelNew(); this.isEditing(); } moveCurrentTo(item) { if (!this.state.isEditing && !this.isGroup(item)) { this._cv.moveCurrentTo(item); this.forceUpdate(); } } // sorting getSort(propName) { let sd = this._cv.sortDescriptions; if (sd.length > 0 && sd[0].property == propName) { return sd[0].ascending ? '▲' : '▼'; } return '◇'; } toggleSort(propName) { let sd = this._cv.sortDescriptions, ascending = true; // if (sd.length > 0 && sd[0].property == propName) { ascending = !sd[0].ascending; } // // remove any old sort descriptors and add the new one sd.splice(0, sd.length, new wijmo.SortDescription(propName, ascending)); } // grouping getGroup(propName) { let index = this._findGroup(propName); return index < 0 ? /*'▯' +*/ Array(this._cv.groupDescriptions.length + 2).join('▷') : /*'▮' +*/ Array(index + 2).join('▶'); } toggleGroup(propName) { let gd = this._cv.groupDescriptions, index = this._findGroup(propName); // if (index >= 0) { gd.removeAt(index); } else { if (propName == 'amount') { // when grouping by amount, use ranges instead of specific values gd.push(new wijmo.PropertyGroupDescription(propName, (item) => { if (item.amount > 1000) return 'Large Amounts'; if (item.amount > 100) return 'Medium Amounts'; if (item.amount > 0) return 'Small Amounts'; // return 'Negative Amounts'; })); } else { // group by specific property values gd.push(new wijmo.PropertyGroupDescription(propName)); } } } isGroup(item) { return item instanceof wijmo.CollectionViewGroup; } _addGroup(groupedList, g) { groupedList.push(g); // if (g.isBottomLevel) { g.items.forEach((item) => groupedList.push(item)); } else { g.groups.forEach((group) => this._addGroup(groupedList, group)); } } _findGroup(propName) { let gd = this._cv.groupDescriptions; for (let i = 0; i < gd.length; i++) { let pgd = gd[i]; if (pgd.propertyName == propName) { return i; } } return -1; } // filtering _filterFn(item) { // check each filter parameter let f = this._filter; // if (f) { if ((f.id == 'odd' && item.id % 2 == 0) || (f.id == 'even' && item.id % 2 != 0)) { return false; } // if (f.country && item.country.indexOf(f.country) < 0) { return false; } // if (f.color && item.color.indexOf(f.color) < 0) { return false; } // if ((f.minAmount || f.minAmount === 0) && item.amount < f.minAmount) { return false; } } // // all passed, return true to include the item return true; } updateCurrentItem(target, binding) { this._cv.currentItem[binding] = target.value; this.forceUpdate(); } } ReactDOM.render(<App />, document.getElementById('app'));
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity CollectionView Overview</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html>
.table { margin-bottom: 0px !important; }
// data used to generate random items const _colors = ['Black', 'White', 'Red', 'Green', 'Blue']; const _countries = ['US', 'Germany', 'UK', 'Japan', 'Italy', 'Greece']; // export function getData(count) { let data = []; // // add count items for (let i = 0; i < count; i++) { // constants used to create data items let countryId = Math.floor(Math.random() * _countries.length), colorId = Math.floor(Math.random() * _colors.length); // // add the item to the list data.push({ id: i, country: _countries[countryId], color: _colors[colorId], amount: Math.random() * 10000 - 5000 }); } // return data; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.js', '@grapecity/wijmo': 'npm:@grapecity/wijmo/index.js', '@grapecity/wijmo.input': 'npm:@grapecity/wijmo.input/index.js', '@grapecity/wijmo.styles': 'npm:@grapecity/wijmo.styles', '@grapecity/wijmo.cultures': 'npm:@grapecity/wijmo.cultures', '@grapecity/wijmo.chart': 'npm:@grapecity/wijmo.chart/index.js', '@grapecity/wijmo.chart.analytics': 'npm:@grapecity/wijmo.chart.analytics/index.js', '@grapecity/wijmo.chart.animation': 'npm:@grapecity/wijmo.chart.animation/index.js', '@grapecity/wijmo.chart.annotation': 'npm:@grapecity/wijmo.chart.annotation/index.js', '@grapecity/wijmo.chart.finance': 'npm:@grapecity/wijmo.chart.finance/index.js', '@grapecity/wijmo.chart.finance.analytics': 'npm:@grapecity/wijmo.chart.finance.analytics/index.js', '@grapecity/wijmo.chart.hierarchical': 'npm:@grapecity/wijmo.chart.hierarchical/index.js', '@grapecity/wijmo.chart.interaction': 'npm:@grapecity/wijmo.chart.interaction/index.js', '@grapecity/wijmo.chart.radar': 'npm:@grapecity/wijmo.chart.radar/index.js', '@grapecity/wijmo.chart.render': 'npm:@grapecity/wijmo.chart.render/index.js', '@grapecity/wijmo.gauge': 'npm:@grapecity/wijmo.gauge/index.js', '@grapecity/wijmo.grid': 'npm:@grapecity/wijmo.grid/index.js', '@grapecity/wijmo.grid.detail': 'npm:@grapecity/wijmo.grid.detail/index.js', '@grapecity/wijmo.grid.filter': 'npm:@grapecity/wijmo.grid.filter/index.js', '@grapecity/wijmo.grid.search': 'npm:@grapecity/wijmo.grid.search/index.js', '@grapecity/wijmo.grid.grouppanel': 'npm:@grapecity/wijmo.grid.grouppanel/index.js', '@grapecity/wijmo.grid.multirow': 'npm:@grapecity/wijmo.grid.multirow/index.js', '@grapecity/wijmo.grid.transposed': 'npm:@grapecity/wijmo.grid.transposed/index.js', '@grapecity/wijmo.grid.pdf': 'npm:@grapecity/wijmo.grid.pdf/index.js', '@grapecity/wijmo.grid.sheet': 'npm:@grapecity/wijmo.grid.sheet/index.js', '@grapecity/wijmo.grid.xlsx': 'npm:@grapecity/wijmo.grid.xlsx/index.js', '@grapecity/wijmo.grid.selector': 'npm:@grapecity/wijmo.grid.selector/index.js', '@grapecity/wijmo.grid.cellmaker': 'npm:@grapecity/wijmo.grid.cellmaker/index.js', '@grapecity/wijmo.grid.immutable': 'npm:@grapecity/wijmo.grid.immutable/index.js', '@grapecity/wijmo.nav': 'npm:@grapecity/wijmo.nav/index.js', '@grapecity/wijmo.odata': 'npm:@grapecity/wijmo.odata/index.js', '@grapecity/wijmo.olap': 'npm:@grapecity/wijmo.olap/index.js', '@grapecity/wijmo.pdf': 'npm:@grapecity/wijmo.pdf/index.js', '@grapecity/wijmo.viewer': 'npm:@grapecity/wijmo.viewer/index.js', '@grapecity/wijmo.xlsx': 'npm:@grapecity/wijmo.xlsx/index.js', '@grapecity/wijmo.undo': 'npm:@grapecity/wijmo.undo/index.js', '@grapecity/wijmo.interop.grid': 'npm:@grapecity/wijmo.interop.grid/index.js', "@grapecity/wijmo.react.chart.analytics": "npm:@grapecity/wijmo.react.chart.analytics/index.js", "@grapecity/wijmo.react.chart.animation": "npm:@grapecity/wijmo.react.chart.animation/index.js", "@grapecity/wijmo.react.chart.annotation": "npm:@grapecity/wijmo.react.chart.annotation/index.js", "@grapecity/wijmo.react.chart.finance.analytics": "npm:@grapecity/wijmo.react.chart.finance.analytics/index.js", "@grapecity/wijmo.react.chart.finance": "npm:@grapecity/wijmo.react.chart.finance/index.js", "@grapecity/wijmo.react.chart.hierarchical": "npm:@grapecity/wijmo.react.chart.hierarchical/index.js", "@grapecity/wijmo.react.chart.interaction": "npm:@grapecity/wijmo.react.chart.interaction/index.js", "@grapecity/wijmo.react.chart.radar": "npm:@grapecity/wijmo.react.chart.radar/index.js", "@grapecity/wijmo.react.chart": "npm:@grapecity/wijmo.react.chart/index.js", "@grapecity/wijmo.react.core": "npm:@grapecity/wijmo.react.core/index.js", "@grapecity/wijmo.react.gauge": "npm:@grapecity/wijmo.react.gauge/index.js", "@grapecity/wijmo.react.grid.detail": "npm:@grapecity/wijmo.react.grid.detail/index.js", "@grapecity/wijmo.react.grid.filter": "npm:@grapecity/wijmo.react.grid.filter/index.js", "@grapecity/wijmo.react.grid.grouppanel": "npm:@grapecity/wijmo.react.grid.grouppanel/index.js", '@grapecity/wijmo.react.grid.search': 'npm:@grapecity/wijmo.react.grid.search/index.js', "@grapecity/wijmo.react.grid.multirow": "npm:@grapecity/wijmo.react.grid.multirow/index.js", "@grapecity/wijmo.react.grid.sheet": "npm:@grapecity/wijmo.react.grid.sheet/index.js", '@grapecity/wijmo.react.grid.transposed': 'npm:@grapecity/wijmo.react.grid.transposed/index.js', '@grapecity/wijmo.react.grid.immutable': 'npm:@grapecity/wijmo.react.grid.immutable/index.js', "@grapecity/wijmo.react.grid": "npm:@grapecity/wijmo.react.grid/index.js", "@grapecity/wijmo.react.input": "npm:@grapecity/wijmo.react.input/index.js", "@grapecity/wijmo.react.olap": "npm:@grapecity/wijmo.react.olap/index.js", "@grapecity/wijmo.react.viewer": "npm:@grapecity/wijmo.react.viewer/index.js", "@grapecity/wijmo.react.nav": "npm:@grapecity/wijmo.react.nav/index.js", "@grapecity/wijmo.react.base": "npm:@grapecity/wijmo.react.base/index.js", 'jszip': 'npm:jszip/dist/jszip.js', 'react': 'npm:react/umd/react.production.min.js', 'react-dom': 'npm:react-dom/umd/react-dom.production.min.js', 'redux': 'npm:redux/dist/redux.min.js', 'react-redux': 'npm:react-redux/dist/react-redux.min.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);