Skip to content

Commit

Permalink
feat(connector): pagination connector
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandre Stanislawski committed Feb 15, 2017
1 parent 0dc42d2 commit 7a876f3
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 109 deletions.
144 changes: 144 additions & 0 deletions src/connectors/pagination/connectPagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import defaults from 'lodash/defaults';
import cx from 'classnames';
import {
bemHelper,
getContainerNode,
} from '../../lib/utils.js';

const defaultLabels = {
previous: '‹',
next: '›',
first: '«',
last: '»',
};
const bem = bemHelper('ais-pagination');

/**
* Add a pagination menu to navigate through the results
* @function pagination
* @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @param {Object} [options.labels] Text to display in the various links (prev, next, first, last)
* @param {string} [options.labels.previous] Label for the Previous link
* @param {string} [options.labels.next] Label for the Next link
* @param {string} [options.labels.first] Label for the First link
* @param {string} [options.labels.last] Label for the Last link
* @param {number} [options.maxPages] The max number of pages to browse
* @param {number} [options.padding=3] The number of pages to display on each side of the current page
* @param {string|DOMElement|boolean} [options.scrollTo='body'] Where to scroll after a click, set to `false` to disable
* @param {boolean} [options.showFirstLast=true] Define if the First and Last links should be displayed
* @param {boolean} [options.autoHideContainer=true] Hide the container when no results match
* @param {Object} [options.cssClasses] CSS classes to be added
* @param {string|string[]} [options.cssClasses.root] CSS classes added to the parent `<ul>`
* @param {string|string[]} [options.cssClasses.item] CSS classes added to each `<li>`
* @param {string|string[]} [options.cssClasses.link] CSS classes added to each link
* @param {string|string[]} [options.cssClasses.page] CSS classes added to page `<li>`
* @param {string|string[]} [options.cssClasses.previous] CSS classes added to the previous `<li>`
* @param {string|string[]} [options.cssClasses.next] CSS classes added to the next `<li>`
* @param {string|string[]} [options.cssClasses.first] CSS classes added to the first `<li>`
* @param {string|string[]} [options.cssClasses.last] CSS classes added to the last `<li>`
* @param {string|string[]} [options.cssClasses.active] CSS classes added to the active `<li>`
* @param {string|string[]} [options.cssClasses.disabled] CSS classes added to the disabled `<li>`
* @return {Object}
*/
const usage = `Usage:
pagination({
container,
[ cssClasses.{root,item,page,previous,next,first,last,active,disabled}={} ],
[ labels.{previous,next,first,last} ],
[ maxPages ],
[ padding=3 ],
[ showFirstLast=true ],
[ autoHideContainer=true ],
[ scrollTo='body' ]
})`;
const connectPagination = paginationRendering => ({
container,
cssClasses: userCssClasses = {},
labels: userLabels = {},
maxPages,
padding = 3,
showFirstLast = true,
autoHideContainer = true,
scrollTo: userScrollTo = 'body',
} = {}) => {
let scrollTo = userScrollTo;

if (!container) {
throw new Error(usage);
}

if (scrollTo === true) {
scrollTo = 'body';
}

const containerNode = getContainerNode(container);
const scrollToNode = scrollTo !== false ? getContainerNode(scrollTo) : false;

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
item: cx(bem('item'), userCssClasses.item),
link: cx(bem('link'), userCssClasses.link),
page: cx(bem('item', 'page'), userCssClasses.page),
previous: cx(bem('item', 'previous'), userCssClasses.previous),
next: cx(bem('item', 'next'), userCssClasses.next),
first: cx(bem('item', 'first'), userCssClasses.first),
last: cx(bem('item', 'last'), userCssClasses.last),
active: cx(bem('item', 'active'), userCssClasses.active),
disabled: cx(bem('item', 'disabled'), userCssClasses.disabled),
};

const labels = defaults(userLabels, defaultLabels);

return {
init({helper, createURL}) {
this.setCurrentPage = page => {
helper.setCurrentPage(page);
if (scrollToNode !== false) {
scrollToNode.scrollIntoView();
}
helper.search();
};

this.createURL = state => page => createURL(state.setPage(page));

paginationRendering({
createURL: this.createURL(helper.state),
cssClasses,
currentPage: helper.getPage(),
labels,
nbHits: 0,
nbPages: 0,
padding,
setCurrentPage: this.setCurrentPage,
shouldAutoHideContainer: autoHideContainer,
showFirstLast,
containerNode,
}, true);
},

getMaxPage(results) {
if (maxPages !== undefined) {
return Math.min(maxPages, results.nbPages);
}
return results.nbPages;
},

render({results, state}) {
paginationRendering({
createURL: this.createURL(state),
cssClasses,
currentPage: results.page,
labels,
nbHits: results.nbHits,
nbPages: this.getMaxPage(results),
padding,
setCurrentPage: this.setCurrentPage,
shouldAutoHideContainer: autoHideContainer && results.nbHits === 0,
showFirstLast,
containerNode,
}, false);
},
};
};

export default connectPagination;
6 changes: 3 additions & 3 deletions src/widgets/pagination/__tests__/pagination-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import expectJSX from 'expect-jsx';
expect.extend(expectJSX);
import pagination from '../pagination';
import Pagination from '../../../components/Pagination/Pagination';
import connectPagination from '../../../connectors/pagination/connectPagination.js';

