diff --git a/package.json b/package.json
index 7715a2e4..6bac5680 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,8 @@
"typescript": "^2.4.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2",
- "webpack-fail-plugin": "^1.0.6"
+ "webpack-fail-plugin": "^1.0.6",
+ "redux-logger": "^3.0.6"
},
"dependencies": {
"immutable": "^3.8.1",
diff --git a/src/components/FilterContainer.js b/src/components/FilterContainer.js
deleted file mode 100644
index 7d46bc05..00000000
--- a/src/components/FilterContainer.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
-
-import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
-import { setFilter } from '../actions';
-
-const EnhancedFilter = OriginalComponent => connect((state, props) => ({
- className: classNamesForComponentSelector(state, 'Filter'),
- style: stylesForComponentSelector(state, 'Filter'),
-}), { setFilter })(props => );
-
-export default EnhancedFilter;
diff --git a/src/components/NextButtonContainer.js b/src/components/NextButtonContainer.js
deleted file mode 100644
index 278d9eb6..00000000
--- a/src/components/NextButtonContainer.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import { connect } from '../utils/griddleConnect';
-
-import { textSelector, hasNextSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
-
-const enhance = OriginalComponent => connect((state, props) => ({
- text: textSelector(state, { key: 'next' }),
- hasNext: hasNextSelector(state, props),
- className: classNamesForComponentSelector(state, 'NextButton'),
- style: stylesForComponentSelector(state, 'NextButton'),
-}))((props) => );
-
-export default enhance;
diff --git a/src/components/PreviousButtonContainer.js b/src/components/PreviousButtonContainer.js
deleted file mode 100644
index 628e227c..00000000
--- a/src/components/PreviousButtonContainer.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { connect } from '../utils/griddleConnect';
-import { textSelector, hasPreviousSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
-
-const enhance = OriginalComponent => connect((state, props) => ({
- text: textSelector(state, { key: 'previous' }),
- hasPrevious: hasPreviousSelector(state, props),
- className: classNamesForComponentSelector(state, 'PreviousButton'),
- style: stylesForComponentSelector(state, 'PreviousButton'),
-}))((props) => );
-
-export default enhance;
diff --git a/src/components/SettingsToggleContainer.js b/src/components/SettingsToggleContainer.js
deleted file mode 100644
index 16037ed3..00000000
--- a/src/components/SettingsToggleContainer.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { connect } from '../utils/griddleConnect';
-import compose from 'recompose/compose';
-import { textSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
-import { toggleSettings as toggleSettingsAction } from '../actions';
-
-const enhancedSettingsToggle = OriginalComponent => compose(
- connect((state, props) => ({
- text: textSelector(state, { key: 'settingsToggle' }),
- className: classNamesForComponentSelector(state, 'SettingsToggle'),
- style: stylesForComponentSelector(state, 'SettingsToggle'),
- }),
- {
- toggleSettings: toggleSettingsAction
- }
- ),
-)(props => );
-
-export default enhancedSettingsToggle;
diff --git a/src/components/SettingsWrapperContainer.js b/src/components/SettingsWrapperContainer.js
deleted file mode 100644
index 8475ad60..00000000
--- a/src/components/SettingsWrapperContainer.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
-import compose from 'recompose/compose';
-import mapProps from 'recompose/mapProps';
-import getContext from 'recompose/getContext';
-
-import { isSettingsEnabledSelector, isSettingsVisibleSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
-
-const EnhancedSettingsWrapper = OriginalComponent => compose(
- getContext({
- components: PropTypes.object,
- }),
- mapProps(props => ({
- Settings: props.components.Settings,
- SettingsToggle: props.components.SettingsToggle
- })),
- connect((state, props) => ({
- isEnabled: isSettingsEnabledSelector(state),
- isVisible: isSettingsVisibleSelector(state),
- className: classNamesForComponentSelector(state, 'SettingsWrapper'),
- style: stylesForComponentSelector(state, 'SettingsWrapper'),
- }))
-)(props => (
-
-));
-
-export default EnhancedSettingsWrapper;
diff --git a/src/components/TableContainer.js b/src/components/TableContainer.js
deleted file mode 100644
index 7c754851..00000000
--- a/src/components/TableContainer.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
-import compose from 'recompose/compose';
-import mapProps from 'recompose/mapProps';
-import getContext from 'recompose/getContext';
-
-import { classNamesForComponentSelector, stylesForComponentSelector, dataLoadingSelector, visibleRowCountSelector } from '../selectors/dataSelectors';
-
-const ComposedContainerComponent = OriginalComponent => compose(
- getContext(
- {
- components: PropTypes.object
- }),
- //TODO: Should we use withHandlers here instead? I realize that's not 100% the intent of that method
- mapProps(props => ({
- TableHeading: props.components.TableHeading,
- TableBody: props.components.TableBody,
- Loading: props.components.Loading,
- NoResults: props.components.NoResults,
- })),
- connect(
- (state, props) => ({
- dataLoading: dataLoadingSelector(state),
- visibleRows: visibleRowCountSelector(state),
- className: classNamesForComponentSelector(state, 'Table'),
- style: stylesForComponentSelector(state, 'Table'),
- })
- ),
-)(props => );
-
-export default ComposedContainerComponent;
diff --git a/src/index.js b/src/index.js
index a898eead..cd8724e1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,55 +5,26 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
-import * as dataReducers from './reducers/dataReducer';
-import components from './components';
+//import * as dataReducers from './reducers/dataReducer';
+//import components from './components';
import settingsComponentObjects from './settingsComponentObjects';
-import * as selectors from './selectors/dataSelectors';
+//import * as selectors from './selectors/dataSelectors';
import { buildGriddleReducer, buildGriddleComponents } from './utils/compositionUtils';
-import { getColumnProperties } from './utils/columnUtils';
-import { getRowProperties } from './utils/rowUtils';
+//import { getColumnProperties } from './utils/columnUtils';
+//import { getRowProperties } from './utils/rowUtils';
import { setSortProperties } from './utils/sortUtils';
import { StoreListener } from './utils/listenerUtils';
-import * as actions from './actions';
-
-const defaultEvents = {
- ...actions,
- onFilter: actions.setFilter,
- setSortProperties
-};
-
-
-const defaultStyleConfig = {
- icons: {
- TableHeadingCell: {
- sortDescendingIcon: '▼',
- sortAscendingIcon: '▲'
- },
- },
- classNames: {
- Cell: 'griddle-cell',
- Filter: 'griddle-filter',
- Loading: 'griddle-loadingResults',
- NextButton: 'griddle-next-button',
- NoResults: 'griddle-noResults',
- PageDropdown: 'griddle-page-select',
- Pagination: 'griddle-pagination',
- PreviousButton: 'griddle-previous-button',
- Row: 'griddle-row',
- RowDefinition: 'griddle-row-definition',
- Settings: 'griddle-settings',
- SettingsToggle: 'griddle-settings-toggle',
- Table: 'griddle-table',
- TableBody: 'griddle-table-body',
- TableHeading: 'griddle-table-heading',
- TableHeadingCell: 'griddle-table-heading-cell',
- TableHeadingCellAscending: 'griddle-heading-ascending',
- TableHeadingCellDescending: 'griddle-heading-descending',
- },
- styles: {
- }
-};
+import { composeSelectors } from './utils/selectorUtils';
+//import * as actions from './actions';
+
+import CorePlugin from './plugins/core';
+
+//const defaultEvents = {
+// ...actions,
+// onFilter: actions.setFilter,
+// setSortProperties
+//};
class Griddle extends Component {
static childContextTypes = {
@@ -62,13 +33,15 @@ class Griddle extends Component {
events: PropTypes.object,
selectors: PropTypes.object,
storeKey: PropTypes.string,
- storeListener: PropTypes.object
+ storeListener: PropTypes.object,
+ actions: PropTypes.object,
}
constructor(props) {
super(props);
const {
+ baselinePlugin=CorePlugin,
plugins=[],
data,
children:rowPropertiesComponent,
@@ -85,23 +58,40 @@ class Griddle extends Component {
...userInitialState
} = props;
- const rowProperties = getRowProperties(rowPropertiesComponent);
- const columnProperties = getColumnProperties(rowPropertiesComponent);
+ switch(typeof baselinePlugin) {
+ case 'function':
+ plugins.unshift(baselinePlugin(props));
+ break;
+ case 'object':
+ plugins.unshift(baselinePlugin);
+ break;
+ };
+
+ this.plugins = plugins;
//Combine / compose the reducers to make a single, unified reducer
- const reducers = buildGriddleReducer([dataReducers, ...plugins.map(p => p.reducer)]);
+ //const reducers = buildGriddleReducer([dataReducers, ...plugins.map(p => p.reducer)]);
+ const reducers = buildGriddleReducer([...plugins.map(p => p.reducer)]);
//Combine / Compose the components to make a single component for each component type
- this.components = buildGriddleComponents([components, ...plugins.map(p => p.components), userComponents]);
+ //this.components = buildGriddleComponents([components, ...plugins.map(p => p.components), userComponents]);
+ this.components = buildGriddleComponents([...plugins.map(p => p.components), userComponents]);
- this.settingsComponentObjects = Object.assign({}, settingsComponentObjects, ...plugins.map(p => p.settingsComponentObjects), userSettingsComponentObjects);
+ // NOTE this goes on the context which for the purposes of breaking out the
+ // 'core' code into a plugin is somewhat of a problem as it should
+ // be associated with the core code not general griddle code.
+ this.settingsComponentObjects = Object.assign({}, ...plugins.map(p => p.settingsComponentObjects), userSettingsComponentObjects);
this.events = Object.assign({}, events, ...plugins.map(p => p.events));
- this.selectors = plugins.reduce((combined, plugin) => ({ ...combined, ...plugin.selectors }), {...selectors});
+ this.selectors = composeSelectors(plugins);
+
+ this.actions = plugins.reduce((combined, plugin) => ({ ...combined, ...plugin.actions }), {});
- const mergedStyleConfig = _.merge({}, defaultStyleConfig, ...plugins.map(p => p.styleConfig), styleConfig);
+ const mergedStyleConfig = _.merge({}, ...plugins.map(p => p.styleConfig), styleConfig);
+ // this would be good to move into the core plugin
+ // and namespace this state to the core plugin
const pageProperties = Object.assign({}, {
currentPage: 1,
pageSize: 10
@@ -110,21 +100,15 @@ class Griddle extends Component {
);
//TODO: This should also look at the default and plugin initial state objects
- const renderProperties = Object.assign({
- rowProperties,
- columnProperties
- }, ...plugins.map(p => p.renderProperties), userRenderProperties);
+ const renderProperties = Object.assign(...plugins.map(p => p.renderProperties), userRenderProperties);
// TODO: Make this its own method
+ // It would be nice if state was namespaced to the plugin
+ // it was associated with. For example pageProperties and
+ // sortProperties are specific to the core plugin. We could
+ // refactor the selectors to grab this data from a different
+ // place but would this affect other users?
const initialState = _.merge(
- {
- enableSettings: true,
- textProperties: {
- next: 'Next',
- previous: 'Previous',
- settingsToggle: 'Settings'
- },
- },
...plugins.map(p => p.initialState),
userInitialState,
{
@@ -161,8 +145,12 @@ class Griddle extends Component {
})
// Only update the state if something has changed.
+ //
+ // NOTE the update state reducer in 'core' griddle is only
+ // concerned with the data, pageProperties, and sortProperties
+ // passing in only changed props breaks the contract it is expecting
if (Object.keys(newState).length > 0) {
- this.store.dispatch(actions.updateState(newState));
+ this.store.dispatch(this.plugins[0].actions.updateState(newState));
}
}
@@ -183,7 +171,8 @@ class Griddle extends Component {
events: this.events,
selectors: this.selectors,
storeKey: this.getStoreKey(),
- storeListener: this.storeListener
+ storeListener: this.storeListener,
+ actions: this.actions,
};
}
diff --git a/src/module.d.ts b/src/module.d.ts
index b25f4af4..019f6874 100644
--- a/src/module.d.ts
+++ b/src/module.d.ts
@@ -425,6 +425,7 @@ export namespace utils {
const compositionUtils: PropertyBag;
const dataUtils: PropertyBag;
const rowUtils: PropertyBag;
+ const selectorUtils: PropertyBag;
const connect : typeof originalConnect;
diff --git a/src/module.js b/src/module.js
index a5d36a0c..e429616d 100644
--- a/src/module.js
+++ b/src/module.js
@@ -1,9 +1,9 @@
import Griddle from './index';
-import * as actions from './actions';
-import components from './components';
-import * as constants from './constants';
-import * as selectors from './selectors/dataSelectors';
+import * as actions from './plugins/core/actions';
+import components from './plugins/core/components';
+import * as constants from './plugins/core/constants';
+import * as selectors from './plugins/core/selectors/dataSelectors';
import settingsComponentObjects from './settingsComponentObjects';
import utils from './utils';
@@ -14,7 +14,7 @@ import PositionPlugin from './plugins/position';
const plugins = {
LegacyStylePlugin,
LocalPlugin,
- PositionPlugin,
+ PositionPlugin
};
const ColumnDefinition = components.ColumnDefinition;
diff --git a/src/actions/index.js b/src/plugins/core/actions/index.js
similarity index 100%
rename from src/actions/index.js
rename to src/plugins/core/actions/index.js
diff --git a/src/components/Cell.js b/src/plugins/core/components/Cell.js
similarity index 100%
rename from src/components/Cell.js
rename to src/plugins/core/components/Cell.js
diff --git a/src/components/CellContainer.js b/src/plugins/core/components/CellContainer.js
similarity index 67%
rename from src/components/CellContainer.js
rename to src/plugins/core/components/CellContainer.js
index b4eaf5ed..5ac8180a 100644
--- a/src/components/CellContainer.js
+++ b/src/plugins/core/components/CellContainer.js
@@ -1,18 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import getContext from 'recompose/getContext';
import mapProps from 'recompose/mapProps';
import compose from 'recompose/compose';
-import {
- customComponentSelector,
- cellValueSelector,
- cellPropertiesSelectorFactory,
- classNamesForComponentSelector,
- stylesForComponentSelector
-} from '../selectors/dataSelectors';
-import { valueOrResult } from '../utils/valueUtils';
+// TODO: refactor this onto the context. We need to handle factories as well
+import { cellPropertiesSelectorFactory } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
+import { valueOrResult } from '../../../utils/valueUtils';
function hasWidthOrStyles(cellProperties) {
return cellProperties.hasOwnProperty('width') || cellProperties.hasOwnProperty('styles');
@@ -36,19 +31,23 @@ function getCellStyles(cellProperties, originalStyles) {
}
const mapStateToProps = () => {
+ // TODO: selector factories on the context
const cellPropertiesSelector = cellPropertiesSelectorFactory();
return (state, props) => {
return {
- value: cellValueSelector(state, props),
- customComponent: customComponentSelector(state, props),
- cellProperties: cellPropertiesSelector(state, props),
- className: classNamesForComponentSelector(state, 'Cell'),
- style: stylesForComponentSelector(state, 'Cell'),
+ value: props.selectors.cellValueSelector(state, props),
+ customComponent: props.selectors.customComponentSelector(state, props),
+ cellProperties: props.selectors.cellPropertiesSelector(state, props),
+ className: props.selectors.classNamesForComponentSelector(state, 'Cell'),
+ style: props.selectors.stylesForComponentSelector(state, 'Cell'),
};
};
}
const ComposedCellContainer = OriginalComponent => compose(
+ getContext({
+ selectors: PropTypes.object
+ }),
connect(mapStateToProps),
mapProps(props => {
return ({
diff --git a/src/components/ColumnDefinition.js b/src/plugins/core/components/ColumnDefinition.js
similarity index 100%
rename from src/components/ColumnDefinition.js
rename to src/plugins/core/components/ColumnDefinition.js
diff --git a/src/components/Filter.js b/src/plugins/core/components/Filter.js
similarity index 100%
rename from src/components/Filter.js
rename to src/plugins/core/components/Filter.js
diff --git a/src/plugins/core/components/FilterContainer.js b/src/plugins/core/components/FilterContainer.js
new file mode 100644
index 00000000..297fd25a
--- /dev/null
+++ b/src/plugins/core/components/FilterContainer.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import compose from 'recompose/compose';
+import getContext from 'recompose/getContext';
+
+import { connect } from '../../../utils/griddleConnect';
+
+const EnhancedFilter = OriginalComponent => compose(
+ getContext({
+ selectors: PropTypes.object,
+ actions: PropTypes.object,
+ }),
+ connect(
+ (state, props) => ({
+ className: props.selectors.classNamesForComponentSelector(state, 'Filter'),
+ style: props.selectors.stylesForComponentSelector(state, 'Filter'),
+ }),
+ (dispatch, props) => ({
+ setFilter: (filter) => dispatch(props.actions.setFilter(filter)),
+ })
+ )
+)(props => );
+
+export default EnhancedFilter;
diff --git a/src/components/FilterEnhancer.js b/src/plugins/core/components/FilterEnhancer.js
similarity index 88%
rename from src/components/FilterEnhancer.js
rename to src/plugins/core/components/FilterEnhancer.js
index 7ca9dad0..1d297f12 100644
--- a/src/components/FilterEnhancer.js
+++ b/src/plugins/core/components/FilterEnhancer.js
@@ -4,7 +4,7 @@ import getContext from 'recompose/getContext';
import mapProps from 'recompose/mapProps';
import compose from 'recompose/compose';
-import { combineHandlers } from '../utils/compositionUtils';
+import { combineHandlers } from '../../../utils/compositionUtils';
const EnhancedFilter = OriginalComponent => compose(
getContext({
diff --git a/src/components/Layout.js b/src/plugins/core/components/Layout.js
similarity index 100%
rename from src/components/Layout.js
rename to src/plugins/core/components/Layout.js
diff --git a/src/components/LayoutContainer.js b/src/plugins/core/components/LayoutContainer.js
similarity index 72%
rename from src/components/LayoutContainer.js
rename to src/plugins/core/components/LayoutContainer.js
index e44570b6..044d5ff5 100644
--- a/src/components/LayoutContainer.js
+++ b/src/plugins/core/components/LayoutContainer.js
@@ -1,20 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import getContext from 'recompose/getContext';
import mapProps from 'recompose/mapProps';
import compose from 'recompose/compose';
-import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
const EnhancedLayout = OriginalComponent => compose(
getContext({
components: PropTypes.object,
+ selectors: PropTypes.object
}),
connect(
(state, props) => ({
- className: classNamesForComponentSelector(state, 'Layout'),
- style: stylesForComponentSelector(state, 'Layout'),
+ className: props.selectors.classNamesForComponentSelector(state, 'Layout'),
+ style: props.selectors.stylesForComponentSelector(state, 'Layout'),
})
),
mapProps( props => ({
diff --git a/src/components/Loading.js b/src/plugins/core/components/Loading.js
similarity index 100%
rename from src/components/Loading.js
rename to src/plugins/core/components/Loading.js
diff --git a/src/components/LoadingContainer.js b/src/plugins/core/components/LoadingContainer.js
similarity index 61%
rename from src/components/LoadingContainer.js
rename to src/plugins/core/components/LoadingContainer.js
index 3c17b6d0..16f6b0c3 100644
--- a/src/components/LoadingContainer.js
+++ b/src/plugins/core/components/LoadingContainer.js
@@ -1,19 +1,19 @@
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
const LoadingContainer = compose(
getContext({
components: PropTypes.object,
+ selectors: PropTypes.object,
}),
connect(
- state => ({
- className: classNamesForComponentSelector(state, 'Loading'),
- style: stylesForComponentSelector(state, 'Loading'),
+ (state, props) => ({
+ className: props.selectors.classNamesForComponentSelector(state, 'Loading'),
+ style: props.selectors.stylesForComponentSelector(state, 'Loading'),
})
),
mapProps((props) => {
diff --git a/src/components/NextButton.js b/src/plugins/core/components/NextButton.js
similarity index 100%
rename from src/components/NextButton.js
rename to src/plugins/core/components/NextButton.js
diff --git a/src/plugins/core/components/NextButtonContainer.js b/src/plugins/core/components/NextButtonContainer.js
new file mode 100644
index 00000000..34bb6d29
--- /dev/null
+++ b/src/plugins/core/components/NextButtonContainer.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import compose from 'recompose/compose';
+import getContext from 'recompose/getContext';
+
+import { connect } from '../../../utils/griddleConnect';
+
+const enhance = OriginalComponent => compose(
+ getContext({
+ selectors: PropTypes.object
+ }),
+ connect((state, props) => ({
+ text: props.selectors.textSelector(state, { key: 'next' }),
+ hasNext: props.selectors.hasNextSelector(state, props),
+ className: props.selectors.classNamesForComponentSelector(state, 'NextButton'),
+ style: props.selectors.stylesForComponentSelector(state, 'NextButton'),
+ }))
+)((props) => );
+
+export default enhance;
diff --git a/src/components/NextButtonEnhancer.js b/src/plugins/core/components/NextButtonEnhancer.js
similarity index 87%
rename from src/components/NextButtonEnhancer.js
rename to src/plugins/core/components/NextButtonEnhancer.js
index 46ae444f..c0ef0ada 100644
--- a/src/components/NextButtonEnhancer.js
+++ b/src/plugins/core/components/NextButtonEnhancer.js
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { combineHandlers } from '../utils/compositionUtils';
+
+import { combineHandlers } from '../../../utils/compositionUtils';
const enhance = OriginalComponent => compose(
getContext({
diff --git a/src/components/NoResults.js b/src/plugins/core/components/NoResults.js
similarity index 100%
rename from src/components/NoResults.js
rename to src/plugins/core/components/NoResults.js
diff --git a/src/components/NoResultsContainer.js b/src/plugins/core/components/NoResultsContainer.js
similarity index 65%
rename from src/components/NoResultsContainer.js
rename to src/plugins/core/components/NoResultsContainer.js
index 2496b0e5..a7517060 100644
--- a/src/components/NoResultsContainer.js
+++ b/src/plugins/core/components/NoResultsContainer.js
@@ -1,20 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
const NoResultsContainer = OriginalComponent => compose(
getContext({
components: PropTypes.object,
+ selectors: PropTypes.object
}),
connect(
- state => ({
- className: classNamesForComponentSelector(state, 'NoResults'),
- style: stylesForComponentSelector(state, 'NoResults'),
+ (state, props) => ({
+ className: props.selectors.classNamesForComponentSelector(state, 'NoResults'),
+ style: props.selectors.stylesForComponentSelector(state, 'NoResults'),
})
),
mapProps((props) => {
diff --git a/src/components/PageDropdown.js b/src/plugins/core/components/PageDropdown.js
similarity index 100%
rename from src/components/PageDropdown.js
rename to src/plugins/core/components/PageDropdown.js
diff --git a/src/components/PageDropdownContainer.js b/src/plugins/core/components/PageDropdownContainer.js
similarity index 50%
rename from src/components/PageDropdownContainer.js
rename to src/plugins/core/components/PageDropdownContainer.js
index 88b2bd8c..cef5e830 100644
--- a/src/components/PageDropdownContainer.js
+++ b/src/plugins/core/components/PageDropdownContainer.js
@@ -1,21 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { currentPageSelector, maxPageSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+
+import { connect } from '../../../utils/griddleConnect';
const enhance = OriginalComponent => compose(
getContext({
events: PropTypes.object,
+ selectors: PropTypes.object
}),
- connect((state, props) => ({
- maxPages: maxPageSelector(state, props),
- currentPage: currentPageSelector(state, props),
- className: classNamesForComponentSelector(state, 'PageDropdown'),
- style: stylesForComponentSelector(state, 'PageDropdown'),
- })),
+ connect(
+ (state, props) => ({
+ maxPages: props.selectors.maxPageSelector(state, props),
+ currentPage: props.selectors.currentPageSelector(state, props),
+ className: props.selectors.classNamesForComponentSelector(state, 'PageDropdown'),
+ style: props.selectors.stylesForComponentSelector(state, 'PageDropdown'),
+ })
+ ),
mapProps(({ events: { onGetPage: setPage }, ...props }) => ({
...props,
setPage,
diff --git a/src/components/Pagination.js b/src/plugins/core/components/Pagination.js
similarity index 100%
rename from src/components/Pagination.js
rename to src/plugins/core/components/Pagination.js
diff --git a/src/components/PaginationContainer.js b/src/plugins/core/components/PaginationContainer.js
similarity index 70%
rename from src/components/PaginationContainer.js
rename to src/plugins/core/components/PaginationContainer.js
index c82b570f..fc351754 100644
--- a/src/components/PaginationContainer.js
+++ b/src/plugins/core/components/PaginationContainer.js
@@ -1,20 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
const EnhancedPaginationContainer = OriginalComponent => compose(
getContext({
components: PropTypes.object,
+ selectors: PropTypes.object
}),
connect(
(state, props) => ({
- className: classNamesForComponentSelector(state, 'Pagination'),
- style: stylesForComponentSelector(state, 'Pagination'),
+ className: props.selectors.classNamesForComponentSelector(state, 'Pagination'),
+ style: props.selectors.stylesForComponentSelector(state, 'Pagination'),
})
),
mapProps((props) => {
diff --git a/src/components/PreviousButton.js b/src/plugins/core/components/PreviousButton.js
similarity index 100%
rename from src/components/PreviousButton.js
rename to src/plugins/core/components/PreviousButton.js
diff --git a/src/plugins/core/components/PreviousButtonContainer.js b/src/plugins/core/components/PreviousButtonContainer.js
new file mode 100644
index 00000000..f3600eee
--- /dev/null
+++ b/src/plugins/core/components/PreviousButtonContainer.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import compose from 'recompose/compose';
+import getContext from 'recompose/getContext';
+
+import { connect } from '../../../utils/griddleConnect';
+
+const enhance = OriginalComponent => compose(
+ getContext({
+ selectors: PropTypes.object
+ }),
+ connect(
+ (state, props) => ({
+ text: props.selectors.textSelector(state, { key: 'previous' }),
+ hasPrevious: props.selectors.hasPreviousSelector(state, props),
+ className: props.selectors.classNamesForComponentSelector(state, 'PreviousButton'),
+ style: props.selectors.stylesForComponentSelector(state, 'PreviousButton'),
+ })
+ )
+)((props) => );
+
+export default enhance;
diff --git a/src/components/PreviousButtonEnhancer.js b/src/plugins/core/components/PreviousButtonEnhancer.js
similarity index 88%
rename from src/components/PreviousButtonEnhancer.js
rename to src/plugins/core/components/PreviousButtonEnhancer.js
index 0de9ae02..19e25f14 100644
--- a/src/components/PreviousButtonEnhancer.js
+++ b/src/plugins/core/components/PreviousButtonEnhancer.js
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { combineHandlers } from '../utils/compositionUtils';
+
+import { combineHandlers } from '../../../utils/compositionUtils';
const enhance = OriginalComponent => compose(
getContext({
diff --git a/src/components/Row.js b/src/plugins/core/components/Row.js
similarity index 100%
rename from src/components/Row.js
rename to src/plugins/core/components/Row.js
diff --git a/src/components/RowContainer.js b/src/plugins/core/components/RowContainer.js
similarity index 52%
rename from src/components/RowContainer.js
rename to src/plugins/core/components/RowContainer.js
index 5006a78f..5930f860 100644
--- a/src/components/RowContainer.js
+++ b/src/plugins/core/components/RowContainer.js
@@ -1,30 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import {
- columnIdsSelector,
- rowDataSelector,
- rowPropertiesSelector,
- classNamesForComponentSelector,
- stylesForComponentSelector,
-} from '../selectors/dataSelectors';
-import { valueOrResult } from '../utils/valueUtils';
+import { connect } from '../../../utils/griddleConnect';
+import { valueOrResult } from '../../../utils/valueUtils';
const ComposedRowContainer = OriginalComponent => compose(
getContext({
components: PropTypes.object,
+ selectors: PropTypes.object
}),
- connect((state, props) => ({
- columnIds: columnIdsSelector(state),
- rowProperties: rowPropertiesSelector(state),
- rowData: rowDataSelector(state, props),
- className: classNamesForComponentSelector(state, 'Row'),
- style: stylesForComponentSelector(state, 'Row'),
- })),
+ connect(
+ (state, props) => ({
+ columnIds: props.selectors.columnIdsSelector(state),
+ rowProperties: props.selectors.rowPropertiesSelector(state),
+ rowData: props.selectors.rowDataSelector(state, props),
+ className: props.selectors.classNamesForComponentSelector(state, 'Row'),
+ style: props.selectors.stylesForComponentSelector(state, 'Row'),
+ })
+ ),
mapProps(props => {
const { components, rowProperties, className, ...otherProps } = props;
return {
@@ -35,7 +31,7 @@ const ComposedRowContainer = OriginalComponent => compose(
}),
)(props => (
));
diff --git a/src/components/RowDefinition.js b/src/plugins/core/components/RowDefinition.js
similarity index 100%
rename from src/components/RowDefinition.js
rename to src/plugins/core/components/RowDefinition.js
diff --git a/src/components/Settings.js b/src/plugins/core/components/Settings.js
similarity index 100%
rename from src/components/Settings.js
rename to src/plugins/core/components/Settings.js
diff --git a/src/components/SettingsContainer.js b/src/plugins/core/components/SettingsContainer.js
similarity index 81%
rename from src/components/SettingsContainer.js
rename to src/plugins/core/components/SettingsContainer.js
index c88801df..04b6c342 100644
--- a/src/components/SettingsContainer.js
+++ b/src/plugins/core/components/SettingsContainer.js
@@ -1,11 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
function getSettingsComponentsArrayFromObject(settingsObject, settingsComponents) {
//TODO: determine if we need to make this faster
@@ -20,12 +19,13 @@ function getSettingsComponentsArrayFromObject(settingsObject, settingsComponents
const EnhancedSettings = OriginalComponent => compose(
getContext({
components: PropTypes.object,
+ selectors: PropTypes.object,
settingsComponentObjects: PropTypes.object
}),
connect(
(state, props) => ({
- className: classNamesForComponentSelector(state, 'Settings'),
- style: stylesForComponentSelector(state, 'Settings'),
+ className: props.selectors.classNamesForComponentSelector(state, 'Settings'),
+ style: props.selectors.stylesForComponentSelector(state, 'Settings'),
})
),
mapProps(props => {
diff --git a/src/components/SettingsToggle.js b/src/plugins/core/components/SettingsToggle.js
similarity index 100%
rename from src/components/SettingsToggle.js
rename to src/plugins/core/components/SettingsToggle.js
diff --git a/src/plugins/core/components/SettingsToggleContainer.js b/src/plugins/core/components/SettingsToggleContainer.js
new file mode 100644
index 00000000..3c99b00a
--- /dev/null
+++ b/src/plugins/core/components/SettingsToggleContainer.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import compose from 'recompose/compose';
+import getContext from 'recompose/getContext';
+
+import { connect } from '../../../utils/griddleConnect';
+
+const enhancedSettingsToggle = OriginalComponent => compose(
+ getContext({
+ selectors: PropTypes.object,
+ actions: PropTypes.object,
+ }),
+ connect(
+ (state, props) => ({
+ text: props.selectors.textSelector(state, { key: 'settingsToggle' }),
+ className: props.selectors.classNamesForComponentSelector(state, 'SettingsToggle'),
+ style: props.selectors.stylesForComponentSelector(state, 'SettingsToggle'),
+ }),
+ (dispatch, props) => ({
+ toggleSettings: () => dispatch(props.actions.toggleSettings())
+ })
+ ),
+)(props => );
+
+export default enhancedSettingsToggle;
diff --git a/src/components/SettingsWrapper.js b/src/plugins/core/components/SettingsWrapper.js
similarity index 100%
rename from src/components/SettingsWrapper.js
rename to src/plugins/core/components/SettingsWrapper.js
diff --git a/src/plugins/core/components/SettingsWrapperContainer.js b/src/plugins/core/components/SettingsWrapperContainer.js
new file mode 100644
index 00000000..e84ba06f
--- /dev/null
+++ b/src/plugins/core/components/SettingsWrapperContainer.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import compose from 'recompose/compose';
+import mapProps from 'recompose/mapProps';
+import getContext from 'recompose/getContext';
+
+import { connect } from '../../../utils/griddleConnect';
+
+const EnhancedSettingsWrapper = OriginalComponent => compose(
+ getContext({
+ components: PropTypes.object,
+ selectors: PropTypes.object,
+ }),
+ connect(
+ (state, props) => ({
+ isEnabled: props.selectors.isSettingsEnabledSelector(state),
+ isVisible: props.selectors.isSettingsVisibleSelector(state),
+ className: props.selectors.classNamesForComponentSelector(state, 'SettingsWrapper'),
+ style: props.selectors.stylesForComponentSelector(state, 'SettingsWrapper'),
+ })
+ ),
+ mapProps(props => {
+ const { components, ...otherProps } = props;
+ return {
+ Settings: components.Settings,
+ SettingsToggle: components.SettingsToggle,
+ ...otherProps
+ }
+ })
+)(props => (
+
+));
+
+export default EnhancedSettingsWrapper;
diff --git a/src/components/Table.js b/src/plugins/core/components/Table.js
similarity index 100%
rename from src/components/Table.js
rename to src/plugins/core/components/Table.js
diff --git a/src/components/TableBody.js b/src/plugins/core/components/TableBody.js
similarity index 100%
rename from src/components/TableBody.js
rename to src/plugins/core/components/TableBody.js
diff --git a/src/components/TableBodyContainer.js b/src/plugins/core/components/TableBodyContainer.js
similarity index 52%
rename from src/components/TableBodyContainer.js
rename to src/plugins/core/components/TableBodyContainer.js
index 590aae9b..98cf7447 100644
--- a/src/components/TableBodyContainer.js
+++ b/src/plugins/core/components/TableBodyContainer.js
@@ -1,24 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { visibleRowIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
const ComposedTableBodyContainer = OriginalComponent => compose(
getContext({
components: PropTypes.object,
selectors: PropTypes.object,
}),
- connect((state, props) => ({
- visibleRowIds: visibleRowIdsSelector(state),
- className: classNamesForComponentSelector(state, 'TableBody'),
- style: stylesForComponentSelector(state, 'TableBody'),
- })),
+ connect(
+ (state, props) => ({
+ visibleRowIds: props.selectors.visibleRowIdsSelector(state),
+ className: props.selectors.classNamesForComponentSelector(state, 'TableBody'),
+ style: props.selectors.stylesForComponentSelector(state, 'TableBody'),
+ })
+ ),
mapProps(props => {
- const { components, ...otherProps } = props;
+ const { components, selectors, ...otherProps } = props;
return {
Row: props.components.Row,
...otherProps,
@@ -26,10 +27,10 @@ const ComposedTableBodyContainer = OriginalComponent => compose(
}),
)(({Row, visibleRowIds, style, className}) => (
));
diff --git a/src/plugins/core/components/TableContainer.js b/src/plugins/core/components/TableContainer.js
new file mode 100644
index 00000000..19a4d2f1
--- /dev/null
+++ b/src/plugins/core/components/TableContainer.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import compose from 'recompose/compose';
+import mapProps from 'recompose/mapProps';
+import getContext from 'recompose/getContext';
+
+import { connect } from '../../../utils/griddleConnect';
+
+const ComposedContainerComponent = OriginalComponent => compose(
+ getContext({
+ components: PropTypes.object,
+ selectors: PropTypes.object
+ }),
+ //TODO: Should we use withHandlers here instead? I realize that's not 100% the intent of that method
+ connect(
+ (state, props) => ({
+ dataLoading: props.selectors.dataLoadingSelector(state),
+ visibleRows: props.selectors.visibleRowCountSelector(state),
+ className: props.selectors.classNamesForComponentSelector(state, 'Table'),
+ style: props.selectors.stylesForComponentSelector(state, 'Table'),
+ })
+ ),
+ //TODO: Should we use withHandlers here instead? I realize that's not 100% the intent of that method
+ mapProps(props => {
+ const { components, dataLoading, visibleRows, className, style } = props;
+ return {
+ TableHeading: components.TableHeading,
+ TableBody: components.TableBody,
+ Loading: components.Loading,
+ NoResults: components.NoResults,
+ dataLoading,
+ visibleRows,
+ className,
+ style
+ }
+ })
+)(props => );
+
+export default ComposedContainerComponent;
diff --git a/src/components/TableHeading.js b/src/plugins/core/components/TableHeading.js
similarity index 100%
rename from src/components/TableHeading.js
rename to src/plugins/core/components/TableHeading.js
diff --git a/src/components/TableHeadingCell.js b/src/plugins/core/components/TableHeadingCell.js
similarity index 100%
rename from src/components/TableHeadingCell.js
rename to src/plugins/core/components/TableHeadingCell.js
diff --git a/src/components/TableHeadingCellContainer.js b/src/plugins/core/components/TableHeadingCellContainer.js
similarity index 62%
rename from src/components/TableHeadingCellContainer.js
rename to src/plugins/core/components/TableHeadingCellContainer.js
index 02121426..70d8018e 100644
--- a/src/components/TableHeadingCellContainer.js
+++ b/src/plugins/core/components/TableHeadingCellContainer.js
@@ -1,13 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { sortPropertyByIdSelector, iconsForComponentSelector, classNamesForComponentSelector, stylesForComponentSelector, customHeadingComponentSelector, cellPropertiesSelector } from '../selectors/dataSelectors';
-import { getSortIconProps } from '../utils/sortUtils';
-import { valueOrResult } from '../utils/valueUtils';
+import { connect } from '../../../utils/griddleConnect';
+import { getSortIconProps } from '../../../utils/sortUtils';
+import { valueOrResult } from '../../../utils/valueUtils';
const DefaultTableHeadingCellContent = ({title, icon}) => (
@@ -22,12 +21,12 @@ const EnhancedHeadingCell = OriginalComponent => compose(
}),
connect(
(state, props) => ({
- sortProperty: sortPropertyByIdSelector(state, props),
- customHeadingComponent: customHeadingComponentSelector(state, props),
- cellProperties: cellPropertiesSelector(state, props),
- className: classNamesForComponentSelector(state, 'TableHeadingCell'),
- style: stylesForComponentSelector(state, 'TableHeadingCell'),
- ...iconsForComponentSelector(state, 'TableHeadingCell'),
+ sortProperty: props.selectors.sortPropertyByIdSelector(state, props),
+ customHeadingComponent: props.selectors.customHeadingComponentSelector(state, props),
+ cellProperties: props.selectors.cellPropertiesSelector(state, props),
+ className: props.selectors.classNamesForComponentSelector(state, 'TableHeadingCell'),
+ style: props.selectors.stylesForComponentSelector(state, 'TableHeadingCell'),
+ ...props.selectors.iconsForComponentSelector(state, 'TableHeadingCell'),
})
),
mapProps(props => {
diff --git a/src/components/TableHeadingCellEnhancer.js b/src/plugins/core/components/TableHeadingCellEnhancer.js
similarity index 89%
rename from src/components/TableHeadingCellEnhancer.js
rename to src/plugins/core/components/TableHeadingCellEnhancer.js
index 3ee6c757..c9c47f0b 100644
--- a/src/components/TableHeadingCellEnhancer.js
+++ b/src/plugins/core/components/TableHeadingCellEnhancer.js
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { combineHandlers } from '../utils/compositionUtils';
+
+import { combineHandlers } from '../../../utils/compositionUtils';
const EnhancedHeadingCell = OriginalComponent => compose(
getContext({
diff --git a/src/components/TableHeadingContainer.js b/src/plugins/core/components/TableHeadingContainer.js
similarity index 61%
rename from src/components/TableHeadingContainer.js
rename to src/plugins/core/components/TableHeadingContainer.js
index a7bf1377..1b9feede 100644
--- a/src/components/TableHeadingContainer.js
+++ b/src/plugins/core/components/TableHeadingContainer.js
@@ -1,11 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
-import { columnTitlesSelector, columnIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
+import { connect } from '../../../utils/griddleConnect';
const ComposedContainerComponent = OriginalComponent => compose(
getContext({
@@ -13,10 +12,10 @@ const ComposedContainerComponent = OriginalComponent => compose(
selectors: PropTypes.object,
}),
connect((state, props) => ({
- columnTitles: columnTitlesSelector(state),
- columnIds: columnIdsSelector(state),
- className: classNamesForComponentSelector(state, 'TableHeading'),
- style: stylesForComponentSelector(state, 'TableHeading'),
+ columnTitles: props.selectors.columnTitlesSelector(state),
+ columnIds: props.selectors.columnIdsSelector(state),
+ className: props.selectors.classNamesForComponentSelector(state, 'TableHeading'),
+ style: props.selectors.stylesForComponentSelector(state, 'TableHeading'),
})),
mapProps(props => {
const { components, ...otherProps } = props;
diff --git a/src/components/Test.js b/src/plugins/core/components/Test.js
similarity index 100%
rename from src/components/Test.js
rename to src/plugins/core/components/Test.js
diff --git a/src/components/__tests__/CellTest.js b/src/plugins/core/components/__tests__/CellTest.js
similarity index 100%
rename from src/components/__tests__/CellTest.js
rename to src/plugins/core/components/__tests__/CellTest.js
diff --git a/src/components/__tests__/FilterTest.js b/src/plugins/core/components/__tests__/FilterTest.js
similarity index 100%
rename from src/components/__tests__/FilterTest.js
rename to src/plugins/core/components/__tests__/FilterTest.js
diff --git a/src/components/__tests__/NextButtonTest.js b/src/plugins/core/components/__tests__/NextButtonTest.js
similarity index 100%
rename from src/components/__tests__/NextButtonTest.js
rename to src/plugins/core/components/__tests__/NextButtonTest.js
diff --git a/src/components/__tests__/PageDropdownTest.js b/src/plugins/core/components/__tests__/PageDropdownTest.js
similarity index 100%
rename from src/components/__tests__/PageDropdownTest.js
rename to src/plugins/core/components/__tests__/PageDropdownTest.js
diff --git a/src/components/__tests__/PaginationTest.js b/src/plugins/core/components/__tests__/PaginationTest.js
similarity index 100%
rename from src/components/__tests__/PaginationTest.js
rename to src/plugins/core/components/__tests__/PaginationTest.js
diff --git a/src/components/__tests__/PreviousButtonTest.js b/src/plugins/core/components/__tests__/PreviousButtonTest.js
similarity index 100%
rename from src/components/__tests__/PreviousButtonTest.js
rename to src/plugins/core/components/__tests__/PreviousButtonTest.js
diff --git a/src/components/__tests__/RowTest.js b/src/plugins/core/components/__tests__/RowTest.js
similarity index 100%
rename from src/components/__tests__/RowTest.js
rename to src/plugins/core/components/__tests__/RowTest.js
diff --git a/src/components/__tests__/SettingsTest.js b/src/plugins/core/components/__tests__/SettingsTest.js
similarity index 100%
rename from src/components/__tests__/SettingsTest.js
rename to src/plugins/core/components/__tests__/SettingsTest.js
diff --git a/src/components/__tests__/SettingsToggleTest.js b/src/plugins/core/components/__tests__/SettingsToggleTest.js
similarity index 100%
rename from src/components/__tests__/SettingsToggleTest.js
rename to src/plugins/core/components/__tests__/SettingsToggleTest.js
diff --git a/src/components/__tests__/SettingsWrapperTest.js b/src/plugins/core/components/__tests__/SettingsWrapperTest.js
similarity index 100%
rename from src/components/__tests__/SettingsWrapperTest.js
rename to src/plugins/core/components/__tests__/SettingsWrapperTest.js
diff --git a/src/components/__tests__/TableBodyTest.js b/src/plugins/core/components/__tests__/TableBodyTest.js
similarity index 100%
rename from src/components/__tests__/TableBodyTest.js
rename to src/plugins/core/components/__tests__/TableBodyTest.js
diff --git a/src/components/__tests__/TableHeadingCellTest.js b/src/plugins/core/components/__tests__/TableHeadingCellTest.js
similarity index 100%
rename from src/components/__tests__/TableHeadingCellTest.js
rename to src/plugins/core/components/__tests__/TableHeadingCellTest.js
diff --git a/src/components/__tests__/TableHeadingTest.js b/src/plugins/core/components/__tests__/TableHeadingTest.js
similarity index 100%
rename from src/components/__tests__/TableHeadingTest.js
rename to src/plugins/core/components/__tests__/TableHeadingTest.js
diff --git a/src/components/__tests__/TableTest.js b/src/plugins/core/components/__tests__/TableTest.js
similarity index 100%
rename from src/components/__tests__/TableTest.js
rename to src/plugins/core/components/__tests__/TableTest.js
diff --git a/src/components/index.js b/src/plugins/core/components/index.js
similarity index 96%
rename from src/components/index.js
rename to src/plugins/core/components/index.js
index 8f285a48..0f253e78 100644
--- a/src/components/index.js
+++ b/src/plugins/core/components/index.js
@@ -26,7 +26,7 @@ import SettingsWrapper from './SettingsWrapper';
import SettingsWrapperContainer from './SettingsWrapperContainer';
import Settings from './Settings';
import SettingsContainer from './SettingsContainer';
-import { components as SettingsComponents } from '../settingsComponentObjects';
+import { components as SettingsComponents } from '../../../settingsComponentObjects';
import NextButton from './NextButton';
import NextButtonEnhancer from './NextButtonEnhancer';
import NextButtonContainer from './NextButtonContainer';
diff --git a/src/constants/index.js b/src/plugins/core/constants/index.js
similarity index 100%
rename from src/constants/index.js
rename to src/plugins/core/constants/index.js
diff --git a/src/plugins/core/index.js b/src/plugins/core/index.js
new file mode 100644
index 00000000..fc831987
--- /dev/null
+++ b/src/plugins/core/index.js
@@ -0,0 +1,15 @@
+import components from './components';
+import * as reducer from './reducers/dataReducer';
+import * as selectors from './selectors/dataSelectors';
+import * as actions from './actions';
+import initialState from './initialState';
+
+const CorePlugin = (config) => ({
+ components,
+ reducer,
+ selectors,
+ actions,
+ ...initialState(config)
+});
+
+export default CorePlugin;
diff --git a/src/plugins/core/initialState.js b/src/plugins/core/initialState.js
new file mode 100644
index 00000000..5f6d0371
--- /dev/null
+++ b/src/plugins/core/initialState.js
@@ -0,0 +1,72 @@
+import { getColumnProperties } from '../../utils/columnUtils';
+import { getRowProperties } from '../../utils/rowUtils';
+
+
+const styleConfig = {
+ icons: {
+ TableHeadingCell: {
+ sortDescendingIcon: '▼',
+ sortAscendingIcon: '▲'
+ },
+ },
+ classNames: {
+ Cell: 'griddle-cell',
+ Filter: 'griddle-filter',
+ Loading: 'griddle-loadingResults',
+ NextButton: 'griddle-next-button',
+ NoResults: 'griddle-noResults',
+ PageDropdown: 'griddle-page-select',
+ Pagination: 'griddle-pagination',
+ PreviousButton: 'griddle-previous-button',
+ Row: 'griddle-row',
+ RowDefinition: 'griddle-row-definition',
+ Settings: 'griddle-settings',
+ SettingsToggle: 'griddle-settings-toggle',
+ Table: 'griddle-table',
+ TableBody: 'griddle-table-body',
+ TableHeading: 'griddle-table-heading',
+ TableHeadingCell: 'griddle-table-heading-cell',
+ TableHeadingCellAscending: 'griddle-heading-ascending',
+ TableHeadingCellDescending: 'griddle-heading-descending',
+ },
+ styles: {
+ }
+};
+
+const initialState = (config) => {
+
+ const {
+ children:rowPropertiesComponent,
+ } = config;
+
+ const rowProperties = getRowProperties(rowPropertiesComponent);
+ const columnProperties = getColumnProperties(rowPropertiesComponent);
+
+ const renderProperties = {
+ rowProperties,
+ columnProperties
+ }
+
+ //const pageProperties = {
+ // currentPage: 1,
+ // pageSize: 10,
+ // ...externalPageProperties,
+ //}
+
+ const localInitialState = {
+ enableSettings: true,
+ textProperties: {
+ next: 'Next',
+ previous: 'Previous',
+ settingsToggle: 'Settings'
+ }
+ }
+
+ return {
+ styleConfig,
+ renderProperties,
+ initialState: localInitialState
+ };
+}
+
+export default initialState;
diff --git a/src/reducers/__tests__/dataReducerTest.js b/src/plugins/core/reducers/__tests__/dataReducerTest.js
similarity index 100%
rename from src/reducers/__tests__/dataReducerTest.js
rename to src/plugins/core/reducers/__tests__/dataReducerTest.js
diff --git a/src/reducers/dataReducer.js b/src/plugins/core/reducers/dataReducer.js
similarity index 93%
rename from src/reducers/dataReducer.js
rename to src/plugins/core/reducers/dataReducer.js
index 26d7c44f..cf17554e 100644
--- a/src/reducers/dataReducer.js
+++ b/src/plugins/core/reducers/dataReducer.js
@@ -16,7 +16,7 @@ import Immutable from 'immutable';
import {
addColumnPropertiesWhenNoneExist,
transformData,
-} from '../utils/dataUtils';
+} from '../../../utils/dataUtils';
function isColumnVisible(state, columnId) {
const hasRenderProperty = state.getIn(['renderProperties', 'columnProperties', columnId]);
@@ -134,9 +134,13 @@ export function GRIDDLE_TOGGLE_COLUMN(state, action) {
export function GRIDDLE_UPDATE_STATE(state, action) {
const { data, ...newState } = action.newState;
- const transformedData = transformData(data, state.get('renderProperties').toJSON());
-
- return state.mergeDeep(Immutable.fromJS(newState))
- .set('data', transformedData.data)
- .set('lookup', transformedData.lookup);
+ if (data !== undefined) {
+ const transformedData = transformData(data, state.get('renderProperties').toJSON());
+
+ return state.mergeDeep(Immutable.fromJS(newState))
+ .set('data', transformedData.data)
+ .set('lookup', transformedData.lookup);
+ } else {
+ return state.mergeDeep(Immutable.fromJS(newState));
+ }
}
diff --git a/src/selectors/__tests__/dataSelectorsTest.js b/src/plugins/core/selectors/__tests__/dataSelectorsTest.js
similarity index 70%
rename from src/selectors/__tests__/dataSelectorsTest.js
rename to src/plugins/core/selectors/__tests__/dataSelectorsTest.js
index 82e07e9e..ec1f3d4c 100644
--- a/src/selectors/__tests__/dataSelectorsTest.js
+++ b/src/plugins/core/selectors/__tests__/dataSelectorsTest.js
@@ -2,27 +2,32 @@ import test from 'ava';
import Immutable from 'immutable';
import * as selectors from '../dataSelectors';
+import { composeSelectors } from '../../../../utils/selectorUtils';
+
+test.beforeEach((t) => {
+ t.context.selectors = composeSelectors([{selectors}]);
+});
test('gets data', test => {
const state = new Immutable.Map().set('data', 'hi');
- test.is(selectors.dataSelector(state), 'hi');
+ test.is(test.context.selectors.dataSelector(state), 'hi');
});
test('gets pageSize', test => {
const state = new Immutable.Map().setIn(['pageProperties', 'pageSize'], 7);
- test.is(selectors.pageSizeSelector(state), 7);
+ test.is(test.context.selectors.pageSizeSelector(state), 7);
});
/* currentPageSelector */
test('gets current page', test => {
const state = new Immutable.Map().setIn(['pageProperties', 'currentPage'], 3);
- test.is(selectors.currentPageSelector(state), 3);
+ test.is(test.context.selectors.currentPageSelector(state), 3);
});
/* recordCountSelector */
test('gets record count', test => {
const state = new Immutable.Map().setIn(['pageProperties', 'recordCount'], 10);
- test.is(selectors.recordCountSelector(state), 10);
+ test.is(test.context.selectors.recordCountSelector(state), 10);
});
/* hasNextSelector */
@@ -35,7 +40,7 @@ test('hasNext gets true when there are more pages', test => {
}
});
- test.true(selectors.hasNextSelector(state));
+ test.true(test.context.selectors.hasNextSelector(state));
});
test('hasNext gets false when there are not more pages', test => {
@@ -47,7 +52,7 @@ test('hasNext gets false when there are not more pages', test => {
}
});
- test.false(selectors.hasNextSelector(state));
+ test.false(test.context.selectors.hasNextSelector(state));
});
/* this is just double checking that we're not showing next when on record 11-20 of 20 */
@@ -60,24 +65,24 @@ test('hasNext gets false when on the last page', test => {
}
});
- test.false(selectors.hasNextSelector(state));
+ test.false(test.context.selectors.hasNextSelector(state));
});
/* hasPreviousSelector */
test('has previous gets true when there are prior pages', test => {
const state = new Immutable.Map().setIn(['pageProperties', 'currentPage'], 2);
- test.true(selectors.hasPreviousSelector(state));
+ test.true(test.context.selectors.hasPreviousSelector(state));
});
test.skip('has previous gets false when there are not prior pages', test => {
const state = new Immutable.Map().setIn(['pageProperties', 'currentPage'], 2);
- test.true(selectors.hasPreviousSelector(state));
+ test.true(test.context.selectors.hasPreviousSelector(state));
})
/* currentPageSelector */
test('gets current page', test => {
const state = new Immutable.Map().setIn(['pageProperties', 'currentPage'], 1);
- test.false(selectors.hasPreviousSelector(state));
+ test.false(test.context.selectors.hasPreviousSelector(state));
})
/* maxPageSelector */
@@ -90,36 +95,36 @@ test('gets max page', test => {
}
});
- test.is(selectors.maxPageSelector(state), 2);
+ test.is(test.context.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.maxPageSelector(otherState), 2);
+ test.is(test.context.selectors.maxPageSelector(otherState), 2);
//when pageSize === recordCount should have 1 page
const onePageState = state.setIn(['pageProperties', 'pageSize'], 20);
- test.is(selectors.maxPageSelector(onePageState), 1);
+ test.is(test.context.selectors.maxPageSelector(onePageState), 1);
//when there are no records, there should be 0 pages
const noDataState = state.setIn(['pageProperties', 'recordCount'], 0);
- test.is(selectors.maxPageSelector(noDataState), 0);
+ test.is(test.context.selectors.maxPageSelector(noDataState), 0);
});
/* filterSelector */
test('gets filter when present', test => {
const state = new Immutable.Map().set('filter', 'some awesome filter');
- test.is(selectors.filterSelector(state), 'some awesome filter');
+ test.is(test.context.selectors.filterSelector(state), 'some awesome filter');
})
test('gets empty string when no filter present', test => {
const state = new Immutable.Map();
- test.is(selectors.filterSelector(state), '');
+ test.is(test.context.selectors.filterSelector(state), '');
});
/* sortColumnsSelector */
test('gets empty array for sortColumns when none specified', test => {
const state = new Immutable.Map();
- test.deepEqual(selectors.sortColumnsSelector(state), []);
+ test.deepEqual(test.context.selectors.sortColumnsSelector(state), []);
});
test('gets sort column array when specified', test => {
@@ -130,7 +135,7 @@ test('gets sort column array when specified', test => {
{ column: 'three', sortAscending: true}
]);
- test.deepEqual(selectors.sortColumnsSelector(state), [
+ test.deepEqual(test.context.selectors.sortColumnsSelector(state), [
{ column: 'one', sortAscending: true},
{ column: 'two', sortAscending: true},
{ column: 'three', sortAscending: true}
@@ -145,18 +150,18 @@ test('allColumnsSelector: gets all columns', test => {
const state = new Immutable.Map().set('data', data);
- test.deepEqual(selectors.allColumnsSelector(state), ['one', 'two', 'three', 'four']);
+ test.deepEqual(test.context.selectors.allColumnsSelector(state), ['one', 'two', 'three', 'four']);
});
test('allColumnsSelector: gets empty array when no data present', test => {
const state = new Immutable.Map();
- test.deepEqual(selectors.allColumnsSelector(state), []);
+ test.deepEqual(test.context.selectors.allColumnsSelector(state), []);
});
test('allColumnsSelector: gets empty array when data is empty', test => {
const state = new Immutable.Map().set('data', new Immutable.List());
- test.deepEqual(selectors.allColumnsSelector(state), []);
+ test.deepEqual(test.context.selectors.allColumnsSelector(state), []);
});
test('allColumnsSelector accounts for made up columns', test => {
@@ -173,7 +178,7 @@ test('allColumnsSelector accounts for made up columns', test => {
}
});
- test.deepEqual(selectors.allColumnsSelector(state), ['one', 'two', 'three', 'something']);
+ test.deepEqual(test.context.selectors.allColumnsSelector(state), ['one', 'two', 'three', 'something']);
});
test('iconByNameSelector gets given icon', test => {
@@ -185,7 +190,7 @@ test('iconByNameSelector gets given icon', test => {
}
});
- test.is(selectors.iconByNameSelector(state, {name: 'one'}), 'yo');
+ test.is(test.context.selectors.iconByNameSelector(state, {name: 'one'}), 'yo');
});
test('iconByNameSelector gets undefined when icon not present in collection', test => {
@@ -197,7 +202,7 @@ test('iconByNameSelector gets undefined when icon not present in collection', te
}
});
- test.is(selectors.iconByNameSelector(state, { name: 'two'}), undefined)
+ test.is(test.context.selectors.iconByNameSelector(state, { name: 'two'}), undefined)
});
test('classNamesForComponentSelector gets given class', test => {
@@ -209,7 +214,7 @@ test('classNamesForComponentSelector gets given class', test => {
}
});
- test.is(selectors.classNamesForComponentSelector(state, 'one'), 'yo');
+ test.is(test.context.selectors.classNamesForComponentSelector(state, 'one'), 'yo');
});
test('classNameForComponentSelector gets undefined when icon not present in collection', test => {
@@ -221,21 +226,21 @@ test('classNameForComponentSelector gets undefined when icon not present in coll
}
});
- test.is(selectors.classNamesForComponentSelector(state, 'two'), undefined);
+ test.is(test.context.selectors.classNamesForComponentSelector(state, 'two'), undefined);
});
test('isSettingsEnabled returns true when not set', test => {
const state = new Immutable.fromJS({});
- test.is(selectors.isSettingsEnabledSelector(state), true);
+ test.is(test.context.selectors.isSettingsEnabledSelector(state), true);
});
test('isSettingsEnabled returns the value that was set', test => {
const enabledState = new Immutable.fromJS({ enableSettings: true });
const disabledState = new Immutable.fromJS({ enableSettings: false });
- test.is(selectors.isSettingsEnabledSelector(enabledState), true);
- test.is(selectors.isSettingsEnabledSelector(disabledState), false);
+ test.is(test.context.selectors.isSettingsEnabledSelector(enabledState), true);
+ test.is(test.context.selectors.isSettingsEnabledSelector(disabledState), false);
});
test('gets text from state', test => {
@@ -245,7 +250,7 @@ test('gets text from state', test => {
}
});
- test.is(selectors.textSelector(state, { key: 'one'}), 'one two three');
+ test.is(test.context.selectors.textSelector(state, { key: 'one'}), 'one two three');
});
test('gets metadata columns', test => {
@@ -261,7 +266,7 @@ test('gets metadata columns', test => {
}
});
- test.deepEqual(selectors.metaDataColumnsSelector(state), ['two']);
+ test.deepEqual(test.context.selectors.metaDataColumnsSelector(state), ['two']);
});
test('it gets columnTitles in the correct order', test => {
@@ -277,7 +282,7 @@ test('it gets columnTitles in the correct order', test => {
}
});
- test.deepEqual(selectors.columnTitlesSelector(state), ['Two', 'One']);
+ test.deepEqual(test.context.selectors.columnTitlesSelector(state), ['Two', 'One']);
});
[undefined, null].map(data =>
@@ -286,7 +291,7 @@ test('it gets columnTitles in the correct order', test => {
data
});
- assert.deepEqual(selectors.visibleRowIdsSelector(state), new Immutable.List());
+ assert.deepEqual(assert.context.selectors.visibleRowIdsSelector(state), new Immutable.List());
})
);
@@ -299,7 +304,7 @@ test('visibleRowIds gets griddleKey from data', (assert) => {
],
});
- assert.deepEqual(selectors.visibleRowIdsSelector(state), new Immutable.List([2, 4, 6]));
+ assert.deepEqual(assert.context.selectors.visibleRowIdsSelector(state), new Immutable.List([2, 4, 6]));
});
test('rowDataSelector gets row data', (assert) => {
@@ -314,5 +319,5 @@ test('rowDataSelector gets row data', (assert) => {
},
});
- assert.deepEqual(selectors.rowDataSelector(state, { griddleKey: 6 }), { griddleKey: 6, id: 1 });
+ assert.deepEqual(assert.context.selectors.rowDataSelector(state, { griddleKey: 6 }), { griddleKey: 6, id: 1 });
});
diff --git a/src/selectors/dataSelectors.js b/src/plugins/core/selectors/dataSelectors.js
similarity index 92%
rename from src/selectors/dataSelectors.js
rename to src/plugins/core/selectors/dataSelectors.js
index 4f560342..97b1ba4e 100644
--- a/src/selectors/dataSelectors.js
+++ b/src/plugins/core/selectors/dataSelectors.js
@@ -1,6 +1,7 @@
import Immutable from 'immutable';
-import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
import _ from 'lodash';
+import { createSelector } from '../../../utils/selectorUtils';
+import { createSelectorCreator, defaultMemoize } from 'reselect';
const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
@@ -29,15 +30,15 @@ export const renderPropertiesSelector = state => (state.get('renderProperties'))
/** Determines if there are previous pages */
export const hasPreviousSelector = createSelector(
- currentPageSelector,
+ 'currentPageSelector',
(currentPage) => (currentPage > 1)
);
/** Gets the max page size
*/
export const maxPageSelector = createSelector(
- pageSizeSelector,
- recordCountSelector,
+ 'pageSizeSelector',
+ 'recordCountSelector',
(pageSize, recordCount) => {
const calc = recordCount / pageSize;
@@ -49,8 +50,8 @@ export const maxPageSelector = createSelector(
/** Determines if there are more pages available. Assumes pageProperties.maxPage is set by the container */
export const hasNextSelector = createSelector(
- currentPageSelector,
- maxPageSelector,
+ 'currentPageSelector',
+ 'maxPageSelector',
(currentPage, maxPage) => {
return currentPage < maxPage;
}
@@ -64,8 +65,8 @@ export const sortColumnsSelector = state => state.get('sortColumns') || [];
/** Gets all the columns */
export const allColumnsSelector = createSelector(
- dataSelector,
- renderPropertiesSelector,
+ 'dataSelector',
+ 'renderPropertiesSelector',
(data, renderProperties) => {
const dataColumns = !data || data.size === 0 ?
[] :
@@ -83,7 +84,7 @@ export const allColumnsSelector = createSelector(
/** Gets the column properties objects sorted by order
*/
export const sortedColumnPropertiesSelector = createSelector(
- renderPropertiesSelector,
+ 'renderPropertiesSelector',
(renderProperties) => (
renderProperties && renderProperties.get('columnProperties') && renderProperties.get('columnProperties').size !== 0 ?
renderProperties.get('columnProperties')
@@ -95,7 +96,7 @@ export const sortedColumnPropertiesSelector = createSelector(
/** Gets metadata column ids
*/
export const metaDataColumnsSelector = createSelector(
- sortedColumnPropertiesSelector,
+ 'sortedColumnPropertiesSelector',
(sortedColumnProperties) => (
sortedColumnProperties ? sortedColumnProperties
.filter(c => c.get('isMetadata'))
@@ -108,8 +109,8 @@ export const metaDataColumnsSelector = createSelector(
/** Gets the visible columns either obtaining the sorted column properties or all columns
*/
export const visibleColumnsSelector = createSelector(
- sortedColumnPropertiesSelector,
- allColumnsSelector,
+ 'sortedColumnPropertiesSelector',
+ 'allColumnsSelector',
(sortedColumnProperties, allColumns) => (
sortedColumnProperties ? sortedColumnProperties
.filter(c => {
@@ -126,8 +127,8 @@ export const visibleColumnsSelector = createSelector(
/** TODO: add tests and docs
*/
export const visibleColumnPropertiesSelector = createSelector(
- visibleColumnsSelector,
- renderPropertiesSelector,
+ 'visibleColumnsSelector',
+ 'renderPropertiesSelector',
(visibleColumns=[], renderProperties) => (
visibleColumns.map(c => {
const columnProperty = renderProperties.getIn(['columnProperties', c]);
@@ -138,9 +139,9 @@ export const visibleColumnPropertiesSelector = createSelector(
/** Gets the possible columns that are currently hidden */
export const hiddenColumnsSelector = createSelector(
- visibleColumnsSelector,
- allColumnsSelector,
- metaDataColumnsSelector,
+ 'visibleColumnsSelector',
+ 'allColumnsSelector',
+ 'metaDataColumnsSelector',
(visibleColumns, allColumns, metaDataColumns) => {
const removeColumns = [...visibleColumns, ...metaDataColumns];
@@ -151,8 +152,8 @@ export const hiddenColumnsSelector = createSelector(
/** TODO: add tests and docs
*/
export const hiddenColumnPropertiesSelector = createSelector(
- hiddenColumnsSelector,
- renderPropertiesSelector,
+ 'hiddenColumnsSelector',
+ 'renderPropertiesSelector',
(hiddenColumns=[], renderProperties) => (
hiddenColumns.map(c => {
const columnProperty = renderProperties.getIn(['columnProperties', c]);
@@ -222,8 +223,8 @@ export const textSelector = (state, { key}) => {
/** Gets the column ids for the visible columns
*/
export const columnIdsSelector = createSelector(
- renderPropertiesSelector,
- visibleColumnsSelector,
+ 'renderPropertiesSelector',
+ 'visibleColumnsSelector',
(renderProperties, visibleColumns) => {
const offset = 1000;
// TODO: Make this better -- This is pretty inefficient
@@ -240,20 +241,20 @@ export const columnIdsSelector = createSelector(
/** Gets the column titles for the visible columns
*/
export const columnTitlesSelector = createSelector(
- columnIdsSelector,
- renderPropertiesSelector,
+ 'columnIdsSelector',
+ 'renderPropertiesSelector',
(columnIds, renderProperties) => columnIds.map(k => renderProperties.getIn(['columnProperties', k, 'title']) || k)
);
/** Gets the griddleIds for the visible rows */
export const visibleRowIdsSelector = createSelector(
- dataSelector,
+ 'dataSelector',
currentPageData => currentPageData ? currentPageData.map(c => c.get('griddleKey')) : new Immutable.List()
);
/** Gets the count of visible rows */
export const visibleRowCountSelector = createSelector(
- visibleRowIdsSelector,
+ 'visibleRowIdsSelector',
(visibleRowIds) => visibleRowIds.size
);
diff --git a/src/plugins/local/components/NextButtonContainer.js b/src/plugins/local/components/NextButtonContainer.js
index 2f872dc0..d3e6c5c7 100644
--- a/src/plugins/local/components/NextButtonContainer.js
+++ b/src/plugins/local/components/NextButtonContainer.js
@@ -2,7 +2,7 @@ import React from 'react';
import { connect } from '../../../utils/griddleConnect';
import { textSelector, hasNextSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
-import { getNext } from '../../../actions';
+import { getNext } from '../../core/actions';
const enhance = OriginalComponent => connect(state => ({
text: textSelector(state, { key: 'next' }),
diff --git a/src/plugins/local/components/PageDropdownContainer.js b/src/plugins/local/components/PageDropdownContainer.js
index b62242a4..fda89910 100644
--- a/src/plugins/local/components/PageDropdownContainer.js
+++ b/src/plugins/local/components/PageDropdownContainer.js
@@ -3,7 +3,7 @@ import { connect } from '../../../utils/griddleConnect';
import { createStructuredSelector } from 'reselect';
import { currentPageSelector, maxPageSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
-import { setPage } from '../../../actions';
+import { setPage } from '../../core/actions';
const enhance = OriginalComponent => connect(state => ({
maxPages: maxPageSelector(state),
diff --git a/src/plugins/local/components/PreviousButtonContainer.js b/src/plugins/local/components/PreviousButtonContainer.js
index 54af2b14..6fdb224a 100644
--- a/src/plugins/local/components/PreviousButtonContainer.js
+++ b/src/plugins/local/components/PreviousButtonContainer.js
@@ -2,7 +2,7 @@ import React from 'react';
import { connect } from '../../../utils/griddleConnect';
import { textSelector, hasPreviousSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
-import { getPrevious } from '../../../actions';
+import { getPrevious } from '../../core/actions';
const enhance = OriginalComponent => connect(state => ({
text: textSelector(state, { key: 'previous' }),
diff --git a/src/plugins/local/components/TableHeadingCellContainer.js b/src/plugins/local/components/TableHeadingCellContainer.js
index 6937acf6..2e1cb4ad 100644
--- a/src/plugins/local/components/TableHeadingCellContainer.js
+++ b/src/plugins/local/components/TableHeadingCellContainer.js
@@ -5,8 +5,8 @@ import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import getContext from 'recompose/getContext';
import withHandlers from 'recompose/withHandlers';
-import { sortPropertyByIdSelector, iconsForComponentSelector, customHeadingComponentSelector, stylesForComponentSelector, classNamesForComponentSelector, cellPropertiesSelector } from '../../../selectors/dataSelectors';
-import { setSortColumn } from '../../../actions';
+import { sortPropertyByIdSelector, iconsForComponentSelector, customHeadingComponentSelector, stylesForComponentSelector, classNamesForComponentSelector, cellPropertiesSelector } from '../../core/selectors/dataSelectors';
+import { setSortColumn } from '../../core/actions';
import { getSortIconProps, setSortProperties } from '../../../utils/sortUtils';
import { valueOrResult } from '../../../utils/valueUtils';
diff --git a/src/plugins/local/index.js b/src/plugins/local/index.js
index 0c32aac1..02c55a82 100644
--- a/src/plugins/local/index.js
+++ b/src/plugins/local/index.js
@@ -6,4 +6,4 @@ export default {
components,
reducer,
selectors
-};
\ No newline at end of file
+};
diff --git a/src/plugins/local/reducers/__tests__/localReducerTests.js b/src/plugins/local/reducers/__tests__/localReducerTests.js
index 42e2080d..f370f0f3 100644
--- a/src/plugins/local/reducers/__tests__/localReducerTests.js
+++ b/src/plugins/local/reducers/__tests__/localReducerTests.js
@@ -2,7 +2,7 @@ import test from 'ava';
import Immutable from 'immutable';
import * as reducers from '../index';
-import constants from '../../../../constants';
+import constants from '../../../core/constants';
test('it loads data', test => {
const state = reducers.GRIDDLE_LOADED_DATA(Immutable.fromJS({ renderProperties: { } }), {
diff --git a/src/plugins/local/reducers/index.js b/src/plugins/local/reducers/index.js
index 7b8dec64..1d77c5e0 100644
--- a/src/plugins/local/reducers/index.js
+++ b/src/plugins/local/reducers/index.js
@@ -1,6 +1,6 @@
import { maxPageSelector, currentPageSelector } from '../selectors/localSelectors';
-import * as dataReducers from '../../../reducers//dataReducer';
+import * as dataReducers from '../../core/reducers/dataReducer';
export function GRIDDLE_INITIALIZED(state) {
return dataReducers.GRIDDLE_INITIALIZED(state);
diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js
index 28b84b12..a1dea52f 100644
--- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js
+++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js
@@ -1,12 +1,18 @@
import test from 'ava';
import Immutable from 'immutable';
+//import * as selectors from '../dataSelectors';
import * as selectors from '../localSelectors';
+import { composeSelectors } from '../../../../utils/selectorUtils';
+
+test.beforeEach((t) => {
+ t.context.selectors = composeSelectors([{selectors}]);
+});
test('gets data', test => {
const state = new Immutable.Map({ data: 'hi' });
- test.deepEqual(selectors.dataSelector(state), 'hi');
+ test.deepEqual(test.context.selectors.dataSelector(state), 'hi');
});
test('gets current page', test => {
@@ -16,7 +22,7 @@ test('gets current page', test => {
}
});
- test.is(selectors.currentPageSelector(state), 4);
+ test.is(test.context.selectors.currentPageSelector(state), 4);
});
test('gets current page size', test => {
@@ -26,7 +32,7 @@ test('gets current page size', test => {
}
});
- test.is(selectors.pageSizeSelector(state), 20);
+ test.is(test.context.selectors.pageSizeSelector(state), 20);
});
test('gets the correct max page', test => {
@@ -47,19 +53,19 @@ test('gets the correct max page', test => {
});
// 8/3 = 2.6... so the number of pages should be 3
- test.is(selectors.maxPageSelector(state), 3);
+ test.is(test.context.selectors.maxPageSelector(state), 3);
});
test('gets the correct filter when filter present', test => {
const state = new Immutable.Map({ filter: 'hi' });
- test.is(selectors.filterSelector(state), 'hi');
+ test.is(test.context.selectors.filterSelector(state), 'hi');
});
test('gets empty string when filter not present', test => {
const state = new Immutable.Map();
- test.is(selectors.filterSelector(state), '');
+ test.is(test.context.selectors.filterSelector(state), '');
});
test('gets sort properties', test => {
@@ -70,7 +76,7 @@ test('gets sort properties', test => {
]
});
- test.deepEqual(selectors.sortPropertiesSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.sortPropertiesSelector(state).toJSON(), [
{ id: 'one', sortAscending: true },
{ id: 'two', sortAscending: false }
]);
@@ -81,7 +87,7 @@ test('gets render properties', test => {
renderProperties: 'hello'
});
- test.is(selectors.renderPropertiesSelector(state), 'hello');
+ test.is(test.context.selectors.renderPropertiesSelector(state), 'hello');
});
test('gets all columns', test => {
@@ -91,7 +97,7 @@ test('gets all columns', test => {
]
});
- test.deepEqual(selectors.allColumnsSelector(state), ['one', 'two', 'three']);
+ test.deepEqual(test.context.selectors.allColumnsSelector(state), ['one', 'two', 'three']);
});
test('gets column orders', test => {
@@ -104,7 +110,7 @@ test('gets column orders', test => {
}
});
- test.deepEqual(selectors.sortedColumnPropertiesSelector(state).toJSON(), {
+ test.deepEqual(test.context.selectors.sortedColumnPropertiesSelector(state).toJSON(), {
two: { id: 'two', title: 'Two', order: 1 },
one: { id: 'one', title: 'One', order: 2 }
});
@@ -123,7 +129,7 @@ test('gets visible columns when columns specified without order', test => {
}
});
- test.deepEqual(selectors.visibleColumnsSelector(state), ['one', 'two']);
+ test.deepEqual(test.context.selectors.visibleColumnsSelector(state), ['one', 'two']);
});
test('gets visible columns in order when columns specified', test => {
@@ -139,7 +145,7 @@ test('gets visible columns in order when columns specified', test => {
}
});
- test.deepEqual(selectors.visibleColumnsSelector(state), ['two', 'one']);
+ test.deepEqual(test.context.selectors.visibleColumnsSelector(state), ['two', 'one']);
});
test('gets all columns as visible columns when no columns specified', test => {
@@ -149,7 +155,7 @@ test('gets all columns as visible columns when no columns specified', test => {
]
});
- test.deepEqual(selectors.visibleColumnsSelector(state), ['one', 'two', 'three']);
+ test.deepEqual(test.context.selectors.visibleColumnsSelector(state), ['one', 'two', 'three']);
});
test('hasNextSelector returns true when more pages', test => {
@@ -170,7 +176,7 @@ test('hasNextSelector returns true when more pages', test => {
}
});
- test.is(selectors.hasNextSelector(state), true);
+ test.is(test.context.selectors.hasNextSelector(state), true);
});
test('hasNextSelector returns false when no more pages', test => {
@@ -191,7 +197,7 @@ test('hasNextSelector returns false when no more pages', test => {
}
});
- test.is(selectors.hasNextSelector(state), false);
+ test.is(test.context.selectors.hasNextSelector(state), false);
});
test('hasPreviousSelector returns true when there is a previous page', test => {
@@ -201,7 +207,7 @@ test('hasPreviousSelector returns true when there is a previous page', test => {
}
});
- test.is(selectors.hasPreviousSelector(state), true);
+ test.is(test.context.selectors.hasPreviousSelector(state), true);
});
test('hasPreviousSelector returns false when there are no previous pages', test => {
@@ -211,7 +217,7 @@ test('hasPreviousSelector returns false when there are no previous pages', test
}
});
- test.is(selectors.hasPreviousSelector(state), false);
+ test.is(test.context.selectors.hasPreviousSelector(state), false);
});
test('filteredDataSelector returns all data when no filter present', test => {
@@ -222,7 +228,7 @@ test('filteredDataSelector returns all data when no filter present', test => {
]
});
- test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.filteredDataSelector(state).toJSON(), [
{ id: '1', name: 'luke skywalker' },
{ id: '2', name: 'han solo' }
]);
@@ -237,7 +243,7 @@ test('filteredDataSelector filters data when filter string present', test => {
]
});
- test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.filteredDataSelector(state).toJSON(), [
{ id: '1', name: 'luke skywalker' }
]);
});
@@ -258,7 +264,7 @@ test('filteredDataSelector filters data respecting filterable', test => {
]
});
- test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.filteredDataSelector(state).toJSON(), [
{ id: '1', name: 'luke skywalker', weapon: 'light saber' }
]);
});
@@ -274,7 +280,7 @@ test('sortedDataSelector uses default sort if no sort method specifed for column
]
});
- test.deepEqual(selectors.sortedDataSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.sortedDataSelector(state).toJSON(), [
{ id: '2', name: 'han solo' },
{ id: '1', name: 'luke skywalker' }
]);
@@ -300,7 +306,7 @@ test('sortedDataSelector uses specified sort', test => {
}
});
- test.deepEqual(selectors.sortedDataSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.sortedDataSelector(state).toJSON(), [
{ id: '1', name: 'luke skywalker' },
{ id: '2', name: 'han solo' }
]);
@@ -322,7 +328,7 @@ test('sortedDataSelector works with multiple sortOptions', test => {
]
});
- test.deepEqual(selectors.sortedDataSelector(state).toJSON(), [
+ test.deepEqual(test.context.selectors.sortedDataSelector(state).toJSON(), [
{ id: '3', name: 'han solo', food: 'apple' },
{ id: '2', name: 'han solo', food: 'banana' },
{ id: '4', name: 'luke skywalker', food: 'apple' },
@@ -344,7 +350,7 @@ test('current page data selector gets correct page', test => {
}
});
- test.deepEqual(selectors.currentPageDataSelector(state).toJSON(), [{ id: '3', name: 'han solo', food: 'apple' }]);
+ test.deepEqual(test.context.selectors.currentPageDataSelector(state).toJSON(), [{ id: '3', name: 'han solo', food: 'apple' }]);
})
test('visible data selector gets only visible columns', test => {
@@ -371,7 +377,7 @@ test('visible data selector gets only visible columns', test => {
}
});
- test.deepEqual(selectors.visibleDataSelector(state).toJSON(), [{ name: 'han solo', food: 'apple' }]);
+ test.deepEqual(test.context.selectors.visibleDataSelector(state).toJSON(), [{ name: 'han solo', food: 'apple' }]);
});
test('visibleRowIdsSelector gets row ids', test => {
@@ -395,7 +401,7 @@ test('visibleRowIdsSelector gets row ids', test => {
}
});
- test.deepEqual(selectors.visibleRowIdsSelector(state).toJSON(), [3, 4]);
+ test.deepEqual(test.context.selectors.visibleRowIdsSelector(state).toJSON(), [3, 4]);
});
test('hidden columns selector shows all columns that are not visible', test => {
@@ -419,7 +425,7 @@ test('hidden columns selector shows all columns that are not visible', test => {
}
});
- test.deepEqual(selectors.hiddenColumnsSelector(state), ['id', 'food']);
+ test.deepEqual(test.context.selectors.hiddenColumnsSelector(state), ['id', 'food']);
});
test('columnIdsSelector gets all column ids', test => {
@@ -452,7 +458,7 @@ test('columnIdsSelector gets all column ids', test => {
}
});
- test.deepEqual(selectors.columnIdsSelector(state), ['first', 'second', 'third']);
+ test.deepEqual(test.context.selectors.columnIdsSelector(state), ['first', 'second', 'third']);
});
test('columnTitlesSelector gets all column titles', test => {
@@ -485,5 +491,5 @@ test('columnTitlesSelector gets all column titles', test => {
}
});
- test.deepEqual(selectors.columnTitlesSelector(state), ['Name', 'ID', 'Food Order']);
+ test.deepEqual(test.context.selectors.columnTitlesSelector(state), ['Name', 'ID', 'Food Order']);
});
diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js
index be77f0d5..e2b34ce0 100644
--- a/src/plugins/local/selectors/localSelectors.js
+++ b/src/plugins/local/selectors/localSelectors.js
@@ -1,10 +1,10 @@
import Immutable from 'immutable';
-import { createSelector } from 'reselect';
+import { createSelector } from '../../../utils/selectorUtils';
import _ from 'lodash';
import { defaultSort } from '../../../utils/sortUtils';
import { getVisibleDataForColumns } from '../../../utils/dataUtils';
-import * as dataSelectors from '../../../selectors/dataSelectors';
+import * as dataSelectors from '../../core/selectors/dataSelectors';
/** Gets the entire data set
* @param {Immutable} state - state object
@@ -35,14 +35,14 @@ export const renderPropertiesSelector = state => (state.get('renderProperties'))
export const metaDataColumnsSelector = dataSelectors.metaDataColumnsSelector;
-const columnPropertiesSelector = state => state.getIn(['renderProperties', 'columnProperties']);
+export const columnPropertiesSelector = state => state.getIn(['renderProperties', 'columnProperties']);
/** Gets the data filtered by the current filter
*/
export const filteredDataSelector = createSelector(
- dataSelector,
- filterSelector,
- columnPropertiesSelector,
+ 'dataSelector',
+ 'filterSelector',
+ 'columnPropertiesSelector',
(data, filter, columnProperties) => {
if (!filter || !data) {
return data;
@@ -67,8 +67,8 @@ export const filteredDataSelector = createSelector(
/** Gets the max page size
*/
export const maxPageSelector = createSelector(
- pageSizeSelector,
- filteredDataSelector,
+ 'pageSizeSelector',
+ 'filteredDataSelector',
(pageSize, data) => {
const total = data ? data.size : 0;
const calc = total / pageSize;
@@ -80,7 +80,7 @@ export const maxPageSelector = createSelector(
)
export const allColumnsSelector = createSelector(
- dataSelector,
+ 'dataSelector',
data => (!data || data.size === 0 ? [] : data.get(0).keySeq().toJSON())
);
@@ -95,8 +95,8 @@ export const visibleColumnsSelector = dataSelectors.visibleColumnsSelector;
/** Returns whether or not this result set has more pages
*/
export const hasNextSelector = createSelector(
- currentPageSelector,
- maxPageSelector,
+ 'currentPageSelector',
+ 'maxPageSelector',
(currentPage, maxPage) => (currentPage < maxPage)
);
@@ -108,10 +108,10 @@ export const hasPreviousSelector = state => (state.getIn(['pageProperties', 'cur
* if no sort method is supplied, it will use the default sort defined in griddle
*/
export const sortedDataSelector = createSelector(
- filteredDataSelector,
- sortPropertiesSelector,
- renderPropertiesSelector,
- sortMethodSelector,
+ 'filteredDataSelector',
+ 'sortPropertiesSelector',
+ 'renderPropertiesSelector',
+ 'sortMethodSelector',
(filteredData, sortProperties, renderProperties, sortMethod = defaultSort) => {
if (!sortProperties) { return filteredData; }
@@ -128,9 +128,9 @@ export const sortedDataSelector = createSelector(
/** Gets the current page of data
*/
export const currentPageDataSelector = createSelector(
- sortedDataSelector,
- pageSizeSelector,
- currentPageSelector,
+ 'sortedDataSelector',
+ 'pageSizeSelector',
+ 'currentPageSelector',
(sortedData, pageSize, currentPage) => {
if (!sortedData) {
return [];
@@ -145,29 +145,29 @@ export const currentPageDataSelector = createSelector(
/** Get the visible data (and only the columns that are visible)
*/
export const visibleDataSelector = createSelector(
- currentPageDataSelector,
- visibleColumnsSelector,
+ 'currentPageDataSelector',
+ 'visibleColumnsSelector',
(currentPageData, visibleColumns) => getVisibleDataForColumns(currentPageData, visibleColumns)
);
/** Gets the griddleIds for the visible rows */
export const visibleRowIdsSelector = createSelector(
- currentPageDataSelector,
+ 'currentPageDataSelector',
currentPageData => (currentPageData ? currentPageData.map(c => c.get('griddleKey')) : new Immutable.List())
);
/** Gets the count of visible rows */
export const visibleRowCountSelector = createSelector(
- visibleRowIdsSelector,
+ 'visibleRowIdsSelector',
(visibleRowIds) => visibleRowIds.size
);
/** Gets the columns that are not currently visible
*/
export const hiddenColumnsSelector = createSelector(
- visibleColumnsSelector,
- allColumnsSelector,
- metaDataColumnsSelector,
+ 'visibleColumnsSelector',
+ 'allColumnsSelector',
+ 'metaDataColumnsSelector',
(visibleColumns, allColumns, metaDataColumns) => {
const removeColumns = [...visibleColumns, ...metaDataColumns];
@@ -178,8 +178,8 @@ export const hiddenColumnsSelector = createSelector(
/** Gets the column ids for the visible columns
*/
export const columnIdsSelector = createSelector(
- visibleDataSelector,
- renderPropertiesSelector,
+ 'visibleDataSelector',
+ 'renderPropertiesSelector',
(visibleData, renderProperties) => {
if(visibleData.size > 0) {
return Object.keys(visibleData.get(0).toJSON()).map(k =>
diff --git a/src/plugins/position/selectors/__tests__/indexTest.js b/src/plugins/position/selectors/__tests__/indexTest.js
index e8503a3c..203ac645 100644
--- a/src/plugins/position/selectors/__tests__/indexTest.js
+++ b/src/plugins/position/selectors/__tests__/indexTest.js
@@ -1,10 +1,20 @@
import test from 'ava';
import Immutable from 'immutable';
+import { composeSelectors } from '../../../../utils/selectorUtils';
+
+import * as coreSelectors from '../../../core/selectors/dataSelectors';
+import * as localSelectors from '../../../local/selectors/localSelectors';
import {
visibleRecordCountSelector
} from '../index';
+import * as selectors from '../index';
+
+test.beforeEach((test) => {
+ test.context.selectors = composeSelectors([{selectors: {...coreSelectors}}, {selectors: {...localSelectors}}, {selectors}]);
+});
+
test('visible record count selector', test => {
const state = new Immutable.fromJS({
positionSettings: {
@@ -16,6 +26,6 @@ test('visible record count selector', test => {
},
});
- test.is(visibleRecordCountSelector(state), 12);
+ test.is(test.context.selectors.visibleRecordCountSelector(state), 12);
});
diff --git a/src/plugins/position/selectors/index.js b/src/plugins/position/selectors/index.js
index fda22695..17d98d35 100644
--- a/src/plugins/position/selectors/index.js
+++ b/src/plugins/position/selectors/index.js
@@ -1,4 +1,6 @@
-import { createSelector } from 'reselect';
+import { createSelector } from '../../../utils/selectorUtils';
+
+import { getVisibleDataForColumns } from '../../../utils/dataUtils';
import { sortedDataSelector, visibleColumnsSelector } from '../../local/selectors/localSelectors';
@@ -18,15 +20,15 @@ export const tableWidthSelector = state => state.getIn(['positionSettings', 'tab
/** Gets the number of viisble rows based on the height of the container and the rowHeight
*/
export const visibleRecordCountSelector = createSelector(
- rowHeightSelector,
- currentHeightSelector,
+ 'rowHeightSelector',
+ 'currentHeightSelector',
(rowHeight, currentHeight) => {
return Math.ceil(currentHeight / rowHeight);
}
);
export const visibleDataLengthSelector = createSelector(
- sortedDataSelector,
+ 'sortedDataSelector',
(sortedData) => {
return sortedData.size;
}
@@ -36,9 +38,9 @@ export const hoizontalScrollChangeSelector = state => state.getIn(['currentPosit
export const verticalScrollChangeSelector = state => state.getIn(['currentPosition', 'yScrollChangePosition']) || 0;
export const startIndexSelector = createSelector(
- verticalScrollChangeSelector,
- rowHeightSelector,
- visibleRecordCountSelector,
+ 'verticalScrollChangeSelector',
+ 'rowHeightSelector',
+ 'visibleRecordCountSelector',
(verticalScrollPosition, rowHeight, visibleRecordCount) => {
// Inspired by : http://jsfiddle.net/vjeux/KbWJ2/9/
return Math.max(0, Math.floor(Math.floor(verticalScrollPosition / rowHeight) - visibleRecordCount * 0.25));
@@ -46,9 +48,9 @@ export const startIndexSelector = createSelector(
);
export const endIndexSelector = createSelector(
- startIndexSelector,
- visibleRecordCountSelector,
- visibleDataLengthSelector,
+ 'startIndexSelector',
+ 'visibleRecordCountSelector',
+ 'visibleDataLengthSelector',
(startDisplayIndex, visibleRecordCount, visibleDataLength) => {
// Inspired by : http://jsfiddle.net/vjeux/KbWJ2/9/
return Math.min(Math.floor(startDisplayIndex + visibleRecordCount * 2), visibleDataLength - 1) + 1;
@@ -56,17 +58,17 @@ export const endIndexSelector = createSelector(
);
export const topSpacerSelector = createSelector(
- rowHeightSelector,
- startIndexSelector,
+ 'rowHeightSelector',
+ 'startIndexSelector',
(rowHeight, startIndex) => {
return rowHeight * startIndex;
}
);
export const bottomSpacerSelector = createSelector(
- rowHeightSelector,
- visibleDataLengthSelector,
- endIndexSelector,
+ 'rowHeightSelector',
+ 'visibleDataLengthSelector',
+ 'endIndexSelector',
(rowHeight, visibleDataLength, endIndex) => {
return rowHeight * (visibleDataLength - endIndex);
}
@@ -77,9 +79,9 @@ export const bottomSpacerSelector = createSelector(
*/
export const currentPageDataSelector = (...args) => {
return createSelector(
- sortedDataSelector,
- startIndexSelector,
- endIndexSelector,
+ 'sortedDataSelector',
+ 'startIndexSelector',
+ 'endIndexSelector',
(sortedData, startDisplayIndex, endDisplayIndex) => {
return sortedData
.skip(startDisplayIndex)
@@ -91,13 +93,13 @@ export const currentPageDataSelector = (...args) => {
/** Get the visible data (and only the columns that are visible)
*/
export const visibleDataSelector = createSelector(
- currentPageDataSelector,
- visibleColumnsSelector,
+ 'currentPageDataSelector',
+ 'visibleColumnsSelector',
(currentPageData, visibleColumns) => getVisibleDataForColumns(currentPageData, visibleColumns)
);
/** Gets the griddleIds for the visible rows */
export const visibleRowIdsSelector = createSelector(
- currentPageDataSelector,
+ 'currentPageDataSelector',
(currentPageData) => currentPageData.map(c => c.get('griddleKey'))
);
diff --git a/src/settingsComponentObjects/ColumnChooser.js b/src/settingsComponentObjects/ColumnChooser.js
index 19fbf93c..a3916ea1 100644
--- a/src/settingsComponentObjects/ColumnChooser.js
+++ b/src/settingsComponentObjects/ColumnChooser.js
@@ -3,8 +3,8 @@ import { connect } from '../utils/griddleConnect';
import compose from 'recompose/compose';
import withHandlers from 'recompose/withHandlers';
-import { visibleColumnPropertiesSelector, hiddenColumnPropertiesSelector } from '../selectors/dataSelectors';
-import { toggleColumn as toggleColumnAction } from '../actions';
+import { visibleColumnPropertiesSelector, hiddenColumnPropertiesSelector } from '../plugins/core/selectors/dataSelectors';
+import { toggleColumn as toggleColumnAction } from '../plugins/core/actions';
const style = {
label: { clear: 'both' }
diff --git a/src/settingsComponentObjects/PageSizeSettings.js b/src/settingsComponentObjects/PageSizeSettings.js
index 08c8579b..3cd0a73e 100644
--- a/src/settingsComponentObjects/PageSizeSettings.js
+++ b/src/settingsComponentObjects/PageSizeSettings.js
@@ -4,9 +4,9 @@ import compose from 'recompose/compose';
import withState from 'recompose/withState';
import withHandlers from 'recompose/withHandlers';
-import { pageSizeSelector } from '../selectors/dataSelectors';
+import { pageSizeSelector } from '../plugins/core/selectors/dataSelectors';
-import { setPageSize as setPageSizeAction } from '../actions';
+import { setPageSize as setPageSizeAction } from '../plugins/core/actions';
const ComposedPageSizeSettings = compose(
connect(
diff --git a/src/utils/__tests__/selectorUtilsTest.js b/src/utils/__tests__/selectorUtilsTest.js
new file mode 100644
index 00000000..f26c81d6
--- /dev/null
+++ b/src/utils/__tests__/selectorUtilsTest.js
@@ -0,0 +1,377 @@
+import test from 'ava';
+
+import {
+ createSelector,
+ composeSelectors
+} from '../selectorUtils';
+
+test('createSelector with only 1 argument should throw an Error', (t) => {
+ const error = t.throws(() => {
+ createSelector(
+ (state) => state
+ );
+ }, Error);
+});
+
+test('createSelector with final results function arg not of function type', (assert) => {
+ const error = assert.throws(() => {
+ createSelector(
+ "someDependency",
+ "badResultFunctionStringArg"
+ );
+ }, Error);
+});
+
+test('createSelector with 1 selector function arg and 1 results function arg', (assert) => {
+ const selector = createSelector(
+ (state) => state,
+ (state) => state
+ );
+
+ assert.is(typeof selector, "function");
+ assert.is(selector.dependencies, undefined);
+});
+
+test('createSelector with 1 selector dependency and 1 results function', (assert) => {
+ const selector = createSelector(
+ "someDependency",
+ (a) => null
+ );
+
+ assert.is(typeof selector, "function");
+ assert.is(typeof selector.dependencies, "object");
+ assert.is(selector.dependencies.length, 1);
+ assert.is(selector.dependencies[0], "someDependency");
+});
+
+test('createSelector with 1 function dependency, 1 selector dependency, and 1 results function', (assert) => {
+ const selector = createSelector(
+ (state) => state,
+ "someDependency",
+ (state, x) => null
+ );
+
+ assert.is(typeof selector, "function");
+ assert.is(typeof selector.dependencies, "object");
+ assert.is(selector.dependencies.length, 1);
+ assert.is(selector.dependencies[0], "someDependency");
+});
+
+test('createSelector with a non string or function argument for one of the first n - 1 args', (assert) => {
+ const error = assert.throws(() => {
+ createSelector(
+ 42,
+ (x) => null
+ );
+ }, Error);
+});
+
+test('createSelector with 1 selector dependency and 1 results function, ' +
+ 'then call the returned generator function with valid resolved dependencies', (assert) => {
+ const resolvedDependencies = {
+ someDependency: () => 42
+ };
+
+ const selector = createSelector(
+ "someDependency",
+ (x) => x
+ )(resolvedDependencies);
+
+ assert.is(selector(), 42);
+ });
+
+test('createSelector with 1 selector function, 1 selector dependency, and 1 results function ' +
+ 'then call the returned generator function with valid resolved dependencies', (assert) => {
+ const someFunction = () => 10;
+
+ const resolvedDependencies = {
+ someDependency: () => 42
+ };
+
+ const selector = createSelector(
+ someFunction,
+ "someDependency",
+ (x, y) => x * y
+ )(resolvedDependencies);
+
+ assert.is(selector(), 420);
+ });
+
+test('createSelector with 1 selector dependency, and 1 results function' +
+ 'then call the returned generator function WITHOUT valid resolved dependencies', (assert) => {
+ const error = assert.throws(() => {
+ createSelector(
+ "someDependency",
+ (x) => x
+ )({});
+ }, Error);
+ });
+
+test('composeSelectors with 1 simple selector', (assert) => {
+ const plugin0 = {
+ selectors: {
+ simpleSelectorA: (state) => state,
+ }
+ };
+
+ const flattenedSelectors = composeSelectors([plugin0]);
+
+ assert.is(typeof flattenedSelectors, "object");
+ assert.is(Object.keys(flattenedSelectors).length, 1);
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorA"));
+});
+
+test('composeSelectors with 2 simple selectors and 1 dependency selector dependent on the 2 simple selectors', (assert) => {
+ const plugin0 = {
+ selectors: {
+ simpleSelectorA: () => 10,
+ simpleSelectorB: () => 2,
+ dependencySelector1: createSelector(
+ 'simpleSelectorA',
+ 'simpleSelectorB',
+ (x, y) => x * y
+ )
+ }
+ }
+
+ const flattenedSelectors = composeSelectors([plugin0]);
+
+ assert.is(typeof flattenedSelectors, "object");
+ assert.is(Object.keys(flattenedSelectors).length, 3);
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorA"));
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorB"));
+ assert.true(flattenedSelectors.hasOwnProperty("dependencySelector1"));
+ assert.is(flattenedSelectors.dependencySelector1(), 20);
+ // this is a crucial test, the composeSelectors function must also trigger
+ // the first run of createSelector's returned selector generator which
+ // changes its behaviour. From now on this function will act like
+ // a selector created by reselect's createSelector
+ assert.is(plugin0.selectors.simpleSelectorA(), 10);
+ assert.is(plugin0.selectors.simpleSelectorB(), 2);
+ assert.is(plugin0.selectors.dependencySelector1(), 20);
+});
+
+test('name me', (assert) => {
+ const plugin0 = {
+ selectors: {
+ simpleSelectorA: () => 10,
+ simpleSelectorB: () => 2,
+ dependencySelector1: createSelector(
+ 'simpleSelectorA',
+ 'simpleSelectorB',
+ (x, y) => x * y
+ )
+ }
+ }
+
+ const plugin1 = {
+ selectors: {
+ dependencySelector1: createSelector(
+ 'simpleSelectorA',
+ 'simpleSelectorB',
+ (x, y) => x + y
+ )
+ }
+ }
+
+ const flattenedSelectors = composeSelectors([plugin0, plugin1]);
+
+ assert.is(typeof flattenedSelectors, "object");
+ assert.is(Object.keys(flattenedSelectors).length, 3);
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorA"));
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorB"));
+ assert.true(flattenedSelectors.hasOwnProperty("dependencySelector1"));
+
+
+ assert.is(flattenedSelectors.simpleSelectorA(), 10);
+ assert.is(flattenedSelectors.simpleSelectorB(), 2);
+ assert.is(flattenedSelectors.dependencySelector1(), 12);
+
+ // this is a crucial test, the composeSelectors function must also trigger
+ // the first run of createSelector's returned selector generator which
+ // changes its behaviour. From now on this function will act like
+ // a selector created by reselect's createSelector
+ assert.is(plugin0.selectors.simpleSelectorA(), 10);
+ assert.is(plugin0.selectors.simpleSelectorB(), 2);
+ assert.is(plugin0.selectors.dependencySelector1(), 12);
+
+ assert.is(plugin1.selectors.dependencySelector1(), 12);
+});
+
+test('name me', (assert) => {
+ const plugin0 = (() => {
+ const simpleSelectorA = () => 10;
+ const simpleSelectorB = () => 2;
+ return {
+ selectors: {
+ simpleSelectorA,
+ simpleSelectorB,
+ dependencySelector1: createSelector(
+ simpleSelectorA,
+ simpleSelectorB,
+ (x, y) => x * y
+ )
+ }
+ }
+ }
+ )();
+
+ const plugin1 = {
+ selectors: {
+ dependencySelector1: createSelector(
+ 'simpleSelectorA',
+ 'simpleSelectorB',
+ (x, y) => x + y
+ )
+ }
+ }
+
+ const flattenedSelectors = composeSelectors([plugin0, plugin1]);
+
+ assert.is(typeof flattenedSelectors, "object");
+ assert.is(Object.keys(flattenedSelectors).length, 3);
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorA"));
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorB"));
+ assert.true(flattenedSelectors.hasOwnProperty("dependencySelector1"));
+
+
+ assert.is(flattenedSelectors.simpleSelectorA(), 10);
+ assert.is(flattenedSelectors.simpleSelectorB(), 2);
+ assert.is(flattenedSelectors.dependencySelector1(), 12);
+
+ // this is a crucial test, the composeSelectors function must also trigger
+ // the first run of createSelector's returned selector generator which
+ // changes its behaviour. From now on this function will act like
+ // a selector created by reselect's createSelector
+ assert.is(plugin0.selectors.simpleSelectorA(), 10);
+ assert.is(plugin0.selectors.simpleSelectorB(), 2);
+ // this selector was declared using selector function arguments
+ // instead of selector dependency arguments, this means it
+ // will NOT be overridden and it should keep its original
+ // behaviour. Note that only the function in the PLUGIN
+ // maintains this behaviour, the function returned in
+ // flattenedSelectors uses the overridden dependencySelector1
+ // from plugin1 as it was the most recently declared selector
+ assert.is(plugin0.selectors.dependencySelector1(), 20);
+
+ assert.is(plugin1.selectors.dependencySelector1(), 12);
+});
+
+test('composeSelectors called with mixed selector function and selector dependency createSelector selectors', (assert) => {
+ const plugin0 = (() => {
+ const simpleSelectorA = () => 10;
+ const simpleSelectorB = () => 2;
+ return {
+ selectors: {
+ simpleSelectorA,
+ simpleSelectorB,
+ dependencySelector1: createSelector(
+ 'simpleSelectorA',
+ 'simpleSelectorB',
+ (x, y) => x * y
+ ),
+ dependencySelector2: createSelector(
+ simpleSelectorA,
+ 'simpleSelectorB',
+ (x, y) => x * y
+ )
+ }
+ }
+ }
+ )();
+
+ const plugin1 = {
+ selectors: {
+ simpleSelectorA: () => 40,
+ simpleSelectorB: () => 1
+ }
+ }
+
+ const flattenedSelectors = composeSelectors([plugin0, plugin1]);
+
+ assert.is(typeof flattenedSelectors, "object");
+ assert.is(Object.keys(flattenedSelectors).length, 4);
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorA"));
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorB"));
+ assert.true(flattenedSelectors.hasOwnProperty("dependencySelector1"));
+ assert.true(flattenedSelectors.hasOwnProperty("dependencySelector2"));
+
+ /* In the scenario, dependencySelector2 is using hybrid selection function
+ * and selector dependency arguments. This means that the selector dependency
+ * argument 'simpleSelectorB' is open to be overridden, it will use whatever
+ * is the latest version of simpleSelectorB, in this case the one from plugin1.
+ * However, the selector function argument simpleSelectorA will NEVER be overridden
+ * for this selector and will thus use the simpleSelectorA function as statically
+ * defined in plugin0. Selector function arguments will always point to the function
+ * they were originally referencing. PLEASE NOTE that if the static function
+ * the argument references is ITSELF LATER OVERRIDDEN it will of course use the new
+ * overridden version.
+ */
+
+
+ assert.is(flattenedSelectors.simpleSelectorA(), 40);
+ assert.is(flattenedSelectors.simpleSelectorB(), 1);
+ assert.is(flattenedSelectors.dependencySelector1(), 40);
+ assert.is(flattenedSelectors.dependencySelector2(), 10);
+
+ // this is a crucial test, the composeSelectors function must also trigger
+ // the first run of createSelector's returned selector generator which
+ // changes its behaviour. From now on this function will act like
+ // a selector created by reselect's createSelector
+ assert.is(plugin0.selectors.simpleSelectorA(), 10);
+ assert.is(plugin0.selectors.simpleSelectorB(), 2);
+ // this selector was declared using selector function arguments
+ // instead of selector dependency arguments, this means it
+ // will NOT be overridden and it should keep its original
+ // behaviour. Note that only the function in the PLUGIN
+ // maintains this behaviour, the function returned in
+ // flattenedSelectors uses the overridden dependencySelector1
+ // from plugin1 as it was the most recently declared selector
+ assert.is(plugin0.selectors.dependencySelector1(), 40);
+ assert.is(plugin0.selectors.dependencySelector2(), 10);
+
+ assert.is(plugin1.selectors.simpleSelectorA(), 40);
+ assert.is(plugin1.selectors.simpleSelectorB(), 1);
+});
+
+test('name me', (assert) => {
+ const plugin0 = {
+ selectors: {
+ simpleSelectorA: () => 10,
+ simpleSelectorB: () => 2,
+ dependencySelector1: createSelector(
+ 'simpleSelectorA',
+ 'simpleSelectorB',
+ (x, y) => x * y
+ )
+ }
+ }
+
+ const flattenedSelectors = composeSelectors([plugin0]);
+
+ const createdDependencySelector1 = plugin0.selectors.dependencySelector1.factory();
+
+ assert.is(typeof flattenedSelectors, "object");
+ assert.is(Object.keys(flattenedSelectors).length, 3);
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorA"));
+ assert.true(flattenedSelectors.hasOwnProperty("simpleSelectorB"));
+ assert.true(flattenedSelectors.hasOwnProperty("dependencySelector1"));
+ assert.is(flattenedSelectors.dependencySelector1(), 20);
+ // this is a crucial test, the composeSelectors function must also trigger
+ // the first run of createSelector's returned selector generator which
+ // changes its behaviour. From now on this function will act like
+ // a selector created by reselect's createSelector
+ assert.is(plugin0.selectors.simpleSelectorA(), 10);
+ assert.is(plugin0.selectors.simpleSelectorB(), 2);
+ assert.is(plugin0.selectors.dependencySelector1(), 20);
+
+ assert.is(plugin0.selectors.dependencySelector1.factory()(), 20);
+ assert.is(plugin0.selectors.dependencySelector1(), 20);
+ assert.is(flattenedSelectors.dependencySelector1.factory()(), 20);
+ assert.is(flattenedSelectors.dependencySelector1(), 20);
+
+ assert.is(flattenedSelectors.dependencySelector1.factory({simpleSelectorA: () => 30})(), 60);
+ assert.is(flattenedSelectors.dependencySelector1(), 20);
+ assert.is(plugin0.selectors.dependencySelector1.factory({simpleSelectorA: () => 20})(), 40);
+ assert.is(plugin0.selectors.dependencySelector1(), 20);
+});
diff --git a/src/utils/compositionUtils.js b/src/utils/compositionUtils.js
index 78b68177..4e47ebe0 100644
--- a/src/utils/compositionUtils.js
+++ b/src/utils/compositionUtils.js
@@ -283,22 +283,49 @@ export function getReducersByWordEnding(reducers, ending) {
*/
export function wrapMethodsByWordEnding(componentArray, wordEnding, keyReplaceString = '') {
return componentArray.reduce((previous, current) => {
- let newObject = {};
+ let newObject = {},
+ mergedObject = previous;
for(var key in current) {
const keyWithoutEnhancer = key.replace(wordEnding, keyReplaceString);
if(key.endsWith(wordEnding) && (previous.hasOwnProperty(keyWithoutEnhancer) || current.hasOwnProperty(keyWithoutEnhancer))) {
// Determine if we are working with an HoC that wraps another HoC
- newObject[keyWithoutEnhancer] = keyWithoutEnhancer.endsWith('Container') || keyWithoutEnhancer.endsWith('Enhancer') ?
+ if(keyWithoutEnhancer.endsWith('Container') || keyWithoutEnhancer.endsWith('Enhancer')) {
// If we are enhancing a container or enhancer flow this stuff since it's likely an HoC
- _.flowRight(current[key], (current[keyWithoutEnhancer] || previous[keyWithoutEnhancer])) :
+ newObject[keyWithoutEnhancer] = _.flowRight(current[key], (current[keyWithoutEnhancer] || previous[keyWithoutEnhancer]));
+ } else {
// Wrap the current component in the Enhancer or container
- current[key](current[keyWithoutEnhancer] || previous[keyWithoutEnhancer])
+ if(Array.isArray(current[key])) {
+ newObject[keyWithoutEnhancer] = current[key].reduce((previousComponent, currentComponent) => {
+ if(previousComponent !== undefined) {
+ return currentComponent(previousComponent);
+ } else {
+ return currentComponent(current[keyWithoutEnhancer]);
+ }
+ }, undefined);
+ } else {
+ newObject[keyWithoutEnhancer] = current[key](current[keyWithoutEnhancer] || previous[keyWithoutEnhancer])
+ }
+
+ }
+ }
+ if(mergedObject[key] === undefined) {
+ mergedObject[key] = current[key];
+ } else {
+ if(key.endsWith('Enhancer')) {
+ if(Array.isArray(mergedObject[key])) {
+ mergedObject[key].push(current[key]);
+ }else {
+ mergedObject[key] = [mergedObject[key], current[key]];
+ }
+ } else {
+ mergedObject[key] = current[key];
+ }
}
}
- return _.pickBy(Object.assign(previous, current, newObject), (v, k) => (!k.endsWith(wordEnding))) ;
+ return _.pickBy(Object.assign(mergedObject, newObject), (v, k) => (!k.endsWith(wordEnding))) ;
}, {})
}
diff --git a/src/utils/index.js b/src/utils/index.js
index 94c5d6fa..6e0daef8 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -3,6 +3,7 @@ import * as compositionUtils from './compositionUtils';
import * as dataUtils from './dataUtils';
import * as rowUtils from './rowUtils';
import * as sortUtils from './sortUtils';
+import * as selectorUtils from './selectorUtils';
import { connect } from './griddleConnect';
export default {
@@ -12,4 +13,5 @@ export default {
rowUtils,
sortUtils,
connect,
+ selectorUtils
};
diff --git a/src/utils/listenerUtils.js b/src/utils/listenerUtils.js
index 086162bd..4868208f 100644
--- a/src/utils/listenerUtils.js
+++ b/src/utils/listenerUtils.js
@@ -27,7 +27,7 @@ export const StoreListener = class StoreListener {
// if no name is provided, do nothing
name && this.removeListener(name);
const unsubscribe = (() => {
- let oldState;
+ let oldState = this.store.getState();
return this.store.subscribe(() => {
const newState = this.store.getState();
listener(oldState, newState, {...otherArgs});
diff --git a/src/utils/selectorUtils.js b/src/utils/selectorUtils.js
new file mode 100644
index 00000000..89e42361
--- /dev/null
+++ b/src/utils/selectorUtils.js
@@ -0,0 +1,397 @@
+import { forOwn, size, values } from 'lodash';
+import { createSelector } from 'reselect'
+
+const DEP = "D";
+const FUNC = "F";
+
+const globalSelectors = {};
+
+/*
+ * Wrapped 'createSelector' that allows for building the selector
+ * dependency tree. Takes any number of arguments, all arguments but the
+ * last must be dependencies, which are the string names of selectors
+ * this selector depends on and the last arg must be the selector function
+ * itself. This structure mirrors very closely what calling 'createSelector'
+ * looks like.
+ *
+ * const mySelector = createSelector(
+ * aSelector,
+ * anotherSelector,
+ * (a, b) => (someLogic....)
+ * );
+ *
+ * const mySelector = griddleCreateSelector(
+ * "aSelector",
+ * "anotherSelector",
+ * (a, b) => (someLogic...)
+ * );
+ *
+ * When the selectors are finally generated, the actual dependency selectors
+ * are passed to the createSelector function.
+ */
+const griddleCreateSelector = (...args) => {
+
+ // All selectors that use createSelector must have a minimum of one
+ // dependency and the selector function itself
+ if (args.length < 2) {
+ throw new Error("Cannot create a selector with fewer than 2 arguments, must have at least one dependency and the selector function");
+ }
+
+ // The first n - 1 args are the dependencies, they must
+ // all be strings.
+ //const dependencies = args.slice(0, args.length - 1);
+
+
+ // The first n - 1 args are selector functions AND/OR
+ // selector dependencies.
+ //
+ // If all arguments are selector functions, this would be
+ // considered a 'legacy' style hard-coded createSelector
+ // invocation. We can immediately delegate to createSelector
+ // and return the selector function.
+ //
+ // If all of the arguments are selector dependencies --
+ // which are strings naming the to-be-resolved selector functions --
+ // then we need to return a selector generator function. This will
+ // later be provided with a list of fully resolved selector
+ // functions that can then be used to generate this selector
+ // using createSelector.
+ //
+ // If there is a MIXTURE of both selector functions and
+ // selector dependency strings, then we still need a
+ // selector generator but only a portion of the functions
+ // need to be resolved for later. We save the provided selector
+ // functions and will merge them with the resolved selectors
+ // when the generator function is called. This could be used
+ // to force a particular dependency from being overridden.
+ // This should be considered an advanced feature, you should
+ // only prevent dependent selector overriding if there is a
+ // very good reason for it.
+ //
+ // As this griddleCreateSelector function can return either the
+ // output of reselect's 'createSelector', or a selector generator
+ // function, the distinguishing feature will be that the selector
+ // generator will have as a prop the .dependencies field which
+ // is a list of the string names of the dependencies for this
+ // generator. If this is a mixed type generator, the length of
+ // the dependencies will be smaller than args.length - 1 based
+ // on how many selector function args were provided.
+ const depsOrFuncs = args.slice(0, args.length - 1);
+ const dofTypeMap = [];
+ const dependencies = {};
+ const functions = {};
+ for (const index in depsOrFuncs) {
+ const depOrFunc = depsOrFuncs[index];
+ switch(typeof depOrFunc) {
+ case "function":
+ dofTypeMap[index] = FUNC;
+ functions[index] = depOrFunc;
+ break;
+ case "string":
+ dofTypeMap[index] = DEP;
+ dependencies[index] = depOrFunc;
+ break;
+ default:
+ throw new Error("The first n - 1 arguments of griddleCreateSelector must be either strings or functions");
+ }
+ }
+
+ // The last of n args is the selector function,
+ // it must be a function
+ const selector = args[args.length - 1];
+ if (typeof selector !== "function") {
+ throw new Error("Last argument must be a function");
+ }
+
+ // If there are a number of function arguments equal to the
+ // first n - 1 arguments, this is a fully hard coded selector,
+ // call createSelector and return.
+ if (size(functions) === args.length - 1) {
+ return createSelector(...args);
+ }
+
+ // Otherwise, this is either a mixed or fully string dependency
+ // selector. Create a selector generator.
+
+ //const selectorGeneratorWrapper = (() => {
+
+ return (() => {
+
+ const createSelectorFuncs = new Map();
+ const ownSelector = selector;
+
+ const factory = (resolvedSelectors = {}) => {
+ const selectors = [];
+ if (!createSelectorFuncs.size) {
+ for (const index in dofTypeMap) {
+ const dofType = dofTypeMap[index];
+ switch(dofType) {
+ case FUNC:
+ //createSelectorFuncs.push(functions[index]);
+ createSelectorFuncs.set(index, functions[index]);
+ break;
+ case DEP:
+ if (resolvedSelectors.hasOwnProperty(dependencies[index])) {
+ //createSelectorFuncs.push(resolvedSelectors[dependencies[index]]);
+ createSelectorFuncs.set(dependencies[index], resolvedSelectors[dependencies[index]]);
+ } else {
+ throw new Error(`Dependency ${dependencies[index]} not found!`);
+ }
+ break;
+ }
+ }
+ createSelectorFuncs.forEach((func) => selectors.push(func));
+ selectors.push(ownSelector);
+ } else {
+ createSelectorFuncs.forEach((func, key) => {
+ if (resolvedSelectors.hasOwnProperty(key)) {
+ selectors.push(resolvedSelectors[key]);
+ } else {
+ selectors.push(func);
+ }
+ });
+ selectors.push(ownSelector);
+ }
+
+ return createSelector(...selectors);
+ };
+
+ const selectorGenerator = (...args) => {
+ if (!selectorGenerator.generatedSelector) {
+ const resolvedSelectors = args[0];
+
+ //const createSelectorFuncs = [];
+ //for (const index in dofTypeMap) {
+ // const dofType = dofTypeMap[index];
+ // switch(dofType) {
+ // case FUNC:
+ // createSelectorFuncs.push(functions[index]);
+ // break;
+ // case DEP:
+ // if (resolvedSelectors.hasOwnProperty(dependencies[index])) {
+ // createSelectorFuncs.push(resolvedSelectors[dependencies[index]]);
+ // } else {
+ // throw new Error(`Dependency ${dependencies[index]} not found!`);
+ // }
+ // break;
+ // }
+ //}
+
+ //// add this selector
+ //createSelectorFuncs.push(selector);
+
+ // call createSelector with the final list of args
+ //selectorGenerator.generatedSelector = createSelector(...createSelectorFuncs);
+ selectorGenerator.generatedSelector = factory(resolvedSelectors);
+ //selectorGenerator.generated = true;
+
+ // can probably just return this, as calls to this function
+ // will now flow into just calling the generated selector function
+ return selectorGenerator;
+
+ // Selector was called directly in legacy code
+ //return createSelector(...createSelectorFuncs)(selectors);
+ } else {
+ return selectorGenerator.generatedSelector(...args)
+ }
+ }
+ selectorGenerator.createSelectorFuncs = createSelectorFuncs;
+ selectorGenerator.ownSelector = ownSelector;
+ selectorGenerator.factory = factory;
+ selectorGenerator.generatedSelector = undefined;
+ selectorGenerator.dependencies = values(dependencies);
+ return selectorGenerator;
+ })();
+
+ // attach the list of string dependencies to the
+ // selector generator
+ //selectorGeneratorWrapper.dependencies = values(dependencies);
+ //return selectorGeneratorWrapper;
+};
+export { griddleCreateSelector as createSelector };
+
+
+export const composeSelectors = (plugins) => {
+
+ // STEP 1
+ // ==========
+ //
+ // Add all selectors to the list of combined selectors.
+ //
+ // Each key in combinedSelectors corresponds to
+ // an array of selectors that were encountered for that given name.
+ // A newer selector that is encountered for a given name is unshifted
+ // onto index 0 of the array such at all index 0's of each array
+ // are the most 'recently' encountered selector for that name. This allows
+ // use to keep track of all the places these selectors were declared so
+ // that when finally building the selectors we can go back to these
+ // references and set them correctly. This specifically allows for the
+ // overriding functionality to work properly with 'hard' import references
+ // to selectors.
+ //
+ // Each encountered selector function is wrapped in an object which is used
+ // to keep track of all the data needed to properly build all the
+ // selector dependency trees
+ const combinedSelectors = new Map();
+
+ plugins.forEach((plugin) => {
+ console.log('Begin parsing selectors for plugin');
+ forOwn(plugin.selectors, (selector, name) => {
+ if (!combinedSelectors.has(name)) {
+ console.log(` First instance of selector ${name} encountered`);
+ combinedSelectors.set(name, [{
+ name,
+ selector,
+ dependencies: selector.dependencies || [],
+ rank: 0,
+ traversed: false
+ }]);
+ } else {
+ console.log(` Overriding existing selector named ${name}`);
+ combinedSelectors.get(name).unshift({
+ name,
+ selector,
+ dependencies: selector.dependencies || [],
+ rank: 0,
+ traversed: false
+ });
+ }
+ });
+ });
+
+ // RANKS
+ // ==========
+ //
+ // The ranks array is populated when running getDependencies
+ // It stores the selectors based on their 'rank'
+ // Rank can be defined recursively as:
+ // - if a selector has no dependencies, rank is 0
+ // - if a selector has 1 or more dependencies, rank is max(all dependency ranks) + 1
+ const ranks = [];
+
+ // GET DEPENDENCIES
+ // ==========
+ //
+ // getDependencies recursively descends through the dependencies
+ // of a given selector doing several things:
+ // - creates a 'flat' list of dependencies for a given selector,
+ // which is a list of all of its dependencies
+ // - calculates the rank of each selector and fills out the above ranks list
+ // - determines if there are any cycles present in the dependency tree
+ //
+ // It also memoizes the results in the combinedSelectors Map by setting the
+ // 'traversed' flag for a given selector. If a selector has been flagged as
+ // 'traversed', it simply returns the previously calculated dependencies
+ const getDependencies = (node, parents) => {
+ // if this node has already been traversed
+ // no need to run the get dependencies logic as they
+ // have already been computed
+ // simply return its list of flattened dependencies
+ if (!node.traversed) {
+
+ // if the node has dependencies, add each one to the node's
+ // list of flattened dependencies and recursively call
+ // getDependencies on each of them
+ if (node.dependencies.length > 0) {
+
+ const flattenedDependencies = new Set();
+ for (let dependency of node.dependencies) {
+ //if (typeof dependency === 'function') continue;
+ if (!combinedSelectors.has(dependency)) {
+ const err = `Selector ${node.name} has dependency ${dependency} but this is not in the list of dependencies! Did you misspell something?`;
+ throw new Error(err);
+ }
+
+ // if any dependency in the recursion chain
+ // matches one of the parents there is a cycle throw an exception
+ // this is an unrecoverable runtime error
+ if (parents.has(dependency)) {
+ let err = "Dependency cycle detected! ";
+ for (let e of parents) {
+ e === dependency ? err += `[[${e}]] -> ` : err += `${e} -> `;
+ }
+ err += `[[${dependency}]]`;
+ console.log(err);
+ throw new Error(err);
+ }
+ flattenedDependencies.add(dependency);
+ const childParents = new Set(parents);
+ childParents.add(dependency);
+ const childsDependencies = getDependencies(combinedSelectors.get(dependency)[0], childParents);
+ childsDependencies.forEach((key) => flattenedDependencies.add(key))
+ const childRank = combinedSelectors.get(dependency)[0].rank;
+ childRank >= node.rank && (node.rank = childRank + 1);
+ }
+ node.flattenedDependencies = flattenedDependencies;
+ node.traversed = true;
+
+ } else {
+
+ // otherwise, this is a leaf node
+ // - set the node's rank to 0
+ // - set the nodes flattenedDependencies to an empty set
+ node.flattenedDependencies = new Set();
+ node.traversed = true;
+ }
+ ranks[node.rank] || (ranks[node.rank] = new Array());
+ ranks[node.rank].push(node);
+ }
+ return node.flattenedDependencies;
+ };
+
+
+ // STEP 4
+ // ==========
+ //
+ // Run getDependencies on each first selector in the 'combinedSelectors' list
+ // This fills out the 'ranks' list for use in the next step
+ for (let e of combinedSelectors) {
+ const [name, selectorChain] = e;
+ getDependencies(selectorChain[0], new Set([name]));
+ }
+
+ // STEP 5
+ // ==========
+ //
+ // Create a flat object of just the actual selector functions
+ // This will be used as the set of selectors on context
+ const flattenedSelectors = {};
+ //console.log({ allSelectors, combinedSelectors, ranks });
+ console.log(ranks);
+ for (let rank of ranks) {
+ for (let selector of rank) {
+ //checking if the selector is generated may not be necessary?
+ if (selector.dependencies.length && !selector.selector.generatedSelector) {
+
+ const generatedSelector = selector.selector(flattenedSelectors);
+
+ const selectorsOfName = combinedSelectors.get(selector.name);
+
+ selectorsOfName.slice(1, selectorsOfName.length).forEach((selectorOfName) => {
+ if (selectorOfName.dependencies.length) {
+ selectorOfName.selector.createSelectorFuncs = generatedSelector.createSelectorFuncs;
+ selectorOfName.selector.generatedSelector = generatedSelector.generatedSelector;
+ }
+ });
+
+ flattenedSelectors[selector.name] = generatedSelector;
+
+ //const childSelectors = { _dependencies: true };
+ //for (let childSelector of selector.dependencies) {
+ // if (typeof childSelector === 'string') {
+ // childSelectors[childSelector] = combinedSelectors.get(childSelector).selector;
+ // }
+ //}
+ //flattenedSelectors[selector.name] = selector.selector(childSelectors);
+ }
+ else {
+ flattenedSelectors[selector.name] = selector.selector;
+ }
+ }
+ }
+
+ // Work-around for direct references to composed selectors
+ //Object.assign(globalSelectors, flattenedSelectors);
+
+ return flattenedSelectors;
+}
diff --git a/stories/index.tsx b/stories/index.tsx
index 58be9ea2..96705708 100644
--- a/stories/index.tsx
+++ b/stories/index.tsx
@@ -9,13 +9,16 @@ import withHandlers from 'recompose/withHandlers';
import withState from 'recompose/withState';
import { Provider, connect as reduxConnect } from 'react-redux';
import { createStore } from 'redux';
-import { createSelector } from 'reselect';
+//import { createSelector } from 'reselect';
import _ from 'lodash';
+import { createLogger } from 'redux-logger';
import GenericGriddle, { connect, actions, components, selectors, plugins, utils, ColumnDefinition, RowDefinition, GriddleProps } from '../src/module';
const { Cell, Row, Table, TableContainer, TableBody, TableHeading, TableHeadingCell } = components;
const { SettingsWrapper, SettingsToggle, Settings } = components;
+const { createSelector } = utils.selectorUtils;
+
const { LegacyStylePlugin, LocalPlugin, PositionPlugin } = plugins;
import fakeData, { FakeData } from './fakeData';
@@ -955,6 +958,147 @@ storiesOf('Plugins', module)
);
})
+ .add('Overridable selectors in plugin', () => {
+
+ const getNext = () => {
+ return {
+ type: "GRIDDLE_NEXT_PAGE"
+ };
+ };
+
+ const getPrevious = () => {
+ return {
+ type: "GRIDDLE_PREVIOUS_PAGE"
+ };
+ };
+
+ const setPage = (pageNumber) => {
+ return {
+ type: "GRIDDLE_SET_PAGE",
+ pageNumber
+ };
+ };
+
+ const GRIDDLE_NEXT_PAGE = (state, action) => {
+ const currentPage = state.getIn(["pageProperties", "currentPage"]);
+ const pageSize = state.getIn(["pageProperties", "pageSize"]);
+ const recordCount = state.get("data").size;
+ const maxPage = Math.ceil(recordCount/pageSize);
+
+ if (currentPage + 1 <= maxPage) {
+ return state.setIn(["pageProperties", "currentPage"], currentPage + 1);
+ } else {
+ return state;
+ }
+ };
+
+ const GRIDDLE_PREVIOUS_PAGE = (state, action) => {
+ const currentPage = state.getIn(["pageProperties", "currentPage"]);
+ const minPage = 1;
+
+ if (currentPage - 1 >= minPage) {
+ return state.setIn(["pageProperties", "currentPage"], currentPage - 1);
+ } else {
+ return state;
+ }
+ };
+
+ const GRIDDLE_SET_PAGE = (state, action) => {
+ const pageNumber = action.pageNumber;
+ const pageSize = state.getIn(["pageProperties", "pageSize"]);
+ const recordCount = state.get("data").size;
+ const maxPage = Math.ceil(recordCount/pageSize);
+ const minPage = 1;
+
+ if (pageNumber >= minPage && pageNumber <= maxPage) {
+ return state.setIn(["pageProperties", "currentPage"], pageNumber);
+ } else {
+ return state;
+ }
+ };
+
+ const allDataSelector = (state) => state.get("data");
+
+ const recordCountSelector = state => state.get("data").size;
+
+ const dataSelector = createSelector (
+ "allDataSelector",
+ "pageSizeSelector",
+ "currentPageSelector",
+ "recordCountSelector",
+ (data, pageSize, currentPage, recordCount) => {
+ currentPage = currentPage - 1;
+ const first = currentPage * pageSize;
+ const last = Math.min((currentPage + 1) * pageSize, recordCount);
+ return data.slice(first, last);
+ }
+ );
+
+ const NextButtonEnhancer = OriginalComponent => compose(
+ connect(
+ null,
+ (dispatch, props) => {
+ return {
+ getNext: () => dispatch(getNext())
+ }
+ }
+ )
+ )((props) => );
+
+ const PageDropdownEnhancer = OriginalComponent => compose(
+ connect(
+ null,
+ (dispatch, props) => {
+ return {
+ setPage: (page) => dispatch(setPage(page))
+ }
+ }
+ )
+ )((props) => );
+
+ const PreviousButtonEnhancer = OriginalComponent => compose(
+ connect(
+ null,
+ (dispatch, props) => {
+ return {
+ getPrevious: () => dispatch(getPrevious())
+ }
+ }
+ )
+ )((props) => );
+
+
+ const OverridableSelectorsPlugin = {
+ components: {
+ NextButtonEnhancer,
+ PageDropdownEnhancer,
+ PreviousButtonEnhancer
+ },
+ reducer: {
+ GRIDDLE_NEXT_PAGE,
+ GRIDDLE_PREVIOUS_PAGE,
+ GRIDDLE_SET_PAGE
+ },
+ selectors: {
+ allDataSelector,
+ recordCountSelector,
+ dataSelector
+ },
+ composedSelectors: {
+ dataSelector
+ }
+ };
+
+ const AnotherPlugin = {
+ selectors: {
+ someSelector: (state) => null
+ }
+ }
+
+ return (
+
+ );
+ })
storiesOf('Data Missing', module)
.add('base (data=undefined)', () => {
diff --git a/yarn.lock b/yarn.lock
index 4561917b..d859aa1a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2316,6 +2316,10 @@ decamelize@^1.0.0, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+deep-diff@^0.3.5:
+ version "0.3.8"
+ resolved "https://registry.npmjs.intuit.net/d/deep-diff/_attachments/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
+
deep-equal@^1.0.0, deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@@ -5354,6 +5358,12 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
+redux-logger@^3.0.6:
+ version "3.0.6"
+ resolved "https://registry.npmjs.intuit.net/r/redux-logger/_attachments/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
+ dependencies:
+ deep-diff "^0.3.5"
+
redux@^3.5.2, redux@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"