diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..83c6c8a --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["react", "es2015", "stage-0"] +} \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ca70c65 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,27 @@ +{ + "extends": "airbnb", + "parser": "babel-eslint", + "env": { + "browser": true, + "jest": true, + "node": true + }, + "plugins": [ + "react" + ], + "rules": { + "comma-dangle": ["error", "never"], + "react/jsx-curly-spacing": [2, "always"], + "react/forbid-prop-types": 0, + "react/jsx-filename-extension": 0, + "react/jsx-space-before-closing": 0, + "react/jsx-tag-spacing": ["error", { "beforeSelfClosing": "always" }], + "import/extensions": 0, // skip import extensions + "import/no-unresolved": [0, { "ignore": ["^react-bootstrap-table"] }], // monorepo setup + "import/prefer-default-export": 0, + "import/no-extraneous-dependencies": 0 + }, + "globals": { + "jest": false + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b512c09..a762eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,24 @@ -node_modules \ No newline at end of file +# node +node_modules +package-lock.json + +# testing +coverage +.eslintcache + +# misc +.DS_Store +.vscode + +# logs +lerna-debug.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# gh-pages +storybook-static + +# build +lib +dist diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c1eb549 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +node_modules +.DS_Store +*~ +*.sublime-project +*.sublime-workspace +*.idea +*.iml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..02a5492 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: node_js + +node_js: + - "7" + - "6" + +cache: + yarn: true + +branches: + only: + # skip master branch when it's under development phase + # - master + - develop + +before_install: + - curl -o- -L https://yarnpkg.com/install.sh | bash -s + - export PATH="$HOME/.yarn/bin:$PATH" + +install: yarn install diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9d8ed01 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing + +# Issues +Before opening an issue, please make sure your problem or request doesn't exist. When opening an issue, please provide the following information if possible: + +* Example code or repo (please keep it simple and minimal) +* Steps to reproduce. +* `react-bootstrap-table2` version. + +Additionally, asking questions and requesting new features are welcomed via [issue tracker](https://github.com/react-bootstrap-table/react-bootstrap-table2/issues). + +# Pull Requests +Check [here](./docs/development.md) for getting started with development. + +* We recommend filing an [issue](https://github.com/react-bootstrap-table/react-bootstrap-table2/issues) before you implement any new features. +* Please ensure that all the test suites have passed before submitting a PR. Besides, always test the actual business logic. +* If your PR is trying to fix a bug, please describe the details as much as you could and tag the bug number with hashtag. + +# For the members of react-bootstrap-table2 org +* Please convert the ticket to issue when the ticket has moved from `Backlog` to `Ready`. +* Please update the docs if any API, feature or component props was changed or added. The code and docs should always be in sync. +* Please add a story example if any new feature was implemented. \ No newline at end of file diff --git a/README.md b/README.md index 1d5d13d..1c2f4e5 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,46 @@ # react-bootstrap-table2 Rebuilt [react-bootstrap-table](https://github.com/AllenFang/react-bootstrap-table) -## The problems/features I want to solve -* Performance -* Fully compatiable with bootstrap 3 and 4(`react-bootstrap-table@4.0.0` already done) -* Clean Code and Testing -* Decrease the size of bundled file -* **Split module/functionality from core module, make core module more lightweight** -* Use [`storybook`](https://github.com/storybooks/storybook) to build examples -* Support the aggregation(summary) view -* Support the table footer -* Support column/row span on header and body -* Support sticky header -* Support table section([react-bootstrap-table#721](https://github.com/AllenFang/react-bootstrap-table/pull/721)) -* Handle events well -* Fix unalign issues -* Make **stateless** table more easy to use(`react-bootstrap-table` alread have `remote` mode but have some bugs) -* Customizable table -* Support the nested data([react-bootstrap-table#50](https://github.com/AllenFang/react-bootstrap-table/issues/50◊)) -* Consider to support column resize -* Consider to make animation on `react-bootstrap-table2` more easy +> `react-bootstrap-table2`'s npm module name is [**`react-bootstrap-table-next`**](https://www.npmjs.com/package/react-bootstrap-table-next) due to some guys already used it ;( -## The feature may lost on react-bootstrap-table -* Have a great chance that I don't support the vertical scrollbar on table \ No newline at end of file +`react-bootstrap-table2` separate some functionalities from core modules to other modules like following: + +* [`react-bootstrap-table2-next`](https://www.npmjs.com/package/react-bootstrap-table-next) + * Core table module, include sorting and row selection +* [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) + * Column filter Addons +* [`react-bootstrap-table2-editor`](https://www.npmjs.com/package/react-bootstrap-table2-editor) + * Cell Editing Addons +* [`react-bootstrap-table2-paginator`](https://www.npmjs.com/package/react-bootstrap-table2-paginator) + * Pagination Addons +* [`react-bootstrap-z-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay) + * Overlay/Loading Addons + +This can help your application with less bundled size and also help us have clean design to avoid handling to much logic in kernal module(SRP). + +## Migration +If you are the user from legacy [`react-bootstrap-table`](https://github.com/AllenFang/react-bootstrap-table/), please have a look on [this](./docs/migration.md). + +## Usage +See [getting started](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/getting-started.html). + +## Online Demo +See `react-bootstrap-table2` [storybook](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html). + +## Roadmap +See [release plans](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/release-plan.html). + +## Development +Please check [development guide](./docs/development.md). + +## How should I run storybook example in my local? + +```sh +$ git clone https://github.com/react-bootstrap-table/react-bootstrap-table2.git +$ cd react-bootstrap-table2 +$ yarn install +$ yarn storybook +$ Go to localhost:6006 +``` + +**Storybook examples: [`packages/react-bootstrap-table2-example/examples`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-example/examples)** \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..828cae1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,277 @@ +# Documentation + +## BootstrapTable Props + +#### Required +* [keyField (**required**)](#keyField) +* [data (**required**)](#data) +* [columns (**required**)](#columns) + +#### Optional +* [remote](#remote) +* [loading](#loading) +* [caption](#caption) +* [striped](#striped) +* [bordered](#bordered) +* [hover](#hover) +* [condensed](#condensed) +* [cellEdit](#cellEdit) +* [selectRow](#selectRow) +* [rowStyle](#rowStyle) +* [rowClasses](#rowClasses) +* [rowEvents](#rowEvents) +* [defaultSorted](#defaultSorted) +* [pagination](#pagination) +* [filter](#filter) +* [onTableChange](#onTableChange) + +### keyField(**required**) - [String] +Tells `react-bootstrap-table2` which column is unique. + +### data(**required**) - [Array] +Provides data for your table. It accepts a single Array object. + +### columns(**required**) - [Object] +Accepts a single Array object, please see [columns definition](./columns.md) for more detail. + +### remote - [Bool | Object] +Default is `false`, if enable`remote`, you are suppose to handle all the table change events, like: pagination, insert, filtering etc. +This is a chance that you can connect to your remote server or database to manipulate your data. +For flexibility reason, you can control what functionality should be handled on remote via a object return: + +```js +remote={ { + filter: true, + pagination: true, + filter: true, + sort: true, + cellEdit: true +} } +``` + +In above case, only column filter will be handled on remote. + +> Note: when remote is enable, you are suppose to give [`onTableChange`](#onTableChange) prop on `BootstrapTable` +> It's the only way to communicate to your remote server and update table states. + +A special case for remote pagination: +```js +remote={ { pagination: true, filter: false, sort: false } } +``` + +There is a apecial case for remote pagination, even you only specified the paignation need to handle as remote, `react-bootstrap-table2` will handle all the table changes(filter, sort etc) as remote mode, because `react-bootstrap-table2` only know the data of current page, but filtering, searching or sort need to work on overall datas. + +### loading - [Bool] +Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled. +When `loading` is `true`, `react-bootstrap-table2` will attend to render a overlay on table via [`overlay`](#overlay) prop, if [`overlay`](#overlay) prop is not given, `react-bootstrap-table2` will ignore the overlay rendering. + +### overlay - [Function] +`overlay` accept a factory funtion which should returning a higher order component. By default, `react-bootstrap-table2-overlay` can be a good option for you: + +```sh +$ npm install react-bootstrap-table2-overlay +``` +```js +import overlayFactory from 'react-bootstrap-table2-overlay'; + + +``` + +Actually, `react-bootstrap-table-overlay` is depends on [`react-loading-overlay`](https://github.com/derrickpelletier/react-loading-overlay) and `overlayFactory` just a factory function and you can pass any props which available for `react-loading-overlay`: + +```js +overlay={ overlayFactory({ spinner: true, background: 'rgba(192,192,192,0.3)' }) } +``` + +### caption - [String | Node] +Same as HTML [caption tag](https://www.w3schools.com/TAgs/tag_caption.asp), you can set it as String or a React JSX. + +### striped - [Bool] +Same as bootstrap `.table-striped` class for adding zebra-stripes to a table. +### bordered - [Bool] +Same as bootstrap `.table-bordered` class for adding borders to a table and table cells. +### hover - [Bool] +Same as bootstrap `.table-hover` class for adding mouse hover effect (grey background color) on table rows. +### condensed - [Bool] +Same as bootstrap `.table-condensed` class for making a table more compact by cutting cell padding in half. + +### cellEdit - [Object] +Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for more detail. + +### selectRow - [Object] +Makes table rows selectable, please see [selectRow definition](./row-selection.md) for more detail. + +### rowStyle = [Object | Function] +Custom the style of table rows: + +```js + +``` + +This prop also accept a callback function for flexible to custom row style: + +```js +const rowStyle = (row, rowIndex) => { + return { ... }; +}; + + +``` + +### rowClasses = [String | Function] +Custom the style of table rows: + +```js + +``` + +This prop also accept a callback function for flexible to custom row style: + +```js +const rowClasses = (row, rowIndex) => { + return 'custom-row-class'; +}; + + +``` + +### rowEvents - [Object] +Custom the events on row: + +```js +const rowEvents = { + onClick: (e) => { + .... + } +}; + +``` + +### defaultSorted - [Array] +`defaultSorted` accept an object array which allow you to define the default sort columns when first render. + +```js +const defaultSorted = [{ + dataField: 'name', // if dataField is not match to any column you defined, it will be ignored. + order: 'desc' // desc or asc +}]; +``` + +### pagination - [Object] +`pagination` allow user to render a pagination panel on the bottom of table. But pagination funcitonality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-paginator` additionaly. + +```sh +$ npm install react-bootstrap-table2-paginator --save +``` + +After installation of `react-bootstrap-table2-paginator`, you can enable pagination on `react-bootstrap-table2` easily: + +```js +import paginator from 'react-bootstrap-table2-paginator'; + +// omit... + + +``` + +`paginator` is a function actually and allow to pass some pagination options, following we list all the available options: + +```js +paginator({ + page, // Specify the current page. It's necessary when remote is enabled + sizePerPage, // Specify the size per page. It's necessary when remote is enabled + totalSize, // Total data size. It's necessary when remote is enabled + pageStartIndex: 0, // first page will be 0, default is 1 + paginationSize: 3, // the pagination bar size, default is 5 + sizePerPageList: [ { + text: '5', value: 5 + }, { + text: '10', value: 10 + }, { + text: 'All', value: products.length + } ], // A numeric array is also available: [5, 10]. the purpose of above example is custom the text + withFirstAndLast: false, // hide the going to first and last page button + alwaysShowAllBtns: true, // always show the next and previous page button + firstPageText: 'First', // the text of first page button + prePageText: 'Prev', // the text of previous page button + nextPageText: 'Next', // the text of next page button + lastPageText: 'Last', // the text of last page button + nextPageTitle: 'Go to next', // the title of next page button + prePageTitle: 'Go to previous', // the title of previous page button + firstPageTitle: 'Go to first', // the title of first page button + lastPageTitle: 'Go to last', // the title of last page button + hideSizePerPage: true, // hide the size per page dorpdown + hidePageListOnlyOnePage: true, // hide pagination bar when only one page, default is false + onPageChange: (page, sizePerPage) => {}, // callback function when page was changing + onSizePerPageChange: (sizePerPage, page) => {}, // callback function when page size was changing +}) +``` + +### filter - [Object] +`filter` allow user to filter data by column. However, filter funcitonality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-filter` firstly. + +```sh +$ npm install react-bootstrap-table2-filter --save +``` + +After installation of `react-bootstrap-table2-filter`, you can configure filter on `react-bootstrap-table2` easily: + +```js +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +// omit... +const columns = [ { + dataField: 'id', + text: 'Production ID' +}, { + dataField: 'name', + text: 'Production Name', + filter: textFilter() // apply text filter +}, { + dataField: 'price', + text: 'Production Price' +} ]; + +``` + +### onTableChange - [Function] +This callback function will be called when [`remote`](#remote) enabled only. + +```js +const onTableChange = (type, newState) => { + // handle any data change here +} + +``` + +There's only two arguments will be passed to `onTableChange`: `type` and `newState`: + +`type` is tell you what kind of functionality to trigger this table's change: available values at the below: + +* `filter` +* `pagination` +* `sort` +* `cellEdit` + +Following is a shape of `newState` + +```js +{ + page, // newest page + sizePerPage, // newest sizePerPage + sortField, // newest sort field + sortOrder, // newest sort order + filters, // an object which have current filter status per column + data, // when you enable remote sort, you may need to base on data to sort if data is filtered/searched + cellEdit: { // You can only see this prop when type is cellEdit + rowId, + dataField, + newValue + } +} +``` \ No newline at end of file diff --git a/docs/cell-edit.md b/docs/cell-edit.md new file mode 100644 index 0000000..8502c8c --- /dev/null +++ b/docs/cell-edit.md @@ -0,0 +1,74 @@ +# Cell Editing +Before start to use cell edit, please remember to install `react-bootstrap-table2-editor` + +```sh +$ npm install react-bootstrap-table2-editor --save +``` + +# Properties on cellEdit prop +* [mode (**required**)](#mode) +* [blurToSave](#blurToSave) +* [nonEditableRows](#nonEditableRows) +* [timeToCloseMessage](#timeToCloseMessage) +* [beforeSaveCell](#beforeSaveCell) +* [afterSaveCell](#afterSaveCell) +* [errorMessage](#errorMessage) +* [onErrorMessageDisappear](#onErrorMessageDisappear) + +## cellEdit - [Object] +Assign a valid `cellEdit` object can enable the cell editing on the cell. The default usage is click/dbclick to trigger cell editing and press `ENTER` to save cell or press `ESC` to cancel editing. + +> Note: The `keyField` column can't be edited + +Following is the shape of `cellEdit` object: +```js +{ + mode: 'click', + blurToSave: true, + timeToCloseMessage: 2500, + errorMessage: '', + beforeSaveCell: (oldValue, newValue, row, column) => { ... }, + afterSaveCell: (oldValue, newValue, row, column) => { ... }, + onErrorMessageDisappear: () => { ... }, + nonEditableRows: () => { ... } +} +``` + +### cellEdit.mode - [String] +`cellEdit.mode` possible value is `click` and `dbclick`. **It's required value** that tell `react-bootstrap-table2` how to trigger the cell editing. + +### cellEdit.blurToSave - [Bool] +Default is `false`, enable it will be able to save the cell automatically when blur from the cell editor. + +### cellEdit.nonEditableRows - [Function] +`cellEdit.nonEditableRows` accept a callback function and expect return an array which used to restrict all the columns of some rows as non-editable. So the each item in return array should be rowkey(`keyField`) + +### cellEdit.timeToCloseMessage - [Function] +If a [`column.validator`](./columns.md#validator) defined and the new value is invalid, `react-bootstrap-table2` will popup a alert at the bottom of editor. `cellEdit.timeToCloseMessage` is a chance to let you decide how long the alert should be stay. Default is 3000 millisecond. + +### cellEdit.beforeSaveCell - [Function] +This callback function will be called before triggering cell update. + +```js +const cellEdit = { + // omit... + beforeSaveCell: (oldValue, newValue, row, column) => { ... } +} +``` + +### cellEdit.afterSaveCell - [Function] +This callback function will be called after updating cell. + +```js +const cellEdit = { + // omit... + afterSaveCell: (oldValue, newValue, row, column) => { ... } +}; +``` + +### cellEdit.errorMessage - [String] +This prop is not often used. Only used when you want to keep the error message in your application state and also handle the cell editing on remote mode. + +### cellEdit.onErrorMessageDisappear - [Function] +This callback function will be called when error message discard so that you can sync the newest error message to your state if you have. + diff --git a/docs/columns.md b/docs/columns.md new file mode 100644 index 0000000..a18a073 --- /dev/null +++ b/docs/columns.md @@ -0,0 +1,580 @@ +# Definition of columns props on BootstrapTable + +Available properties in a column object: + +#### Required +* [dataField (**required**)](#dataField) +* [text (**required**)](#text) + +#### Optional +* [hidden](#hidden) +* [formatter](#formatter) +* [formatExtraData](#formatExtraData) +* [sort](#sort) +* [sortFunc](#sortFunc) +* [classes](#classes) +* [style](#style) +* [title](#title) +* [events](#events) +* [align](#align) +* [attrs](#attrs) +* [headerFormatter](#headerFormatter) +* [headerClasses](#headerClasses) +* [headerStyle](#headerStyle) +* [headerTitle](#headerTitle) +* [headerEvents](#headerEvents) +* [headerAlign](#headerAlign) +* [headerAttrs](#headerAttrs) +* [headerSortingClasses](#headerSortingClasses) +* [headerSortingStyle](#headerSortingStyle) +* [editable](#editable) +* [validator](#validator) +* [editCellStyle](#editCellStyle) +* [editCellClasses](#editCellClasses) +* [filter](#filter) +* [filterValue](#filterValue) + +Following is a most simplest and basic usage: + +```js +const rows = [ { id: 1, name: '...', price: '102' } ]; +const columns = [ { + dataField: 'id', + text: 'Production ID' + }, { + dataField: 'name', + text: 'Production Name' + }, { + dataField: 'price', + text: 'Production Price' + } +]; +``` + +Let's introduce the definition of column object + +## column.dataField (**required**) - [String] +Use `dataField` to specify what field should be apply on this column. If your raw data is nested, for example: + +```js +const row = { + id: 'A001', + address: { + postal: '1234-12335', + city: 'Chicago' + } +} +``` +You can use `dataField` with dot(`.`) to describe nested object: + +```js +dataField: 'address.postal' +dataField: 'address.city' +``` + +## column.text (**required**) - [String] +`text` will be the column text in header column by default, if your header is not only text or you want to customize the header column, please check [`column.headerFormatter`](#headerFormatter) + +## column.hidden - [Bool] +`hidden` allow you to hide column when `true` given. + +## column.formatter - [Function] +`formatter` allow you to customize the table column and only accept a callback function which take four arguments and a JSX/String are expected for return. + +* `cell` +* `row` +* `rowIndex` +* [`formatExtraData`](#formatExtraData) + +## column.headerFormatter - [Function] +`headerFormatter` allow you to customize the header column and only accept a callback function which take three arguments and a JSX/String are expected for return. + +* `column`: current column object itself +* `colIndex`: index of current column +* `components`: an object which contain all of other react element, like sort caret or filter etc. + +The third argument: `components` have following specified properties: +```js +{ + sortElement, // sort caret element, it will not be undefined when you enable sort on this column + filterElement // filter element, it will not be undefined when you enable column.filter on this column +} +``` + +## column.formatExtraData - [Any] +It's only used for [`column.formatter`](#formatter), you can define any value for it and will be passed as fourth argument for [`column.formatter`](#formatter) callback function. + +## column.sort - [Bool] +Enable the column sort via a `true` value given. + +## column.sortFunc - [Function] +`column.sortFunc` only work when `column.sort` is enable. `sortFunc` allow you to define your sorting algorithm. This callback function accept four arguments: + +```js +{ + // omit... + sort: true, + sortFunc: (a, b, order, dataField) => { + if (order === 'asc') return a - b; + else return b - a; + } +} +``` +> The possible value of `order` argument is **`asc`** and **`desc`**. + +## column.classes - [String | Function] +It's availabe to have custom class on table column: + +```js +{ + // omit... + classes: 'id-custom-cell' +} +``` +In addition, `classes` also accept a callback function which have more power to custom the css class on each columns. This callback function take **4** arguments and a `String` is expected to return:: + + +```js +{ + classes: function callback(cell, row, rowIndex, colIndex) { ... } +} +``` + +**Parameters** +* `cell`: The value of current cell. +* `row`: The value of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `String` will be the result as element class. + +## column.headerClasses - [String | Function] +It's similar to [`column.classes`](#classes), `headerClasses` is availabe to have customized class on table header column: + +```js +{ + // omit... + headerClasses: 'id-custom-cell' +} +``` +Furthermore, it also accept a callback function which takes 2 arguments and a `String` is expect to return: + +```js +{ + headerClasses: function callback(column, colIndex) { ... } +} +``` + +**Parameters** +* `column`: The value of current column. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `String` will be the result of element headerClasses. + +## column.style - [Object | Function] +It's availabe to have custom style on table column: + +```js +{ + // omit... + style: { backgroundColor: 'green' } +} +``` + +In addition, similar to [`column.classes`](#classes), `style` also accept a callback function which have more power to customize the `inline style` on each columns. This callback function takes **4** arguments and an `Object` is expect to return: + + +```js +{ + style: function callback(cell, row, rowIndex, colIndex) { ... } +} +``` + +**Parameters** +* `cell`: The value of current cell. +* `row`: The value of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `Object` will be the result of element style. + + +## column.headerStyle - [Object | Function] +It's availabe to have customized inline-style on table header column: + +```js +{ + // omit... + headerStyle: { backgroundColor: 'green' } +} +``` + +Moreover, it also accept a callback function which takes **2** arguments and an `Object` is expect to return: + +```js +{ + headerStyle: function callback(column, colIndex) { ... } +} +``` + +**Parameters** +* `column`: The value of current column. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `Object` will be the result of element headerStyle. + + +## column.title - [Bool | Function] +`react-bootstrap-table2` is disable [`HTML title`](https://www.w3schools.com/tags/tag_title.asp) as default. You can assign `title` as `true` to enable the HTML title on table column and take `cell content` as default value. Additionally, you could customize title via a callback. It takes **4** arguments and a `String` is expect to return: + + +```js +{ + // omit... + title: function callback(cell, row, rowIndex, colIndex) { ... } + // return custom title here +} +``` + +**Parameters** +* `cell`: The value of current cell. +* `row`: The value of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `String` will be the result of element title. + +## column.headerTitle - [Bool | Function] +`headerTitle` is only for the title on header column, default is disable. The usage almost same as [`column.title`](#title), + +```js +{ + // omit... + headerTitle: true +} +``` + +It's also availabe to custom via a callback function: +```js +{ + headerTitle: function callback(column, colIndex) { ... } +} +``` + +**Parameters** +* `column`: The value of current column. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `String` will be the result of element headerTitle. + +## column.align - [String | Function] +You can configure the [CSS text-align](https://www.w3schools.com/cssref/pr_text_text-align.asp) for table column by `align` property. + +Besides, `align` also accept a callback function for dynamically setting text align. It takes **4** arguments and a `String` is expect to return: + +```js +{ + // omit... + align: function callback(cell, row, rowIndex, colIndex) { ... } +} +``` + +**Parameters** +* `cell`: The value of current cell. +* `row`: The value of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `String` will be the result of element text alignment. + +## column.headerAlign - [String | Function] +It's almost same as [`column.align`](#align), but it's for the [CSS text-align](https://www.w3schools.com/cssref/pr_text_text-align.asp) on header column. + +```js +{ + // omit... + headerAlign: 'center' +} +``` + +Also, you can custom the align by a callback function: + +```js +{ + // omit... + headerAlign: (column, colIndex) => { + // column is an object and perform itself + // return custom title here + } +} +``` +**Parameters** +* `column`: The value of current column. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `String` will be the result of element headerAlign. + + +## column.events - [Object] +You can assign any [HTML Event](https://www.w3schools.com/tags/ref_eventattributes.asp) on table column via event property: + +```js +{ + // omit... + events: { + onClick: e => { ... } + } +} +``` + +## column.headerEvents - [Object] +`headerEvents` same as [`column.events`](#events) but this is for header column. + +```js +{ + // omit... + headerEvents: { + onClick: e => { ... } + } +} +``` + +## column.attrs - [Object | Function] +Via `attrs` property, You can customize table column [HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes) which allow user to configure the elements or adjust their behavior. + +```js +{ + // omit... + attrs: { + title: 'bar', + 'data-test': 'foo' + } +} +``` +Not only `Object`, `callback function` is also acceptable. It takes `4` arguments and an `Object` is expect to return: + +```js +{ + attrs: function callback(cell, row, rowIndex, colIndex) { ... } +} +``` + +**Parameters** +* `cell`: The value of current cell. +* `row`: The value of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `Object` will be the result of element HTML attributes. + +> Caution: + +> If `column.classes`, `column.style`, `column.title`, `column.hidden` or `column.align` was given at the same time, property `attrs` has lower priorty and it will be overwrited. + +```js +{ + // omit... + title: true, // get higher priority + attrs: { title: 'test' } +} +``` + +## column.headerAttrs - [Object | Function] +`headerAttrs` is similiar to [`column.attrs`](#attrs) but it works for header column. +```js +{ + // omit... + headerAttrs: { + title: 'bar', + 'data-test': 'foo' + } +} +``` + +Additionally, customize the header attributes by a **2** arguments callback function: + +```js +{ + // omit... + headerAttrs: (column, colIndex) => ({ + // return customized HTML attribute here + }) +} +``` + +**Parameters** +* `column`: The value of current column. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +**Return value** + +A new `Object` will be the result of element headerAttrs. + +> Caution: +> Same as [column.attrs](#attrs), it has lower priority and will be +> overwrited when other props related to HTML attributes were given. + +### headerSortingClasses - [String | Function] + +`headerSortingClasses` allows to customize `class` for header cell when this column is sorting. + +```js +const headerSortingClasses = 'demo-sorting'; +``` + +Furthermore, it also accepts a callback which takes **4** arguments and `String` is expected to return: + +```js +const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => { ... } +``` + +* `column`: The value of current column. +* `sortOrder`: The order of current sorting +* `isLastSorting`: Is the last one of sorted columns. +* `colIndex`: The index of the current column being processed in BootstrapTable. + +### headerSortingStyle - [Object | Function] + +It's similiar to [headerSortingClasses](#headerSortingClasses). It allows to customize the style of header cell when this column is sorting. A style `Object` and `callback` are acceptable. `callback` takes **4** arguments and an `Object` is expected to return: + +```js +const sortingHeaderStyle = { + backgroundColor: 'red' +}; +``` + +## column.editable - [Bool | Function] +`column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`. + +If a callback function given, you can control the editable level as cell level: + +```js +{ + // omit... + editable: (cell, row, rowIndex, colIndex) => { + // return true or false; + } +} +``` + +## column.validator - [Function] +`column.validator` used for validate the data when cell on updating. it's should accept a callback function with following argument: +`newValue`, `row` and `column`: + +```js +{ + // omit... + validator: (newValue, row, column) => { + return ...; + } +} +``` + +The return value can be a bool or an object. If your valiation is pass, return `true` explicitly. If your valiation is invalid, return following object instead: +```js +{ + valid: false, + message: 'SOME_REASON_HERE' +} +``` + +## column.editCellStyle - [Object | Function] +You can use `column.editCellStyle` to custom the style of `` when cell editing. It like most of customizable functionality, it also accept a callback function with following params: + +**Parameters** +* `cell`: The value of current cell. +* `row`: The object of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +```js +{ + editCellStyle: { ... } +} +``` +Or take a callback function + +```js +{ + editCellStyle: (cell, row, rowIndex, colIndex) => { + // it is suppose to return an object + } +} +``` + +## column.editCellClasses - [String | Function] +You can use `column.editCellClasses` to add custom class on `` when cell editing. It's same as [`column.editCellStyle`](#editCellStyle) which also accept a callback function to able to custom your class more flexible. Following is the arguments of this callback function: `cell`, `row`, `rowIndex`, `colIndex`. + +```js +{ + editCellClasses: 'custom-class' +} +``` +Or take a callback function + +```js +{ + editCellClasses: (cell, row, rowIndex, colIndex) => { + // it is suppose to return a string + } +} +``` + +## column.filter - [Object] +Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters: + +* Text(`textFilter`) + +We have a quick example to show you how to use `column.filter`: + +``` +import { textFilter } from 'react-bootstrap-table2-filter'; + +// omit... +{ + dataField: 'price', + text: 'Product Price', + filter: textFilter() +} +``` + +For some reason of simple customization, `react-bootstrap-table2` allow you to pass some props to filter factory function. Please check [here](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-filter/README.md) for more detail tutorial. + +## column.filterValue - [Function] +Sometimes, if the cell/column value that you don't want to filter on them, you can define `filterValue` to return a actual value you wanna be filterd: + +**Parameters** +* `cell`: The value of current cell. +* `row`: The value of current row. + +**Return value** + +A final `String` value you want to be filtered. + +```js +// omit... +{ + dataField: 'price', + text: 'Product Price', + filter: textFilter(), + filterValue: (cell, row) => owners[cell] +} +``` \ No newline at end of file diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..ade502e --- /dev/null +++ b/docs/development.md @@ -0,0 +1,28 @@ +## Development Guide + +### Setup +```bash +$ git clone https://github.com/react-bootstrap-table/react-bootstrap-table2.git +$ cd react-bootstrap-table +$ npm install +$ lerna bootstrap # ./node_modules/.bin/lerna bootstrap +``` +### Development +```bash +$ npm run storybook +``` + +### Launch StoryBook +We use [storybook](https://storybook.js.org/) to list our examples and it also has hot reload from source code. Sometimes, it is also a good entry point to development. + +```bash +$ cd packages/react-bootstrap-table2-example +$ npm run storybook +``` + +### Testing +```bash +$ npm test +$ npm run test:watch # for watch mode +$ npm run test:coverage # generate coverage report +``` \ No newline at end of file diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..a2968c2 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,116 @@ +# Migration Guide + +* Please see the [CHANGELOG](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/new-version-0.1.0.html) for `react-bootstrap-table2` first drop. +* Please see the [Roadmap](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/release-plan.html) for `react-bootstrap-table2` in 2018/Q1. +* Feel free to see the [offical docs](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/about.html), we list all the basic usage here!! + +## Preface + +Currently, **I still can't implement all the mainly features in legacy `react-bootstrap-table`**, so please watch our github repo or [blog](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/) to make sure the legacy features you wanted are already implemented on `react-bootstrap-table2`. Anyway, ask me by open issue is ok. + +----- + +`react-bootstrap-table2` separate some functionalities from core modules to other modules like following: + +* [`react-bootstrap-table2-next`](https://www.npmjs.com/package/react-bootstrap-table-next) + * Core table module, include sorting and row selection +* [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) + * Column filter Addons +* [`react-bootstrap-table2-editor`](https://www.npmjs.com/package/react-bootstrap-table2-editor) + * Cell Editing Addons +* [`react-bootstrap-table2-paginator`](https://www.npmjs.com/package/react-bootstrap-table2-paginator) + * Pagination Addons +* [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay) + * Overlay/Loading Addons + +This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernal module(SRP). Hence, which means you probably need to install above addons when you need specific features. + +## Core Table Migration + +There is a big chagne is that there's no `TableHeaderColumn` in the `react-bootstrap-table2`, instead you are supposed to be define the `columns` prop on `BootstrapTable`: + +```js +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +``` + +The `text` property is just same as the children for the `TableHeaderColumn`, if you want to custom the header, there's a new property is: [`headerFormatter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnheaderformatter-function). + +* [`BootstrapTable` Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html) +* [Column Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html) + +## Table Sort + +Please see [Work with table sort](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-sort.html). + +- [x] Basic sorting +- [x] Custom sort function +- [x] Default Sort +- [x] Remote mode +- [x] Custom the sorting header +- [ ] Custom the sort caret +- [ ] Sort management +- [ ] Multi sort + +Due to no `TableHeaderColumn` so that no `dataSort` here, please add [`sort`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnsort-bool) property on column definitation. + +## Row Selection + +Please see [Work with selection](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-row-select.html). +Please see [available selectRow configurations](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/row-select-props.html). + +No huge change for row selection, but can not custom the selection column currently. Coming soon!!! + +## Column Filter + +Please see [Work with column filter](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-filter.html). +Please see [available filter configuration](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/filter-props.html). + +- [x] Text Filter +- [x] Custom Text Filter +- [x] Remote Filter +- [ ] Custom Filter Component +- [ ] Regex Filter +- [ ] Select Filter +- [ ] Number Filter +- [ ] Date Filter +- [ ] Array Filter +- [ ] Programmatically Filter + +Remember to install [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) firstly. + +Due to no `TableHeaderColumn` so that no `filter` here, please add [`filter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnfilter-object) property on column definitation and [`filter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html#filter-object) prop on `BootstrapTable`. + +## Cell Edit + +Please see [Work with cell edit](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-celledit.html). +Please see [available cell edit configurations](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/cell-edit-props.html). + +Remember to install [`react-bootstrap-table2-editor`](https://www.npmjs.com/package/react-bootstrap-table2-editor) firstly. + +No big changes for cell editing, [`validator`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will not support the async call(Promise). + +## Pagination + +Please see [Work with pagination](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-pagination.html). +Please see [available pagination configurations](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html). + +Remember to install [`react-bootstrap-table2-paginator`](https://www.npmjs.com/package/react-bootstrap-table2-paginator) firstly. + +No big changes for pagination, but still can't custom the pagination list, button and sizePerPage dropdown. + +## Remote + +> It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html). \ No newline at end of file diff --git a/docs/row-selection.md b/docs/row-selection.md new file mode 100644 index 0000000..e50ef83 --- /dev/null +++ b/docs/row-selection.md @@ -0,0 +1,183 @@ + +# Row selection +`react-bootstrap-table2` supports the row selection feature. By passing prop `selectRow` to enable row selection. When you enable this feature, `react-bootstrap-table2` will append a new selection column at first. + +## Required +* [mode (**required**)](#mode) + +## Optional +* [style](#style) +* [classes)](#classes) +* [bgColor](#bgColor) +* [nonSelectable)](#nonSelectable) +* [clickToSelect)](#clickToSelect) +* [clickToEdit](#clickToEdit) +* [onSelect](#onSelect) +* [onSelectAll](#onSelectAll) +* [hideSelectColumn](#hideSelectColumn) + +### selectRow.mode - [String] + +Specifying the selection way for `single(radio)` or `multiple(checkbox)`. If `radio` was assigned, there will be a radio button in the selection column; otherwise, the `checkbox` instead. + +#### values +* **radio** +* **checkbox** + +#### examples + +```js +const selectRow = { + mode: 'radio' // single row selection +}; + + +``` + +```js +const selectRow = { + mode: 'checkbox' // multiple row selection +}; + + +``` + +### selectRow.style - [Object | Function] +`selectRow.style` allow you to have custom style on selected rows: + +```js +const selectRow = { + mode: 'checkbox', + style: { background: 'red' } +}; +``` + +If you wanna more flexible customization, `selectRow.style` also accept a function: + +```js +const selectRow = { + mode: 'checkbox', + style: (row, rowIndex) => { return ...; } +}; +``` + +### selectRow.classes - [String | Function] +`selectRow.classes` allow you to add css class on selected rows: + +```js +const selectRow = { + mode: 'checkbox', + classes: 'custom-class' +}; +``` + +If you wanna more flexible customization, `selectRow.classes` also accept a function: + +```js +const selectRow = { + mode: 'checkbox', + classes: (row, rowIndex) => { return ...; } +}; +``` + +### selectRow.bgColor - [String | Function] +The backgroud color when row is selected + +```js +const selectRow = { + mode: 'checkbox', + bgColor: 'red' +}; +``` + +There's also a more good way to custom it: + +```js +const selectRow = { + mode: 'checkbox', + bgColor: (row, rowIndex) => { + return ....; // return a color code + } +}; +``` + +### selectRow.nonSelectable - [Array] +This prop allow you to restrict some rows which can not be selected by user. `selectRow.nonSelectable` accept an rowkeys array. + +```js +const selectRow = { + mode: 'checkbox', + nonSelectable: [1, 3 ,5] +}; +``` + +### selectRow.clickToSelect - [Bool] +Allow user to select row by clicking on the row. + +```js +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; +``` + +> Note: When you also enable [cellEdit](./cell-edit.md), the `selectRow.clickToSelect` will deactivate the functionality of cell editing +> If you want to click on row to select row and edit cell simultaneously, you are suppose to enable [`selectRow.clickToEdit`](#clickToEdit) + +### selectRow.clickToEdit - [Bool] +Able to click to edit cell and select row + +```js +const selectRow = { + mode: 'checkbox', + clickToSelect: true + clickToEdit: true +}; +``` + +### selectRow.onSelect - [Function] +This callback function will be called when a row is select/unselect and pass following three arguments: +`row`, `isSelect` and `rowIndex`. + +```js +const selectRow = { + mode: 'checkbox', + onSelect: (row, isSelect, rowIndex) => { + // ... + } +}; +``` + +### selectRow.onSelectAll - [Function] +This callback function will be called when select/unselect all and it only work when you configure [`selectRow.mode`](#mode) as `checkbox`. + +```js +const selectRow = { + mode: 'checkbox', + onSelectAll: (isSelect, results) => { + // ... + } +}; +``` + +### selectRow.hideSelectColumn - [Bool] +Default is `false`, if you don't want to have a selection column, give this prop as `true` + +```js +const selectRow = { + mode: 'radio', + hideSelectColumn: true, + clickToSelect: true, + bgColor: 'red' +}; +``` diff --git a/enzyme-setup.js b/enzyme-setup.js new file mode 100644 index 0000000..e952b30 --- /dev/null +++ b/enzyme-setup.js @@ -0,0 +1,8 @@ +import Adapter from 'enzyme-adapter-react-16'; +import { configure } from 'enzyme'; + +const configureEnzyme = () => { + configure({ adapter: new Adapter() }); +}; + +configureEnzyme(); diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 0000000..f124ae9 --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,84 @@ +import gulp from 'gulp'; +import babel from 'gulp-babel'; +import sass from 'gulp-sass'; +import cleanCSS from 'gulp-clean-css'; +import cleanDir from 'gulp-clean'; +import rename from 'gulp-rename'; +import shell from 'gulp-shell'; + +const LIB = 'lib'; +const DIST = 'dist'; +const TEST = 'test'; +const PKG_PATH = './packages'; +const NODE_MODULES = 'node_modules'; + +const JS_PKGS = [ + 'react-bootstrap-table2', + 'react-bootstrap-table2-editor', + 'react-bootstrap-table2-filter', + 'react-bootstrap-table2-overlay', + 'react-bootstrap-table2-paginator' +].reduce((pkg, curr) => `${curr}|${pkg}`, ''); + +const JS_SKIPS = `+(${TEST}|${LIB}|${DIST}|${NODE_MODULES})`; + +const STYLE_PKGS = [ + 'react-bootstrap-table2', + 'react-bootstrap-table2-paginator' +].reduce((pkg, curr) => `${curr}|${pkg}`, ''); + +const STYLE_SKIPS = `+(${NODE_MODULES})`; + + +function clean() { + return gulp + .src(`./packages/+(${JS_PKGS})/+(${LIB}|${DIST})`, { allowEmpty: true }) + .pipe(cleanDir()); +} + +function scripts() { + return gulp + .src([ + `./packages/+(${JS_PKGS})/**/*.js`, + `!packages/+(${JS_PKGS})/${JS_SKIPS}/**/*.js` + ]) + .pipe(babel()) + .pipe(rename((path) => { + if (path.dirname.indexOf('src') > -1) { + path.dirname = path.dirname.replace('src', `${LIB}/src`); + } else { + path.dirname += `/${LIB}`; + } + })) + .pipe(gulp.dest(PKG_PATH)); +} + +function styles() { + return gulp + .src([ + `./packages/+(${STYLE_PKGS})/style/**/*.scss`, + `!packages/+(${STYLE_PKGS})/${STYLE_SKIPS}/**/*.scss` + ]) + .pipe(sass().on('error', sass.logError)) + .pipe(rename((path) => { + path.dirname = path.dirname.replace('style', DIST); + })) + .pipe(gulp.dest(PKG_PATH)) + .pipe(cleanCSS({ compatibility: 'ie8' })) + .pipe(rename((path) => { + path.extname = '.min.css'; + })) + .pipe(gulp.dest(PKG_PATH)); +} + +function umd() { + return gulp.src('./webpack.prod.config.babel.js') + .pipe(shell(['webpack --config <%= file.path %>'])); +} + +const buildJS = gulp.parallel(umd, scripts); +const buildCSS = styles; +const build = gulp.series(clean, gulp.parallel(buildJS, buildCSS)); + +gulp.task('prod', build); +gulp.task('default', build); diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..1653f5e --- /dev/null +++ b/lerna.json @@ -0,0 +1,7 @@ +{ + "lerna": "2.0.0", + "packages": [ + "packages/*" + ], + "version": "independent" +} diff --git a/package.json b/package.json index 3234f45..45f4029 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,104 @@ { "name": "react-bootstrap-table2", "version": "0.0.1", + "private": true, "description": "Rebuilt for react-bootstrap-table", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "postinstall": "lerna bootstrap", + "build": "./node_modules/.bin/gulp prod", + "lint": "eslint ./packages --ext .js --ext .jsx --ignore-path .gitignore", + "pretest": "yarn lint --cache", + "test": "jest", + "test:coverage": "jest --coverage", + "test:watch": "jest --watch", + "storybook": "cd ./packages/react-bootstrap-table2-example && yarn storybook", + "gh-pages:clean": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:clean", + "gh-pages:build": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:build" }, "repository": { "type": "git", "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git" }, - "author": "", - "license": "ISC", + "author": "AllenFang", + "contributors": [ + { + "name": "Allen Fang", + "email": "ayu780129@hotmail.com", + "url": "https://github.com/AllenFang" + }, + { + "name": "Chun-MingChen", + "email": "nick830314@gmail.com", + "url": "https://github.com/Chun-MingChen" + } + ], + "license": "MIT", "bugs": { "url": "https://github.com/react-bootstrap-table/react-bootstrap-table2/issues" }, - "homepage": "https://github.com/react-bootstrap-table/react-bootstrap-table2#readme" + "homepage": "https://github.com/react-bootstrap-table/react-bootstrap-table2#readme", + "devDependencies": { + "babel-cli": "6.26.0", + "babel-core": "6.25.0", + "babel-eslint": "7.2.3", + "babel-jest": "20.0.3", + "babel-loader": "7.1.1", + "babel-preset-es2015": "6.24.1", + "babel-preset-react": "6.24.1", + "babel-preset-stage-0": "6.24.1", + "babel-register": "6.24.1", + "css-loader": "0.28.1", + "enzyme": "3.1.1", + "enzyme-adapter-react-16": "1.0.4", + "eslint": "4.5.0", + "eslint-config-airbnb": "15.1.0", + "eslint-loader": "1.9.0", + "eslint-plugin-import": "2.7.0", + "eslint-plugin-jsx-a11y": "5.1.1", + "eslint-plugin-react": "7.2.1", + "gulp": "4.0.0", + "gulp-babel": "7.0.0", + "gulp-clean": "0.4.0", + "gulp-clean-css": "3.9.2", + "gulp-rename": "^1.2.2", + "gulp-sass": "3.1.0", + "gulp-shell": "0.6.5", + "html-webpack-plugin": "2.30.1", + "jest": "20.0.4", + "jsdom": "11.2.0", + "jsdom-global": "3.0.2", + "lerna": "2.8.0", + "node-sass": "4.5.3", + "react-test-renderer": "16.0.0", + "sass-loader": "6.0.6", + "sinon": "3.2.1", + "style-loader": "0.17.0", + "webpack": "3.5.4", + "webpack-dev-server": "2.7.1" + }, + "dependencies": { + "classnames": "2.2.5", + "prop-types": "15.5.10", + "react": "16.0.0", + "react-dom": "16.0.0" + }, + "jest": { + "collectCoverageFrom": [ + "packages/**/*.js" + ], + "roots": [ + "/packages" + ], + "setupFiles": [ + "/enzyme-setup.js" + ], + "modulePaths": [ + "/packages/react-bootstrap-table2" + ], + "testEnvironment": "node", + "testMatch": [ + "**/test/**/*.test.js" + ] + } } diff --git a/packages/react-bootstrap-table2-editor/README.md b/packages/react-bootstrap-table2-editor/README.md new file mode 100644 index 0000000..02e0e46 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/README.md @@ -0,0 +1,61 @@ +# react-bootstrap-table2-editor + +`react-bootstrap-table2` separate the cell edit code base to [`react-bootstrap-table2-editor`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-editor), so there's a little bit different when you use cell edit than `react-bootstrap-table`. In the following, we are going to show you how to enable the cell edit + +**[Live Demo For Cell Edit](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Cell%20Editing)** + +**[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/cell-edit-props.html)** + +----- + +## Install + +```sh +$ npm install react-bootstrap-table2-editor --save +``` + +## How + +We have [two ways](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/cell-edit-props.html#celleditmode-string) to trigger a editable cell as editing cell: + +* click +* dbclick + +That's look into how we enable the cell edit on tabe: + +```js +import cellEditFactory from 'react-bootstrap-table2-editor'; + +// omit + + +``` + +How user save their new editings? We offer two ways: + +* Press ENTER(**default**) +* Blur from current editing cell(Need to enable the [cellEdit.blurToSave](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/cell-edit-props.html#celleditblurtosave-bool)) + +## Editable Cell +`react-bootstrap-table2` support you to configure the cell editable on three level: + +* Row Level ([cellEdit.nonEditableRows](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/cell-edit-props.html#celleditnoneditablerows-function)) +* Column Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as bool value) +* Cell Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as a callback function) + +## Customize Style/Class +Currently, we only support the editing cell style/class customization, in the future, we will offer more customizations. + +### Editing Cell + +* Customize the editing cell style via [column.editCellStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellstyle-object-function) +* Customize the editing cell classname via [column.editCellClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellclasses-string-function) + +## Validation + +[`column.validator`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it! \ No newline at end of file diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js new file mode 100644 index 0000000..df715d7 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/index.js @@ -0,0 +1,16 @@ +import wrapperFactory from './src/wrapper'; +import editingCellFactory from './src/editing-cell'; +import { + CLICK_TO_CELL_EDIT, + DBCLICK_TO_CELL_EDIT, + DELAY_FOR_DBCLICK +} from './src/const'; + +export default (options = {}) => ({ + wrapperFactory, + editingCellFactory, + CLICK_TO_CELL_EDIT, + DBCLICK_TO_CELL_EDIT, + DELAY_FOR_DBCLICK, + options +}); diff --git a/packages/react-bootstrap-table2-editor/package.json b/packages/react-bootstrap-table2-editor/package.json new file mode 100644 index 0000000..3cd186b --- /dev/null +++ b/packages/react-bootstrap-table2-editor/package.json @@ -0,0 +1,47 @@ +{ + "name": "react-bootstrap-table2-editor", + "version": "0.0.3", + "description": "it's the editor addon for react-bootstrap-table2", + "main": "./lib/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git" + }, + "keywords": [ + "react", + "bootstrap", + "table", + "grid", + "react-bootstrap-table-addons", + "react-component" + ], + "files": [ + "lib/", + "dist/" + ], + "tags": [ + "react" + ], + "author": "AllenFang", + "contributors": [ + { + "name": "Allen Fang", + "email": "ayu780129@hotmail.com", + "url": "https://github.com/AllenFang" + }, + { + "name": "Chun-MingChen", + "email": "nick830314@gmail.com", + "url": "https://github.com/Chun-MingChen" + } + ], + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^16.0.0", + "react-dom": "^16.0.0" + } +} diff --git a/packages/react-bootstrap-table2-editor/src/const.js b/packages/react-bootstrap-table2-editor/src/const.js new file mode 100644 index 0000000..acbef9d --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/const.js @@ -0,0 +1,4 @@ +export const TIME_TO_CLOSE_MESSAGE = 3000; +export const DELAY_FOR_DBCLICK = 200; +export const CLICK_TO_CELL_EDIT = 'click'; +export const DBCLICK_TO_CELL_EDIT = 'dbclick'; diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell.js b/packages/react-bootstrap-table2-editor/src/editing-cell.js new file mode 100644 index 0000000..5e54984 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/editing-cell.js @@ -0,0 +1,153 @@ +/* eslint react/prop-types: 0 */ +/* eslint no-return-assign: 0 */ +/* eslint class-methods-use-this: 0 */ +/* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */ +import React, { Component } from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; + +import TextEditor from './text-editor'; +import EditorIndicator from './editor-indicator'; +import { TIME_TO_CLOSE_MESSAGE } from './const'; + +export default _ => + class EditingCell extends Component { + static propTypes = { + row: PropTypes.object.isRequired, + column: PropTypes.object.isRequired, + onUpdate: PropTypes.func.isRequired, + onEscape: PropTypes.func.isRequired, + timeToCloseMessage: PropTypes.number, + className: PropTypes.string, + style: PropTypes.object + } + + static defaultProps = { + timeToCloseMessage: TIME_TO_CLOSE_MESSAGE, + className: null, + style: {} + } + + constructor(props) { + super(props); + this.indicatorTimer = null; + this.clearTimer = this.clearTimer.bind(this); + this.handleBlur = this.handleBlur.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.beforeComplete = this.beforeComplete.bind(this); + this.state = { + invalidMessage: null + }; + } + + componentWillReceiveProps({ message }) { + if (_.isDefined(message)) { + this.createTimer(); + this.setState(() => ({ + invalidMessage: message + })); + } + } + + componentWillUnmount() { + this.clearTimer(); + } + + clearTimer() { + if (this.indicatorTimer) { + clearTimeout(this.indicatorTimer); + } + } + + createTimer() { + this.clearTimer(); + const { timeToCloseMessage, onErrorMessageDisappear } = this.props; + this.indicatorTimer = _.sleep(() => { + this.setState(() => ({ + invalidMessage: null + })); + if (_.isFunction(onErrorMessageDisappear)) onErrorMessageDisappear(); + }, timeToCloseMessage); + } + + beforeComplete(row, column, newValue) { + const { onUpdate } = this.props; + if (_.isFunction(column.validator)) { + const validateForm = column.validator(newValue, row, column); + if (_.isObject(validateForm) && !validateForm.valid) { + this.setState(() => ({ + invalidMessage: validateForm.message + })); + this.createTimer(); + return; + } + } + onUpdate(row, column, newValue); + } + + handleBlur() { + const { onEscape, blurToSave, row, column } = this.props; + if (blurToSave) { + const value = this.editor.text.value; + if (!_.isDefined(value)) { + // TODO: for other custom or embed editor + } + this.beforeComplete(row, column, value); + } else { + onEscape(); + } + } + + handleKeyDown(e) { + const { onEscape, row, column } = this.props; + if (e.keyCode === 27) { // ESC + onEscape(); + } else if (e.keyCode === 13) { // ENTER + const value = e.currentTarget.value; + if (!_.isDefined(value)) { + // TODO: for other custom or embed editor + } + this.beforeComplete(row, column, value); + } + } + + handleClick(e) { + if (e.target.tagName !== 'TD') { + // To avoid the row selection event be triggered, + // When user define selectRow.clickToSelect and selectRow.clickToEdit + // We shouldn't trigger selection event even if user click on the cell editor(input) + e.stopPropagation(); + } + } + + render() { + const { invalidMessage } = this.state; + const { row, column, className, style } = this.props; + const { dataField } = column; + + const value = _.get(row, dataField); + const editorAttrs = { + onKeyDown: this.handleKeyDown, + onBlur: this.handleBlur + }; + + const hasError = _.isDefined(invalidMessage); + const editorClass = hasError ? cs('animated', 'shake') : null; + return ( + + this.editor = node } + defaultValue={ value } + className={ editorClass } + { ...editorAttrs } + /> + { hasError ? : null } + + ); + } + }; diff --git a/packages/react-bootstrap-table2-editor/src/editor-indicator.js b/packages/react-bootstrap-table2-editor/src/editor-indicator.js new file mode 100644 index 0000000..c19819d --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/editor-indicator.js @@ -0,0 +1,19 @@ +/* eslint no-return-assign: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const EditorIndicator = ({ invalidMessage }) => + ( +
+ { invalidMessage } +
+ ); + +EditorIndicator.propTypes = { + invalidMessage: PropTypes.string +}; + +EditorIndicator.defaultProps = { + invalidMessage: null +}; +export default EditorIndicator; diff --git a/packages/react-bootstrap-table2-editor/src/text-editor.js b/packages/react-bootstrap-table2-editor/src/text-editor.js new file mode 100644 index 0000000..52233e7 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/text-editor.js @@ -0,0 +1,40 @@ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; + +class TextEditor extends Component { + componentDidMount() { + const { defaultValue } = this.props; + this.text.value = defaultValue; + this.text.focus(); + } + + render() { + const { defaultValue, className, ...rest } = this.props; + const editorClass = cs('form-control editor edit-text', className); + return ( + this.text = node } + type="text" + className={ editorClass } + { ...rest } + /> + ); + } +} + +TextEditor.propTypes = { + className: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object + ]), + defaultValue: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]).isRequired +}; +TextEditor.defaultProps = { + className: null +}; +export default TextEditor; diff --git a/packages/react-bootstrap-table2-editor/src/wrapper.js b/packages/react-bootstrap-table2-editor/src/wrapper.js new file mode 100644 index 0000000..97138d4 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/wrapper.js @@ -0,0 +1,135 @@ +/* eslint react/prop-types: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from './const'; + +export default ( + Base, + { _, remoteResolver } +) => { + let EditingCell; + return class CellEditWrapper extends remoteResolver(Component) { + static propTypes = { + options: PropTypes.shape({ + mode: PropTypes.oneOf([CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT]).isRequired, + onErrorMessageDisappear: PropTypes.func, + blurToSave: PropTypes.bool, + beforeSaveCell: PropTypes.func, + afterSaveCell: PropTypes.func, + nonEditableRows: PropTypes.func, + timeToCloseMessage: PropTypes.number, + errorMessage: PropTypes.string + }) + } + + constructor(props) { + super(props); + EditingCell = props.cellEdit.editingCellFactory(_); + this.startEditing = this.startEditing.bind(this); + this.escapeEditing = this.escapeEditing.bind(this); + this.completeEditing = this.completeEditing.bind(this); + this.handleCellUpdate = this.handleCellUpdate.bind(this); + this.state = { + ridx: null, + cidx: null, + message: null, + isDataChanged: false + }; + } + + componentWillReceiveProps(nextProps) { + if (nextProps.cellEdit && this.isRemoteCellEdit()) { + if (nextProps.cellEdit.options.errorMessage) { + this.setState(() => ({ + isDataChanged: false, + message: nextProps.cellEdit.options.errorMessage + })); + } else { + this.setState(() => ({ + isDataChanged: true + })); + this.escapeEditing(); + } + } else { + this.setState(() => ({ + isDataChanged: false + })); + } + } + + handleCellUpdate(row, column, newValue) { + const { keyField, cellEdit, store } = this.props; + const { beforeSaveCell, afterSaveCell } = cellEdit.options; + const oldValue = _.get(row, column.dataField); + const rowId = _.get(row, keyField); + if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column); + if (this.isRemoteCellEdit()) { + this.handleCellChange(rowId, column.dataField, newValue); + } else { + store.edit(rowId, column.dataField, newValue); + if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column); + this.completeEditing(); + } + } + + completeEditing() { + this.setState(() => ({ + ridx: null, + cidx: null, + message: null, + isDataChanged: true + })); + } + + startEditing(ridx, cidx) { + const editing = () => { + this.setState(() => ({ + ridx, + cidx, + isDataChanged: false + })); + }; + + const { selectRow } = this.props; + if (!selectRow || (selectRow.clickToEdit || !selectRow.clickToSelect)) editing(); + } + + escapeEditing() { + this.setState(() => ({ + ridx: null, + cidx: null + })); + } + + render() { + const { isDataChanged, ...stateRest } = this.state; + const { + cellEdit: { + options: { nonEditableRows, errorMessage, ...optionsRest }, + editingCellFactory, + ...cellEditRest + } + } = this.props; + const newCellEdit = { + ...optionsRest, + ...cellEditRest, + ...stateRest, + EditingCell, + nonEditableRows: _.isDefined(nonEditableRows) ? nonEditableRows() : [], + onStart: this.startEditing, + onEscape: this.escapeEditing, + onUpdate: this.handleCellUpdate + }; + + return ( + + ); + } + }; +}; diff --git a/packages/react-bootstrap-table2-editor/test/editing-cell.test.js b/packages/react-bootstrap-table2-editor/test/editing-cell.test.js new file mode 100644 index 0000000..9b34d93 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/editing-cell.test.js @@ -0,0 +1,234 @@ +/* eslint react/prop-types: 0 */ +import 'jsdom-global/register'; +import React from 'react'; +import sinon from 'sinon'; +import { shallow, mount } from 'enzyme'; + +import _ from 'react-bootstrap-table-next/src/utils'; +import editingCellFactory from '../src/editing-cell'; +import TextEditor from '../src/text-editor'; +import EditorIndicator from '../src/editor-indicator'; + +const EditingCell = editingCellFactory(_); +const TableRowWrapper = props => ( + + + { props.children } + +
+); + + +describe('EditingCell', () => { + let wrapper; + let onUpdate; + let onEscape; + const row = { + id: 1, + name: 'A' + }; + + let column = { + dataField: 'id', + text: 'ID' + }; + + beforeEach(() => { + onEscape = sinon.stub(); + onUpdate = sinon.stub(); + wrapper = shallow( + + ); + }); + + it('should render default editor successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('td').length).toBe(1); + expect(wrapper.find(TextEditor).length).toBe(1); + expect(wrapper.state().invalidMessage).toBeNull(); + }); + + it('should render TextEditor with correct props', () => { + const textEditor = wrapper.find(TextEditor); + expect(textEditor.props().defaultValue).toEqual(row[column.dataField]); + expect(textEditor.props().onKeyDown).toBeDefined(); + expect(textEditor.props().onBlur).toBeDefined(); + expect(textEditor.props().className).toBeNull(); + }); + + it('should not render EditorIndicator due to state.invalidMessage is null', () => { + const indicator = wrapper.find(EditorIndicator); + expect(indicator.length).toEqual(0); + }); + + it('when press ENTER on TextEditor should call onUpdate correctly', () => { + const newValue = 'test'; + const textEditor = wrapper.find(TextEditor); + textEditor.simulate('keyDown', { keyCode: 13, currentTarget: { value: newValue } }); + expect(onUpdate.callCount).toBe(1); + expect(onUpdate.calledWith(row, column, newValue)).toBe(true); + }); + + it('when press ESC on TextEditor should call onEscape correctly', () => { + const textEditor = wrapper.find(TextEditor); + textEditor.simulate('keyDown', { keyCode: 27 }); + expect(onEscape.callCount).toBe(1); + }); + + it('when blur from TextEditor should call onEscape correctly', () => { + const textEditor = wrapper.find(TextEditor); + textEditor.simulate('blur'); + expect(onEscape.callCount).toBe(1); + }); + + describe('if style prop is defined', () => { + const customStyle = { backgroundColor: 'red' }; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render component with style successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('td').prop('style')).toEqual(customStyle); + }); + }); + + describe('if className prop is defined', () => { + const className = 'test-class'; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render component with style successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass(className)).toBe(true); + }); + }); + + describe('if blurToSave prop is true', () => { + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('when blur from TextEditor should call onUpdate correctly', () => { + const textEditor = wrapper.find(TextEditor); + textEditor.simulate('blur'); + expect(onUpdate.callCount).toBe(1); + expect(onUpdate.calledWith(row, column, `${row[column.dataField]}`)).toBe(true); + }); + }); + + describe('when column.validator is defined', () => { + let newValue; + let validForm; + let validatorCallBack; + + describe('and column.validator return an object', () => { + beforeEach(() => { + newValue = 'newValue'; + validForm = { valid: false, message: 'Something is invalid' }; + validatorCallBack = sinon.stub().returns(validForm); + column = { + dataField: 'id', + text: 'ID', + validator: validatorCallBack + }; + wrapper = mount( + + ); + wrapper.instance().beforeComplete(row, column, newValue); + }); + + it('should call column.validator successfully', () => { + expect(validatorCallBack.callCount).toBe(1); + expect(validatorCallBack.calledWith(newValue, row, column)).toBe(true); + }); + + it('should not call onUpdate', () => { + expect(onUpdate.callCount).toBe(0); + }); + + it('should set indicatorTimer successfully', () => { + expect(wrapper.instance().indicatorTimer).toBeDefined(); + }); + + it('should set invalidMessage state correctly', () => { + expect(wrapper.state().invalidMessage).toEqual(validForm.message); + }); + + it('should render TextEditor with correct shake and animated class', () => { + const editor = wrapper.find(TextEditor); + expect(editor.html()).toEqual(''); + /* Following is better, but it will not work after upgrade React to 16 and enzyme... */ + // expect(editor.length).toEqual(1); + // expect(editor.props().classNames).toEqual('animated shake'); + }); + + /* Following is better, but it will not work after upgrade React to 16 and enzyme... */ + xit('should render EditorIndicator correctly', () => { + const indicator = wrapper.find(EditorIndicator); + expect(indicator.length).toEqual(1); + expect(indicator.props().invalidMessage).toEqual(validForm.message); + }); + }); + + describe('and column.validator return true or something', () => { + beforeEach(() => { + newValue = 'newValue'; + validForm = true; + validatorCallBack = sinon.stub().returns(validForm); + column = { + dataField: 'id', + text: 'ID', + validator: validatorCallBack + }; + wrapper.instance().beforeComplete(row, column, newValue); + }); + + it('should call column.validator successfully', () => { + expect(validatorCallBack.callCount).toBe(1); + expect(validatorCallBack.calledWith(newValue, row, column)).toBe(true); + }); + + it('should call onUpdate', () => { + expect(onUpdate.callCount).toBe(1); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-editor/test/text-editor.test.js b/packages/react-bootstrap-table2-editor/test/text-editor.test.js new file mode 100644 index 0000000..845d788 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/text-editor.test.js @@ -0,0 +1,42 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount } from 'enzyme'; + +import TextEditor from '../src/text-editor'; + +describe('TextEditor', () => { + let wrapper; + const value = 'test'; + + beforeEach(() => { + wrapper = mount( + + ); + }); + + it('should render TextEditor correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('input').length).toBe(1); + expect(wrapper.find('input').prop('type')).toEqual('text'); + expect(wrapper.find('.form-control.editor.edit-text').length).toBe(1); + }); + + describe('when className prop defined', () => { + const className = 'test-class'; + beforeEach(() => { + wrapper = mount( + + ); + }); + + it('should render correct custom classname', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass(className)).toBeTruthy(); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-editor/test/wrapper.test.js b/packages/react-bootstrap-table2-editor/test/wrapper.test.js new file mode 100644 index 0000000..8264ca8 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/wrapper.test.js @@ -0,0 +1,330 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import _ from 'react-bootstrap-table-next/src/utils'; +import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver'; +import Store from 'react-bootstrap-table-next/src/store'; +import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; +import cellEditFactory from '..'; +import * as Const from '../src/const'; +import wrapperFactory from '../src/wrapper'; + +describe('CellEditWrapper', () => { + let wrapper; + let instance; + const onTableChangeCB = sinon.stub(); + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const createTableProps = (props = {}) => { + const { cellEdit, ...rest } = props; + const tableProps = { + keyField: 'id', + columns, + data, + _, + store: new Store('id'), + cellEdit: cellEditFactory(cellEdit), + onTableChange: onTableChangeCB, + ...rest + }; + tableProps.store.data = data; + return tableProps; + }; + + const CellEditWrapper = wrapperFactory(BootstrapTable, { + _, + remoteResolver + }); + + const createCellEditWrapper = (props, renderFragment = true) => { + wrapper = shallow(); + instance = wrapper.instance(); + if (renderFragment) { + const fragment = instance.render(); + wrapper = shallow(
{ fragment }
); + } + }; + + afterEach(() => { + onTableChangeCB.reset(); + }); + + beforeEach(() => { + const props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT } + }); + createCellEditWrapper(props); + }); + + it('should render CellEditWrapper correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(BootstrapTable)).toBeDefined(); + }); + + it('should have correct state', () => { + expect(instance.state.ridx).toBeNull(); + expect(instance.state.cidx).toBeNull(); + expect(instance.state.message).toBeNull(); + expect(instance.state.isDataChanged).toBeFalsy(); + }); + + it('should inject correct props to base component', () => { + const base = wrapper.find(BootstrapTable); + expect(base.props().cellEdit).toBeDefined(); + expect(base.props().cellEdit.onStart).toBeDefined(); + expect(base.props().cellEdit.onEscape).toBeDefined(); + expect(base.props().cellEdit.onUpdate).toBeDefined(); + expect(base.props().cellEdit.EditingCell).toBeDefined(); + expect(base.props().cellEdit.ridx).toBeNull(); + expect(base.props().cellEdit.cidx).toBeNull(); + expect(base.props().cellEdit.message).toBeNull(); + expect(base.props().isDataChanged).toBe(instance.state.isDataChanged); + }); + + describe('when receive new cellEdit prop', () => { + const spy = jest.spyOn(CellEditWrapper.prototype, 'escapeEditing'); + + describe('and cellEdit is not work on remote', () => { + beforeEach(() => { + const props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT } + }); + createCellEditWrapper(props); + wrapper.setProps({ cellEdit: props.cellEdit }); + }); + + it('should always setting state.isDataChanged as false', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + }); + }); + + describe('and cellEdit is work on remote', () => { + let errorMessage; + let props; + beforeEach(() => { + props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, + remote: true + }); + }); + + describe('and cellEdit.errorMessage is defined', () => { + beforeEach(() => { + createCellEditWrapper(props, false); + errorMessage = 'test'; + const newCellEdit = { + ...props.cellEdit, + options: { ...props.cellEdit.options, errorMessage } + }; + wrapper.setProps({ cellEdit: newCellEdit }); + }); + + it('should setting correct state', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + expect(instance.state.message).toEqual(errorMessage); + }); + }); + + describe('and cellEdit.errorMessage is undefined', () => { + beforeEach(() => { + errorMessage = null; + createCellEditWrapper(props, false); + const newCellEdit = { + ...props.cellEdit, + options: { ...props.cellEdit.options, errorMessage } + }; + wrapper.setProps({ cellEdit: newCellEdit }); + }); + + it('should setting correct state', () => { + expect(wrapper.state().isDataChanged).toBeTruthy(); + }); + + it('should escape current editing', () => { + expect(spy).toHaveBeenCalled(); + }); + }); + }); + }); + + describe('call escapeEditing function', () => { + it('should set state correctly', () => { + instance.escapeEditing(); + expect(instance.state.ridx).toBeNull(); + expect(instance.state.cidx).toBeNull(); + }); + }); + + describe('call startEditing function', () => { + const ridx = 1; + const cidx = 3; + + it('should set state correctly', () => { + instance.startEditing(ridx, cidx); + expect(instance.state.ridx).toEqual(ridx); + expect(instance.state.cidx).toEqual(cidx); + expect(instance.state.isDataChanged).toBeFalsy(); + }); + + describe('if selectRow.clickToSelect is defined', () => { + beforeEach(() => { + const selectRow = { mode: 'checkbox', clickToSelect: true }; + const props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, + selectRow + }); + createCellEditWrapper(props); + }); + + it('should not set state', () => { + instance.startEditing(ridx, cidx); + expect(instance.state.ridx).toBeNull(); + expect(instance.state.cidx).toBeDefined(); + }); + }); + + describe('if selectRow.clickToSelect and selectRow.clickToEdit is defined', () => { + beforeEach(() => { + const selectRow = { mode: 'checkbox', clickToSelect: true, clickToEdit: true }; + const props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, + selectRow + }); + createCellEditWrapper(props); + }); + + it('should set state correctly', () => { + instance.startEditing(ridx, cidx); + expect(instance.state.ridx).toEqual(ridx); + expect(instance.state.cidx).toEqual(cidx); + }); + }); + }); + + describe('call completeEditing function', () => { + it('should set state correctly', () => { + instance.completeEditing(); + expect(instance.state.ridx).toBeNull(); + expect(instance.state.cidx).toBeNull(); + expect(instance.state.message).toBeNull(); + expect(instance.state.isDataChanged).toBeTruthy(); + }); + }); + + describe('call handleCellUpdate function', () => { + let props; + const row = data[0]; + const column = columns[1]; + const newValue = 'new name'; + + describe('when cell edit is work on remote', () => { + const spy = jest.spyOn(CellEditWrapper.prototype, 'handleCellChange'); + + beforeEach(() => { + props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, + remote: true + }); + createCellEditWrapper(props); + instance.handleCellUpdate(row, column, newValue); + }); + + it('should calling handleCellChange correctly', () => { + expect(spy).toHaveBeenCalled(); + expect(spy.mock.calls).toHaveLength(1); + expect(spy.mock.calls[0]).toHaveLength(3); + expect(spy.mock.calls[0][0]).toEqual(row.id); + expect(spy.mock.calls[0][1]).toEqual(column.dataField); + expect(spy.mock.calls[0][2]).toEqual(newValue); + }); + }); + + describe('when cell edit is not work on remote', () => { + const spyOnCompleteEditing = jest.spyOn(CellEditWrapper.prototype, 'completeEditing'); + const spyOnStoreEdit = jest.spyOn(Store.prototype, 'edit'); + + beforeEach(() => { + props = createTableProps({ + cellEdit: { mode: Const.CLICK_TO_CELL_EDIT } + }); + createCellEditWrapper(props); + instance.handleCellUpdate(row, column, newValue); + }); + + afterEach(() => { + spyOnStoreEdit.mockReset(); + spyOnCompleteEditing.mockReset(); + }); + + it('should calling props.store.edit', () => { + expect(spyOnStoreEdit).toHaveBeenCalled(); + expect(spyOnStoreEdit.mock.calls).toHaveLength(1); + expect(spyOnStoreEdit.mock.calls[0]).toHaveLength(3); + expect(spyOnStoreEdit.mock.calls[0][0]).toEqual(row.id); + expect(spyOnStoreEdit.mock.calls[0][1]).toEqual(column.dataField); + expect(spyOnStoreEdit.mock.calls[0][2]).toEqual(newValue); + }); + + it('should calling completeEditing function', () => { + expect(spyOnCompleteEditing).toHaveBeenCalled(); + }); + + describe('if cellEdit.afterSaveCell prop defined', () => { + const aftereSaveCellCallBack = sinon.stub(); + + beforeEach(() => { + props = createTableProps({ + cellEdit: { + mode: Const.CLICK_TO_CELL_EDIT, + afterSaveCell: aftereSaveCellCallBack + } + }); + createCellEditWrapper(props); + instance.handleCellUpdate(row, column, newValue); + }); + + it('should calling cellEdit.afterSaveCell correctly', () => { + expect(aftereSaveCellCallBack.callCount).toBe(1); + expect(aftereSaveCellCallBack.calledWith( + row[column.dataField], newValue, row, column) + ).toBe(true); + }); + }); + }); + + describe('if cellEdit.beforeSaveCell prop defined', () => { + const beforeSaveCellCallBack = sinon.stub(); + beforeEach(() => { + props = createTableProps({ + cellEdit: { + mode: Const.CLICK_TO_CELL_EDIT, + beforeSaveCell: beforeSaveCellCallBack + } + }); + createCellEditWrapper(props); + instance.handleCellUpdate(row, column, newValue); + }); + + it('should calling cellEdit.beforeSaveCell correctly', () => { + expect(beforeSaveCellCallBack.callCount).toBe(1); + expect(beforeSaveCellCallBack.calledWith( + row[column.dataField], newValue, row, column) + ).toBe(true); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-example/.storybook/.babelrc b/packages/react-bootstrap-table2-example/.storybook/.babelrc new file mode 100644 index 0000000..3feddd3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/.storybook/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["react", "es2015", "stage-0", ["env", {"modules": false} ]], + "plugins": ["transform-class-properties"] +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/.storybook/addons.js b/packages/react-bootstrap-table2-example/.storybook/addons.js new file mode 100644 index 0000000..afebf47 --- /dev/null +++ b/packages/react-bootstrap-table2-example/.storybook/addons.js @@ -0,0 +1,5 @@ +/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ + +import '@storybook/addon-actions/register'; +import '@storybook/addon-links/register'; +import '@storybook/addon-console'; diff --git a/packages/react-bootstrap-table2-example/.storybook/config.js b/packages/react-bootstrap-table2-example/.storybook/config.js new file mode 100644 index 0000000..f50d246 --- /dev/null +++ b/packages/react-bootstrap-table2-example/.storybook/config.js @@ -0,0 +1,26 @@ +/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ +import React from 'react'; +import { configure, addDecorator } from '@storybook/react'; +import { withConsole } from '@storybook/addon-console'; + +function loadStories() { + require('stories'); +} + +const styles = { + margin: '15px', +}; + +const componentDecorator = (story) => ( +
+ { story() } +
+); + + +// prepend the story name to log messages +addDecorator((storyFn, context) => withConsole()(storyFn)(context)); + +addDecorator(componentDecorator); + +configure(loadStories, module); diff --git a/packages/react-bootstrap-table2-example/.storybook/preview-head.html b/packages/react-bootstrap-table2-example/.storybook/preview-head.html new file mode 100644 index 0000000..edb9231 --- /dev/null +++ b/packages/react-bootstrap-table2-example/.storybook/preview-head.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js new file mode 100644 index 0000000..166aef5 --- /dev/null +++ b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js @@ -0,0 +1,65 @@ +const path = require('path'); + +const sourcePath = path.join(__dirname, '../../react-bootstrap-table2/index.js'); +const paginationSourcePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/index.js'); +const overlaySourcePath = path.join(__dirname, '../../react-bootstrap-table2-overlay/index.js'); +const filterSourcePath = path.join(__dirname, '../../react-bootstrap-table2-filter/index.js'); +const editorSourcePath = path.join(__dirname, '../../react-bootstrap-table2-editor/index.js'); +const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style'); +const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style'); +const storyPath = path.join(__dirname, '../stories'); +const examplesPath = path.join(__dirname, '../examples'); +const srcPath = path.join(__dirname, '../src'); +const aliasPath = { + examples: examplesPath, + stories: storyPath, + src: srcPath, + components: path.join(srcPath, 'components'), + utils: path.join(srcPath, 'utils'), + + 'react-bootstrap-table-next': sourcePath, + 'react-bootstrap-table2-editor': editorSourcePath, + 'react-bootstrap-table2-filter': filterSourcePath, + 'react-bootstrap-table2-overlay': overlaySourcePath, + 'react-bootstrap-table2-paginator': paginationSourcePath, +}; + +const loaders = [{ + enforce: 'pre', + test: /\.js?$/, + exclude: /node_modules/, + include: [examplesPath, storyPath], + loader: 'eslint-loader', +}, { + test: /\.js?$/, + use: ['babel-loader'], + exclude: /node_modules/ +}, { + test: /\.css$/, + use: ['style-loader', 'css-loader'], +}, { + test: /\.scss$/, + use: ['style-loader', 'css-loader', 'sass-loader'], + include: [storyPath, sourceStylePath, paginationStylePath], +}, { + test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/, + loader: 'url-loader?limit=100000', +}]; + +// Export a function. Accept the base config as the only param. +module.exports = (storybookBaseConfig, configType) => { + // configType has a value of 'DEVELOPMENT' or 'PRODUCTION' + // You can change the configuration based on that. + // 'PRODUCTION' is used when building the static version of storybook. + + // loaders + loaders.forEach(value => { + storybookBaseConfig.module.rules.push(value); + }) + + // alias + storybookBaseConfig.resolve.alias = aliasPath; + + // Return the altered config + return storybookBaseConfig; +}; diff --git a/packages/react-bootstrap-table2-example/examples/basic/borderless-table.js b/packages/react-bootstrap-table2-example/examples/basic/borderless-table.js new file mode 100644 index 0000000..bccdef1 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/borderless-table.js @@ -0,0 +1,42 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +// omit... + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/basic/caption-table.js b/packages/react-bootstrap-table2-example/examples/basic/caption-table.js new file mode 100644 index 0000000..e613545 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/caption-table.js @@ -0,0 +1,49 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const CaptionElement = () =>

