From eb4fc7815f27366eb990e956df72a26a7241ac44 Mon Sep 17 00:00:00 2001 From: Ryan Lanciaux Date: Sun, 30 Oct 2016 15:22:51 -0400 Subject: [PATCH 1/7] Move files around for controlled Griddle component --- src/components/index.js | 2 -- src/index.js | 8 ++++++-- src/{ => plugins/local}/components/FilterContainer.js | 4 ++-- src/plugins/local/components/index.js | 4 +++- 4 files changed, 11 insertions(+), 7 deletions(-) rename src/{ => plugins/local}/components/FilterContainer.js (70%) diff --git a/src/components/index.js b/src/components/index.js index 98458ff3..5f116ce1 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -9,7 +9,6 @@ import Layout from './Layout'; import LayoutContainer from './LayoutContainer'; import Pagination from './Pagination'; import Filter from './Filter'; -import FilterContainer from './FilterContainer'; import SettingsToggle from './SettingsToggle'; import SettingsToggleContainer from './SettingsToggleContainer'; import SettingsWrapper from './SettingsWrapper'; @@ -29,7 +28,6 @@ const components = { LayoutContainer, Pagination, Filter, - FilterContainer, SettingsToggle, SettingsToggleContainer, SettingsWrapper, diff --git a/src/index.js b/src/index.js index 0b385cda..be9f6005 100644 --- a/src/index.js +++ b/src/index.js @@ -14,13 +14,14 @@ import { getRowProperties } from './utils/rowUtils'; export default class extends Component { static childContextTypes = { components: React.PropTypes.object.isRequired, - settingsComponentObjects: React.PropTypes.object + settingsComponentObjects: React.PropTypes.object, + events: React.PropTypes.object } constructor(props) { super(props); - const { plugins=[], data, children:rowPropertiesComponent } = props; + const { plugins=[], data, children:rowPropertiesComponent, events={} } = props; const rowProperties = getRowProperties(rowPropertiesComponent); const columnProperties = getColumnProperties(rowPropertiesComponent); @@ -33,6 +34,8 @@ export default class extends Component { this.settingsComponentObjects = Object.assign({}, settingsComponentObjects, ...plugins.map(p => plugins.settingsComponentObjects)); + this.events = Object.assign({}, events, ...plugins.map(p => plugins.events)); + //TODO: This should also look at the default and plugin initial state objects const renderProperties = { rowProperties, @@ -85,6 +88,7 @@ export default class extends Component { return { components: this.components, settingsComponentObjects: this.settingsComponentObjects, + events: this.events, }; } diff --git a/src/components/FilterContainer.js b/src/plugins/local/components/FilterContainer.js similarity index 70% rename from src/components/FilterContainer.js rename to src/plugins/local/components/FilterContainer.js index 59ac7352..0f84ea5c 100644 --- a/src/components/FilterContainer.js +++ b/src/plugins/local/components/FilterContainer.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { setFilter } from '../actions'; +import { setFilter } from '../../../actions'; const EnhancedFilterContainer = OriginalComponent => connect( null, @@ -9,4 +9,4 @@ const EnhancedFilterContainer = OriginalComponent => connect( } )((props) => ); -export default EnhancedFilterContainer; \ No newline at end of file +export default EnhancedFilterContainer; diff --git a/src/plugins/local/components/index.js b/src/plugins/local/components/index.js index 04b8a4e5..cce4ffa7 100644 --- a/src/plugins/local/components/index.js +++ b/src/plugins/local/components/index.js @@ -4,6 +4,7 @@ import RowContainer from './RowContainer'; import CellContainer from './CellContainer'; import PaginationContainer from './PaginationContainer'; import TableHeadingCellContainer from './TableHeadingCellContainer'; +import FilterContainer from './FilterContainer'; export default { TableBodyContainer, @@ -12,4 +13,5 @@ export default { CellContainer, PaginationContainer, TableHeadingCellContainer, -} \ No newline at end of file + FilterContainer, +} From 5f40939fda4302ad7ad20725ab91d9f456b0ddf1 Mon Sep 17 00:00:00 2001 From: Ryan Lanciaux Date: Sun, 30 Oct 2016 16:32:29 -0400 Subject: [PATCH 2/7] Initial callback working for controlled component --- .../local => }/components/CellContainer.js | 5 +-- src/components/ColumnDefinition.js | 1 - src/components/Filter.js | 2 +- src/components/FilterContainer.js | 13 ++++++ src/components/RowContainer.js | 25 +++++++++++ src/components/RowDefinition.js | 2 +- src/components/TableBodyContainer.js | 28 ++++++++++++ src/components/TableHeadingContainer.js | 28 ++++++++++++ src/components/index.js | 10 +++++ .../local/components/TableHeadingContainer.js | 2 +- src/plugins/local/components/index.js | 4 -- src/plugins/local/selectors/localSelectors.js | 7 +-- src/selectors/dataSelectors.js | 43 +++++++++++++++++++ src/utils/rowUtils.js | 4 +- stories/index.js | 22 ++++++++-- 15 files changed, 175 insertions(+), 21 deletions(-) rename src/{plugins/local => }/components/CellContainer.js (78%) create mode 100644 src/components/FilterContainer.js create mode 100644 src/components/RowContainer.js create mode 100644 src/components/TableBodyContainer.js create mode 100644 src/components/TableHeadingContainer.js diff --git a/src/plugins/local/components/CellContainer.js b/src/components/CellContainer.js similarity index 78% rename from src/plugins/local/components/CellContainer.js rename to src/components/CellContainer.js index 3bb98304..3a8ff443 100644 --- a/src/plugins/local/components/CellContainer.js +++ b/src/components/CellContainer.js @@ -2,8 +2,7 @@ import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import { getContext, mapProps, compose, withHandlers } from 'recompose'; -import { cellValueSelector } from '../selectors/localSelectors'; -import { customComponentSelector } from '../../../selectors/dataSelectors'; +import { customComponentSelector, cellValueSelector } from '../selectors/dataSelectors'; const ComposedCellContainer = OriginalComponent => compose( connect((state, props) => ({ @@ -26,4 +25,4 @@ const ComposedCellContainer = OriginalComponent => compose( /> )) -export default ComposedCellContainer; \ No newline at end of file +export default ComposedCellContainer; diff --git a/src/components/ColumnDefinition.js b/src/components/ColumnDefinition.js index 8f9c25f0..a674b17f 100644 --- a/src/components/ColumnDefinition.js +++ b/src/components/ColumnDefinition.js @@ -49,7 +49,6 @@ export default class ColumnDefinition extends Component { }; render() { - console.log('in column definition'); return null; } } diff --git a/src/components/Filter.js b/src/components/Filter.js index dd35a621..e64fbb0f 100644 --- a/src/components/Filter.js +++ b/src/components/Filter.js @@ -21,4 +21,4 @@ class Filter extends Component { } } -export default Filter; \ No newline at end of file +export default Filter; diff --git a/src/components/FilterContainer.js b/src/components/FilterContainer.js new file mode 100644 index 00000000..9aaa77a1 --- /dev/null +++ b/src/components/FilterContainer.js @@ -0,0 +1,13 @@ +import React, { PropTypes } from 'react'; +import { withHandlers, getContext, compose, mapProps } from 'recompose'; + +const EnhancedFilter = OriginalComponent => compose( + getContext({ + events: PropTypes.object + }), + mapProps(props => ({ + setFilter: props.events.onFilter + })) +)(({setFilter}) => ); + +export default EnhancedFilter; diff --git a/src/components/RowContainer.js b/src/components/RowContainer.js new file mode 100644 index 00000000..15f10409 --- /dev/null +++ b/src/components/RowContainer.js @@ -0,0 +1,25 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { getContext, mapProps, compose, withHandlers } from 'recompose'; +import { columnIdsSelector } from '../selectors/dataSelectors'; + +const ComposedRowContainer = OriginalComponent => compose( + getContext({ + components: PropTypes.object + }), + connect((state) => ({ + columnIds: columnIdsSelector(state) + })), + mapProps(props => ({ + Cell: props.components.Cell, + ...props + })), +)(({Cell, columnIds, griddleKey}) => ( + +)); + +export default ComposedRowContainer; diff --git a/src/components/RowDefinition.js b/src/components/RowDefinition.js index 13d5d13a..3e8f3ffc 100644 --- a/src/components/RowDefinition.js +++ b/src/components/RowDefinition.js @@ -25,4 +25,4 @@ export default class extends Component { render () { return null; } -} \ No newline at end of file +} diff --git a/src/components/TableBodyContainer.js b/src/components/TableBodyContainer.js new file mode 100644 index 00000000..a4a4d79c --- /dev/null +++ b/src/components/TableBodyContainer.js @@ -0,0 +1,28 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { getContext, mapProps, compose, withHandlers } from 'recompose'; + +import { visibleRowIdsSelector } from '../selectors/dataSelectors'; + +const ComposedTableBodyContainer = OriginalComponent => compose( + getContext({ + components: PropTypes.object + }), + connect((state) => ({ + visibleRowIds: visibleRowIdsSelector(state) + })), + mapProps(props => ({ + Row: props.components.Row, + ...props + })), + // withHandlers({ + // Row: props => props.components.Row + // }) +)(({Row, visibleRowIds}) => ( + +)); + +export default ComposedTableBodyContainer; diff --git a/src/components/TableHeadingContainer.js b/src/components/TableHeadingContainer.js new file mode 100644 index 00000000..9684baca --- /dev/null +++ b/src/components/TableHeadingContainer.js @@ -0,0 +1,28 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { getContext, mapProps, compose, withHandlers } from 'recompose'; +import { columnTitlesSelector, columnIdsSelector } from '../selectors/dataSelectors'; + +const ComposedContainerComponent = OriginalComponent => compose( + getContext({ + components: PropTypes.object + }), + connect((state) => ({ + columnTitles: columnTitlesSelector(state), + columnIds: columnIdsSelector(state) + })), + mapProps(props => ({ + TableHeadingCell: props.components.TableHeadingCell, + ...props + })) + // withHandlers({ + // TableHeadingCell: props => props.components.TableHeadingCell + // }) +)(({TableHeadingCell, columnTitles, columnIds }) => ( + +)); + +export default ComposedContainerComponent; diff --git a/src/components/index.js b/src/components/index.js index 5f116ce1..f7bf5520 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,14 +1,19 @@ import Cell from './Cell'; +import CellContainer from './CellContainer'; import Row from './Row'; +import RowContainer from './RowContainer'; import Table from './Table'; import TableBody from './TableBody'; +import TableBodyContainer from './TableBodyContainer'; import TableHeading from './TableHeading'; +import TableHeadingContainer from './TableHeadingContainer'; import TableHeadingCell from './TableHeadingCell'; import TableContainer from './TableContainer'; import Layout from './Layout'; import LayoutContainer from './LayoutContainer'; import Pagination from './Pagination'; import Filter from './Filter'; +import FilterContainer from './FilterContainer'; import SettingsToggle from './SettingsToggle'; import SettingsToggleContainer from './SettingsToggleContainer'; import SettingsWrapper from './SettingsWrapper'; @@ -18,16 +23,21 @@ import SettingsContainer from './SettingsContainer'; const components = { Cell, + CellContainer, Row, + RowContainer, Table, TableBody, + TableBodyContainer, TableHeading, + TableHeadingContainer, TableHeadingCell, TableContainer, Layout, LayoutContainer, Pagination, Filter, + FilterContainer, SettingsToggle, SettingsToggleContainer, SettingsWrapper, diff --git a/src/plugins/local/components/TableHeadingContainer.js b/src/plugins/local/components/TableHeadingContainer.js index 700e19f0..7e570492 100644 --- a/src/plugins/local/components/TableHeadingContainer.js +++ b/src/plugins/local/components/TableHeadingContainer.js @@ -25,4 +25,4 @@ const ComposedContainerComponent = OriginalComponent => compose( TableHeadingCell={TableHeadingCell} /> )); -export default ComposedContainerComponent; \ No newline at end of file +export default ComposedContainerComponent; diff --git a/src/plugins/local/components/index.js b/src/plugins/local/components/index.js index cce4ffa7..ef1a2a38 100644 --- a/src/plugins/local/components/index.js +++ b/src/plugins/local/components/index.js @@ -1,16 +1,12 @@ -import TableHeadingContainer from './TableHeadingContainer'; import TableBodyContainer from './TableBodyContainer'; import RowContainer from './RowContainer'; -import CellContainer from './CellContainer'; import PaginationContainer from './PaginationContainer'; import TableHeadingCellContainer from './TableHeadingCellContainer'; import FilterContainer from './FilterContainer'; export default { TableBodyContainer, - TableHeadingContainer, RowContainer, - CellContainer, PaginationContainer, TableHeadingCellContainer, FilterContainer, diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index 4a567eb1..673d8b1c 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -183,12 +183,7 @@ export const columnTitlesSelector = createSelector( } ) -// TODO: Needs tests and jsdoc -export const cellValueSelector = (state, { griddleKey, columnId }) => { - return state.get('data') - .find(r => r.get('griddleKey') === griddleKey) - .get(columnId); -} +export const cellValueSelector = dataSelectors.cellValueSelector; // TODO: Needs tests and jsdoc export const rowDataSelector = (state, { griddleKey}) => { diff --git a/src/selectors/dataSelectors.js b/src/selectors/dataSelectors.js index 759d57b3..fdeba52a 100644 --- a/src/selectors/dataSelectors.js +++ b/src/selectors/dataSelectors.js @@ -184,3 +184,46 @@ export const textSelector = (state, { key}) => { return state.getIn(['textProperties', key]); } +/** Gets the column ids for the visible columns +*/ +export const columnIdsSelector = createSelector( + dataSelector, + renderPropertiesSelector, + (visibleData, renderProperties) => { + if(visibleData.size > 0) { + return Object.keys(visibleData.get(0).toJSON()).map(k => + renderProperties.getIn(['columnProperties', k, 'id']) || k + ) + } + } +) + +/** Gets the column titles for the visible columns + */ +export const columnTitlesSelector = createSelector( + dataSelector, + renderPropertiesSelector, + (visibleData, renderProperties) => { + if(visibleData.size > 0) { + return Object.keys(visibleData.get(0).toJSON()).map(k => + renderProperties.getIn(['columnProperties', k, 'title']) || k + ) + } + + return []; + } +) + +/** Gets the griddleIds for the visible rows */ +export const visibleRowIdsSelector = createSelector( + dataSelector, + (currentPageData) => currentPageData.map(c => c.get('griddleKey')) +); + +// TODO: Needs tests and jsdoc +export const cellValueSelector = (state, { griddleKey, columnId }) => { + return state.get('data') + .find(r => r.get('griddleKey') === griddleKey) + .get(columnId); +} + diff --git a/src/utils/rowUtils.js b/src/utils/rowUtils.js index e5157143..609469b1 100644 --- a/src/utils/rowUtils.js +++ b/src/utils/rowUtils.js @@ -2,6 +2,8 @@ * @param {Object} rowPropertiesComponent - A react component that contains rowProperties as props */ export function getRowProperties(rowPropertiesComponent) { + if (!rowPropertiesComponent) return null; + let rowProps = Object.assign({}, rowPropertiesComponent.props); delete rowProps.children; @@ -10,4 +12,4 @@ export function getRowProperties(rowPropertiesComponent) { } return rowProps; -} \ No newline at end of file +} diff --git a/stories/index.js b/stories/index.js index 61d9c311..6b0ac145 100644 --- a/stories/index.js +++ b/stories/index.js @@ -13,7 +13,7 @@ import { Table } from '../src/components/Table'; import TableContainer from '../src/components/TableContainer'; import ColumnDefinition from '../src/components/ColumnDefinition'; import RowDefinition from '../src/components/RowDefinition'; - +import _ from 'lodash'; import { rowDataSelector } from '../src/plugins/local/selectors/localSelectors'; import fakeData from './fakeData'; @@ -36,6 +36,17 @@ function sortBySecondCharacter(data, column, sortAscending = true) { }); } +// from mdn +function getRandomIntInclusive(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function getRandomFakeData() { + const start = getRandomIntInclusive(0, fakeData.length - 10); + return fakeData.slice(start, start + 10); +} const GreenLeftSortIconComponent = (props) => ( {props.icon && {props.icon}} @@ -89,7 +100,6 @@ storiesOf('Griddle main', module) - ) }) @@ -103,10 +113,16 @@ storiesOf('Griddle main', module) - ) }) +.add('with controlled griddle component', () => { + const data = getRandomFakeData(); + const events = { + onFilter: (filter) => console.log('CALLBACK', filter) + } + return +}) .add('with custom heading component', () => { return (
From 7659d2f5da05477406d3b74a47f2e13fa939f2a6 Mon Sep 17 00:00:00 2001 From: Ryan Lanciaux Date: Sun, 30 Oct 2016 22:51:15 -0400 Subject: [PATCH 3/7] Dispatching on new props (not _really_ supposed to do this) --- src/actions/index.js | 8 ++++++++ src/constants/index.js | 1 + src/index.js | 12 +++++++++++- src/reducers/dataReducer.js | 7 ++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index e2095c0f..2c3276f4 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -7,6 +7,7 @@ import { GRIDDLE_TOGGLE_SETTINGS, GRIDDLE_TOGGLE_COLUMN, GRIDDLE_SET_PAGE_SIZE, + GRIDDLE_UPDATE_STATE, } from '../constants'; export function getNext() { @@ -61,3 +62,10 @@ export function setPageSize(pageSize) { pageSize } } + +export function updateState(newState) { + return { + type: GRIDDLE_UPDATE_STATE, + newState + } +} diff --git a/src/constants/index.js b/src/constants/index.js index 6c67a562..9bf25151 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -17,3 +17,4 @@ export const GRIDDLE_TOGGLE_COLUMN = 'GRIDDLE_TOGGLE_COLUMN'; export const GRIDDLE_ROW_TOGGLED = 'GRIDDLE_ROW_TOGGLED'; export const GRIDDLE_ROW_SELECTION_TOGGLED = 'GRIDDLE_ROW_SELECTION_TOGGLED'; export const GRIDDLE_TOGGLE_SETTINGS = 'GRIDDLE_TOGGLE_SETTINGS'; +export const GRIDDLE_UPDATE_STATE = 'GRIDDLE_UPDATE_STATE'; diff --git a/src/index.js b/src/index.js index be9f6005..7fc2dd36 100644 --- a/src/index.js +++ b/src/index.js @@ -10,8 +10,9 @@ import settingsComponentObjects from './settingsComponentObjects'; import { buildGriddleReducer, buildGriddleComponents } from './utils/compositionUtils'; import { getColumnProperties } from './utils/columnUtils'; import { getRowProperties } from './utils/rowUtils'; +import { updateState } from './actions'; -export default class extends Component { +class Griddle extends Component { static childContextTypes = { components: React.PropTypes.object.isRequired, settingsComponentObjects: React.PropTypes.object, @@ -84,6 +85,13 @@ export default class extends Component { ); } + componentWillReceiveProps(nextProps) { + const { data, pageProperties } = nextProps; + + this.store.dispatch(updateState({ data, pageProperties })); + + } + getChildContext() { return { components: this.components, @@ -101,3 +109,5 @@ export default class extends Component { } } + +export default Griddle; diff --git a/src/reducers/dataReducer.js b/src/reducers/dataReducer.js index 99bb52b2..9f500ad5 100644 --- a/src/reducers/dataReducer.js +++ b/src/reducers/dataReducer.js @@ -97,7 +97,7 @@ export function GRIDDLE_TOGGLE_SETTINGS(state, action) { return state.set('showSettings', !showSettings); } -export function GRIDDLE_TOGGLE_COLUMN(state, action) { +export function GRIDDLE_TOGGLE_COLUMN(state, action) { // flips the visible state if the column property exists return state.getIn(['renderProperties', 'columnProperties', action.columnId]) ? state.setIn(['renderProperties', 'columnProperties', action.columnId, 'visible'], @@ -107,3 +107,8 @@ export function GRIDDLE_TOGGLE_COLUMN(state, action) { state.setIn(['renderProperties', 'columnProperties', action.columnId], new Immutable.Map({ id: action.columnId, visible: true })) } + +export function GRIDDLE_UPDATE_STATE(state, action) { + console.log('UPDATE STATE'); + return state.mergeDeep(action.newState);; +} From c7d5f751daaa398ef9e9abfaeec853930a49e2b2 Mon Sep 17 00:00:00 2001 From: Ryan Lanciaux Date: Mon, 31 Oct 2016 06:13:21 -0400 Subject: [PATCH 4/7] Table heading Cell from Joel's changes --- src/components/TableHeadingCellContainer.js | 21 ++++++++++ src/components/index.js | 2 + src/reducers/dataReducer.js | 1 - stories/index.js | 44 ++++++++++++++++++--- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 src/components/TableHeadingCellContainer.js diff --git a/src/components/TableHeadingCellContainer.js b/src/components/TableHeadingCellContainer.js new file mode 100644 index 00000000..21a7a5c9 --- /dev/null +++ b/src/components/TableHeadingCellContainer.js @@ -0,0 +1,21 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { getContext, mapProps, compose, withHandlers } from 'recompose'; +import { setSortProperties } from '../utils/columnUtils'; + +const EnhancedHeadingCell = OriginalComponent => compose( + getContext({ + events: PropTypes.object + }), + withHandlers({ + onClick: ({ events: { onSort }, columnId }) => event => { + onSort({ id: columnId }) + } + }), +)((props) => { + return ( + + ); +}); + +export default EnhancedHeadingCell; diff --git a/src/components/index.js b/src/components/index.js index f7bf5520..9266dfe2 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -8,6 +8,7 @@ import TableBodyContainer from './TableBodyContainer'; import TableHeading from './TableHeading'; import TableHeadingContainer from './TableHeadingContainer'; import TableHeadingCell from './TableHeadingCell'; +import TableHeadingCellContainer from './TableHeadingCellContainer'; import TableContainer from './TableContainer'; import Layout from './Layout'; import LayoutContainer from './LayoutContainer'; @@ -32,6 +33,7 @@ const components = { TableHeading, TableHeadingContainer, TableHeadingCell, + TableHeadingCellContainer, TableContainer, Layout, LayoutContainer, diff --git a/src/reducers/dataReducer.js b/src/reducers/dataReducer.js index 9f500ad5..afa88670 100644 --- a/src/reducers/dataReducer.js +++ b/src/reducers/dataReducer.js @@ -109,6 +109,5 @@ export function GRIDDLE_TOGGLE_COLUMN(state, action) { } export function GRIDDLE_UPDATE_STATE(state, action) { - console.log('UPDATE STATE'); return state.mergeDeep(action.newState);; } diff --git a/stories/index.js b/stories/index.js index 6b0ac145..5188f308 100644 --- a/stories/index.js +++ b/stories/index.js @@ -117,11 +117,46 @@ storiesOf('Griddle main', module) ) }) .add('with controlled griddle component', () => { - const data = getRandomFakeData(); - const events = { - onFilter: (filter) => console.log('CALLBACK', filter) + + class Something extends React.Component { + constructor() { + super(); + + this.state = { + data: getRandomFakeData() + }; + } + + onFilter = (filter) => { + console.log('onFilter', filter); + this.setState({ data: getRandomFakeData() }) + } + + onSort = (sortProperties) => { + console.log('onSort', sortProperties); + this.setState({ data: getRandomFakeData() }) + } + + render() { + const pageProperties = { + currentPage: getRandomIntInclusive(1, 1000), + maxPage: getRandomIntInclusive(1, 1000) + } + + // don't do things this way - fine for example storybook + const events = { + onFilter: this.onFilter, + onSort: this.onSort, + } + + return + } } - return + + return }) .add('with custom heading component', () => { return ( @@ -133,7 +168,6 @@ storiesOf('Griddle main', module) -
) }) From 5f1714cca2a57041101f22bb707af1b5ad4299d0 Mon Sep 17 00:00:00 2001 From: Ryan Lanciaux Date: Mon, 31 Oct 2016 06:56:39 -0400 Subject: [PATCH 5/7] Working pagination callbacks --- src/components/PaginationContainer.js | 29 +++++++++++++++++ src/components/index.js | 2 ++ src/selectors/__tests__/dataSelectorsTest.js | 8 ++--- src/selectors/dataSelectors.js | 34 ++++++++++++-------- stories/index.js | 22 +++++++++++-- 5 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 src/components/PaginationContainer.js diff --git a/src/components/PaginationContainer.js b/src/components/PaginationContainer.js new file mode 100644 index 00000000..890bb9f0 --- /dev/null +++ b/src/components/PaginationContainer.js @@ -0,0 +1,29 @@ +import React, { PropTypes } from 'react'; +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux'; +import { compose, mapProps, getContext } from 'recompose'; +import { createStructuredSelector } from 'reselect'; + +import { hasNextSelector, hasPreviousSelector, currentPageSelector, maxPageSelector } from '../selectors/dataSelectors'; + +const EnhancedPaginationContainer = OriginalComponent => compose( + getContext({ + events: PropTypes.object + }), + connect( + createStructuredSelector({ + hasNext: hasNextSelector, + hasPrevious: hasPreviousSelector, + maxPages: maxPageSelector, + currentPage: currentPageSelector + }), + ), + mapProps(({ events: {onNext:getNext, onPrevious:getPrevious, onGetPage:setPage }, ...props }) => ({ + getNext, + getPrevious, + setPage, + ...props + })) +)((props) => ); + +export default EnhancedPaginationContainer; diff --git a/src/components/index.js b/src/components/index.js index 9266dfe2..5cbfed7f 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -13,6 +13,7 @@ import TableContainer from './TableContainer'; import Layout from './Layout'; import LayoutContainer from './LayoutContainer'; import Pagination from './Pagination'; +import PaginationContainer from './PaginationContainer'; import Filter from './Filter'; import FilterContainer from './FilterContainer'; import SettingsToggle from './SettingsToggle'; @@ -38,6 +39,7 @@ const components = { Layout, LayoutContainer, Pagination, + PaginationContainer, Filter, FilterContainer, SettingsToggle, diff --git a/src/selectors/__tests__/dataSelectorsTest.js b/src/selectors/__tests__/dataSelectorsTest.js index 5fa2d7ed..3a9b8cff 100644 --- a/src/selectors/__tests__/dataSelectorsTest.js +++ b/src/selectors/__tests__/dataSelectorsTest.js @@ -90,19 +90,19 @@ test('gets max page', test => { } }); - test.is(selectors.maxPageCountSelector(state), 2); + test.is(selectors.maxPageSelector(state), 2); //ensure that we get 2 pages when full pageSize would not be displayed on next page const otherState = state.setIn(['pageProperties', 'pageSize'], 11); - test.is(selectors.maxPageCountSelector(otherState), 2); + test.is(selectors.maxPageSelector(otherState), 2); //when pageSize === recordCount should have 1 page const onePageState = state.setIn(['pageProperties', 'pageSize'], 20); - test.is(selectors.maxPageCountSelector(onePageState), 1); + test.is(selectors.maxPageSelector(onePageState), 1); //when there are no records, there should be 0 pages const noDataState = state.setIn(['pageProperties', 'recordCount'], 0); - test.is(selectors.maxPageCountSelector(noDataState), 0); + test.is(selectors.maxPageSelector(noDataState), 0); }); /* filterSelector */ diff --git a/src/selectors/dataSelectors.js b/src/selectors/dataSelectors.js index fdeba52a..9ba97604 100644 --- a/src/selectors/dataSelectors.js +++ b/src/selectors/dataSelectors.js @@ -1,5 +1,6 @@ import Immutable from 'immutable'; import { createSelector } from 'reselect'; +import _ from 'lodash'; import MAX_SAFE_INTEGER from 'max-safe-integer' //import { createSelector } from 'reselect'; @@ -19,27 +20,34 @@ export const recordCountSelector = state => state.getIn(['pageProperties', 'reco /** Gets the render properties */ export const renderPropertiesSelector = state => (state.get('renderProperties')); -/** Determines if there are more pages available. Assumes pageProperties.maxPage is set by the container */ -export const hasNextSelector = createSelector( - currentPageSelector, - pageSizeSelector, - recordCountSelector, - (currentPage, pageSize, recordCount) => { - return (currentPage * pageSize) < recordCount; - } -); - /** Determines if there are previous pages */ export const hasPreviousSelector = createSelector( currentPageSelector, (currentPage) => (currentPage > 1) ); -/** Determines the maxPageCount based on pageSize / recordCount */ -export const maxPageCountSelector = createSelector( +/** Gets the max page size + */ +export const maxPageSelector = createSelector( pageSizeSelector, recordCountSelector, - (pageSize, recordCount) => (Math.ceil(recordCount / pageSize)) + (pageSize, recordCount) => { + const calc = recordCount / pageSize; + + const result = calc > Math.floor(calc) ? Math.floor(calc) + 1 : Math.floor(calc); + + return _.isFinite(result) ? result : 1; + } +); + +/** Determines if there are more pages available. Assumes pageProperties.maxPage is set by the container */ +export const hasNextSelector = createSelector( + currentPageSelector, + maxPageSelector, + (currentPage, maxPage) => { + console.log(`hasNext current: ${currentPage} max: ${maxPage}`) + return currentPage < maxPage; + } ); /** Gets current filter */ diff --git a/stories/index.js b/stories/index.js index 5188f308..1cdef47f 100644 --- a/stories/index.js +++ b/stories/index.js @@ -137,16 +137,34 @@ storiesOf('Griddle main', module) this.setState({ data: getRandomFakeData() }) } + onNext = () => { + console.log('onNext'); + this.setState({ data: getRandomFakeData() }) + } + + onPrevious = () => { + console.log('onPrevious'); + this.setState({ data: getRandomFakeData() }) + } + + onGetPage = (pageNumber) => { + console.log('onGetPage', pageNumber); + this.setState({ data: getRandomFakeData() }) + } + render() { const pageProperties = { - currentPage: getRandomIntInclusive(1, 1000), - maxPage: getRandomIntInclusive(1, 1000) + currentPage: getRandomIntInclusive(1, 10), + recordCount: getRandomIntInclusive(1, 1000) } // don't do things this way - fine for example storybook const events = { onFilter: this.onFilter, onSort: this.onSort, + onNext: this.onNext, + onPrevious: this.onPrevious, + onGetPage: this.onGetPage } return Date: Mon, 31 Oct 2016 07:39:50 -0400 Subject: [PATCH 6/7] Ability to set sort in controlled component --- src/components/PageDropdown.js | 7 +++- src/components/TableHeadingCellContainer.js | 38 +++++++++++++++++++++ src/index.js | 7 ++-- stories/index.js | 14 ++++++-- 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/components/PageDropdown.js b/src/components/PageDropdown.js index 93ed7c02..5fba94b5 100644 --- a/src/components/PageDropdown.js +++ b/src/components/PageDropdown.js @@ -1,8 +1,13 @@ import React, { PropTypes, Component } from 'react'; +import _ from 'lodash'; /** Gets a range from a single value. * Could probably make this take a predicate to avoid running through the loop twice */ -const getRange = (number) => Array(number).fill().map((_, i) => i + 1); +const getRange = (number) => { + if (!_.isFinite(number)) { return [0] } + + return Array(number).fill().map((_, i) => i + 1); +} class PageDropdown extends Component { static propTypes = { diff --git a/src/components/TableHeadingCellContainer.js b/src/components/TableHeadingCellContainer.js index 21a7a5c9..01eb3f78 100644 --- a/src/components/TableHeadingCellContainer.js +++ b/src/components/TableHeadingCellContainer.js @@ -1,9 +1,35 @@ import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import { getContext, mapProps, compose, withHandlers } from 'recompose'; +import { sortPropertyByIdSelector, iconByNameSelector, customHeadingComponentSelector } from '../selectors/dataSelectors'; import { setSortProperties } from '../utils/columnUtils'; +const DefaultTableHeadingCellContent = ({title, icon}) => ( + + { title } + { icon && {icon} } + +) + +function getIcon({sortProperty, sortAscendingIcon, sortDescendingIcon}) { +console.log(sortProperty); + if (sortProperty) { + return sortProperty.sortAscending ? sortAscendingIcon : sortDescendingIcon; + } + + // return null so we don't render anything if no sortProperty + return null; +} + const EnhancedHeadingCell = OriginalComponent => compose( + connect( + (state, props) => ({ + sortProperty: sortPropertyByIdSelector(state, props), + sortAscendingIcon: iconByNameSelector(state, { name: 'sortAscending'}), + sortDescendingIcon: iconByNameSelector(state, { name: 'sortDescending'}), + customHeadingComponent: customHeadingComponentSelector(state, props) + }) + ), getContext({ events: PropTypes.object }), @@ -12,6 +38,18 @@ const EnhancedHeadingCell = OriginalComponent => compose( onSort({ id: columnId }) } }), + mapProps(props => { + const icon = getIcon(props); + const title = props.customHeadingComponent ? + : + ; + + return { + ...props, + icon, + title + }; + }) )((props) => { return ( diff --git a/src/index.js b/src/index.js index 7fc2dd36..800ae5be 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,7 @@ class Griddle extends Component { constructor(props) { super(props); - const { plugins=[], data, children:rowPropertiesComponent, events={} } = props; + const { plugins=[], data, children:rowPropertiesComponent, events={}, sortProperties={} } = props; const rowProperties = getRowProperties(rowPropertiesComponent); const columnProperties = getColumnProperties(rowPropertiesComponent); @@ -86,10 +86,9 @@ class Griddle extends Component { } componentWillReceiveProps(nextProps) { - const { data, pageProperties } = nextProps; + const { data, pageProperties, sortProperties } = nextProps; - this.store.dispatch(updateState({ data, pageProperties })); - + this.store.dispatch(updateState({ data, pageProperties, sortProperties })); } getChildContext() { diff --git a/stories/index.js b/stories/index.js index 1cdef47f..3fe7c065 100644 --- a/stories/index.js +++ b/stories/index.js @@ -123,7 +123,8 @@ storiesOf('Griddle main', module) super(); this.state = { - data: getRandomFakeData() + data: getRandomFakeData(), + sortProperties: {} }; } @@ -134,7 +135,15 @@ storiesOf('Griddle main', module) onSort = (sortProperties) => { console.log('onSort', sortProperties); - this.setState({ data: getRandomFakeData() }) + this.setState({ + data: getRandomFakeData(), + sortProperties: { + something: { + ...sortProperties, + sortAscending: getRandomIntInclusive(0,1) > 0 ? true : false + } + } + }) } onNext = () => { @@ -170,6 +179,7 @@ storiesOf('Griddle main', module) return } } From 9adf6e2a971c8f294341559fd0dcb267490f7fe8 Mon Sep 17 00:00:00 2001 From: Joel Lanciaux Date: Thu, 3 Nov 2016 01:45:02 -0400 Subject: [PATCH 7/7] Added default param values for updateState, added keys. --- src/actions/index.js | 4 ++-- src/components/PageDropdown.js | 2 +- src/components/Row.js | 1 + src/reducers/dataReducer.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index 2c3276f4..7b7cf347 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -63,9 +63,9 @@ export function setPageSize(pageSize) { } } -export function updateState(newState) { +export function updateState({ data = [], pageProperties = {}, sortProperties = {} }) { return { type: GRIDDLE_UPDATE_STATE, - newState + newState: { data, pageProperties, sortProperties } } } diff --git a/src/components/PageDropdown.js b/src/components/PageDropdown.js index 5fba94b5..851f02a9 100644 --- a/src/components/PageDropdown.js +++ b/src/components/PageDropdown.js @@ -30,7 +30,7 @@ class PageDropdown extends Component { > {getRange(maxPages) .map(num => ( - + ))} ); diff --git a/src/components/Row.js b/src/components/Row.js index 5c57e558..48648eb4 100644 --- a/src/components/Row.js +++ b/src/components/Row.js @@ -4,6 +4,7 @@ const Row = ({Cell, griddleKey, columnIds}) => ( { columnIds && columnIds.map(c => ( diff --git a/src/reducers/dataReducer.js b/src/reducers/dataReducer.js index afa88670..050c14f6 100644 --- a/src/reducers/dataReducer.js +++ b/src/reducers/dataReducer.js @@ -109,5 +109,5 @@ export function GRIDDLE_TOGGLE_COLUMN(state, action) { } export function GRIDDLE_UPDATE_STATE(state, action) { - return state.mergeDeep(action.newState);; + return state.mergeDeep(action.newState); }