describe('pagination call', () => {
it('throws an exception when no container', () => {
Expand All @@ -24,7 +25,6 @@ describe('pagination()', () => {
beforeEach(() => {
ReactDOM = {render: sinon.spy()};
pagination.__Rewire__('ReactDOM', ReactDOM);
pagination.__Rewire__('autoHideContainerHOC', sinon.stub().returns(Pagination));

container = document.createElement('div');
cssClasses = {
Expand All @@ -44,6 +44,7 @@ describe('pagination()', () => {
helper = {
setCurrentPage: sinon.spy(),
search: sinon.spy(),
getPage: () => 0,
};
widget.init({helper});
});
Expand Down Expand Up @@ -77,7 +78,7 @@ describe('pagination()', () => {
const getContainerNode = sinon.stub().returns({
scrollIntoView,
});
pagination.__Rewire__('getContainerNode', getContainerNode);
connectPagination.__Rewire__('getContainerNode', getContainerNode);
});

it('should not scroll', () => {
Expand Down Expand Up @@ -142,7 +143,6 @@ describe('pagination MaxPage', () => {
beforeEach(() => {
ReactDOM = {render: sinon.spy()};
pagination.__Rewire__('ReactDOM', ReactDOM);
pagination.__Rewire__('autoHideContainerHOC', sinon.stub().returns(Pagination));

container = document.createElement('div');
cssClasses = {
Expand Down
138 changes: 32 additions & 106 deletions src/widgets/pagination/pagination.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import defaults from 'lodash/defaults';
import cx from 'classnames';
import {
bemHelper,
getContainerNode,
} from '../../lib/utils.js';
import autoHideContainerHOC from '../../decorators/autoHideContainer.js';
import PaginationComponent from '../../components/Pagination/Pagination.js';

const defaultLabels = {
previous: '‹',
next: '›',
first: '«',
last: '»',
};
const bem = bemHelper('ais-pagination');
import Pagination from '../../components/Pagination/Pagination.js';
import connectPagination from '../../connectors/pagination/connectPagination.js';

/**
* Add a pagination menu to navigate through the results
Expand Down Expand Up @@ -44,95 +30,35 @@ const bem = bemHelper('ais-pagination');
* @param {string|string[]} [options.cssClasses.disabled] CSS classes added to the disabled `<li>`
* @return {Object}
*/
const usage = `Usage:
pagination({
container,
[ cssClasses.{root,item,page,previous,next,first,last,active,disabled}={} ],
[ labels.{previous,next,first,last} ],
[ maxPages ],
[ padding=3 ],
[ showFirstLast=true ],
[ autoHideContainer=true ],
[ scrollTo='body' ]
})`;
function pagination({
container,
cssClasses: userCssClasses = {},
labels: userLabels = {},
maxPages,
padding = 3,
showFirstLast = true,
autoHideContainer = true,
scrollTo: userScrollTo = 'body',
} = {}) {
let scrollTo = userScrollTo;

if (!container) {
throw new Error(usage);
}

if (scrollTo === true) {
scrollTo = 'body';
}

const containerNode = getContainerNode(container);
const scrollToNode = scrollTo !== false ? getContainerNode(scrollTo) : false;
let Pagination = PaginationComponent;
if (autoHideContainer === true) {
Pagination = autoHideContainerHOC(Pagination);
}

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
item: cx(bem('item'), userCssClasses.item),
link: cx(bem('link'), userCssClasses.link),
page: cx(bem('item', 'page'), userCssClasses.page),
previous: cx(bem('item', 'previous'), userCssClasses.previous),
next: cx(bem('item', 'next'), userCssClasses.next),
first: cx(bem('item', 'first'), userCssClasses.first),
last: cx(bem('item', 'last'), userCssClasses.last),
active: cx(bem('item', 'active'), userCssClasses.active),
disabled: cx(bem('item', 'disabled'), userCssClasses.disabled),
};
export default connectPagination(defaultRendering);

const labels = defaults(userLabels, defaultLabels);

return {
init({helper}) {
this.setCurrentPage = page => {
helper.setCurrentPage(page);
if (scrollToNode !== false) {
scrollToNode.scrollIntoView();
}
helper.search();
};
},

getMaxPage(results) {
if (maxPages !== undefined) {
return Math.min(maxPages, results.nbPages);
}
return results.nbPages;
},

render({results, state, createURL}) {
ReactDOM.render(
<Pagination
createURL={page => createURL(state.setPage(page))}
cssClasses={cssClasses}
currentPage={results.page}
labels={labels}
nbHits={results.nbHits}
nbPages={this.getMaxPage(results)}
padding={padding}
setCurrentPage={this.setCurrentPage}
shouldAutoHideContainer={results.nbHits === 0}
showFirstLast={showFirstLast}
/>,
containerNode
);
},
};
function defaultRendering({
createURL,
cssClasses,
currentPage,
labels,
nbHits,
nbPages,
padding,
setCurrentPage,
shouldAutoHideContainer,
showFirstLast,
containerNode,
}, isFirstRendering) {
if (isFirstRendering) return;
ReactDOM.render(
<Pagination
createURL={createURL}
cssClasses={cssClasses}
currentPage={currentPage}
labels={labels}
nbHits={nbHits}
nbPages={nbPages}
padding={padding}
setCurrentPage={setCurrentPage}
shouldAutoHideContainer={shouldAutoHideContainer}
showFirstLast={showFirstLast}
/>,
containerNode
);
}

export default pagination;

0 comments on commit 7a876f3

Please sign in to comment.