Component as Header

; + + + +} columns={ columns } /> +`; + +const Caption = () =>

Component as Header

; + +export default () => ( +
+ + } columns={ columns } /> + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/basic/index.js b/packages/react-bootstrap-table2-example/examples/basic/index.js new file mode 100644 index 0000000..310583c --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/index.js @@ -0,0 +1,42 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/basic/no-data-table.js b/packages/react-bootstrap-table2-example/examples/basic/no-data-table.js new file mode 100644 index 0000000..329fabf --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/no-data-table.js @@ -0,0 +1,37 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +// omit... + + + +// Following is a more flexible example + +function indication() { + // return something here +} + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/basic/striped-hover-condensed-table.js b/packages/react-bootstrap-table2-example/examples/basic/striped-hover-condensed-table.js new file mode 100644 index 0000000..3451b8f --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/striped-hover-condensed-table.js @@ -0,0 +1,46 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +// omit... + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/blur-to-save-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/blur-to-save-table.js new file mode 100644 index 0000000..15175ba --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/blur-to-save-table.js @@ -0,0 +1,60 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js new file mode 100644 index 0000000..b2b58e8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js @@ -0,0 +1,61 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellClasses: 'editing-name' +}, { + dataField: 'price', + text: 'Product Price', + editCellClasses: (cell, row, rowIndex, colIndex) => + (cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101') +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellClasses: 'editing-name' +}, { + dataField: 'price', + text: 'Product Price', + editCellClasses: (cell, row, rowIndex, colIndex) => + (cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101') +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-hooks-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-hooks-table.js new file mode 100644 index 0000000..afb1523 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-hooks-table.js @@ -0,0 +1,64 @@ +/* eslint no-unused-vars: 0 */ +/* eslint no-console: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + { console.log('Before Saving Cell!!'); }, + afterSaveCell: (oldValue, newValue, row, column) => { console.log('After Saving Cell!!'); } + }) } +/> +`; + +export default () => ( +
+ { console.log('Before Saving Cell!!'); }, + afterSaveCell: (oldValue, newValue, row, column) => { console.log('After Saving Cell!!'); } + }) } + /> + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js new file mode 100644 index 0000000..59e40d8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js @@ -0,0 +1,69 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellStyle: { + backgroundColor: '#20B2AA' + } +}, { + dataField: 'price', + text: 'Product Price', + editCellStyle: (cell, row, rowIndex, colIndex) => { + const backgroundColor = cell > 2101 ? '#00BFFF' : '#00FFFF'; + return { backgroundColor }; + } +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellStyle: { + backgroundColor: '#20B2AA' + } +}, { + dataField: 'price', + text: 'Product Price', + editCellStyle: (cell, row, rowIndex, colIndex) => { + const backgroundColor = cell > 2101 ? '#00BFFF' : '#00FFFF'; + return { backgroundColor }; + } +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-validator-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-validator-table.js new file mode 100644 index 0000000..457c890 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-validator-table.js @@ -0,0 +1,91 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + validator: (newValue, row, column) => { + if (isNaN(newValue)) { + return { + valid: false, + message: 'Price should be numeric' + }; + } + if (newValue < 2000) { + return { + valid: false, + message: 'Price should bigger than 2000' + }; + } + return true; + } +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + validator: (newValue, row, column) => { + if (isNaN(newValue)) { + return { + valid: false, + message: 'Price should be numeric' + }; + } + if (newValue < 2000) { + return { + valid: false, + message: 'Price should bigger than 2000' + }; + } + return true; + } +}]; + + +`; + +export default () => ( +
+

Product Price should bigger than $2000

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-level-editable-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-level-editable-table.js new file mode 100644 index 0000000..2338df1 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-level-editable-table.js @@ -0,0 +1,58 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + editable: (content, row, rowIndex, columnIndex) => content > 2101 +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + editable: (content, row, rowIndex, columnIndex) => content > 2101 +}]; + + +`; + +export default () => ( +
+

Only Product Price is bigger than 2101 is editable

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/click-to-edit-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/click-to-edit-table.js new file mode 100644 index 0000000..38bfb0c --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/click-to-edit-table.js @@ -0,0 +1,56 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Click to edit cell

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/column-level-editable-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/column-level-editable-table.js new file mode 100644 index 0000000..5e9d8a9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/column-level-editable-table.js @@ -0,0 +1,64 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editable: false +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' + // Product Name column can't be edit anymore + editable: false +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Product Name is non editable

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-table.js new file mode 100644 index 0000000..9c0deee --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-table.js @@ -0,0 +1,55 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Double click to edit cell

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/row-level-editable-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/row-level-editable-table.js new file mode 100644 index 0000000..f0ef206 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/row-level-editable-table.js @@ -0,0 +1,62 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + [0, 3] + }) } +/> +`; +export default () => ( +
+

Product ID: 0, 3 is non editable

+ [0, 3] + }) } + /> + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js new file mode 100644 index 0000000..0901eb6 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js @@ -0,0 +1,75 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { jobsGenerator } from 'utils/common'; + +const jobs = jobsGenerator(5); + +const owners = ['Allen', 'Bob', 'Cat']; +const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money']; + +const columns = [{ + dataField: 'id', + text: 'Job ID' +}, { + dataField: 'name', + text: 'Job Name', + filter: textFilter() +}, { + dataField: 'owner', + text: 'Job Owner', + filter: textFilter(), + formatter: (cell, row) => owners[cell], + filterValue: (cell, row) => owners[cell] +}, { + dataField: 'type', + text: 'Job Type', + filter: textFilter(), + formatter: (cell, row) => types[cell], + filterValue: (cell, row) => types[cell] +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const owners = ['Allen', 'Bob', 'Cat']; +const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money']; +const columns = [{ + dataField: 'id', + text: 'Job ID' +}, { + dataField: 'name', + text: 'Job Name', + filter: textFilter() +}, { + dataField: 'owner', + text: 'Job Owner', + filter: textFilter(), + formatter: (cell, row) => owners[cell], + filterValue: (cell, row) => owners[cell] +}, { + dataField: 'type', + text: 'Job Type', + filter: textFilter(), + filterValue: (cell, row) => types[cell] +}]; + +// shape of job: { id: 0, name: 'Job name 0', owner: 1, type: 3 } + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-text-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-text-filter.js new file mode 100644 index 0000000..e4be0a8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-text-filter.js @@ -0,0 +1,69 @@ +/* eslint no-console: 0 */ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + delay: 1000, // default is 500ms + style: { + backgroundColor: 'yellow' + }, + className: 'test-classname', + placeholder: 'Custom PlaceHolder', + onClick: e => console.log(e) + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + delay: 1000, // default is 500ms + style: { + backgroundColor: 'yellow' + }, + className: 'test-classname', + placeholder: 'Custom PlaceHolder', + onClick: e => console.log(e) + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-default-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-default-value.js new file mode 100644 index 0000000..d37c2f5 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-default-value.js @@ -0,0 +1,56 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + defaultValue: '2103' + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + defaultValue: '2103' + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-eq-comparator.js b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-eq-comparator.js new file mode 100644 index 0000000..cb34658 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter-eq-comparator.js @@ -0,0 +1,57 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter({ + comparator: Comparator.EQ // default is Comparator.LIKE + }) +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter({ + comparator: Comparator.EQ + }) +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + + +`; + +export default () => ( +
+

Product Name filter apply Equal Comparator

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/text-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter.js new file mode 100644 index 0000000..19eab47 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/text-filter.js @@ -0,0 +1,52 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-align-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-align-table.js new file mode 100644 index 0000000..6e98f2e --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-align-table.js @@ -0,0 +1,53 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + align: 'center' +}, { + dataField: 'name', + text: 'Product Name', + align: (cell, row, rowIndex, colIndex) => { + if (rowIndex % 2 === 0) return 'right'; + return 'left'; + } +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + align: 'center' +}, { + dataField: 'name', + text: 'Product Name', + align: (cell, row, rowIndex, colIndex) => { + if (rowIndex % 2 === 0) return 'right'; + return 'left'; + } +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-attrs-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-attrs-table.js new file mode 100644 index 0000000..7e7bfcb --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-attrs-table.js @@ -0,0 +1,48 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + attrs: { title: 'id column' } +}, { + dataField: 'name', + text: 'Product Name', + attrs: (cell, row, rowIndex, colIndex) => ({ 'data-test': `customized data ${rowIndex}` }) +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + attrs: { title: 'id column' } +}, { + dataField: 'name', + text: 'Product Name', + attrs: (cell, row, rowIndex, colIndex) => ({ 'data-test': \`customized data \${rowIndex}\` }) +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Try to hover on Product Name header column

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-class-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-class-table.js new file mode 100644 index 0000000..5fb7db3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-class-table.js @@ -0,0 +1,53 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + classes: 'demo-key-row' +}, { + dataField: 'name', + text: 'Product Name', + classes: (cell, row, rowIndex, colIndex) => { + if (rowIndex % 2 === 0) return 'demo-row-even'; + return 'demo-row-odd'; + } +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + classes: 'demo-key-row' +}, { + dataField: 'name', + text: 'Product Name', + classes: (cell, row, rowIndex, colIndex) => { + if (rowIndex % 2 === 0) return 'demo-row-even'; + return 'demo-row-odd'; + } +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js new file mode 100644 index 0000000..32f0d69 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js @@ -0,0 +1,51 @@ +/* eslint no-unused-vars: 0 */ +/* eslint no-alert: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + events: { + onClick: () => alert('Click on Product ID field') + } +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + events: { + onClick: () => alert('Click on Product ID field') + } +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Try to Click on Product ID columns

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-format-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-format-table.js new file mode 100644 index 0000000..dc5bb29 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-format-table.js @@ -0,0 +1,74 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +function priceFormatter(cell, row) { + if (row.onSale) { + return ( + $ { cell } NTD(Sales!!) + ); + } + + return ( + $ { cell } NTD + ); +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + formatter: priceFormatter +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +function priceFormatter(cell, row) { + if (row.onSale) { + return ( + + $ { cell } NTD(Sales!!) + + ); + } + + return ( + $ { cell } NTD + ); +} + +const columns = [ +// omit... +{ + dataField: 'price', + text: 'Product Price', + formatter: priceFormatter +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-format-with-extra-data-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-format-with-extra-data-table.js new file mode 100644 index 0000000..ac388ca --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-format-with-extra-data-table.js @@ -0,0 +1,73 @@ +/* eslint no-console: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(5, (value, index) => ({ + id: index, + name: `User Name ${index}`, + rank: Math.random() < 0.5 ? 'down' : 'up' +})); + +function rankFormatter(cell, row, rowIndex, formatExtraData) { + return ( + + ); +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'rank', + text: 'Rank', + formatter: rankFormatter, + formatExtraData: { + up: 'glyphicon glyphicon-chevron-up', + down: 'glyphicon glyphicon-chevron-down' + } +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +function rankFormatter(cell, row, rowIndex, formatExtraData) { + return ( + + ); +} + +const columns = [ +// omit... +{ + dataField: 'rank', + text: 'Rank', + formatter: rankFormatter, + formatExtraData: { + up: 'glyphicon glyphicon-chevron-up', + down: 'glyphicon glyphicon-chevron-down' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-hidden-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-hidden-table.js new file mode 100644 index 0000000..9930248 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-hidden-table.js @@ -0,0 +1,41 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + hidden: true +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + hidden: true +}, +// omit... +]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-style-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-style-table.js new file mode 100644 index 0000000..f37d7b9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-style-table.js @@ -0,0 +1,71 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + style: { + fontWeight: 'bold', + fontSize: '18px' + } +}, { + dataField: 'name', + text: 'Product Name', + style: (cell, row, rowIndex, colIndex) => { + if (rowIndex % 2 === 0) { + return { + backgroundColor: '#81c784' + }; + } + return { + backgroundColor: '#c8e6c9' + }; + } +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + style: { + fontWeight: 'bold', + fontSize: '18px' + } +}, { + dataField: 'name', + text: 'Product Name', + style: (cell, row, rowIndex, colIndex) => { + if (rowIndex % 2 === 0) { + return { + backgroundColor: '#81c784' + }; + } + return { + backgroundColor: '#c8e6c9' + }; + } +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-title-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-title-table.js new file mode 100644 index 0000000..016b0cb --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/column-title-table.js @@ -0,0 +1,46 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + title: true +}, { + dataField: 'name', + text: 'Product Name', + title: (cell, row, rowIndex, colIndex) => `this is custom title for ${cell}` +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID', + title: true +}, { + dataField: 'name', + text: 'Product Name', + title: (cell, row, rowIndex, colIndex) => \`this is custom title for \${cell}\` +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Try to hover on any Product Name cells

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/nested-data-table.js b/packages/react-bootstrap-table2-example/examples/columns/nested-data-table.js new file mode 100644 index 0000000..eb62ddc --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/columns/nested-data-table.js @@ -0,0 +1,62 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(5, (value, index) => ({ + id: index, + name: `User Name ${index}`, + phone: 21009831 + index, + address: { + city: 'New York', + postCode: '1111-4512' + } +})); + +const columns = [{ + dataField: 'id', + text: 'User ID' +}, { + dataField: 'name', + text: 'User Name' +}, { + dataField: 'phone', + text: 'Phone' +}, { + dataField: 'address.city', + text: 'City' +}, { + dataField: 'address.postCode', + text: 'PostCode' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'User ID' +}, { + dataField: 'name', + text: 'User Name' +}, { + dataField: 'phone', + text: 'Phone' +}, { + dataField: 'address.city', + text: 'City' +}, { + dataField: 'address.postCode', + text: 'PostCode' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-align-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-align-table.js new file mode 100644 index 0000000..f9fcab8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-align-table.js @@ -0,0 +1,47 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerAlign: 'center' +}, { + dataField: 'name', + text: 'Product Name', + headerAlign: (column, colIndex) => 'right' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerAlign: 'center' +}, { + dataField: 'name', + text: 'Product Name', + headerAlign: (column, colIndex) => 'right' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-attrs-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-attrs-table.js new file mode 100644 index 0000000..7f01864 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-attrs-table.js @@ -0,0 +1,47 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerAttrs: { title: 'ID header column' } +}, { + dataField: 'name', + text: 'Product Name', + headerAttrs: (column, colIndex) => ({ 'data-test': `customized data ${colIndex}` }) +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerAttrs: { title: 'ID header column' } +}, { + dataField: 'name', + text: 'Product Name', + headerAttrs: (column, colIndex) => ({ 'data-test': \`customized data \${colIndex}\` }) +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-class-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-class-table.js new file mode 100644 index 0000000..b897bc2 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-class-table.js @@ -0,0 +1,53 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + headerClasses: 'demo-row-odd' +}, { + dataField: 'price', + text: 'Product Price', + headerClasses: (column, colIndex) => { + if (colIndex % 2 === 0) return 'demo-row-even'; + return 'demo-row-odd'; + } +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + headerClasses: 'demo-row-odd' +}, { + dataField: 'price', + text: 'Product Price', + headerClasses: (column, colIndex) => { + if (colIndex % 2 === 0) return 'demo-row-even'; + return 'demo-row-odd'; + } +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-event-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-event-table.js new file mode 100644 index 0000000..7928a37 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-event-table.js @@ -0,0 +1,51 @@ +/* eslint no-unused-vars: 0 */ +/* eslint no-alert: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerEvents: { + onClick: () => alert('Click on Product ID header column') + } +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + events: { + onClick: () => alert('Click on Product ID header column') + } +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Try to Click on Product ID header column

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-format-filter-sort-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-format-filter-sort-table.js new file mode 100644 index 0000000..ce30d70 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-format-filter-sort-table.js @@ -0,0 +1,91 @@ +/* eslint no-unused-vars: 0 */ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +function priceFormatter(column, colIndex, { sortElement, filterElement }) { + return ( +
+ { filterElement } + { column.text } + { sortElement } +
+ ); +} + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price', + sort: true, + filter: textFilter(), + headerFormatter: priceFormatter +}]; + +const defaultSorted = [{ + dataField: 'name', + order: 'desc' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +// ... +function priceFormatter(column, colIndex, { sortElement, filterElement }) { + return ( +
+ { filterElement } + { column.text } + { sortElement } +
+ ); +} + +const columns = [ +// omit... +{ + dataField: 'price', + text: 'Product Price', + sort: true, + filter: textFilter(), + headerFormatter: priceFormatter +}]; + + +`; + +export default class DefaultSortTable extends React.PureComponent { + render() { + return ( +
+ + { sourceCode } +
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-format-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-format-table.js new file mode 100644 index 0000000..16e34fd --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-format-table.js @@ -0,0 +1,61 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +function priceFormatter(column, colIndex) { + return ( +
$$ { column.text } $$
+ ); +} + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + headerFormatter: priceFormatter +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +function priceFormatter(column, colIndex) { + return ( +
$$ { column.text } $$
+ ); +} + +const columns = [ +// omit... +{ + dataField: 'price', + text: 'Product Price', + headerFormatter: priceFormatter +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-style-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-style-table.js new file mode 100644 index 0000000..80ec299 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-style-table.js @@ -0,0 +1,69 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + headerStyle: { + backgroundColor: '#c8e6c9' + } +}, { + dataField: 'price', + text: 'Product Price', + headerStyle: (column, colIndex) => { + if (colIndex % 2 === 0) { + return { + backgroundColor: '#81c784' + }; + } + return { + backgroundColor: '#c8e6c9' + }; + } +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + headerStyle: { + backgroundColor: '#c8e6c9' + } +}, { + dataField: 'price', + text: 'Product Price', + headerStyle: (column, colIndex) => { + if (colIndex % 2 === 0) { + return { + backgroundColor: '#81c784' + }; + } + return { + backgroundColor: '#c8e6c9' + }; + } +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-title-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-title-table.js new file mode 100644 index 0000000..94c71b7 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-title-table.js @@ -0,0 +1,47 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerTitle: true +}, { + dataField: 'name', + text: 'Product Name', + headerTitle: (column, colIndex) => `this is custom title for ${column.text}` +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + headerTitle: true +}, { + dataField: 'name', + text: 'Product Name', + headerTitle: (column, colIndex) => \`this is custom title for \${column.text}\` +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/loading-overlay/empty-table-overlay.js b/packages/react-bootstrap-table2-example/examples/loading-overlay/empty-table-overlay.js new file mode 100644 index 0000000..faa0261 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/loading-overlay/empty-table-overlay.js @@ -0,0 +1,145 @@ +/* eslint react/no-multi-comp: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +// ... +const RemotePagination = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = ({ page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} +`; + +const NoDataIndication = () => ( +
+
+
+
+
+
+
+); + +const Table = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ } + /> + { sourceCode } +
+); + +Table.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class EmptyTableOverlay extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = (type, { page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 3000); + this.setState(() => ({ data: [] })); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} + +export default EmptyTableOverlay; diff --git a/packages/react-bootstrap-table2-example/examples/loading-overlay/table-overlay.js b/packages/react-bootstrap-table2-example/examples/loading-overlay/table-overlay.js new file mode 100644 index 0000000..efd296c --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/loading-overlay/table-overlay.js @@ -0,0 +1,158 @@ +/* eslint react/no-multi-comp: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import overlayFactory from 'react-bootstrap-table2-overlay'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import overlayFactory from 'react-bootstrap-table2-overlay'; + +// ... +const RemotePagination = ({ loading, data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +RemotePagination.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + loading: PropTypes.bool.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + loading: false, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = ({ page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + loading: false, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 3000); + this.setState(() => ({ loading: true })); + } + + render() { + const { data, sizePerPage, page, loading } = this.state; + return ( + + ); + } +} +`; + +const RemotePagination = ({ loading, data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +RemotePagination.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + loading: PropTypes.bool.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + loading: false, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = (type, { page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + loading: false, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 3000); + this.setState(() => ({ loading: true })); + } + + render() { + const { data, sizePerPage, page, loading } = this.state; + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/pagination/custom-pagination.js b/packages/react-bootstrap-table2-example/examples/pagination/custom-pagination.js new file mode 100644 index 0000000..63197a3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/custom-pagination.js @@ -0,0 +1,82 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +// ... + +const options = { + paginationSize: 4, + pageStartIndex: 0, + // alwaysShowAllBtns: true, // Always show next and previous button + // withFirstAndLast: false, // Hide the going to First and Last page button + // hideSizePerPage: true, // Hide the sizePerPage dropdown always + // hidePageListOnlyOnePage: true, // Hide the pagination list when only one page + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + sizePerPageList: [{ + text: '5', value: 5 + }, { + text: '10', value: 10 + }, { + text: 'All', value: products.length + }] // A numeric array is also available. the purpose of above example is custom the text +}; + + +`; +const options = { + paginationSize: 4, + pageStartIndex: 0, + // alwaysShowAllBtns: true // Always show next and previous button + // withFirstAndLast: false // Hide the going to First and Last page button + // hideSizePerPage: true, // Hide the sizePerPage dropdown always + // hidePageListOnlyOnePage: true, // Hide the pagination list when only one page + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + sizePerPageList: [{ + text: '5', value: 5 + }, { + text: '10', value: 10 + }, { + text: 'All', value: products.length + }] // A numeric array is also available. the purpose of above example is custom the text +}; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/pagination/index.js b/packages/react-bootstrap-table2-example/examples/pagination/index.js new file mode 100644 index 0000000..728f62d --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/index.js @@ -0,0 +1,45 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/pagination/pagination-hooks.js b/packages/react-bootstrap-table2-example/examples/pagination/pagination-hooks.js new file mode 100644 index 0000000..21807d8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/pagination-hooks.js @@ -0,0 +1,82 @@ +/* eslint react/prefer-stateless-function: 0 */ +/* eslint no-console: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const options = { + onSizePerPageChange: (sizePerPage, page) => { + console.log('Size per page change!!!'); + console.log('Newest size per page:' + sizePerPage); + console.log('Newest page:' + page); + }, + onPageChange: (page, sizePerPage) => { + console.log('Page change!!!'); + console.log('Newest size per page:' + sizePerPage); + console.log('Newest page:' + page); + } +}; + + +`; + +const options = { + onSizePerPageChange: (sizePerPage, page) => { + console.log('Size per page change!!!'); + console.log(`Newest size per page: ${sizePerPage}`); + console.log(`Newest page: ${page}`); + }, + onPageChange: (page, sizePerPage) => { + console.log('Page change!!!'); + console.log(`Newest size per page: ${sizePerPage}`); + console.log(`Newest page: ${page}`); + } +}; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-all.js b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js new file mode 100644 index 0000000..2720584 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js @@ -0,0 +1,202 @@ +/* eslint guard-for-in: 0 */ +/* eslint no-restricted-syntax: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +// ... + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +RemoteAll.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + totalSize: products.length, + sizePerPage: 10 + }; + this.handleTableChange = this.handleTableChange.bind(this); + } + + handleTableChange = (type, { page, sizePerPage, filters }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + page, + data: result.slice(currentIndex, currentIndex + sizePerPage), + totalSize: result.length, + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} +`; + +const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+

When remote.pagination is enabled, the filtering, + sorting and searching will also change to remote mode automatically

+ + { sourceCode } +
+); + +RemoteAll.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + totalSize: products.length, + sizePerPage: 10 + }; + this.handleTableChange = this.handleTableChange.bind(this); + } + + handleTableChange = (type, { page, sizePerPage, filters }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + page, + data: result.slice(currentIndex, currentIndex + sizePerPage), + totalSize: result.length, + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-celledit.js b/packages/react-bootstrap-table2-example/examples/remote/remote-celledit.js new file mode 100644 index 0000000..68ec867 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-celledit.js @@ -0,0 +1,164 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +// ... + +const RemoteCellEdit = (props) => { + const cellEdit = { + mode: 'click', + errorMessage: props.errorMessage + }; + + return ( +
+ + { sourceCode } +
+ ); +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products, + errorMessage: null + }; + } + + handleTableChange = (type, { data, cellEdit: { rowId, dataField, newValue } }) => { + setTimeout(() => { + if (newValue === 'test' && dataField === 'name') { + this.setState(() => ({ + data, + errorMessage: 'Oops, product name shouldn't be "test"' + })); + } else { + const result = data.map((row) => { + if (row.id === rowId) { + const newRow = { ...row }; + newRow[dataField] = newValue; + return newRow; + } + return row; + }); + this.setState(() => ({ + data: result, + errorMessage: null + })); + } + }, 2000); + } + + render() { + return ( + + ); + } +} +`; + + +const RemoteCellEdit = (props) => { + const cellEdit = { + mode: 'click', + errorMessage: props.errorMessage + }; + + return ( +
+ + { sourceCode } +
+ ); +}; + +RemoteCellEdit.propTypes = { + data: PropTypes.array.isRequired, + onTableChange: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products, + errorMessage: null + }; + } + + handleTableChange = (type, { data, cellEdit: { rowId, dataField, newValue } }) => { + setTimeout(() => { + if (newValue === 'test' && dataField === 'name') { + this.setState(() => ({ + data, + errorMessage: 'Oops, product name shouldn\'t be "test"' + })); + } else { + const result = data.map((row) => { + if (row.id === rowId) { + const newRow = { ...row }; + newRow[dataField] = newValue; + return newRow; + } + return row; + }); + this.setState(() => ({ + data: result, + errorMessage: null + })); + } + }, 2000); + } + + render() { + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-filter.js b/packages/react-bootstrap-table2-example/examples/remote/remote-filter.js new file mode 100644 index 0000000..85932a6 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-filter.js @@ -0,0 +1,160 @@ +/* eslint guard-for-in: 0 */ +/* eslint no-restricted-syntax: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(17); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const RemoteFilter = props => ( +
+ + { sourceCode } +
+); + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { filters }) => { + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} +`; + +const RemoteFilter = props => ( +
+ + { sourceCode } +
+); + +RemoteFilter.propTypes = { + data: PropTypes.array.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { filters }) => { + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-pagination.js b/packages/react-bootstrap-table2-example/examples/remote/remote-pagination.js new file mode 100644 index 0000000..0765863 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-pagination.js @@ -0,0 +1,133 @@ +/* eslint react/no-multi-comp: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +// ... +const RemotePagination = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = (type, { page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} +`; + +const RemotePagination = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { sourceCode } +
+); + +RemotePagination.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = (type, { page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-sort.js b/packages/react-bootstrap-table2-example/examples/remote/remote-sort.js new file mode 100644 index 0000000..bd6147f --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-sort.js @@ -0,0 +1,163 @@ +/* eslint no-restricted-syntax: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(5); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price', + sort: true +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const RemoteSort = props => ( +
+ + { sourceCode } +
+); + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { sortField, sortOrder, data }) => { + setTimeout(() => { + let result; + if (sortOrder === 'asc') { + result = data.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return 1; + } else if (b[sortField] > a[sortField]) { + return -1; + } + return 0; + }); + } else { + result = data.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return -1; + } else if (b[sortField] > a[sortField]) { + return 1; + } + return 0; + }); + } + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} +`; + +const RemoteSort = props => ( +
+ + { sourceCode } +
+); + +RemoteSort.propTypes = { + data: PropTypes.array.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { sortField, sortOrder, data }) => { + setTimeout(() => { + let result; + if (sortOrder === 'asc') { + result = data.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return 1; + } else if (b[sortField] > a[sortField]) { + return -1; + } + return 0; + }); + } else { + result = data.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return -1; + } else if (b[sortField] > a[sortField]) { + return 1; + } + return 0; + }); + } + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select-with-cell-edit.js b/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select-with-cell-edit.js new file mode 100644 index 0000000..742d072 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select-with-cell-edit.js @@ -0,0 +1,72 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + clickToEdit: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + clickToEdit: true // Click to edit cell also +}; + +const cellEdit = { + mode: 'click' +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select.js b/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select.js new file mode 100644 index 0000000..6789789 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select.js @@ -0,0 +1,57 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/hide-selection-column.js b/packages/react-bootstrap-table2-example/examples/row-selection/hide-selection-column.js new file mode 100644 index 0000000..134e378 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/hide-selection-column.js @@ -0,0 +1,61 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + hideSelectColumn: true, + bgColor: '#00BFFF' +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + hideSelectColumn: true, + bgColor: '#00BFFF' +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js b/packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js new file mode 100644 index 0000000..6789789 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js @@ -0,0 +1,57 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js b/packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js new file mode 100644 index 0000000..7cf5e15 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js @@ -0,0 +1,59 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + nonSelectable: [0, 2, 4] +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + nonSelectable: [0, 2, 4] +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/selection-bgcolor.js b/packages/react-bootstrap-table2-example/examples/row-selection/selection-bgcolor.js new file mode 100644 index 0000000..ea391e4 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-bgcolor.js @@ -0,0 +1,94 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow1 = { + mode: 'checkbox', + clickToSelect: true, + bgColor: '#00BFFF' +}; + +const selectRow2 = { + mode: 'checkbox', + clickToSelect: true, + bgColor: (row, rowIndex) => (rowIndex > 1 ? '#00BFFF' : '#00FFFF') +}; + +const sourceCode1 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + bgColor: '#00BFFF' +}; + + +`; + +const sourceCode2 = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + bgColor: (row, rowIndex) => (rowIndex > 1 ? '#00BFFF' : '#00FFFF') +}; + + +`; + +export default () => ( +
+ + { sourceCode1 } + + { sourceCode2 } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/selection-class.js b/packages/react-bootstrap-table2-example/examples/row-selection/selection-class.js new file mode 100644 index 0000000..7804c60 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-class.js @@ -0,0 +1,96 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow1 = { + mode: 'checkbox', + clickToSelect: true, + classes: 'selection-row' +}; + +const selectRow2 = { + mode: 'checkbox', + clickToSelect: true, + classes: (row, rowIndex) => + (rowIndex > 1 ? 'row-index-bigger-than-2101' : 'row-index-small-than-2101') +}; + +const sourceCode1 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + classes: 'selection-row' +}; + + +`; + +const sourceCode2 = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + classes: (row, rowIndex) => + (rowIndex > 1 ? 'row-index-bigger-than-2101' : 'row-index-small-than-2101') +}; + + +`; + +export default () => ( +
+ + { sourceCode1 } + + { sourceCode2 } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/selection-hooks.js b/packages/react-bootstrap-table2-example/examples/row-selection/selection-hooks.js new file mode 100644 index 0000000..9b104c0 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-hooks.js @@ -0,0 +1,68 @@ + +/* eslint no-console: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + onSelect: (row, isSelect, rowIndex) => { + console.log(row.id); + console.log(isSelect); + console.log(rowIndex); + }, + onSelectAll: (isSelect, rows) => { + console.log(isSelect); + console.log(rows); + } +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/selection-style.js b/packages/react-bootstrap-table2-example/examples/row-selection/selection-style.js new file mode 100644 index 0000000..97f32b9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-style.js @@ -0,0 +1,100 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow1 = { + mode: 'checkbox', + clickToSelect: true, + style: { backgroundColor: '#c8e6c9' } +}; + +const selectRow2 = { + mode: 'checkbox', + clickToSelect: true, + style: (row, rowIndex) => { + const backgroundColor = rowIndex > 1 ? '#00BFFF' : '#00FFFF'; + return { backgroundColor }; + } +}; + +const sourceCode1 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + style: { backgroundColor: '#c8e6c9' } +}; + + +`; + +const sourceCode2 = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + style: (row, rowIndex) => { + const backgroundColor = rowIndex > 1 ? '#00BFFF' : '#00FFFF'; + return { backgroundColor }; + } +}; + + +`; + +export default () => ( +
+ + { sourceCode1 } + + { sourceCode2 } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js b/packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js new file mode 100644 index 0000000..4df2109 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js @@ -0,0 +1,57 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'radio', + clickToSelect: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'radio', + clickToSelect: true +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/rows/row-class.js b/packages/react-bootstrap-table2-example/examples/rows/row-class.js new file mode 100644 index 0000000..b7aa8bb --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/rows/row-class.js @@ -0,0 +1,83 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowClasses1 = 'custom-row-class'; + +const sourceCode1 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowClasses = 'custom-row-class'; + + +`; + +const rowClasses2 = (row, rowIndex) => { + let classes = null; + + if (rowIndex > 2) { + classes = 'index-bigger-than-two'; + } + + return classes; +}; + +const sourceCode2 = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowClasses = (row, rowIndex) => { + let classes = null; + + if (rowIndex > 2) { + classes = 'index-bigger-than-two'; + } + + return classes; +}; + + +`; + +export default () => ( +
+ + { sourceCode1 } + + { sourceCode2 } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/rows/row-event.js b/packages/react-bootstrap-table2-example/examples/rows/row-event.js new file mode 100644 index 0000000..915f9af --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/rows/row-event.js @@ -0,0 +1,57 @@ +/* eslint no-unused-vars: 0 */ +/* eslint no-alert: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowEvents = { + onClick: (e) => { + alert('click on row'); + } +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowEvents = { + onClick: (e) => { + alert('click on row'); + } +}; + + +`; + +export default () => ( +
+

Try to click on any rows

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/rows/row-style.js b/packages/react-bootstrap-table2-example/examples/rows/row-style.js new file mode 100644 index 0000000..024cc84 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/rows/row-style.js @@ -0,0 +1,95 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowStyle1 = { backgroundColor: '#c8e6c9' }; + +const sourceCode1 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowStyle = { backgroundColor: '#c8e6c9' }; + + +`; + +const rowStyle2 = (row, rowIndex) => { + const style = {}; + if (row.id > 3) { + style.backgroundColor = '#c8e6c9'; + } else { + style.backgroundColor = '#00BFFF'; + } + + if (rowIndex > 2) { + style.fontWeight = 'bold'; + style.color = 'white'; + } + + return style; +}; + +const sourceCode2 = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const rowStyle2 = (row, rowIndex) => { + const style = {}; + if (row.id > 3) { + style.backgroundColor = '#c8e6c9'; + } else { + style.backgroundColor = '#00BFFF'; + } + + if (rowIndex > 2) { + style.fontWeight = 'bold'; + style.color = 'white'; + } + + return style; +}; + + +`; + +export default () => ( +
+ + { sourceCode1 } + + { sourceCode2 } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/sort/custom-sort-table.js b/packages/react-bootstrap-table2-example/examples/sort/custom-sort-table.js new file mode 100644 index 0000000..a04b5ae --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/custom-sort-table.js @@ -0,0 +1,63 @@ +/* eslint no-unused-vars: 0 */ + +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + // here, we implement a custom sort which perform a reverse sorting + sortFunc: (a, b, order, dataField) => { + if (order === 'asc') { + return b - a; + } + return a - b; // desc + } +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + // here, we implement a custom sort which perform a reverse sorting + sortFunc: (a, b, order, dataField) => { + if (order === 'asc') { + return b - a; + } + return a - b; // desc + } +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+

Product ID sorting is reverted

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js b/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js new file mode 100644 index 0000000..115fe16 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js @@ -0,0 +1,71 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price', + sort: true +}]; + +const defaultSorted = [{ + dataField: 'name', + order: 'desc' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price', + sort: true +}]; + +const defaultSorted = [{ + dataField: 'name', + order: 'desc' +}]; + + +`; + + +class DefaultSortTable extends React.PureComponent { + render() { + return ( +
+ + { sourceCode } +
+ ); + } +} + +export default DefaultSortTable; diff --git a/packages/react-bootstrap-table2-example/examples/sort/enable-sort-table.js b/packages/react-bootstrap-table2-example/examples/sort/enable-sort-table.js new file mode 100644 index 0000000..16e628f --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/enable-sort-table.js @@ -0,0 +1,46 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true +}, { + dataField: 'name', + text: 'Product Name', + sort: true +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/sort/header-sorting-classes.js b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-classes.js new file mode 100644 index 0000000..b1b470e --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-classes.js @@ -0,0 +1,63 @@ +/* eslint + no-unused-vars: 0 + arrow-body-style: 0 +*/ + +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => ( + sortOrder === 'asc' ? 'demo-sorting-asc' : 'demo-sorting-desc' +); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingClasses +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingClasses +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => ( + sortOrder === 'asc' ? 'demo-sorting-asc' : 'demo-sorting-desc' +); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingClasses +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingClasses +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/sort/header-sorting-style.js b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-style.js new file mode 100644 index 0000000..cd2c6f0 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-style.js @@ -0,0 +1,56 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const headerSortingStyle = { backgroundColor: '#c8e6c9' }; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingStyle +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingStyle +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const headerSortingStyle = { backgroundColor: '#c8e6c9' }; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingStyle +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingStyle +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/welcome.js b/packages/react-bootstrap-table2-example/examples/welcome.js new file mode 100644 index 0000000..41f3547 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/welcome.js @@ -0,0 +1,40 @@ +import React from 'react'; +import Typed from 'typed.js'; + +export default class Welcome extends React.Component { + componentDidMount() { + // type.js config + const options = { + strings: ['It\'s a bootstrap table rebuilt for React.js '], + typeSpeed: 50, + backSpeed: 50 + }; + this.typed = new Typed(this.el, options); + } + + componentWillUnmount() { + // Make sure to destroy Typed instance on unmounting to prevent memory leaks + this.typed.destroy(); + } + + render() { + return ( +
+
+

react-bootstrap-table2

+ { this.el = el; } } + /> +
+ + + +
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/package.json b/packages/react-bootstrap-table2-example/package.json new file mode 100644 index 0000000..169e3db --- /dev/null +++ b/packages/react-bootstrap-table2-example/package.json @@ -0,0 +1,33 @@ +{ + "name": "react-bootstrap-table2-example", + "version": "0.0.3", + "description": "", + "main": "index.js", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "storybook": "start-storybook -p 6006", + "gh-pages:clean": "rimraf ./storybook-static", + "gh-pages:build": "build-storybook" + }, + "author": "", + "license": "ISC", + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^15.0.0", + "react-dom": "^15.0.0" + }, + "dependencies": { + "bootstrap": "^3.3.7" + }, + "devDependencies": { + "@storybook/addon-console": "^1.0.0", + "@storybook/react": "^3.2.8", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-preset-env": "^1.6.1", + "react-redux": "^5.0.6", + "redux": "^3.7.2", + "redux-thunk": "^2.2.0", + "typed.js": "^2.0.5" + } +} diff --git a/packages/react-bootstrap-table2-example/src/components/common/code-block.js b/packages/react-bootstrap-table2-example/src/components/common/code-block.js new file mode 100644 index 0000000..c1be07d --- /dev/null +++ b/packages/react-bootstrap-table2-example/src/components/common/code-block.js @@ -0,0 +1,26 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default class extends Component { + static propTypes = { + children: PropTypes.string + } + static defaultProps = { + children: '' + } + componentDidMount() { + // code-prettify + // run the PR.prettyPrint() function once your page has finished loading + if (typeof (PR) !== 'undefined') PR.prettyPrint(); // eslint-disable-line no-undef + } + + render() { + return ( +
+
+          { this.props.children }
+        
+
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js new file mode 100644 index 0000000..0d5ad84 --- /dev/null +++ b/packages/react-bootstrap-table2-example/src/utils/common.js @@ -0,0 +1,31 @@ +/** + * products generator for stories + * + * @param {Number} quantity - quantity of products + * @param {Function} callback - callback func which is similiar to 'mapFunction' + * aims to customize product format + * + * @return {Array} - products array + */ +export const productsGenerator = (quantity = 5, callback) => { + if (callback) return Array.from({ length: quantity }, callback); + + // if no given callback, retrun default product format. + return ( + Array.from({ length: quantity }, (value, index) => ({ + id: index, + name: `Item name ${index}`, + price: 2100 + index + })) + ); +}; + +export const jobsGenerator = (quantity = 5) => + Array.from({ length: quantity }, (value, index) => ({ + id: index, + name: `Job name ${index}`, + owner: Math.floor(Math.random() * 3), + type: Math.floor(Math.random() * 5) + })); + +export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js new file mode 100644 index 0000000..8037edb --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -0,0 +1,195 @@ +/* eslint import/no-unresolved: 0 */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; + +// welcome +import Welcome from 'examples/welcome'; +// basic +import BasicTable from 'examples/basic'; +import BorderlessTable from 'examples/basic/borderless-table'; +import StripHoverCondensedTable from 'examples/basic/striped-hover-condensed-table'; +import NoDataTable from 'examples/basic/no-data-table'; +import CaptionTable from 'examples/basic/caption-table'; + +// work on columns +import NestedDataTable from 'examples/columns/nested-data-table'; +import ColumnFormatTable from 'examples/columns/column-format-table'; +import ColumnFormatExtraDataTable from 'examples/columns/column-format-with-extra-data-table'; +import ColumnClassTable from 'examples/columns/column-class-table'; +import ColumnStyleTable from 'examples/columns/column-style-table'; +import ColumnAlignTable from 'examples/columns/column-align-table'; +import ColumnTitleTable from 'examples/columns/column-title-table'; +import ColumnEventTable from 'examples/columns/column-event-table'; +import ColumnHiddenTable from 'examples/columns/column-hidden-table'; +import ColumnAttrsTable from 'examples/columns/column-attrs-table'; + +// work on header columns +import HeaderColumnFormatTable from 'examples/header-columns/column-format-table'; +import HeaderColumnFormatWithSortFilterTable from 'examples/header-columns/column-format-filter-sort-table.js'; +import HeaderColumnAlignTable from 'examples/header-columns/column-align-table'; +import HeaderColumnTitleTable from 'examples/header-columns/column-title-table'; +import HeaderColumnEventTable from 'examples/header-columns/column-event-table'; +import HeaderColumnClassTable from 'examples/header-columns/column-class-table'; +import HeaderColumnStyleTable from 'examples/header-columns/column-style-table'; +import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table'; + +// column filter +import TextFilter from 'examples/column-filter/text-filter'; +import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value'; +import TextFilterComparator from 'examples/column-filter/text-filter-eq-comparator'; +import CustomTextFilter from 'examples/column-filter/custom-text-filter'; +import CustomFilterValue from 'examples/column-filter/custom-filter-value'; + +// work on rows +import RowStyleTable from 'examples/rows/row-style'; +import RowClassTable from 'examples/rows/row-class'; +import RowEventTable from 'examples/rows/row-event'; + +// table sort +import EnableSortTable from 'examples/sort/enable-sort-table'; +import DefaultSortTable from 'examples/sort/default-sort-table'; +import CustomSortTable from 'examples/sort/custom-sort-table'; +import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes'; +import HeaderSortingStyleTable from 'examples/sort/header-sorting-style'; + +// cell editing +import ClickToEditTable from 'examples/cell-edit/click-to-edit-table'; +import DoubleClickToEditTable from 'examples/cell-edit/dbclick-to-edit-table'; +import BlurToSaveTable from 'examples/cell-edit/blur-to-save-table'; +import RowLevelEditableTable from 'examples/cell-edit/row-level-editable-table'; +import ColumnLevelEditableTable from 'examples/cell-edit/column-level-editable-table'; +import CellLevelEditable from 'examples/cell-edit/cell-level-editable-table'; +import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table'; +import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table'; +import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table'; +import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table'; + +// work on row selection +import SingleSelectionTable from 'examples/row-selection/single-selection'; +import MultipleSelectionTable from 'examples/row-selection/multiple-selection'; +import ClickToSelectTable from 'examples/row-selection/click-to-select'; +import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-select-with-cell-edit'; +import SelectionStyleTable from 'examples/row-selection/selection-style'; +import SelectionClassTable from 'examples/row-selection/selection-class'; +import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows'; +import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor'; +import SelectionHooks from 'examples/row-selection/selection-hooks'; +import HideSelectionColumnTable from 'examples/row-selection/hide-selection-column'; + +// pagination +import PaginationTable from 'examples/pagination'; +import PaginationHooksTable from 'examples/pagination/pagination-hooks'; +import CustomPaginationTable from 'examples/pagination/custom-pagination'; + +// loading overlay +import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay'; +import TableOverlay from 'examples/loading-overlay/table-overlay'; + +// remote +import RemoteSort from 'examples/remote/remote-sort'; +import RemoteFilter from 'examples/remote/remote-filter'; +import RemotePaginationTable from 'examples/remote/remote-pagination'; +import RemoteCellEdit from 'examples/remote/remote-celledit'; +import RemoteAll from 'examples/remote/remote-all'; + +// css style +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'stories/stylesheet/tomorrow.min.css'; +import 'stories/stylesheet/storybook.scss'; +import '../../react-bootstrap-table2/style/react-bootstrap-table2.scss'; +import '../../react-bootstrap-table2-paginator/style/react-bootstrap-table2-paginator.scss'; + +// import { action } from '@storybook/addon-actions'; + +// action('hello'); +storiesOf('Welcome', module) + .add('react bootstrap table 2 ', () => ); + +storiesOf('Basic Table', module) + .add('basic table', () => ) + .add('striped, hover, condensed table', () => ) + .add('borderless table', () => ) + .add('Indication For Empty Table', () => ) + .add('Table with caption', () => ); + +storiesOf('Work on Columns', module) + .add('Display Nested Data', () => ) + .add('Column Formatter', () => ) + .add('Column Formatter with Custom Data', () => ) + .add('Column Align', () => ) + .add('Column Title', () => ) + .add('Column Hidden', () => ) + .add('Column Event', () => ) + .add('Customize Column Class', () => ) + .add('Customize Column Style', () => ) + .add('Customize Column HTML attribute', () => ); + +storiesOf('Work on Header Columns', module) + .add('Column Formatter', () => ) + .add('Column Format with Filter and Sort', () => ) + .add('Column Align', () => ) + .add('Column Title', () => ) + .add('Column Event', () => ) + .add('Customize Column Class', () => ) + .add('Customize Column Style', () => ) + .add('Customize Column HTML attribute', () => ); + +storiesOf('Column Filter', module) + .add('Text Filter', () => ) + .add('Text Filter with Default Value', () => ) + .add('Text Filter with Comparator', () => ) + .add('Custom Text Filter', () => ) + // add another filter type example right here. + .add('Custom Filter Value', () => ); + +storiesOf('Work on Rows', module) + .add('Customize Row Style', () => ) + .add('Customize Row Class', () => ) + .add('Row Event', () => ); + +storiesOf('Sort Table', module) + .add('Enable Sort', () => ) + .add('Default Sort Table', () => ) + .add('Custom Sort Fuction', () => ) + .add('Custom Classes on Sorting Header Column', () => ) + .add('Custom Style on Sorting Header Column', () => ); + +storiesOf('Cell Editing', module) + .add('Click to Edit', () => ) + .add('DoubleClick to Edit', () => ) + .add('Blur to Save Cell', () => ) + .add('Row Level Editable', () => ) + .add('Column Level Editable', () => ) + .add('Cell Level Editable', () => ) + .add('Rich Hook Functions', () => ) + .add('Validation', () => ) + .add('Custom Cell Style When Editing', () => ) + .add('Custom Cell Classes When Editing', () => ); + +storiesOf('Row Selection', module) + .add('Single Selection', () => ) + .add('Multiple Selection', () => ) + .add('Click to Select', () => ) + .add('Click to Select and Edit Cell', () => ) + .add('Selection Style', () => ) + .add('Selection Class', () => ) + .add('Selection Background Color', () => ) + .add('Not Selectabled Rows', () => ) + .add('Selection Hooks', () => ) + .add('Hide Selection Column', () => ); + +storiesOf('Pagination', module) + .add('Basic Pagination Table', () => ) + .add('Pagination Hooks', () => ) + .add('Custom Pagination', () => ); + +storiesOf('EmptyTableOverlay', module) + .add('Empty Table Overlay', () => ) + .add('Table Overlay', () => ); + +storiesOf('Remote', module) + .add('Remote Sort', () => ) + .add('Remote Filter', () => ) + .add('Remote Pagination', () => ) + .add('Remote Cell Editing', () => ) + .add('Remote All', () => ); diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss new file mode 100644 index 0000000..5ee5a54 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss @@ -0,0 +1,14 @@ + +body { + font-family: "Roboto", Arial, "Helvetica Neue", Helvetica, sans-serif; + font-weight: 300; +} + +// font color +$grey-500: #9E9E9E; +$grey-900: #212121; +$pink-500: #E91E63; +$green-lighten-2: #81c784; +$green-lighten-4: #c8e6c9; +$light-blue: #00BFFF; +$markdown-color: #f6f8fa; diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/base/_code-block.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_code-block.scss new file mode 100644 index 0000000..48b2052 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_code-block.scss @@ -0,0 +1,10 @@ +.highlight-text-html-basic { + margin-bottom: 16px; + + pre { + background-color: $markdown-color; + padding: 16px; + border-radius: 3px; + } +} + diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/base/_github-corner.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_github-corner.scss new file mode 100644 index 0000000..a00c580 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_github-corner.scss @@ -0,0 +1,27 @@ +.github-corner:hover .octo-arm { + animation: octocat-wave 560ms ease-in-out; +} + +@keyframes octocat-wave { + 0%, + 100% { + transform: rotate(0) + } + 20%, + 60% { + transform: rotate(-25deg) + } + 40%, + 80% { + transform: rotate(10deg) + } +} + +@media (max-width:500px) { + .github-corner:hover .octo-arm { + animation: none + } + .github-corner .octo-arm { + animation: octocat-wave 560ms ease-in-out + } +} diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss new file mode 100644 index 0000000..23f543a --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss @@ -0,0 +1,11 @@ +.editing-name { + background-color: #20B2AA; +} + +.editing-price-bigger-than-2101 { + background-color: #00BFFF; +} + +.editing-price-small-than-2101 { + background-color: #00FFFF; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss new file mode 100644 index 0000000..3468c70 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss @@ -0,0 +1,12 @@ +.demo-key-row { + font-weight: bold; + font-size: 18px; +} + +.demo-row-even { + background-color: $green-lighten-2; +} + +.demo-row-odd { + background-color: $green-lighten-4; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/loading-overlay/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/loading-overlay/_index.scss new file mode 100644 index 0000000..982c07f --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/loading-overlay/_index.scss @@ -0,0 +1,52 @@ +.spinner { + margin: 100px auto; + width: 50px; + height: 40px; + text-align: center; + font-size: 10px; +} + +.spinner > div { + background-color: #333; + height: 100%; + width: 6px; + display: inline-block; + + -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; + animation: sk-stretchdelay 1.2s infinite ease-in-out; +} + +.spinner .rect2 { + -webkit-animation-delay: -1.1s; + animation-delay: -1.1s; +} + +.spinner .rect3 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} + +.spinner .rect4 { + -webkit-animation-delay: -0.9s; + animation-delay: -0.9s; +} + +.spinner .rect5 { + -webkit-animation-delay: -0.8s; + animation-delay: -0.8s; +} + +@-webkit-keyframes sk-stretchdelay { + 0%, 40%, 100% { -webkit-transform: scaleY(0.4) } + 20% { -webkit-transform: scaleY(1.0) } +} + +@keyframes sk-stretchdelay { + 0%, 40%, 100% { + transform: scaleY(0.4); + -webkit-transform: scaleY(0.4); + } 20% { + transform: scaleY(1.0); + -webkit-transform: scaleY(1.0); + } +} diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/row-selection/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/row-selection/_index.scss new file mode 100644 index 0000000..91cfc67 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/row-selection/_index.scss @@ -0,0 +1,11 @@ +.selection-row { + background-color: #c8e6c9; +} + +.row-index-bigger-than-2101 { + background-color: #00BFFF; +} + +.row-index-small-than-2101 { + background-color: #00FFFF; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/rows/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/rows/_index.scss new file mode 100644 index 0000000..16da83d --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/rows/_index.scss @@ -0,0 +1,7 @@ +.custom-row-class { + background-color: #c8e6c9; +} + +.index-bigger-than-two { + background-color: #00BFFF; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/sort/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/sort/_index.scss new file mode 100644 index 0000000..bb8b1e8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/sort/_index.scss @@ -0,0 +1,8 @@ +.demo-sorting, +.demo-sorting-asc { + background-color: $green-lighten-2; +} + +.demo-sorting-desc { + background-color: $light-blue; +} diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss new file mode 100644 index 0000000..cf0a024 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss @@ -0,0 +1,12 @@ +/* customized style for storybook*/ +@import "base/base"; +@import "base/github-corner"; +@import "base/code-block"; + +@import "welcome/index"; +@import "columns/index"; +@import "cell-edit/index"; +@import "row-selection/index"; +@import "rows/index"; +@import "sort/index"; +@import "loading-overlay/index"; diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/tomorrow.min.css b/packages/react-bootstrap-table2-example/stories/stylesheet/tomorrow.min.css new file mode 100644 index 0000000..3638ea9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/tomorrow.min.css @@ -0,0 +1,2 @@ +/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */ +.prettyprint{background:#fff;font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#4d4d4c}ol.linenums{margin-top:0;margin-bottom:0;color:#8e908c}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#fff;list-style-type:decimal}@media screen{.str{color:#718c00}.kwd{color:#8959a8}.com{color:#8e908c}.typ{color:#4271ae}.lit{color:#f5871f}.pun{color:#4d4d4c}.opn{color:#4d4d4c}.clo{color:#4d4d4c}.tag{color:#c82829}.atn{color:#f5871f}.atv{color:#3e999f}.dec{color:#f5871f}.var{color:#c82829}.fun{color:#4271ae}} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/welcome/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/welcome/_index.scss new file mode 100644 index 0000000..1d7949c --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/welcome/_index.scss @@ -0,0 +1,38 @@ +.welcome { + margin-top: 70px; + text-align: center; + padding: 30px 30px; + + &-title { + color: $grey-900; + } + &-sub-title { + font-size: 30px; + color: $grey-500; + } +} + +span.love-icon { + color: $pink-500; +} + +/* Adding cursor blinking animation */ +.typed-cursor{ + font-size: 30px; + color: $grey-500; + opacity: 1; + animation: typedjsBlink 0.7s infinite; +} +@keyframes typedjsBlink{ + 50% { opacity: 0.0; } +} +@-webkit-keyframes typedjsBlink{ + 0% { opacity: 1; } + 50% { opacity: 0.0; } + 100% { opacity: 1; } +} +.typed-fade-out{ + opacity: 0; + transition: opacity .25s; + animation: 0; +} diff --git a/packages/react-bootstrap-table2-example/test/utils/common.test.js b/packages/react-bootstrap-table2-example/test/utils/common.test.js new file mode 100644 index 0000000..ca8ecab --- /dev/null +++ b/packages/react-bootstrap-table2-example/test/utils/common.test.js @@ -0,0 +1,36 @@ +import { productsGenerator } from '../../src/utils/common'; + +describe('Utils', () => { + describe('productsGenerator', () => { + const quantity = 2; + + it('should return an array', () => { + expect(Array.isArray(productsGenerator())).toBe(true); + }); + + it('should return 5 products without params', () => { + expect(productsGenerator().length).toEqual(5); + }); + + it('should return an array with given quntity', () => { + expect(productsGenerator(quantity).length).toEqual(quantity); + }); + + describe('when callback is defined', () => { + const callback = (value, index) => ({ + id: index, + name: 'react-bootstrap-table-2' + }); + + it('should return customized products format', () => { + const products = productsGenerator(quantity, callback); + const product = products[0]; + + expect(Array.isArray(products)).toBe(true); + expect(products.length).toBe(quantity); + expect(product).toHaveProperty('id', 0); + expect(product).toHaveProperty('name', 'react-bootstrap-table-2'); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md new file mode 100644 index 0000000..f8b6788 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/README.md @@ -0,0 +1,55 @@ +# react-bootstrap-table2-filter + +`react-bootstrap-table2` separate the filter core code base to [`react-bootstrap-table2-filter`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-filter), so there's a little bit different when you use column filter than `react-bootstrap-table`. In the following, we are going to show you how to enable the column filter: + +**[Live Demo For Column Filter](https://github.com/react-bootstrap-table/react-bootstrap-table2/blob/gh-pages-src/storybook/index.html?selectedKind=Column%20Filter)** + +**[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/filter-props.html)** + +----- + +## Install + +```sh +$ npm install react-bootstrap-table2-filter --save +``` + +You can get all types of filters via import and these filters are a factory function to create a individual filter instance. Currently, we support following filters: + +* TextFilter +* **Coming soon!** + +## Text Filter +Following is a quick demo for enable the column filter on **Product Price** column!! + +```js +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +// omit... +const columns = [ + ..., { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + + +``` + +In addition, we preserve all of the filter features and functionality in legacy `react-bootstrap-table`, but in different way to do it: + +```js +import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; +// omit... + +const priceFilter = textFilter({ + placeholder: 'My Custom PlaceHolder', // custom the input placeholder + className: 'my-custom-text-filter', // custom classname on input + defaultValue: 'test', // default filtering value + comparator: Comparator.EQ, // default is Comparator.LIKE + style: { ... }, // your custom styles on input + delay: 1000 // how long will trigger filtering after user typing, default is 500 ms +}); + +// omit... +``` \ No newline at end of file diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js new file mode 100644 index 0000000..f6335a8 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/index.js @@ -0,0 +1,15 @@ +import TextFilter from './src/components/text'; +import wrapperFactory from './src/wrapper'; +import * as Comparison from './src/comparison'; + +export default (options = {}) => ({ + wrapperFactory, + options +}); + +export const Comparator = Comparison; + +export const textFilter = (props = {}) => ({ + Filter: TextFilter, + props +}); diff --git a/packages/react-bootstrap-table2-filter/package.json b/packages/react-bootstrap-table2-filter/package.json new file mode 100644 index 0000000..e9e95b1 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/package.json @@ -0,0 +1,44 @@ +{ + "name": "react-bootstrap-table2-filter", + "version": "0.0.3", + "description": "it's a column filter addon for react-bootstrap-table2", + "main": "./lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git" + }, + "license": "MIT", + "keywords": [ + "react", + "bootstrap", + "table", + "grid", + "react-bootstrap-table-addons", + "react-component" + ], + "files": [ + "lib/", + "dist/" + ], + "tags": [ + "react" + ], + "author": "AllenFang", + "contributors": [ + { + "name": "Allen Fang", + "email": "ayu780129@hotmail.com", + "url": "https://github.com/AllenFang" + }, + { + "name": "Chun-MingChen", + "email": "nick830314@gmail.com", + "url": "https://github.com/Chun-MingChen" + } + ], + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^16.0.0", + "react-dom": "^16.0.0" + } +} diff --git a/packages/react-bootstrap-table2-filter/src/comparison.js b/packages/react-bootstrap-table2-filter/src/comparison.js new file mode 100644 index 0000000..cc24214 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/comparison.js @@ -0,0 +1,2 @@ +export const LIKE = 'LIKE'; +export const EQ = '='; diff --git a/packages/react-bootstrap-table2-filter/src/components/text.js b/packages/react-bootstrap-table2-filter/src/components/text.js new file mode 100644 index 0000000..1c102c3 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/components/text.js @@ -0,0 +1,107 @@ +/* eslint react/require-default-props: 0 */ +/* eslint react/prop-types: 0 */ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import { PropTypes } from 'prop-types'; + +import { LIKE, EQ } from '../comparison'; +import { FILTER_TYPE, FILTER_DELAY } from '../const'; + +class TextFilter extends Component { + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.handleClick = this.handleClick.bind(this); + this.timeout = null; + this.state = { + value: props.defaultValue + }; + } + componentDidMount() { + const defaultValue = this.input.value; + if (defaultValue) { + this.props.onFilter(this.props.column, defaultValue, FILTER_TYPE.TEXT); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.defaultValue !== this.props.defaultValue) { + this.applyFilter(nextProps.defaultValue); + } + } + + componentWillUnmount() { + this.cleanTimer(); + } + + filter(e) { + e.stopPropagation(); + this.cleanTimer(); + const filterValue = e.target.value; + this.setState(() => ({ value: filterValue })); + this.timeout = setTimeout(() => { + this.props.onFilter(this.props.column, filterValue, FILTER_TYPE.TEXT); + }, this.props.delay); + } + + cleanTimer() { + if (this.timeout) { + clearTimeout(this.timeout); + } + } + + cleanFiltered() { + const value = this.props.defaultValue; + this.setState(() => ({ value })); + this.props.onFilter(this.props.column, value, FILTER_TYPE.TEXT); + } + + applyFilter(filterText) { + this.setState(() => ({ value: filterText })); + this.props.onFilter(this.props.column, filterText, FILTER_TYPE.TEXT); + } + + handleClick(e) { + e.stopPropagation(); + if (this.props.onClick) { + this.props.onClick(e); + } + } + + render() { + const { placeholder, column: { text }, style, className, onFilter, ...rest } = this.props; + // stopPropagation for onClick event is try to prevent sort was triggered. + return ( + this.input = n } + type="text" + className={ `filter text-filter form-control ${className}` } + style={ style } + onChange={ this.filter } + onClick={ this.handleClick } + placeholder={ placeholder || `Enter ${text}...` } + value={ this.state.value } + /> + ); + } +} + +TextFilter.propTypes = { + onFilter: PropTypes.func.isRequired, + column: PropTypes.object.isRequired, + comparator: PropTypes.oneOf([LIKE, EQ]), + defaultValue: PropTypes.string, + delay: PropTypes.number, + placeholder: PropTypes.string, + style: PropTypes.object, + className: PropTypes.string +}; + +TextFilter.defaultProps = { + delay: FILTER_DELAY, + defaultValue: '' +}; + + +export default TextFilter; diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js new file mode 100644 index 0000000..25faae0 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/const.js @@ -0,0 +1,5 @@ +export const FILTER_TYPE = { + TEXT: 'TEXT' +}; + +export const FILTER_DELAY = 500; diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js new file mode 100644 index 0000000..03914a8 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/filter.js @@ -0,0 +1,45 @@ +import { FILTER_TYPE } from './const'; +import { LIKE, EQ } from './comparison'; + +export const filterByText = _ => ( + data, + dataField, + { filterVal, comparator = LIKE }, + customFilterValue +) => + data.filter((row) => { + let cell = _.get(row, dataField); + if (customFilterValue) { + cell = customFilterValue(cell, row); + } + const cellStr = _.isDefined(cell) ? cell.toString() : ''; + if (comparator === EQ) { + return cellStr === filterVal; + } + return cellStr.indexOf(filterVal) > -1; + }); + +export const filterFactory = _ => (filterType) => { + let filterFn; + switch (filterType) { + case FILTER_TYPE.TEXT: + filterFn = filterByText(_); + break; + default: + filterFn = filterByText(_); + } + return filterFn; +}; + +export const filters = (store, columns, _) => (currFilters) => { + const factory = filterFactory(_); + let result = store.getAllData(); + let filterFn; + Object.keys(currFilters).forEach((dataField) => { + const filterObj = currFilters[dataField]; + filterFn = factory(filterObj.filterType); + const { filterValue } = columns.find(col => col.dataField === dataField); + result = filterFn(result, dataField, filterObj, filterValue); + }); + return result; +}; diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js new file mode 100644 index 0000000..7e197fa --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -0,0 +1,77 @@ +/* eslint no-param-reassign: 0 */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { filters } from './filter'; +import { LIKE } from './comparison'; + +export default (Base, { + _, + remoteResolver +}) => + class FilterWrapper extends remoteResolver(Component) { + static propTypes = { + store: PropTypes.object.isRequired, + columns: PropTypes.array.isRequired + } + + constructor(props) { + super(props); + this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false }; + this.onFilter = this.onFilter.bind(this); + } + + componentWillReceiveProps({ isDataChanged, store, columns }) { + // consider to use lodash.isEqual + const isRemoteFilter = this.isRemoteFiltering() || this.isRemotePagination(); + if (isRemoteFilter || + JSON.stringify(this.state.currFilters) !== JSON.stringify(store.filters)) { + // I think this condition only isRemoteFilter is enough + store.filteredData = store.getAllData(); + this.setState(() => ({ isDataChanged: true, currFilters: store.filters })); + } else if (isDataChanged) { + if (!isRemoteFilter && Object.keys(this.state.currFilters).length > 0) { + store.filteredData = filters(store, columns, _)(this.state.currFilters); + } + this.setState(() => ({ isDataChanged })); + } else { + this.setState(() => ({ isDataChanged: false })); + } + } + + onFilter(column, filterVal, filterType) { + const { store, columns } = this.props; + const currFilters = Object.assign({}, this.state.currFilters); + const { dataField, filter } = column; + + if (!_.isDefined(filterVal) || filterVal === '') { + delete currFilters[dataField]; + } else { + const { comparator = LIKE } = filter.props; + currFilters[dataField] = { filterVal, filterType, comparator }; + } + store.filters = currFilters; + + if (this.isRemoteFiltering() || this.isRemotePagination()) { + this.handleRemoteFilterChange(); + // when remote filtering is enable, dont set currFilters state + // in the componentWillReceiveProps, + // it's the key point that we can know the filter is changed + return; + } + + store.filteredData = filters(store, columns, _)(currFilters); + this.setState(() => ({ currFilters, isDataChanged: true })); + } + + render() { + return ( + + ); + } + }; diff --git a/packages/react-bootstrap-table2-filter/test/components/text.test.js b/packages/react-bootstrap-table2-filter/test/components/text.test.js new file mode 100644 index 0000000..4a9c282 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/test/components/text.test.js @@ -0,0 +1,190 @@ +import 'jsdom-global/register'; +import React from 'react'; +import sinon from 'sinon'; +import { mount } from 'enzyme'; +import TextFilter from '../../src/components/text'; +import { FILTER_TYPE } from '../../src/const'; + +jest.useFakeTimers(); +describe('Text Filter', () => { + let wrapper; + let instance; + const onFilter = sinon.stub(); + const column = { + dataField: 'price', + text: 'Price' + }; + + afterEach(() => { + onFilter.reset(); + }); + + describe('initialization', () => { + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should have correct state', () => { + expect(instance.state.value).toEqual(instance.props.defaultValue); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(wrapper.find('input[type="text"]')).toHaveLength(1); + expect(instance.input.getAttribute('placeholder')).toEqual(`Enter ${column.text}...`); + }); + }); + + describe('when defaultValue is defined', () => { + const defaultValue = '123'; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should have correct state', () => { + expect(instance.state.value).toEqual(defaultValue); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(instance.input.value).toEqual(defaultValue); + }); + + it('should calling onFilter on componentDidMount', () => { + expect(onFilter.calledOnce).toBeTruthy(); + expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.TEXT)).toBeTruthy(); + }); + }); + + describe('when placeholder is defined', () => { + const placeholder = 'test'; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(instance.input.getAttribute('placeholder')).toEqual(placeholder); + }); + }); + + describe('when style is defined', () => { + const style = { backgroundColor: 'red' }; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + }); + + it('should rendering component successfully', () => { + expect(wrapper).toHaveLength(1); + expect(wrapper.find('input').prop('style')).toEqual(style); + }); + }); + + describe('componentWillReceiveProps', () => { + const nextDefaultValue = 'tester'; + const nextProps = { + onFilter, + column, + defaultValue: nextDefaultValue + }; + + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting state correctly when props.defaultValue is changed', () => { + expect(instance.state.value).toEqual(nextDefaultValue); + }); + + it('should calling onFilter correctly when props.defaultValue is changed', () => { + expect(onFilter.calledOnce).toBeTruthy(); + expect(onFilter.calledWith(column, nextDefaultValue, FILTER_TYPE.TEXT)).toBeTruthy(); + }); + }); + + describe('cleanFiltered', () => { + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.cleanFiltered(); + }); + + it('should setting state correctly', () => { + expect(instance.state.value).toEqual(instance.props.defaultValue); + }); + + it('should calling onFilter correctly', () => { + expect(onFilter.calledOnce).toBeTruthy(); + expect(onFilter.calledWith( + column, instance.props.defaultValue, FILTER_TYPE.TEXT)).toBeTruthy(); + }); + }); + + describe('applyFilter', () => { + const filterText = 'test'; + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.applyFilter(filterText); + }); + + it('should setting state correctly', () => { + expect(instance.state.value).toEqual(filterText); + }); + + it('should calling onFilter correctly', () => { + expect(onFilter.calledOnce).toBeTruthy(); + expect(onFilter.calledWith(column, filterText, FILTER_TYPE.TEXT)).toBeTruthy(); + }); + }); + + describe('filter', () => { + const event = { stopPropagation: sinon.stub(), target: { value: 'tester' } }; + + beforeEach(() => { + wrapper = mount( + + ); + instance = wrapper.instance(); + instance.filter(event); + }); + + afterEach(() => { + setTimeout.mockClear(); + }); + + it('should calling e.stopPropagation', () => { + expect(event.stopPropagation.calledOnce).toBeTruthy(); + }); + + it('should setting state correctly', () => { + expect(instance.state.value).toEqual(event.target.value); + }); + + it('should calling setTimeout correctly', () => { + expect(setTimeout.mock.calls).toHaveLength(1); + expect(setTimeout.mock.calls[0]).toHaveLength(2); + expect(setTimeout.mock.calls[0][1]).toEqual(instance.props.delay); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-filter/test/filter.test.js b/packages/react-bootstrap-table2-filter/test/filter.test.js new file mode 100644 index 0000000..e834f17 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/test/filter.test.js @@ -0,0 +1,94 @@ +import sinon from 'sinon'; +import _ from 'react-bootstrap-table-next/src/utils'; +import Store from 'react-bootstrap-table-next/src/store'; + +import { filters } from '../src/filter'; +import { FILTER_TYPE } from '../src/const'; +import { LIKE, EQ } from '../src/comparison'; + +const data = []; +for (let i = 0; i < 20; i += 1) { + data.push({ + id: i, + name: `itme name ${i}`, + price: 200 + i + }); +} + +describe('filter', () => { + let store; + let filterFn; + let currFilters; + let columns; + + beforeEach(() => { + store = new Store('id'); + store.data = data; + currFilters = {}; + columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }, { + dataField: 'price', + text: 'Price' + }]; + }); + + describe('text filter', () => { + beforeEach(() => { + filterFn = filters(store, columns, _); + }); + + describe(`when default comparator is ${LIKE}`, () => { + it('should returning correct result', () => { + currFilters.name = { + filterVal: '3', + filterType: FILTER_TYPE.TEXT + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(2); + }); + }); + + describe(`when default comparator is ${EQ}`, () => { + it('should returning correct result', () => { + currFilters.name = { + filterVal: 'itme name 3', + filterType: FILTER_TYPE.TEXT, + comparator: EQ + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(1); + }); + }); + + describe('column.filterValue is defined', () => { + beforeEach(() => { + columns[1].filterValue = sinon.stub(); + filterFn = filters(store, columns, _); + }); + + it('should calling custom filterValue callback correctly', () => { + currFilters.name = { + filterVal: '3', + filterType: FILTER_TYPE.TEXT + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(columns[1].filterValue.callCount).toBe(data.length); + const calls = columns[1].filterValue.getCalls(); + calls.forEach((call, i) => { + expect(call.calledWith(data[i].name, data[i])).toBeTruthy(); + }); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js new file mode 100644 index 0000000..3d86285 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/test/wrapper.test.js @@ -0,0 +1,252 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import _ from 'react-bootstrap-table-next/src/utils'; +import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver'; +import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; +import Store from 'react-bootstrap-table-next/src/store'; +import filter, { textFilter } from '..'; +import wrapperFactory from '../src/wrapper'; +import { FILTER_TYPE } from '../src/const'; + +const data = []; +for (let i = 0; i < 20; i += 1) { + data.push({ + id: i, + name: `itme name ${i}`, + price: 200 + i + }); +} + +describe('Wrapper', () => { + let wrapper; + let instance; + const onTableChangeCB = sinon.stub(); + + afterEach(() => { + onTableChangeCB.reset(); + }); + + const createTableProps = (props) => { + const tableProps = { + keyField: 'id', + columns: [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name', + filter: textFilter() + }, { + dataField: 'price', + text: 'Price', + filter: textFilter() + }], + data, + filter: filter(), + _, + store: new Store('id'), + onTableChange: onTableChangeCB, + ...props + }; + tableProps.store.data = data; + return tableProps; + }; + + const FilterWrapper = wrapperFactory(BootstrapTable, { + _, + remoteResolver + }); + + const createFilterWrapper = (props, renderFragment = true) => { + wrapper = shallow(); + instance = wrapper.instance(); + if (renderFragment) { + const fragment = instance.render(); + wrapper = shallow(
{ fragment }
); + } + }; + + describe('default filter wrapper', () => { + const props = createTableProps(); + + beforeEach(() => { + createFilterWrapper(props); + }); + + it('should rendering correctly', () => { + expect(wrapper.length).toBe(1); + }); + + it('should initializing state correctly', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + expect(instance.state.currFilters).toEqual({}); + }); + + it('should rendering BootstraTable correctly', () => { + const table = wrapper.find(BootstrapTable); + expect(table.length).toBe(1); + expect(table.prop('onFilter')).toBeDefined(); + expect(table.prop('isDataChanged')).toEqual(instance.state.isDataChanged); + }); + }); + + describe('componentWillReceiveProps', () => { + let nextProps; + + describe('when props.store.filters is same as current state.currFilters', () => { + beforeEach(() => { + nextProps = createTableProps(); + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting isDataChanged as false (Temporary solution)', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + }); + }); + + describe('when props.isDataChanged is true', () => { + beforeEach(() => { + nextProps = createTableProps({ isDataChanged: true }); + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting isDataChanged as true', () => { + expect(instance.state.isDataChanged).toBeTruthy(); + }); + }); + + describe('when props.store.filters is different from current state.currFilters', () => { + const nextData = []; + + beforeEach(() => { + nextProps = createTableProps(); + nextProps.store.filters = { price: { filterVal: 20, filterType: FILTER_TYPE.TEXT } }; + nextProps.store.setAllData(nextData); + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting states correctly', () => { + expect(nextProps.store.filteredData).toEqual(nextData); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(instance.state.currFilters).toBe(nextProps.store.filters); + }); + }); + + describe('when remote filter is enabled', () => { + let props; + const nextData = []; + + beforeEach(() => { + props = createTableProps({ remote: { filter: true } }); + createFilterWrapper(props); + nextProps = createTableProps({ remote: { filter: true } }); + nextProps.store.setAllData(nextData); + instance.componentWillReceiveProps(nextProps); + }); + + it('should setting states correctly', () => { + expect(nextProps.store.filteredData).toEqual(nextData); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(instance.state.currFilters).toBe(nextProps.store.filters); + }); + }); + }); + + describe('onFilter', () => { + let props; + + beforeEach(() => { + props = createTableProps(); + createFilterWrapper(props); + }); + + describe('when filterVal is empty or undefined', () => { + const filterVals = ['', undefined]; + + it('should setting store object correctly', () => { + filterVals.forEach((filterVal) => { + instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); + expect(props.store.filtering).toBeFalsy(); + }); + }); + + it('should setting state correctly', () => { + filterVals.forEach((filterVal) => { + instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(0); + }); + }); + }); + + describe('when filterVal is existing', () => { + const filterVal = '3'; + + it('should setting store object correctly', () => { + instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); + expect(props.store.filters).toEqual(instance.state.currFilters); + }); + + it('should setting state correctly', () => { + instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(1); + }); + }); + + describe('when remote filter is enabled', () => { + const filterVal = '3'; + + beforeEach(() => { + props = createTableProps(); + props.remote = { filter: true }; + createFilterWrapper(props); + instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); + }); + + it('should not setting store object correctly', () => { + expect(props.store.filters).not.toEqual(instance.state.currFilters); + }); + + it('should not setting state', () => { + expect(instance.state.isDataChanged).toBeFalsy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(0); + }); + + it('should calling props.onRemoteFilterChange correctly', () => { + expect(onTableChangeCB.calledOnce).toBeTruthy(); + }); + }); + + describe('combination', () => { + it('should setting store object correctly', () => { + instance.onFilter(props.columns[1], '3', FILTER_TYPE.TEXT); + expect(props.store.filters).toEqual(instance.state.currFilters); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(1); + + instance.onFilter(props.columns[1], '2', FILTER_TYPE.TEXT); + expect(props.store.filters).toEqual(instance.state.currFilters); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(1); + + instance.onFilter(props.columns[2], '2', FILTER_TYPE.TEXT); + expect(props.store.filters).toEqual(instance.state.currFilters); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(2); + + instance.onFilter(props.columns[2], '', FILTER_TYPE.TEXT); + expect(props.store.filters).toEqual(instance.state.currFilters); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(1); + + instance.onFilter(props.columns[1], '', FILTER_TYPE.TEXT); + expect(props.store.filters).toEqual(instance.state.currFilters); + expect(instance.state.isDataChanged).toBeTruthy(); + expect(Object.keys(instance.state.currFilters)).toHaveLength(0); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-overlay/README.md b/packages/react-bootstrap-table2-overlay/README.md new file mode 100644 index 0000000..b76619d --- /dev/null +++ b/packages/react-bootstrap-table2-overlay/README.md @@ -0,0 +1,14 @@ +# react-bootstrap-table2-overlay +In `react-bootstrap-table2`, you will be easier to custom the loading or lverlay on table no matter if remote enabled or not. In the following, we have two way to do it: + +----- + +## Empty Table +[**`noDataIndication`**](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html#nodataindication-function) is a simple case you can take it, if current data size is empty, `react-bootstrap-table2` will call the `noDataIndication` prop and get the result to display on the table. + +[**Here**](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=EmptyTableOverlay) is a quick exmaple for `noDataIndication`. + +## Loading Table +In the most of case for remote mode, you need the loading animation to tell the user the table is loading or doing some action in the background. Hence, you can lervarge [**`overlay`**](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html#overlay-function) prop. + +[**Here**](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=EmptyTableOverlay) is also a example for `overlay` \ No newline at end of file diff --git a/packages/react-bootstrap-table2-overlay/index.js b/packages/react-bootstrap-table2-overlay/index.js new file mode 100644 index 0000000..a5db872 --- /dev/null +++ b/packages/react-bootstrap-table2-overlay/index.js @@ -0,0 +1,29 @@ +/* eslint no-return-assign: 0 */ +import React from 'react'; +import LoadingOverlay from 'react-loading-overlay'; + +export default options => (element, loading) => + class TableLoadingOverlayWrapper extends React.Component { + componentDidMount() { + if (loading) { + const { wrapper } = this.overlay; + const masker = wrapper.firstChild; + const headerDOM = wrapper.parentElement.querySelector('thead'); + const bodyDOM = wrapper.parentElement.querySelector('tbody'); + masker.style.marginTop = window.getComputedStyle(headerDOM).height; + masker.style.height = window.getComputedStyle(bodyDOM).height; + } + } + + render() { + return ( + this.overlay = n } + { ...options } + active={ loading } + > + { element } + + ); + } + }; diff --git a/packages/react-bootstrap-table2-overlay/package.json b/packages/react-bootstrap-table2-overlay/package.json new file mode 100644 index 0000000..340ca9f --- /dev/null +++ b/packages/react-bootstrap-table2-overlay/package.json @@ -0,0 +1,47 @@ +{ + "name": "react-bootstrap-table2-overlay", + "version": "0.0.3", + "description": "it's a loading overlay addons for react-bootstrap-table2", + "main": "./lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git" + }, + "license": "MIT", + "keywords": [ + "react", + "bootstrap", + "table", + "grid", + "react-bootstrap-table-addons", + "react-component" + ], + "files": [ + "lib/", + "dist/" + ], + "tags": [ + "react" + ], + "author": "AllenFang", + "contributors": [ + { + "name": "Allen Fang", + "email": "ayu780129@hotmail.com", + "url": "https://github.com/AllenFang" + }, + { + "name": "Chun-MingChen", + "email": "nick830314@gmail.com", + "url": "https://github.com/Chun-MingChen" + } + ], + "dependencies": { + "react-loading-overlay": "0.2.8" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^16.0.0", + "react-dom": "^16.0.0" + } +} diff --git a/packages/react-bootstrap-table2-overlay/test/index.test.js b/packages/react-bootstrap-table2-overlay/test/index.test.js new file mode 100644 index 0000000..1292475 --- /dev/null +++ b/packages/react-bootstrap-table2-overlay/test/index.test.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { shallow, render } from 'enzyme'; +import LoadingOverlay from 'react-loading-overlay'; + +import overlayFactory from '..'; + +describe('overlayFactory', () => { + let wrapper; + // let instance; + + const createTable = () => ( +
+ + + + + + + + { [1, 2].map(row => ( + + ))} + +
column1column2
{ row }test
+ ); + + describe('when loading is false', () => { + beforeEach(() => { + const tableElm = createTable(); + const Overlay = overlayFactory()(tableElm, false); + wrapper = shallow(); + }); + + it('should rendering Overlay component correctly', () => { + const overlay = wrapper.find(LoadingOverlay); + expect(wrapper.length).toBe(1); + expect(overlay.length).toBe(1); + expect(overlay.prop('active')).toBeFalsy(); + }); + }); + + describe('when loading is true', () => { + beforeEach(() => { + const tableElm = createTable(); + const Overlay = overlayFactory()(tableElm, true); + wrapper = render(); + }); + + it('should rendering Overlay component correctly', () => { + const overlay = wrapper.find(LoadingOverlay); + expect(wrapper.length).toBe(1); + expect(overlay.length).toBe(0); + }); + }); + + describe('when options is given', () => { + const options = { + spinner: true, + background: 'red' + }; + beforeEach(() => { + const tableElm = createTable(); + const Overlay = overlayFactory(options)(tableElm, false); + wrapper = shallow(); + }); + + it('should rendering Overlay component with options correctly', () => { + const overlay = wrapper.find(LoadingOverlay); + expect(wrapper.length).toBe(1); + expect(overlay.length).toBe(1); + expect(overlay.prop('active')).toBeFalsy(); + Object.keys(options).forEach((key) => { + expect(overlay.prop(key)).toEqual(options[key]); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-overlay/yarn.lock b/packages/react-bootstrap-table2-overlay/yarn.lock new file mode 100644 index 0000000..f40152b --- /dev/null +++ b/packages/react-bootstrap-table2-overlay/yarn.lock @@ -0,0 +1,200 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +base64-js@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + +buffer@^5.0.3: + version "5.0.8" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +chain-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + +css-to-react-native@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b" + dependencies: + css-color-keywords "^1.0.0" + fbjs "^0.8.5" + postcss-value-parser "^3.3.0" + +dom-helpers@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +hoist-non-react-statics@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" + +iconv-lite@~0.4.13: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +ieee754@^1.1.4: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + +is-function@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" + +is-plain-object@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +js-tokens@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +loose-envify@^1.0.0, loose-envify@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6: + version "15.6.0" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +react-loading-overlay@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/react-loading-overlay/-/react-loading-overlay-0.2.8.tgz#c1c5531c9cfa4be6caca6b9aa0c1eb19e22b03fe" + dependencies: + prop-types "^15.5.10" + react-transition-group "^1.2.1" + styled-components "^2.1.2" + +react-transition-group@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6" + dependencies: + chain-function "^1.0.0" + dom-helpers "^3.2.0" + loose-envify "^1.3.1" + prop-types "^15.5.6" + warning "^3.0.0" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +styled-components@^2.1.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.2.4.tgz#dd87fd3dafd359e7a0d570aec1bd07d691c0b5a2" + dependencies: + buffer "^5.0.3" + css-to-react-native "^2.0.3" + fbjs "^0.8.9" + hoist-non-react-statics "^1.2.0" + is-function "^1.0.1" + is-plain-object "^2.0.1" + prop-types "^15.5.4" + stylis "^3.4.0" + supports-color "^3.2.3" + +stylis@^3.4.0: + version "3.4.5" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.5.tgz#d7b9595fc18e7b9c8775eca8270a9a1d3e59806e" + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +ua-parser-js@^0.7.9: + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" + +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + dependencies: + loose-envify "^1.0.0" + +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/packages/react-bootstrap-table2-paginator/README.md b/packages/react-bootstrap-table2-paginator/README.md new file mode 100644 index 0000000..9350be4 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/README.md @@ -0,0 +1,40 @@ +# react-bootstrap-table2-pagination + +`react-bootstrap-table2` separate the pagination code base to [`react-bootstrap-table2-pagination`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-paginator), so there's a little bit different when you use pagination. In the following, we are going to show you how to enable and configure the a pagination table + +**[Live Demo For Pagination](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Pagination)** + +**[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html)** + +----- + +## Install + +```sh +$ npm install react-bootstrap-table2-pagination --save +``` + +## Add CSS + +```js +// es5 +require('react-bootstrap-table2-paginator/dist/react-bootstrap-table2-paginator.min.css'); + +// es6 +import 'react-bootstrap-table2-paginator/dist/react-bootstrap-table2-paginator.min.css'; +``` + +## How + +Let's enable a pagination on your table: + +```js +import paginationFactory from 'react-bootstrap-table2-paginator'; +// omit... + + +``` + +## Customization + +See [pagination props](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html) \ No newline at end of file diff --git a/packages/react-bootstrap-table2-paginator/index.js b/packages/react-bootstrap-table2-paginator/index.js new file mode 100644 index 0000000..edd0067 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/index.js @@ -0,0 +1,6 @@ +import wrapperFactory from './src/wrapper'; + +export default (options = {}) => ({ + wrapperFactory, + options +}); diff --git a/packages/react-bootstrap-table2-paginator/package.json b/packages/react-bootstrap-table2-paginator/package.json new file mode 100644 index 0000000..123c344 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/package.json @@ -0,0 +1,44 @@ +{ + "name": "react-bootstrap-table2-paginator", + "version": "0.0.3", + "description": "it's the pagination addon for react-bootstrap-table2", + "main": "./lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git" + }, + "license": "MIT", + "keywords": [ + "react", + "bootstrap", + "table", + "grid", + "react-bootstrap-table-addons", + "react-component" + ], + "files": [ + "lib/", + "dist/" + ], + "tags": [ + "react" + ], + "author": "AllenFang", + "contributors": [ + { + "name": "Allen Fang", + "email": "ayu780129@hotmail.com", + "url": "https://github.com/AllenFang" + }, + { + "name": "Chun-MingChen", + "email": "nick830314@gmail.com", + "url": "https://github.com/Chun-MingChen" + } + ], + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^16.0.0", + "react-dom": "^16.0.0" + } +} diff --git a/packages/react-bootstrap-table2-paginator/src/const.js b/packages/react-bootstrap-table2-paginator/src/const.js new file mode 100644 index 0000000..eaa1bac --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/const.js @@ -0,0 +1,17 @@ +export default { + PAGINATION_SIZE: 5, + PAGE_START_INDEX: 1, + With_FIRST_AND_LAST: true, + SHOW_ALL_PAGE_BTNS: false, + FIRST_PAGE_TEXT: '<<', + PRE_PAGE_TEXT: '<', + NEXT_PAGE_TEXT: '>', + LAST_PAGE_TEXT: '>>', + NEXT_PAGE_TITLE: 'next page', + LAST_PAGE_TITLE: 'last page', + PRE_PAGE_TITLE: 'previous page', + FIRST_PAGE_TITLE: 'first page', + SIZE_PER_PAGE_LIST: [10, 25, 30, 50], + HIDE_SIZE_PER_PAGE: false, + HIDE_PAGE_LIST_ONLY_ONE_PAGE: false +}; diff --git a/packages/react-bootstrap-table2-paginator/src/page-button.js b/packages/react-bootstrap-table2-paginator/src/page-button.js new file mode 100644 index 0000000..272fa19 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/page-button.js @@ -0,0 +1,47 @@ +/* eslint react/require-default-props: 0 */ +/* eslint jsx-a11y/href-no-hash: 0 */ +import cs from 'classnames'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +class PageButton extends Component { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + + handleClick(e) { + e.preventDefault(); + this.props.onPageChange(this.props.page); + } + + render() { + const { + page, + title, + active, + disabled + } = this.props; + const classes = cs({ + active, + disabled, + 'page-item': true + }); + + return ( +
  • + { page } +
  • + ); + } +} + +PageButton.propTypes = { + onPageChange: PropTypes.func.isRequired, + page: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + active: PropTypes.bool.isRequired, + disabled: PropTypes.bool.isRequired, + title: PropTypes.string +}; + +export default PageButton; diff --git a/packages/react-bootstrap-table2-paginator/src/page-resolver.js b/packages/react-bootstrap-table2-paginator/src/page-resolver.js new file mode 100644 index 0000000..931595f --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/page-resolver.js @@ -0,0 +1,133 @@ +/* eslint no-mixed-operators: 0 */ +export default ExtendBase => + class PageResolver extends ExtendBase { + backToPrevPage() { + const { currPage, pageStartIndex } = this.props; + return (currPage - 1) < pageStartIndex ? pageStartIndex : currPage - 1; + } + + goToNextPage() { + const { currPage } = this.props; + const { lastPage } = this.state; + return (currPage + 1) > lastPage ? lastPage : currPage + 1; + } + + initialState() { + const totalPages = this.calculateTotalPage(); + const lastPage = this.calculateLastPage(totalPages); + return { totalPages, lastPage, dropdownOpen: false }; + } + + calculateTotalPage(sizePerPage = this.props.currSizePerPage, dataSize = this.props.dataSize) { + return Math.ceil(dataSize / sizePerPage); + } + + calculateLastPage(totalPages) { + const { pageStartIndex } = this.props; + return pageStartIndex + totalPages - 1; + } + + calculatePages( + totalPages = this.state.totalPages, + lastPage = this.state.lastPage) { + const { + currPage, + paginationSize, + pageStartIndex, + withFirstAndLast, + firstPageText, + prePageText, + nextPageText, + lastPageText, + alwaysShowAllBtns + } = this.props; + + let pages; + let endPage = totalPages; + if (endPage <= 0) return []; + + let startPage = Math.max(currPage - Math.floor(paginationSize / 2), pageStartIndex); + endPage = startPage + paginationSize - 1; + + if (endPage > lastPage) { + endPage = lastPage; + startPage = endPage - paginationSize + 1; + } + + if (startPage !== pageStartIndex && totalPages > paginationSize && withFirstAndLast) { + pages = [firstPageText, prePageText]; + } else if (totalPages > 1 || alwaysShowAllBtns) { + pages = [prePageText]; + } else { + pages = []; + } + + for (let i = startPage; i <= endPage; i += 1) { + if (i >= pageStartIndex) pages.push(i); + } + + if (endPage <= lastPage && pages.length > 1) { + pages.push(nextPageText); + } + if (endPage !== lastPage && withFirstAndLast) { + pages.push(lastPageText); + } + return pages; + } + + calculatePageStatus(pages = [], lastPage = this.state.lastPage) { + const { + currPage, + pageStartIndex, + firstPageText, + prePageText, + nextPageText, + lastPageText, + alwaysShowAllBtns + } = this.props; + const isStart = page => + (currPage === pageStartIndex && (page === firstPageText || page === prePageText)); + const isEnd = page => + (currPage === lastPage && (page === nextPageText || page === lastPageText)); + + return pages + .filter((page) => { + if (alwaysShowAllBtns) { + return true; + } + return !(isStart(page) || isEnd(page)); + }) + .map((page) => { + let title; + const active = page === currPage; + const disabled = (isStart(page) || isEnd(page)); + + if (page === nextPageText) { + title = this.props.nextPageTitle; + } else if (page === prePageText) { + title = this.props.prePageTitle; + } else if (page === firstPageText) { + title = this.props.firstPageTitle; + } else if (page === lastPageText) { + title = this.props.lastPageTitle; + } else { + title = `${page}`; + } + + return { page, active, disabled, title }; + }); + } + + calculateSizePerPageStatus() { + const { sizePerPageList } = this.props; + return sizePerPageList.map((_sizePerPage) => { + const pageText = _sizePerPage.text || _sizePerPage; + const pageNumber = _sizePerPage.value || _sizePerPage; + return { + text: `${pageText}`, + page: pageNumber + }; + }); + } + }; + diff --git a/packages/react-bootstrap-table2-paginator/src/page.js b/packages/react-bootstrap-table2-paginator/src/page.js new file mode 100644 index 0000000..aa3085a --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/page.js @@ -0,0 +1,17 @@ +export const getByCurrPage = (store, pageStartIndex) => { + const dataSize = store.data.length; + if (!dataSize) return []; + const getNormalizedPage = () => { + const offset = Math.abs(1 - pageStartIndex); + return store.page + offset; + }; + const end = (getNormalizedPage() * store.sizePerPage) - 1; + const start = end - (store.sizePerPage - 1); + + const result = []; + for (let i = start; i <= end; i += 1) { + result.push(store.data[i]); + if (i + 1 === dataSize) break; + } + return result; +}; diff --git a/packages/react-bootstrap-table2-paginator/src/pagination-list.js b/packages/react-bootstrap-table2-paginator/src/pagination-list.js new file mode 100644 index 0000000..805af53 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/pagination-list.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import PageButton from './page-button'; + +const PaginatonList = props => ( +
      + { + props.pages.map(pageProps => ( + + )) + } +
    +); + +PaginatonList.propTypes = { + pages: PropTypes.arrayOf(PropTypes.shape({ + page: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + active: PropTypes.bool, + disable: PropTypes.bool, + title: PropTypes.string + })).isRequired, + onPageChange: PropTypes.func.isRequired +}; + +export default PaginatonList; diff --git a/packages/react-bootstrap-table2-paginator/src/pagination.js b/packages/react-bootstrap-table2-paginator/src/pagination.js new file mode 100644 index 0000000..d103e4b --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/pagination.js @@ -0,0 +1,170 @@ +/* eslint react/require-default-props: 0 */ +/* eslint arrow-body-style: 0 */ +import cs from 'classnames'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import pageResolver from './page-resolver'; +import SizePerPageDropDown from './size-per-page-dropdown'; +import PaginationList from './pagination-list'; +import Const from './const'; + +class Pagination extends pageResolver(Component) { + constructor(props) { + super(props); + this.closeDropDown = this.closeDropDown.bind(this); + this.toggleDropDown = this.toggleDropDown.bind(this); + this.handleChangePage = this.handleChangePage.bind(this); + this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this); + this.state = this.initialState(); + } + + componentWillReceiveProps(nextProps) { + const { dataSize, currSizePerPage } = nextProps; + if (currSizePerPage !== this.props.currSizePerPage || dataSize !== this.props.dataSize) { + const totalPages = this.calculateTotalPage(currSizePerPage, dataSize); + const lastPage = this.calculateLastPage(totalPages); + this.setState({ totalPages, lastPage }); + } + } + + toggleDropDown() { + const dropdownOpen = !this.state.dropdownOpen; + this.setState(() => { + return { dropdownOpen }; + }); + } + + closeDropDown() { + this.setState(() => { + return { dropdownOpen: false }; + }); + } + + handleChangeSizePerPage(sizePerPage) { + const { currSizePerPage, onSizePerPageChange } = this.props; + const selectedSize = typeof sizePerPage === 'string' ? parseInt(sizePerPage, 10) : sizePerPage; + let { currPage } = this.props; + if (selectedSize !== currSizePerPage) { + const newTotalPages = this.calculateTotalPage(selectedSize); + const newLastPage = this.calculateLastPage(newTotalPages); + if (currPage > newLastPage) currPage = newLastPage; + onSizePerPageChange(selectedSize, currPage); + } + this.closeDropDown(); + } + + handleChangePage(newPage) { + let page; + const { + currPage, + pageStartIndex, + prePageText, + nextPageText, + lastPageText, + firstPageText, + onPageChange + // keepSizePerPageState + } = this.props; + const { lastPage } = this.state; + + if (newPage === prePageText) { + page = this.backToPrevPage(); + } else if (newPage === nextPageText) { + page = (currPage + 1) > lastPage ? lastPage : currPage + 1; + } else if (newPage === lastPageText) { + page = lastPage; + } else if (newPage === firstPageText) { + page = pageStartIndex; + } else { + page = parseInt(newPage, 10); + } + + // if (keepSizePerPageState) { this.closeDropDown(); } + + if (page !== currPage) { + onPageChange(page); + } + } + + render() { + const { totalPages, lastPage, dropdownOpen: open } = this.state; + const { + sizePerPageList, + currSizePerPage, + hideSizePerPage, + hidePageListOnlyOnePage + } = this.props; + const pages = this.calculatePageStatus(this.calculatePages(totalPages), lastPage); + + const pageListClass = cs( + 'react-bootstrap-table-pagination-list', + 'col-md-6 col-xs-6 col-sm-6 col-lg-6', { + 'react-bootstrap-table-pagination-list-hidden': (hidePageListOnlyOnePage && totalPages === 1) + }); + return ( +
    +
    + { + sizePerPageList.length > 1 && !hideSizePerPage ? + ( + + ) : null + } +
    +
    + +
    +
    + ); + } +} + +Pagination.propTypes = { + dataSize: PropTypes.number.isRequired, + sizePerPageList: PropTypes.array.isRequired, + currPage: PropTypes.number.isRequired, + currSizePerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, + onSizePerPageChange: PropTypes.func.isRequired, + pageStartIndex: PropTypes.number, + paginationSize: PropTypes.number, + firstPageText: PropTypes.string, + prePageText: PropTypes.string, + nextPageText: PropTypes.string, + lastPageText: PropTypes.string, + nextPageTitle: PropTypes.string, + prePageTitle: PropTypes.string, + firstPageTitle: PropTypes.string, + lastPageTitle: PropTypes.string, + withFirstAndLast: PropTypes.bool, + alwaysShowAllBtns: PropTypes.bool, + hideSizePerPage: PropTypes.bool, + hidePageListOnlyOnePage: PropTypes.bool +}; + +Pagination.defaultProps = { + pageStartIndex: Const.PAGE_START_INDEX, + paginationSize: Const.PAGINATION_SIZE, + withFirstAndLast: Const.With_FIRST_AND_LAST, + alwaysShowAllBtns: Const.SHOW_ALL_PAGE_BTNS, + firstPageText: Const.FIRST_PAGE_TEXT, + prePageText: Const.PRE_PAGE_TEXT, + nextPageText: Const.NEXT_PAGE_TEXT, + lastPageText: Const.LAST_PAGE_TEXT, + sizePerPageList: Const.SIZE_PER_PAGE_LIST, + nextPageTitle: Const.NEXT_PAGE_TITLE, + prePageTitle: Const.PRE_PAGE_TITLE, + firstPageTitle: Const.FIRST_PAGE_TITLE, + lastPageTitle: Const.LAST_PAGE_TITLE, + hideSizePerPage: Const.HIDE_SIZE_PER_PAGE, + hidePageListOnlyOnePage: Const.HIDE_PAGE_LIST_ONLY_ONE_PAGE +}; + +export default Pagination; diff --git a/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js new file mode 100644 index 0000000..5ae4298 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js @@ -0,0 +1,85 @@ +import React from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; +import SizePerPageOption from './size-per-page-option'; + +const sizePerPageDefaultClass = 'react-bs-table-sizePerPage-dropdown'; + +const SizePerPageDropDown = (props) => { + const { + open, + hidden, + onClick, + onBlur, + options, + className, + variation, + btnContextual, + currSizePerPage, + onSizePerPageChange + } = props; + + const dropDownStyle = { visibility: hidden ? 'hidden' : 'visible' }; + const dropdownClasses = cs( + open ? 'open show' : '', + sizePerPageDefaultClass, + variation, + className, + ); + + return ( + + +
      + { + options.map(option => ( + + )) + } +
    +
    + ); +}; + +SizePerPageDropDown.propTypes = { + currSizePerPage: PropTypes.string.isRequired, + options: PropTypes.array.isRequired, + onClick: PropTypes.func.isRequired, + onBlur: PropTypes.func.isRequired, + onSizePerPageChange: PropTypes.func.isRequired, + open: PropTypes.bool, + hidden: PropTypes.bool, + btnContextual: PropTypes.string, + variation: PropTypes.oneOf(['dropdown', 'dropup']), + className: PropTypes.string +}; +SizePerPageDropDown.defaultProps = { + open: false, + hidden: false, + btnContextual: 'btn-default btn-secondary', + variation: 'dropdown', + className: '' +}; + + +export default SizePerPageDropDown; diff --git a/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js new file mode 100644 index 0000000..eba6ca6 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js @@ -0,0 +1,32 @@ +/* eslint jsx-a11y/href-no-hash: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const SizePerPageOption = ({ + text, + page, + onSizePerPageChange +}) => ( +
  • + { + e.preventDefault(); + onSizePerPageChange(page); + } } + > + { text } + +
  • +); + +SizePerPageOption.propTypes = { + text: PropTypes.string.isRequired, + page: PropTypes.number.isRequired, + onSizePerPageChange: PropTypes.func.isRequired +}; + +export default SizePerPageOption; diff --git a/packages/react-bootstrap-table2-paginator/src/wrapper.js b/packages/react-bootstrap-table2-paginator/src/wrapper.js new file mode 100644 index 0000000..5393a79 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/wrapper.js @@ -0,0 +1,159 @@ +/* eslint react/prop-types: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import Const from './const'; +import Pagination from './pagination'; +import { getByCurrPage } from './page'; + +export default (Base, { + remoteResolver +}) => + class PaginationWrapper extends remoteResolver(Component) { + static propTypes = { + store: PropTypes.object.isRequired + } + + constructor(props) { + super(props); + this.handleChangePage = this.handleChangePage.bind(this); + this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this); + + let currPage; + let currSizePerPage; + const { options } = props.pagination; + const sizePerPageList = options.sizePerPageList || Const.SIZE_PER_PAGE_LIST; + + // initialize current page + if (typeof options.page !== 'undefined') { + currPage = options.page; + } else if (typeof options.pageStartIndex !== 'undefined') { + currPage = options.pageStartIndex; + } else { + currPage = Const.PAGE_START_INDEX; + } + + // initialize current sizePerPage + if (typeof options.sizePerPage !== 'undefined') { + currSizePerPage = options.sizePerPage; + } else if (typeof sizePerPageList[0] === 'object') { + currSizePerPage = sizePerPageList[0].value; + } else { + currSizePerPage = sizePerPageList[0]; + } + + this.state = { currPage, currSizePerPage }; + this.saveToStore(currPage, currSizePerPage); + } + + componentWillReceiveProps(nextProps) { + let needNewState = false; + let { currPage, currSizePerPage } = this.state; + const { page, sizePerPage, pageStartIndex, onPageChange } = nextProps.pagination.options; + + if (typeof page !== 'undefined' && currPage !== page) { // user defined page + currPage = page; + needNewState = true; + } else if (nextProps.isDataChanged) { // user didn't defined page but data change + currPage = typeof pageStartIndex !== 'undefined' ? pageStartIndex : Const.PAGE_START_INDEX; + needNewState = true; + } + + if (typeof sizePerPage !== 'undefined') { + currSizePerPage = sizePerPage; + needNewState = true; + } + + this.saveToStore(currPage, currSizePerPage); + + if (needNewState) { + if (onPageChange) { + onPageChange(currPage, currSizePerPage); + } + this.setState(() => ({ currPage, currSizePerPage })); + } + } + + saveToStore(page, sizePerPage) { + this.props.store.page = page; + this.props.store.sizePerPage = sizePerPage; + } + + handleChangePage(currPage) { + const { currSizePerPage } = this.state; + const { pagination: { options } } = this.props; + this.saveToStore(currPage, currSizePerPage); + + if (options.onPageChange) { + options.onPageChange(currPage, currSizePerPage); + } + if (this.isRemotePagination()) { + this.handleRemotePageChange(); + return; + } + this.setState(() => ({ currPage })); + } + + handleChangeSizePerPage(currSizePerPage, currPage) { + const { pagination: { options } } = this.props; + this.saveToStore(currPage, currSizePerPage); + + if (options.onSizePerPageChange) { + options.onSizePerPageChange(currSizePerPage, currPage); + } + if (this.isRemotePagination()) { + this.handleRemotePageChange(); + return; + } + this.setState(() => ({ + currPage, + currSizePerPage + })); + } + + render() { + const { pagination: { options }, store } = this.props; + const { currPage, currSizePerPage } = this.state; + const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ? + Const.With_FIRST_AND_LAST : options.withFirstAndLast; + const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ? + Const.SHOW_ALL_PAGE_BTNS : options.alwaysShowAllBtns; + const hideSizePerPage = typeof options.hideSizePerPage === 'undefined' ? + Const.HIDE_SIZE_PER_PAGE : options.hideSizePerPage; + const hidePageListOnlyOnePage = typeof options.hidePageListOnlyOnePage === 'undefined' ? + Const.HIDE_PAGE_LIST_ONLY_ONE_PAGE : options.hidePageListOnlyOnePage; + const pageStartIndex = typeof options.pageStartIndex === 'undefined' ? + Const.PAGE_START_INDEX : options.pageStartIndex; + + const data = this.isRemotePagination() ? + this.props.data : + getByCurrPage(store, pageStartIndex); + + return [ + , + + ]; + } + }; diff --git a/packages/react-bootstrap-table2-paginator/style/react-bootstrap-table2-paginator.scss b/packages/react-bootstrap-table2-paginator/style/react-bootstrap-table2-paginator.scss new file mode 100644 index 0000000..a99007e --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/style/react-bootstrap-table2-paginator.scss @@ -0,0 +1,8 @@ +.react-bootstrap-table-page-btns-ul { + float: right; + margin-top: 0px; +} + +.react-bootstrap-table-pagination-list-hidden { + display: none; +} diff --git a/packages/react-bootstrap-table2-paginator/test/page-button.test.js b/packages/react-bootstrap-table2-paginator/test/page-button.test.js new file mode 100644 index 0000000..4e3d800 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/page-button.test.js @@ -0,0 +1,117 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import PageButton from '../src/page-button'; + +describe('PageButton', () => { + let wrapper; + const onPageChangeCallback = sinon.stub(); + const props = { + onPageChange: onPageChangeCallback, + page: 2 + }; + + describe('default PageButton', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should rendering PageButton correctly', () => { + expect(wrapper.find('a.page-link').length).toBe(1); + expect(wrapper.text()).toEqual(`${props.page}`); + }); + + describe('when clicking', () => { + let preventDefault; + beforeEach(() => { + preventDefault = sinon.stub(); + wrapper.find('a.page-link').simulate('click', { preventDefault }); + }); + + afterEach(() => { + onPageChangeCallback.reset(); + }); + + it('should calling e.preventDefault', () => { + expect(preventDefault.calledOnce).toBeTruthy(); + }); + + it('should calling onPageChange prop', () => { + expect(onPageChangeCallback.calledOnce).toBeTruthy(); + }); + + it('should calling onPageChange prop with correct argument', () => { + expect(onPageChangeCallback.calledWith(props.page)).toBeTruthy(); + }); + }); + }); + + describe('when active prop is true', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('active')).toBeTruthy(); + }); + }); + + describe('when active prop is false', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('active')).toBeFalsy(); + }); + }); + + describe('when disabled prop is true', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('disabled')).toBeTruthy(); + }); + }); + + describe('when disabled prop is false', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('disabled')).toBeFalsy(); + }); + }); + + describe('when title prop is defined', () => { + const title = 'aTitle'; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.prop('title')).toEqual(title); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js b/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js new file mode 100644 index 0000000..cb5d5cf --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js @@ -0,0 +1,416 @@ +import React, { Component } from 'react'; +import { shallow } from 'enzyme'; + +import pageResolver from '../src/page-resolver'; + +const extendTo = Base => + class MockComponent extends Base { + constructor(props) { + super(props); + this.state = this.initialState(); + } + render() { return null; } + }; + +describe('PageResolver', () => { + const ExtendBase = pageResolver(Component); + const MockComponent = extendTo(ExtendBase); + + const createMockProps = () => ({ + dataSize: 100, + sizePerPageList: [10, 20, 30, 50], + currPage: 1, + currSizePerPage: 10, + pageStartIndex: 1, + paginationSize: 5, + withFirstAndLast: true, + firstPageText: '<<', + prePageText: '<', + nextPageText: '>', + lastPageText: '>>', + alwaysShowAllBtns: false + }); + + let wrapper; + + describe('initialize', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, createMockProps(), null); + wrapper = shallow(mockElement); + }); + + it('should creating initial state correctly', () => { + const instance = wrapper.instance(); + expect(instance.state.totalPages).toBeDefined(); + expect(instance.state.totalPages).toEqual(instance.calculateTotalPage()); + expect(instance.state.lastPage).toBeDefined(); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(instance.state.totalPages)); + expect(instance.state.dropdownOpen).toBeDefined(); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + }); + + describe('backToPrevPage', () => { + const props = createMockProps(); + + describe('when props.currPage is not hitting props.pageStartIndex', () => { + beforeEach(() => { + props.currPage = 2; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting previous page correctly', () => { + const instance = wrapper.instance(); + expect(instance.backToPrevPage()).toEqual(props.currPage - 1); + }); + }); + + describe('when props.currPage is hitting props.pageStartIndex', () => { + beforeEach(() => { + props.currPage = props.pageStartIndex; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should always getting page which must eq props.pageStartIndex', () => { + const instance = wrapper.instance(); + expect(instance.backToPrevPage()).toEqual(props.pageStartIndex); + }); + }); + }); + + describe('goToNextPage', () => { + const props = createMockProps(); + + describe('when props.currPage is not hitting state.lastPage', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting previous page correctly', () => { + const instance = wrapper.instance(); + expect(instance.goToNextPage()).toEqual(props.currPage + 1); + }); + }); + + describe('when props.currPage is hitting state.lastpage', () => { + beforeEach(() => { + props.currPage = 10; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should always getting page which must eq props.pageStartIndex', () => { + const instance = wrapper.instance(); + expect(instance.goToNextPage()).toEqual(instance.state.lastPage); + }); + }); + }); + + describe('calculateTotalPage', () => { + const props = createMockProps(); + + describe('when missing sizePerPage argument', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting total pages correctly by default props.currSizePerPage', () => { + const instance = wrapper.instance(); + expect(instance.calculateTotalPage()).toEqual(10); + }); + }); + + describe('when sizePerPage argument given', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting total pages correctly by sizePerPage argument', () => { + const instance = wrapper.instance(); + expect(instance.calculateTotalPage(25)).toEqual(4); + }); + }); + }); + + describe('calculateLastPage', () => { + beforeEach(() => { + const props = createMockProps(); + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting last page correctly', () => { + const instance = wrapper.instance(); + expect(instance.calculateLastPage(instance.state.totalPages)).toEqual(10); + }); + }); + + describe('calculatePages', () => { + describe('calculate by state.totalPages and state.lastPage', () => { + const props = createMockProps(); + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting pages list correctly', () => { + const instance = wrapper.instance(); + expect(instance.calculatePages()).toEqual( + [props.prePageText, 1, 2, 3, 4, 5, props.nextPageText, props.lastPageText]); + + expect(instance.calculatePages(4, 4)).toEqual( + [props.prePageText, 1, 2, 3, 4, props.nextPageText]); + }); + }); + + describe('calculate by props.currPage', () => { + const props = createMockProps(); + const { firstPageText, prePageText, nextPageText, lastPageText } = props; + + it('should getting pages list correctly', () => { + const currPages = Array.from(Array(10).keys()); + currPages.forEach((currPage) => { + props.currPage = currPage + 1; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + + if (props.currPage < 4) { + expect(pageList).toEqual( + [prePageText, 1, 2, 3, 4, 5, nextPageText, lastPageText]); + } else if (props.currPage > 7) { + expect(pageList).toEqual( + [firstPageText, prePageText, 6, 7, 8, 9, 10, nextPageText]); + } else if (props.currPage === 4) { + expect(pageList).toEqual( + [firstPageText, prePageText, 2, 3, 4, 5, 6, nextPageText, lastPageText]); + } else if (props.currPage === 5) { + expect(pageList).toEqual( + [firstPageText, prePageText, 3, 4, 5, 6, 7, nextPageText, lastPageText]); + } else if (props.currPage === 6) { + expect(pageList).toEqual( + [firstPageText, prePageText, 4, 5, 6, 7, 8, nextPageText, lastPageText]); + } else { + expect(pageList).toEqual( + [firstPageText, prePageText, 5, 6, 7, 8, 9, nextPageText, lastPageText]); + } + }); + }); + }); + + describe('the quantity of pages is calculated by props.paginationSize', () => { + const props = createMockProps(); + const indicators = [ + props.firstPageText, props.prePageText, props.lastPageText, props.nextPageText + ]; + + it('should getting pages list correctly', () => { + [1, 3, 5, 8, 10].forEach((paginationSize) => { + props.paginationSize = paginationSize; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + const result = pageList.filter(p => indicators.indexOf(p) === -1); + expect(result.length).toEqual(props.paginationSize); + }); + }); + }); + + describe('when props.withFirstAndLast is true', () => { + const props = createMockProps(); + describe('and last page is not visible by props.currPage', () => { + it('should getting pages list which contain last page indication', () => { + [1, 2, 3, 4, 5, 6, 7].forEach((currPage) => { + props.currPage = currPage; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.lastPageText) > -1).toBeTruthy(); + }); + }); + }); + + describe('and first page is not visible by props.currPage', () => { + it('should getting pages list which contain first page indication', () => { + [10, 9, 8, 7, 6, 5, 4].forEach((currPage) => { + props.currPage = currPage; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.firstPageText) > -1).toBeTruthy(); + }); + }); + }); + }); + + describe('when props.withFirstAndLast is false', () => { + const props = createMockProps(); + it('should not contain first and last page indication always', () => { + const currPages = Array.from(Array(10).keys()); + currPages.forEach((currPage) => { + props.currPage = currPage + 1; + props.withFirstAndLast = false; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.lastPageText) > -1).toBeFalsy(); + expect(pageList.indexOf(props.firstPageText) > -1).toBeFalsy(); + }); + }); + }); + + describe('when props.pageStartIndex is negative number', () => { + const props = createMockProps(); + props.pageStartIndex = -2; + props.currPage = -2; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting last page correctly', () => { + const pageList = wrapper.instance().calculatePages(); + expect(pageList).toEqual( + [props.prePageText, -2, -1, 0, 1, 2, props.nextPageText, props.lastPageText]); + }); + }); + + describe('when props.alwaysShowAllBtns is true', () => { + const props = createMockProps(); + props.alwaysShowAllBtns = true; + props.currPage = 1; + props.dataSize = 11; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should always having next and previous page indication', () => { + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.nextPageText) > -1).toBeTruthy(); + expect(pageList.indexOf(props.prePageText) > -1).toBeTruthy(); + }); + }); + + describe('when state.totalPages is zero', () => { + const props = createMockProps(); + props.dataSize = 0; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting empty array', () => { + expect(wrapper.instance().calculatePages()).toEqual([]); + }); + }); + }); + + describe('calculatePageStatus', () => { + let instance; + let pageStatus; + + describe('default case', () => { + const props = createMockProps(); + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + instance = wrapper.instance(); + pageStatus = instance.calculatePageStatus(instance.calculatePages()); + }); + + it('should returning correct format for page status', () => { + pageStatus.forEach((p) => { + expect(Object.prototype.hasOwnProperty.call(p, 'page')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(p, 'active')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(p, 'disabled')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(p, 'title')).toBeTruthy(); + }); + }); + + it('should mark active status as true when it is props.currPage', () => { + expect(pageStatus.find(p => p.page === props.currPage).active).toBeTruthy(); + }); + + it('only have one page\'s active status is true', () => { + expect(pageStatus.filter(p => p.page === props.currPage).length).toEqual(1); + }); + }); + + describe('when alwaysShowAllBtns is false', () => { + const props = createMockProps(); + describe('and props.currPage is on first page', () => { + it('should filter out previous page indication', () => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + instance = wrapper.instance(); + const pageList = instance.calculatePages(); + pageStatus = instance.calculatePageStatus(pageList); + + expect(pageStatus.find(p => p.page === props.prePageText)).not.toBeDefined(); + }); + }); + + describe('and props.currPage is on last page', () => { + it('should filter out next page indication', () => { + props.currPage = 10; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + instance = wrapper.instance(); + const pageList = instance.calculatePages(); + pageStatus = instance.calculatePageStatus(pageList); + + expect(pageStatus.find(p => p.page === props.nextPageText)).not.toBeDefined(); + }); + }); + }); + }); + + describe('calculateSizePerPageStatus', () => { + describe('when props.sizePerPageList is an number array', () => { + const props = createMockProps(); + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting correctly sizePerPage status', () => { + const instance = wrapper.instance(); + const result = instance.calculateSizePerPageStatus(); + expect(result.length).toEqual(props.sizePerPageList.length); + result.forEach((sizePerPage, i) => { + expect(sizePerPage.text).toEqual(`${props.sizePerPageList[i]}`); + expect(sizePerPage.page).toEqual(props.sizePerPageList[i]); + }); + }); + }); + + describe('when props.sizePerPageList is an object array', () => { + const props = createMockProps(); + props.sizePerPageList = [{ + text: 'ten', value: 10 + }, { + text: 'thirty', value: 30 + }]; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting correctly sizePerPage status', () => { + const instance = wrapper.instance(); + const result = instance.calculateSizePerPageStatus(); + expect(result.length).toEqual(props.sizePerPageList.length); + result.forEach((sizePerPage, i) => { + expect(sizePerPage.text).toEqual(props.sizePerPageList[i].text); + expect(sizePerPage.page).toEqual(props.sizePerPageList[i].value); + }); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/page.test.js b/packages/react-bootstrap-table2-paginator/test/page.test.js new file mode 100644 index 0000000..1e092c9 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/page.test.js @@ -0,0 +1,51 @@ +import Store from 'react-bootstrap-table-next/src/store'; +import { getByCurrPage } from '../src/page'; + +describe('Page Functions', () => { + let data; + let store; + const params = [ + // [page, sizePerPage, pageStartIndex] + [1, 10, 1], + [1, 25, 1], + [1, 30, 1], + [3, 30, 1], + [4, 30, 1], + [10, 10, 1], + [0, 10, 0], + [1, 10, 0], + [9, 10, 0] + ]; + + describe('getByCurrPage', () => { + beforeEach(() => { + data = []; + for (let i = 0; i < 100; i += 1) { + data.push({ id: i, name: `test_name${i}` }); + } + store = new Store('id'); + store.data = data; + }); + + it('should always return correct data', () => { + params.forEach(([page, sizePerPage, pageStartIndex]) => { + store.page = page; + store.sizePerPage = sizePerPage; + const rows = getByCurrPage(store, pageStartIndex); + expect(rows).toBeDefined(); + expect(Array.isArray(rows)).toBeTruthy(); + expect(rows.every(row => !!row)).toBeTruthy(); + }); + }); + + it('should return empty array when store.data is empty', () => { + store.data = []; + params.forEach(([page, sizePerPage, pageStartIndex]) => { + store.page = page; + store.sizePerPage = sizePerPage; + const rows = getByCurrPage(store, pageStartIndex); + expect(rows).toHaveLength(0); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/pagination-list.test.js b/packages/react-bootstrap-table2-paginator/test/pagination-list.test.js new file mode 100644 index 0000000..0545006 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/pagination-list.test.js @@ -0,0 +1,42 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import PageButton from '../src/page-button'; +import PaginationList from '../src/pagination-list'; + +describe('PaginationList', () => { + let wrapper; + const onPageChange = sinon.stub(); + const pages = [{ + page: 1, + active: false, + disabled: false, + title: '1' + }, { + page: 2, + active: true, + disabled: false, + title: '2' + }, { + page: 3, + active: false, + disabled: false, + title: '3' + }]; + + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should rendering PaginatonList correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('ul.react-bootstrap-table-page-btns-ul').length).toBe(1); + expect(wrapper.find(PageButton).length).toBe(pages.length); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/pagination.test.js b/packages/react-bootstrap-table2-paginator/test/pagination.test.js new file mode 100644 index 0000000..a9e5d35 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/pagination.test.js @@ -0,0 +1,288 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import SizePerPageDropDown from '../src/size-per-page-dropdown'; +import PaginationList from '../src/pagination-list'; +import Pagination from '../src/pagination'; + +describe('Pagination', () => { + let wrapper; + let instance; + + const createMockProps = props => ({ + dataSize: 100, + sizePerPageList: [10, 20, 30, 50], + currPage: 1, + currSizePerPage: 10, + pageStartIndex: 1, + paginationSize: 5, + withFirstAndLast: true, + firstPageText: '<<', + prePageText: '<', + nextPageText: '>', + lastPageText: '>>', + alwaysShowAllBtns: false, + onPageChange: sinon.stub(), + onSizePerPageChange: sinon.stub(), + hidePageListOnlyOnePage: false, + hideSizePerPage: false, + ...props + }); + + describe('default pagiantion', () => { + const props = createMockProps(); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should rendering correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('react-bootstrap-table-pagination')).toBeTruthy(); + expect(wrapper.find('.react-bootstrap-table-pagination-list-hidden').length).toBe(0); + }); + + it('should having correct state', () => { + expect(instance.state).toBeDefined(); + expect(instance.state.totalPages).toEqual(instance.calculateTotalPage()); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(instance.state.totalPages)); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + + it('should rendering PaginationList component successfully', () => { + const paginationList = wrapper.find(PaginationList); + expect(paginationList.length).toBe(1); + expect(paginationList.prop('pages')).toEqual(instance.calculatePageStatus(instance.calculatePages())); + expect(paginationList.prop('onPageChange')).toEqual(instance.handleChangePage); + }); + + it('should rendering SizePerPageDropDown component successfully', () => { + const sizePerPageDropDown = wrapper.find(SizePerPageDropDown); + expect(sizePerPageDropDown.length).toBe(1); + + expect(sizePerPageDropDown.prop('currSizePerPage')).toEqual(`${props.currSizePerPage}`); + expect(sizePerPageDropDown.prop('options')).toEqual(instance.calculateSizePerPageStatus()); + expect(sizePerPageDropDown.prop('onSizePerPageChange')).toEqual(instance.handleChangeSizePerPage); + expect(sizePerPageDropDown.prop('onClick')).toEqual(instance.toggleDropDown); + expect(sizePerPageDropDown.prop('open')).toEqual(instance.state.dropdownOpen); + }); + }); + + describe('when props.sizePerPageList is empty array', () => { + beforeEach(() => { + const props = createMockProps({ sizePerPageList: [] }); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should not rendering SizePerPageDropDown component', () => { + const sizePerPageDropDown = wrapper.find(SizePerPageDropDown); + expect(sizePerPageDropDown.length).toBe(0); + }); + }); + + describe('when props.hideSizePerPage is true', () => { + beforeEach(() => { + const props = createMockProps({ hideSizePerPage: true }); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should not rendering SizePerPageDropDown component', () => { + const sizePerPageDropDown = wrapper.find(SizePerPageDropDown); + expect(sizePerPageDropDown.length).toBe(0); + }); + }); + + describe('when props.hidePageListOnlyOnePage is true', () => { + beforeEach(() => { + const props = createMockProps({ hidePageListOnlyOnePage: true, dataSize: 7 }); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should find react-bootstrap-table-pagination-list-hidden class when only one page', () => { + expect(wrapper.find('.react-bootstrap-table-pagination-list-hidden').length).toBe(1); + }); + }); + + describe('componentWillReceiveProps', () => { + describe('when next props.currSizePerPage is diff than current one', () => { + const nextProps = createMockProps({ currSizePerPage: 20 }); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should setting correct state.totalPages', () => { + instance.componentWillReceiveProps(nextProps); + expect(instance.state.totalPages).toEqual( + instance.calculateTotalPage(nextProps.currSizePerPage)); + }); + + it('should setting correct state.lastPage', () => { + instance.componentWillReceiveProps(nextProps); + const totalPages = instance.calculateTotalPage(nextProps.currSizePerPage); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(totalPages)); + }); + }); + + describe('when next props.dataSize is diff than current one', () => { + const nextProps = createMockProps({ dataSize: 33 }); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should setting correct state.totalPages', () => { + instance.componentWillReceiveProps(nextProps); + expect(instance.state.totalPages).toEqual( + instance.calculateTotalPage(nextProps.currSizePerPage, nextProps.dataSize)); + }); + + it('should setting correct state.lastPage', () => { + instance.componentWillReceiveProps(nextProps); + const totalPages = instance.calculateTotalPage( + nextProps.currSizePerPage, nextProps.dataSize); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(totalPages)); + }); + }); + }); + + describe('toggleDropDown', () => { + beforeEach(() => { + const props = createMockProps(); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should setting state.dropdownOpen as true when it is false', () => { + instance.toggleDropDown(); + expect(instance.state.dropdownOpen).toBeTruthy(); + }); + + it('should setting state.dropdownOpen as false when it is true', () => { + instance.toggleDropDown(); + instance.toggleDropDown(); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + }); + + describe('closeDropDown', () => { + beforeEach(() => { + const props = createMockProps(); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should always setting state.dropdownOpen as false', () => { + instance.closeDropDown(); + expect(instance.state.dropdownOpen).toBeFalsy(); + instance.closeDropDown(); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + }); + + describe('handleChangeSizePerPage', () => { + const props = createMockProps(); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should always setting state.dropdownOpen to false', () => { + instance.handleChangeSizePerPage(10); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + + describe('when new sizePerPage is same as current one', () => { + it('should not calling props.onSizePerPageChange callback', () => { + instance.handleChangeSizePerPage(10); + expect(props.onSizePerPageChange.callCount).toBe(0); + }); + }); + + describe('when new sizePerPage is diff than current one', () => { + it('should not calling props.onSizePerPageChange callback', () => { + instance.handleChangeSizePerPage(30); + expect(props.onSizePerPageChange.callCount).toBe(1); + }); + + describe('and new current page is still in the new lagination list', () => { + it('should calling props.onSizePerPageChange with correct argument', () => { + expect(props.onSizePerPageChange.calledWith(30, props.currPage)); + }); + }); + + describe('and new current page is still in the new lagination list', () => { + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should calling props.onSizePerPageChange with correct argument', () => { + expect(props.onSizePerPageChange.calledWith(30, 4)); + }); + }); + }); + }); + + describe('handleChangePage', () => { + const props = createMockProps(); + + beforeEach(() => { + props.currPage = 6; + wrapper = shallow(); + instance = wrapper.instance(); + }); + + afterEach(() => { + props.onPageChange.reset(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.prePageText', () => { + instance.handleChangePage(props.prePageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(5)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.nextPageText', () => { + instance.handleChangePage(props.nextPageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(7)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.lastPageText', () => { + instance.handleChangePage(props.lastPageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(10)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.firstPageText', () => { + instance.handleChangePage(props.firstPageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(props.pageStartIndex)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is a numeric page', () => { + const newPage = '8'; + instance.handleChangePage(newPage); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(parseInt(newPage, 10))).toBeTruthy(); + }); + + it('should not calling props.onPageChange correctly when page is not changed', () => { + const newPage = props.currPage; + instance.handleChangePage(newPage); + expect(props.onPageChange.callCount).toBe(0); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js new file mode 100644 index 0000000..21e11cc --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js @@ -0,0 +1,127 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import SizePerPageOption from '../src/size-per-page-option'; +import SizePerPageDropDown from '../src/size-per-page-dropdown'; + +describe('SizePerPageDropDown', () => { + let wrapper; + const currSizePerPage = '25'; + const options = [{ + text: '10', + page: 10 + }, { + text: '25', + page: 25 + }]; + const onClick = sinon.stub(); + const onBlur = sinon.stub(); + const onSizePerPageChange = sinon.stub(); + const props = { + currSizePerPage, + options, + onClick, + onBlur, + onSizePerPageChange + }; + + describe('default SizePerPageDropDown component', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should rendering SizePerPageDropDown correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('button').length).toBe(1); + expect(wrapper.find('button').text()).toEqual(`${currSizePerPage} `); + }); + + it('should rendering SizePerPageOption successfully', () => { + expect(wrapper.find('ul.dropdown-menu').length).toBe(1); + const sizePerPageOptions = wrapper.find(SizePerPageOption); + expect(sizePerPageOptions.length).toBe(options.length); + sizePerPageOptions.forEach((sizePerPage, i) => { + const option = options[i]; + expect(sizePerPage.prop('text')).toEqual(option.text); + expect(sizePerPage.prop('page')).toEqual(option.page); + expect(sizePerPage.prop('onSizePerPageChange')).toEqual(onSizePerPageChange); + }); + }); + + it('default variation is dropdown', () => { + expect(wrapper.hasClass('dropdown')).toBeTruthy(); + }); + + it('default dropdown is not open', () => { + expect(wrapper.hasClass('open show')).toBeFalsy(); + expect(wrapper.find('[aria-expanded=false]').length).toBe(1); + }); + }); + + describe('when open prop is true', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should rendering SizePerPageDropDown correctly', () => { + expect(wrapper.hasClass('open show')).toBeTruthy(); + expect(wrapper.find('[aria-expanded=true]').length).toBe(1); + }); + }); + + describe('when hidden prop is true', () => { + beforeEach(() => { + wrapper = shallow( +