giga-grid
Version:
Massively performant, multi-layered React.js table widget Written in TypeScript
1,184 lines (1,079 loc) • 1.53 MB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("react"), require("react-dom"));
else if(typeof define === 'function' && define.amd)
define(["react", "react-dom"], factory);
else if(typeof exports === 'object')
exports["GigaGrid"] = factory(require("react"), require("react-dom"));
else
root["GigaGrid"] = factory(root["React"], root["ReactDOM"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var GigaGrid_1 = __webpack_require__(1);
exports.GigaGrid = GigaGrid_1.GigaGrid;
var GigaStore_1 = __webpack_require__(7);
exports.GigaActionType = GigaStore_1.GigaActionType;
var ColumnLike_1 = __webpack_require__(6);
exports.AggregationMethod = ColumnLike_1.AggregationMethod;
exports.ColumnFormat = ColumnLike_1.ColumnFormat;
exports.SortDirection = ColumnLike_1.SortDirection;
var Cell_1 = __webpack_require__(49);
exports.Cell = Cell_1.Cell;
var SubtotalAggregator_1 = __webpack_require__(31);
exports.format = SubtotalAggregator_1.format;
var Row_1 = __webpack_require__(33);
exports.Row = Row_1.Row;
var CellRenderer_1 = __webpack_require__(50);
exports.CellRenderer = CellRenderer_1.CellRenderer;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var React = __webpack_require__(2);
var ReactDOM = __webpack_require__(3);
var _ = __webpack_require__(4);
var ColumnLike_1 = __webpack_require__(6);
var GigaStore_1 = __webpack_require__(7);
var flux_1 = __webpack_require__(43);
var FrozenTableBody_1 = __webpack_require__(45);
var ScrollableTableBody_1 = __webpack_require__(162);
var TableHeader_1 = __webpack_require__(164);
var SettingsPopover_1 = __webpack_require__(171);
var ServerStore_1 = __webpack_require__(34);
var $ = __webpack_require__(36);
/**
* The root component of this React library. assembles raw data into `Row` objects which are then translated into their
* virtual DOM representation
*
* The bulk of the table state is stored in `tree`, which contains subtotal and detail rows
* Rows can be hidden if filtered out or sorted among other things, subtotal rows can be collapsed etc
* mutations to the state of table from user initiated actions can be thought of as mutates on the `tree`
*
* **IMPORTANT** GigaGrid the component does not actually mutate its own state nor give its children the ability
* to mutate its state. State mutation is managed entirely by the GigaStore flux Store. Events generated by the
* children of this component are emitted to a central dispatcher and are dispatched to the GigaStore
*
* **Developer Warning** Please DO NOT pass a reference of this component to its children nor call setState() in the component
**/
var GigaGrid = (function (_super) {
__extends(GigaGrid, _super);
function GigaGrid(props) {
var _this = _super.call(this, props) || this;
// This is very bad. Good thing we have started the process to rewrite this
_this.lastYScrollAmount = 0;
_this.encapsulatedScrollHandler = function (e) { return _this.scrollHandler(e); };
_this.encapsulatedWheelScrollHandler = function (e) { return _this.wheelScrollHandler(e); };
_this.dispatcher = new flux_1.Dispatcher();
_this.store = GigaGrid.createStore(props, _this.dispatcher);
_this.state = _this.store.getState();
// do not call setState again, this is the only place! otherwise you are violating the principles of Flux
// not that would be wrong but it would break the 1 way data flow and make keeping track of mutation difficult
_this.store.addListener(function () {
_this.setState(_this.store.getState());
});
return _this;
}
GigaGrid.createStore = function (props, dispatcher) {
if (props.useServerStore)
return new ServerStore_1.ServerStore(dispatcher, props);
else
return new GigaStore_1.GigaStore(dispatcher, props);
};
GigaGrid.prototype.submitColumnConfigChange = function (action) {
this.dispatcher.dispatch(action);
};
GigaGrid.prototype.toggleSettingsPopover = function () {
this.dispatcher.dispatch({
type: GigaStore_1.GigaActionType.TOGGLE_SETTINGS_POPOVER
});
};
GigaGrid.prototype.renderSettingsPopover = function () {
var _this = this;
var state = this.store.getState();
if (state.showSettingsPopover)
return (React.createElement("div", null,
React.createElement(SettingsPopover_1.SettingsPopover, { subtotalBys: state.subtotalBys, columns: state.columns, onSubmit: function (action) { return _this.submitColumnConfigChange(action); }, onDismiss: function () { return _this.toggleSettingsPopover(); }, additionalUserButtons: state.additionalUserButtons })));
else
return null;
};
GigaGrid.prototype.render = function () {
var columns;
var state = this.store.getState();
if (this.props.columnGroups)
columns = ColumnLike_1.ColumnFactory.createColumnsFromGroupDefinition(this.props.columnGroups, state);
else
columns = [state.columns];
var bodyStyle = {};
/**
* As noted in the collapseHeight property of the GigaProps interface, if collapseHeight is true, the table will
* collapse to the height of the table itself it is smaller than the container
*/
if (this.props.collapseHeight)
bodyStyle.maxHeight = this.props.bodyHeight;
else
bodyStyle.height = this.props.bodyHeight;
/**
* We need to figure out what columns go in which sub table depending on how many static left headers there are
*/
var allCols = columns[columns.length - 1];
var leftCols, rightCols;
// Static headers experience a latency issue in internet explorer. Let's not enable it for now
if (isNaN(this.props.staticLeftHeaders)) {
leftCols = [];
rightCols = allCols;
}
else if (allCols.length > this.props.staticLeftHeaders) {
leftCols = _.take(allCols, this.props.staticLeftHeaders);
rightCols = _.takeRight(allCols, allCols.length - this.props.staticLeftHeaders);
}
else
throw "Please declare a staticLeftHeaders prop which is less than the number of columns in the table.";
return (React.createElement("div", { className: "giga-grid giga-grid-" + this.state.gridID },
this.renderSettingsPopover(),
React.createElement("div", { className: "giga-grid-header-container" },
React.createElement(TableHeader_1.TableHeader, { dispatcher: this.dispatcher, columns: columns, tableHeaderClass: this.props.tableHeaderClass, staticLeftHeaders: this.props.staticLeftHeaders, gridProps: this.props })),
React.createElement("div", { ref: function (c) { return state.viewport = c; }, className: "giga-grid-body-viewport", style: bodyStyle },
leftCols.length == 0 ? "" :
React.createElement("div", { className: "giga-grid-left-headers-container" },
React.createElement("div", { className: "giga-grid-body-canvas" },
React.createElement(FrozenTableBody_1.FrozenTableBody, { dispatcher: this.dispatcher, rows: state.rasterizedRows, columns: leftCols, displayStart: state.displayStart, displayEnd: state.displayEnd, rowHeight: this.props.rowHeight, gridProps: this.props }))),
React.createElement("div", { className: "giga-grid-right-data-container" },
React.createElement("div", { ref: function (c) { return state.canvas = c; }, className: "giga-grid-body-canvas" },
React.createElement(ScrollableTableBody_1.ScrollableTableBody, { dispatcher: this.dispatcher, rows: state.rasterizedRows, columns: rightCols, displayStart: state.displayStart, displayEnd: state.displayEnd, rowHeight: this.props.rowHeight, gridProps: this.props }))))));
};
GigaGrid.prototype.componentWillReceiveProps = function (nextProps) {
var neverReinitialize = this.props.neverReinitialize;
if (!neverReinitialize) {
var payload = {
type: GigaStore_1.GigaActionType.INITIALIZE,
props: nextProps
};
this.dispatcher.dispatch(payload);
this.expandTable();
}
};
/**
* on component update, we use jquery to align table headers
* this is the "give up" solution, implemented in 0.1.7
*/
GigaGrid.prototype.componentDidUpdate = function (prevProps, prevState) {
// No longer sync every time new rows are added
if (this.state.rasterizedRows.length !== prevState.rasterizedRows.length ||
this.state.filterBys !== prevState.filterBys ||
this.state.sortBys !== prevState.sortBys ||
this.state.subtotalBys !== prevState.subtotalBys)
this.synchTableHeaderWidthToFirstRow();
var node = ReactDOM.findDOMNode(this);
$(node).parent().find('.giga-grid-left-headers-container').scrollTop(this.lastYScrollAmount);
$(node).parent().parent().find('.giga-grid-right-data-container').scrollTop(this.lastYScrollAmount);
};
/**
* yes this is still a thing!
*/
GigaGrid.prototype.synchTableHeaderWidthToFirstRow = function () {
var node = ReactDOM.findDOMNode(this);
var bodyHeight = this.props.bodyHeight;
var columns = this.state.columns;
/**
* To improve performance, we use our own dynamic stylesheet for giga-grid. jQuery is slow, so by adding
* a class to each cell, labeled with each column number, we are able to simply change the dimensions of a cell
* dynamically by changing the styling directly in the stylesheet, instead of at the element level.
*/
var widths = [];
// Gets with of .content in a cell, or 80, whichever is greater
function getWidthForDataCell(elem) {
var leftPadding = +($(elem).css("padding-left").replace(/[^\d.-]/g, ''));
var rightPadding = +($(elem).css("padding-right").replace(/[^\d.-]/g, ''));
// 80 px is the min width of cell
return Math.max($(elem).find(".content").innerWidth() + leftPadding + rightPadding, 80);
}
// This function alligns header cells and their underlying data cells
function allignColumns($headerContainers, $rows) {
_.forEach($headerContainers, function (header, index) {
var headerWidth = getWidthForDataCell(header);
// Get all data cells underlying this column
var $dataElems = $rows.find(".content-container:nth-of-type(" + (index + 1) + ")");
// Get all widths of underlying data cells, and find the largest
var dataWidths = _.map($dataElems, function (elem) { return getWidthForDataCell(elem); });
var columnWidth = Math.max.apply(null, dataWidths.concat(headerWidth)) + 10; // Adding 10 for padding
// Because we are no longer syncing every time rows are added, make sure the cells don't shrink
columnWidth = Math.max(columnWidth, $(header).innerWidth());
widths.push(columnWidth);
});
}
// Get jQuery objects for four "quadrant" containers
var $leftHeaderContainers = $(node).find(".left-static-headers .table-header");
var $rightHeaderContainers = $(node).find(".right-scrolling-headers .table-header:not(.blank-header-cell)");
var $leftHeaderRows = $(node).find(".giga-grid-left-headers-container .giga-grid-row");
var $dataRows = $(node).find(".giga-grid-right-data-container .giga-grid-row");
// There will never been a horiz scrollbar in the left headers, so give it less height in case it shows up on the right side
var extraScrollbarHeight = getHorizontalScrollbarThickness();
var viewportHeight = bodyHeight ? parseInt(bodyHeight) : $(node).find(".giga-grid-body-viewport").innerHeight();
var $rightDataContainer = $(node).find(".giga-grid-right-data-container");
// Set max height of row containers so scroll bar shows up
$rightDataContainer.css("max-height", viewportHeight);
allignColumns($leftHeaderContainers, $leftHeaderRows);
allignColumns($rightHeaderContainers, $dataRows);
// Overwrite with width size if explicitly stated
widths = widths.map(function (w, idx) { return columns[idx].width || w; });
var gigaGridWidth = $(node).innerWidth();
var sumOfHeaderWidths = widths.reduce(function (sum, memo) { return sum + memo; }, 0);
// If the table doesn't fit the width of the container, make them fit it
if (gigaGridWidth * .98 > sumOfHeaderWidths) {
var $blankCell = $(node).find(".table-header.blank-header-cell");
var expandAllHeadersBy_1 = (gigaGridWidth - sumOfHeaderWidths - $blankCell.innerWidth()) / _.filter(columns, function (def) { return !def.width; }).length;
widths = widths.map(function (w, idx) { return columns[idx].width ? w : Math.floor(w + expandAllHeadersBy_1); });
}
var oldSheetNode = $("head > style#giga-grid-style-" + this.state.gridID);
var sheet = (_.findWhere(document.styleSheets, { ownerNode: oldSheetNode }) || this.createGigaGridStyleSheet());
for (var i = 0; i < $leftHeaderContainers.length + $rightHeaderContainers.length; ++i) {
var selectorText = ".giga-grid-" + this.state.gridID + " .giga-grid-column-" + i;
var cssText = "width: " + widths[i] + "px !important;";
var oldRule = _.findWhere(sheet.cssRules, { selectorText: selectorText });
if (oldRule)
sheet.deleteRule(sheet.rules.indexOf(oldRule));
if (!oldRule || oldRule.style.cssText !== cssText)
sheet.insertRule(selectorText + " { " + cssText + " }", 0);
}
var $leftHeadersDataContainer = $(node).find(".giga-grid-left-headers-container");
var $bodyViewport = $(node).find(".giga-grid-body-viewport");
//setting max-width of sticky data and headers as 75% of the body-viewport width
$leftHeadersDataContainer.css("max-width", 0.75 * $bodyViewport.innerWidth());
$(node).find(".left-static-headers").css("max-width", 0.75 * $bodyViewport.innerWidth());
// After we're done with all this, make sure the data container and respective headers has max-width matching the container minus the left-headers
// #71 subtract one additional pixel for some rare screen configurations
$(node).find(".giga-grid-right-data-container").css("max-width", $(node).innerWidth() - $leftHeadersDataContainer.innerWidth() - 1);
$(node).find(".right-scrolling-headers").css("max-width", $(node).innerWidth() - $leftHeadersDataContainer.innerWidth());
// If the scrollbars push the table up on the right side, we need to make the left side flush with the right
setTimeout(function () {
$leftHeadersDataContainer.css("max-height", viewportHeight - (hasNodeHorizOverflowed($rightDataContainer.get(0)) ? extraScrollbarHeight : 0));
});
};
/**
* Creates a new stylesheet for this grid
* @returns {CSSStyleSheet}
*/
GigaGrid.prototype.createGigaGridStyleSheet = function () {
var style = document.createElement("style");
style.setAttribute("id", "giga-grid-style-" + this.state.gridID);
document.head.appendChild(style);
return style.sheet;
};
GigaGrid.prototype.scrollHandler = function (e) {
e.preventDefault();
var node = ReactDOM.findDOMNode(this);
var dataContainer = $(node).parent().find('.giga-grid-right-data-container');
var scrollLeftAmount = dataContainer.scrollLeft();
var scrollTopAmount = dataContainer.scrollTop();
this.lastYScrollAmount = scrollTopAmount;
$(node).parent().find('.giga-grid-left-headers-container').scrollTop(scrollTopAmount);
$(node).parent().parent().find('.right-scrolling-headers').scrollTop(scrollTopAmount);
$(node).parent().parent().find('.right-scrolling-headers').scrollLeft(scrollLeftAmount);
this.dispatchDisplayBoundChange();
return false;
};
/**
* A wheely important function. You can't scroll normally in the left-headers area, but a user would expect the
* table to scroll if he or she uses the mousewheel. So we have to listen for this event.
*/
GigaGrid.prototype.wheelScrollHandler = function (e) {
e.preventDefault();
// This covers all browsers, see https://www.sitepoint.com/html5-javascript-mouse-wheel/
var amountToScroll = -Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))) * 53;
// We give the illusion that IE scrolls at a normal pace by tripling the scroll amount
if (isIE())
amountToScroll = amountToScroll * 3;
var node = ReactDOM.findDOMNode(this);
var dataContainer = $(node).parent().find('.giga-grid-right-data-container');
var scrollTopAmount = dataContainer.scrollTop();
this.lastYScrollAmount = scrollTopAmount + amountToScroll;
console.log(amountToScroll);
$(node).parent().find('.giga-grid-left-headers-container').scrollTop(scrollTopAmount + amountToScroll);
$(node).parent().parent().find('.giga-grid-right-data-container').scrollTop(scrollTopAmount + amountToScroll);
this.dispatchDisplayBoundChange();
return false;
};
GigaGrid.prototype.reflowTable = function () {
this.dispatchDisplayBoundChange();
this.synchTableHeaderWidthToFirstRow();
};
GigaGrid.prototype.componentDidMount = function () {
/*
* subscribe to window event listeners
*/
if (typeof window !== "undefined") {
window.addEventListener('resize', this.synchTableHeaderWidthToFirstRow.bind(this));
this.enableScrollHandlers();
}
/*
re-compute displayStart && displayEnd
*/
this.reflowTable();
this.expandTable();
};
GigaGrid.prototype.componentWillUnmount = function () {
/*
* unsubscribe to window event listeners
*/
if (typeof window !== "undefined") {
window.removeEventListener('resize', this.synchTableHeaderWidthToFirstRow);
this.disableScrollHandlers();
}
};
// Bind scroll listener to move headers when data container is scrolled
GigaGrid.prototype.enableScrollHandlers = function () {
var node = ReactDOM.findDOMNode(this);
var leftPanel = $(node).find('.giga-grid-left-headers-container').get(0);
var rightPanel = $(node).find('.giga-grid-right-data-container').get(0);
rightPanel && rightPanel.addEventListener('scroll', this.encapsulatedScrollHandler);
rightPanel && rightPanel.addEventListener('mousewheel', this.encapsulatedWheelScrollHandler);
rightPanel && rightPanel.addEventListener('MozMousePixelScroll', this.encapsulatedWheelScrollHandler);
leftPanel && leftPanel.addEventListener('mousewheel', this.encapsulatedWheelScrollHandler);
leftPanel && leftPanel.addEventListener('MozMousePixelScroll', this.encapsulatedWheelScrollHandler);
};
// Unbind the scroll listeners
GigaGrid.prototype.disableScrollHandlers = function () {
var node = ReactDOM.findDOMNode(this);
$(node).find('.giga-grid-right-data-container').unbind('scroll', this.scrollHandler);
var leftPanel = $(node).find('.giga-grid-left-headers-container').get(0);
var rightPanel = $(node).find('.giga-grid-right-data-container').get(0);
rightPanel && rightPanel.removeEventListener('scroll', this.encapsulatedScrollHandler);
rightPanel && rightPanel.removeEventListener('mousewheel', this.encapsulatedWheelScrollHandler);
rightPanel && rightPanel.removeEventListener('MozMousePixelScroll', this.encapsulatedWheelScrollHandler);
leftPanel && leftPanel.removeEventListener('mousewheel', this.encapsulatedWheelScrollHandler);
leftPanel && leftPanel.removeEventListener('MozMousePixelScroll', this.encapsulatedWheelScrollHandler);
};
GigaGrid.prototype.dispatchDisplayBoundChange = function () {
var state = this.store.getState();
var $viewport = $(state.viewport);
var $canvas = $(state.canvas);
var action = {
type: GigaStore_1.GigaActionType.CHANGE_ROW_DISPLAY_BOUNDS,
canvas: $canvas,
viewport: $viewport,
rowHeight: this.props.rowHeight,
bodyHeight: this.props.bodyHeight
};
this.dispatcher.dispatch(action);
};
GigaGrid.prototype.expandTable = function () {
if (this.props.expandTable) {
this.dispatcher.dispatch({
type: GigaStore_1.GigaActionType.EXPAND_ALL
});
}
};
return GigaGrid;
}(React.Component));
GigaGrid.defaultProps = {
initialSubtotalBys: [],
initialSortBys: [],
initialFilterBys: [],
data: [],
columnDefs: [],
rowHeight: "25px",
collapseHeight: false,
expandTable: false,
additionalUserButtons: []
};
exports.GigaGrid = GigaGrid;
function getHorizontalScrollbarThickness() {
var el = document.createElement('div');
el.style.visibility = 'hidden';
el.style.overflow = 'scroll';
document.body.appendChild(el);
var h = el.offsetHeight - el.clientHeight;
document.body.removeChild(el);
return h;
}
exports.getHorizontalScrollbarThickness = getHorizontalScrollbarThickness;
function hasNodeHorizOverflowed(htmlElement) {
return htmlElement.scrollWidth > htmlElement.offsetWidth;
}
function isIE() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf("MSIE ");
return msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./);
}
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_2__;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/**
* @license
* lodash 3.10.1 (Custom Build) <https://lodash.com/>
* Build: `lodash modern -d -o ./index.js`
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <https://lodash.com/license>
*/
;(function() {
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
var undefined;
/** Used as the semantic version number. */
var VERSION = '3.10.1';
/** Used to compose bitmasks for wrapper metadata. */
var BIND_FLAG = 1,
BIND_KEY_FLAG = 2,
CURRY_BOUND_FLAG = 4,
CURRY_FLAG = 8,
CURRY_RIGHT_FLAG = 16,
PARTIAL_FLAG = 32,
PARTIAL_RIGHT_FLAG = 64,
ARY_FLAG = 128,
REARG_FLAG = 256;
/** Used as default options for `_.trunc`. */
var DEFAULT_TRUNC_LENGTH = 30,
DEFAULT_TRUNC_OMISSION = '...';
/** Used to detect when a function becomes hot. */
var HOT_COUNT = 150,
HOT_SPAN = 16;
/** Used as the size to enable large array optimizations. */
var LARGE_ARRAY_SIZE = 200;
/** Used to indicate the type of lazy iteratees. */
var LAZY_FILTER_FLAG = 1,
LAZY_MAP_FLAG = 2;
/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';
/** Used as the internal argument placeholder. */
var PLACEHOLDER = '__lodash_placeholder__';
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
arrayTag = '[object Array]',
boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
funcTag = '[object Function]',
mapTag = '[object Map]',
numberTag = '[object Number]',
objectTag = '[object Object]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';
/** Used to match empty string literals in compiled template source. */
var reEmptyStringLeading = /\b__p \+= '';/g,
reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
/** Used to match HTML entities and HTML characters. */
var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
reUnescapedHtml = /[&<>"'`]/g,
reHasEscapedHtml = RegExp(reEscapedHtml.source),
reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
/** Used to match template delimiters. */
var reEscape = /<%-([\s\S]+?)%>/g,
reEvaluate = /<%([\s\S]+?)%>/g,
reInterpolate = /<%=([\s\S]+?)%>/g;
/** Used to match property names within property paths. */
var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,
reIsPlainProp = /^\w*$/,
rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g;
/**
* Used to match `RegExp` [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns)
* and those outlined by [`EscapeRegExpPattern`](http://ecma-international.org/ecma-262/6.0/#sec-escaperegexppattern).
*/
var reRegExpChars = /^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,
reHasRegExpChars = RegExp(reRegExpChars.source);
/** Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). */
var reComboMark = /[\u0300-\u036f\ufe20-\ufe23]/g;
/** Used to match backslashes in property paths. */
var reEscapeChar = /\\(\\)?/g;
/** Used to match [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components). */
var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
/** Used to match `RegExp` flags from their coerced string values. */
var reFlags = /\w*$/;
/** Used to detect hexadecimal string values. */
var reHasHexPrefix = /^0[xX]/;
/** Used to detect host constructors (Safari > 5). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used to detect unsigned integer values. */
var reIsUint = /^\d+$/;
/** Used to match latin-1 supplementary letters (excluding mathematical operators). */
var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;
/** Used to ensure capturing order of template delimiters. */
var reNoMatch = /($^)/;
/** Used to match unescaped characters in compiled string literals. */
var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
/** Used to match words to create compound words. */
var reWords = (function() {
var upper = '[A-Z\\xc0-\\xd6\\xd8-\\xde]',
lower = '[a-z\\xdf-\\xf6\\xf8-\\xff]+';
return RegExp(upper + '+(?=' + upper + lower + ')|' + upper + '?' + lower + '|' + upper + '+|[0-9]+', 'g');
}());
/** Used to assign default `context` object properties. */
var contextProps = [
'Array', 'ArrayBuffer', 'Date', 'Error', 'Float32Array', 'Float64Array',
'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Math', 'Number',
'Object', 'RegExp', 'Set', 'String', '_', 'clearTimeout', 'isFinite',
'parseFloat', 'parseInt', 'setTimeout', 'TypeError', 'Uint8Array',
'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap'
];
/** Used to make template sourceURLs easier to identify. */
var templateCounter = -1;
/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dateTag] = typedArrayTags[errorTag] =
typedArrayTags[funcTag] = typedArrayTags[mapTag] =
typedArrayTags[numberTag] = typedArrayTags[objectTag] =
typedArrayTags[regexpTag] = typedArrayTags[setTag] =
typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
/** Used to identify `toStringTag` values supported by `_.clone`. */
var cloneableTags = {};
cloneableTags[argsTag] = cloneableTags[arrayTag] =
cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
cloneableTags[dateTag] = cloneableTags[float32Tag] =
cloneableTags[float64Tag] = cloneableTags[int8Tag] =
cloneableTags[int16Tag] = cloneableTags[int32Tag] =
cloneableTags[numberTag] = cloneableTags[objectTag] =
cloneableTags[regexpTag] = cloneableTags[stringTag] =
cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
cloneableTags[errorTag] = cloneableTags[funcTag] =
cloneableTags[mapTag] = cloneableTags[setTag] =
cloneableTags[weakMapTag] = false;
/** Used to map latin-1 supplementary letters to basic latin letters. */
var deburredLetters = {
'\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
'\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
'\xc7': 'C', '\xe7': 'c',
'\xd0': 'D', '\xf0': 'd',
'\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
'\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
'\xcC': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
'\xeC': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i',
'\xd1': 'N', '\xf1': 'n',
'\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
'\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
'\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
'\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
'\xdd': 'Y', '\xfd': 'y', '\xff': 'y',
'\xc6': 'Ae', '\xe6': 'ae',
'\xde': 'Th', '\xfe': 'th',
'\xdf': 'ss'
};
/** Used to map characters to HTML entities. */
var htmlEscapes = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`'
};
/** Used to map HTML entities to characters. */
var htmlUnescapes = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
''': "'",
'`': '`'
};
/** Used to determine if values are of the language type `Object`. */
var objectTypes = {
'function': true,
'object': true
};
/** Used to escape characters for inclusion in compiled regexes. */
var regexpEscapes = {
'0': 'x30', '1': 'x31', '2': 'x32', '3': 'x33', '4': 'x34',
'5': 'x35', '6': 'x36', '7': 'x37', '8': 'x38', '9': 'x39',
'A': 'x41', 'B': 'x42', 'C': 'x43', 'D': 'x44', 'E': 'x45', 'F': 'x46',
'a': 'x61', 'b': 'x62', 'c': 'x63', 'd': 'x64', 'e': 'x65', 'f': 'x66',
'n': 'x6e', 'r': 'x72', 't': 'x74', 'u': 'x75', 'v': 'x76', 'x': 'x78'
};
/** Used to escape characters for inclusion in compiled string literals. */
var stringEscapes = {
'\\': '\\',
"'": "'",
'\n': 'n',
'\r': 'r',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
/** Detect free variable `exports`. */
var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
/** Detect free variable `global` from Node.js. */
var freeGlobal = freeExports && freeModule && typeof global == 'object' && global && global.Object && global;
/** Detect free variable `self`. */
var freeSelf = objectTypes[typeof self] && self && self.Object && self;
/** Detect free variable `window`. */
var freeWindow = objectTypes[typeof window] && window && window.Object && window;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
/**
* Used as a reference to the global object.
*
* The `this` value is used if it's the global object to avoid Greasemonkey's
* restricted `window` object, otherwise the `window` object is used.
*/
var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;
/*--------------------------------------------------------------------------*/
/**
* The base implementation of `compareAscending` which compares values and
* sorts them in ascending order without guaranteeing a stable sort.
*
* @private
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {number} Returns the sort order indicator for `value`.
*/
function baseCompareAscending(value, other) {
if (value !== other) {
var valIsNull = value === null,
valIsUndef = value === undefined,
valIsReflexive = value === value;
var othIsNull = other === null,
othIsUndef = other === undefined,
othIsReflexive = other === other;
if ((value > other && !othIsNull) || !valIsReflexive ||
(valIsNull && !othIsUndef && othIsReflexive) ||
(valIsUndef && othIsReflexive)) {
return 1;
}
if ((value < other && !valIsNull) || !othIsReflexive ||
(othIsNull && !valIsUndef && valIsReflexive) ||
(othIsUndef && valIsReflexive)) {
return -1;
}
}
return 0;
}
/**
* The base implementation of `_.findIndex` and `_.findLastIndex` without
* support for callback shorthands and `this` binding.
*
* @private
* @param {Array} array The array to search.
* @param {Function} predicate The function invoked per iteration.
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseFindIndex(array, predicate, fromRight) {
var length = array.length,
index = fromRight ? length : -1;
while ((fromRight ? index-- : ++index < length)) {
if (predicate(array[index], index, array)) {
return index;
}
}
return -1;
}
/**
* The base implementation of `_.indexOf` without support for binary searches.
*
* @private
* @param {Array} array The array to search.
* @param {*} value The value to search for.
* @param {number} fromIndex The index to search from.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseIndexOf(array, value, fromIndex) {
if (value !== value) {
return indexOfNaN(array, fromIndex);
}
var index = fromIndex - 1,
length = array.length;
while (++index < length) {
if (array[index] === value) {
return index;
}
}
return -1;
}
/**
* The base implementation of `_.isFunction` without support for environments
* with incorrect `typeof` results.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
*/
function baseIsFunction(value) {
// Avoid a Chakra JIT bug in compatibility modes of IE 11.
// See https://github.com/jashkenas/underscore/issues/1621 for more details.
return typeof value == 'function' || false;
}
/**
* Converts `value` to a string if it's not one. An empty string is returned
* for `null` or `undefined` values.
*
* @private
* @param {*} value The value to process.
* @returns {string} Returns the string.
*/
function baseToString(value) {
return value == null ? '' : (value + '');
}
/**
* Used by `_.trim` and `_.trimLeft` to get the index of the first character
* of `string` that is not found in `chars`.
*
* @private
* @param {string} string The string to inspect.
* @param {string} chars The characters to find.
* @returns {number} Returns the index of the first character not found in `chars`.
*/
function charsLeftIndex(string, chars) {
var index = -1,
length = string.length;
while (++index < length && chars.indexOf(string.charAt(index)) > -1) {}
return index;
}
/**
* Used by `_.trim` and `_.trimRight` to get the index of the last character
* of `string` that is not found in `chars`.
*
* @private
* @param {string} string The string to inspect.
* @param {string} chars The characters to find.
* @returns {number} Returns the index of the last character not found in `chars`.
*/
function charsRightIndex(string, chars) {
var index = string.length;
while (index-- && chars.indexOf(string.charAt(index)) > -1) {}
return index;
}
/**
* Used by `_.sortBy` to compare transformed elements of a collection and stable
* sort them in ascending order.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @returns {number} Returns the sort order indicator for `object`.
*/
function compareAscending(object, other) {
return baseCompareAscending(object.criteria, other.criteria) || (object.index - other.index);
}
/**
* Used by `_.sortByOrder` to compare multiple properties of a value to another
* and stable sort them.
*
* If `orders` is unspecified, all valuess are sorted in ascending order. Otherwise,
* a value is sorted in ascending order if its corresponding order is "asc", and
* descending if "desc".
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {boolean[]} orders The order to sort by for each property.
* @returns {number} Returns the sort order indicator for `object`.
*/
function compareMultiple(object, other, orders) {
var index = -1,
objCriteria = object.criteria,
othCriteria = other.criteria,
length = objCriteria.length,
ordersLength = orders.length;
while (++index < length) {
var result = baseCompareAscending(objCriteria[index], othCriteria[index]);
if (result) {
if (index >= ordersLength) {
return result;
}
var order = orders[index];
return result * ((order === 'asc' || order === true) ? 1 : -1);
}
}
// Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
// that causes it, under certain circumstances, to provide the same value for
// `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
// for more details.
//
// This also ensures a stable sort in V8 and other engines.
// See https://code.google.com/p/v8/issues/detail?id=90 for more details.
return object.index - other.index;
}
/**
* Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
*
* @private
* @param {string} letter The matched letter to deburr.
* @returns {string} Returns the deburred letter.
*/
function deburrLetter(letter) {
return deburredLetters[letter];
}
/**
* Used by `_.escape` to convert characters to HTML entities.
*
* @private
* @param {string} chr The matched character to escape.
* @returns {string} Returns the escaped character.
*/
function escapeHtmlChar(chr) {
return htmlEscapes[chr];
}
/**
* Used by `_.escapeRegExp` to escape characters for inclusion in compiled regexes.
*
* @private
* @param {string} chr The matched character to escape.
* @param {string} leadingChar The capture group for a leading character.
* @param {string} whitespaceChar The capture group for a whitespace character.
* @returns {string} Returns the escaped character.
*/
function escapeRegExpChar(chr, leadingChar, whitespaceChar) {
if (leadingChar) {
chr = regexpEscapes[chr];
} else if (whitespaceChar) {
chr = stringEscapes[chr];
}
return '\\' + chr;
}
/**
* Used by `_.template` to escape characters for inclusion in compiled string literals.
*
* @private
* @param {string} chr The matched character to escape.
* @returns {string} Returns the escaped character.
*/
function escapeStringChar(chr) {
return '\\' + stringEscapes[chr];
}
/**
* Gets the index at which the first occurrence of `NaN` is found in `array`.
*
* @private
* @param {Array} array The array to search.
* @param {number} fromIndex The index to search from.
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {number} Returns the index of the matched `NaN`, else `-1`.
*/
function indexOfNaN(array, fromIndex, fromRight) {
var length = array.length,
index = fromIndex + (fromRight ? 0 : -1);
while ((fromRight ? index-- : ++index < length)) {
var other = array[index];
if (other !== other) {
return index;
}
}
return -1;
}
/**
* Checks if `value` is object-like.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
*/
function isObjectLike(value) {
return !!value && typeof value == 'object';
}
/**
* Used by `trimmedLeftIndex` and `trimmedRightIndex` to determine if a
* character code is whitespace.
*
* @private
* @param {number} charCode The character code to inspect.
* @returns {boolean} Returns `true` if `charCode` is whitespace, else `false`.
*/
function isSpace(charCode) {
return ((charCode <= 160 && (charCode >= 9 && charCode <= 13) || charCode == 32 || charCode == 160) || charCode == 5760 || charCode == 6158 ||
(charCode >= 8192 && (charCode <= 8202 || charCode == 8232 || charCode == 8233 || charCode == 8239 || charCode == 8287 || charCode == 12288 || charCode == 65279)));
}
/**
* Replaces all `placeholder` elements in `array` with an internal placeholder
* and returns an array of their indexes.
*
* @private
* @param {Array} array The array to modify.
* @param {*} placeholder The placeholder to replace.
* @returns {Array} Returns the new array of placeholder indexes.
*/
function replaceHolders(array, placeholder) {
var index = -1,
length = array.length,
resIndex = -1,
result = [];
while (++index < length) {
if (array[index] === placeholder) {
array[index] = PLACEHOLDER;
result[++resIndex] = index;
}
}
return result;
}
/**
* An implementation of `_.uniq` optimized for sorted arrays without support
* for callback shorthands and `this` binding.
*
* @private
* @param {Array} array The array to inspect.
* @param {Function} [iteratee] The function invoked per iteration.
* @returns {Array} Returns the new duplicate-value-free array.
*/
function sortedUniq(array, iteratee) {
var seen,
index = -1,
length = array.length,
resIndex = -1,
result = [];
while (++index < length) {
var value = array[index],
computed = iteratee ? iteratee(value, index, array) : value;
if (!index || seen !== computed) {
seen = computed;
result[++resIndex] = value;
}
}
return result;
}
/**
* Used by `_.trim` and `_.trimLeft` to get the index of the first non-whitespace
* character of `string`.
*
* @private
* @param {string} string The string to inspect.
* @returns {number} Returns the index of the first non-whitespace character.
*/
function trimmedLeftIndex(string) {
var index = -1,
length = string.length;
while (++index < length && isSpace(string.charCodeAt(index))) {}
return index;
}
/**
* Used by `_.trim` and `_.trimRight` to get the index of the last non-whitespace
* character of `string`.
*
* @private
* @param {string} string The string to inspect.
* @returns {number} Returns the index of the last non-whitespace character.
*/
function trimmedRightIndex(string) {
var index = string.length;
while (index-- && isSpace(string.charCodeAt(index))) {}
return index;
}
/**
* Used by `_.unescape` to convert HTML entities to characters.
*
* @private
* @param {string} chr The matched character to unescape.
* @returns {string} Returns the unescaped character.
*/
function unescapeHtmlChar(chr) {
return htmlUnescapes[chr];
}
/*--------------------------------------------------------------------------*/
/**
* Create a new pristine `lodash` function using the given `context` object.
*
* @static
* @memberOf _
* @category Utility
* @param {Object} [context=root] The context object.
* @returns {Function} Returns a new `lodash` function.
* @example
*
* _.mixin({ 'foo': _.constant('foo') });
*
* var lodash = _.runInContext();
* lodash.mixin({ 'bar': lodash.constant('bar') });
*
* _.isFunction(_.foo);
* // => true
* _.isFunction(_.bar);
* // => false
*
* lodash.isFunction(lodash.foo);
* // => false
* lodash.isFunction(lodash.bar);
* // => true
*
* // using `context` to mock `Date#getTime` use in `_.now`
* var mock = _.runInContext({
* 'Date': function() {
* return { 'getTime': getTimeMock };
* }
* });
*
* // or creating a suped-up `defer` in Node.js
* var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
*/
function runInContext(context) {
// Avoid issues with some ES3 environments that attempt to use values, named
// after built-in constructors like `Object`, for the creation of literals.
// ES5 clears this up by stating that literals must use built-in constructors.
// See https://es5.github.io/#x11.1.5 for more details.
context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
/** Native constructor references. */
var Array = context.Array,
Date = context.Date,
Erro