paginated-redux
Version:
A higher order reducer (or transducer) that adds pagination, sorting, and filtering to a reducer of an array of objects.
229 lines (192 loc) • 8.98 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
// Return a new array, a subset of `list`, which matches `filter`. Assumes an
// array of objects and cyclers through each object, and looks at each property,
// and compares all string properties to the value of the `filter` string,
// returning only those which contain an exact match.
var filteredList = function filteredList() {
var filter = arguments.length <= 0 || arguments[0] === undefined ? '' : arguments[0];
var list = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
if (filter) {
return list.filter(function (el) {
return Object.keys(el).some(function (prop) {
return el[prop] && typeof el[prop] === 'string' && el[prop].toLowerCase().indexOf(filter.toLowerCase()) >= 0;
});
});
}
return list;
};
// Return `list` sorted by `prop` in either ascending or decending order based
// on the value of `order` (either 'asc' or 'desc').
var sortedList = function sortedList() {
var prop = arguments.length <= 0 || arguments[0] === undefined ? 'name' : arguments[0];
var order = arguments.length <= 1 || arguments[1] === undefined ? 'asc' : arguments[1];
var list = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];
return list.sort(function (compA, compB) {
var a = compA;
var b = compB;
// If a prop is provided to sort by, assume each element is an object with
// a property called `prop`.
if (prop) {
a = compA[prop];
b = compB[prop];
}
if (a > b) return order === 'asc' ? 1 : -1;
if (a < b) return order === 'asc' ? -1 : 1;
return 0;
});
};
// Return a new array that is the reverse of `list`.
var reversedList = function reversedList(list) {
return list.slice().reverse();
};
// Return the total number of pages that can be made from `list`.
var totalPages = function totalPages() {
var per = arguments.length <= 0 || arguments[0] === undefined ? 10 : arguments[0];
var list = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var total = Math.ceil(list.length / per);
return total ? total : 0;
};
// Return a slice of all `list` starting at `start` up to `per`
// (or the length of list; whichever comes first).
var slicedList = function slicedList() {
var page = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];
var per = arguments.length <= 1 || arguments[1] === undefined ? 10 : arguments[1];
var list = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];
var start = (page - 1) * per;
var end = per === 0 ? list.length : start + per;
return end === list.length ? list.slice(start) : list.slice(start, end);
};
// params:
// 1. the reducer being augmented
// 2. definitions of action types
// 3. options
var paginated = function paginated(reducer) {
var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var _ref$GOTO_PAGE = _ref.GOTO_PAGE;
var GOTO_PAGE = _ref$GOTO_PAGE === undefined ? 'GOTO_PAGE' : _ref$GOTO_PAGE;
var _ref$NEXT_PAGE = _ref.NEXT_PAGE;
var NEXT_PAGE = _ref$NEXT_PAGE === undefined ? 'NEXT_PAGE' : _ref$NEXT_PAGE;
var _ref$PREV_PAGE = _ref.PREV_PAGE;
var PREV_PAGE = _ref$PREV_PAGE === undefined ? 'PREV_PAGE' : _ref$PREV_PAGE;
var _ref$FILTER = _ref.FILTER;
var FILTER = _ref$FILTER === undefined ? 'FILTER' : _ref$FILTER;
var _ref$SORT = _ref.SORT;
var SORT = _ref$SORT === undefined ? 'SORT' : _ref$SORT;
var _ref2 = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
var _ref2$defaultPage = _ref2.defaultPage;
var defaultPage = _ref2$defaultPage === undefined ? 1 : _ref2$defaultPage;
var _ref2$defaultSortOrde = _ref2.defaultSortOrder;
var defaultSortOrder = _ref2$defaultSortOrde === undefined ? 'asc' : _ref2$defaultSortOrde;
var _ref2$defaultSortBy = _ref2.defaultSortBy;
var defaultSortBy = _ref2$defaultSortBy === undefined ? 'name' : _ref2$defaultSortBy;
var _ref2$defaultPer = _ref2.defaultPer;
var defaultPer = _ref2$defaultPer === undefined ? 10 : _ref2$defaultPer;
var _ref2$defaultFilter = _ref2.defaultFilter;
var defaultFilter = _ref2$defaultFilter === undefined ? '' : _ref2$defaultFilter;
var _ref2$defaultTotal = _ref2.defaultTotal;
var defaultTotal = _ref2$defaultTotal === undefined ? 0 : _ref2$defaultTotal;
// NOTE: the reducer's array is named "list" at this point.
// TODO: Is there a way to define the name of this property outside this module?
// NOTE: cacheList is a temporary cached array of sorted + filtered elements
// from the total list so that it doesn't need to be re-calculated each time
// the pagedList function is called.
var initialState = {
list: reducer(undefined, {}),
pageList: [],
cacheList: sortedList(defaultSortBy, defaultSortOrder, filteredList(defaultFilter, reducer(undefined, {}))),
page: defaultPage,
total: defaultTotal,
per: defaultPer,
order: defaultSortOrder,
by: defaultSortBy,
filter: defaultFilter
};
return function () {
var state = arguments.length <= 0 || arguments[0] === undefined ? initialState : arguments[0];
var action = arguments[1];
var list = state.list;
var cacheList = state.cacheList;
var page = state.page;
var total = state.total;
var per = state.per;
var order = state.order;
var by = state.by;
var filter = state.filter;
// NOTE: I'm using blocks (i.e., statments wrapped in {}) for a few
// conditions so that I can reuse the same variable const in different
// blocks without causing a duplicate declaration conflicts.
switch (action.type) {
// Go to a specific page. Can be used to initialize the list into a certain
// page state.
case GOTO_PAGE:
return _extends({}, state, {
page: action.page,
pageList: slicedList(action.page, per, cacheList)
});
// If the the action is fired whilst at the end of the list, swing around
// back to the beginning.
case NEXT_PAGE:
var nextPage = page + 1;
if (nextPage > state.list.length - 1) nextPage = 0;
return _extends({}, state, {
page: nextPage,
pageList: slicedList(nextPage, per, cacheList)
});
// If the action is fired whilst already at the beginning of the list,
// swing around to the end of the list (this behaviour can be handled
// differently through the UI if this is not the desired behaviour, for
// example, by simply not presenting the user with the "prev" button at
// all if already on the first page so it is not possible to wrap around).
case PREV_PAGE:
var prevPage = page - 1;
if (prevPage < 0) prevPage = state.list.length - 1;
return _extends({}, state, {
page: prevPage,
pageList: slicedList(prevPage, per, cacheList)
});
// Reset page to 1 as this existing page has lost its meaning due to the
// list changing form.
case FILTER:
{
var newCache = sortedList(by, order, filteredList(action.filter, list));
return _extends({}, state, {
filter: action.filter,
cacheList: newCache,
pageList: slicedList(1, per, newCache)
});
}
// There's a bit of optimization going on here. If the `by` hasn't changed
// (meaning the user clicked on the currently active column), then simply
// reverse the order of the cacheList (which is cheaper than running through
// the entire filter and sort functions). If the `by` has changed, *then*
// run the cacheList through the whole sort/filter combo to get a new list.
case SORT:
{
var newOrder = action.by === by && order === 'asc' ? 'desc' : 'asc';
var _newCache = action.by === by ? reversedList(cacheList) : sortedList(action.by, newOrder, filteredList(filter, list));
return _extends({}, state, {
by: action.by,
order: newOrder,
cacheList: _newCache,
pageList: slicedList(page, per, _newCache)
});
}
// Setup the default list and cache and calculate the total.
default:
{
var newList = reducer(state.list, action);
var _newCache2 = sortedList(by, order, filteredList(filter, newList));
return _extends({}, state, {
list: newList,
cacheList: _newCache2,
pageList: slicedList(page, per, cacheList),
total: totalPages(per, _newCache2)
});
}}
};
};
exports.default = paginated;