CollectionView (React)

The CollectionView class is the main data management class used with Wijmo controls.

This creates a CollectionView object based on an array with 500 items.

The CollectionView is configured to show pages with 10 items each. The collection is shown in an HTML table that you can filter, sort, and group using the controls at the top of each column.

Learn about FlexGrid | CollectionView Documentation | CollectionView API Reference

This example uses React.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import ReactDOM from 'react-dom/client'; import React, { useEffect, useRef, useState } from 'react'; import useEvent from 'react-use-event-hook'; import * as wijmo from '@mescius/wijmo'; import * as wjInput from '@mescius/wijmo.react.input'; import './app.css'; import { getData } from './data'; const filter = { id: '', country: '', color: '', minAmount: '' }; function App() { const [count, setCount] = useState(0); const [cv] = useState(new wijmo.CollectionView(getData(500), { pageSize: 10, newItemCreator: () => { var newItem = getData(1)[0]; newItem.id = -1; return newItem; } })); const [isEditing, setIsEditing] = useState(false); const [groupedList, setGroupedList] = useState(cv.items); const timeOut = useRef(null); useEffect(() => { cv.filter = filterFn; cv.collectionChanged.addHandler(() => { let gl = cv.items; if (cv.groups && cv.groups.length > 0) { gl = []; cv.groups.forEach(group => addGroup(gl, group)); } setGroupedList(gl); }); cv.currentChanged.addHandler(() => { isEditingFn(); forceUpdate(); }); }, []); const forceUpdate = () => { setCount((prevCount) => prevCount + 1); }; const doFilter = () => { if (timeOut.current) { clearTimeout(timeOut.current); } // timeOut.current = setTimeout(() => { timeOut.current = null; cv.refresh(); }, 250); }; const pageSizeChanged = useEvent((sender) => { if (sender.selectedItem) { cv.pageSize = sender.selectedValue; } }); const idChanged = useEvent((sender) => { if (sender.selectedItem) { filter.id = sender.selectedValue; doFilter(); } }); const countryChanged = useEvent((sender) => { if (sender.selectedItem) { filter.country = sender.selectedValue; doFilter(); } }); const colorChanged = useEvent((sender) => { if (sender.selectedItem) { filter.color = sender.selectedValue; doFilter(); } }); const amountChanged = useEvent((sender) => { if (sender.selectedItem) { filter.minAmount = sender.selectedValue; doFilter(); } }); // IEditableCollectionView commands const isEditingFn = () => { setIsEditing(cv.isEditingItem || cv.isAddingNew); }; const edit = () => { cv.editItem(cv.currentItem); isEditingFn(); }; const add = () => { cv.addNew(); isEditingFn(); }; const deleteItem = () => { cv.remove(cv.currentItem); isEditingFn(); }; const commit = () => { cv.commitEdit(); cv.commitNew(); isEditingFn(); }; const cancel = () => { cv.cancelEdit(); cv.cancelNew(); isEditingFn(); }; const moveCurrentTo = (item) => { if (!isEditing && !isGroup(item)) { cv.moveCurrentTo(item); forceUpdate(); } }; // sorting const getSort = (propName) => { let sd = cv.sortDescriptions; if (sd.length > 0 && sd[0].property == propName) { return sd[0].ascending ? '▲' : '▼'; } return '◇'; }; const toggleSort = (propName) => { let sd = 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 const getGroup = (propName) => { let index = findGroup(propName); return index < 0 ? /*'▯' +*/ Array(cv.groupDescriptions.length + 2).join('▷') : /*'▮' +*/ Array(index + 2).join('▶'); }; const toggleGroup = (propName) => { let gd = cv.groupDescriptions, index = 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)); } } }; const isGroup = (item) => { return item instanceof wijmo.CollectionViewGroup; }; const addGroup = (groupedList, g) => { groupedList.push(g); // if (g.isBottomLevel) { g.items.forEach((item) => groupedList.push(item)); } else { g.groups.forEach((group) => addGroup(groupedList, group)); } }; const findGroup = (propName) => { let gd = cv.groupDescriptions; for (let i = 0; i < gd.length; i++) { let pgd = gd[i]; if (pgd.propertyName == propName) { return i; } } return -1; }; // filtering const filterFn = (item) => { // check each filter parameter let f = 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; }; const updateCurrentItem = (target, binding) => { cv.currentItem[binding] = target.value; forceUpdate(); }; 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={cv.currentItem ? cv.currentItem.id : ''} onChange={e => updateCurrentItem(e.target, 'id')} disabled={!isEditing}/> </dd> <dt>Country</dt> <dd> <input type="text" className="form-control" value={cv.currentItem ? cv.currentItem.country : ''} onChange={e => updateCurrentItem(e.target, 'country')} disabled={!isEditing}/> </dd> <dt>Color</dt> <dd> <input type="text" className="form-control" value={cv.currentItem ? cv.currentItem.color : ''} onChange={e => updateCurrentItem(e.target, 'color')} disabled={!isEditing}/> </dd> <dt>Amount</dt> <dd> <input type="number" className="form-control" value={cv.currentItem ? cv.currentItem.amount : ''} onChange={e => updateCurrentItem(e.target, 'amount')} disabled={!isEditing}/> </dd> <dt></dt> <dd> <div className="btn-group data-btn-group"> <button onClick={edit} style={{ display: !isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Edit</button> <button onClick={add} style={{ display: !isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Add</button> <button onClick={deleteItem} style={{ display: !isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Delete</button> <button onClick={commit} style={{ display: isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Commit</button> <button onClick={cancel} style={{ display: 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> <wjInput.CollectionViewNavigator cv={cv}></wjInput.CollectionViewNavigator> </dd> <dt>pages</dt> <dd> <wjInput.CollectionViewNavigator cv={cv} byPage></wjInput.CollectionViewNavigator> </dd> </dl> <wjInput.Menu header='Page Size' value={cv.pageSize} itemClicked={pageSizeChanged}> <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={filter.id} itemClicked={idChanged} 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 => toggleSort('id')}>{getSort('id')}</button> </div> </th> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='Country' value={filter.country} itemClicked={countryChanged} 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 => toggleSort('country')}>{getSort('country')}</button> <button className="btn btn-default" onClick={e => toggleGroup('country')}>{getGroup('country')}</button> </div> </th> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='Color' value={filter.color} itemClicked={colorChanged} 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 => toggleSort('color')}>{getSort('color')}</button> <button className="btn btn-default" onClick={e => toggleGroup('color')}>{getGroup('color')}</button> </div> </th> <th className="text-center"> <div className="btn-group"> <wjInput.Menu header='Amount' value={filter.minAmount} itemClicked={amountChanged} 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 => toggleSort('amount')}>{getSort('amount')}</button> <button className="btn btn-default" onClick={e => toggleGroup('amount')}>{getGroup('amount')}</button> </div> </th> </tr> </thead> <tbody> {groupedList.map((item, index) => { return <tr key={index} className={item == cv.currentItem ? 'success' : ''} onClick={e => moveCurrentTo(item)}> <td style={{ display: 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: isGroup(item) ? 'none' : '' }} className="text-center">{item.id}</td> <td style={{ display: isGroup(item) ? 'none' : '' }} className="text-center">{item.country}</td> <td style={{ display: isGroup(item) ? 'none' : '' }} className="text-center">{item.color}</td> <td style={{ display: isGroup(item) ? 'none' : '' }} className="text-center">{item.amount}</td> </tr>; })} </tbody> </table> </div>); } const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo CollectionView Overview</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.40/system.src.js" integrity="sha512-G6mEj6h18+m3MvzdviSDfPle/TfH0//cXcB33AKlNR/Rha0yQsKefDZKRTkIZos97HEGq2JMV1RT5ybMoQ3WsQ==" crossorigin="anonymous"></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; }
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', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.grid.immutable': 'npm:@mescius/wijmo.grid.immutable/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.react.chart.analytics": "npm:@mescius/wijmo.react.chart.analytics/index.js", "@mescius/wijmo.react.chart.animation": "npm:@mescius/wijmo.react.chart.animation/index.js", "@mescius/wijmo.react.chart.annotation": "npm:@mescius/wijmo.react.chart.annotation/index.js", "@mescius/wijmo.react.chart.finance.analytics": "npm:@mescius/wijmo.react.chart.finance.analytics/index.js", "@mescius/wijmo.react.chart.finance": "npm:@mescius/wijmo.react.chart.finance/index.js", "@mescius/wijmo.react.chart.hierarchical": "npm:@mescius/wijmo.react.chart.hierarchical/index.js", "@mescius/wijmo.react.chart.interaction": "npm:@mescius/wijmo.react.chart.interaction/index.js", "@mescius/wijmo.react.chart.radar": "npm:@mescius/wijmo.react.chart.radar/index.js", "@mescius/wijmo.react.chart": "npm:@mescius/wijmo.react.chart/index.js", "@mescius/wijmo.react.core": "npm:@mescius/wijmo.react.core/index.js", '@mescius/wijmo.react.chart.map': 'npm:@mescius/wijmo.react.chart.map/index.js', "@mescius/wijmo.react.gauge": "npm:@mescius/wijmo.react.gauge/index.js", "@mescius/wijmo.react.grid.detail": "npm:@mescius/wijmo.react.grid.detail/index.js", "@mescius/wijmo.react.grid.filter": "npm:@mescius/wijmo.react.grid.filter/index.js", "@mescius/wijmo.react.grid.grouppanel": "npm:@mescius/wijmo.react.grid.grouppanel/index.js", '@mescius/wijmo.react.grid.search': 'npm:@mescius/wijmo.react.grid.search/index.js', "@mescius/wijmo.react.grid.multirow": "npm:@mescius/wijmo.react.grid.multirow/index.js", "@mescius/wijmo.react.grid.sheet": "npm:@mescius/wijmo.react.grid.sheet/index.js", '@mescius/wijmo.react.grid.transposed': 'npm:@mescius/wijmo.react.grid.transposed/index.js', '@mescius/wijmo.react.grid.transposedmultirow': 'npm:@mescius/wijmo.react.grid.transposedmultirow/index.js', '@mescius/wijmo.react.grid.immutable': 'npm:@mescius/wijmo.react.grid.immutable/index.js', "@mescius/wijmo.react.grid": "npm:@mescius/wijmo.react.grid/index.js", "@mescius/wijmo.react.input": "npm:@mescius/wijmo.react.input/index.js", "@mescius/wijmo.react.olap": "npm:@mescius/wijmo.react.olap/index.js", "@mescius/wijmo.react.viewer": "npm:@mescius/wijmo.react.viewer/index.js", "@mescius/wijmo.react.nav": "npm:@mescius/wijmo.react.nav/index.js", "@mescius/wijmo.react.base": "npm:@mescius/wijmo.react.base/index.js", '@mescius/wijmo.react.barcode.common': 'npm:@mescius/wijmo.react.barcode.common/index.js', '@mescius/wijmo.react.barcode.composite': 'npm:@mescius/wijmo.react.barcode.composite/index.js', '@mescius/wijmo.react.barcode.specialized': 'npm:@mescius/wijmo.react.barcode.specialized/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', 'react-dom/client': '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', "react-use-event-hook": "npm:react-use-event-hook/dist/esm/useEvent.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);