mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2025-10-16 12:05:41 +00:00
1574 lines
59 KiB
TypeScript
1574 lines
59 KiB
TypeScript
import * as React from 'react';
|
|
import { render } from 'react-dom';
|
|
import {
|
|
BootstrapTable,
|
|
ButtonGroupProps,
|
|
CellEdit,
|
|
ColumnDescription,
|
|
CustomFilter,
|
|
CustomSelectProps,
|
|
DeleteButton,
|
|
EditableAttrs,
|
|
EditValidatorObject,
|
|
ExpandColumnComponentProps,
|
|
ExpandColumnOptions,
|
|
ExportCSVButton,
|
|
Filter,
|
|
FilterData,
|
|
FilterType,
|
|
FooterData,
|
|
InsertButton,
|
|
InsertModalColumnDescription,
|
|
InsertModalFooter,
|
|
InsertModalHeader,
|
|
ModalBodyInterface,
|
|
Options,
|
|
PaginationPanelProps,
|
|
SearchField,
|
|
SearchFieldInterface,
|
|
SearchFieldProps,
|
|
SearchPanelProps,
|
|
SelectRow,
|
|
ShowSelectedOnlyButton,
|
|
SizePerPageDropDown,
|
|
SortOrder,
|
|
TableHeaderColumn,
|
|
ToolBarProps,
|
|
CustomFilterParameters
|
|
} from 'react-bootstrap-table';
|
|
|
|
interface Product {
|
|
id: number;
|
|
name: string;
|
|
price?: number;
|
|
quality?: number;
|
|
inStockStatus?: number;
|
|
sales?: number;
|
|
}
|
|
const products: Product[] = [{
|
|
id: 1,
|
|
name: "Item name 1",
|
|
price: 100
|
|
}, {
|
|
id: 2,
|
|
name: "Item name 2",
|
|
price: 100
|
|
}];
|
|
|
|
type JobType = 'A' | 'B' | 'C' | 'D';
|
|
interface Job {
|
|
id: number;
|
|
status: string;
|
|
name: string;
|
|
type: JobType;
|
|
active: 'Y' | 'N';
|
|
}
|
|
const jobs = [
|
|
{ id: 1, status: '200', name: 'Item name 1', type: 'B', active: 'N' },
|
|
{ id: 2, status: '200', name: 'Item name 2', type: 'B', active: 'Y' }
|
|
];
|
|
const jobTypes = ['A', 'B', 'C', 'D'];
|
|
|
|
interface ExtendedJob {
|
|
id: number;
|
|
name: string;
|
|
type1: JobType;
|
|
type2: JobType;
|
|
active: 'Y' | 'N';
|
|
datetime: Date;
|
|
}
|
|
const extendedJobs = [
|
|
{ id: 1, name: 'Item name 1', type1: 'A', type2: 'B', active: 'N', datetime: '2001-12-28T14:57:00' },
|
|
{ id: 2, name: 'Item name 2', type1: 'A', type2: 'B', active: 'Y', datetime: '2002-12-28T14:57:00' }
|
|
];
|
|
|
|
const inStockStatus = {
|
|
1: 'yes',
|
|
2: 'no'
|
|
};
|
|
|
|
// It's a data format example.
|
|
function priceFormatter(cell: number, row: Product) {
|
|
return '<i class="glyphicon glyphicon-usd"></i> ' + cell;
|
|
}
|
|
|
|
render(
|
|
<BootstrapTable data={products} striped={true} hover={true} ignoreSinglePage>
|
|
<TableHeaderColumn dataField="id" isKey={true} dataAlign="center" dataSort={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField="name" dataSort={true} editable={{ type: 'textarea', rows: 10 }}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField="price" dataFormat={priceFormatter}>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>,
|
|
document.getElementById("app")
|
|
);
|
|
|
|
const qualityType = {
|
|
0: 'good',
|
|
1: 'bad',
|
|
2: 'unknown'
|
|
};
|
|
|
|
function enumFormatter(cell: any, row: any, enumObject: any, rowIndex: number) {
|
|
console.log(`The row index: ${rowIndex}`);
|
|
return enumObject[cell];
|
|
}
|
|
|
|
class SelectFilterWithDefaultValue extends React.Component {
|
|
render() {
|
|
return (
|
|
<BootstrapTable data={products}>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='quality' filterFormatted dataFormat={enumFormatter}
|
|
formatExtraData={qualityType} filter={{ type: 'SelectFilter', options: qualityType, defaultValue: 1 }}>Product Quality</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
class TextFilterWithCondition extends React.Component {
|
|
render() {
|
|
return (
|
|
<BootstrapTable data={products}>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' filter={{ type: 'TextFilter', delay: 1000, condition: 'eq' }}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
interface CustomFilterParams {
|
|
textOK: string;
|
|
textNOK: string;
|
|
}
|
|
function getCustomFilter(filterHandler: (parameters?: CustomFilterParameters<CustomFilterParams>, type?: 'CustomFilter') => void, customFilterParameters: CustomFilterParameters<CustomFilterParams>) {
|
|
return (
|
|
<div />
|
|
);
|
|
}
|
|
|
|
function customFilterCallback(cell: any, callbackParameters: CustomFilterParams) { return true; }
|
|
|
|
class CustomFilterTable extends React.Component {
|
|
render() {
|
|
const filter: Filter = { type: 'CustomFilter', getElement: getCustomFilter, customFilterParameters: { callback: customFilterCallback, callbackParameters: { textOK: 'yes', textNOK: 'no' }} };
|
|
return (
|
|
<BootstrapTable data={products}>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='isInStock' filter={filter}>Product Is In Stock</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
class RemotePropsDefault extends React.Component {
|
|
render() {
|
|
const filter: CustomFilter = { type: 'CustomFilter', getElement: getCustomFilter, customFilterParameters: { callback: customFilterCallback, callbackParameters: { textOK: 'yes', textNOK: 'no' }} };
|
|
return (
|
|
<BootstrapTable
|
|
data={products}
|
|
remote={(remoteObj) => {
|
|
remoteObj.cellEdit = true;
|
|
return remoteObj;
|
|
}}
|
|
options={{
|
|
onCellEdit: (row: any, fieldName: string, value: any) => { console.info(row); return value; }
|
|
}}
|
|
>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='isInStock' filter={filter}>Product Is In Stock</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
class RemotePropsSpecified extends React.Component {
|
|
render() {
|
|
const filter: CustomFilter<CustomFilterParams> = {
|
|
type: 'CustomFilter',
|
|
getElement: getCustomFilter,
|
|
customFilterParameters: {
|
|
callback: customFilterCallback,
|
|
callbackParameters: { textOK: 'yes', textNOK: 'no' }
|
|
}
|
|
};
|
|
return (
|
|
<BootstrapTable
|
|
data={products}
|
|
remote={(remoteObj) => {
|
|
remoteObj.cellEdit = true;
|
|
return remoteObj;
|
|
}}
|
|
options={{
|
|
onCellEdit: (row: any, fieldName: string, value: any) => { console.info(row); return value; }
|
|
}}
|
|
>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='isInStock' filter={filter}>Product Is In Stock</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
class RemoteBool extends React.Component {
|
|
render() {
|
|
const filter: Filter = { type: 'CustomFilter', getElement: getCustomFilter, customFilterParameters: {callback: customFilterCallback, callbackParameters: { textOK: 'yes', textNOK: 'no' } }};
|
|
return (
|
|
<BootstrapTable
|
|
data={products}
|
|
remote
|
|
>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='isInStock' filter={filter}>Product Is In Stock</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See http://allenfang.github.io/react-bootstrap-table/docs.html#tdAttr
|
|
*/
|
|
const tdAttrExample = <BootstrapTable data={ products } search>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' tdAttr={ { "data-attr": 'test' } }>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>;
|
|
|
|
/**
|
|
* See http://allenfang.github.io/react-bootstrap-table/docs.html#tdStyle
|
|
*/
|
|
const tdStyleExample = <BootstrapTable data={ products } search>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' tdStyle={ { whiteSpace: 'normal' } }>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>;
|
|
|
|
/**
|
|
* See http://allenfang.github.io/react-bootstrap-table/docs.html#thStyle
|
|
*/
|
|
const thStyleExample = <BootstrapTable data={ products } search>
|
|
<TableHeaderColumn dataField='id' isKey>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' thStyle={ { fontWeight: 'lighter' } }>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>;
|
|
|
|
/**
|
|
* Adopted from https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-header-span/column-header-span-complex.js
|
|
*/
|
|
class ColumnHeaderSpanComplex extends React.Component {
|
|
render() {
|
|
const options: Options = { exportCSVSeparator: '##' };
|
|
return (
|
|
<BootstrapTable data={products} insertRow deleteRow exportCSV options={options}>
|
|
<TableHeaderColumn row={0} rowSpan={2} dataField='id' isKey={true} >ID</TableHeaderColumn>
|
|
<TableHeaderColumn row={0} colSpan={3} dataSort csvHeader='Product' headerAlign='right'>Product</TableHeaderColumn>
|
|
<TableHeaderColumn row={1} dataField='name' width='175' dataAlign='center'>name</TableHeaderColumn>
|
|
<TableHeaderColumn row={1} dataField='price' dataSort>price</TableHeaderColumn>
|
|
<TableHeaderColumn row={1} dataField='coupon' width='70'>Coupon</TableHeaderColumn>
|
|
<TableHeaderColumn row={0} csvHeader='In stock' rowSpan={2} dataField='status' export={false}>In stock</TableHeaderColumn>
|
|
<TableHeaderColumn row={0} colSpan={2} csvHeader='Customer' filter={{ type: 'TextFilter', delay: 1000 }}>Customer</TableHeaderColumn>
|
|
<TableHeaderColumn row={1} csvHeader='name' dataField='customer'>name</TableHeaderColumn>
|
|
<TableHeaderColumn row={1} csvHeader='order' dataField='order' dataSort>order</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pagination options
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/pagination/custom-pagination-table.js
|
|
*/
|
|
class PaginationTable extends React.Component {
|
|
sizePerPageListChange = (sizePerPage: number) => {
|
|
alert(`sizePerPage: ${sizePerPage}`);
|
|
}
|
|
|
|
onPageChange = (page: number, sizePerPage: number) => {
|
|
alert(`page: ${page}, sizePerPage: ${sizePerPage}`);
|
|
}
|
|
|
|
renderShowsTotal = (start: number, to: number, total: number) => {
|
|
return (
|
|
<p style={{ color: 'blue' }}>
|
|
From {start} to {to}, totals is {total} (its a customize text)
|
|
</p>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const options: Options<Product> = {
|
|
onPageChange: this.onPageChange,
|
|
onSizePerPageList: this.sizePerPageListChange,
|
|
page: 2, // which page you want to show as default
|
|
sizePerPageList: [{
|
|
text: '5', value: 5
|
|
}, {
|
|
text: '10', value: 10
|
|
}, {
|
|
text: 'All', value: products.length
|
|
}], // you can change the dropdown list for size per page
|
|
sizePerPage: 5, // which size per page you want to locate as default
|
|
pageStartIndex: 0, // where to start counting the pages
|
|
paginationSize: 3, // the pagination bar size.
|
|
prePage: 'Prev', // Previous page button text
|
|
nextPage: 'Next', // Next page button text
|
|
firstPage: 'First', // First page button text
|
|
lastPage: 'Last', // Last page button text
|
|
prePageTitle: 'Go to previous', // Previous page button title
|
|
nextPageTitle: 'Go to next', // Next page button title
|
|
firstPageTitle: 'Go to first', // First page button title
|
|
lastPageTitle: 'Go to Last', // Last page button title
|
|
paginationShowsTotal: this.renderShowsTotal, // Accept bool or function
|
|
paginationPosition: 'top', // default is bottom, top and both is all available
|
|
keepSizePerPageState: true, // default is false, enable will keep sizePerPage dropdown state(open/clode) when external rerender happened
|
|
hideSizePerPage: true, // You can hide the dropdown for sizePerPage
|
|
alwaysShowAllBtns: true, // Always show next and previous button
|
|
withFirstAndLast: false, // Hide the going to First and Last page button
|
|
hidePageListOnlyOnePage: true // Hide the page list if only one page.
|
|
};
|
|
|
|
return (
|
|
<BootstrapTable data={products} pagination={true} options={options}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adopted from https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/cell-edit/blur-to-escape-table.js
|
|
*/
|
|
class BlurToEscapeTable extends React.Component {
|
|
render() {
|
|
const cellEditProp: CellEdit<Product> = {
|
|
mode: 'click',
|
|
blurToEscape: true
|
|
};
|
|
|
|
return (
|
|
<BootstrapTable data={products} cellEdit={cellEditProp} striped={true} hover={true} condensed={true} bordered={false}>
|
|
<TableHeaderColumn width='150px' dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cell classnames, row saving hooks.
|
|
* @see https://githum.com/AllenFang/react-bootstrap-table/blob/master/examples/js/cell-edit/cell-edit-classname.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/cell-edit/cell-edit-hook-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/manipulation/insert-row-table.js
|
|
*/
|
|
class EditCellClassNameTable extends React.Component {
|
|
jobNameValidator = (value: string) => {
|
|
const response: EditValidatorObject = { isValid: true, notification: { type: 'success', msg: '', title: '' } };
|
|
if (!value) {
|
|
response.isValid = false;
|
|
response.notification.type = 'error';
|
|
response.notification.msg = 'Value must be inserted';
|
|
response.notification.title = 'Requested Value';
|
|
} else if (value.length < 10) {
|
|
response.isValid = false;
|
|
response.notification.type = 'error';
|
|
response.notification.msg = 'Value must have 10+ characters';
|
|
response.notification.title = 'Invalid Value';
|
|
}
|
|
return response;
|
|
}
|
|
|
|
jobStatusValidator = (value: string) => {
|
|
const nan = isNaN(parseInt(value, 10));
|
|
if (nan) {
|
|
return 'Job Status must be a integer!';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
invalidJobStatus = (cell: string, row: Job) => {
|
|
console.log(`${cell} at row id: ${row.id} fails on editing`);
|
|
return 'invalid-jobstatus-class';
|
|
}
|
|
|
|
editingJobStatus = (cell: string, row: Job) => {
|
|
console.log(`${cell} at row id: ${row.id} in current editing`);
|
|
return 'editing-jobstatus-class';
|
|
}
|
|
|
|
onBeforeSaveCellAsync = <K extends keyof Job>(row: Job, cellName: K, cellValue: Job[K], done: (ok: boolean) => void): boolean | 1 => {
|
|
setTimeout(() => {
|
|
done(false); // it's not ok to save :(
|
|
}, 3000);
|
|
return 1; // return 1 === async operation.
|
|
}
|
|
|
|
onAfterSaveCell(row: Job, cellName: string, cellValue: any) {
|
|
alert(`Save cell ${cellName} with value ${cellValue}`);
|
|
let rowStr = '';
|
|
for (const prop in row) {
|
|
rowStr += `${prop}: ${row[prop as keyof Job]}\n`;
|
|
}
|
|
alert('The whole row :\n' + rowStr);
|
|
}
|
|
|
|
onAfterInsertRow = (row: any) => {
|
|
let newRowStr = '';
|
|
|
|
for (const prop in row) {
|
|
newRowStr += `${prop}: ${row[prop]} \n`;
|
|
}
|
|
alert('The new row is:\n ' + newRowStr);
|
|
}
|
|
|
|
render() {
|
|
const cellEditProp: CellEdit<Job> = {
|
|
mode: 'dbclick',
|
|
blurToSave: true,
|
|
beforeSaveCell: this.onBeforeSaveCellAsync,
|
|
afterSaveCell: this.onAfterSaveCell
|
|
};
|
|
const options: Options<Job> = {
|
|
afterInsertRow: this.onAfterInsertRow // A hook for after insert rows
|
|
};
|
|
|
|
return (
|
|
<BootstrapTable data={jobs} cellEdit={cellEditProp} insertRow={true} options={options}>
|
|
<TableHeaderColumn dataField='id' isKey={true} editable={false}>Job ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='status' editable={{ validator: this.jobStatusValidator }}
|
|
editColumnClassName={this.editingJobStatus} invalidEditColumnClassName={this.invalidJobStatus}>Job Status</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' editable={{ type: 'textarea', validator: this.jobNameValidator }}
|
|
editColumnClassName='editing-jobsname-class' invalidEditColumnClassName='invalid-jobsname-class'>Job Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='type' editable={{ type: 'select', options: { values: jobTypes } }}>Job Type</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='active' editable={{ type: 'checkbox', options: { values: 'Y:N' } }}>Active</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Multiple field sorting, filtering, searching and clearing
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/manipulation/filter-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/manipulation/multi-search-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/manipulation/search-clear-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/manipulation/strict-multi-search-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/remote/remote-store-search.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/sort/custom-caret-sort-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/sort/custom-sort-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/sort/sort-style-table.js
|
|
*/
|
|
class MultiSortAndFiltering extends React.Component {
|
|
tableRef: BootstrapTable | null;
|
|
idRef: TableHeaderColumn | null;
|
|
state = { data: products };
|
|
|
|
onSortChange = (name: string, order: SortOrder) => {
|
|
console.log(`Next sort = ${name}:${order}`);
|
|
}
|
|
|
|
cleanSort = () => { this.tableRef.cleanSort(); };
|
|
|
|
cleanSelected = () => { this.tableRef.cleanSelected(); };
|
|
|
|
onFilterChange = (filter: FilterData) => {
|
|
Object.keys(filter).forEach((column) => {
|
|
console.log(`Filtering ${column}: ${JSON.stringify(filter[column])}`);
|
|
});
|
|
}
|
|
|
|
cleanFilters = () => { this.idRef.cleanFiltered(); };
|
|
|
|
afterColumnFilter = (filterConds: ReadonlyArray<FilterData>, result: ReadonlyArray<Product>) => {
|
|
console.log('Filter Conditions: ');
|
|
filterConds.forEach((filterCond: FilterData) => {
|
|
Object.keys(filterCond).forEach((fieldName: string) =>
|
|
console.log(`Filter column = ${fieldName}, Filter value = ${filterCond[fieldName]}`));
|
|
});
|
|
console.log('Result is:');
|
|
for (const resultItem of result) {
|
|
console.log(`Product: ${resultItem.id}, ${resultItem.name}, ${resultItem.price}`);
|
|
}
|
|
}
|
|
|
|
onSearchChange = (searchText: string, colInfos: ReadonlyArray<ColumnDescription<Product>>, multiColumnSearch: boolean) => {
|
|
this.setState({ data: products.filter((product) => product.name = searchText) });
|
|
}
|
|
|
|
afterSearch = (searchText: string, result: ReadonlyArray<Product>) => {
|
|
console.log(`Your search text is ${searchText}`);
|
|
console.log('Result is:');
|
|
for (const resultItem of result) {
|
|
console.log(`Product: ${resultItem.id}, ${resultItem.name}, ${resultItem.price}`);
|
|
}
|
|
}
|
|
|
|
getNameCaret = (direction: SortOrder | null, fieldName: string) =>
|
|
(direction === 'asc')
|
|
? (<span> up</span>)
|
|
: (direction === 'desc')
|
|
? (<span> down</span>)
|
|
: (<span> up/down</span>)
|
|
|
|
revertSortFunc = (a: Product, b: Product, order: SortOrder) =>
|
|
(order === 'desc') ? a.price - b.price : b.price - a.price
|
|
|
|
customSortStyle = (order: SortOrder, dataField: string) =>
|
|
(order === 'desc') ? 'sort-desc' : 'sort-asc'
|
|
|
|
render() {
|
|
const options: Options<Product> = {
|
|
onSortChange: this.onSortChange,
|
|
onFilterChange: this.onFilterChange,
|
|
noDataText: 'This is custom text for empty data',
|
|
withoutNoDataText: false,
|
|
afterColumnFilter: this.afterColumnFilter,
|
|
onSearchChange: this.onSearchChange,
|
|
afterSearch: this.afterSearch,
|
|
clearSearch: true,
|
|
sortIndicator: true
|
|
};
|
|
return (
|
|
<div>
|
|
<button className='btn ben-default' onClick={this.cleanSort}>Clean</button>
|
|
<BootstrapTable data={this.state.data} options={options} multiColumnSort={2}
|
|
multiColumnSearch={true} scrollTop={'Bottom'} strictSearch={true} fetchInfo={{ dataTotalSize: 5 }}>
|
|
<TableHeaderColumn dataField='id' isKey={true} searchable={false} dataSort={true}
|
|
filter={{ type: 'TextFilter', delay: 500 }} sortHeaderColumnClassName='sorted'>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' defaultASC={true} dataSort={true} caretRender={this.getNameCaret}
|
|
sortHeaderColumnClassName={this.customSortStyle}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price' sortFunc={this.revertSortFunc}
|
|
filter={{ type: 'NumberFilter', options: [10, 50, 100], numberComparators: ['>=', '<=', '='] }}>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sort with extra data.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/sort/custom-sort-with-extra-data-table.js
|
|
*/
|
|
class CustomSortWithExtraDataTable extends React.Component {
|
|
sortByName = (a: any, b: any, order: SortOrder, field: string, enumObject: any) => {
|
|
if (order === 'desc') {
|
|
if (enumObject[a[field]] > enumObject[b[field]]) {
|
|
return -1;
|
|
} else if (enumObject[a[field]] < enumObject[b[field]]) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
if (enumObject[a[field]] < enumObject[b[field]]) {
|
|
return -1;
|
|
} else if (enumObject[a[field]] > enumObject[b[field]]) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<BootstrapTable data={products}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='quality'
|
|
dataFormat={enumFormatter} formatExtraData={qualityType}
|
|
dataSort={true}
|
|
sortFunc={this.sortByName} sortFuncExtraData={qualityType}>
|
|
Product Quality</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render custom pagination with provided SizePerPageDropDown component.
|
|
*/
|
|
class CustomPagination extends React.Component {
|
|
renderPagination = (props: PaginationPanelProps) => (
|
|
<div>
|
|
<SizePerPageDropDown variation='dropup' options={props.sizePerPageList} btnContextual='primary' currSizePerPage={props.sizePerPage.toString()} />
|
|
<div>
|
|
<button onClick={() => props.changeSizePerPage(25)} className='btn btn-default'>Click to force size per page as 25</button>
|
|
<button onClick={() => props.toggleDropDown()} className='btn btn-default'>Click to force toggle dropdown</button>
|
|
<button onClick={() => props.changePage(3)} className='btn btn-default'>Jump to page 3</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
render() {
|
|
const options: Options = {
|
|
paginationPanel: this.renderPagination
|
|
};
|
|
|
|
return (
|
|
<BootstrapTable data={products} options={options} pagination>
|
|
<TableHeaderColumn dataField='id' />
|
|
<TableHeaderColumn dataField='name' />
|
|
<TableHeaderColumn dataField='price' />
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Customize the entire insert modal.
|
|
* Adapted from https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/insert-modal/custom-insert-modal.js
|
|
*/
|
|
class CustomModal extends React.Component {
|
|
handleSaveBtnClick = (columns: ReadonlyArray<InsertModalColumnDescription<Product>>, onSave: (row: any) => void) => {
|
|
const newRow: {[field: string]: string} = {};
|
|
columns.forEach((column, i) => {
|
|
newRow[column.field] = (this.refs[column.field] as HTMLInputElement).value;
|
|
}, this);
|
|
onSave(newRow);
|
|
}
|
|
|
|
createCustomModal = (
|
|
onModalClose: () => void,
|
|
onSave: (row: any) => void,
|
|
columns: ReadonlyArray<InsertModalColumnDescription<Product>>,
|
|
validateState: { [dataField: string]: string },
|
|
ignoreEditable: boolean
|
|
) => (
|
|
<div style={{ backgroundColor: '#eeeeee' }} className='modal-content'>
|
|
<h2 style={{ color: 'red' }}>Custom Insert Modal</h2>
|
|
<div>
|
|
{
|
|
columns.map((column, i) => {
|
|
const {
|
|
editable,
|
|
format,
|
|
field,
|
|
name,
|
|
hiddenOnInsert
|
|
} = column;
|
|
|
|
if (hiddenOnInsert) {
|
|
return null;
|
|
}
|
|
const error = validateState[field] ?
|
|
(<span className='help-block bg-danger'>{validateState[field]}</span>) :
|
|
null;
|
|
return (
|
|
<div className='form-group' key={field}>
|
|
<label>{name} : </label>
|
|
<input ref={field} type='text' defaultValue={''} />
|
|
{error}
|
|
</div>
|
|
);
|
|
})
|
|
}
|
|
</div>
|
|
<div>
|
|
<button className='btn btn-danger' onClick={onModalClose}>Leave</button>
|
|
<button className='btn btn-success' onClick={(e) => this.handleSaveBtnClick(columns, onSave)}>Confirm</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
render() {
|
|
const options: Options<Product> = {
|
|
insertModal: this.createCustomModal
|
|
};
|
|
return (
|
|
<BootstrapTable data={products} options={options} insertRow>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom Insert Modal Fields, both as a custom component and as custom fields.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/insert-modal/custom-insert-modal-field.js
|
|
*/
|
|
class SalesRadioField extends React.Component<{editorClass: string, ignoreEditable: boolean}> {
|
|
yes: HTMLInputElement | null;
|
|
no: HTMLInputElement | null;
|
|
|
|
getFieldValue = () => {
|
|
return this.yes.checked ? 'Yes' : 'No';
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
<label className='radio-inline'>
|
|
<input ref={(node) => { this.yes = node; }} type='radio' name='optradio' value='Yes' />Yes
|
|
</label>
|
|
<label className='radio-inline'>
|
|
<input ref={(node) => { this.no = node; }} type='radio' name='optradio' value='No' />No
|
|
</label>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
class CustomInsertModalFieldTable extends React.Component {
|
|
customKeyField = (
|
|
column: InsertModalColumnDescription<Product>,
|
|
attr: EditableAttrs,
|
|
editorClass: string,
|
|
ignoreEditable: boolean
|
|
) => {
|
|
const seqId = products.length;
|
|
return (
|
|
<input type='text' { ...attr } disabled value={seqId} className={`${editorClass}`} />
|
|
);
|
|
}
|
|
|
|
customNameField = (
|
|
column: InsertModalColumnDescription<Product>,
|
|
attr: EditableAttrs,
|
|
editorClass: string,
|
|
ignoreEditable: boolean,
|
|
defaultValue: any) => {
|
|
const fruits = ['banana', 'apple', 'orange', 'tomato', 'strawberries'];
|
|
return (
|
|
<select className={`${editorClass}`} { ...attr }>
|
|
{
|
|
fruits.map(name => (<option key={name} value={name}>{name}</option>))
|
|
}
|
|
</select>
|
|
);
|
|
}
|
|
|
|
customSaleField = (
|
|
column: InsertModalColumnDescription<Product>,
|
|
attr: EditableAttrs,
|
|
editorClass: string,
|
|
ignoreEditable: boolean,
|
|
defaultValue: any) => {
|
|
return (
|
|
<SalesRadioField ref={attr.ref} editorClass={editorClass} ignoreEditable={ignoreEditable} />
|
|
);
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<BootstrapTable ref='table' data={products} insertRow={true}>
|
|
<TableHeaderColumn dataField='id' isKey={true} customInsertEditor={{ getElement: this.customKeyField }}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' customInsertEditor={{ getElement: this.customNameField }}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='sales' customInsertEditor={{ getElement: this.customSaleField }}>On Sales?</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom modal header, body & footer.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/insert-modal/default-custom-insert-modal-header.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/insert-modal/custom-insert-modal-body.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/insert-modal/default-custom-insert-modal-footer.js
|
|
*/
|
|
interface MyCustomBodyProps {
|
|
columns: ReadonlyArray<InsertModalColumnDescription<Product>>;
|
|
validateState: { [dataField: string]: string };
|
|
ignoreEditable: boolean;
|
|
}
|
|
class MyCustomBody extends React.Component<MyCustomBodyProps> implements ModalBodyInterface<Product> {
|
|
getFieldValue() {
|
|
const newRow: Partial<Product> = {};
|
|
this.props.columns.forEach((column, i) => {
|
|
newRow[column.field] = (this.refs[column.field] as HTMLInputElement).value as number & string;
|
|
}, this);
|
|
return newRow as Product;
|
|
}
|
|
|
|
render() {
|
|
const { columns, validateState } = this.props;
|
|
return (
|
|
<div className='modal-body'>
|
|
<h2 style={{ color: 'red' }}>Custom body</h2>
|
|
<div>
|
|
{
|
|
this.props.columns.map((column, i) => {
|
|
const {
|
|
editable,
|
|
format,
|
|
field,
|
|
name,
|
|
hiddenOnInsert
|
|
} = column;
|
|
|
|
if (hiddenOnInsert) {
|
|
return null;
|
|
}
|
|
const error = validateState[field] ?
|
|
(<span className='help-block bg-danger'>{validateState[field]}</span>) :
|
|
null;
|
|
return (
|
|
<div className='form-group' key={field}>
|
|
<label>{name}</label>
|
|
<input ref={field} type='text' defaultValue={''} />
|
|
{error}
|
|
</div>
|
|
);
|
|
})
|
|
}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
class DefaultCustomInsertModalHeaderFooterTable extends React.Component {
|
|
createCustomModalFooter = (closeModal: () => void, save: () => void) => {
|
|
return (
|
|
<InsertModalFooter
|
|
className='my-custom-class'
|
|
saveBtnText='CustomSaveText'
|
|
closeBtnText='CustomCloseText'
|
|
closeBtnContextual='btn-warning'
|
|
saveBtnContextual='btn-success'
|
|
closeBtnClass='my-close-btn-class'
|
|
saveBtnClass='my-save-btn-class'
|
|
beforeClose={(e) => {}}
|
|
beforeSave={(e) => {}}
|
|
onModalClose={closeModal}
|
|
onSave={save} />
|
|
);
|
|
}
|
|
|
|
createCustomModalHeader = (closeModal: () => void, save: () => void) => {
|
|
return (
|
|
<InsertModalHeader
|
|
className='my-custom-class'
|
|
title='This is my custom title'
|
|
beforeClose={(e) => { }}
|
|
onModalClose={closeModal}
|
|
hideClose={true} />
|
|
);
|
|
}
|
|
|
|
createCustomModalBody = (
|
|
columns: ReadonlyArray<InsertModalColumnDescription<Product>>,
|
|
validateState: { [dataField: string]: string },
|
|
ignoreEditable: boolean
|
|
) => (
|
|
<MyCustomBody
|
|
columns={columns}
|
|
validateState={validateState}
|
|
ignoreEditable={ignoreEditable}
|
|
/>
|
|
)
|
|
|
|
render() {
|
|
const options: Options<Product> = {
|
|
insertModalFooter: this.createCustomModalFooter,
|
|
insertModalHeader: this.createCustomModalHeader,
|
|
insertModalBody: this.createCustomModalBody
|
|
};
|
|
return (
|
|
<BootstrapTable data={products} options={options} insertRow={true}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom Toolbar, including Search Panel & Buttons, and custom delete confirmation.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/toolbar/custom-button-group.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/toolbar/custom-toolbar-1.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/search/default-custom-search-field.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/search/custom-search-panel-1.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/search/fully-custom-search-field.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/insert-button/default-custom-insert-button.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/delete-button/default-custom-delete-button.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/custom/csv-button/default-custom-csv-button.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/manipulation/del-row-custom-confirm.js
|
|
*/
|
|
class MySearchField extends React.Component<SearchFieldProps> implements SearchFieldInterface {
|
|
field: SearchField | null;
|
|
|
|
getValue() {
|
|
return this.field.getValue();
|
|
}
|
|
|
|
setValue(search: string) {
|
|
this.field.setValue(search);
|
|
}
|
|
|
|
render() {
|
|
const { ...rest} = this.props;
|
|
return (
|
|
<SearchField
|
|
className={this.props.className}
|
|
defaultValue={this.props.defaultValue}
|
|
placeholder={this.props.placeholder}
|
|
onKeyUp={this.props.onKeyUp}
|
|
ref={(node) => { this.field = node; }}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
class CustomButtonGroup extends React.Component {
|
|
createInsertButton = (onClick: (e: React.MouseEvent<{}>) => void) => (
|
|
<InsertButton
|
|
btnText='CustomInsertText'
|
|
btnContextual='btn-warning'
|
|
className='my-custom-class'
|
|
btnGlyphicon='glyphicon-edit'
|
|
onClick={onClick} />
|
|
)
|
|
|
|
createDeleteButton = (onClick: (e: React.MouseEvent<{}>) => void) => (
|
|
<DeleteButton
|
|
btnText='CustomDeleteText'
|
|
btnContextual='btn-danger'
|
|
className='my-custom-class'
|
|
btnGlyphicon='glyphicon-edit'
|
|
onClick={onClick} />
|
|
)
|
|
|
|
createExportCSVButton = (onClick: (e: React.MouseEvent<{}>) => void) => (
|
|
<ExportCSVButton
|
|
btnText='CustomExportText'
|
|
btnContextual='btn-danger'
|
|
className='my-custom-class'
|
|
btnGlyphicon='glyphicon-edit'
|
|
onClick={onClick} />
|
|
)
|
|
|
|
createShowSelectedOnlyButton = (onClick: (e: React.MouseEvent<{}>) => void, showSelected: boolean) => (
|
|
<ShowSelectedOnlyButton
|
|
showAllText='Show All'
|
|
showOnlySelectText='Show Selected'
|
|
btnContextual='btn-info'
|
|
className='my-custom-class'
|
|
onClick={onClick} />
|
|
)
|
|
|
|
createCustomButtonGroup = (props: ButtonGroupProps) => (
|
|
<div className='my-custom-class'>
|
|
{props.showSelectedOnlyBtn}
|
|
{props.exportCSVBtn}
|
|
{props.insertBtn}
|
|
{props.deleteBtn}
|
|
<button type='button'
|
|
className={`btn btn-primary`}>
|
|
MyCustomBtn
|
|
</button>
|
|
</div>
|
|
)
|
|
|
|
createCustomSearchField = (props: SearchFieldProps) => (
|
|
<MySearchField
|
|
className='my-custom-class'
|
|
defaultValue={props.defaultValue}
|
|
placeholder={props.placeholder}
|
|
onKeyUp={props.onKeyUp}
|
|
/>
|
|
)
|
|
|
|
createCustomClearSearch = (onClick: (e: React.MouseEvent<{}>) => void) => (
|
|
<button
|
|
className='btn btn-success'
|
|
onClick={onClick}>
|
|
Clear Search
|
|
</button>
|
|
)
|
|
|
|
createCustomSearchPanel = (props: SearchPanelProps) => (
|
|
<div>
|
|
<div className='input-group'>
|
|
<span className='input-group-btn'>
|
|
{props.clearBtn}
|
|
</span>
|
|
</div>
|
|
{props.searchField}
|
|
</div>
|
|
)
|
|
|
|
createCustomToolBar = (props: ToolBarProps) => {
|
|
return (
|
|
<div style={{ margin: '15px' }}>
|
|
{props.components.btnGroup}
|
|
<div className='col-xs-8 col-sm-4 col-md-4 col-lg-2'>
|
|
{props.components.searchPanel}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
customConfirm = (next: () => void, dropRowKeys: ReadonlyArray<number>) => {
|
|
const dropRowKeysStr = dropRowKeys.join(',');
|
|
if (confirm(`(It's a custom confirm)Are you sure you want to delete ${dropRowKeysStr}?`)) {
|
|
next();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const selectRow: SelectRow = {
|
|
mode: 'checkbox',
|
|
showOnlySelected: true
|
|
};
|
|
const options: Options<Product> = {
|
|
insertBtn: this.createInsertButton,
|
|
deleteBtn: this.createDeleteButton,
|
|
exportCSVBtn: this.createExportCSVButton,
|
|
showSelectedOnlyBtn: this.createShowSelectedOnlyButton,
|
|
btnGroup: this.createCustomButtonGroup,
|
|
clearSearch: true,
|
|
clearSearchBtn: this.createCustomClearSearch,
|
|
searchField: this.createCustomSearchField,
|
|
searchPanel: this.createCustomSearchPanel,
|
|
toolBar: this.createCustomToolBar,
|
|
searchDelayTime: 3000,
|
|
handleConfirmDeleteRow: this.customConfirm
|
|
};
|
|
return (
|
|
<BootstrapTable data={products}
|
|
options={options}
|
|
selectRow={selectRow}
|
|
insertRow={true}
|
|
deleteRow={true}
|
|
exportCSV={true}
|
|
search={true}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expanding Rows & Selection
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/expandRow/expand-row-by-column.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/expandRow/expand-row-with-selection.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/expandRow/custom-expand-indicator.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/expandRow/custom-expand-class.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/expandRow/auto-collapse.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/expandRow/manage-expanding.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/all-select.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/custom-multi-select-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/default-select-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/select-bgcolor-dynamic-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/select-filter-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/select-hook-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/selection/select-row-class-table.js
|
|
*/
|
|
class ExpandRowExample extends React.Component<{}, {expanding: number[]}> {
|
|
isExpandableRow = (row: any) => row.id === 2;
|
|
|
|
expandComponent = (row: any) => (
|
|
<div>You expanded the row.</div>
|
|
)
|
|
|
|
expandColumnComponent = ({ isExpandableRow, isExpanded }: ExpandColumnComponentProps) => {
|
|
const content = isExpandableRow
|
|
? (isExpanded ? '(-)' : '(+)')
|
|
: ' ';
|
|
return (
|
|
<div> {content} </div>
|
|
);
|
|
}
|
|
|
|
handleExpand = (rowKey: number, isExpand: boolean) => {
|
|
if (isExpand) {
|
|
this.setState({ expanding: [...this.state.expanding] });
|
|
} else {
|
|
this.setState({ expanding: [...this.state.expanding.filter(id => id !== rowKey)] });
|
|
}
|
|
}
|
|
|
|
onSelectAll = (isSelected: boolean) => (isSelected) ? products.map(row => row.id) : [];
|
|
|
|
onSelect = (row: Product, isSelected: boolean, e: React.MouseEvent<any>, rowIndex: number) => {
|
|
const rowStr = `id: "${row.id}", name: ${row.name}, price: ${row.price}`;
|
|
console.log(e);
|
|
alert(`Selected: ${isSelected}, rowIndex: ${rowIndex}, row: ${rowStr}`);
|
|
}
|
|
|
|
customMultiSelect = (props: CustomSelectProps) => (
|
|
<div className='checkbox-personalized'>
|
|
<input
|
|
type={props.type}
|
|
name={'checkbox' + props.rowIndex}
|
|
id={'checkbox' + props.rowIndex}
|
|
checked={props.checked}
|
|
disabled={props.disabled}
|
|
onChange={e => props.onChange(e, props.rowIndex)}
|
|
ref={input => {
|
|
if (input) {
|
|
input.indeterminate = props.indeterminate;
|
|
}
|
|
}} />
|
|
<label htmlFor={'checkbox' + props.rowIndex}>
|
|
<div className='check'></div>
|
|
</label>
|
|
</div>
|
|
)
|
|
|
|
selectedRowClass = (row: Product, isSelect: boolean) =>
|
|
(isSelect)
|
|
? ((row.id >= 3) ? 'bigger-than-three-select-row' : 'less-than-three-select-row')
|
|
: ''
|
|
|
|
render() {
|
|
const options: Options<Product> = {
|
|
expandRowBgColor: 'rgb(242, 255, 163)',
|
|
expandBy: 'column',
|
|
onlyOneExpanding: true,
|
|
expanding: this.state.expanding,
|
|
onExpand: this.handleExpand,
|
|
expandParentClass: 'custom-expand-parent',
|
|
expandBodyClass: (row, rowIndex, isExpanding) => {
|
|
return (!isExpanding)
|
|
? 'current-is-hidden'
|
|
: (rowIndex > 1)
|
|
? 'custom-expand-body-1'
|
|
: 'custom-expand-body-0';
|
|
}
|
|
};
|
|
const selectRow: SelectRow<Product> = {
|
|
mode: 'checkbox',
|
|
bgColor: (row: Product, isSelect: boolean) =>
|
|
(isSelect)
|
|
? ((row.id < 2) ? 'blue' : ((row.id < 4) ? 'red' : 'yellow'))
|
|
: null,
|
|
clickToSelect: true, // click to select, default is false
|
|
clickToExpand: true, // click to expand row, default is false
|
|
onSelect: this.onSelect,
|
|
onSelectAll: this.onSelectAll,
|
|
customComponent: this.customMultiSelect,
|
|
hideSelectColumn: false,
|
|
selected: [0, 2],
|
|
showOnlySelected: true,
|
|
onlyUnselectVisible: true,
|
|
className: this.selectedRowClass,
|
|
columnWidth: '60px',
|
|
unselectable: [1, 3]
|
|
};
|
|
const expandColumnOptions: ExpandColumnOptions = {
|
|
expandColumnVisible: true,
|
|
expandColumnBeforeSelectColumn: false,
|
|
expandColumnComponent: this.expandColumnComponent,
|
|
columnWidth: 50
|
|
};
|
|
return (
|
|
<BootstrapTable data={products}
|
|
options={options}
|
|
expandableRow={this.isExpandableRow}
|
|
expandComponent={this.expandComponent}
|
|
expandColumnOptions={expandColumnOptions}
|
|
autoCollapse={{ sort: true, search: true, filter: true }}
|
|
selectRow={selectRow}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' expandable={false}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price' expandable={false}>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Table mouse enter/leave events
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/others/mouse-event-table.js
|
|
*/
|
|
class MouseEventTable extends React.Component {
|
|
render() {
|
|
const options: Options<Product> = {
|
|
onMouseLeave: () => { console.log('mouse left table'); },
|
|
onMouseEnter: () => { console.log('mouse entered table'); },
|
|
onRowMouseOut: (row: any, e: React.MouseEvent<{}>) => {
|
|
console.log(e);
|
|
console.log('mouse left row ' + row.id);
|
|
},
|
|
onRowMouseOver: (row: any, e: React.MouseEvent<{}>) => {
|
|
console.log(e);
|
|
console.log('mouse entered row ' + row.id);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<BootstrapTable data={products} options={options}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hidden on insert modal with async row error callback & read-only field
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/advance/hide-on-insert-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/advance/insert-error-handle-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/advance/validator-table-read-only.js
|
|
*/
|
|
class HideOnInsertTable extends React.Component {
|
|
handleAddRowWithASyncError = (row: any, colInfo: ReadonlyArray<ColumnDescription<Job>>, errorCallback: (msg: string) => void) => {
|
|
setTimeout(() => {
|
|
errorCallback('Sorry, There\'s some error happend');
|
|
}, 5000);
|
|
return false;
|
|
}
|
|
render() {
|
|
const options: Options<Job> = {
|
|
onAddRow: this.handleAddRowWithASyncError
|
|
};
|
|
return (
|
|
<BootstrapTable data={jobs} insertRow={true} options={options}>
|
|
<TableHeaderColumn dataField='id' isKey={true} autoValue>Job ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' hiddenOnInsert>Job Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='type' editable={{ type: 'select', options: { values: jobTypes }, readOnly: true }}>Job Type</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='active' editable={{ type: 'checkbox', options: { values: 'Y:N' } }}>Active</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Edit field types
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/advance/edit-type-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/advance/insert-default-value-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/advance/insert-error-handle-table.js
|
|
*/
|
|
class EditTypeTable extends React.Component {
|
|
formatType = (cell: string) => `TYPE_${cell}`;
|
|
|
|
jobTypes = (row: ExtendedJob) => (row.id > 2) ? ['A', 'B'] : ['B', 'C', 'D', 'E'];
|
|
|
|
handleAddRowWithSyncError = () => {
|
|
return 'Sorry, There\'s some error happend';
|
|
}
|
|
|
|
render() {
|
|
const attrs = {
|
|
rows: 10,
|
|
onKeyDown: () => { console.log('keydown event trigger'); }
|
|
};
|
|
const cellEditProp: CellEdit<ExtendedJob> = {
|
|
mode: 'click',
|
|
blurToSave: true
|
|
};
|
|
const options: Options<ExtendedJob> = {
|
|
onAddRow: this.handleAddRowWithSyncError
|
|
};
|
|
return (
|
|
<BootstrapTable data={extendedJobs} cellEdit={cellEditProp} options={options} insertRow={true}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Job ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' editable={{ type: 'textarea', attrs, defaultValue: 'Default Job Name' }}>Job Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='type1' dataFormat={this.formatType} editable={{ type: 'select', options: { values: jobTypes }, defaultValue: 'C' }}>Job Type1</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='type2' editable={{ type: 'select', options: { values: this.jobTypes }, defaultValue: 'A' }}>Job Type2</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='active' editable={{ type: 'checkbox', options: { values: 'Y:N' }, defaultValue: 'N' }}>Active</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='datetime' editable={{ type: 'datetime', defaultValue: new Date() }}>Date Time</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* React component format for column.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-format/react-column-format-table.js
|
|
*/
|
|
class ActiveFormatter extends React.Component<{ active: boolean }> {
|
|
render() {
|
|
return (
|
|
<input type='checkbox' checked={this.props.active} />
|
|
);
|
|
}
|
|
}
|
|
|
|
export default class ReactColumnFormatTable extends React.Component {
|
|
activeFormatter = (cell: boolean, row: ExtendedJob) => (<ActiveFormatter active={cell} />);
|
|
render() {
|
|
return (
|
|
<BootstrapTable data={extendedJobs}>
|
|
<TableHeaderColumn dataField='id' isKey={true} hidden={true}>Job ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Job Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='active' dataFormat={this.activeFormatter}>Active</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format Extra Data
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-format/extra-data-column-format-table.js
|
|
*/
|
|
class ExtraDataColumnFormatTable extends React.Component {
|
|
enumFormatter = (cell: number, row: Product, enumObject: {[id: number]: string}) => enumObject[cell];
|
|
render() {
|
|
return (
|
|
<BootstrapTable data={products} >
|
|
<TableHeaderColumn dataField='id' isKey={true} dataAlign='center'>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' headerAlign='center' dataAlign='right'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='quality' dataFormat={enumFormatter} formatExtraData={qualityType}>Product Quality</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='inStock' dataFormat={enumFormatter} formatExtraData={inStockStatus}>Product Stock Status</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Column titles
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column/column-title-table.js
|
|
*/
|
|
class ColumnAlignTable extends React.Component {
|
|
customTitle = (cell: number, row: any, rowIndex: number, colIndex: number) => `${row.name} for ${cell}`;
|
|
|
|
render() {
|
|
const options: Options<Product> = {
|
|
exportCSVText: 'my_export',
|
|
insertText: 'my_insert',
|
|
deleteText: 'my_delete',
|
|
saveText: 'my_save',
|
|
closeText: 'my_close'
|
|
};
|
|
return (
|
|
<BootstrapTable data={products} options={options}>
|
|
<TableHeaderColumn dataField='id' isKey={true} columnTitle={this.customTitle} >Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' width='50%' headerTitle={false} columnTitle={true}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price' columnTitle='Hard code string' headerText='Custom Title'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Table Footer
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/footer/footer-table.js
|
|
*/
|
|
class FooterTable extends React.Component {
|
|
render() {
|
|
const footerData: FooterData[][] = [
|
|
[
|
|
{ label: 'Total', columnIndex: 0 },
|
|
{
|
|
label: 'Total value',
|
|
columnIndex: 2,
|
|
align: 'right',
|
|
formatter: (tableData: Array<{price: number}>) => {
|
|
let label = 0;
|
|
for (let i = 0, tableDataLen = tableData.length; i < tableDataLen; i++) {
|
|
label += tableData[i].price;
|
|
}
|
|
return (
|
|
<strong>{label}</strong>
|
|
);
|
|
}
|
|
}
|
|
]
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<BootstrapTable data={products} footerData={footerData} footer pagination search keyBoardNav>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Keyboard navigation
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/keyboard-nav/custom-style-nav-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/keyboard-nav/custom-style-nav-with-cell-edit-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/keyboard-nav/disable-click-to-nav-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/keyboard-nav/enter-to-edit-with-nav-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/keyboard-nav/nav-with-expand-table.js
|
|
*/
|
|
class CustomStyleNavTable extends React.Component {
|
|
customStyle = (cell: any, row: any) => {
|
|
return {
|
|
backgroundColor: 'red'
|
|
};
|
|
}
|
|
|
|
render() {
|
|
const cellEdit: CellEdit<Product> = {
|
|
mode: 'click',
|
|
blurToSave: true
|
|
};
|
|
const keyBoardNav = {
|
|
customStyleOnEditCell: this.customStyle,
|
|
customStyle: this.customStyle,
|
|
clickToNav: false,
|
|
enterToEdit: true,
|
|
enterToExpand: false
|
|
};
|
|
return (
|
|
<BootstrapTable data={products} keyBoardNav={keyBoardNav} cellEdit={cellEdit}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Table body styles
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/style/inline-style-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/style/table-class-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/style/td-class-string-table.js
|
|
*/
|
|
class TrClassStringTable extends React.Component {
|
|
render() {
|
|
return (
|
|
<BootstrapTable insertRow exportCSV data={products}
|
|
tableStyle={{ border: '#0000FF 2.5px solid' }}
|
|
containerStyle={{ border: '#FFBB73 2.5px solid' }}
|
|
headerStyle={{ border: 'red 1px solid' }}
|
|
bodyStyle={{ border: 'green 1px solid' }}
|
|
tableHeaderClass='my-header-class'
|
|
tableBodyClass='my-body-class'
|
|
containerClass='my-container-class'
|
|
tableContainerClass='my-table-container-class'
|
|
headerContainerClass='my-header-container-class'
|
|
bodyContainerClass='my-body-container-class'
|
|
trClassName='tr-string-example'>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' columnClassName='td-column-string-example'>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price' className='td-header-string-example'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Table styles set by functions.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/style/td-class-function-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/style/tr-class-function-table.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/style/tr-style-table.js
|
|
*/
|
|
class TrClassFunctionTable extends React.Component {
|
|
headerColumnClassNameFormat = () => 'th-string-example';
|
|
|
|
columnClassNameFormat = (fieldValue: string | number, row: Product, rowIdx: number, colIdx: number) =>
|
|
rowIdx % 2 === 0 ? 'td-column-function-even-example' : 'td-column-function-odd-example'
|
|
|
|
trClassFormat = (rowData: ReadonlyArray<Product>, rIndex: number) =>
|
|
rIndex % 3 === 0 ? 'tr-function-example' : ''
|
|
|
|
trStyle = (row: Product, rowIndex: number) => ({ backgroundColor: '#FFFAFA' });
|
|
|
|
render() {
|
|
return (
|
|
<BootstrapTable data={products} trClassName={this.trClassFormat} trStyle={this.trStyle} >
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' className={this.headerColumnClassNameFormat}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price' columnClassName={this.columnClassNameFormat}>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Filter types.
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/all-filters.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/date-filter-programmatically.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/number-filter-programmatically.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/regex-filter-programmatically.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/select-filter-programmatically.js
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/text-filter-programmatically.js
|
|
*/
|
|
class AllFilters extends React.Component {
|
|
name1: TableHeaderColumn | null;
|
|
name2: TableHeaderColumn | null;
|
|
quality: TableHeaderColumn | null;
|
|
price: TableHeaderColumn | null;
|
|
satisfaction: TableHeaderColumn | null;
|
|
inStockDate: TableHeaderColumn | null;
|
|
|
|
handlerClickCleanFiltered = () => {
|
|
this.name1.cleanFiltered();
|
|
this.name2.cleanFiltered();
|
|
this.quality.cleanFiltered();
|
|
this.price.cleanFiltered();
|
|
this.satisfaction.cleanFiltered();
|
|
this.inStockDate.cleanFiltered();
|
|
}
|
|
|
|
applyFiltersProgramatically = () => {
|
|
this.name1.applyFilter('Item 1');
|
|
this.name2.applyFilter('[name]');
|
|
this.quality.applyFilter(1);
|
|
this.price.applyFilter({
|
|
number: 10.5,
|
|
comparator: '<='
|
|
});
|
|
this.satisfaction.applyFilter({
|
|
number: 2,
|
|
comparator: '>'
|
|
});
|
|
this.inStockDate.applyFilter({
|
|
date: new Date(2015, 0, 1),
|
|
comparator: '='
|
|
});
|
|
}
|
|
|
|
dateFormatter = (cell: Date, row: any) => {
|
|
if (typeof cell !== 'object') {
|
|
cell = new Date(cell);
|
|
}
|
|
|
|
return `${('0' + cell.getDate()).slice(-2)}/${('0' + (cell.getMonth() + 1)).slice(-2)}/${cell.getFullYear()}`;
|
|
}
|
|
|
|
render() {
|
|
const satisfaction = [0, 1, 2, 3, 4, 5];
|
|
return (
|
|
<BootstrapTable ref='table' data={products}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>
|
|
Product ID
|
|
<br /><a onClick={this.handlerClickCleanFiltered} style={{ cursor: 'pointer' }}>clear filters</a>
|
|
</TableHeaderColumn>
|
|
<TableHeaderColumn ref={(node) => { this.name1 = node; }} dataField='name' filter={{ type: 'TextFilter', placeholder: 'Please enter a value' }}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn ref={(node) => { this.name2 = node; }} dataField='name' filter={{ type: 'RegexFilter', placeholder: 'Please enter a regex' }}>Product Name</TableHeaderColumn>
|
|
< TableHeaderColumn ref={(node) => { this.quality = node; }} dataField='quality' filter={{ type: 'SelectFilter', options: qualityType }}
|
|
dataFormat={enumFormatter} formatExtraData={qualityType}>Product Quality</TableHeaderColumn>
|
|
<TableHeaderColumn ref={(node) => { this.price = node; }} dataField='price' filter={{ type: 'NumberFilter', delay: 1000 }}>Product Price</TableHeaderColumn>
|
|
< TableHeaderColumn ref= {(node) => { this.satisfaction = node; }} dataField='satisfaction' filter={{ type: 'NumberFilter', options: satisfaction }}>Buyer Satisfaction</TableHeaderColumn>
|
|
< TableHeaderColumn ref= {(node) => { this.inStockDate = node; }} dataField='inStockDate' filter={{ type: 'DateFilter' }} dataFormat={this.dateFormatter}>In Stock From</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set Array filter programatically
|
|
* @see https://github.com/AllenFang/react-bootstrap-table/blob/master/examples/js/column-filter/array-filter-programmatically.js
|
|
*/
|
|
class ProgrammaticallyArrayFilter extends React.Component {
|
|
table: BootstrapTable | null;
|
|
|
|
/* Filtering passing an array of values */
|
|
handleBtnClick = () => {
|
|
this.table.handleFilterData({
|
|
name: { type: 'ArrayFilter', value: ['Item name 3', 'Item name 4'] },
|
|
price: { type: 'ArrayFilter', value: [2100, 2104] }
|
|
});
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
<button onClick={this.handleBtnClick} className='btn btn-default'>Click to apply text filter</button>
|
|
<BootstrapTable ref={(node) => { this.table = node; }} data={products}>
|
|
<TableHeaderColumn dataField='id' isKey={true}>Product ID</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='name' filter={{ type: 'TextFilter', delay: 1000 }}>Product Name</TableHeaderColumn>
|
|
<TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
|
|
</BootstrapTable>
|
|
</div>
|
|
);
|
|
}
|
|
}
|