atomic-form
Version:
A React form with data collection and validation.
1,373 lines (1,212 loc) • 463 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("react"));
else if(typeof define === 'function' && define.amd)
define(["react"], factory);
else if(typeof exports === 'object')
exports["AtomicForm"] = factory(require("react"));
else
root["AtomicForm"] = factory(root["React"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_5__) {
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
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _AtomicForm2 = __webpack_require__(1);
var _AtomicForm3 = _interopRequireDefault(_AtomicForm2);
exports.AtomicForm = _AtomicForm3['default'];
var _AtomicForm4 = _interopRequireDefault(_AtomicForm2);
exports['default'] = _AtomicForm4['default'];
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var _react = __webpack_require__(5);
var _react2 = _interopRequireDefault(_react);
var _lodash = __webpack_require__(2);
var _lodash2 = _interopRequireDefault(_lodash);
var _validator = __webpack_require__(3);
var _validator2 = _interopRequireDefault(_validator);
var AtomicForm = (function (_React$Component) {
_inherits(AtomicForm, _React$Component);
function AtomicForm(props, context) {
_classCallCheck(this, AtomicForm);
_get(Object.getPrototypeOf(AtomicForm.prototype), "constructor", this).call(this, props, context);
this.validateForm = this.validateForm.bind(this);
this.allValid = this.allValid.bind(this);
this.formData = this.formData.bind(this);
this.getFormValue = this.getFormValue.bind(this);
this.recursiveCloneChildren = this.recursiveCloneChildren.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = this.getState();
}
_createClass(AtomicForm, [{
key: "getState",
value: function getState() {
if (this.props.getState) {
return this.props.getState();
} else {
return {
formData: this.props.initialData || {}
};
}
}
}, {
key: "componentWillReceiveProps",
value: function componentWillReceiveProps(nextProps) {
if (_lodash2["default"].isEmpty(this.state.formData) && !_lodash2["default"].isEmpty(nextProps.initialData)) {
this.setState({ formData: nextProps.initialData });
}
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
this.updateFormData();
}
}, {
key: "updateFormData",
value: function updateFormData() {
if (this.props.updateFormData) {
return this.props.updateFormData(this.refs);
} else {
_lodash2["default"].forEach(this.refs, (function (val, key) {
var value = this.getFormValue(key);
if (!_lodash2["default"].isEmpty(value) || _lodash2["default"].isBoolean(value)) {
val.getDOMNode().value = value;
}
}).bind(this));
}
}
}, {
key: "handleSubmit",
value: function handleSubmit(e) {
e.preventDefault();
var formData = this.formData();
var formValidation = this.validateForm(formData);
if (this.allValid(formValidation)) {
this.props.doSubmit(formData, formValidation);
} else {
this.props.afterValidation(formValidation);
this.setState({ formData: formData });
}
}
}, {
key: "allValid",
value: function allValid(formValidation) {
return _lodash2["default"].every(_lodash2["default"].values(formValidation), function (v) {
return v.isValid;
});
}
}, {
key: "validateForm",
value: function validateForm(formData) {
var _this = this;
var result = {};
_lodash2["default"].forEach(this.refs, (function (val, key) {
var validators = _this.refs[key].props.validate;
result[key] = {};
result[key].isValid = true;
if (validators) {
if (_lodash2["default"].isArray(validators)) {
_lodash2["default"].forEach(validators, (function (validator) {
if (_lodash2["default"].isFunction(validator.validate)) {
result[key].isValid = result[key].isValid && validator.validate(formData[key], formData);
} else if (validator.validate == "isPresent") {
result[key].isValid = result[key].isValid && !!formData[key];
} else {
var args = validator.args || [];
result[key].isValid = result[key].isValid && _validator2["default"][validator.validate].apply(_validator2["default"], [formData[key]].concat(_toConsumableArray(args)));
}
if (!result[key].isValid) {
result[key].message = result[key].message || [];
result[key].message.push(validator.message || "");
}
}).bind(_this));
} else {
console.log("Validators must be an Array for form key: " + key);
}
}
}).bind(this));
return result;
}
}, {
key: "formData",
value: function formData() {
var _this2 = this;
if (this.props.collectFormData) {
return this.props.collectFormData(this.refs);
} else {
var formData = {};
_lodash2["default"].forEach(this.refs, (function (val, ref) {
var domNode = _react2["default"].findDOMNode(_this2.refs[ref]);
var keyArray = ref.split(".");
if (keyArray.length > 1) {
var firstKey = keyArray.shift();
var data = {};
data[keyArray.pop()] = domNode.value;
while (keyArray.length > 0) {
var temp = {};
temp[keyArray.pop()] = data;
data = temp;
}
formData[firstKey] = _lodash2["default"].merge(data, formData[firstKey]);
} else {
formData[ref] = domNode.value;
}
}).bind(this));
return formData;
}
}
}, {
key: "getFormValue",
value: function getFormValue(ref) {
var keyArray = ref.split(".");
var value = this.state.formData;
_lodash2["default"].forEach(keyArray, (function (key) {
if (_lodash2["default"].last(keyArray) == key) {
value = value[key] || '';
} else {
value = value[key] || {};
}
}).bind(this));
return value;
}
// By default React will discard refs from the children. We override the behavior to include the refs
// See: https://facebook.github.io/react/docs/clone-with-props.html
}, {
key: "recursiveCloneChildren",
value: function recursiveCloneChildren(children) {
var _this3 = this;
return _react2["default"].Children.map(children, function (child) {
if (!_lodash2["default"].isObject(child)) return child;
var childProps = {};
if (child.ref) {
var func;
if (child.props.type == "checkbox" || child.props.type == "radio") {
func = function () {
var formData = _this3.state.formData;
formData[child.ref] = _this3.refs[child.ref].getDOMNode().checked;
_this3.setState({ formData: formData });
};
} else {
func = function () {
var formData = _this3.state.formData;
formData[child.ref] = _this3.refs[child.ref].getDOMNode().value;
_this3.setState({ formData: formData });
};
}
childProps.onChange = func;
childProps.ref = child.ref;
}
childProps.children = _this3.recursiveCloneChildren(child.props.children);
return _react2["default"].cloneElement(child, childProps);
});
}
}, {
key: "render",
value: function render() {
var _this4 = this;
return _react2["default"].createElement(
"form",
{ onSubmit: function (e) {
_this4.handleSubmit(e);
} },
this.recursiveCloneChildren(this.props.children)
);
}
}]);
return AtomicForm;
})(_react2["default"].Component);
exports["default"] = AtomicForm;
module.exports = exports["default"];
/***/ },
/* 2 */
/***/ 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,
Error = context.Error,
Function = context.Function,
Math = context.Math,
Number = context.Number,
Object = context.Object,
RegExp = context.RegExp,
String = context.String,
TypeError = context.TypeError;
/** Used for native method references. */
var arrayProto = Array.prototype,
objectProto = Object.prototype,
stringProto = String.prototype;
/** Used to resolve the decompiled source of functions. */
var fnToString = Function.prototype.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to generate unique IDs. */
var idCounter = 0;
/**
* Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
* of values.
*/
var objToString = objectProto.toString;
/** Used to restore the original `_` reference in `_.noConflict`. */
var oldDash = root._;
/** Used to detect if a method is native. */
var reIsNative = RegExp('^' +
fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);
/** Native method references. */
var ArrayBuffer = context.ArrayBuffer,
clearTimeout = context.clearTimeout,
parseFloat = context.parseFloat,
pow = Math.pow,
propertyIsEnumerable = objectProto.propertyIsEnumerable,
Set = getNative(context, 'Set'),
setTimeout = context.setTimeout,
splice = arrayProto.splice,
Uint8Array = context.Uint8Array,
WeakMap = getNative(context, 'WeakMap');
/* Native method references for those with the same name as other `lodash` methods. */
var nativeCeil = Math.ceil,
nativeCreate = getNative(Object, 'create'),
nativeFloor = Math.floor,
nativeIsArray = getNative(Array, 'isArray'),
nativeIsFinite = context.isFinite,
nativeKeys = getNative(Object, 'keys'),
nativeMax = Math.max,
nativeMin = Math.min,
nativeNow = getNative(Date, 'now'),
nativeParseInt = context.parseInt,
nativeRandom = Math.random;
/** Used as references for `-Infinity` and `Infinity`. */
var NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY,
POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
/** Used as references for the maximum length and index of an array. */
var MAX_ARRAY_LENGTH = 4294967295,
MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
/**
* Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
* of an array-like value.
*/
var MAX_SAFE_INTEGER = 9007199254740991;
/** Used to store function metadata. */
var metaMap = WeakMap && new WeakMap;
/** Used to lookup unminified function names. */
var realNames = {};
/*------------------------------------------------------------------------*/
/**
* Creates a `lodash` object which wraps `value` to enable implicit chaining.
* Methods that operate on and return arrays, collections, and functions can
* be chained together. Methods that retrieve a single value or may return a
* primitive value will automatically end the chain returning the unwrapped
* value. Explicit chaining may be enabled using `_.chain`. The execution of
* chained methods is lazy, that is, execution is deferred until `_#value`
* is implicitly or explicitly called.
*
* Lazy evaluation allows several methods to support shortcut fusion. Shortcut
* fusion is an optimization strategy which merge iteratee calls; this can help
* to avoid the creation of intermediate data structures and greatly reduce the
* number of iteratee executions.
*
* Chaining is supported in custom builds as long as the `_#value` method is
* directly or indirectly included in the build.
*
* In addition to lodash methods, wrappers have `Array` and `String` methods.
*
* The wrapper `Array` methods are:
* `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`,
* `splice`, and `unshift`
*
* The wrapper `String` methods are:
* `replace` and `split`
*
* The wrapper methods that support shortcut fusion are:
* `compact`, `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`,
* `first`, `initial`, `last`, `map`, `pluck`, `reject`, `rest`, `reverse`,
* `slice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `toArray`,
* and `where`
*
* The chainable wrapper methods are:
* `after`, `ary`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`,
* `callback`, `chain`, `chunk`, `commit`, `compact`, `concat`, `constant`,
* `countBy`, `create`, `curry`, `debounce`, `defaults`, `defaultsDeep`,
* `defer`, `delay`, `difference`, `drop`, `dropRight`, `dropRightWhile`,
* `dropWhile`, `fill`, `filter`, `flatten`, `flattenDeep`, `flow`, `flowRight`,
* `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
* `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
* `invoke`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`,
* `matchesProperty`, `memoize`, `merge`, `method`, `methodOf`, `mixin`,
* `modArgs`, `negate`, `omit`, `once`, `pairs`, `partial`, `partialRight`,
* `partition`, `pick`, `plant`, `pluck`, `property`, `propertyOf`, `pull`,
* `pullAt`, `push`, `range`, `rearg`, `reject`, `remove`, `rest`, `restParam`,
* `reverse`, `set`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByAll`,
* `sortByOrder`, `splice`, `spread`, `take`, `takeRight`, `takeRightWhile`,
* `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `toPlainObject`,
* `transform`, `union`, `uniq`, `unshift`, `unzip`, `unzipWith`, `values`,
* `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, `zipObject`, `zipWith`
*
* The wrapper methods that are **not** chainable by default are:
* `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,
* `deburr`, `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,
* `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`,
* `floor`, `get`, `gt`, `gte`, `has`, `identity`, `includes`, `indexOf`,
* `inRange`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
* `isEmpty`, `isEqual`, `isError`, `isFinite` `isFunction`, `isMatch`,
* `isNative`, `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`,
* `isRegExp`, `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`,
* `last`, `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`,
* `now`, `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`,
* `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`,
* `snakeCase`, `some`, `sortedIndex`, `sortedLastIndex`, `startCase`,
* `startsWith`, `sum`, `template`, `trim`, `trimLeft`, `trimRight`, `trunc`,
* `unescape`, `uniqueId`, `value`, and `words`
*
* The wrapper method `sample` will return a wrapped value when `n` is provided,
* otherwise an unwrapped value is returned.
*
* @name _
* @constructor
* @category Chain
* @param {*} value The value to wrap in a `lodash` instance.
* @returns {Object} Returns the new `lodash` wrapper instance.
* @example
*
* var wrapped = _([1, 2, 3]);
*
* // returns an unwrapped value
* wrapped.reduce(function(total, n) {
* return total + n;
* });
* // => 6
*
* // returns a wrapped value
* var squares = wrapped.map(function(n) {
* return n * n;
* });
*
* _.isArray(squares);
* // => false
*
* _.isArray(squares.value());
* // => true
*/
function lodash(value) {
if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
if (value instanceof LodashWrapper) {
return value;
}
if (hasOwnProperty.call(value, '__chain__') && hasOwnProperty.call(value, '__wrapped__')) {
return wrapperClone(value);
}
}
return new LodashWrapper(value);
}
/**
* The function whose prototype all chaining wrappers inherit from.
*
* @private
*/
function baseLodash() {
// No operation performed.
}
/**
* The base constructor for creating `lodash` wrapper objects.
*
* @private
* @param {*} value The value to wrap.
* @param {boolean} [chainAll] Enable chaining for all wrapper methods.
* @param {Array} [actions=[]] Actions to peform to resolve the unwrapped value.
*/
function LodashWrapper(value, chainAll, actions) {
this.__wrapped__ = value;
this.__actions__ = actions || [];
this.__chain__ = !!chainAll;
}
/**
* An object environment feature flags.
*
* @static
* @memberOf _
* @type Object
*/
var support = lodash.support = {};
/**
* By default, the template delimiters used by lodash are like those in
* embedded Ruby (ERB). Change the following template settings to use
* alternative delimiters.
*
* @static
* @memberOf _
* @type Object
*/
lodash.templateSettings = {
/**
* Used to detect `data` property values to be HTML-escaped.
*
* @memberOf _.templateSettings
* @type RegExp
*/
'escape': reEscape,
/**
* Used to detect code to be evaluated.
*
* @memberOf _.templateSettings
* @type RegExp
*/
'evaluate': reEvaluate,
/**
* Used to detect `data` property values to inject.
*
* @memberOf _.templateSettings
* @type RegExp
*/
'interpolate': reInterpolate,
/**
* Used to reference the data object in the template text.
*
* @memberOf _.templateSettings
* @type string
*/
'variable': '',
/**
* Used to import variables into the compiled template.
*
* @memberOf _.templateSettings
* @type Object
*/
'imports': {
/**
* A reference to the `lodash` function.
*
* @memberOf _.templateSettings.imports
* @type Function
*/
'_': lodash
}
};
/*------------------------------------------------------------------------*/
/**
* Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
*
* @private
* @param {*} value The value to wrap.
*/
function LazyWrapper(value) {
this.__wrapped__ = value;
this.__actions__ = [];
this.__dir__ = 1;
this.__filtered__ = false;
this.__iteratees__ = [];
this.__takeCount__ = POSITIVE_INFINITY;
this.__views__ = [];
}
/**
* Creates a clone of the lazy wrapper object.
*
* @private
* @name clone
* @memberOf LazyWrapper
* @returns {Object} Returns the cloned `LazyWrapper` object.
*/
function lazyClone() {
var result = new LazyWrapper(this.__wrapped__);
result.__actions__ = arrayCopy(this.__actions__);
result.__dir__ = this.__dir__;
result.__filtered__ = this.__filtered__;
result.__iteratees__ = arrayCopy(this.__iteratees__);
result.__takeCount__ = this.__takeCount__;
result.__views__ = arrayCopy(this.__views__);
return result;
}
/**
* Reverses the direction of lazy iteration.
*
* @private
* @name reverse
* @memberOf LazyWrapper
* @returns {Object} Returns the new reversed `LazyWrapper` object.
*/
function lazyReverse() {
if (this.__filtered__) {
var result = new LazyWrapper(this);
result.__dir__ = -1;
result.__filtered__ = true;
} else {
result = this.clone();
result.__dir__ *= -1;
}
return result;
}
/**
* Extracts the unwrapped value from its lazy wrapper.
*
* @private
* @name value
* @memberOf LazyWrapper
* @returns {*}