概览

CollectionView 类是用于wijmo控件的主要数据管理类。此示例显示ICollectionView的工作方式。它基于包含500个项的数组创建一个CollectionView对象。CollectionView 配置为默认情况下显示包含10个项目的页面。该集合显示在HTML表中,您可以使用每列顶部的控件对其进行筛选、排序和分组。

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wijmo from '@grapecity/wijmo'; import * as input from '@grapecity/wijmo.input'; import { getData } from './data'; import { TableView } from './table-view'; import { CollectionViewPager } from './collection-view-pager'; import { CollectionViewNavigator } from './collection-view-navigator'; import { ItemEditor } from './item-editor'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // create a CollectionView let view = new wijmo.CollectionView(getData(500), { pageSize: 10, newItemCreator: () => { let newItem = getData(1)[0]; newItem.id = -1; return newItem; } }); // new TableView('#tableView', view, { amount: 'n2' }); new CollectionViewNavigator("#navigator", view); new CollectionViewPager("#pager", view); new ItemEditor('#editor', view); // let menu = new input.Menu("#pageSizeMenu", { itemsSource: [ { value: 0, text: 'No Paging' }, { value: 10, text: '10' }, { value: 15, text: '15' }, { value: 30, text: '30' }, { value: 50, text: '50' } ], displayMemberPath: 'text', selectedValuePath: 'value', selectedValue: view.pageSize, selectedIndexChanged: (sender) => { if (sender.selectedIndex >= 0) { updateMenuHeader(sender); view.pageSize = sender.selectedValue; } } }); updateMenuHeader(menu); // function updateMenuHeader(menu) { if (menu.selectedIndex >= 0) { menu.header = `Page Size: <b>${menu.selectedItem.text}</b>`; } } } <!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 class="container-fluid"> <div id="menu"></div> <div class="row"> <div class="col-md-6"> <h4>Current Item</h4> <div id="editor"></div> </div> <div class="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> <div id="pageSizeMenu"></div> </div> </div> <div id="tableView"></div> </div> </body> </html> // export function getData(count) { let countries = ['US', 'Germany', 'UK', 'Japan', 'Italy', 'Greece'], colors = ['Black', 'White', 'Red', 'Green', 'Blue'], 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); // // create the item let item = { id: i, country: countries[countryId], color: colors[colorId], amount: Math.random() * 10000 - 5000 }; // // add the item to the list data.push(item); } // return data; } .table { margin-bottom: 0px !important; } import * as wijmo from '@grapecity/wijmo'; export class CollectionViewNavigatorBase extends wijmo.Control { // constructor(element, view) { wijmo.assert(!!view, 'view must not be null.'); // super(element); // this.applyTemplate('wj-control wj-content wj-pager', this.getTemplate(), { _first: 'first', _prev: 'prev', _next: 'next', _last: 'last', _input: 'input' }); // this._first.addEventListener('click', () => this._onFirstClick()); this._prev.addEventListener('click', () => this._onPrevClick()); this._next.addEventListener('click', () => this._onNextClick()); this._last.addEventListener('click', () => this._onLastClick()); // this._view = view; this._view.collectionChanged.addHandler(() => this.invalidate()); this.invalidate(); } // get view() { return this._view; } } CollectionViewNavigatorBase.controlTemplate = `<div class="wj-input-group"> <span class="wj-input-group-btn" > <button class="wj-btn wj-btn-default" type="button" wj-part="first"> <span class="wj-glyph-left" style="margin-right: -4px;"></span> <span class="wj-glyph-left"></span> </button> </span> <span class="wj-input-group-btn" > <button class="wj-btn wj-btn-default" type="button" wj-part="prev"> <span class="wj-glyph-left"></span> </button> </span> <input type="text" class="wj-form-control" wj-part="input" disabled /> <span class="wj-input-group-btn" > <button class="wj-btn wj-btn-default" type="button" wj-part="next"> <span class="wj-glyph-right"></span> </button> </span> <span class="wj-input-group-btn" > <button class="wj-btn wj-btn-default" type="button" wj-part="last"> <span class="wj-glyph-right"></span> <span class="wj-glyph-right" style="margin-left: -4px;"></span> </button> </span> </div>`; import { CollectionViewNavigatorBase } from './collection-view-navigator-base'; // export class CollectionViewNavigator extends CollectionViewNavigatorBase { constructor(element, view) { super(element, view); this.view.currentChanged.addHandler(() => this.invalidate()); } // refresh() { super.refresh(); this._first.disabled = this._prev.disabled = this.view.currentPosition <= 0; this._next.disabled = this._last.disabled = this.view.currentPosition >= this.view.itemCount - 1; this._input.value = `${this.view.currentPosition + 1} / ${this.view.itemCount}`; } // _onFirstClick() { this.view.moveCurrentToFirst(); } // _onPrevClick() { this.view.moveCurrentToPrevious(); } // _onNextClick() { this.view.moveCurrentToNext(); } // _onLastClick() { this.view.moveCurrentToLast(); } } import { CollectionViewNavigatorBase } from './collection-view-navigator-base'; // export class CollectionViewPager extends CollectionViewNavigatorBase { constructor(element, view) { super(element, view); this.view.pageChanged.addHandler(() => this.invalidate()); } // refresh() { super.refresh(); this._first.disabled = this._prev.disabled = this.view.pageIndex <= 0; this._next.disabled = this._last.disabled = this.view.pageIndex >= this.view.pageCount - 1; this._input.value = `${this.view.pageIndex + 1} / ${this.view.pageCount}`; } // _onFirstClick() { this.view.moveToFirstPage(); } // _onPrevClick() { this.view.moveToPreviousPage(); } // _onNextClick() { this.view.moveToNextPage(); } // _onLastClick() { this.view.moveToLastPage(); } } import * as wijmo from '@grapecity/wijmo'; // export class ItemEditor extends wijmo.Control { // constructor(element, view) { wijmo.assert(!!view, 'view must not be null.'); // super(element); // this.applyTemplate('', this.getTemplate(), { _tbId: 'id', _tbCountry: 'country', _tbColor: 'color', _tbAmount: 'amount', _btnEdit: 'edit', _btnAdd: 'add', _btnDelete: 'delete', _btnCommit: 'commit', _btnCancel: 'cancel' }); // this._btnEdit.addEventListener('click', () => this._edit()); this._btnAdd.addEventListener('click', () => this._add()); this._btnDelete.addEventListener('click', () => this._remove()); this._btnCommit.addEventListener('click', () => this._commit()); this._btnCancel.addEventListener('click', () => this._cancel()); // this._view = view; this._view.currentChanged.addHandler(() => this.invalidate()); // this.invalidate(); } refresh() { super.refresh(); // let cur = this._view.currentItem, edit = this._isEditing(); // this._tbId.disabled = !edit; this._tbCountry.disabled = !edit; this._tbColor.disabled = !edit; this._tbAmount.disabled = !edit; // this._tbId.value = cur ? cur.id.toString() : ''; this._tbCountry.value = cur ? cur.country.toString() : ''; this._tbColor.value = cur ? cur.color.toString() : ''; this._tbAmount.value = cur ? cur.amount.toString() : ''; // let display = edit ? 'none' : ''; this._btnEdit.style.display = display; this._btnAdd.style.display = display; this._btnDelete.style.display = display; // display = edit ? '' : 'none'; this._btnCommit.style.display = display; this._btnCancel.style.display = display; } // _isEditing() { return this._view.isEditingItem || this._view.isAddingNew; } // _edit() { this._view.editItem(this._view.currentItem); this.invalidate(); } // _add() { this._view.addNew(); } // _remove() { this._view.remove(this._view.currentItem); } // _commit() { let cur = this._view.currentItem; // cur.id = this._tbId.value; cur.country = this._tbCountry.value; cur.color = this._tbColor.value; cur.amount = this._tbAmount.value; // this._view.commitEdit(); this._view.commitNew(); this.invalidate(); } // _cancel() { this._view.cancelEdit(); this._view.cancelNew(); this.invalidate(); } } ItemEditor.controlTemplate = `<dl class="dl-horizontal"> <dt>ID</dt> <dd> <input type="text" class="form-control" wj-part="id" /> </dd> <dt>Country</dt> <dd> <input type="text" class="form-control" wj-part="country" /> </dd> <dt>Color</dt> <dd> <input type="text" class="form-control" wj-part="color" /> </dd> <dt>Amount</dt> <dd> <input type="number" class="form-control" wj-part="amount" /> </dd> <dt></dt> <dd> <div class="btn-group data-btn-group"> <button class="btn btn-default btn-sm" wj-part="edit">Edit</button> <button class="btn btn-default btn-sm" wj-part="add">Add</button> <button class="btn btn-default btn-sm" wj-part="delete">Delete</button> <button class="btn btn-default btn-sm" wj-part="commit">Commit</button> <button class="btn btn-default btn-sm" wj-part="cancel">Cancel</button> </div> </dd> </dl>`; import * as wijmo from '@grapecity/wijmo'; // export class TableView extends wijmo.Control { // constructor(selector, view, format) { super(selector); // this.applyTemplate('table-responsive', this.getTemplate(), { _table: 'table' }); this._format = format || {}; // this._view = view; this._view.collectionChanged.addHandler(() => this.invalidate()); this._view.currentChanged.addHandler(() => this.invalidate()); // this.invalidate(); } // refresh() { this._table.innerHTML = ''; // // create header if (this._view.itemCount > 0) { let item = this._view.items[0], head = this._table.createTHead(), row = head.insertRow(); // row.className = 'active'; Object.keys(item).forEach(key => { let cell = document.createElement('th'); cell.className = 'text-center'; // // header text let txt = document.createTextNode(wijmo.toHeaderCase(key) + '\u00A0'); // + &nbsp; cell.appendChild(txt); // // sort button let btn = document.createElement('button'); btn.className = 'btn btn-default'; btn.textContent = this._getSort(key); btn.addEventListener('click', () => this._toggleSort(key)); cell.appendChild(btn); // row.appendChild(cell); }); } // // create body let body = this._table.createTBody(); this._view.items.forEach(item => { let row = body.insertRow(); // if (item == this._view.currentItem) { row.className = 'success'; } // row.addEventListener('click', () => this._moveCurrentTo(row, item)); // Object.keys(item).forEach(key => { let cell = row.insertCell(), fmt = this._format[key], val = item[key]; // cell.textContent = fmt ? wijmo.format(`{val:${fmt}}`, { val: val }) : val; cell.className = 'text-center'; }); }); } // _moveCurrentTo(row, item) { if (!this._isEditingView && !this._isGroupRow(item)) { this._view.moveCurrentTo(item); } } // get _isEditingView() { return this._view.isEditingItem || this._view.isAddingNew; } // _isGroupRow(item) { return item instanceof wijmo.CollectionViewGroup; } // _getSort(field) { let sd = this._view.sortDescriptions; // if (sd.length > 0 && sd[0].property === field) { return sd[0].ascending ? '▲' : '▼'; } // return '◇'; } // _toggleSort(field) { let sd = this._view.sortDescriptions, ascending = true; // if (sd.length > 0 && sd[0].property === field) { ascending = !sd[0].ascending; } // // remove any old sort descriptors and add the new one sd.splice(0, sd.length, new wijmo.SortDescription(field, ascending)); } } TableView.controlTemplate = '<table class="table table-condensed table-bordered" wj-part="table"></table>'; import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wijmo from '@grapecity/wijmo'; // import { Component, Inject, enableProdMode, NgModule } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { WjInputModule } from '@grapecity/wijmo.angular2.input'; import { DataService, TDataItem } from './app.data'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { private _timeOut: number; // cv: wijmo.CollectionView; groupedList: any[]; filter: { id?: string, country?: string, color?: string, minAmount?: number | string }; // constructor(@Inject(DataService) private dataService: DataService) { this.cv = new wijmo.CollectionView(dataService.getData(500), { pageSize: 10, filter: this._filterFun.bind(this), newItemCreator: () => { var newItem = dataService.getData(1)[0]; newItem.id = -1; return newItem; } }); // this.groupedList = this.cv.items; // this.cv.collectionChanged.addHandler(() => { this.groupedList = this.cv.items; if (this.cv.groups && this.cv.groups.length > 0) { this.groupedList = []; this.cv.groups.forEach(group => this._addGroup(group)); } }); // this.filter = { id: '', country: '', color: '', minAmount: '' }; } // doFilter() { if (this._timeOut) { clearTimeout(this._timeOut); } // this._timeOut = setTimeout(() => { this._timeOut = null; this.cv.refresh(); }, 250); } // // IEditableCollectionView commands isEditing() { return this.cv.isEditingItem || this.cv.isAddingNew; } // edit() { this.cv.editItem(this.cv.currentItem); } // add() { this.cv.addNew(); } // delete() { this.cv.remove(this.cv.currentItem); } // commit() { this.cv.commitEdit(); this.cv.commitNew(); } // cancel() { this.cv.cancelEdit(); this.cv.cancelNew(); } // moveCurrentTo(item: any) { if (!this.isEditing() && !this.isGroup(item)) { this.cv.moveCurrentTo(item); } } // // sorting getSort(propName: string) { let sd = this.cv.sortDescriptions; if (sd.length > 0 && sd[0].property == propName) { return sd[0].ascending ? '▲' : '▼'; } return '◇'; } // toggleSort(propName: string) { 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: string) { let index = this._findGroup(propName); return index < 0 ? /*'▯' +*/ Array(this.cv.groupDescriptions.length + 2).join('▷') : /*'▮' +*/ Array(index + 2).join('▶'); } // toggleGroup(propName: string) { 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: TDataItem) => { 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: any) { return item instanceof wijmo.CollectionViewGroup; } // private _addGroup(g: wijmo.CollectionViewGroup) { this.groupedList.push(g); // if (g.isBottomLevel) { g.items.forEach(item => this.groupedList.push(item)); } else { g.groups.forEach(group => this._addGroup(group)); } } // private _findGroup(propName: string) { let gd = this.cv.groupDescriptions; // for (let i = 0; i < gd.length; i++) { if (gd[i].propertyName == propName) { return i; } } // return -1; } // // filtering private _filterFun(item: TDataItem) { // 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; } } // @NgModule({ imports: [WjInputModule, FormsModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule); <!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"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html> <div class="container-fluid"> <div class="row"> <div class="col-md-6"> <h4>Current Item</h4> <dl class="dl-horizontal"> <dt>ID</dt> <dd> <input type="text" class="form-control" [ngModel]="cv.currentItem?.id" (ngModelChange)="cv.currentItem.id=$event" [disabled]="!isEditing()" /> </dd> <dt>Country</dt> <dd> <input type="text" class="form-control" [ngModel]="cv.currentItem?.country" (ngModelChange)="cv.currentItem.country=$event" [disabled]="!isEditing()" /> </dd> <dt>Color</dt> <dd> <input type="text" class="form-control" [ngModel]="cv.currentItem?.color" (ngModelChange)="cv.currentItem.color=$event" [disabled]="!isEditing()" /> </dd> <dt>Amount</dt> <dd> <input type="number" class="form-control" [ngModel]="cv.currentItem?.amount" (ngModelChange)="cv.currentItem.amount=$event" [disabled]="!isEditing()" /> </dd> <dt></dt> <dd> <div class="btn-group data-btn-group"> <button (click)="edit()" [ngStyle]="{display: !isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Edit</button> <button (click)="add()" [ngStyle]="{display: !isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Add</button> <button (click)="delete()" [ngStyle]="{display: !isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Delete</button> <button (click)="commit()" [ngStyle]="{display: isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Commit</button> <button (click)="cancel()" [ngStyle]="{display: isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Cancel</button> </div> </dd> </dl> </div> <div class="col-md-6"> <h4>Navigation</h4> <dl> <dt>items</dt> <dd> <wj-collection-view-navigator [cv]="cv"></wj-collection-view-navigator> </dd> <dt>pages</dt> <dd> <wj-collection-view-pager [cv]="cv"></wj-collection-view-pager> </dd> </dl> <wj-menu [(value)]="cv.pageSize" [header]="'Page Size'"> <wj-menu-item [value]="0">No Paging</wj-menu-item> <wj-menu-item [value]="10">10</wj-menu-item> <wj-menu-item [value]="15">15</wj-menu-item> <wj-menu-item [value]="30">30</wj-menu-item> <wj-menu-item [value]="50">50</wj-menu-item> </wj-menu> </div> </div> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center"> <div class="btn-group"> <wj-menu [(value)]="filter.id" [header]="'ID'" (itemClicked)="doFilter()" style="display:block"> <wj-menu-item [value]="''">(All)</wj-menu-item> <wj-menu-item [value]="'odd'">Odd</wj-menu-item> <wj-menu-item [value]="'even'">Even</wj-menu-item> </wj-menu> <button class="btn btn-default" (click)="toggleSort('id')">{{getSort('id')}}</button> </div> </th> <th class="text-center"> <div class="btn-group"> <wj-menu [(value)]="filter.country" [header]="'Country'" (itemClicked)="doFilter()" style="display:block"> <wj-menu-item [value]="''">(All)</wj-menu-item> <wj-menu-item [value]="'US'">US</wj-menu-item> <wj-menu-item [value]="'Germany'">Germany</wj-menu-item> <wj-menu-item [value]="'UK'">UK</wj-menu-item> <wj-menu-item [value]="'Japan'">Japan</wj-menu-item> <wj-menu-item [value]="'Italy'">Italy</wj-menu-item> <wj-menu-item [value]="'Greece'">Greece</wj-menu-item> <wj-menu-item [value]="'France'">France</wj-menu-item> </wj-menu> <button class="btn btn-default" (click)="toggleSort('country')">{{getSort('country')}}</button> <button class="btn btn-default" (click)="toggleGroup('country')">{{getGroup('country')}}</button> </div> </th> <th class="text-center"> <div class="btn-group"> <wj-menu [(value)]="filter.color" [header]="'Color'" (itemClicked)="doFilter()" style="display:block"> <wj-menu-item [value]="''">(All)</wj-menu-item> <wj-menu-item [value]="'Black'">Black</wj-menu-item> <wj-menu-item [value]="'White'">White</wj-menu-item> <wj-menu-item [value]="'Red'">Red</wj-menu-item> <wj-menu-item [value]="'Green'">Green</wj-menu-item> <wj-menu-item [value]="'Blue'">Blue</wj-menu-item> <wj-menu-item [value]="'Yellow'">Yellow</wj-menu-item> <wj-menu-item [value]="'Brown'">Brown</wj-menu-item> <wj-menu-item [value]="'Orange'">Orange</wj-menu-item> </wj-menu> <button class="btn btn-default" (click)="toggleSort('color')">{{getSort('color')}}</button> <button class="btn btn-default" (click)="toggleGroup('color')">{{getGroup('color')}}</button> </div> </th> <th class="text-center"> <div class="btn-group"> <wj-menu [(value)]="filter.minAmount" [header]="'Amount'" (itemClicked)="doFilter()" style="display:block"> <wj-menu-item [value]="''">(All)</wj-menu-item> <wj-menu-item [value]="0">&gt; 0</wj-menu-item> <wj-menu-item [value]="500">&gt; 500</wj-menu-item> <wj-menu-item [value]="1000">&gt; 1,000</wj-menu-item> </wj-menu> <button class="btn btn-default" (click)="toggleSort('amount')">{{getSort('amount')}}</button> <button class="btn btn-default" (click)="toggleGroup('amount')">{{getGroup('amount')}}</button> </div> </th> </tr> </thead> <tbody> <tr *ngFor="let item of groupedList" [ngClass]="{success: item == cv.currentItem}" (click)="moveCurrentTo(item)"> <!-- group row --> <td [ngStyle]="{display:isGroup(item)?'':'none'}" colspan="4" class="active"> <span [ngStyle]="{display:'inline-block', width: (item.level * 25) + 'px'}"></span> <b>{{item.name}}</b> ({{item.items?.length}} items) </td> <!-- data row --> <td [ngStyle]="{display:isGroup(item)?'none':''}" class="text-center">{{item.id}}</td> <td [ngStyle]="{display:isGroup(item)?'none':''}" class="text-center">{{item.country}}</td> <td [ngStyle]="{display:isGroup(item)?'none':''}" class="text-center">{{item.color}}</td> <td [ngStyle]="{display:isGroup(item)?'none':''}" class="text-center">{{item.amount | number:'1.2-2'}} </td> </tr> </tbody> </table> </div> import { Injectable } from '@angular/core'; // export type TDataItem = { id: number; country: string; color: string; amount: number; } // @Injectable() export class DataService { // data used to generate random items private _colors = ['Black', 'White', 'Red', 'Green', 'Blue']; private _countries = ['US', 'Germany', 'UK', 'Japan', 'Italy', 'Greece']; // getData(count: number) { let data: TDataItem[] = []; // // add count items for (let i = 0; i < count; i++) { // constants used to create data items let countryId = Math.floor(Math.random() * this._countries.length), colorId = Math.floor(Math.random() * this._colors.length); // // add the item to the list data.push({ id: i, country: this._countries[countryId], color: this._colors[colorId], amount: Math.random() * 10000 - 5000 }); } // return data; } } .table { margin-bottom: 0px !important; } <template> <div class="container-fluid"> <div class="row"> <div class="col-md-6"> <h4>Current Item</h4> <dl class="dl-horizontal"> <dt>ID</dt> <dd> <input type="text" class="form-control" v-model="cv.currentItem.id" :disabled="!isEditing()" /> </dd> <dt>Country</dt> <dd> <input type="text" class="form-control" v-model="cv.currentItem.country" :disabled="!isEditing()" /> </dd> <dt>Color</dt> <dd> <input type="text" class="form-control" v-model="cv.currentItem.color" :disabled="!isEditing()" /> </dd> <dt>Amount</dt> <dd> <input type="number" class="form-control" v-model="cv.currentItem.amount" :disabled="!isEditing()" /> </dd> <dt></dt> <dd> <div class="btn-group data-btn-group"> <button @click="edit()" v-bind:style="{display: !isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Edit</button> <button @click="add()" v-bind:style="{display: !isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Add</button> <button @click="deleteItem()" v-bind:style="{display: !isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Delete</button> <button @click="commit()" v-bind:style="{display: isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Commit</button> <button @click="cancel()" v-bind:style="{display: isEditing() ? '' : 'none'}" class="btn btn-default btn-sm">Cancel</button> </div> </dd> </dl> </div> <div class="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> <wj-menu :header="pageHeader" displayMemberPath="header" selectedValuePath="value" :itemsSource="pageSource" :itemClicked="selectionPageChanged"></wj-menu> </div> </div> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center"> <div class="btn-group"> <wj-menu :header="filterHeader" displayMemberPath="header" selectedValuePath="value" :itemClicked="selectionIDChanged" style="display:block" :itemsSource="filterSource"></wj-menu> <button class="btn btn-default" @click="toggleSort('id')">{{getSort('id')}}</button> </div> </th> <th class="text-center"> <div class="btn-group"> <wj-menu :header="countryHeader" :itemClicked="selectionCountryChanged" displayMemberPath="header" selectedValuePath="value" style="display:block" :itemsSource="countrySource"></wj-menu> <button class="btn btn-default" @click="toggleSort('country')">{{getSort('country')}}</button> <button class="btn btn-default" @click="toggleGroup('country')">{{getGroup('country')}}</button> </div> </th> <th class="text-center"> <div class="btn-group"> <wj-menu :header="colorHeader" :itemClicked="selectionColorChanged" displayMemberPath="header" selectedValuePath="value" style="display:block" :itemsSource="colorSource"></wj-menu> <button class="btn btn-default" @click="toggleSort('color')">{{getSort('color')}}</button> <button class="btn btn-default" @click="toggleGroup('color')">{{getGroup('color')}}</button> </div> </th> <th class="text-center"> <div class="btn-group"> <wj-menu :header="amountHeader" :itemClicked="selectionAmountChanged" displayMemberPath="header" selectedValuePath="value" style="display:block" :itemsSource="amountSource"></wj-menu> <button class="btn btn-default" @click="toggleSort('amount')">{{getSort('amount')}}</button> <button class="btn btn-default" @click="toggleGroup('amount')">{{getGroup('amount')}}</button> </div> </th> </tr> </thead> <tbody> <tr v-for="item in groupedList" v-bind:key="item" v-bind:class="{success: item == cv.currentItem}" @click="moveCurrentTo(item)"> <!-- group row --> <td v-bind:style="{display:isGroup(item)?'':'none'}" colspan="4" class="active"> <span v-bind:style="{display:'inline-block', width: (item.level * 25) + 'px'}"></span> <b>{{item.name}}</b> ( items) </td> <!-- data row --> <td v-bind:style="{display:isGroup(item)?'none':''}" class="text-center">{{item.id}}</td> <td v-bind:style="{display:isGroup(item)?'none':''}" class="text-center">{{item.country}}</td> <td v-bind:style="{display:isGroup(item)?'none':''}" class="text-center">{{item.color}}</td> <td v-bind:style="{display:isGroup(item)?'none':''}" class="text-center">{{item.amount | number:'1.2-2'}} </td> </tr> </tbody> </table> </div> </template> <script> import "bootstrap.css"; import "@grapecity/wijmo.styles/wijmo.css"; import Vue from 'vue'; import { WjInputModule } from '@grapecity/wijmo.vue2.input'; import { getData } from './data'; import * as wijmo from '@grapecity/wijmo'; import { CollectionViewPager } from './collection-view-pager'; import { CollectionViewNavigator } from './collection-view-navigator'; new Vue({ el: '#app', data: function () { return { cv: new wijmo.CollectionView(getData(500), { pageSize: 10, filter: this._filterFun.bind(this), newItemCreator: () => { var newItem = getData(1)[0]; newItem.id = -1; return newItem; } }), groupedList: [], filter: {}, pageHeader: '', pageSource: [{header: "No Paging", value: 0},{header: "10", value: 10},{header: "15", value: 15},{header: "30", value: 30}, {header: "50", value: 50}], filterHeader: '', filterSource:[{header: "(All)", value: ""},{header: "Odd", value: "odd"}, {header: "Even", value: "even"}], countryHeader: '', countrySource:[{header: "(All)", value:""}, {header: "US", value: "US"}, {header: "Germany", value: "Germany"}, {header: "UK", value: "UK"}, {header: "Japan", value: "Japan"}, {header: "Italy", value: "Italy"}, {header: "Greece", value: "Greece"}, {header: "France", value: "France"}], colorHeader: '', colorSource:[{header: "(All)", value: ""}, {header: "Black", value: 'Black'}, {header: "White", value: 'White'}, {header: "Red", value: 'Red'}, {header: "Green", value: 'Green'}, {header: "Blue", value: 'Blue'}, {header: "Yellow", value: 'Yellow'}, {header: "Brown", value: 'Brown'}, {header: "Orange", value: 'Orange'}], amountHeader: '', amountSource:[{header: "(All)", value: ""}, {header: "&gt; 0", value: "0"}, {header: "&gt; 500", value: "500"}, {header: "&gt; 1,000", value: "1000"}] } }, mounted: function(){ this.filter = { id: '', country: '', color: '', minAmount: '' }; this.pageHeader = "Page Size: <b>" + this.cv.pageSize + "</b>"; this.filterHeader = 'ID: <b>' + this.getFilteredItem(this.filterSource, this.filter.id) + '</b>'; this.countryHeader = 'Country: <b>' + this.getFilteredItem(this.countrySource, this.filter.country) + '</b>'; this.colorHeader = 'Color: <b>' + this.getFilteredItem(this.colorSource, this.filter.color) + '</b>'; this.amountHeader = 'Amount: <b>' + this.getFilteredItem(this.amountSource, this.filter.minAmount) + '</b>'; new CollectionViewNavigator("#navigator", this.cv); new CollectionViewPager("#pager", this.cv); this.groupedList = this.cv.items; // this.cv.collectionChanged.addHandler(() => { this.groupedList = this.cv.items; if (this.cv.groups && this.cv.groups.length > 0) { this.groupedList = []; this.cv.groups.forEach(group => this._addGroup(group)); } }); }, methods:{ doFilter() { if (this._timeOut) { clearTimeout(this._timeOut); } // this._timeOut = setTimeout(() => { this._timeOut = null; this.cv.refresh(); }, 250); }, getFilteredItem(source, value){ let item = source.filter((item)=>{ return item.value == value })[0]; if(item !== undefined){ return item.header; } }, selectionPageChanged(menu){ this.cv.pageSize = menu.selectedValue; this.pageHeader = 'Page Size: <b>' + menu.selectedItem.header + '</b>'; }, selectionIDChanged(menu){ this.filter.id = menu.selectedValue; this.filterHeader = 'ID: <b>' + this.getFilteredItem(this.filterSource, this.filter.id) + '</b>'; this.doFilter(); }, selectionCountryChanged(menu){ this.filter.country = menu.selectedValue; this.countryHeader = 'ID: <b>' + this.getFilteredItem(this.countrySource, this.filter.country) + '</b>'; this.doFilter(); }, selectionColorChanged(menu){ this.filter.color = menu.selectedValue; this.colorHeader = 'ID: <b>' + this.getFilteredItem(this.colorSource, this.filter.color) + '</b>'; this.doFilter(); }, selectionAmountChanged(menu){ this.filter.minAmount = menu.selectedValue; this.amountHeader = 'ID: <b>' + this.getFilteredItem(this.amountSource, this.filter.minAmount) + '</b>'; this.doFilter(); }, // // IEditableCollectionView commands isEditing() { return this.cv.isEditingItem || this.cv.isAddingNew; }, // edit() { this.cv.editItem(this.cv.currentItem); }, // add() { this.cv.addNew(); }, // deleteItem() { this.cv.remove(this.cv.currentItem); }, // commit() { this.cv.commitEdit(); this.cv.commitNew(); }, // cancel() { this.cv.cancelEdit(); this.cv.cancelNew(); }, // moveCurrentTo(item) { if (!this.isEditing() && !this.isGroup(item)) { this.cv.moveCurrentTo(item); } }, // // 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(g) { this.groupedList.push(g); // if (g.isBottomLevel) { g.items.forEach(item => this.groupedList.push(item)); } else { g.groups.forEach(group => this._addGroup(group)); } }, // _findGroup(propName) { let gd = this.cv.groupDescriptions; // for (let i = 0; i < gd.length; i++) { if (gd[i].propertyName == propName) { return i; } } // return -1; }, // // filtering _filterFun(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; } } }) </script> <style> .table { margin-bottom: 0px !important; } </style> <!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.vue'); </script> </head> <body> <div id="app"> </div> </body> </html> export function getData(count) { // data used to generate random items let _colors = ['Black', 'White', 'Red', 'Green', 'Blue']; let _countries = ['US', 'Germany', 'UK', 'Japan', 'Italy', 'Greece']; 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; } 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.doFilter = () => { if (this.state._timeOut) { clearTimeout(this.state._timeOut); } // this.setState({ _timeOut: setTimeout(() => { this.setState({ _timeOut: null }); this.state.cv.refresh(); }, 250) }); }; this.getFilteredItem = (source, value) => { let item = source.find((item) => { return item.value == value; }); if (item !== undefined) { return item.header; } else { return source[0].header; } }; this.selectionPageChanged = (menu) => { let cv = this.state.cv; cv.pageSize = menu.selectedValue; this.setState({ cv: cv, pageHeader: 'Page Size: <b>' + menu.selectedItem.header + '</b>' }); }; this.selectionIDChanged = (menu) => { let filter = this.state.filter; filter.minAmount = menu.selectedValue; this.setState({ filter: filter, filterHeader: 'ID: <b>' + this.getFilteredItem(this.state.filterSource, this.state.filter.id) + '</b>' }); this.doFilter(); }; this.selectionCountryChanged = (menu) => { let filter = this.state.filter; filter.country = menu.selectedValue; this.setState({ filter: filter, countryHeader: 'ID: <b>' + this.getFilteredItem(this.state.countrySource, this.state.filter.country) + '</b>' }); this.doFilter(); }; this.selectionColorChanged = (menu) => { let filter = this.state.filter; filter.color = menu.selectedValue; this.setState({ filter: filter, colorHeader: 'ID: <b>' + this.getFilteredItem(this.state.colorSource, this.state.filter.color) + '</b>' }); this.doFilter(); }; this.selectionAmountChanged = (menu) => { let filter = this.state.filter; filter.minAmount = menu.selectedValue; this.setState({ filter: filter, amountHeader: 'ID: <b>' + this.getFilteredItem(this.state.amountSource, this.state.filter.minAmount) + '</b>' }); this.doFilter(); }; // IEditableCollectionView commands this.isEditing = () => { this.setState({ isEditing: this.state.cv.isEditingItem || this.state.cv.isAddingNew }); }; this.edit = () => { this.state.cv.editItem(this.state.cv.currentItem); this.isEditing(); }; this.add = () => { this.state.cv.addNew(); this.isEditing(); }; this.deleteItem = () => { this.state.cv.remove(this.state.cv.currentItem); this.isEditing(); }; this.commit = () => { this.state.cv.commitEdit(); this.state.cv.commitNew(); this.isEditing(); }; this.cancel = () => { this.state.cv.cancelEdit(); this.state.cv.cancelNew(); this.isEditing(); }; this.moveCurrentTo = (item) => { if (!this.state.isEditing && !this.isGroup(item)) { let cv = this.state.cv; cv.currentItem = item; this.setState({ cv: cv }); } }; // sorting this.getSort = (propName) => { let sd = this.state.cv.sortDescriptions; if (sd.length > 0 && sd[0].property == propName) { return sd[0].ascending ? '▲' : '▼'; } return '◇'; }; this.toggleSort = (propName) => { let sd = this.state.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 this.getGroup = (propName) => { let index = this._findGroup(propName); return index < 0 ? /*'▯' +*/ Array(this.state.cv.groupDescriptions.length + 2).join('▷') : /*'▮' +*/ Array(index + 2).join('▶'); }; this.toggleGroup = (propName) => { let gd = this.state.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)); } } }; this.isGroup = (item) => { return item instanceof wijmo.CollectionViewGroup; }; this._addGroup = (g) => { let groupedList = this.state.groupedList; groupedList.push(g); this.setState({ groupedList: groupedList }); // if (g.isBottomLevel) { g.items.forEach((item) => this.state.groupedList.push(item)); } else { g.groups.forEach((group) => this._addGroup(group)); } }; this._findGroup = (propName) => { let gd = this.state.cv.groupDescriptions; // for (let i = 0; i < gd.length; i++) { if (gd[i].propertyName == propName) { return i; } } // return -1; }; // filtering this._filterFun = (item) => { // check each filter parameter let f = this.state.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; }; this.updateCurrentItem = (target, key) => { let cv = this.state.cv, currentItem = this.state.cv.currentItem; currentItem[key] = target.value; cv.currentItem = currentItem; this.setState({ cv: cv }); }; this.state = { filter: {}, cv: new wijmo.CollectionView(getData(500), { pageSize: 10, filter: () => { }, newItemCreator: () => { var newItem = getData(1)[0]; newItem.id = -1; return newItem; } }), isEditing: false, groupedList: [], pageHeader: '', _timeOut: null, pageSource: [{ header: "No Paging", value: 0 }, { header: "10", value: 10 }, { header: "15", value: 15 }, { header: "30", value: 30 }, { header: "50", value: 50 }], filterHeader: '(All)', filterSource: [{ header: "(All)", value: "" }, { header: "Odd", value: "odd" }, { header: "Even", value: "even" }], countryHeader: '(All)', countrySource: [{ header: "(All)", value: "" }, { header: "US", value: "US" }, { header: "Germany", value: "Germany" }, { header: "UK", value: "UK" }, { header: "Japan", value: "Japan" }, { header: "Italy", value: "Italy" }, { header: "Greece", value: "Greece" }, { header: "France", value: "France" }], colorHeader: '(All)', colorSource: [{ header: "(All)", value: "" }, { header: "Black", value: 'Black' }, { header: "White", value: 'White' }, { header: "Red", value: 'Red' }, { header: "Green", value: 'Green' }, { header: "Blue", value: 'Blue' }, { header: "Yellow", value: 'Yellow' }, { header: "Brown", value: 'Brown' }, { header: "Orange", value: 'Orange' }], amountHeader: '(All)', amountSource: [{ header: "(All)", value: "" }, { header: "> 0", value: "0" }, { header: "> 500", value: "500" }, { header: "> 1,000", value: "1000" }] }; } ; componentWillMount() { let cv = this.state.cv; cv.filter = this._filterFun; cv.currentItem = this.state.cv.items[0]; this.setState({ cv: cv }); } componentDidMount() { this.setState({ filter: { id: '', country: '', color: '', minAmount: '' }, pageHeader: "Page Size: <b>" + this.state.cv.pageSize + "</b>", filterHeader: 'ID: <b>' + this.getFilteredItem(this.state.filterSource, this.state.filter.id) + '</b>', countryHeader: 'Country: <b>' + this.getFilteredItem(this.state.countrySource, this.state.filter.country) + '</b>', colorHeader: 'Color: <b>' + this.getFilteredItem(this.state.colorSource, this.state.filter.color) + '</b>', amountHeader: 'Amount: <b>' + this.getFilteredItem(this.state.amountSource, this.state.filter.minAmount) + '</b>' }); new CollectionViewNavigator("#navigator", this.state.cv, this.moveCurrentTo); new CollectionViewPager("#pager", this.state.cv); this.setState({ groupedList: this.state.cv.items }); // let cv = this.state.cv; cv.collectionChanged.addHandler(() => { this.setState({ groupedList: this.state.cv.items }, () => { if (this.state.cv.groups && this.state.cv.groups.length > 0) { this.setState({ groupedList: [] }, () => { this.state.cv.groups.forEach((group) => this._addGroup(group)); }); } }); }); this.setState({ cv: cv }); } 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.state.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.state.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.state.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.state.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} style={{ display: !this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Edit</button> <button onClick={this.add} style={{ display: !this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Add</button> <button onClick={this.deleteItem} style={{ display: !this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Delete</button> <button onClick={this.commit} style={{ display: this.state.isEditing ? '' : 'none' }} className="btn btn-default btn-sm">Commit</button> <button onClick={this.cancel} 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={this.state.pageHeader} displayMemberPath="header" selectedValuePath="value" itemsSource={this.state.pageSource} itemClicked={this.selectionPageChanged}> </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={this.state.filterHeader} displayMemberPath="header" selectedValuePath="value" itemClicked={this.selectionIDChanged} style={{ display: "block" }} itemsSource={this.state.filterSource}> </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={this.state.countryHeader} itemClicked={this.selectionCountryChanged} displayMemberPath="header" selectedValuePath="value" style={{ display: "block" }} itemsSource={this.state.countrySource}> </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={this.state.colorHeader} itemClicked={this.selectionColorChanged} displayMemberPath="header" selectedValuePath="value" style={{ display: "block" }} itemsSource={this.state.colorSource}></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={this.state.amountHeader} itemClicked={this.selectionAmountChanged} displayMemberPath="header" selectedValuePath="value" style={{ display: "block" }} itemsSource={this.state.amountSource}> </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.state.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>; } } 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>AutoComplete</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; }