UNPKG

giga-grid

Version:

Massively performant, multi-layered React.js table widget Written in TypeScript

1,184 lines (1,079 loc) 1.53 MB
(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 = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '`': '&#96;' }; /** Used to map HTML entities to characters. */ var htmlUnescapes = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#39;': "'", '&#96;': '`' }; /** 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