56110 lines
1.6 MiB
56110 lines
1.6 MiB
/**
|
|
* @license
|
|
* Lodash <https://lodash.com/>
|
|
* Copyright JS Foundation and other contributors <https://js.foundation/>
|
|
* Released under MIT license <https://lodash.com/license>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
*/
|
|
;(function() {
|
|
|
|
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
|
|
var undefined;
|
|
|
|
/** Used as the semantic version number. */
|
|
var VERSION = '4.17.4';
|
|
|
|
/** Used as the size to enable large array optimizations. */
|
|
var LARGE_ARRAY_SIZE = 200;
|
|
|
|
/** Error message constants. */
|
|
var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
|
|
FUNC_ERROR_TEXT = 'Expected a function';
|
|
|
|
/** Used to stand-in for `undefined` hash values. */
|
|
var HASH_UNDEFINED = '__lodash_hash_undefined__';
|
|
|
|
/** Used as the maximum memoize cache size. */
|
|
var MAX_MEMOIZE_SIZE = 500;
|
|
|
|
/** Used as the internal argument placeholder. */
|
|
var PLACEHOLDER = '__lodash_placeholder__';
|
|
|
|
/** Used to compose bitmasks for cloning. */
|
|
var CLONE_DEEP_FLAG = 1,
|
|
CLONE_FLAT_FLAG = 2,
|
|
CLONE_SYMBOLS_FLAG = 4;
|
|
|
|
/** Used to compose bitmasks for value comparisons. */
|
|
var COMPARE_PARTIAL_FLAG = 1,
|
|
COMPARE_UNORDERED_FLAG = 2;
|
|
|
|
/** Used to compose bitmasks for function metadata. */
|
|
var WRAP_BIND_FLAG = 1,
|
|
WRAP_BIND_KEY_FLAG = 2,
|
|
WRAP_CURRY_BOUND_FLAG = 4,
|
|
WRAP_CURRY_FLAG = 8,
|
|
WRAP_CURRY_RIGHT_FLAG = 16,
|
|
WRAP_PARTIAL_FLAG = 32,
|
|
WRAP_PARTIAL_RIGHT_FLAG = 64,
|
|
WRAP_ARY_FLAG = 128,
|
|
WRAP_REARG_FLAG = 256,
|
|
WRAP_FLIP_FLAG = 512;
|
|
|
|
/** Used as default options for `_.truncate`. */
|
|
var DEFAULT_TRUNC_LENGTH = 30,
|
|
DEFAULT_TRUNC_OMISSION = '...';
|
|
|
|
/** Used to detect hot functions by number of calls within a span of milliseconds. */
|
|
var HOT_COUNT = 800,
|
|
HOT_SPAN = 16;
|
|
|
|
/** Used to indicate the type of lazy iteratees. */
|
|
var LAZY_FILTER_FLAG = 1,
|
|
LAZY_MAP_FLAG = 2,
|
|
LAZY_WHILE_FLAG = 3;
|
|
|
|
/** Used as references for various `Number` constants. */
|
|
var INFINITY = 1 / 0,
|
|
MAX_SAFE_INTEGER = 9007199254740991,
|
|
MAX_INTEGER = 1.7976931348623157e+308,
|
|
NAN = 0 / 0;
|
|
|
|
/** 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 to associate wrap methods with their bit flags. */
|
|
var wrapFlags = [
|
|
['ary', WRAP_ARY_FLAG],
|
|
['bind', WRAP_BIND_FLAG],
|
|
['bindKey', WRAP_BIND_KEY_FLAG],
|
|
['curry', WRAP_CURRY_FLAG],
|
|
['curryRight', WRAP_CURRY_RIGHT_FLAG],
|
|
['flip', WRAP_FLIP_FLAG],
|
|
['partial', WRAP_PARTIAL_FLAG],
|
|
['partialRight', WRAP_PARTIAL_RIGHT_FLAG],
|
|
['rearg', WRAP_REARG_FLAG]
|
|
];
|
|
|
|
/** `Object#toString` result references. */
|
|
var argsTag = '[object Arguments]',
|
|
arrayTag = '[object Array]',
|
|
asyncTag = '[object AsyncFunction]',
|
|
boolTag = '[object Boolean]',
|
|
dateTag = '[object Date]',
|
|
domExcTag = '[object DOMException]',
|
|
errorTag = '[object Error]',
|
|
funcTag = '[object Function]',
|
|
genTag = '[object GeneratorFunction]',
|
|
mapTag = '[object Map]',
|
|
numberTag = '[object Number]',
|
|
nullTag = '[object Null]',
|
|
objectTag = '[object Object]',
|
|
promiseTag = '[object Promise]',
|
|
proxyTag = '[object Proxy]',
|
|
regexpTag = '[object RegExp]',
|
|
setTag = '[object Set]',
|
|
stringTag = '[object String]',
|
|
symbolTag = '[object Symbol]',
|
|
undefinedTag = '[object Undefined]',
|
|
weakMapTag = '[object WeakMap]',
|
|
weakSetTag = '[object WeakSet]';
|
|
|
|
var arrayBufferTag = '[object ArrayBuffer]',
|
|
dataViewTag = '[object DataView]',
|
|
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);/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)[^\\]|\\.)*?\1)\]/,
|
|
reIsPlainProp = /^\w*$/,
|
|
reLeadingDot = /^\./,
|
|
rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
|
|
|
|
/**
|
|
* Used to match `RegExp`
|
|
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
|
|
*/
|
|
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
|
|
reHasRegExpChar = RegExp(reRegExpChar.source);
|
|
|
|
/** Used to match leading and trailing whitespace. */
|
|
var reTrim = /^\s+|\s+$/g,
|
|
reTrimStart = /^\s+/,
|
|
reTrimEnd = /\s+$/;
|
|
|
|
/** Used to match wrap detail comments. */
|
|
var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
|
|
reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
|
|
reSplitDetails = /,? & /;
|
|
|
|
/** Used to match words composed of alphanumeric characters. */
|
|
var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
|
|
|
|
/** Used to match backslashes in property paths. */
|
|
var reEscapeChar = /\\(\\)?/g;
|
|
|
|
/**
|
|
* Used to match
|
|
* [ES template delimiters](http://ecma-international.org/ecma-262/7.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 bad signed hexadecimal string values. */
|
|
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
|
|
|
|
/** Used to detect binary string values. */
|
|
var reIsBinary = /^0b[01]+$/i;
|
|
|
|
/** Used to detect host constructors (Safari). */
|
|
var reIsHostCtor = /^\[object .+?Constructor\]$/;
|
|
|
|
/** Used to detect octal string values. */
|
|
var reIsOctal = /^0o[0-7]+$/i;
|
|
|
|
/** Used to detect unsigned integer values. */
|
|
var reIsUint = /^(?:0|[1-9]\d*)$/;
|
|
|
|
/** Used to match Latin Unicode letters (excluding mathematical operators). */
|
|
var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/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 compose unicode character classes. */
|
|
var rsAstralRange = '\\ud800-\\udfff',
|
|
rsComboMarksRange = '\\u0300-\\u036f',
|
|
reComboHalfMarksRange = '\\ufe20-\\ufe2f',
|
|
rsComboSymbolsRange = '\\u20d0-\\u20ff',
|
|
rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
|
|
rsDingbatRange = '\\u2700-\\u27bf',
|
|
rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
|
|
rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
|
|
rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
|
|
rsPunctuationRange = '\\u2000-\\u206f',
|
|
rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
|
|
rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
|
|
rsVarRange = '\\ufe0e\\ufe0f',
|
|
rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
|
|
|
|
/** Used to compose unicode capture groups. */
|
|
var rsApos = "['\u2019]",
|
|
rsAstral = '[' + rsAstralRange + ']',
|
|
rsBreak = '[' + rsBreakRange + ']',
|
|
rsCombo = '[' + rsComboRange + ']',
|
|
rsDigits = '\\d+',
|
|
rsDingbat = '[' + rsDingbatRange + ']',
|
|
rsLower = '[' + rsLowerRange + ']',
|
|
rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
|
|
rsFitz = '\\ud83c[\\udffb-\\udfff]',
|
|
rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
|
|
rsNonAstral = '[^' + rsAstralRange + ']',
|
|
rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
|
|
rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
|
|
rsUpper = '[' + rsUpperRange + ']',
|
|
rsZWJ = '\\u200d';
|
|
|
|
/** Used to compose unicode regexes. */
|
|
var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
|
|
rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
|
|
rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
|
|
rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
|
|
reOptMod = rsModifier + '?',
|
|
rsOptVar = '[' + rsVarRange + ']?',
|
|
rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
|
|
rsOrdLower = '\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)',
|
|
rsOrdUpper = '\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)',
|
|
rsSeq = rsOptVar + reOptMod + rsOptJoin,
|
|
rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
|
|
rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
|
|
|
|
/** Used to match apostrophes. */
|
|
var reApos = RegExp(rsApos, 'g');
|
|
|
|
/**
|
|
* Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
|
|
* [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
|
|
*/
|
|
var reComboMark = RegExp(rsCombo, 'g');
|
|
|
|
/** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
|
|
var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
|
|
|
|
/** Used to match complex or compound words. */
|
|
var reUnicodeWord = RegExp([
|
|
rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
|
|
rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')',
|
|
rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower,
|
|
rsUpper + '+' + rsOptContrUpper,
|
|
rsOrdUpper,
|
|
rsOrdLower,
|
|
rsDigits,
|
|
rsEmoji
|
|
].join('|'), 'g');
|
|
|
|
/** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
|
|
var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
|
|
|
|
/** Used to detect strings that need a more robust regexp to match words. */
|
|
var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
|
|
|
|
/** Used to assign default `context` object properties. */
|
|
var contextProps = [
|
|
'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
|
|
'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
|
|
'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array',
|
|
'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
|
|
'_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
|
|
];
|
|
|
|
/** 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[dataViewTag] = 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[dataViewTag] =
|
|
cloneableTags[boolTag] = cloneableTags[dateTag] =
|
|
cloneableTags[float32Tag] = cloneableTags[float64Tag] =
|
|
cloneableTags[int8Tag] = cloneableTags[int16Tag] =
|
|
cloneableTags[int32Tag] = cloneableTags[mapTag] =
|
|
cloneableTags[numberTag] = cloneableTags[objectTag] =
|
|
cloneableTags[regexpTag] = cloneableTags[setTag] =
|
|
cloneableTags[stringTag] = cloneableTags[symbolTag] =
|
|
cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
|
|
cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
|
|
cloneableTags[errorTag] = cloneableTags[funcTag] =
|
|
cloneableTags[weakMapTag] = false;
|
|
|
|
/** Used to map Latin Unicode letters to basic Latin letters. */
|
|
var deburredLetters = {
|
|
// Latin-1 Supplement block.
|
|
'\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',
|
|
// Latin Extended-A block.
|
|
'\u0100': 'A', '\u0102': 'A', '\u0104': 'A',
|
|
'\u0101': 'a', '\u0103': 'a', '\u0105': 'a',
|
|
'\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C',
|
|
'\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c',
|
|
'\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd',
|
|
'\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E',
|
|
'\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e',
|
|
'\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G',
|
|
'\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g',
|
|
'\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h',
|
|
'\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I',
|
|
'\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i',
|
|
'\u0134': 'J', '\u0135': 'j',
|
|
'\u0136': 'K', '\u0137': 'k', '\u0138': 'k',
|
|
'\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L',
|
|
'\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l',
|
|
'\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N',
|
|
'\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n',
|
|
'\u014c': 'O', '\u014e': 'O', '\u0150': 'O',
|
|
'\u014d': 'o', '\u014f': 'o', '\u0151': 'o',
|
|
'\u0154': 'R', '\u0156': 'R', '\u0158': 'R',
|
|
'\u0155': 'r', '\u0157': 'r', '\u0159': 'r',
|
|
'\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S',
|
|
'\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's',
|
|
'\u0162': 'T', '\u0164': 'T', '\u0166': 'T',
|
|
'\u0163': 't', '\u0165': 't', '\u0167': 't',
|
|
'\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U',
|
|
'\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u',
|
|
'\u0174': 'W', '\u0175': 'w',
|
|
'\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y',
|
|
'\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z',
|
|
'\u017a': 'z', '\u017c': 'z', '\u017e': 'z',
|
|
'\u0132': 'IJ', '\u0133': 'ij',
|
|
'\u0152': 'Oe', '\u0153': 'oe',
|
|
'\u0149': "'n", '\u017f': 's'
|
|
};
|
|
|
|
/** Used to map characters to HTML entities. */
|
|
var htmlEscapes = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
|
|
/** Used to map HTML entities to characters. */
|
|
var htmlUnescapes = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
''': "'"
|
|
};
|
|
|
|
/** Used to escape characters for inclusion in compiled string literals. */
|
|
var stringEscapes = {
|
|
'\\': '\\',
|
|
"'": "'",
|
|
'\n': 'n',
|
|
'\r': 'r',
|
|
'\u2028': 'u2028',
|
|
'\u2029': 'u2029'
|
|
};
|
|
|
|
/** Built-in method references without a dependency on `root`. */
|
|
var freeParseFloat = parseFloat,
|
|
freeParseInt = parseInt;
|
|
|
|
/** Detect free variable `global` from Node.js. */
|
|
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
|
|
|
|
/** Detect free variable `self`. */
|
|
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
|
|
|
|
/** Used as a reference to the global object. */
|
|
var root = freeGlobal || freeSelf || Function('return this')();
|
|
|
|
/** Detect free variable `exports`. */
|
|
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
|
|
|
|
/** Detect free variable `module`. */
|
|
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
|
|
|
|
/** Detect the popular CommonJS extension `module.exports`. */
|
|
var moduleExports = freeModule && freeModule.exports === freeExports;
|
|
|
|
/** Detect free variable `process` from Node.js. */
|
|
var freeProcess = moduleExports && freeGlobal.process;
|
|
|
|
/** Used to access faster Node.js helpers. */
|
|
var nodeUtil = (function() {
|
|
try {
|
|
return freeProcess && freeProcess.binding && freeProcess.binding('util');
|
|
} catch (e) {}
|
|
}());
|
|
|
|
/* Node.js helper references. */
|
|
var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
|
|
nodeIsDate = nodeUtil && nodeUtil.isDate,
|
|
nodeIsMap = nodeUtil && nodeUtil.isMap,
|
|
nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
|
|
nodeIsSet = nodeUtil && nodeUtil.isSet,
|
|
nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Adds the key-value `pair` to `map`.
|
|
*
|
|
* @private
|
|
* @param {Object} map The map to modify.
|
|
* @param {Array} pair The key-value pair to add.
|
|
* @returns {Object} Returns `map`.
|
|
*/
|
|
function addMapEntry(map, pair) {
|
|
// Don't return `map.set` because it's not chainable in IE 11.
|
|
map.set(pair[0], pair[1]);
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Adds `value` to `set`.
|
|
*
|
|
* @private
|
|
* @param {Object} set The set to modify.
|
|
* @param {*} value The value to add.
|
|
* @returns {Object} Returns `set`.
|
|
*/
|
|
function addSetEntry(set, value) {
|
|
// Don't return `set.add` because it's not chainable in IE 11.
|
|
set.add(value);
|
|
return set;
|
|
}
|
|
|
|
/**
|
|
* A faster alternative to `Function#apply`, this function invokes `func`
|
|
* with the `this` binding of `thisArg` and the arguments of `args`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to invoke.
|
|
* @param {*} thisArg The `this` binding of `func`.
|
|
* @param {Array} args The arguments to invoke `func` with.
|
|
* @returns {*} Returns the result of `func`.
|
|
*/
|
|
function apply(func, thisArg, args) {
|
|
switch (args.length) {
|
|
case 0: return func.call(thisArg);
|
|
case 1: return func.call(thisArg, args[0]);
|
|
case 2: return func.call(thisArg, args[0], args[1]);
|
|
case 3: return func.call(thisArg, args[0], args[1], args[2]);
|
|
}
|
|
return func.apply(thisArg, args);
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseAggregator` for arrays.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} setter The function to set `accumulator` values.
|
|
* @param {Function} iteratee The iteratee to transform keys.
|
|
* @param {Object} accumulator The initial aggregated object.
|
|
* @returns {Function} Returns `accumulator`.
|
|
*/
|
|
function arrayAggregator(array, setter, iteratee, accumulator) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length;
|
|
|
|
while (++index < length) {
|
|
var value = array[index];
|
|
setter(accumulator, value, iteratee(value), array);
|
|
}
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.forEach` for arrays without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function arrayEach(array, iteratee) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length;
|
|
|
|
while (++index < length) {
|
|
if (iteratee(array[index], index, array) === false) {
|
|
break;
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.forEachRight` for arrays without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function arrayEachRight(array, iteratee) {
|
|
var length = array == null ? 0 : array.length;
|
|
|
|
while (length--) {
|
|
if (iteratee(array[length], length, array) === false) {
|
|
break;
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.every` for arrays without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @returns {boolean} Returns `true` if all elements pass the predicate check,
|
|
* else `false`.
|
|
*/
|
|
function arrayEvery(array, predicate) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length;
|
|
|
|
while (++index < length) {
|
|
if (!predicate(array[index], index, array)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.filter` for arrays without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @returns {Array} Returns the new filtered array.
|
|
*/
|
|
function arrayFilter(array, predicate) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length,
|
|
resIndex = 0,
|
|
result = [];
|
|
|
|
while (++index < length) {
|
|
var value = array[index];
|
|
if (predicate(value, index, array)) {
|
|
result[resIndex++] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.includes` for arrays without support for
|
|
* specifying an index to search from.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to inspect.
|
|
* @param {*} target The value to search for.
|
|
* @returns {boolean} Returns `true` if `target` is found, else `false`.
|
|
*/
|
|
function arrayIncludes(array, value) {
|
|
var length = array == null ? 0 : array.length;
|
|
return !!length && baseIndexOf(array, value, 0) > -1;
|
|
}
|
|
|
|
/**
|
|
* This function is like `arrayIncludes` except that it accepts a comparator.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to inspect.
|
|
* @param {*} target The value to search for.
|
|
* @param {Function} comparator The comparator invoked per element.
|
|
* @returns {boolean} Returns `true` if `target` is found, else `false`.
|
|
*/
|
|
function arrayIncludesWith(array, value, comparator) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length;
|
|
|
|
while (++index < length) {
|
|
if (comparator(value, array[index])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.map` for arrays without support for iteratee
|
|
* shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array} Returns the new mapped array.
|
|
*/
|
|
function arrayMap(array, iteratee) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length,
|
|
result = Array(length);
|
|
|
|
while (++index < length) {
|
|
result[index] = iteratee(array[index], index, array);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Appends the elements of `values` to `array`.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to modify.
|
|
* @param {Array} values The values to append.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function arrayPush(array, values) {
|
|
var index = -1,
|
|
length = values.length,
|
|
offset = array.length;
|
|
|
|
while (++index < length) {
|
|
array[offset + index] = values[index];
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.reduce` for arrays without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @param {*} [accumulator] The initial value.
|
|
* @param {boolean} [initAccum] Specify using the first element of `array` as
|
|
* the initial value.
|
|
* @returns {*} Returns the accumulated value.
|
|
*/
|
|
function arrayReduce(array, iteratee, accumulator, initAccum) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length;
|
|
|
|
if (initAccum && length) {
|
|
accumulator = array[++index];
|
|
}
|
|
while (++index < length) {
|
|
accumulator = iteratee(accumulator, array[index], index, array);
|
|
}
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.reduceRight` for arrays without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @param {*} [accumulator] The initial value.
|
|
* @param {boolean} [initAccum] Specify using the last element of `array` as
|
|
* the initial value.
|
|
* @returns {*} Returns the accumulated value.
|
|
*/
|
|
function arrayReduceRight(array, iteratee, accumulator, initAccum) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (initAccum && length) {
|
|
accumulator = array[--length];
|
|
}
|
|
while (length--) {
|
|
accumulator = iteratee(accumulator, array[length], length, array);
|
|
}
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.some` for arrays without support for iteratee
|
|
* shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} [array] The array to iterate over.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @returns {boolean} Returns `true` if any element passes the predicate check,
|
|
* else `false`.
|
|
*/
|
|
function arraySome(array, predicate) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length;
|
|
|
|
while (++index < length) {
|
|
if (predicate(array[index], index, array)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the size of an ASCII `string`.
|
|
*
|
|
* @private
|
|
* @param {string} string The string inspect.
|
|
* @returns {number} Returns the string size.
|
|
*/
|
|
var asciiSize = baseProperty('length');
|
|
|
|
/**
|
|
* Converts an ASCII `string` to an array.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to convert.
|
|
* @returns {Array} Returns the converted array.
|
|
*/
|
|
function asciiToArray(string) {
|
|
return string.split('');
|
|
}
|
|
|
|
/**
|
|
* Splits an ASCII `string` into an array of its words.
|
|
*
|
|
* @private
|
|
* @param {string} The string to inspect.
|
|
* @returns {Array} Returns the words of `string`.
|
|
*/
|
|
function asciiWords(string) {
|
|
return string.match(reAsciiWord) || [];
|
|
}
|
|
|
|
/**
|
|
* The base implementation of methods like `_.findKey` and `_.findLastKey`,
|
|
* without support for iteratee shorthands, which iterates over `collection`
|
|
* using `eachFunc`.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to inspect.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @param {Function} eachFunc The function to iterate over `collection`.
|
|
* @returns {*} Returns the found element or its key, else `undefined`.
|
|
*/
|
|
function baseFindKey(collection, predicate, eachFunc) {
|
|
var result;
|
|
eachFunc(collection, function(value, key, collection) {
|
|
if (predicate(value, key, collection)) {
|
|
result = key;
|
|
return false;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.findIndex` and `_.findLastIndex` without
|
|
* support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @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 value, else `-1`.
|
|
*/
|
|
function baseFindIndex(array, predicate, fromIndex, fromRight) {
|
|
var length = array.length,
|
|
index = fromIndex + (fromRight ? 1 : -1);
|
|
|
|
while ((fromRight ? index-- : ++index < length)) {
|
|
if (predicate(array[index], index, array)) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.indexOf` without `fromIndex` bounds checks.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @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) {
|
|
return value === value
|
|
? strictIndexOf(array, value, fromIndex)
|
|
: baseFindIndex(array, baseIsNaN, fromIndex);
|
|
}
|
|
|
|
/**
|
|
* This function is like `baseIndexOf` except that it accepts a comparator.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} value The value to search for.
|
|
* @param {number} fromIndex The index to search from.
|
|
* @param {Function} comparator The comparator invoked per element.
|
|
* @returns {number} Returns the index of the matched value, else `-1`.
|
|
*/
|
|
function baseIndexOfWith(array, value, fromIndex, comparator) {
|
|
var index = fromIndex - 1,
|
|
length = array.length;
|
|
|
|
while (++index < length) {
|
|
if (comparator(array[index], value)) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isNaN` without support for number objects.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
|
|
*/
|
|
function baseIsNaN(value) {
|
|
return value !== value;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.mean` and `_.meanBy` without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {number} Returns the mean.
|
|
*/
|
|
function baseMean(array, iteratee) {
|
|
var length = array == null ? 0 : array.length;
|
|
return length ? (baseSum(array, iteratee) / length) : NAN;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.property` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {string} key The key of the property to get.
|
|
* @returns {Function} Returns the new accessor function.
|
|
*/
|
|
function baseProperty(key) {
|
|
return function(object) {
|
|
return object == null ? undefined : object[key];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.propertyOf` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Function} Returns the new accessor function.
|
|
*/
|
|
function basePropertyOf(object) {
|
|
return function(key) {
|
|
return object == null ? undefined : object[key];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.reduce` and `_.reduceRight`, without support
|
|
* for iteratee shorthands, which iterates over `collection` using `eachFunc`.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @param {*} accumulator The initial value.
|
|
* @param {boolean} initAccum Specify using the first or last element of
|
|
* `collection` as the initial value.
|
|
* @param {Function} eachFunc The function to iterate over `collection`.
|
|
* @returns {*} Returns the accumulated value.
|
|
*/
|
|
function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
|
|
eachFunc(collection, function(value, index, collection) {
|
|
accumulator = initAccum
|
|
? (initAccum = false, value)
|
|
: iteratee(accumulator, value, index, collection);
|
|
});
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sortBy` which uses `comparer` to define the
|
|
* sort order of `array` and replaces criteria objects with their corresponding
|
|
* values.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to sort.
|
|
* @param {Function} comparer The function to define sort order.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function baseSortBy(array, comparer) {
|
|
var length = array.length;
|
|
|
|
array.sort(comparer);
|
|
while (length--) {
|
|
array[length] = array[length].value;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sum` and `_.sumBy` without support for
|
|
* iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {number} Returns the sum.
|
|
*/
|
|
function baseSum(array, iteratee) {
|
|
var result,
|
|
index = -1,
|
|
length = array.length;
|
|
|
|
while (++index < length) {
|
|
var current = iteratee(array[index]);
|
|
if (current !== undefined) {
|
|
result = result === undefined ? current : (result + current);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.times` without support for iteratee shorthands
|
|
* or max array length checks.
|
|
*
|
|
* @private
|
|
* @param {number} n The number of times to invoke `iteratee`.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array} Returns the array of results.
|
|
*/
|
|
function baseTimes(n, iteratee) {
|
|
var index = -1,
|
|
result = Array(n);
|
|
|
|
while (++index < n) {
|
|
result[index] = iteratee(index);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
|
|
* of key-value pairs for `object` corresponding to the property names of `props`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Array} props The property names to get values for.
|
|
* @returns {Object} Returns the key-value pairs.
|
|
*/
|
|
function baseToPairs(object, props) {
|
|
return arrayMap(props, function(key) {
|
|
return [key, object[key]];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.unary` without support for storing metadata.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to cap arguments for.
|
|
* @returns {Function} Returns the new capped function.
|
|
*/
|
|
function baseUnary(func) {
|
|
return function(value) {
|
|
return func(value);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.values` and `_.valuesIn` which creates an
|
|
* array of `object` property values corresponding to the property names
|
|
* of `props`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Array} props The property names to get values for.
|
|
* @returns {Object} Returns the array of property values.
|
|
*/
|
|
function baseValues(object, props) {
|
|
return arrayMap(props, function(key) {
|
|
return object[key];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks if a `cache` value for `key` exists.
|
|
*
|
|
* @private
|
|
* @param {Object} cache The cache to query.
|
|
* @param {string} key The key of the entry to check.
|
|
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
|
*/
|
|
function cacheHas(cache, key) {
|
|
return cache.has(key);
|
|
}
|
|
|
|
/**
|
|
* Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
|
|
* that is not found in the character symbols.
|
|
*
|
|
* @private
|
|
* @param {Array} strSymbols The string symbols to inspect.
|
|
* @param {Array} chrSymbols The character symbols to find.
|
|
* @returns {number} Returns the index of the first unmatched string symbol.
|
|
*/
|
|
function charsStartIndex(strSymbols, chrSymbols) {
|
|
var index = -1,
|
|
length = strSymbols.length;
|
|
|
|
while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
|
|
* that is not found in the character symbols.
|
|
*
|
|
* @private
|
|
* @param {Array} strSymbols The string symbols to inspect.
|
|
* @param {Array} chrSymbols The character symbols to find.
|
|
* @returns {number} Returns the index of the last unmatched string symbol.
|
|
*/
|
|
function charsEndIndex(strSymbols, chrSymbols) {
|
|
var index = strSymbols.length;
|
|
|
|
while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of `placeholder` occurrences in `array`.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} placeholder The placeholder to search for.
|
|
* @returns {number} Returns the placeholder count.
|
|
*/
|
|
function countHolders(array, placeholder) {
|
|
var length = array.length,
|
|
result = 0;
|
|
|
|
while (length--) {
|
|
if (array[length] === placeholder) {
|
|
++result;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
|
|
* letters to basic Latin letters.
|
|
*
|
|
* @private
|
|
* @param {string} letter The matched letter to deburr.
|
|
* @returns {string} Returns the deburred letter.
|
|
*/
|
|
var deburrLetter = basePropertyOf(deburredLetters);
|
|
|
|
/**
|
|
* Used by `_.escape` to convert characters to HTML entities.
|
|
*
|
|
* @private
|
|
* @param {string} chr The matched character to escape.
|
|
* @returns {string} Returns the escaped character.
|
|
*/
|
|
var escapeHtmlChar = basePropertyOf(htmlEscapes);
|
|
|
|
/**
|
|
* 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 value at `key` of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} [object] The object to query.
|
|
* @param {string} key The key of the property to get.
|
|
* @returns {*} Returns the property value.
|
|
*/
|
|
function getValue(object, key) {
|
|
return object == null ? undefined : object[key];
|
|
}
|
|
|
|
/**
|
|
* Checks if `string` contains Unicode symbols.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to inspect.
|
|
* @returns {boolean} Returns `true` if a symbol is found, else `false`.
|
|
*/
|
|
function hasUnicode(string) {
|
|
return reHasUnicode.test(string);
|
|
}
|
|
|
|
/**
|
|
* Checks if `string` contains a word composed of Unicode symbols.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to inspect.
|
|
* @returns {boolean} Returns `true` if a word is found, else `false`.
|
|
*/
|
|
function hasUnicodeWord(string) {
|
|
return reHasUnicodeWord.test(string);
|
|
}
|
|
|
|
/**
|
|
* Converts `iterator` to an array.
|
|
*
|
|
* @private
|
|
* @param {Object} iterator The iterator to convert.
|
|
* @returns {Array} Returns the converted array.
|
|
*/
|
|
function iteratorToArray(iterator) {
|
|
var data,
|
|
result = [];
|
|
|
|
while (!(data = iterator.next()).done) {
|
|
result.push(data.value);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts `map` to its key-value pairs.
|
|
*
|
|
* @private
|
|
* @param {Object} map The map to convert.
|
|
* @returns {Array} Returns the key-value pairs.
|
|
*/
|
|
function mapToArray(map) {
|
|
var index = -1,
|
|
result = Array(map.size);
|
|
|
|
map.forEach(function(value, key) {
|
|
result[++index] = [key, value];
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a unary function that invokes `func` with its argument transformed.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to wrap.
|
|
* @param {Function} transform The argument transform.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function overArg(func, transform) {
|
|
return function(arg) {
|
|
return func(transform(arg));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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 = 0,
|
|
result = [];
|
|
|
|
while (++index < length) {
|
|
var value = array[index];
|
|
if (value === placeholder || value === PLACEHOLDER) {
|
|
array[index] = PLACEHOLDER;
|
|
result[resIndex++] = index;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts `set` to an array of its values.
|
|
*
|
|
* @private
|
|
* @param {Object} set The set to convert.
|
|
* @returns {Array} Returns the values.
|
|
*/
|
|
function setToArray(set) {
|
|
var index = -1,
|
|
result = Array(set.size);
|
|
|
|
set.forEach(function(value) {
|
|
result[++index] = value;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts `set` to its value-value pairs.
|
|
*
|
|
* @private
|
|
* @param {Object} set The set to convert.
|
|
* @returns {Array} Returns the value-value pairs.
|
|
*/
|
|
function setToPairs(set) {
|
|
var index = -1,
|
|
result = Array(set.size);
|
|
|
|
set.forEach(function(value) {
|
|
result[++index] = [value, value];
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.indexOf` which performs strict equality
|
|
* comparisons of values, i.e. `===`.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @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 strictIndexOf(array, value, fromIndex) {
|
|
var index = fromIndex - 1,
|
|
length = array.length;
|
|
|
|
while (++index < length) {
|
|
if (array[index] === value) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.lastIndexOf` which performs strict equality
|
|
* comparisons of values, i.e. `===`.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @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 strictLastIndexOf(array, value, fromIndex) {
|
|
var index = fromIndex + 1;
|
|
while (index--) {
|
|
if (array[index] === value) {
|
|
return index;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of symbols in `string`.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to inspect.
|
|
* @returns {number} Returns the string size.
|
|
*/
|
|
function stringSize(string) {
|
|
return hasUnicode(string)
|
|
? unicodeSize(string)
|
|
: asciiSize(string);
|
|
}
|
|
|
|
/**
|
|
* Converts `string` to an array.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to convert.
|
|
* @returns {Array} Returns the converted array.
|
|
*/
|
|
function stringToArray(string) {
|
|
return hasUnicode(string)
|
|
? unicodeToArray(string)
|
|
: asciiToArray(string);
|
|
}
|
|
|
|
/**
|
|
* Used by `_.unescape` to convert HTML entities to characters.
|
|
*
|
|
* @private
|
|
* @param {string} chr The matched character to unescape.
|
|
* @returns {string} Returns the unescaped character.
|
|
*/
|
|
var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
|
|
|
|
/**
|
|
* Gets the size of a Unicode `string`.
|
|
*
|
|
* @private
|
|
* @param {string} string The string inspect.
|
|
* @returns {number} Returns the string size.
|
|
*/
|
|
function unicodeSize(string) {
|
|
var result = reUnicode.lastIndex = 0;
|
|
while (reUnicode.test(string)) {
|
|
++result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts a Unicode `string` to an array.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to convert.
|
|
* @returns {Array} Returns the converted array.
|
|
*/
|
|
function unicodeToArray(string) {
|
|
return string.match(reUnicode) || [];
|
|
}
|
|
|
|
/**
|
|
* Splits a Unicode `string` into an array of its words.
|
|
*
|
|
* @private
|
|
* @param {string} The string to inspect.
|
|
* @returns {Array} Returns the words of `string`.
|
|
*/
|
|
function unicodeWords(string) {
|
|
return string.match(reUnicodeWord) || [];
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Create a new pristine `lodash` function using the `context` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.1.0
|
|
* @category Util
|
|
* @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
|
|
*
|
|
* // Create a suped-up `defer` in Node.js.
|
|
* var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
|
|
*/
|
|
var runInContext = (function runInContext(context) {
|
|
context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
|
|
|
|
/** Built-in constructor references. */
|
|
var Array = context.Array,
|
|
Date = context.Date,
|
|
Error = context.Error,
|
|
Function = context.Function,
|
|
Math = context.Math,
|
|
Object = context.Object,
|
|
RegExp = context.RegExp,
|
|
String = context.String,
|
|
TypeError = context.TypeError;
|
|
|
|
/** Used for built-in method references. */
|
|
var arrayProto = Array.prototype,
|
|
funcProto = Function.prototype,
|
|
objectProto = Object.prototype;
|
|
|
|
/** Used to detect overreaching core-js shims. */
|
|
var coreJsData = context['__core-js_shared__'];
|
|
|
|
/** Used to resolve the decompiled source of functions. */
|
|
var funcToString = funcProto.toString;
|
|
|
|
/** Used to check objects for own properties. */
|
|
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
|
|
/** Used to generate unique IDs. */
|
|
var idCounter = 0;
|
|
|
|
/** Used to detect methods masquerading as native. */
|
|
var maskSrcKey = (function() {
|
|
var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
|
|
return uid ? ('Symbol(src)_1.' + uid) : '';
|
|
}());
|
|
|
|
/**
|
|
* Used to resolve the
|
|
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var nativeObjectToString = objectProto.toString;
|
|
|
|
/** Used to infer the `Object` constructor. */
|
|
var objectCtorString = funcToString.call(Object);
|
|
|
|
/** Used to restore the original `_` reference in `_.noConflict`. */
|
|
var oldDash = root._;
|
|
|
|
/** Used to detect if a method is native. */
|
|
var reIsNative = RegExp('^' +
|
|
funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
|
|
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
|
|
);
|
|
|
|
/** Built-in value references. */
|
|
var Buffer = moduleExports ? context.Buffer : undefined,
|
|
Symbol = context.Symbol,
|
|
Uint8Array = context.Uint8Array,
|
|
allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
|
|
getPrototype = overArg(Object.getPrototypeOf, Object),
|
|
objectCreate = Object.create,
|
|
propertyIsEnumerable = objectProto.propertyIsEnumerable,
|
|
splice = arrayProto.splice,
|
|
spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
|
|
symIterator = Symbol ? Symbol.iterator : undefined,
|
|
symToStringTag = Symbol ? Symbol.toStringTag : undefined;
|
|
|
|
var defineProperty = (function() {
|
|
try {
|
|
var func = getNative(Object, 'defineProperty');
|
|
func({}, '', {});
|
|
return func;
|
|
} catch (e) {}
|
|
}());
|
|
|
|
/** Mocked built-ins. */
|
|
var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
|
|
ctxNow = Date && Date.now !== root.Date.now && Date.now,
|
|
ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
|
|
|
|
/* Built-in method references for those with the same name as other `lodash` methods. */
|
|
var nativeCeil = Math.ceil,
|
|
nativeFloor = Math.floor,
|
|
nativeGetSymbols = Object.getOwnPropertySymbols,
|
|
nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
|
|
nativeIsFinite = context.isFinite,
|
|
nativeJoin = arrayProto.join,
|
|
nativeKeys = overArg(Object.keys, Object),
|
|
nativeMax = Math.max,
|
|
nativeMin = Math.min,
|
|
nativeNow = Date.now,
|
|
nativeParseInt = context.parseInt,
|
|
nativeRandom = Math.random,
|
|
nativeReverse = arrayProto.reverse;
|
|
|
|
/* Built-in method references that are verified to be native. */
|
|
var DataView = getNative(context, 'DataView'),
|
|
Map = getNative(context, 'Map'),
|
|
Promise = getNative(context, 'Promise'),
|
|
Set = getNative(context, 'Set'),
|
|
WeakMap = getNative(context, 'WeakMap'),
|
|
nativeCreate = getNative(Object, 'create');
|
|
|
|
/** Used to store function metadata. */
|
|
var metaMap = WeakMap && new WeakMap;
|
|
|
|
/** Used to lookup unminified function names. */
|
|
var realNames = {};
|
|
|
|
/** Used to detect maps, sets, and weakmaps. */
|
|
var dataViewCtorString = toSource(DataView),
|
|
mapCtorString = toSource(Map),
|
|
promiseCtorString = toSource(Promise),
|
|
setCtorString = toSource(Set),
|
|
weakMapCtorString = toSource(WeakMap);
|
|
|
|
/** Used to convert symbols to primitives and strings. */
|
|
var symbolProto = Symbol ? Symbol.prototype : undefined,
|
|
symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
|
|
symbolToString = symbolProto ? symbolProto.toString : undefined;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates a `lodash` object which wraps `value` to enable implicit method
|
|
* chain sequences. 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 sequence
|
|
* and return the unwrapped value. Otherwise, the value must be unwrapped
|
|
* with `_#value`.
|
|
*
|
|
* Explicit chain sequences, which must be unwrapped with `_#value`, may be
|
|
* enabled using `_.chain`.
|
|
*
|
|
* The execution of chained methods is lazy, that is, it's deferred until
|
|
* `_#value` is implicitly or explicitly called.
|
|
*
|
|
* Lazy evaluation allows several methods to support shortcut fusion.
|
|
* Shortcut fusion is an optimization to merge iteratee calls; this avoids
|
|
* the creation of intermediate arrays and can greatly reduce the number of
|
|
* iteratee executions. Sections of a chain sequence qualify for shortcut
|
|
* fusion if the section is applied to an array and iteratees accept only
|
|
* one argument. The heuristic for whether a section qualifies for shortcut
|
|
* fusion is subject to change.
|
|
*
|
|
* 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`, `shift`, `sort`, `splice`, and `unshift`
|
|
*
|
|
* The wrapper `String` methods are:
|
|
* `replace` and `split`
|
|
*
|
|
* The wrapper methods that support shortcut fusion are:
|
|
* `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
|
|
* `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
|
|
* `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
|
|
*
|
|
* The chainable wrapper methods are:
|
|
* `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
|
|
* `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
|
|
* `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
|
|
* `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
|
|
* `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
|
|
* `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
|
|
* `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
|
|
* `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
|
|
* `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
|
|
* `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
|
|
* `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
|
|
* `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
|
|
* `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
|
|
* `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
|
|
* `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
|
|
* `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
|
|
* `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
|
|
* `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
|
|
* `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
|
|
* `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
|
|
* `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
|
|
* `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
|
|
* `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
|
|
* `zipObject`, `zipObjectDeep`, and `zipWith`
|
|
*
|
|
* The wrapper methods that are **not** chainable by default are:
|
|
* `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
|
|
* `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
|
|
* `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
|
|
* `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
|
|
* `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
|
|
* `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
|
|
* `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
|
|
* `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
|
|
* `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
|
|
* `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
|
|
* `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
|
|
* `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
|
|
* `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
|
|
* `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
|
|
* `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
|
|
* `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
|
|
* `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
|
|
* `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
|
|
* `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
|
|
* `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
|
|
* `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
|
|
* `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
|
|
* `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
|
|
* `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
|
|
* `upperFirst`, `value`, and `words`
|
|
*
|
|
* @name _
|
|
* @constructor
|
|
* @category Seq
|
|
* @param {*} value The value to wrap in a `lodash` instance.
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* function square(n) {
|
|
* return n * n;
|
|
* }
|
|
*
|
|
* var wrapped = _([1, 2, 3]);
|
|
*
|
|
* // Returns an unwrapped value.
|
|
* wrapped.reduce(_.add);
|
|
* // => 6
|
|
*
|
|
* // Returns a wrapped value.
|
|
* var squares = wrapped.map(square);
|
|
*
|
|
* _.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, '__wrapped__')) {
|
|
return wrapperClone(value);
|
|
}
|
|
}
|
|
return new LodashWrapper(value);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.create` without support for assigning
|
|
* properties to the created object.
|
|
*
|
|
* @private
|
|
* @param {Object} proto The object to inherit from.
|
|
* @returns {Object} Returns the new object.
|
|
*/
|
|
var baseCreate = (function() {
|
|
function object() {}
|
|
return function(proto) {
|
|
if (!isObject(proto)) {
|
|
return {};
|
|
}
|
|
if (objectCreate) {
|
|
return objectCreate(proto);
|
|
}
|
|
object.prototype = proto;
|
|
var result = new object;
|
|
object.prototype = undefined;
|
|
return result;
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* The function whose prototype chain sequence 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 explicit method chain sequences.
|
|
*/
|
|
function LodashWrapper(value, chainAll) {
|
|
this.__wrapped__ = value;
|
|
this.__actions__ = [];
|
|
this.__chain__ = !!chainAll;
|
|
this.__index__ = 0;
|
|
this.__values__ = undefined;
|
|
}
|
|
|
|
/**
|
|
* By default, the template delimiters used by lodash are like those in
|
|
* embedded Ruby (ERB) as well as ES2015 template strings. 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
|
|
}
|
|
};
|
|
|
|
// Ensure wrappers are instances of `baseLodash`.
|
|
lodash.prototype = baseLodash.prototype;
|
|
lodash.prototype.constructor = lodash;
|
|
|
|
LodashWrapper.prototype = baseCreate(baseLodash.prototype);
|
|
LodashWrapper.prototype.constructor = LodashWrapper;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
* @param {*} value The value to wrap.
|
|
*/
|
|
function LazyWrapper(value) {
|
|
this.__wrapped__ = value;
|
|
this.__actions__ = [];
|
|
this.__dir__ = 1;
|
|
this.__filtered__ = false;
|
|
this.__iteratees__ = [];
|
|
this.__takeCount__ = MAX_ARRAY_LENGTH;
|
|
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__ = copyArray(this.__actions__);
|
|
result.__dir__ = this.__dir__;
|
|
result.__filtered__ = this.__filtered__;
|
|
result.__iteratees__ = copyArray(this.__iteratees__);
|
|
result.__takeCount__ = this.__takeCount__;
|
|
result.__views__ = copyArray(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 {*} Returns the unwrapped value.
|
|
*/
|
|
function lazyValue() {
|
|
var array = this.__wrapped__.value(),
|
|
dir = this.__dir__,
|
|
isArr = isArray(array),
|
|
isRight = dir < 0,
|
|
arrLength = isArr ? array.length : 0,
|
|
view = getView(0, arrLength, this.__views__),
|
|
start = view.start,
|
|
end = view.end,
|
|
length = end - start,
|
|
index = isRight ? end : (start - 1),
|
|
iteratees = this.__iteratees__,
|
|
iterLength = iteratees.length,
|
|
resIndex = 0,
|
|
takeCount = nativeMin(length, this.__takeCount__);
|
|
|
|
if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
|
|
return baseWrapperValue(array, this.__actions__);
|
|
}
|
|
var result = [];
|
|
|
|
outer:
|
|
while (length-- && resIndex < takeCount) {
|
|
index += dir;
|
|
|
|
var iterIndex = -1,
|
|
value = array[index];
|
|
|
|
while (++iterIndex < iterLength) {
|
|
var data = iteratees[iterIndex],
|
|
iteratee = data.iteratee,
|
|
type = data.type,
|
|
computed = iteratee(value);
|
|
|
|
if (type == LAZY_MAP_FLAG) {
|
|
value = computed;
|
|
} else if (!computed) {
|
|
if (type == LAZY_FILTER_FLAG) {
|
|
continue outer;
|
|
} else {
|
|
break outer;
|
|
}
|
|
}
|
|
}
|
|
result[resIndex++] = value;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Ensure `LazyWrapper` is an instance of `baseLodash`.
|
|
LazyWrapper.prototype = baseCreate(baseLodash.prototype);
|
|
LazyWrapper.prototype.constructor = LazyWrapper;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates a hash object.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
* @param {Array} [entries] The key-value pairs to cache.
|
|
*/
|
|
function Hash(entries) {
|
|
var index = -1,
|
|
length = entries == null ? 0 : entries.length;
|
|
|
|
this.clear();
|
|
while (++index < length) {
|
|
var entry = entries[index];
|
|
this.set(entry[0], entry[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes all key-value entries from the hash.
|
|
*
|
|
* @private
|
|
* @name clear
|
|
* @memberOf Hash
|
|
*/
|
|
function hashClear() {
|
|
this.__data__ = nativeCreate ? nativeCreate(null) : {};
|
|
this.size = 0;
|
|
}
|
|
|
|
/**
|
|
* Removes `key` and its value from the hash.
|
|
*
|
|
* @private
|
|
* @name delete
|
|
* @memberOf Hash
|
|
* @param {Object} hash The hash to modify.
|
|
* @param {string} key The key of the value to remove.
|
|
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
|
*/
|
|
function hashDelete(key) {
|
|
var result = this.has(key) && delete this.__data__[key];
|
|
this.size -= result ? 1 : 0;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the hash value for `key`.
|
|
*
|
|
* @private
|
|
* @name get
|
|
* @memberOf Hash
|
|
* @param {string} key The key of the value to get.
|
|
* @returns {*} Returns the entry value.
|
|
*/
|
|
function hashGet(key) {
|
|
var data = this.__data__;
|
|
if (nativeCreate) {
|
|
var result = data[key];
|
|
return result === HASH_UNDEFINED ? undefined : result;
|
|
}
|
|
return hasOwnProperty.call(data, key) ? data[key] : undefined;
|
|
}
|
|
|
|
/**
|
|
* Checks if a hash value for `key` exists.
|
|
*
|
|
* @private
|
|
* @name has
|
|
* @memberOf Hash
|
|
* @param {string} key The key of the entry to check.
|
|
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
|
*/
|
|
function hashHas(key) {
|
|
var data = this.__data__;
|
|
return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
|
|
}
|
|
|
|
/**
|
|
* Sets the hash `key` to `value`.
|
|
*
|
|
* @private
|
|
* @name set
|
|
* @memberOf Hash
|
|
* @param {string} key The key of the value to set.
|
|
* @param {*} value The value to set.
|
|
* @returns {Object} Returns the hash instance.
|
|
*/
|
|
function hashSet(key, value) {
|
|
var data = this.__data__;
|
|
this.size += this.has(key) ? 0 : 1;
|
|
data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
|
|
return this;
|
|
}
|
|
|
|
// Add methods to `Hash`.
|
|
Hash.prototype.clear = hashClear;
|
|
Hash.prototype['delete'] = hashDelete;
|
|
Hash.prototype.get = hashGet;
|
|
Hash.prototype.has = hashHas;
|
|
Hash.prototype.set = hashSet;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates an list cache object.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
* @param {Array} [entries] The key-value pairs to cache.
|
|
*/
|
|
function ListCache(entries) {
|
|
var index = -1,
|
|
length = entries == null ? 0 : entries.length;
|
|
|
|
this.clear();
|
|
while (++index < length) {
|
|
var entry = entries[index];
|
|
this.set(entry[0], entry[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes all key-value entries from the list cache.
|
|
*
|
|
* @private
|
|
* @name clear
|
|
* @memberOf ListCache
|
|
*/
|
|
function listCacheClear() {
|
|
this.__data__ = [];
|
|
this.size = 0;
|
|
}
|
|
|
|
/**
|
|
* Removes `key` and its value from the list cache.
|
|
*
|
|
* @private
|
|
* @name delete
|
|
* @memberOf ListCache
|
|
* @param {string} key The key of the value to remove.
|
|
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
|
*/
|
|
function listCacheDelete(key) {
|
|
var data = this.__data__,
|
|
index = assocIndexOf(data, key);
|
|
|
|
if (index < 0) {
|
|
return false;
|
|
}
|
|
var lastIndex = data.length - 1;
|
|
if (index == lastIndex) {
|
|
data.pop();
|
|
} else {
|
|
splice.call(data, index, 1);
|
|
}
|
|
--this.size;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets the list cache value for `key`.
|
|
*
|
|
* @private
|
|
* @name get
|
|
* @memberOf ListCache
|
|
* @param {string} key The key of the value to get.
|
|
* @returns {*} Returns the entry value.
|
|
*/
|
|
function listCacheGet(key) {
|
|
var data = this.__data__,
|
|
index = assocIndexOf(data, key);
|
|
|
|
return index < 0 ? undefined : data[index][1];
|
|
}
|
|
|
|
/**
|
|
* Checks if a list cache value for `key` exists.
|
|
*
|
|
* @private
|
|
* @name has
|
|
* @memberOf ListCache
|
|
* @param {string} key The key of the entry to check.
|
|
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
|
*/
|
|
function listCacheHas(key) {
|
|
return assocIndexOf(this.__data__, key) > -1;
|
|
}
|
|
|
|
/**
|
|
* Sets the list cache `key` to `value`.
|
|
*
|
|
* @private
|
|
* @name set
|
|
* @memberOf ListCache
|
|
* @param {string} key The key of the value to set.
|
|
* @param {*} value The value to set.
|
|
* @returns {Object} Returns the list cache instance.
|
|
*/
|
|
function listCacheSet(key, value) {
|
|
var data = this.__data__,
|
|
index = assocIndexOf(data, key);
|
|
|
|
if (index < 0) {
|
|
++this.size;
|
|
data.push([key, value]);
|
|
} else {
|
|
data[index][1] = value;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Add methods to `ListCache`.
|
|
ListCache.prototype.clear = listCacheClear;
|
|
ListCache.prototype['delete'] = listCacheDelete;
|
|
ListCache.prototype.get = listCacheGet;
|
|
ListCache.prototype.has = listCacheHas;
|
|
ListCache.prototype.set = listCacheSet;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates a map cache object to store key-value pairs.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
* @param {Array} [entries] The key-value pairs to cache.
|
|
*/
|
|
function MapCache(entries) {
|
|
var index = -1,
|
|
length = entries == null ? 0 : entries.length;
|
|
|
|
this.clear();
|
|
while (++index < length) {
|
|
var entry = entries[index];
|
|
this.set(entry[0], entry[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes all key-value entries from the map.
|
|
*
|
|
* @private
|
|
* @name clear
|
|
* @memberOf MapCache
|
|
*/
|
|
function mapCacheClear() {
|
|
this.size = 0;
|
|
this.__data__ = {
|
|
'hash': new Hash,
|
|
'map': new (Map || ListCache),
|
|
'string': new Hash
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Removes `key` and its value from the map.
|
|
*
|
|
* @private
|
|
* @name delete
|
|
* @memberOf MapCache
|
|
* @param {string} key The key of the value to remove.
|
|
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
|
*/
|
|
function mapCacheDelete(key) {
|
|
var result = getMapData(this, key)['delete'](key);
|
|
this.size -= result ? 1 : 0;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the map value for `key`.
|
|
*
|
|
* @private
|
|
* @name get
|
|
* @memberOf MapCache
|
|
* @param {string} key The key of the value to get.
|
|
* @returns {*} Returns the entry value.
|
|
*/
|
|
function mapCacheGet(key) {
|
|
return getMapData(this, key).get(key);
|
|
}
|
|
|
|
/**
|
|
* Checks if a map value for `key` exists.
|
|
*
|
|
* @private
|
|
* @name has
|
|
* @memberOf MapCache
|
|
* @param {string} key The key of the entry to check.
|
|
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
|
*/
|
|
function mapCacheHas(key) {
|
|
return getMapData(this, key).has(key);
|
|
}
|
|
|
|
/**
|
|
* Sets the map `key` to `value`.
|
|
*
|
|
* @private
|
|
* @name set
|
|
* @memberOf MapCache
|
|
* @param {string} key The key of the value to set.
|
|
* @param {*} value The value to set.
|
|
* @returns {Object} Returns the map cache instance.
|
|
*/
|
|
function mapCacheSet(key, value) {
|
|
var data = getMapData(this, key),
|
|
size = data.size;
|
|
|
|
data.set(key, value);
|
|
this.size += data.size == size ? 0 : 1;
|
|
return this;
|
|
}
|
|
|
|
// Add methods to `MapCache`.
|
|
MapCache.prototype.clear = mapCacheClear;
|
|
MapCache.prototype['delete'] = mapCacheDelete;
|
|
MapCache.prototype.get = mapCacheGet;
|
|
MapCache.prototype.has = mapCacheHas;
|
|
MapCache.prototype.set = mapCacheSet;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
*
|
|
* Creates an array cache object to store unique values.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
* @param {Array} [values] The values to cache.
|
|
*/
|
|
function SetCache(values) {
|
|
var index = -1,
|
|
length = values == null ? 0 : values.length;
|
|
|
|
this.__data__ = new MapCache;
|
|
while (++index < length) {
|
|
this.add(values[index]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds `value` to the array cache.
|
|
*
|
|
* @private
|
|
* @name add
|
|
* @memberOf SetCache
|
|
* @alias push
|
|
* @param {*} value The value to cache.
|
|
* @returns {Object} Returns the cache instance.
|
|
*/
|
|
function setCacheAdd(value) {
|
|
this.__data__.set(value, HASH_UNDEFINED);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is in the array cache.
|
|
*
|
|
* @private
|
|
* @name has
|
|
* @memberOf SetCache
|
|
* @param {*} value The value to search for.
|
|
* @returns {number} Returns `true` if `value` is found, else `false`.
|
|
*/
|
|
function setCacheHas(value) {
|
|
return this.__data__.has(value);
|
|
}
|
|
|
|
// Add methods to `SetCache`.
|
|
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
|
|
SetCache.prototype.has = setCacheHas;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates a stack cache object to store key-value pairs.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
* @param {Array} [entries] The key-value pairs to cache.
|
|
*/
|
|
function Stack(entries) {
|
|
var data = this.__data__ = new ListCache(entries);
|
|
this.size = data.size;
|
|
}
|
|
|
|
/**
|
|
* Removes all key-value entries from the stack.
|
|
*
|
|
* @private
|
|
* @name clear
|
|
* @memberOf Stack
|
|
*/
|
|
function stackClear() {
|
|
this.__data__ = new ListCache;
|
|
this.size = 0;
|
|
}
|
|
|
|
/**
|
|
* Removes `key` and its value from the stack.
|
|
*
|
|
* @private
|
|
* @name delete
|
|
* @memberOf Stack
|
|
* @param {string} key The key of the value to remove.
|
|
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
|
*/
|
|
function stackDelete(key) {
|
|
var data = this.__data__,
|
|
result = data['delete'](key);
|
|
|
|
this.size = data.size;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the stack value for `key`.
|
|
*
|
|
* @private
|
|
* @name get
|
|
* @memberOf Stack
|
|
* @param {string} key The key of the value to get.
|
|
* @returns {*} Returns the entry value.
|
|
*/
|
|
function stackGet(key) {
|
|
return this.__data__.get(key);
|
|
}
|
|
|
|
/**
|
|
* Checks if a stack value for `key` exists.
|
|
*
|
|
* @private
|
|
* @name has
|
|
* @memberOf Stack
|
|
* @param {string} key The key of the entry to check.
|
|
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
|
*/
|
|
function stackHas(key) {
|
|
return this.__data__.has(key);
|
|
}
|
|
|
|
/**
|
|
* Sets the stack `key` to `value`.
|
|
*
|
|
* @private
|
|
* @name set
|
|
* @memberOf Stack
|
|
* @param {string} key The key of the value to set.
|
|
* @param {*} value The value to set.
|
|
* @returns {Object} Returns the stack cache instance.
|
|
*/
|
|
function stackSet(key, value) {
|
|
var data = this.__data__;
|
|
if (data instanceof ListCache) {
|
|
var pairs = data.__data__;
|
|
if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
|
|
pairs.push([key, value]);
|
|
this.size = ++data.size;
|
|
return this;
|
|
}
|
|
data = this.__data__ = new MapCache(pairs);
|
|
}
|
|
data.set(key, value);
|
|
this.size = data.size;
|
|
return this;
|
|
}
|
|
|
|
// Add methods to `Stack`.
|
|
Stack.prototype.clear = stackClear;
|
|
Stack.prototype['delete'] = stackDelete;
|
|
Stack.prototype.get = stackGet;
|
|
Stack.prototype.has = stackHas;
|
|
Stack.prototype.set = stackSet;
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates an array of the enumerable property names of the array-like `value`.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to query.
|
|
* @param {boolean} inherited Specify returning inherited property names.
|
|
* @returns {Array} Returns the array of property names.
|
|
*/
|
|
function arrayLikeKeys(value, inherited) {
|
|
var isArr = isArray(value),
|
|
isArg = !isArr && isArguments(value),
|
|
isBuff = !isArr && !isArg && isBuffer(value),
|
|
isType = !isArr && !isArg && !isBuff && isTypedArray(value),
|
|
skipIndexes = isArr || isArg || isBuff || isType,
|
|
result = skipIndexes ? baseTimes(value.length, String) : [],
|
|
length = result.length;
|
|
|
|
for (var key in value) {
|
|
if ((inherited || hasOwnProperty.call(value, key)) &&
|
|
!(skipIndexes && (
|
|
// Safari 9 has enumerable `arguments.length` in strict mode.
|
|
key == 'length' ||
|
|
// Node.js 0.10 has enumerable non-index properties on buffers.
|
|
(isBuff && (key == 'offset' || key == 'parent')) ||
|
|
// PhantomJS 2 has enumerable non-index properties on typed arrays.
|
|
(isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
|
|
// Skip index properties.
|
|
isIndex(key, length)
|
|
))) {
|
|
result.push(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.sample` for arrays.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to sample.
|
|
* @returns {*} Returns the random element.
|
|
*/
|
|
function arraySample(array) {
|
|
var length = array.length;
|
|
return length ? array[baseRandom(0, length - 1)] : undefined;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.sampleSize` for arrays.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to sample.
|
|
* @param {number} n The number of elements to sample.
|
|
* @returns {Array} Returns the random elements.
|
|
*/
|
|
function arraySampleSize(array, n) {
|
|
return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.shuffle` for arrays.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to shuffle.
|
|
* @returns {Array} Returns the new shuffled array.
|
|
*/
|
|
function arrayShuffle(array) {
|
|
return shuffleSelf(copyArray(array));
|
|
}
|
|
|
|
/**
|
|
* This function is like `assignValue` except that it doesn't assign
|
|
* `undefined` values.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to modify.
|
|
* @param {string} key The key of the property to assign.
|
|
* @param {*} value The value to assign.
|
|
*/
|
|
function assignMergeValue(object, key, value) {
|
|
if ((value !== undefined && !eq(object[key], value)) ||
|
|
(value === undefined && !(key in object))) {
|
|
baseAssignValue(object, key, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assigns `value` to `key` of `object` if the existing value is not equivalent
|
|
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to modify.
|
|
* @param {string} key The key of the property to assign.
|
|
* @param {*} value The value to assign.
|
|
*/
|
|
function assignValue(object, key, value) {
|
|
var objValue = object[key];
|
|
if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
|
|
(value === undefined && !(key in object))) {
|
|
baseAssignValue(object, key, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the index at which the `key` is found in `array` of key-value pairs.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} key The key to search for.
|
|
* @returns {number} Returns the index of the matched value, else `-1`.
|
|
*/
|
|
function assocIndexOf(array, key) {
|
|
var length = array.length;
|
|
while (length--) {
|
|
if (eq(array[length][0], key)) {
|
|
return length;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Aggregates elements of `collection` on `accumulator` with keys transformed
|
|
* by `iteratee` and values set by `setter`.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} setter The function to set `accumulator` values.
|
|
* @param {Function} iteratee The iteratee to transform keys.
|
|
* @param {Object} accumulator The initial aggregated object.
|
|
* @returns {Function} Returns `accumulator`.
|
|
*/
|
|
function baseAggregator(collection, setter, iteratee, accumulator) {
|
|
baseEach(collection, function(value, key, collection) {
|
|
setter(accumulator, value, iteratee(value), collection);
|
|
});
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.assign` without support for multiple sources
|
|
* or `customizer` functions.
|
|
*
|
|
* @private
|
|
* @param {Object} object The destination object.
|
|
* @param {Object} source The source object.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseAssign(object, source) {
|
|
return object && copyObject(source, keys(source), object);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.assignIn` without support for multiple sources
|
|
* or `customizer` functions.
|
|
*
|
|
* @private
|
|
* @param {Object} object The destination object.
|
|
* @param {Object} source The source object.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseAssignIn(object, source) {
|
|
return object && copyObject(source, keysIn(source), object);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `assignValue` and `assignMergeValue` without
|
|
* value checks.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to modify.
|
|
* @param {string} key The key of the property to assign.
|
|
* @param {*} value The value to assign.
|
|
*/
|
|
function baseAssignValue(object, key, value) {
|
|
if (key == '__proto__' && defineProperty) {
|
|
defineProperty(object, key, {
|
|
'configurable': true,
|
|
'enumerable': true,
|
|
'value': value,
|
|
'writable': true
|
|
});
|
|
} else {
|
|
object[key] = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.at` without support for individual paths.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {string[]} paths The property paths to pick.
|
|
* @returns {Array} Returns the picked elements.
|
|
*/
|
|
function baseAt(object, paths) {
|
|
var index = -1,
|
|
length = paths.length,
|
|
result = Array(length),
|
|
skip = object == null;
|
|
|
|
while (++index < length) {
|
|
result[index] = skip ? undefined : get(object, paths[index]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.clamp` which doesn't coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {number} number The number to clamp.
|
|
* @param {number} [lower] The lower bound.
|
|
* @param {number} upper The upper bound.
|
|
* @returns {number} Returns the clamped number.
|
|
*/
|
|
function baseClamp(number, lower, upper) {
|
|
if (number === number) {
|
|
if (upper !== undefined) {
|
|
number = number <= upper ? number : upper;
|
|
}
|
|
if (lower !== undefined) {
|
|
number = number >= lower ? number : lower;
|
|
}
|
|
}
|
|
return number;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.clone` and `_.cloneDeep` which tracks
|
|
* traversed objects.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to clone.
|
|
* @param {boolean} bitmask The bitmask flags.
|
|
* 1 - Deep clone
|
|
* 2 - Flatten inherited properties
|
|
* 4 - Clone symbols
|
|
* @param {Function} [customizer] The function to customize cloning.
|
|
* @param {string} [key] The key of `value`.
|
|
* @param {Object} [object] The parent object of `value`.
|
|
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
|
|
* @returns {*} Returns the cloned value.
|
|
*/
|
|
function baseClone(value, bitmask, customizer, key, object, stack) {
|
|
var result,
|
|
isDeep = bitmask & CLONE_DEEP_FLAG,
|
|
isFlat = bitmask & CLONE_FLAT_FLAG,
|
|
isFull = bitmask & CLONE_SYMBOLS_FLAG;
|
|
|
|
if (customizer) {
|
|
result = object ? customizer(value, key, object, stack) : customizer(value);
|
|
}
|
|
if (result !== undefined) {
|
|
return result;
|
|
}
|
|
if (!isObject(value)) {
|
|
return value;
|
|
}
|
|
var isArr = isArray(value);
|
|
if (isArr) {
|
|
result = initCloneArray(value);
|
|
if (!isDeep) {
|
|
return copyArray(value, result);
|
|
}
|
|
} else {
|
|
var tag = getTag(value),
|
|
isFunc = tag == funcTag || tag == genTag;
|
|
|
|
if (isBuffer(value)) {
|
|
return cloneBuffer(value, isDeep);
|
|
}
|
|
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
|
|
result = (isFlat || isFunc) ? {} : initCloneObject(value);
|
|
if (!isDeep) {
|
|
return isFlat
|
|
? copySymbolsIn(value, baseAssignIn(result, value))
|
|
: copySymbols(value, baseAssign(result, value));
|
|
}
|
|
} else {
|
|
if (!cloneableTags[tag]) {
|
|
return object ? value : {};
|
|
}
|
|
result = initCloneByTag(value, tag, baseClone, isDeep);
|
|
}
|
|
}
|
|
// Check for circular references and return its corresponding clone.
|
|
stack || (stack = new Stack);
|
|
var stacked = stack.get(value);
|
|
if (stacked) {
|
|
return stacked;
|
|
}
|
|
stack.set(value, result);
|
|
|
|
var keysFunc = isFull
|
|
? (isFlat ? getAllKeysIn : getAllKeys)
|
|
: (isFlat ? keysIn : keys);
|
|
|
|
var props = isArr ? undefined : keysFunc(value);
|
|
arrayEach(props || value, function(subValue, key) {
|
|
if (props) {
|
|
key = subValue;
|
|
subValue = value[key];
|
|
}
|
|
// Recursively populate clone (susceptible to call stack limits).
|
|
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.conforms` which doesn't clone `source`.
|
|
*
|
|
* @private
|
|
* @param {Object} source The object of property predicates to conform to.
|
|
* @returns {Function} Returns the new spec function.
|
|
*/
|
|
function baseConforms(source) {
|
|
var props = keys(source);
|
|
return function(object) {
|
|
return baseConformsTo(object, source, props);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.conformsTo` which accepts `props` to check.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Object} source The object of property predicates to conform to.
|
|
* @returns {boolean} Returns `true` if `object` conforms, else `false`.
|
|
*/
|
|
function baseConformsTo(object, source, props) {
|
|
var length = props.length;
|
|
if (object == null) {
|
|
return !length;
|
|
}
|
|
object = Object(object);
|
|
while (length--) {
|
|
var key = props[length],
|
|
predicate = source[key],
|
|
value = object[key];
|
|
|
|
if ((value === undefined && !(key in object)) || !predicate(value)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.delay` and `_.defer` which accepts `args`
|
|
* to provide to `func`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to delay.
|
|
* @param {number} wait The number of milliseconds to delay invocation.
|
|
* @param {Array} args The arguments to provide to `func`.
|
|
* @returns {number|Object} Returns the timer id or timeout object.
|
|
*/
|
|
function baseDelay(func, wait, args) {
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
return setTimeout(function() { func.apply(undefined, args); }, wait);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of methods like `_.difference` without support
|
|
* for excluding multiple arrays or iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Array} values The values to exclude.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
*/
|
|
function baseDifference(array, values, iteratee, comparator) {
|
|
var index = -1,
|
|
includes = arrayIncludes,
|
|
isCommon = true,
|
|
length = array.length,
|
|
result = [],
|
|
valuesLength = values.length;
|
|
|
|
if (!length) {
|
|
return result;
|
|
}
|
|
if (iteratee) {
|
|
values = arrayMap(values, baseUnary(iteratee));
|
|
}
|
|
if (comparator) {
|
|
includes = arrayIncludesWith;
|
|
isCommon = false;
|
|
}
|
|
else if (values.length >= LARGE_ARRAY_SIZE) {
|
|
includes = cacheHas;
|
|
isCommon = false;
|
|
values = new SetCache(values);
|
|
}
|
|
outer:
|
|
while (++index < length) {
|
|
var value = array[index],
|
|
computed = iteratee == null ? value : iteratee(value);
|
|
|
|
value = (comparator || value !== 0) ? value : 0;
|
|
if (isCommon && computed === computed) {
|
|
var valuesIndex = valuesLength;
|
|
while (valuesIndex--) {
|
|
if (values[valuesIndex] === computed) {
|
|
continue outer;
|
|
}
|
|
}
|
|
result.push(value);
|
|
}
|
|
else if (!includes(values, computed, comparator)) {
|
|
result.push(value);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.forEach` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array|Object} Returns `collection`.
|
|
*/
|
|
var baseEach = createBaseEach(baseForOwn);
|
|
|
|
/**
|
|
* The base implementation of `_.forEachRight` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array|Object} Returns `collection`.
|
|
*/
|
|
var baseEachRight = createBaseEach(baseForOwnRight, true);
|
|
|
|
/**
|
|
* The base implementation of `_.every` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @returns {boolean} Returns `true` if all elements pass the predicate check,
|
|
* else `false`
|
|
*/
|
|
function baseEvery(collection, predicate) {
|
|
var result = true;
|
|
baseEach(collection, function(value, index, collection) {
|
|
result = !!predicate(value, index, collection);
|
|
return result;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of methods like `_.max` and `_.min` which accepts a
|
|
* `comparator` to determine the extremum value.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} iteratee The iteratee invoked per iteration.
|
|
* @param {Function} comparator The comparator used to compare values.
|
|
* @returns {*} Returns the extremum value.
|
|
*/
|
|
function baseExtremum(array, iteratee, comparator) {
|
|
var index = -1,
|
|
length = array.length;
|
|
|
|
while (++index < length) {
|
|
var value = array[index],
|
|
current = iteratee(value);
|
|
|
|
if (current != null && (computed === undefined
|
|
? (current === current && !isSymbol(current))
|
|
: comparator(current, computed)
|
|
)) {
|
|
var computed = current,
|
|
result = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.fill` without an iteratee call guard.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to fill.
|
|
* @param {*} value The value to fill `array` with.
|
|
* @param {number} [start=0] The start position.
|
|
* @param {number} [end=array.length] The end position.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function baseFill(array, value, start, end) {
|
|
var length = array.length;
|
|
|
|
start = toInteger(start);
|
|
if (start < 0) {
|
|
start = -start > length ? 0 : (length + start);
|
|
}
|
|
end = (end === undefined || end > length) ? length : toInteger(end);
|
|
if (end < 0) {
|
|
end += length;
|
|
}
|
|
end = start > end ? 0 : toLength(end);
|
|
while (start < end) {
|
|
array[start++] = value;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.filter` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @returns {Array} Returns the new filtered array.
|
|
*/
|
|
function baseFilter(collection, predicate) {
|
|
var result = [];
|
|
baseEach(collection, function(value, index, collection) {
|
|
if (predicate(value, index, collection)) {
|
|
result.push(value);
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.flatten` with support for restricting flattening.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to flatten.
|
|
* @param {number} depth The maximum recursion depth.
|
|
* @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
|
|
* @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
|
|
* @param {Array} [result=[]] The initial result value.
|
|
* @returns {Array} Returns the new flattened array.
|
|
*/
|
|
function baseFlatten(array, depth, predicate, isStrict, result) {
|
|
var index = -1,
|
|
length = array.length;
|
|
|
|
predicate || (predicate = isFlattenable);
|
|
result || (result = []);
|
|
|
|
while (++index < length) {
|
|
var value = array[index];
|
|
if (depth > 0 && predicate(value)) {
|
|
if (depth > 1) {
|
|
// Recursively flatten arrays (susceptible to call stack limits).
|
|
baseFlatten(value, depth - 1, predicate, isStrict, result);
|
|
} else {
|
|
arrayPush(result, value);
|
|
}
|
|
} else if (!isStrict) {
|
|
result[result.length] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `baseForOwn` which iterates over `object`
|
|
* properties returned by `keysFunc` and invokes `iteratee` for each property.
|
|
* Iteratee functions may exit iteration early by explicitly returning `false`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @param {Function} keysFunc The function to get the keys of `object`.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
var baseFor = createBaseFor();
|
|
|
|
/**
|
|
* This function is like `baseFor` except that it iterates over properties
|
|
* in the opposite order.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @param {Function} keysFunc The function to get the keys of `object`.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
var baseForRight = createBaseFor(true);
|
|
|
|
/**
|
|
* The base implementation of `_.forOwn` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseForOwn(object, iteratee) {
|
|
return object && baseFor(object, iteratee, keys);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.forOwnRight` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseForOwnRight(object, iteratee) {
|
|
return object && baseForRight(object, iteratee, keys);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.functions` which creates an array of
|
|
* `object` function property names filtered from `props`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Array} props The property names to filter.
|
|
* @returns {Array} Returns the function names.
|
|
*/
|
|
function baseFunctions(object, props) {
|
|
return arrayFilter(props, function(key) {
|
|
return isFunction(object[key]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.get` without support for default values.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path of the property to get.
|
|
* @returns {*} Returns the resolved value.
|
|
*/
|
|
function baseGet(object, path) {
|
|
path = castPath(path, object);
|
|
|
|
var index = 0,
|
|
length = path.length;
|
|
|
|
while (object != null && index < length) {
|
|
object = object[toKey(path[index++])];
|
|
}
|
|
return (index && index == length) ? object : undefined;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `getAllKeys` and `getAllKeysIn` which uses
|
|
* `keysFunc` and `symbolsFunc` to get the enumerable property names and
|
|
* symbols of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Function} keysFunc The function to get the keys of `object`.
|
|
* @param {Function} symbolsFunc The function to get the symbols of `object`.
|
|
* @returns {Array} Returns the array of property names and symbols.
|
|
*/
|
|
function baseGetAllKeys(object, keysFunc, symbolsFunc) {
|
|
var result = keysFunc(object);
|
|
return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `getTag` without fallbacks for buggy environments.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to query.
|
|
* @returns {string} Returns the `toStringTag`.
|
|
*/
|
|
function baseGetTag(value) {
|
|
if (value == null) {
|
|
return value === undefined ? undefinedTag : nullTag;
|
|
}
|
|
return (symToStringTag && symToStringTag in Object(value))
|
|
? getRawTag(value)
|
|
: objectToString(value);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.gt` which doesn't coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if `value` is greater than `other`,
|
|
* else `false`.
|
|
*/
|
|
function baseGt(value, other) {
|
|
return value > other;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.has` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {Object} [object] The object to query.
|
|
* @param {Array|string} key The key to check.
|
|
* @returns {boolean} Returns `true` if `key` exists, else `false`.
|
|
*/
|
|
function baseHas(object, key) {
|
|
return object != null && hasOwnProperty.call(object, key);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.hasIn` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {Object} [object] The object to query.
|
|
* @param {Array|string} key The key to check.
|
|
* @returns {boolean} Returns `true` if `key` exists, else `false`.
|
|
*/
|
|
function baseHasIn(object, key) {
|
|
return object != null && key in Object(object);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.inRange` which doesn't coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {number} number The number to check.
|
|
* @param {number} start The start of the range.
|
|
* @param {number} end The end of the range.
|
|
* @returns {boolean} Returns `true` if `number` is in the range, else `false`.
|
|
*/
|
|
function baseInRange(number, start, end) {
|
|
return number >= nativeMin(start, end) && number < nativeMax(start, end);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of methods like `_.intersection`, without support
|
|
* for iteratee shorthands, that accepts an array of arrays to inspect.
|
|
*
|
|
* @private
|
|
* @param {Array} arrays The arrays to inspect.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of shared values.
|
|
*/
|
|
function baseIntersection(arrays, iteratee, comparator) {
|
|
var includes = comparator ? arrayIncludesWith : arrayIncludes,
|
|
length = arrays[0].length,
|
|
othLength = arrays.length,
|
|
othIndex = othLength,
|
|
caches = Array(othLength),
|
|
maxLength = Infinity,
|
|
result = [];
|
|
|
|
while (othIndex--) {
|
|
var array = arrays[othIndex];
|
|
if (othIndex && iteratee) {
|
|
array = arrayMap(array, baseUnary(iteratee));
|
|
}
|
|
maxLength = nativeMin(array.length, maxLength);
|
|
caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
|
|
? new SetCache(othIndex && array)
|
|
: undefined;
|
|
}
|
|
array = arrays[0];
|
|
|
|
var index = -1,
|
|
seen = caches[0];
|
|
|
|
outer:
|
|
while (++index < length && result.length < maxLength) {
|
|
var value = array[index],
|
|
computed = iteratee ? iteratee(value) : value;
|
|
|
|
value = (comparator || value !== 0) ? value : 0;
|
|
if (!(seen
|
|
? cacheHas(seen, computed)
|
|
: includes(result, computed, comparator)
|
|
)) {
|
|
othIndex = othLength;
|
|
while (--othIndex) {
|
|
var cache = caches[othIndex];
|
|
if (!(cache
|
|
? cacheHas(cache, computed)
|
|
: includes(arrays[othIndex], computed, comparator))
|
|
) {
|
|
continue outer;
|
|
}
|
|
}
|
|
if (seen) {
|
|
seen.push(computed);
|
|
}
|
|
result.push(value);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.invert` and `_.invertBy` which inverts
|
|
* `object` with values transformed by `iteratee` and set by `setter`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} setter The function to set `accumulator` values.
|
|
* @param {Function} iteratee The iteratee to transform values.
|
|
* @param {Object} accumulator The initial inverted object.
|
|
* @returns {Function} Returns `accumulator`.
|
|
*/
|
|
function baseInverter(object, setter, iteratee, accumulator) {
|
|
baseForOwn(object, function(value, key, object) {
|
|
setter(accumulator, iteratee(value), key, object);
|
|
});
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.invoke` without support for individual
|
|
* method arguments.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path of the method to invoke.
|
|
* @param {Array} args The arguments to invoke the method with.
|
|
* @returns {*} Returns the result of the invoked method.
|
|
*/
|
|
function baseInvoke(object, path, args) {
|
|
path = castPath(path, object);
|
|
object = parent(object, path);
|
|
var func = object == null ? object : object[toKey(last(path))];
|
|
return func == null ? undefined : apply(func, object, args);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isArguments`.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
|
|
*/
|
|
function baseIsArguments(value) {
|
|
return isObjectLike(value) && baseGetTag(value) == argsTag;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isArrayBuffer` without Node.js optimizations.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
|
|
*/
|
|
function baseIsArrayBuffer(value) {
|
|
return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isDate` without Node.js optimizations.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a date object, else `false`.
|
|
*/
|
|
function baseIsDate(value) {
|
|
return isObjectLike(value) && baseGetTag(value) == dateTag;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isEqual` which supports partial comparisons
|
|
* and tracks traversed objects.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @param {boolean} bitmask The bitmask flags.
|
|
* 1 - Unordered comparison
|
|
* 2 - Partial comparison
|
|
* @param {Function} [customizer] The function to customize comparisons.
|
|
* @param {Object} [stack] Tracks traversed `value` and `other` objects.
|
|
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
|
*/
|
|
function baseIsEqual(value, other, bitmask, customizer, stack) {
|
|
if (value === other) {
|
|
return true;
|
|
}
|
|
if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
|
|
return value !== value && other !== other;
|
|
}
|
|
return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseIsEqual` for arrays and objects which performs
|
|
* deep comparisons and tracks traversed objects enabling objects with circular
|
|
* references to be compared.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to compare.
|
|
* @param {Object} other The other object to compare.
|
|
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
|
|
* @param {Function} customizer The function to customize comparisons.
|
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
|
* @param {Object} [stack] Tracks traversed `object` and `other` objects.
|
|
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
|
*/
|
|
function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
|
|
var objIsArr = isArray(object),
|
|
othIsArr = isArray(other),
|
|
objTag = objIsArr ? arrayTag : getTag(object),
|
|
othTag = othIsArr ? arrayTag : getTag(other);
|
|
|
|
objTag = objTag == argsTag ? objectTag : objTag;
|
|
othTag = othTag == argsTag ? objectTag : othTag;
|
|
|
|
var objIsObj = objTag == objectTag,
|
|
othIsObj = othTag == objectTag,
|
|
isSameTag = objTag == othTag;
|
|
|
|
if (isSameTag && isBuffer(object)) {
|
|
if (!isBuffer(other)) {
|
|
return false;
|
|
}
|
|
objIsArr = true;
|
|
objIsObj = false;
|
|
}
|
|
if (isSameTag && !objIsObj) {
|
|
stack || (stack = new Stack);
|
|
return (objIsArr || isTypedArray(object))
|
|
? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
|
|
: equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
|
|
}
|
|
if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
|
|
var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
|
|
othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
|
|
|
|
if (objIsWrapped || othIsWrapped) {
|
|
var objUnwrapped = objIsWrapped ? object.value() : object,
|
|
othUnwrapped = othIsWrapped ? other.value() : other;
|
|
|
|
stack || (stack = new Stack);
|
|
return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
|
|
}
|
|
}
|
|
if (!isSameTag) {
|
|
return false;
|
|
}
|
|
stack || (stack = new Stack);
|
|
return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isMap` without Node.js optimizations.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a map, else `false`.
|
|
*/
|
|
function baseIsMap(value) {
|
|
return isObjectLike(value) && getTag(value) == mapTag;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isMatch` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Object} source The object of property values to match.
|
|
* @param {Array} matchData The property names, values, and compare flags to match.
|
|
* @param {Function} [customizer] The function to customize comparisons.
|
|
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
*/
|
|
function baseIsMatch(object, source, matchData, customizer) {
|
|
var index = matchData.length,
|
|
length = index,
|
|
noCustomizer = !customizer;
|
|
|
|
if (object == null) {
|
|
return !length;
|
|
}
|
|
object = Object(object);
|
|
while (index--) {
|
|
var data = matchData[index];
|
|
if ((noCustomizer && data[2])
|
|
? data[1] !== object[data[0]]
|
|
: !(data[0] in object)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
while (++index < length) {
|
|
data = matchData[index];
|
|
var key = data[0],
|
|
objValue = object[key],
|
|
srcValue = data[1];
|
|
|
|
if (noCustomizer && data[2]) {
|
|
if (objValue === undefined && !(key in object)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
var stack = new Stack;
|
|
if (customizer) {
|
|
var result = customizer(objValue, srcValue, key, object, source, stack);
|
|
}
|
|
if (!(result === undefined
|
|
? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack)
|
|
: result
|
|
)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isNative` without bad shim checks.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a native function,
|
|
* else `false`.
|
|
*/
|
|
function baseIsNative(value) {
|
|
if (!isObject(value) || isMasked(value)) {
|
|
return false;
|
|
}
|
|
var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
|
|
return pattern.test(toSource(value));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isRegExp` without Node.js optimizations.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
|
|
*/
|
|
function baseIsRegExp(value) {
|
|
return isObjectLike(value) && baseGetTag(value) == regexpTag;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isSet` without Node.js optimizations.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a set, else `false`.
|
|
*/
|
|
function baseIsSet(value) {
|
|
return isObjectLike(value) && getTag(value) == setTag;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.isTypedArray` without Node.js optimizations.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
|
|
*/
|
|
function baseIsTypedArray(value) {
|
|
return isObjectLike(value) &&
|
|
isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.iteratee`.
|
|
*
|
|
* @private
|
|
* @param {*} [value=_.identity] The value to convert to an iteratee.
|
|
* @returns {Function} Returns the iteratee.
|
|
*/
|
|
function baseIteratee(value) {
|
|
// Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
|
|
// See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
|
|
if (typeof value == 'function') {
|
|
return value;
|
|
}
|
|
if (value == null) {
|
|
return identity;
|
|
}
|
|
if (typeof value == 'object') {
|
|
return isArray(value)
|
|
? baseMatchesProperty(value[0], value[1])
|
|
: baseMatches(value);
|
|
}
|
|
return property(value);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
*/
|
|
function baseKeys(object) {
|
|
if (!isPrototype(object)) {
|
|
return nativeKeys(object);
|
|
}
|
|
var result = [];
|
|
for (var key in Object(object)) {
|
|
if (hasOwnProperty.call(object, key) && key != 'constructor') {
|
|
result.push(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
*/
|
|
function baseKeysIn(object) {
|
|
if (!isObject(object)) {
|
|
return nativeKeysIn(object);
|
|
}
|
|
var isProto = isPrototype(object),
|
|
result = [];
|
|
|
|
for (var key in object) {
|
|
if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
|
|
result.push(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.lt` which doesn't coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if `value` is less than `other`,
|
|
* else `false`.
|
|
*/
|
|
function baseLt(value, other) {
|
|
return value < other;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.map` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} iteratee The function invoked per iteration.
|
|
* @returns {Array} Returns the new mapped array.
|
|
*/
|
|
function baseMap(collection, iteratee) {
|
|
var index = -1,
|
|
result = isArrayLike(collection) ? Array(collection.length) : [];
|
|
|
|
baseEach(collection, function(value, key, collection) {
|
|
result[++index] = iteratee(value, key, collection);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.matches` which doesn't clone `source`.
|
|
*
|
|
* @private
|
|
* @param {Object} source The object of property values to match.
|
|
* @returns {Function} Returns the new spec function.
|
|
*/
|
|
function baseMatches(source) {
|
|
var matchData = getMatchData(source);
|
|
if (matchData.length == 1 && matchData[0][2]) {
|
|
return matchesStrictComparable(matchData[0][0], matchData[0][1]);
|
|
}
|
|
return function(object) {
|
|
return object === source || baseIsMatch(object, source, matchData);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
|
|
*
|
|
* @private
|
|
* @param {string} path The path of the property to get.
|
|
* @param {*} srcValue The value to match.
|
|
* @returns {Function} Returns the new spec function.
|
|
*/
|
|
function baseMatchesProperty(path, srcValue) {
|
|
if (isKey(path) && isStrictComparable(srcValue)) {
|
|
return matchesStrictComparable(toKey(path), srcValue);
|
|
}
|
|
return function(object) {
|
|
var objValue = get(object, path);
|
|
return (objValue === undefined && objValue === srcValue)
|
|
? hasIn(object, path)
|
|
: baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.merge` without support for multiple sources.
|
|
*
|
|
* @private
|
|
* @param {Object} object The destination object.
|
|
* @param {Object} source The source object.
|
|
* @param {number} srcIndex The index of `source`.
|
|
* @param {Function} [customizer] The function to customize merged values.
|
|
* @param {Object} [stack] Tracks traversed source values and their merged
|
|
* counterparts.
|
|
*/
|
|
function baseMerge(object, source, srcIndex, customizer, stack) {
|
|
if (object === source) {
|
|
return;
|
|
}
|
|
baseFor(source, function(srcValue, key) {
|
|
if (isObject(srcValue)) {
|
|
stack || (stack = new Stack);
|
|
baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
|
|
}
|
|
else {
|
|
var newValue = customizer
|
|
? customizer(object[key], srcValue, (key + ''), object, source, stack)
|
|
: undefined;
|
|
|
|
if (newValue === undefined) {
|
|
newValue = srcValue;
|
|
}
|
|
assignMergeValue(object, key, newValue);
|
|
}
|
|
}, keysIn);
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseMerge` for arrays and objects which performs
|
|
* deep merges and tracks traversed objects enabling objects with circular
|
|
* references to be merged.
|
|
*
|
|
* @private
|
|
* @param {Object} object The destination object.
|
|
* @param {Object} source The source object.
|
|
* @param {string} key The key of the value to merge.
|
|
* @param {number} srcIndex The index of `source`.
|
|
* @param {Function} mergeFunc The function to merge values.
|
|
* @param {Function} [customizer] The function to customize assigned values.
|
|
* @param {Object} [stack] Tracks traversed source values and their merged
|
|
* counterparts.
|
|
*/
|
|
function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
|
|
var objValue = object[key],
|
|
srcValue = source[key],
|
|
stacked = stack.get(srcValue);
|
|
|
|
if (stacked) {
|
|
assignMergeValue(object, key, stacked);
|
|
return;
|
|
}
|
|
var newValue = customizer
|
|
? customizer(objValue, srcValue, (key + ''), object, source, stack)
|
|
: undefined;
|
|
|
|
var isCommon = newValue === undefined;
|
|
|
|
if (isCommon) {
|
|
var isArr = isArray(srcValue),
|
|
isBuff = !isArr && isBuffer(srcValue),
|
|
isTyped = !isArr && !isBuff && isTypedArray(srcValue);
|
|
|
|
newValue = srcValue;
|
|
if (isArr || isBuff || isTyped) {
|
|
if (isArray(objValue)) {
|
|
newValue = objValue;
|
|
}
|
|
else if (isArrayLikeObject(objValue)) {
|
|
newValue = copyArray(objValue);
|
|
}
|
|
else if (isBuff) {
|
|
isCommon = false;
|
|
newValue = cloneBuffer(srcValue, true);
|
|
}
|
|
else if (isTyped) {
|
|
isCommon = false;
|
|
newValue = cloneTypedArray(srcValue, true);
|
|
}
|
|
else {
|
|
newValue = [];
|
|
}
|
|
}
|
|
else if (isPlainObject(srcValue) || isArguments(srcValue)) {
|
|
newValue = objValue;
|
|
if (isArguments(objValue)) {
|
|
newValue = toPlainObject(objValue);
|
|
}
|
|
else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
|
|
newValue = initCloneObject(srcValue);
|
|
}
|
|
}
|
|
else {
|
|
isCommon = false;
|
|
}
|
|
}
|
|
if (isCommon) {
|
|
// Recursively merge objects and arrays (susceptible to call stack limits).
|
|
stack.set(srcValue, newValue);
|
|
mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
|
|
stack['delete'](srcValue);
|
|
}
|
|
assignMergeValue(object, key, newValue);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.nth` which doesn't coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to query.
|
|
* @param {number} n The index of the element to return.
|
|
* @returns {*} Returns the nth element of `array`.
|
|
*/
|
|
function baseNth(array, n) {
|
|
var length = array.length;
|
|
if (!length) {
|
|
return;
|
|
}
|
|
n += n < 0 ? length : 0;
|
|
return isIndex(n, length) ? array[n] : undefined;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.orderBy` without param guards.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
|
|
* @param {string[]} orders The sort orders of `iteratees`.
|
|
* @returns {Array} Returns the new sorted array.
|
|
*/
|
|
function baseOrderBy(collection, iteratees, orders) {
|
|
var index = -1;
|
|
iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));
|
|
|
|
var result = baseMap(collection, function(value, key, collection) {
|
|
var criteria = arrayMap(iteratees, function(iteratee) {
|
|
return iteratee(value);
|
|
});
|
|
return { 'criteria': criteria, 'index': ++index, 'value': value };
|
|
});
|
|
|
|
return baseSortBy(result, function(object, other) {
|
|
return compareMultiple(object, other, orders);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.pick` without support for individual
|
|
* property identifiers.
|
|
*
|
|
* @private
|
|
* @param {Object} object The source object.
|
|
* @param {string[]} paths The property paths to pick.
|
|
* @returns {Object} Returns the new object.
|
|
*/
|
|
function basePick(object, paths) {
|
|
return basePickBy(object, paths, function(value, path) {
|
|
return hasIn(object, path);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.pickBy` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Object} object The source object.
|
|
* @param {string[]} paths The property paths to pick.
|
|
* @param {Function} predicate The function invoked per property.
|
|
* @returns {Object} Returns the new object.
|
|
*/
|
|
function basePickBy(object, paths, predicate) {
|
|
var index = -1,
|
|
length = paths.length,
|
|
result = {};
|
|
|
|
while (++index < length) {
|
|
var path = paths[index],
|
|
value = baseGet(object, path);
|
|
|
|
if (predicate(value, path)) {
|
|
baseSet(result, castPath(path, object), value);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseProperty` which supports deep paths.
|
|
*
|
|
* @private
|
|
* @param {Array|string} path The path of the property to get.
|
|
* @returns {Function} Returns the new accessor function.
|
|
*/
|
|
function basePropertyDeep(path) {
|
|
return function(object) {
|
|
return baseGet(object, path);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.pullAllBy` without support for iteratee
|
|
* shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to modify.
|
|
* @param {Array} values The values to remove.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function basePullAll(array, values, iteratee, comparator) {
|
|
var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
|
|
index = -1,
|
|
length = values.length,
|
|
seen = array;
|
|
|
|
if (array === values) {
|
|
values = copyArray(values);
|
|
}
|
|
if (iteratee) {
|
|
seen = arrayMap(array, baseUnary(iteratee));
|
|
}
|
|
while (++index < length) {
|
|
var fromIndex = 0,
|
|
value = values[index],
|
|
computed = iteratee ? iteratee(value) : value;
|
|
|
|
while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
|
|
if (seen !== array) {
|
|
splice.call(seen, fromIndex, 1);
|
|
}
|
|
splice.call(array, fromIndex, 1);
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.pullAt` without support for individual
|
|
* indexes or capturing the removed elements.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to modify.
|
|
* @param {number[]} indexes The indexes of elements to remove.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function basePullAt(array, indexes) {
|
|
var length = array ? indexes.length : 0,
|
|
lastIndex = length - 1;
|
|
|
|
while (length--) {
|
|
var index = indexes[length];
|
|
if (length == lastIndex || index !== previous) {
|
|
var previous = index;
|
|
if (isIndex(index)) {
|
|
splice.call(array, index, 1);
|
|
} else {
|
|
baseUnset(array, index);
|
|
}
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.random` without support for returning
|
|
* floating-point numbers.
|
|
*
|
|
* @private
|
|
* @param {number} lower The lower bound.
|
|
* @param {number} upper The upper bound.
|
|
* @returns {number} Returns the random number.
|
|
*/
|
|
function baseRandom(lower, upper) {
|
|
return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.range` and `_.rangeRight` which doesn't
|
|
* coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {number} start The start of the range.
|
|
* @param {number} end The end of the range.
|
|
* @param {number} step The value to increment or decrement by.
|
|
* @param {boolean} [fromRight] Specify iterating from right to left.
|
|
* @returns {Array} Returns the range of numbers.
|
|
*/
|
|
function baseRange(start, end, step, fromRight) {
|
|
var index = -1,
|
|
length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
|
|
result = Array(length);
|
|
|
|
while (length--) {
|
|
result[fromRight ? length : ++index] = start;
|
|
start += step;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.repeat` which doesn't coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to repeat.
|
|
* @param {number} n The number of times to repeat the string.
|
|
* @returns {string} Returns the repeated string.
|
|
*/
|
|
function baseRepeat(string, n) {
|
|
var result = '';
|
|
if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
|
|
return result;
|
|
}
|
|
// Leverage the exponentiation by squaring algorithm for a faster repeat.
|
|
// See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
|
|
do {
|
|
if (n % 2) {
|
|
result += string;
|
|
}
|
|
n = nativeFloor(n / 2);
|
|
if (n) {
|
|
string += string;
|
|
}
|
|
} while (n);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.rest` which doesn't validate or coerce arguments.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to apply a rest parameter to.
|
|
* @param {number} [start=func.length-1] The start position of the rest parameter.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function baseRest(func, start) {
|
|
return setToString(overRest(func, start, identity), func + '');
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sample`.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to sample.
|
|
* @returns {*} Returns the random element.
|
|
*/
|
|
function baseSample(collection) {
|
|
return arraySample(values(collection));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sampleSize` without param guards.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to sample.
|
|
* @param {number} n The number of elements to sample.
|
|
* @returns {Array} Returns the random elements.
|
|
*/
|
|
function baseSampleSize(collection, n) {
|
|
var array = values(collection);
|
|
return shuffleSelf(array, baseClamp(n, 0, array.length));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.set`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to set.
|
|
* @param {*} value The value to set.
|
|
* @param {Function} [customizer] The function to customize path creation.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseSet(object, path, value, customizer) {
|
|
if (!isObject(object)) {
|
|
return object;
|
|
}
|
|
path = castPath(path, object);
|
|
|
|
var index = -1,
|
|
length = path.length,
|
|
lastIndex = length - 1,
|
|
nested = object;
|
|
|
|
while (nested != null && ++index < length) {
|
|
var key = toKey(path[index]),
|
|
newValue = value;
|
|
|
|
if (index != lastIndex) {
|
|
var objValue = nested[key];
|
|
newValue = customizer ? customizer(objValue, key, nested) : undefined;
|
|
if (newValue === undefined) {
|
|
newValue = isObject(objValue)
|
|
? objValue
|
|
: (isIndex(path[index + 1]) ? [] : {});
|
|
}
|
|
}
|
|
assignValue(nested, key, newValue);
|
|
nested = nested[key];
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `setData` without support for hot loop shorting.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to associate metadata with.
|
|
* @param {*} data The metadata.
|
|
* @returns {Function} Returns `func`.
|
|
*/
|
|
var baseSetData = !metaMap ? identity : function(func, data) {
|
|
metaMap.set(func, data);
|
|
return func;
|
|
};
|
|
|
|
/**
|
|
* The base implementation of `setToString` without support for hot loop shorting.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to modify.
|
|
* @param {Function} string The `toString` result.
|
|
* @returns {Function} Returns `func`.
|
|
*/
|
|
var baseSetToString = !defineProperty ? identity : function(func, string) {
|
|
return defineProperty(func, 'toString', {
|
|
'configurable': true,
|
|
'enumerable': false,
|
|
'value': constant(string),
|
|
'writable': true
|
|
});
|
|
};
|
|
|
|
/**
|
|
* The base implementation of `_.shuffle`.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to shuffle.
|
|
* @returns {Array} Returns the new shuffled array.
|
|
*/
|
|
function baseShuffle(collection) {
|
|
return shuffleSelf(values(collection));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.slice` without an iteratee call guard.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to slice.
|
|
* @param {number} [start=0] The start position.
|
|
* @param {number} [end=array.length] The end position.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
*/
|
|
function baseSlice(array, start, end) {
|
|
var index = -1,
|
|
length = array.length;
|
|
|
|
if (start < 0) {
|
|
start = -start > length ? 0 : (length + start);
|
|
}
|
|
end = end > length ? length : end;
|
|
if (end < 0) {
|
|
end += length;
|
|
}
|
|
length = start > end ? 0 : ((end - start) >>> 0);
|
|
start >>>= 0;
|
|
|
|
var result = Array(length);
|
|
while (++index < length) {
|
|
result[index] = array[index + start];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.some` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @returns {boolean} Returns `true` if any element passes the predicate check,
|
|
* else `false`.
|
|
*/
|
|
function baseSome(collection, predicate) {
|
|
var result;
|
|
|
|
baseEach(collection, function(value, index, collection) {
|
|
result = predicate(value, index, collection);
|
|
return !result;
|
|
});
|
|
return !!result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
|
|
* performs a binary search of `array` to determine the index at which `value`
|
|
* should be inserted into `array` in order to maintain its sort order.
|
|
*
|
|
* @private
|
|
* @param {Array} array The sorted array to inspect.
|
|
* @param {*} value The value to evaluate.
|
|
* @param {boolean} [retHighest] Specify returning the highest qualified index.
|
|
* @returns {number} Returns the index at which `value` should be inserted
|
|
* into `array`.
|
|
*/
|
|
function baseSortedIndex(array, value, retHighest) {
|
|
var low = 0,
|
|
high = array == null ? low : array.length;
|
|
|
|
if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
|
|
while (low < high) {
|
|
var mid = (low + high) >>> 1,
|
|
computed = array[mid];
|
|
|
|
if (computed !== null && !isSymbol(computed) &&
|
|
(retHighest ? (computed <= value) : (computed < value))) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid;
|
|
}
|
|
}
|
|
return high;
|
|
}
|
|
return baseSortedIndexBy(array, value, identity, retHighest);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
|
|
* which invokes `iteratee` for `value` and each element of `array` to compute
|
|
* their sort ranking. The iteratee is invoked with one argument; (value).
|
|
*
|
|
* @private
|
|
* @param {Array} array The sorted array to inspect.
|
|
* @param {*} value The value to evaluate.
|
|
* @param {Function} iteratee The iteratee invoked per element.
|
|
* @param {boolean} [retHighest] Specify returning the highest qualified index.
|
|
* @returns {number} Returns the index at which `value` should be inserted
|
|
* into `array`.
|
|
*/
|
|
function baseSortedIndexBy(array, value, iteratee, retHighest) {
|
|
value = iteratee(value);
|
|
|
|
var low = 0,
|
|
high = array == null ? 0 : array.length,
|
|
valIsNaN = value !== value,
|
|
valIsNull = value === null,
|
|
valIsSymbol = isSymbol(value),
|
|
valIsUndefined = value === undefined;
|
|
|
|
while (low < high) {
|
|
var mid = nativeFloor((low + high) / 2),
|
|
computed = iteratee(array[mid]),
|
|
othIsDefined = computed !== undefined,
|
|
othIsNull = computed === null,
|
|
othIsReflexive = computed === computed,
|
|
othIsSymbol = isSymbol(computed);
|
|
|
|
if (valIsNaN) {
|
|
var setLow = retHighest || othIsReflexive;
|
|
} else if (valIsUndefined) {
|
|
setLow = othIsReflexive && (retHighest || othIsDefined);
|
|
} else if (valIsNull) {
|
|
setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
|
|
} else if (valIsSymbol) {
|
|
setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
|
|
} else if (othIsNull || othIsSymbol) {
|
|
setLow = false;
|
|
} else {
|
|
setLow = retHighest ? (computed <= value) : (computed < value);
|
|
}
|
|
if (setLow) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid;
|
|
}
|
|
}
|
|
return nativeMin(high, MAX_ARRAY_INDEX);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
|
|
* support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
*/
|
|
function baseSortedUniq(array, iteratee) {
|
|
var index = -1,
|
|
length = array.length,
|
|
resIndex = 0,
|
|
result = [];
|
|
|
|
while (++index < length) {
|
|
var value = array[index],
|
|
computed = iteratee ? iteratee(value) : value;
|
|
|
|
if (!index || !eq(computed, seen)) {
|
|
var seen = computed;
|
|
result[resIndex++] = value === 0 ? 0 : value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.toNumber` which doesn't ensure correct
|
|
* conversions of binary, hexadecimal, or octal string values.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to process.
|
|
* @returns {number} Returns the number.
|
|
*/
|
|
function baseToNumber(value) {
|
|
if (typeof value == 'number') {
|
|
return value;
|
|
}
|
|
if (isSymbol(value)) {
|
|
return NAN;
|
|
}
|
|
return +value;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.toString` which doesn't convert nullish
|
|
* values to empty strings.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to process.
|
|
* @returns {string} Returns the string.
|
|
*/
|
|
function baseToString(value) {
|
|
// Exit early for strings to avoid a performance hit in some environments.
|
|
if (typeof value == 'string') {
|
|
return value;
|
|
}
|
|
if (isArray(value)) {
|
|
// Recursively convert values (susceptible to call stack limits).
|
|
return arrayMap(value, baseToString) + '';
|
|
}
|
|
if (isSymbol(value)) {
|
|
return symbolToString ? symbolToString.call(value) : '';
|
|
}
|
|
var result = (value + '');
|
|
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.uniqBy` without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
*/
|
|
function baseUniq(array, iteratee, comparator) {
|
|
var index = -1,
|
|
includes = arrayIncludes,
|
|
length = array.length,
|
|
isCommon = true,
|
|
result = [],
|
|
seen = result;
|
|
|
|
if (comparator) {
|
|
isCommon = false;
|
|
includes = arrayIncludesWith;
|
|
}
|
|
else if (length >= LARGE_ARRAY_SIZE) {
|
|
var set = iteratee ? null : createSet(array);
|
|
if (set) {
|
|
return setToArray(set);
|
|
}
|
|
isCommon = false;
|
|
includes = cacheHas;
|
|
seen = new SetCache;
|
|
}
|
|
else {
|
|
seen = iteratee ? [] : result;
|
|
}
|
|
outer:
|
|
while (++index < length) {
|
|
var value = array[index],
|
|
computed = iteratee ? iteratee(value) : value;
|
|
|
|
value = (comparator || value !== 0) ? value : 0;
|
|
if (isCommon && computed === computed) {
|
|
var seenIndex = seen.length;
|
|
while (seenIndex--) {
|
|
if (seen[seenIndex] === computed) {
|
|
continue outer;
|
|
}
|
|
}
|
|
if (iteratee) {
|
|
seen.push(computed);
|
|
}
|
|
result.push(value);
|
|
}
|
|
else if (!includes(seen, computed, comparator)) {
|
|
if (seen !== result) {
|
|
seen.push(computed);
|
|
}
|
|
result.push(value);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.unset`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The property path to unset.
|
|
* @returns {boolean} Returns `true` if the property is deleted, else `false`.
|
|
*/
|
|
function baseUnset(object, path) {
|
|
path = castPath(path, object);
|
|
object = parent(object, path);
|
|
return object == null || delete object[toKey(last(path))];
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `_.update`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to update.
|
|
* @param {Function} updater The function to produce the updated value.
|
|
* @param {Function} [customizer] The function to customize path creation.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseUpdate(object, path, updater, customizer) {
|
|
return baseSet(object, path, updater(baseGet(object, path)), customizer);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of methods like `_.dropWhile` and `_.takeWhile`
|
|
* without support for iteratee shorthands.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to query.
|
|
* @param {Function} predicate The function invoked per iteration.
|
|
* @param {boolean} [isDrop] Specify dropping elements instead of taking them.
|
|
* @param {boolean} [fromRight] Specify iterating from right to left.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
*/
|
|
function baseWhile(array, predicate, isDrop, fromRight) {
|
|
var length = array.length,
|
|
index = fromRight ? length : -1;
|
|
|
|
while ((fromRight ? index-- : ++index < length) &&
|
|
predicate(array[index], index, array)) {}
|
|
|
|
return isDrop
|
|
? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
|
|
: baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
|
|
}
|
|
|
|
/**
|
|
* The base implementation of `wrapperValue` which returns the result of
|
|
* performing a sequence of actions on the unwrapped `value`, where each
|
|
* successive action is supplied the return value of the previous.
|
|
*
|
|
* @private
|
|
* @param {*} value The unwrapped value.
|
|
* @param {Array} actions Actions to perform to resolve the unwrapped value.
|
|
* @returns {*} Returns the resolved value.
|
|
*/
|
|
function baseWrapperValue(value, actions) {
|
|
var result = value;
|
|
if (result instanceof LazyWrapper) {
|
|
result = result.value();
|
|
}
|
|
return arrayReduce(actions, function(result, action) {
|
|
return action.func.apply(action.thisArg, arrayPush([result], action.args));
|
|
}, result);
|
|
}
|
|
|
|
/**
|
|
* The base implementation of methods like `_.xor`, without support for
|
|
* iteratee shorthands, that accepts an array of arrays to inspect.
|
|
*
|
|
* @private
|
|
* @param {Array} arrays The arrays to inspect.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of values.
|
|
*/
|
|
function baseXor(arrays, iteratee, comparator) {
|
|
var length = arrays.length;
|
|
if (length < 2) {
|
|
return length ? baseUniq(arrays[0]) : [];
|
|
}
|
|
var index = -1,
|
|
result = Array(length);
|
|
|
|
while (++index < length) {
|
|
var array = arrays[index],
|
|
othIndex = -1;
|
|
|
|
while (++othIndex < length) {
|
|
if (othIndex != index) {
|
|
result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
|
|
}
|
|
}
|
|
}
|
|
return baseUniq(baseFlatten(result, 1), iteratee, comparator);
|
|
}
|
|
|
|
/**
|
|
* This base implementation of `_.zipObject` which assigns values using `assignFunc`.
|
|
*
|
|
* @private
|
|
* @param {Array} props The property identifiers.
|
|
* @param {Array} values The property values.
|
|
* @param {Function} assignFunc The function to assign values.
|
|
* @returns {Object} Returns the new object.
|
|
*/
|
|
function baseZipObject(props, values, assignFunc) {
|
|
var index = -1,
|
|
length = props.length,
|
|
valsLength = values.length,
|
|
result = {};
|
|
|
|
while (++index < length) {
|
|
var value = index < valsLength ? values[index] : undefined;
|
|
assignFunc(result, props[index], value);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Casts `value` to an empty array if it's not an array like object.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to inspect.
|
|
* @returns {Array|Object} Returns the cast array-like object.
|
|
*/
|
|
function castArrayLikeObject(value) {
|
|
return isArrayLikeObject(value) ? value : [];
|
|
}
|
|
|
|
/**
|
|
* Casts `value` to `identity` if it's not a function.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to inspect.
|
|
* @returns {Function} Returns cast function.
|
|
*/
|
|
function castFunction(value) {
|
|
return typeof value == 'function' ? value : identity;
|
|
}
|
|
|
|
/**
|
|
* Casts `value` to a path array if it's not one.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to inspect.
|
|
* @param {Object} [object] The object to query keys on.
|
|
* @returns {Array} Returns the cast property path array.
|
|
*/
|
|
function castPath(value, object) {
|
|
if (isArray(value)) {
|
|
return value;
|
|
}
|
|
return isKey(value, object) ? [value] : stringToPath(toString(value));
|
|
}
|
|
|
|
/**
|
|
* A `baseRest` alias which can be replaced with `identity` by module
|
|
* replacement plugins.
|
|
*
|
|
* @private
|
|
* @type {Function}
|
|
* @param {Function} func The function to apply a rest parameter to.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
var castRest = baseRest;
|
|
|
|
/**
|
|
* Casts `array` to a slice if it's needed.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to inspect.
|
|
* @param {number} start The start position.
|
|
* @param {number} [end=array.length] The end position.
|
|
* @returns {Array} Returns the cast slice.
|
|
*/
|
|
function castSlice(array, start, end) {
|
|
var length = array.length;
|
|
end = end === undefined ? length : end;
|
|
return (!start && end >= length) ? array : baseSlice(array, start, end);
|
|
}
|
|
|
|
/**
|
|
* A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
|
|
*
|
|
* @private
|
|
* @param {number|Object} id The timer id or timeout object of the timer to clear.
|
|
*/
|
|
var clearTimeout = ctxClearTimeout || function(id) {
|
|
return root.clearTimeout(id);
|
|
};
|
|
|
|
/**
|
|
* Creates a clone of `buffer`.
|
|
*
|
|
* @private
|
|
* @param {Buffer} buffer The buffer to clone.
|
|
* @param {boolean} [isDeep] Specify a deep clone.
|
|
* @returns {Buffer} Returns the cloned buffer.
|
|
*/
|
|
function cloneBuffer(buffer, isDeep) {
|
|
if (isDeep) {
|
|
return buffer.slice();
|
|
}
|
|
var length = buffer.length,
|
|
result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
|
|
|
|
buffer.copy(result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `arrayBuffer`.
|
|
*
|
|
* @private
|
|
* @param {ArrayBuffer} arrayBuffer The array buffer to clone.
|
|
* @returns {ArrayBuffer} Returns the cloned array buffer.
|
|
*/
|
|
function cloneArrayBuffer(arrayBuffer) {
|
|
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
|
|
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `dataView`.
|
|
*
|
|
* @private
|
|
* @param {Object} dataView The data view to clone.
|
|
* @param {boolean} [isDeep] Specify a deep clone.
|
|
* @returns {Object} Returns the cloned data view.
|
|
*/
|
|
function cloneDataView(dataView, isDeep) {
|
|
var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
|
|
return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `map`.
|
|
*
|
|
* @private
|
|
* @param {Object} map The map to clone.
|
|
* @param {Function} cloneFunc The function to clone values.
|
|
* @param {boolean} [isDeep] Specify a deep clone.
|
|
* @returns {Object} Returns the cloned map.
|
|
*/
|
|
function cloneMap(map, isDeep, cloneFunc) {
|
|
var array = isDeep ? cloneFunc(mapToArray(map), CLONE_DEEP_FLAG) : mapToArray(map);
|
|
return arrayReduce(array, addMapEntry, new map.constructor);
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `regexp`.
|
|
*
|
|
* @private
|
|
* @param {Object} regexp The regexp to clone.
|
|
* @returns {Object} Returns the cloned regexp.
|
|
*/
|
|
function cloneRegExp(regexp) {
|
|
var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
|
|
result.lastIndex = regexp.lastIndex;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `set`.
|
|
*
|
|
* @private
|
|
* @param {Object} set The set to clone.
|
|
* @param {Function} cloneFunc The function to clone values.
|
|
* @param {boolean} [isDeep] Specify a deep clone.
|
|
* @returns {Object} Returns the cloned set.
|
|
*/
|
|
function cloneSet(set, isDeep, cloneFunc) {
|
|
var array = isDeep ? cloneFunc(setToArray(set), CLONE_DEEP_FLAG) : setToArray(set);
|
|
return arrayReduce(array, addSetEntry, new set.constructor);
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of the `symbol` object.
|
|
*
|
|
* @private
|
|
* @param {Object} symbol The symbol object to clone.
|
|
* @returns {Object} Returns the cloned symbol object.
|
|
*/
|
|
function cloneSymbol(symbol) {
|
|
return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `typedArray`.
|
|
*
|
|
* @private
|
|
* @param {Object} typedArray The typed array to clone.
|
|
* @param {boolean} [isDeep] Specify a deep clone.
|
|
* @returns {Object} Returns the cloned typed array.
|
|
*/
|
|
function cloneTypedArray(typedArray, isDeep) {
|
|
var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
|
|
return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
|
|
}
|
|
|
|
/**
|
|
* Compares values to sort them in ascending order.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {number} Returns the sort order indicator for `value`.
|
|
*/
|
|
function compareAscending(value, other) {
|
|
if (value !== other) {
|
|
var valIsDefined = value !== undefined,
|
|
valIsNull = value === null,
|
|
valIsReflexive = value === value,
|
|
valIsSymbol = isSymbol(value);
|
|
|
|
var othIsDefined = other !== undefined,
|
|
othIsNull = other === null,
|
|
othIsReflexive = other === other,
|
|
othIsSymbol = isSymbol(other);
|
|
|
|
if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
|
|
(valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
|
|
(valIsNull && othIsDefined && othIsReflexive) ||
|
|
(!valIsDefined && othIsReflexive) ||
|
|
!valIsReflexive) {
|
|
return 1;
|
|
}
|
|
if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
|
|
(othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
|
|
(othIsNull && valIsDefined && valIsReflexive) ||
|
|
(!othIsDefined && valIsReflexive) ||
|
|
!othIsReflexive) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Used by `_.orderBy` to compare multiple properties of a value to another
|
|
* and stable sort them.
|
|
*
|
|
* If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
|
|
* specify an order of "desc" for descending or "asc" for ascending sort order
|
|
* of corresponding values.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to compare.
|
|
* @param {Object} other The other object to compare.
|
|
* @param {boolean[]|string[]} 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 = compareAscending(objCriteria[index], othCriteria[index]);
|
|
if (result) {
|
|
if (index >= ordersLength) {
|
|
return result;
|
|
}
|
|
var order = orders[index];
|
|
return result * (order == 'desc' ? -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://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
|
|
return object.index - other.index;
|
|
}
|
|
|
|
/**
|
|
* Creates an array that is the composition of partially applied arguments,
|
|
* placeholders, and provided arguments into a single array of arguments.
|
|
*
|
|
* @private
|
|
* @param {Array} args The provided arguments.
|
|
* @param {Array} partials The arguments to prepend to those provided.
|
|
* @param {Array} holders The `partials` placeholder indexes.
|
|
* @params {boolean} [isCurried] Specify composing for a curried function.
|
|
* @returns {Array} Returns the new array of composed arguments.
|
|
*/
|
|
function composeArgs(args, partials, holders, isCurried) {
|
|
var argsIndex = -1,
|
|
argsLength = args.length,
|
|
holdersLength = holders.length,
|
|
leftIndex = -1,
|
|
leftLength = partials.length,
|
|
rangeLength = nativeMax(argsLength - holdersLength, 0),
|
|
result = Array(leftLength + rangeLength),
|
|
isUncurried = !isCurried;
|
|
|
|
while (++leftIndex < leftLength) {
|
|
result[leftIndex] = partials[leftIndex];
|
|
}
|
|
while (++argsIndex < holdersLength) {
|
|
if (isUncurried || argsIndex < argsLength) {
|
|
result[holders[argsIndex]] = args[argsIndex];
|
|
}
|
|
}
|
|
while (rangeLength--) {
|
|
result[leftIndex++] = args[argsIndex++];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This function is like `composeArgs` except that the arguments composition
|
|
* is tailored for `_.partialRight`.
|
|
*
|
|
* @private
|
|
* @param {Array} args The provided arguments.
|
|
* @param {Array} partials The arguments to append to those provided.
|
|
* @param {Array} holders The `partials` placeholder indexes.
|
|
* @params {boolean} [isCurried] Specify composing for a curried function.
|
|
* @returns {Array} Returns the new array of composed arguments.
|
|
*/
|
|
function composeArgsRight(args, partials, holders, isCurried) {
|
|
var argsIndex = -1,
|
|
argsLength = args.length,
|
|
holdersIndex = -1,
|
|
holdersLength = holders.length,
|
|
rightIndex = -1,
|
|
rightLength = partials.length,
|
|
rangeLength = nativeMax(argsLength - holdersLength, 0),
|
|
result = Array(rangeLength + rightLength),
|
|
isUncurried = !isCurried;
|
|
|
|
while (++argsIndex < rangeLength) {
|
|
result[argsIndex] = args[argsIndex];
|
|
}
|
|
var offset = argsIndex;
|
|
while (++rightIndex < rightLength) {
|
|
result[offset + rightIndex] = partials[rightIndex];
|
|
}
|
|
while (++holdersIndex < holdersLength) {
|
|
if (isUncurried || argsIndex < argsLength) {
|
|
result[offset + holders[holdersIndex]] = args[argsIndex++];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Copies the values of `source` to `array`.
|
|
*
|
|
* @private
|
|
* @param {Array} source The array to copy values from.
|
|
* @param {Array} [array=[]] The array to copy values to.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function copyArray(source, array) {
|
|
var index = -1,
|
|
length = source.length;
|
|
|
|
array || (array = Array(length));
|
|
while (++index < length) {
|
|
array[index] = source[index];
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Copies properties of `source` to `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} source The object to copy properties from.
|
|
* @param {Array} props The property identifiers to copy.
|
|
* @param {Object} [object={}] The object to copy properties to.
|
|
* @param {Function} [customizer] The function to customize copied values.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function copyObject(source, props, object, customizer) {
|
|
var isNew = !object;
|
|
object || (object = {});
|
|
|
|
var index = -1,
|
|
length = props.length;
|
|
|
|
while (++index < length) {
|
|
var key = props[index];
|
|
|
|
var newValue = customizer
|
|
? customizer(object[key], source[key], key, object, source)
|
|
: undefined;
|
|
|
|
if (newValue === undefined) {
|
|
newValue = source[key];
|
|
}
|
|
if (isNew) {
|
|
baseAssignValue(object, key, newValue);
|
|
} else {
|
|
assignValue(object, key, newValue);
|
|
}
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* Copies own symbols of `source` to `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} source The object to copy symbols from.
|
|
* @param {Object} [object={}] The object to copy symbols to.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function copySymbols(source, object) {
|
|
return copyObject(source, getSymbols(source), object);
|
|
}
|
|
|
|
/**
|
|
* Copies own and inherited symbols of `source` to `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} source The object to copy symbols from.
|
|
* @param {Object} [object={}] The object to copy symbols to.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function copySymbolsIn(source, object) {
|
|
return copyObject(source, getSymbolsIn(source), object);
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.groupBy`.
|
|
*
|
|
* @private
|
|
* @param {Function} setter The function to set accumulator values.
|
|
* @param {Function} [initializer] The accumulator object initializer.
|
|
* @returns {Function} Returns the new aggregator function.
|
|
*/
|
|
function createAggregator(setter, initializer) {
|
|
return function(collection, iteratee) {
|
|
var func = isArray(collection) ? arrayAggregator : baseAggregator,
|
|
accumulator = initializer ? initializer() : {};
|
|
|
|
return func(collection, setter, getIteratee(iteratee, 2), accumulator);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.assign`.
|
|
*
|
|
* @private
|
|
* @param {Function} assigner The function to assign values.
|
|
* @returns {Function} Returns the new assigner function.
|
|
*/
|
|
function createAssigner(assigner) {
|
|
return baseRest(function(object, sources) {
|
|
var index = -1,
|
|
length = sources.length,
|
|
customizer = length > 1 ? sources[length - 1] : undefined,
|
|
guard = length > 2 ? sources[2] : undefined;
|
|
|
|
customizer = (assigner.length > 3 && typeof customizer == 'function')
|
|
? (length--, customizer)
|
|
: undefined;
|
|
|
|
if (guard && isIterateeCall(sources[0], sources[1], guard)) {
|
|
customizer = length < 3 ? undefined : customizer;
|
|
length = 1;
|
|
}
|
|
object = Object(object);
|
|
while (++index < length) {
|
|
var source = sources[index];
|
|
if (source) {
|
|
assigner(object, source, index, customizer);
|
|
}
|
|
}
|
|
return object;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a `baseEach` or `baseEachRight` function.
|
|
*
|
|
* @private
|
|
* @param {Function} eachFunc The function to iterate over a collection.
|
|
* @param {boolean} [fromRight] Specify iterating from right to left.
|
|
* @returns {Function} Returns the new base function.
|
|
*/
|
|
function createBaseEach(eachFunc, fromRight) {
|
|
return function(collection, iteratee) {
|
|
if (collection == null) {
|
|
return collection;
|
|
}
|
|
if (!isArrayLike(collection)) {
|
|
return eachFunc(collection, iteratee);
|
|
}
|
|
var length = collection.length,
|
|
index = fromRight ? length : -1,
|
|
iterable = Object(collection);
|
|
|
|
while ((fromRight ? index-- : ++index < length)) {
|
|
if (iteratee(iterable[index], index, iterable) === false) {
|
|
break;
|
|
}
|
|
}
|
|
return collection;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a base function for methods like `_.forIn` and `_.forOwn`.
|
|
*
|
|
* @private
|
|
* @param {boolean} [fromRight] Specify iterating from right to left.
|
|
* @returns {Function} Returns the new base function.
|
|
*/
|
|
function createBaseFor(fromRight) {
|
|
return function(object, iteratee, keysFunc) {
|
|
var index = -1,
|
|
iterable = Object(object),
|
|
props = keysFunc(object),
|
|
length = props.length;
|
|
|
|
while (length--) {
|
|
var key = props[fromRight ? length : ++index];
|
|
if (iteratee(iterable[key], key, iterable) === false) {
|
|
break;
|
|
}
|
|
}
|
|
return object;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` to invoke it with the optional `this`
|
|
* binding of `thisArg`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to wrap.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createBind(func, bitmask, thisArg) {
|
|
var isBind = bitmask & WRAP_BIND_FLAG,
|
|
Ctor = createCtor(func);
|
|
|
|
function wrapper() {
|
|
var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
|
|
return fn.apply(isBind ? thisArg : this, arguments);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.lowerFirst`.
|
|
*
|
|
* @private
|
|
* @param {string} methodName The name of the `String` case method to use.
|
|
* @returns {Function} Returns the new case function.
|
|
*/
|
|
function createCaseFirst(methodName) {
|
|
return function(string) {
|
|
string = toString(string);
|
|
|
|
var strSymbols = hasUnicode(string)
|
|
? stringToArray(string)
|
|
: undefined;
|
|
|
|
var chr = strSymbols
|
|
? strSymbols[0]
|
|
: string.charAt(0);
|
|
|
|
var trailing = strSymbols
|
|
? castSlice(strSymbols, 1).join('')
|
|
: string.slice(1);
|
|
|
|
return chr[methodName]() + trailing;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.camelCase`.
|
|
*
|
|
* @private
|
|
* @param {Function} callback The function to combine each word.
|
|
* @returns {Function} Returns the new compounder function.
|
|
*/
|
|
function createCompounder(callback) {
|
|
return function(string) {
|
|
return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that produces an instance of `Ctor` regardless of
|
|
* whether it was invoked as part of a `new` expression or by `call` or `apply`.
|
|
*
|
|
* @private
|
|
* @param {Function} Ctor The constructor to wrap.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createCtor(Ctor) {
|
|
return function() {
|
|
// Use a `switch` statement to work with class constructors. See
|
|
// http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
|
|
// for more details.
|
|
var args = arguments;
|
|
switch (args.length) {
|
|
case 0: return new Ctor;
|
|
case 1: return new Ctor(args[0]);
|
|
case 2: return new Ctor(args[0], args[1]);
|
|
case 3: return new Ctor(args[0], args[1], args[2]);
|
|
case 4: return new Ctor(args[0], args[1], args[2], args[3]);
|
|
case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
|
|
case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
|
|
case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
|
}
|
|
var thisBinding = baseCreate(Ctor.prototype),
|
|
result = Ctor.apply(thisBinding, args);
|
|
|
|
// Mimic the constructor's `return` behavior.
|
|
// See https://es5.github.io/#x13.2.2 for more details.
|
|
return isObject(result) ? result : thisBinding;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` to enable currying.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to wrap.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @param {number} arity The arity of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createCurry(func, bitmask, arity) {
|
|
var Ctor = createCtor(func);
|
|
|
|
function wrapper() {
|
|
var length = arguments.length,
|
|
args = Array(length),
|
|
index = length,
|
|
placeholder = getHolder(wrapper);
|
|
|
|
while (index--) {
|
|
args[index] = arguments[index];
|
|
}
|
|
var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
|
|
? []
|
|
: replaceHolders(args, placeholder);
|
|
|
|
length -= holders.length;
|
|
if (length < arity) {
|
|
return createRecurry(
|
|
func, bitmask, createHybrid, wrapper.placeholder, undefined,
|
|
args, holders, undefined, undefined, arity - length);
|
|
}
|
|
var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
|
|
return apply(fn, this, args);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a `_.find` or `_.findLast` function.
|
|
*
|
|
* @private
|
|
* @param {Function} findIndexFunc The function to find the collection index.
|
|
* @returns {Function} Returns the new find function.
|
|
*/
|
|
function createFind(findIndexFunc) {
|
|
return function(collection, predicate, fromIndex) {
|
|
var iterable = Object(collection);
|
|
if (!isArrayLike(collection)) {
|
|
var iteratee = getIteratee(predicate, 3);
|
|
collection = keys(collection);
|
|
predicate = function(key) { return iteratee(iterable[key], key, iterable); };
|
|
}
|
|
var index = findIndexFunc(collection, predicate, fromIndex);
|
|
return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a `_.flow` or `_.flowRight` function.
|
|
*
|
|
* @private
|
|
* @param {boolean} [fromRight] Specify iterating from right to left.
|
|
* @returns {Function} Returns the new flow function.
|
|
*/
|
|
function createFlow(fromRight) {
|
|
return flatRest(function(funcs) {
|
|
var length = funcs.length,
|
|
index = length,
|
|
prereq = LodashWrapper.prototype.thru;
|
|
|
|
if (fromRight) {
|
|
funcs.reverse();
|
|
}
|
|
while (index--) {
|
|
var func = funcs[index];
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
|
|
var wrapper = new LodashWrapper([], true);
|
|
}
|
|
}
|
|
index = wrapper ? index : length;
|
|
while (++index < length) {
|
|
func = funcs[index];
|
|
|
|
var funcName = getFuncName(func),
|
|
data = funcName == 'wrapper' ? getData(func) : undefined;
|
|
|
|
if (data && isLaziable(data[0]) &&
|
|
data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) &&
|
|
!data[4].length && data[9] == 1
|
|
) {
|
|
wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
|
|
} else {
|
|
wrapper = (func.length == 1 && isLaziable(func))
|
|
? wrapper[funcName]()
|
|
: wrapper.thru(func);
|
|
}
|
|
}
|
|
return function() {
|
|
var args = arguments,
|
|
value = args[0];
|
|
|
|
if (wrapper && args.length == 1 && isArray(value)) {
|
|
return wrapper.plant(value).value();
|
|
}
|
|
var index = 0,
|
|
result = length ? funcs[index].apply(this, args) : value;
|
|
|
|
while (++index < length) {
|
|
result = funcs[index].call(this, result);
|
|
}
|
|
return result;
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` to invoke it with optional `this`
|
|
* binding of `thisArg`, partial application, and currying.
|
|
*
|
|
* @private
|
|
* @param {Function|string} func The function or method name to wrap.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @param {Array} [partials] The arguments to prepend to those provided to
|
|
* the new function.
|
|
* @param {Array} [holders] The `partials` placeholder indexes.
|
|
* @param {Array} [partialsRight] The arguments to append to those provided
|
|
* to the new function.
|
|
* @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
|
|
* @param {Array} [argPos] The argument positions of the new function.
|
|
* @param {number} [ary] The arity cap of `func`.
|
|
* @param {number} [arity] The arity of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
|
|
var isAry = bitmask & WRAP_ARY_FLAG,
|
|
isBind = bitmask & WRAP_BIND_FLAG,
|
|
isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
|
|
isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
|
|
isFlip = bitmask & WRAP_FLIP_FLAG,
|
|
Ctor = isBindKey ? undefined : createCtor(func);
|
|
|
|
function wrapper() {
|
|
var length = arguments.length,
|
|
args = Array(length),
|
|
index = length;
|
|
|
|
while (index--) {
|
|
args[index] = arguments[index];
|
|
}
|
|
if (isCurried) {
|
|
var placeholder = getHolder(wrapper),
|
|
holdersCount = countHolders(args, placeholder);
|
|
}
|
|
if (partials) {
|
|
args = composeArgs(args, partials, holders, isCurried);
|
|
}
|
|
if (partialsRight) {
|
|
args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
|
|
}
|
|
length -= holdersCount;
|
|
if (isCurried && length < arity) {
|
|
var newHolders = replaceHolders(args, placeholder);
|
|
return createRecurry(
|
|
func, bitmask, createHybrid, wrapper.placeholder, thisArg,
|
|
args, newHolders, argPos, ary, arity - length
|
|
);
|
|
}
|
|
var thisBinding = isBind ? thisArg : this,
|
|
fn = isBindKey ? thisBinding[func] : func;
|
|
|
|
length = args.length;
|
|
if (argPos) {
|
|
args = reorder(args, argPos);
|
|
} else if (isFlip && length > 1) {
|
|
args.reverse();
|
|
}
|
|
if (isAry && ary < length) {
|
|
args.length = ary;
|
|
}
|
|
if (this && this !== root && this instanceof wrapper) {
|
|
fn = Ctor || createCtor(fn);
|
|
}
|
|
return fn.apply(thisBinding, args);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.invertBy`.
|
|
*
|
|
* @private
|
|
* @param {Function} setter The function to set accumulator values.
|
|
* @param {Function} toIteratee The function to resolve iteratees.
|
|
* @returns {Function} Returns the new inverter function.
|
|
*/
|
|
function createInverter(setter, toIteratee) {
|
|
return function(object, iteratee) {
|
|
return baseInverter(object, setter, toIteratee(iteratee), {});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that performs a mathematical operation on two values.
|
|
*
|
|
* @private
|
|
* @param {Function} operator The function to perform the operation.
|
|
* @param {number} [defaultValue] The value used for `undefined` arguments.
|
|
* @returns {Function} Returns the new mathematical operation function.
|
|
*/
|
|
function createMathOperation(operator, defaultValue) {
|
|
return function(value, other) {
|
|
var result;
|
|
if (value === undefined && other === undefined) {
|
|
return defaultValue;
|
|
}
|
|
if (value !== undefined) {
|
|
result = value;
|
|
}
|
|
if (other !== undefined) {
|
|
if (result === undefined) {
|
|
return other;
|
|
}
|
|
if (typeof value == 'string' || typeof other == 'string') {
|
|
value = baseToString(value);
|
|
other = baseToString(other);
|
|
} else {
|
|
value = baseToNumber(value);
|
|
other = baseToNumber(other);
|
|
}
|
|
result = operator(value, other);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.over`.
|
|
*
|
|
* @private
|
|
* @param {Function} arrayFunc The function to iterate over iteratees.
|
|
* @returns {Function} Returns the new over function.
|
|
*/
|
|
function createOver(arrayFunc) {
|
|
return flatRest(function(iteratees) {
|
|
iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
|
|
return baseRest(function(args) {
|
|
var thisArg = this;
|
|
return arrayFunc(iteratees, function(iteratee) {
|
|
return apply(iteratee, thisArg, args);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates the padding for `string` based on `length`. The `chars` string
|
|
* is truncated if the number of characters exceeds `length`.
|
|
*
|
|
* @private
|
|
* @param {number} length The padding length.
|
|
* @param {string} [chars=' '] The string used as padding.
|
|
* @returns {string} Returns the padding for `string`.
|
|
*/
|
|
function createPadding(length, chars) {
|
|
chars = chars === undefined ? ' ' : baseToString(chars);
|
|
|
|
var charsLength = chars.length;
|
|
if (charsLength < 2) {
|
|
return charsLength ? baseRepeat(chars, length) : chars;
|
|
}
|
|
var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
|
|
return hasUnicode(chars)
|
|
? castSlice(stringToArray(result), 0, length).join('')
|
|
: result.slice(0, length);
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` to invoke it with the `this` binding
|
|
* of `thisArg` and `partials` prepended to the arguments it receives.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to wrap.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @param {*} thisArg The `this` binding of `func`.
|
|
* @param {Array} partials The arguments to prepend to those provided to
|
|
* the new function.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createPartial(func, bitmask, thisArg, partials) {
|
|
var isBind = bitmask & WRAP_BIND_FLAG,
|
|
Ctor = createCtor(func);
|
|
|
|
function wrapper() {
|
|
var argsIndex = -1,
|
|
argsLength = arguments.length,
|
|
leftIndex = -1,
|
|
leftLength = partials.length,
|
|
args = Array(leftLength + argsLength),
|
|
fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
|
|
|
|
while (++leftIndex < leftLength) {
|
|
args[leftIndex] = partials[leftIndex];
|
|
}
|
|
while (argsLength--) {
|
|
args[leftIndex++] = arguments[++argsIndex];
|
|
}
|
|
return apply(fn, isBind ? thisArg : this, args);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a `_.range` or `_.rangeRight` function.
|
|
*
|
|
* @private
|
|
* @param {boolean} [fromRight] Specify iterating from right to left.
|
|
* @returns {Function} Returns the new range function.
|
|
*/
|
|
function createRange(fromRight) {
|
|
return function(start, end, step) {
|
|
if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
|
|
end = step = undefined;
|
|
}
|
|
// Ensure the sign of `-0` is preserved.
|
|
start = toFinite(start);
|
|
if (end === undefined) {
|
|
end = start;
|
|
start = 0;
|
|
} else {
|
|
end = toFinite(end);
|
|
}
|
|
step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
|
|
return baseRange(start, end, step, fromRight);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that performs a relational operation on two values.
|
|
*
|
|
* @private
|
|
* @param {Function} operator The function to perform the operation.
|
|
* @returns {Function} Returns the new relational operation function.
|
|
*/
|
|
function createRelationalOperation(operator) {
|
|
return function(value, other) {
|
|
if (!(typeof value == 'string' && typeof other == 'string')) {
|
|
value = toNumber(value);
|
|
other = toNumber(other);
|
|
}
|
|
return operator(value, other);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` to continue currying.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to wrap.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @param {Function} wrapFunc The function to create the `func` wrapper.
|
|
* @param {*} placeholder The placeholder value.
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @param {Array} [partials] The arguments to prepend to those provided to
|
|
* the new function.
|
|
* @param {Array} [holders] The `partials` placeholder indexes.
|
|
* @param {Array} [argPos] The argument positions of the new function.
|
|
* @param {number} [ary] The arity cap of `func`.
|
|
* @param {number} [arity] The arity of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
|
|
var isCurry = bitmask & WRAP_CURRY_FLAG,
|
|
newHolders = isCurry ? holders : undefined,
|
|
newHoldersRight = isCurry ? undefined : holders,
|
|
newPartials = isCurry ? partials : undefined,
|
|
newPartialsRight = isCurry ? undefined : partials;
|
|
|
|
bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);
|
|
bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
|
|
|
|
if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
|
|
bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
|
|
}
|
|
var newData = [
|
|
func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
|
|
newHoldersRight, argPos, ary, arity
|
|
];
|
|
|
|
var result = wrapFunc.apply(undefined, newData);
|
|
if (isLaziable(func)) {
|
|
setData(result, newData);
|
|
}
|
|
result.placeholder = placeholder;
|
|
return setWrapToString(result, func, bitmask);
|
|
}
|
|
|
|
/**
|
|
* Creates a function like `_.round`.
|
|
*
|
|
* @private
|
|
* @param {string} methodName The name of the `Math` method to use when rounding.
|
|
* @returns {Function} Returns the new round function.
|
|
*/
|
|
function createRound(methodName) {
|
|
var func = Math[methodName];
|
|
return function(number, precision) {
|
|
number = toNumber(number);
|
|
precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
|
|
if (precision) {
|
|
// Shift with exponential notation to avoid floating-point issues.
|
|
// See [MDN](https://mdn.io/round#Examples) for more details.
|
|
var pair = (toString(number) + 'e').split('e'),
|
|
value = func(pair[0] + 'e' + (+pair[1] + precision));
|
|
|
|
pair = (toString(value) + 'e').split('e');
|
|
return +(pair[0] + 'e' + (+pair[1] - precision));
|
|
}
|
|
return func(number);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a set object of `values`.
|
|
*
|
|
* @private
|
|
* @param {Array} values The values to add to the set.
|
|
* @returns {Object} Returns the new set.
|
|
*/
|
|
var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
|
|
return new Set(values);
|
|
};
|
|
|
|
/**
|
|
* Creates a `_.toPairs` or `_.toPairsIn` function.
|
|
*
|
|
* @private
|
|
* @param {Function} keysFunc The function to get the keys of a given object.
|
|
* @returns {Function} Returns the new pairs function.
|
|
*/
|
|
function createToPairs(keysFunc) {
|
|
return function(object) {
|
|
var tag = getTag(object);
|
|
if (tag == mapTag) {
|
|
return mapToArray(object);
|
|
}
|
|
if (tag == setTag) {
|
|
return setToPairs(object);
|
|
}
|
|
return baseToPairs(object, keysFunc(object));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that either curries or invokes `func` with optional
|
|
* `this` binding and partially applied arguments.
|
|
*
|
|
* @private
|
|
* @param {Function|string} func The function or method name to wrap.
|
|
* @param {number} bitmask The bitmask flags.
|
|
* 1 - `_.bind`
|
|
* 2 - `_.bindKey`
|
|
* 4 - `_.curry` or `_.curryRight` of a bound function
|
|
* 8 - `_.curry`
|
|
* 16 - `_.curryRight`
|
|
* 32 - `_.partial`
|
|
* 64 - `_.partialRight`
|
|
* 128 - `_.rearg`
|
|
* 256 - `_.ary`
|
|
* 512 - `_.flip`
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @param {Array} [partials] The arguments to be partially applied.
|
|
* @param {Array} [holders] The `partials` placeholder indexes.
|
|
* @param {Array} [argPos] The argument positions of the new function.
|
|
* @param {number} [ary] The arity cap of `func`.
|
|
* @param {number} [arity] The arity of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
|
|
var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
|
|
if (!isBindKey && typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
var length = partials ? partials.length : 0;
|
|
if (!length) {
|
|
bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
|
|
partials = holders = undefined;
|
|
}
|
|
ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
|
|
arity = arity === undefined ? arity : toInteger(arity);
|
|
length -= holders ? holders.length : 0;
|
|
|
|
if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
|
|
var partialsRight = partials,
|
|
holdersRight = holders;
|
|
|
|
partials = holders = undefined;
|
|
}
|
|
var data = isBindKey ? undefined : getData(func);
|
|
|
|
var newData = [
|
|
func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
|
|
argPos, ary, arity
|
|
];
|
|
|
|
if (data) {
|
|
mergeData(newData, data);
|
|
}
|
|
func = newData[0];
|
|
bitmask = newData[1];
|
|
thisArg = newData[2];
|
|
partials = newData[3];
|
|
holders = newData[4];
|
|
arity = newData[9] = newData[9] === undefined
|
|
? (isBindKey ? 0 : func.length)
|
|
: nativeMax(newData[9] - length, 0);
|
|
|
|
if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
|
|
bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
|
|
}
|
|
if (!bitmask || bitmask == WRAP_BIND_FLAG) {
|
|
var result = createBind(func, bitmask, thisArg);
|
|
} else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
|
|
result = createCurry(func, bitmask, arity);
|
|
} else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
|
|
result = createPartial(func, bitmask, thisArg, partials);
|
|
} else {
|
|
result = createHybrid.apply(undefined, newData);
|
|
}
|
|
var setter = data ? baseSetData : setData;
|
|
return setWrapToString(setter(result, newData), func, bitmask);
|
|
}
|
|
|
|
/**
|
|
* Used by `_.defaults` to customize its `_.assignIn` use to assign properties
|
|
* of source objects to the destination object for all destination properties
|
|
* that resolve to `undefined`.
|
|
*
|
|
* @private
|
|
* @param {*} objValue The destination value.
|
|
* @param {*} srcValue The source value.
|
|
* @param {string} key The key of the property to assign.
|
|
* @param {Object} object The parent object of `objValue`.
|
|
* @returns {*} Returns the value to assign.
|
|
*/
|
|
function customDefaultsAssignIn(objValue, srcValue, key, object) {
|
|
if (objValue === undefined ||
|
|
(eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
|
|
return srcValue;
|
|
}
|
|
return objValue;
|
|
}
|
|
|
|
/**
|
|
* Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
|
|
* objects into destination objects that are passed thru.
|
|
*
|
|
* @private
|
|
* @param {*} objValue The destination value.
|
|
* @param {*} srcValue The source value.
|
|
* @param {string} key The key of the property to merge.
|
|
* @param {Object} object The parent object of `objValue`.
|
|
* @param {Object} source The parent object of `srcValue`.
|
|
* @param {Object} [stack] Tracks traversed source values and their merged
|
|
* counterparts.
|
|
* @returns {*} Returns the value to assign.
|
|
*/
|
|
function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
|
|
if (isObject(objValue) && isObject(srcValue)) {
|
|
// Recursively merge objects and arrays (susceptible to call stack limits).
|
|
stack.set(srcValue, objValue);
|
|
baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
|
|
stack['delete'](srcValue);
|
|
}
|
|
return objValue;
|
|
}
|
|
|
|
/**
|
|
* Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
|
|
* objects.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to inspect.
|
|
* @param {string} key The key of the property to inspect.
|
|
* @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
|
|
*/
|
|
function customOmitClone(value) {
|
|
return isPlainObject(value) ? undefined : value;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseIsEqualDeep` for arrays with support for
|
|
* partial deep comparisons.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to compare.
|
|
* @param {Array} other The other array to compare.
|
|
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
|
|
* @param {Function} customizer The function to customize comparisons.
|
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
|
* @param {Object} stack Tracks traversed `array` and `other` objects.
|
|
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
|
|
*/
|
|
function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
|
|
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
|
|
arrLength = array.length,
|
|
othLength = other.length;
|
|
|
|
if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
|
|
return false;
|
|
}
|
|
// Assume cyclic values are equal.
|
|
var stacked = stack.get(array);
|
|
if (stacked && stack.get(other)) {
|
|
return stacked == other;
|
|
}
|
|
var index = -1,
|
|
result = true,
|
|
seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;
|
|
|
|
stack.set(array, other);
|
|
stack.set(other, array);
|
|
|
|
// Ignore non-index properties.
|
|
while (++index < arrLength) {
|
|
var arrValue = array[index],
|
|
othValue = other[index];
|
|
|
|
if (customizer) {
|
|
var compared = isPartial
|
|
? customizer(othValue, arrValue, index, other, array, stack)
|
|
: customizer(arrValue, othValue, index, array, other, stack);
|
|
}
|
|
if (compared !== undefined) {
|
|
if (compared) {
|
|
continue;
|
|
}
|
|
result = false;
|
|
break;
|
|
}
|
|
// Recursively compare arrays (susceptible to call stack limits).
|
|
if (seen) {
|
|
if (!arraySome(other, function(othValue, othIndex) {
|
|
if (!cacheHas(seen, othIndex) &&
|
|
(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
|
|
return seen.push(othIndex);
|
|
}
|
|
})) {
|
|
result = false;
|
|
break;
|
|
}
|
|
} else if (!(
|
|
arrValue === othValue ||
|
|
equalFunc(arrValue, othValue, bitmask, customizer, stack)
|
|
)) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
stack['delete'](array);
|
|
stack['delete'](other);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseIsEqualDeep` for comparing objects of
|
|
* the same `toStringTag`.
|
|
*
|
|
* **Note:** This function only supports comparing values with tags of
|
|
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to compare.
|
|
* @param {Object} other The other object to compare.
|
|
* @param {string} tag The `toStringTag` of the objects to compare.
|
|
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
|
|
* @param {Function} customizer The function to customize comparisons.
|
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
|
* @param {Object} stack Tracks traversed `object` and `other` objects.
|
|
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
|
*/
|
|
function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
|
|
switch (tag) {
|
|
case dataViewTag:
|
|
if ((object.byteLength != other.byteLength) ||
|
|
(object.byteOffset != other.byteOffset)) {
|
|
return false;
|
|
}
|
|
object = object.buffer;
|
|
other = other.buffer;
|
|
|
|
case arrayBufferTag:
|
|
if ((object.byteLength != other.byteLength) ||
|
|
!equalFunc(new Uint8Array(object), new Uint8Array(other))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
case boolTag:
|
|
case dateTag:
|
|
case numberTag:
|
|
// Coerce booleans to `1` or `0` and dates to milliseconds.
|
|
// Invalid dates are coerced to `NaN`.
|
|
return eq(+object, +other);
|
|
|
|
case errorTag:
|
|
return object.name == other.name && object.message == other.message;
|
|
|
|
case regexpTag:
|
|
case stringTag:
|
|
// Coerce regexes to strings and treat strings, primitives and objects,
|
|
// as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
|
|
// for more details.
|
|
return object == (other + '');
|
|
|
|
case mapTag:
|
|
var convert = mapToArray;
|
|
|
|
case setTag:
|
|
var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
|
|
convert || (convert = setToArray);
|
|
|
|
if (object.size != other.size && !isPartial) {
|
|
return false;
|
|
}
|
|
// Assume cyclic values are equal.
|
|
var stacked = stack.get(object);
|
|
if (stacked) {
|
|
return stacked == other;
|
|
}
|
|
bitmask |= COMPARE_UNORDERED_FLAG;
|
|
|
|
// Recursively compare objects (susceptible to call stack limits).
|
|
stack.set(object, other);
|
|
var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
|
|
stack['delete'](object);
|
|
return result;
|
|
|
|
case symbolTag:
|
|
if (symbolValueOf) {
|
|
return symbolValueOf.call(object) == symbolValueOf.call(other);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseIsEqualDeep` for objects with support for
|
|
* partial deep comparisons.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to compare.
|
|
* @param {Object} other The other object to compare.
|
|
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
|
|
* @param {Function} customizer The function to customize comparisons.
|
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
|
* @param {Object} stack Tracks traversed `object` and `other` objects.
|
|
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
|
*/
|
|
function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
|
|
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
|
|
objProps = getAllKeys(object),
|
|
objLength = objProps.length,
|
|
othProps = getAllKeys(other),
|
|
othLength = othProps.length;
|
|
|
|
if (objLength != othLength && !isPartial) {
|
|
return false;
|
|
}
|
|
var index = objLength;
|
|
while (index--) {
|
|
var key = objProps[index];
|
|
if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
|
|
return false;
|
|
}
|
|
}
|
|
// Assume cyclic values are equal.
|
|
var stacked = stack.get(object);
|
|
if (stacked && stack.get(other)) {
|
|
return stacked == other;
|
|
}
|
|
var result = true;
|
|
stack.set(object, other);
|
|
stack.set(other, object);
|
|
|
|
var skipCtor = isPartial;
|
|
while (++index < objLength) {
|
|
key = objProps[index];
|
|
var objValue = object[key],
|
|
othValue = other[key];
|
|
|
|
if (customizer) {
|
|
var compared = isPartial
|
|
? customizer(othValue, objValue, key, other, object, stack)
|
|
: customizer(objValue, othValue, key, object, other, stack);
|
|
}
|
|
// Recursively compare objects (susceptible to call stack limits).
|
|
if (!(compared === undefined
|
|
? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
|
|
: compared
|
|
)) {
|
|
result = false;
|
|
break;
|
|
}
|
|
skipCtor || (skipCtor = key == 'constructor');
|
|
}
|
|
if (result && !skipCtor) {
|
|
var objCtor = object.constructor,
|
|
othCtor = other.constructor;
|
|
|
|
// Non `Object` object instances with different constructors are not equal.
|
|
if (objCtor != othCtor &&
|
|
('constructor' in object && 'constructor' in other) &&
|
|
!(typeof objCtor == 'function' && objCtor instanceof objCtor &&
|
|
typeof othCtor == 'function' && othCtor instanceof othCtor)) {
|
|
result = false;
|
|
}
|
|
}
|
|
stack['delete'](object);
|
|
stack['delete'](other);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseRest` which flattens the rest array.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to apply a rest parameter to.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function flatRest(func) {
|
|
return setToString(overRest(func, undefined, flatten), func + '');
|
|
}
|
|
|
|
/**
|
|
* Creates an array of own enumerable property names and symbols of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names and symbols.
|
|
*/
|
|
function getAllKeys(object) {
|
|
return baseGetAllKeys(object, keys, getSymbols);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of own and inherited enumerable property names and
|
|
* symbols of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names and symbols.
|
|
*/
|
|
function getAllKeysIn(object) {
|
|
return baseGetAllKeys(object, keysIn, getSymbolsIn);
|
|
}
|
|
|
|
/**
|
|
* Gets metadata for `func`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to query.
|
|
* @returns {*} Returns the metadata for `func`.
|
|
*/
|
|
var getData = !metaMap ? noop : function(func) {
|
|
return metaMap.get(func);
|
|
};
|
|
|
|
/**
|
|
* Gets the name of `func`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to query.
|
|
* @returns {string} Returns the function name.
|
|
*/
|
|
function getFuncName(func) {
|
|
var result = (func.name + ''),
|
|
array = realNames[result],
|
|
length = hasOwnProperty.call(realNames, result) ? array.length : 0;
|
|
|
|
while (length--) {
|
|
var data = array[length],
|
|
otherFunc = data.func;
|
|
if (otherFunc == null || otherFunc == func) {
|
|
return data.name;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the argument placeholder value for `func`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to inspect.
|
|
* @returns {*} Returns the placeholder value.
|
|
*/
|
|
function getHolder(func) {
|
|
var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
|
|
return object.placeholder;
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
|
|
* this function returns the custom method, otherwise it returns `baseIteratee`.
|
|
* If arguments are provided, the chosen function is invoked with them and
|
|
* its result is returned.
|
|
*
|
|
* @private
|
|
* @param {*} [value] The value to convert to an iteratee.
|
|
* @param {number} [arity] The arity of the created iteratee.
|
|
* @returns {Function} Returns the chosen function or its result.
|
|
*/
|
|
function getIteratee() {
|
|
var result = lodash.iteratee || iteratee;
|
|
result = result === iteratee ? baseIteratee : result;
|
|
return arguments.length ? result(arguments[0], arguments[1]) : result;
|
|
}
|
|
|
|
/**
|
|
* Gets the data for `map`.
|
|
*
|
|
* @private
|
|
* @param {Object} map The map to query.
|
|
* @param {string} key The reference key.
|
|
* @returns {*} Returns the map data.
|
|
*/
|
|
function getMapData(map, key) {
|
|
var data = map.__data__;
|
|
return isKeyable(key)
|
|
? data[typeof key == 'string' ? 'string' : 'hash']
|
|
: data.map;
|
|
}
|
|
|
|
/**
|
|
* Gets the property names, values, and compare flags of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the match data of `object`.
|
|
*/
|
|
function getMatchData(object) {
|
|
var result = keys(object),
|
|
length = result.length;
|
|
|
|
while (length--) {
|
|
var key = result[length],
|
|
value = object[key];
|
|
|
|
result[length] = [key, value, isStrictComparable(value)];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the native function at `key` of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {string} key The key of the method to get.
|
|
* @returns {*} Returns the function if it's native, else `undefined`.
|
|
*/
|
|
function getNative(object, key) {
|
|
var value = getValue(object, key);
|
|
return baseIsNative(value) ? value : undefined;
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to query.
|
|
* @returns {string} Returns the raw `toStringTag`.
|
|
*/
|
|
function getRawTag(value) {
|
|
var isOwn = hasOwnProperty.call(value, symToStringTag),
|
|
tag = value[symToStringTag];
|
|
|
|
try {
|
|
value[symToStringTag] = undefined;
|
|
var unmasked = true;
|
|
} catch (e) {}
|
|
|
|
var result = nativeObjectToString.call(value);
|
|
if (unmasked) {
|
|
if (isOwn) {
|
|
value[symToStringTag] = tag;
|
|
} else {
|
|
delete value[symToStringTag];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates an array of the own enumerable symbols of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of symbols.
|
|
*/
|
|
var getSymbols = !nativeGetSymbols ? stubArray : function(object) {
|
|
if (object == null) {
|
|
return [];
|
|
}
|
|
object = Object(object);
|
|
return arrayFilter(nativeGetSymbols(object), function(symbol) {
|
|
return propertyIsEnumerable.call(object, symbol);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Creates an array of the own and inherited enumerable symbols of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of symbols.
|
|
*/
|
|
var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
|
|
var result = [];
|
|
while (object) {
|
|
arrayPush(result, getSymbols(object));
|
|
object = getPrototype(object);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Gets the `toStringTag` of `value`.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to query.
|
|
* @returns {string} Returns the `toStringTag`.
|
|
*/
|
|
var getTag = baseGetTag;
|
|
|
|
// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
|
|
if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
|
|
(Map && getTag(new Map) != mapTag) ||
|
|
(Promise && getTag(Promise.resolve()) != promiseTag) ||
|
|
(Set && getTag(new Set) != setTag) ||
|
|
(WeakMap && getTag(new WeakMap) != weakMapTag)) {
|
|
getTag = function(value) {
|
|
var result = baseGetTag(value),
|
|
Ctor = result == objectTag ? value.constructor : undefined,
|
|
ctorString = Ctor ? toSource(Ctor) : '';
|
|
|
|
if (ctorString) {
|
|
switch (ctorString) {
|
|
case dataViewCtorString: return dataViewTag;
|
|
case mapCtorString: return mapTag;
|
|
case promiseCtorString: return promiseTag;
|
|
case setCtorString: return setTag;
|
|
case weakMapCtorString: return weakMapTag;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the view, applying any `transforms` to the `start` and `end` positions.
|
|
*
|
|
* @private
|
|
* @param {number} start The start of the view.
|
|
* @param {number} end The end of the view.
|
|
* @param {Array} transforms The transformations to apply to the view.
|
|
* @returns {Object} Returns an object containing the `start` and `end`
|
|
* positions of the view.
|
|
*/
|
|
function getView(start, end, transforms) {
|
|
var index = -1,
|
|
length = transforms.length;
|
|
|
|
while (++index < length) {
|
|
var data = transforms[index],
|
|
size = data.size;
|
|
|
|
switch (data.type) {
|
|
case 'drop': start += size; break;
|
|
case 'dropRight': end -= size; break;
|
|
case 'take': end = nativeMin(end, start + size); break;
|
|
case 'takeRight': start = nativeMax(start, end - size); break;
|
|
}
|
|
}
|
|
return { 'start': start, 'end': end };
|
|
}
|
|
|
|
/**
|
|
* Extracts wrapper details from the `source` body comment.
|
|
*
|
|
* @private
|
|
* @param {string} source The source to inspect.
|
|
* @returns {Array} Returns the wrapper details.
|
|
*/
|
|
function getWrapDetails(source) {
|
|
var match = source.match(reWrapDetails);
|
|
return match ? match[1].split(reSplitDetails) : [];
|
|
}
|
|
|
|
/**
|
|
* Checks if `path` exists on `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path to check.
|
|
* @param {Function} hasFunc The function to check properties.
|
|
* @returns {boolean} Returns `true` if `path` exists, else `false`.
|
|
*/
|
|
function hasPath(object, path, hasFunc) {
|
|
path = castPath(path, object);
|
|
|
|
var index = -1,
|
|
length = path.length,
|
|
result = false;
|
|
|
|
while (++index < length) {
|
|
var key = toKey(path[index]);
|
|
if (!(result = object != null && hasFunc(object, key))) {
|
|
break;
|
|
}
|
|
object = object[key];
|
|
}
|
|
if (result || ++index != length) {
|
|
return result;
|
|
}
|
|
length = object == null ? 0 : object.length;
|
|
return !!length && isLength(length) && isIndex(key, length) &&
|
|
(isArray(object) || isArguments(object));
|
|
}
|
|
|
|
/**
|
|
* Initializes an array clone.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to clone.
|
|
* @returns {Array} Returns the initialized clone.
|
|
*/
|
|
function initCloneArray(array) {
|
|
var length = array.length,
|
|
result = array.constructor(length);
|
|
|
|
// Add properties assigned by `RegExp#exec`.
|
|
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
|
|
result.index = array.index;
|
|
result.input = array.input;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Initializes an object clone.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to clone.
|
|
* @returns {Object} Returns the initialized clone.
|
|
*/
|
|
function initCloneObject(object) {
|
|
return (typeof object.constructor == 'function' && !isPrototype(object))
|
|
? baseCreate(getPrototype(object))
|
|
: {};
|
|
}
|
|
|
|
/**
|
|
* Initializes an object clone based on its `toStringTag`.
|
|
*
|
|
* **Note:** This function only supports cloning values with tags of
|
|
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to clone.
|
|
* @param {string} tag The `toStringTag` of the object to clone.
|
|
* @param {Function} cloneFunc The function to clone values.
|
|
* @param {boolean} [isDeep] Specify a deep clone.
|
|
* @returns {Object} Returns the initialized clone.
|
|
*/
|
|
function initCloneByTag(object, tag, cloneFunc, isDeep) {
|
|
var Ctor = object.constructor;
|
|
switch (tag) {
|
|
case arrayBufferTag:
|
|
return cloneArrayBuffer(object);
|
|
|
|
case boolTag:
|
|
case dateTag:
|
|
return new Ctor(+object);
|
|
|
|
case dataViewTag:
|
|
return cloneDataView(object, isDeep);
|
|
|
|
case float32Tag: case float64Tag:
|
|
case int8Tag: case int16Tag: case int32Tag:
|
|
case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
|
|
return cloneTypedArray(object, isDeep);
|
|
|
|
case mapTag:
|
|
return cloneMap(object, isDeep, cloneFunc);
|
|
|
|
case numberTag:
|
|
case stringTag:
|
|
return new Ctor(object);
|
|
|
|
case regexpTag:
|
|
return cloneRegExp(object);
|
|
|
|
case setTag:
|
|
return cloneSet(object, isDeep, cloneFunc);
|
|
|
|
case symbolTag:
|
|
return cloneSymbol(object);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inserts wrapper `details` in a comment at the top of the `source` body.
|
|
*
|
|
* @private
|
|
* @param {string} source The source to modify.
|
|
* @returns {Array} details The details to insert.
|
|
* @returns {string} Returns the modified source.
|
|
*/
|
|
function insertWrapDetails(source, details) {
|
|
var length = details.length;
|
|
if (!length) {
|
|
return source;
|
|
}
|
|
var lastIndex = length - 1;
|
|
details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
|
|
details = details.join(length > 2 ? ', ' : ' ');
|
|
return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a flattenable `arguments` object or array.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
|
|
*/
|
|
function isFlattenable(value) {
|
|
return isArray(value) || isArguments(value) ||
|
|
!!(spreadableSymbol && value && value[spreadableSymbol]);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like index.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
|
|
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
|
|
*/
|
|
function isIndex(value, length) {
|
|
length = length == null ? MAX_SAFE_INTEGER : length;
|
|
return !!length &&
|
|
(typeof value == 'number' || reIsUint.test(value)) &&
|
|
(value > -1 && value % 1 == 0 && value < length);
|
|
}
|
|
|
|
/**
|
|
* Checks if the given arguments are from an iteratee call.
|
|
*
|
|
* @private
|
|
* @param {*} value The potential iteratee value argument.
|
|
* @param {*} index The potential iteratee index or key argument.
|
|
* @param {*} object The potential iteratee object argument.
|
|
* @returns {boolean} Returns `true` if the arguments are from an iteratee call,
|
|
* else `false`.
|
|
*/
|
|
function isIterateeCall(value, index, object) {
|
|
if (!isObject(object)) {
|
|
return false;
|
|
}
|
|
var type = typeof index;
|
|
if (type == 'number'
|
|
? (isArrayLike(object) && isIndex(index, object.length))
|
|
: (type == 'string' && index in object)
|
|
) {
|
|
return eq(object[index], value);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a property name and not a property path.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @param {Object} [object] The object to query keys on.
|
|
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
|
|
*/
|
|
function isKey(value, object) {
|
|
if (isArray(value)) {
|
|
return false;
|
|
}
|
|
var type = typeof value;
|
|
if (type == 'number' || type == 'symbol' || type == 'boolean' ||
|
|
value == null || isSymbol(value)) {
|
|
return true;
|
|
}
|
|
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
|
|
(object != null && value in Object(object));
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is suitable for use as unique object key.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
|
|
*/
|
|
function isKeyable(value) {
|
|
var type = typeof value;
|
|
return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
|
|
? (value !== '__proto__')
|
|
: (value === null);
|
|
}
|
|
|
|
/**
|
|
* Checks if `func` has a lazy counterpart.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to check.
|
|
* @returns {boolean} Returns `true` if `func` has a lazy counterpart,
|
|
* else `false`.
|
|
*/
|
|
function isLaziable(func) {
|
|
var funcName = getFuncName(func),
|
|
other = lodash[funcName];
|
|
|
|
if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
|
|
return false;
|
|
}
|
|
if (func === other) {
|
|
return true;
|
|
}
|
|
var data = getData(other);
|
|
return !!data && func === data[0];
|
|
}
|
|
|
|
/**
|
|
* Checks if `func` has its source masked.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to check.
|
|
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
|
|
*/
|
|
function isMasked(func) {
|
|
return !!maskSrcKey && (maskSrcKey in func);
|
|
}
|
|
|
|
/**
|
|
* Checks if `func` is capable of being masked.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `func` is maskable, else `false`.
|
|
*/
|
|
var isMaskable = coreJsData ? isFunction : stubFalse;
|
|
|
|
/**
|
|
* Checks if `value` is likely a prototype object.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
|
|
*/
|
|
function isPrototype(value) {
|
|
var Ctor = value && value.constructor,
|
|
proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
|
|
|
|
return value === proto;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` if suitable for strict
|
|
* equality comparisons, else `false`.
|
|
*/
|
|
function isStrictComparable(value) {
|
|
return value === value && !isObject(value);
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `matchesProperty` for source values suitable
|
|
* for strict equality comparisons, i.e. `===`.
|
|
*
|
|
* @private
|
|
* @param {string} key The key of the property to get.
|
|
* @param {*} srcValue The value to match.
|
|
* @returns {Function} Returns the new spec function.
|
|
*/
|
|
function matchesStrictComparable(key, srcValue) {
|
|
return function(object) {
|
|
if (object == null) {
|
|
return false;
|
|
}
|
|
return object[key] === srcValue &&
|
|
(srcValue !== undefined || (key in Object(object)));
|
|
};
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.memoize` which clears the memoized function's
|
|
* cache when it exceeds `MAX_MEMOIZE_SIZE`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to have its output memoized.
|
|
* @returns {Function} Returns the new memoized function.
|
|
*/
|
|
function memoizeCapped(func) {
|
|
var result = memoize(func, function(key) {
|
|
if (cache.size === MAX_MEMOIZE_SIZE) {
|
|
cache.clear();
|
|
}
|
|
return key;
|
|
});
|
|
|
|
var cache = result.cache;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Merges the function metadata of `source` into `data`.
|
|
*
|
|
* Merging metadata reduces the number of wrappers used to invoke a function.
|
|
* This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
|
|
* may be applied regardless of execution order. Methods like `_.ary` and
|
|
* `_.rearg` modify function arguments, making the order in which they are
|
|
* executed important, preventing the merging of metadata. However, we make
|
|
* an exception for a safe combined case where curried functions have `_.ary`
|
|
* and or `_.rearg` applied.
|
|
*
|
|
* @private
|
|
* @param {Array} data The destination metadata.
|
|
* @param {Array} source The source metadata.
|
|
* @returns {Array} Returns `data`.
|
|
*/
|
|
function mergeData(data, source) {
|
|
var bitmask = data[1],
|
|
srcBitmask = source[1],
|
|
newBitmask = bitmask | srcBitmask,
|
|
isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);
|
|
|
|
var isCombo =
|
|
((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) ||
|
|
((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) ||
|
|
((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG));
|
|
|
|
// Exit early if metadata can't be merged.
|
|
if (!(isCommon || isCombo)) {
|
|
return data;
|
|
}
|
|
// Use source `thisArg` if available.
|
|
if (srcBitmask & WRAP_BIND_FLAG) {
|
|
data[2] = source[2];
|
|
// Set when currying a bound function.
|
|
newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
|
|
}
|
|
// Compose partial arguments.
|
|
var value = source[3];
|
|
if (value) {
|
|
var partials = data[3];
|
|
data[3] = partials ? composeArgs(partials, value, source[4]) : value;
|
|
data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
|
|
}
|
|
// Compose partial right arguments.
|
|
value = source[5];
|
|
if (value) {
|
|
partials = data[5];
|
|
data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
|
|
data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
|
|
}
|
|
// Use source `argPos` if available.
|
|
value = source[7];
|
|
if (value) {
|
|
data[7] = value;
|
|
}
|
|
// Use source `ary` if it's smaller.
|
|
if (srcBitmask & WRAP_ARY_FLAG) {
|
|
data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
|
|
}
|
|
// Use source `arity` if one is not provided.
|
|
if (data[9] == null) {
|
|
data[9] = source[9];
|
|
}
|
|
// Use source `func` and merge bitmasks.
|
|
data[0] = source[0];
|
|
data[1] = newBitmask;
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* This function is like
|
|
* [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
|
|
* except that it includes inherited enumerable properties.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
*/
|
|
function nativeKeysIn(object) {
|
|
var result = [];
|
|
if (object != null) {
|
|
for (var key in Object(object)) {
|
|
result.push(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a string using `Object.prototype.toString`.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to convert.
|
|
* @returns {string} Returns the converted string.
|
|
*/
|
|
function objectToString(value) {
|
|
return nativeObjectToString.call(value);
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `baseRest` which transforms the rest array.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to apply a rest parameter to.
|
|
* @param {number} [start=func.length-1] The start position of the rest parameter.
|
|
* @param {Function} transform The rest array transform.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function overRest(func, start, transform) {
|
|
start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
|
|
return function() {
|
|
var args = arguments,
|
|
index = -1,
|
|
length = nativeMax(args.length - start, 0),
|
|
array = Array(length);
|
|
|
|
while (++index < length) {
|
|
array[index] = args[start + index];
|
|
}
|
|
index = -1;
|
|
var otherArgs = Array(start + 1);
|
|
while (++index < start) {
|
|
otherArgs[index] = args[index];
|
|
}
|
|
otherArgs[start] = transform(array);
|
|
return apply(func, this, otherArgs);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the parent value at `path` of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {Array} path The path to get the parent value of.
|
|
* @returns {*} Returns the parent value.
|
|
*/
|
|
function parent(object, path) {
|
|
return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
|
|
}
|
|
|
|
/**
|
|
* Reorder `array` according to the specified indexes where the element at
|
|
* the first index is assigned as the first element, the element at
|
|
* the second index is assigned as the second element, and so on.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to reorder.
|
|
* @param {Array} indexes The arranged array indexes.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function reorder(array, indexes) {
|
|
var arrLength = array.length,
|
|
length = nativeMin(indexes.length, arrLength),
|
|
oldArray = copyArray(array);
|
|
|
|
while (length--) {
|
|
var index = indexes[length];
|
|
array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Sets metadata for `func`.
|
|
*
|
|
* **Note:** If this function becomes hot, i.e. is invoked a lot in a short
|
|
* period of time, it will trip its breaker and transition to an identity
|
|
* function to avoid garbage collection pauses in V8. See
|
|
* [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
|
|
* for more details.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to associate metadata with.
|
|
* @param {*} data The metadata.
|
|
* @returns {Function} Returns `func`.
|
|
*/
|
|
var setData = shortOut(baseSetData);
|
|
|
|
/**
|
|
* A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to delay.
|
|
* @param {number} wait The number of milliseconds to delay invocation.
|
|
* @returns {number|Object} Returns the timer id or timeout object.
|
|
*/
|
|
var setTimeout = ctxSetTimeout || function(func, wait) {
|
|
return root.setTimeout(func, wait);
|
|
};
|
|
|
|
/**
|
|
* Sets the `toString` method of `func` to return `string`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to modify.
|
|
* @param {Function} string The `toString` result.
|
|
* @returns {Function} Returns `func`.
|
|
*/
|
|
var setToString = shortOut(baseSetToString);
|
|
|
|
/**
|
|
* Sets the `toString` method of `wrapper` to mimic the source of `reference`
|
|
* with wrapper details in a comment at the top of the source body.
|
|
*
|
|
* @private
|
|
* @param {Function} wrapper The function to modify.
|
|
* @param {Function} reference The reference function.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @returns {Function} Returns `wrapper`.
|
|
*/
|
|
function setWrapToString(wrapper, reference, bitmask) {
|
|
var source = (reference + '');
|
|
return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
|
|
}
|
|
|
|
/**
|
|
* Creates a function that'll short out and invoke `identity` instead
|
|
* of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
|
|
* milliseconds.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to restrict.
|
|
* @returns {Function} Returns the new shortable function.
|
|
*/
|
|
function shortOut(func) {
|
|
var count = 0,
|
|
lastCalled = 0;
|
|
|
|
return function() {
|
|
var stamp = nativeNow(),
|
|
remaining = HOT_SPAN - (stamp - lastCalled);
|
|
|
|
lastCalled = stamp;
|
|
if (remaining > 0) {
|
|
if (++count >= HOT_COUNT) {
|
|
return arguments[0];
|
|
}
|
|
} else {
|
|
count = 0;
|
|
}
|
|
return func.apply(undefined, arguments);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* A specialized version of `_.shuffle` which mutates and sets the size of `array`.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to shuffle.
|
|
* @param {number} [size=array.length] The size of `array`.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function shuffleSelf(array, size) {
|
|
var index = -1,
|
|
length = array.length,
|
|
lastIndex = length - 1;
|
|
|
|
size = size === undefined ? length : size;
|
|
while (++index < size) {
|
|
var rand = baseRandom(index, lastIndex),
|
|
value = array[rand];
|
|
|
|
array[rand] = array[index];
|
|
array[index] = value;
|
|
}
|
|
array.length = size;
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Converts `string` to a property path array.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to convert.
|
|
* @returns {Array} Returns the property path array.
|
|
*/
|
|
var stringToPath = memoizeCapped(function(string) {
|
|
var result = [];
|
|
if (reLeadingDot.test(string)) {
|
|
result.push('');
|
|
}
|
|
string.replace(rePropName, function(match, number, quote, string) {
|
|
result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
|
|
});
|
|
return result;
|
|
});
|
|
|
|
/**
|
|
* Converts `value` to a string key if it's not a string or symbol.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to inspect.
|
|
* @returns {string|symbol} Returns the key.
|
|
*/
|
|
function toKey(value) {
|
|
if (typeof value == 'string' || isSymbol(value)) {
|
|
return value;
|
|
}
|
|
var result = (value + '');
|
|
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
|
|
}
|
|
|
|
/**
|
|
* Converts `func` to its source code.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to convert.
|
|
* @returns {string} Returns the source code.
|
|
*/
|
|
function toSource(func) {
|
|
if (func != null) {
|
|
try {
|
|
return funcToString.call(func);
|
|
} catch (e) {}
|
|
try {
|
|
return (func + '');
|
|
} catch (e) {}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Updates wrapper `details` based on `bitmask` flags.
|
|
*
|
|
* @private
|
|
* @returns {Array} details The details to modify.
|
|
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
|
|
* @returns {Array} Returns `details`.
|
|
*/
|
|
function updateWrapDetails(details, bitmask) {
|
|
arrayEach(wrapFlags, function(pair) {
|
|
var value = '_.' + pair[0];
|
|
if ((bitmask & pair[1]) && !arrayIncludes(details, value)) {
|
|
details.push(value);
|
|
}
|
|
});
|
|
return details.sort();
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of `wrapper`.
|
|
*
|
|
* @private
|
|
* @param {Object} wrapper The wrapper to clone.
|
|
* @returns {Object} Returns the cloned wrapper.
|
|
*/
|
|
function wrapperClone(wrapper) {
|
|
if (wrapper instanceof LazyWrapper) {
|
|
return wrapper.clone();
|
|
}
|
|
var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
|
|
result.__actions__ = copyArray(wrapper.__actions__);
|
|
result.__index__ = wrapper.__index__;
|
|
result.__values__ = wrapper.__values__;
|
|
return result;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates an array of elements split into groups the length of `size`.
|
|
* If `array` can't be split evenly, the final chunk will be the remaining
|
|
* elements.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to process.
|
|
* @param {number} [size=1] The length of each chunk
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the new array of chunks.
|
|
* @example
|
|
*
|
|
* _.chunk(['a', 'b', 'c', 'd'], 2);
|
|
* // => [['a', 'b'], ['c', 'd']]
|
|
*
|
|
* _.chunk(['a', 'b', 'c', 'd'], 3);
|
|
* // => [['a', 'b', 'c'], ['d']]
|
|
*/
|
|
function chunk(array, size, guard) {
|
|
if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
|
|
size = 1;
|
|
} else {
|
|
size = nativeMax(toInteger(size), 0);
|
|
}
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length || size < 1) {
|
|
return [];
|
|
}
|
|
var index = 0,
|
|
resIndex = 0,
|
|
result = Array(nativeCeil(length / size));
|
|
|
|
while (index < length) {
|
|
result[resIndex++] = baseSlice(array, index, (index += size));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates an array with all falsey values removed. The values `false`, `null`,
|
|
* `0`, `""`, `undefined`, and `NaN` are falsey.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to compact.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @example
|
|
*
|
|
* _.compact([0, 1, false, 2, '', 3]);
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function compact(array) {
|
|
var index = -1,
|
|
length = array == null ? 0 : array.length,
|
|
resIndex = 0,
|
|
result = [];
|
|
|
|
while (++index < length) {
|
|
var value = array[index];
|
|
if (value) {
|
|
result[resIndex++] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a new array concatenating `array` with any additional arrays
|
|
* and/or values.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to concatenate.
|
|
* @param {...*} [values] The values to concatenate.
|
|
* @returns {Array} Returns the new concatenated array.
|
|
* @example
|
|
*
|
|
* var array = [1];
|
|
* var other = _.concat(array, 2, [3], [[4]]);
|
|
*
|
|
* console.log(other);
|
|
* // => [1, 2, 3, [4]]
|
|
*
|
|
* console.log(array);
|
|
* // => [1]
|
|
*/
|
|
function concat() {
|
|
var length = arguments.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
var args = Array(length - 1),
|
|
array = arguments[0],
|
|
index = length;
|
|
|
|
while (index--) {
|
|
args[index - 1] = arguments[index];
|
|
}
|
|
return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
|
|
}
|
|
|
|
/**
|
|
* Creates an array of `array` values not included in the other given arrays
|
|
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons. The order and references of result values are
|
|
* determined by the first array.
|
|
*
|
|
* **Note:** Unlike `_.pullAll`, this method returns a new array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {...Array} [values] The values to exclude.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @see _.without, _.xor
|
|
* @example
|
|
*
|
|
* _.difference([2, 1], [2, 3]);
|
|
* // => [1]
|
|
*/
|
|
var difference = baseRest(function(array, values) {
|
|
return isArrayLikeObject(array)
|
|
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.difference` except that it accepts `iteratee` which
|
|
* is invoked for each element of `array` and `values` to generate the criterion
|
|
* by which they're compared. The order and references of result values are
|
|
* determined by the first array. The iteratee is invoked with one argument:
|
|
* (value).
|
|
*
|
|
* **Note:** Unlike `_.pullAllBy`, this method returns a new array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {...Array} [values] The values to exclude.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @example
|
|
*
|
|
* _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
|
|
* // => [1.2]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
|
|
* // => [{ 'x': 2 }]
|
|
*/
|
|
var differenceBy = baseRest(function(array, values) {
|
|
var iteratee = last(values);
|
|
if (isArrayLikeObject(iteratee)) {
|
|
iteratee = undefined;
|
|
}
|
|
return isArrayLikeObject(array)
|
|
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2))
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.difference` except that it accepts `comparator`
|
|
* which is invoked to compare elements of `array` to `values`. The order and
|
|
* references of result values are determined by the first array. The comparator
|
|
* is invoked with two arguments: (arrVal, othVal).
|
|
*
|
|
* **Note:** Unlike `_.pullAllWith`, this method returns a new array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {...Array} [values] The values to exclude.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
|
|
*
|
|
* _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
|
|
* // => [{ 'x': 2, 'y': 1 }]
|
|
*/
|
|
var differenceWith = baseRest(function(array, values) {
|
|
var comparator = last(values);
|
|
if (isArrayLikeObject(comparator)) {
|
|
comparator = undefined;
|
|
}
|
|
return isArrayLikeObject(array)
|
|
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* Creates a slice of `array` with `n` elements dropped from the beginning.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.5.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {number} [n=1] The number of elements to drop.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* _.drop([1, 2, 3]);
|
|
* // => [2, 3]
|
|
*
|
|
* _.drop([1, 2, 3], 2);
|
|
* // => [3]
|
|
*
|
|
* _.drop([1, 2, 3], 5);
|
|
* // => []
|
|
*
|
|
* _.drop([1, 2, 3], 0);
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function drop(array, n, guard) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
n = (guard || n === undefined) ? 1 : toInteger(n);
|
|
return baseSlice(array, n < 0 ? 0 : n, length);
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` with `n` elements dropped from the end.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {number} [n=1] The number of elements to drop.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* _.dropRight([1, 2, 3]);
|
|
* // => [1, 2]
|
|
*
|
|
* _.dropRight([1, 2, 3], 2);
|
|
* // => [1]
|
|
*
|
|
* _.dropRight([1, 2, 3], 5);
|
|
* // => []
|
|
*
|
|
* _.dropRight([1, 2, 3], 0);
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function dropRight(array, n, guard) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
n = (guard || n === undefined) ? 1 : toInteger(n);
|
|
n = length - n;
|
|
return baseSlice(array, 0, n < 0 ? 0 : n);
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` excluding elements dropped from the end.
|
|
* Elements are dropped until `predicate` returns falsey. The predicate is
|
|
* invoked with three arguments: (value, index, array).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': true },
|
|
* { 'user': 'fred', 'active': false },
|
|
* { 'user': 'pebbles', 'active': false }
|
|
* ];
|
|
*
|
|
* _.dropRightWhile(users, function(o) { return !o.active; });
|
|
* // => objects for ['barney']
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
|
|
* // => objects for ['barney', 'fred']
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.dropRightWhile(users, ['active', false]);
|
|
* // => objects for ['barney']
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.dropRightWhile(users, 'active');
|
|
* // => objects for ['barney', 'fred', 'pebbles']
|
|
*/
|
|
function dropRightWhile(array, predicate) {
|
|
return (array && array.length)
|
|
? baseWhile(array, getIteratee(predicate, 3), true, true)
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` excluding elements dropped from the beginning.
|
|
* Elements are dropped until `predicate` returns falsey. The predicate is
|
|
* invoked with three arguments: (value, index, array).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': false },
|
|
* { 'user': 'fred', 'active': false },
|
|
* { 'user': 'pebbles', 'active': true }
|
|
* ];
|
|
*
|
|
* _.dropWhile(users, function(o) { return !o.active; });
|
|
* // => objects for ['pebbles']
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.dropWhile(users, { 'user': 'barney', 'active': false });
|
|
* // => objects for ['fred', 'pebbles']
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.dropWhile(users, ['active', false]);
|
|
* // => objects for ['pebbles']
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.dropWhile(users, 'active');
|
|
* // => objects for ['barney', 'fred', 'pebbles']
|
|
*/
|
|
function dropWhile(array, predicate) {
|
|
return (array && array.length)
|
|
? baseWhile(array, getIteratee(predicate, 3), true)
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* Fills elements of `array` with `value` from `start` up to, but not
|
|
* including, `end`.
|
|
*
|
|
* **Note:** This method mutates `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.2.0
|
|
* @category Array
|
|
* @param {Array} array The array to fill.
|
|
* @param {*} value The value to fill `array` with.
|
|
* @param {number} [start=0] The start position.
|
|
* @param {number} [end=array.length] The end position.
|
|
* @returns {Array} Returns `array`.
|
|
* @example
|
|
*
|
|
* var array = [1, 2, 3];
|
|
*
|
|
* _.fill(array, 'a');
|
|
* console.log(array);
|
|
* // => ['a', 'a', 'a']
|
|
*
|
|
* _.fill(Array(3), 2);
|
|
* // => [2, 2, 2]
|
|
*
|
|
* _.fill([4, 6, 8, 10], '*', 1, 3);
|
|
* // => [4, '*', '*', 10]
|
|
*/
|
|
function fill(array, value, start, end) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
|
|
start = 0;
|
|
end = length;
|
|
}
|
|
return baseFill(array, value, start, end);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.find` except that it returns the index of the first
|
|
* element `predicate` returns truthy for instead of the element itself.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @param {number} [fromIndex=0] The index to search from.
|
|
* @returns {number} Returns the index of the found element, else `-1`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': false },
|
|
* { 'user': 'fred', 'active': false },
|
|
* { 'user': 'pebbles', 'active': true }
|
|
* ];
|
|
*
|
|
* _.findIndex(users, function(o) { return o.user == 'barney'; });
|
|
* // => 0
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.findIndex(users, { 'user': 'fred', 'active': false });
|
|
* // => 1
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.findIndex(users, ['active', false]);
|
|
* // => 0
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.findIndex(users, 'active');
|
|
* // => 2
|
|
*/
|
|
function findIndex(array, predicate, fromIndex) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return -1;
|
|
}
|
|
var index = fromIndex == null ? 0 : toInteger(fromIndex);
|
|
if (index < 0) {
|
|
index = nativeMax(length + index, 0);
|
|
}
|
|
return baseFindIndex(array, getIteratee(predicate, 3), index);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.findIndex` except that it iterates over elements
|
|
* of `collection` from right to left.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @param {number} [fromIndex=array.length-1] The index to search from.
|
|
* @returns {number} Returns the index of the found element, else `-1`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': true },
|
|
* { 'user': 'fred', 'active': false },
|
|
* { 'user': 'pebbles', 'active': false }
|
|
* ];
|
|
*
|
|
* _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
|
|
* // => 2
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.findLastIndex(users, { 'user': 'barney', 'active': true });
|
|
* // => 0
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.findLastIndex(users, ['active', false]);
|
|
* // => 2
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.findLastIndex(users, 'active');
|
|
* // => 0
|
|
*/
|
|
function findLastIndex(array, predicate, fromIndex) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return -1;
|
|
}
|
|
var index = length - 1;
|
|
if (fromIndex !== undefined) {
|
|
index = toInteger(fromIndex);
|
|
index = fromIndex < 0
|
|
? nativeMax(length + index, 0)
|
|
: nativeMin(index, length - 1);
|
|
}
|
|
return baseFindIndex(array, getIteratee(predicate, 3), index, true);
|
|
}
|
|
|
|
/**
|
|
* Flattens `array` a single level deep.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to flatten.
|
|
* @returns {Array} Returns the new flattened array.
|
|
* @example
|
|
*
|
|
* _.flatten([1, [2, [3, [4]], 5]]);
|
|
* // => [1, 2, [3, [4]], 5]
|
|
*/
|
|
function flatten(array) {
|
|
var length = array == null ? 0 : array.length;
|
|
return length ? baseFlatten(array, 1) : [];
|
|
}
|
|
|
|
/**
|
|
* Recursively flattens `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to flatten.
|
|
* @returns {Array} Returns the new flattened array.
|
|
* @example
|
|
*
|
|
* _.flattenDeep([1, [2, [3, [4]], 5]]);
|
|
* // => [1, 2, 3, 4, 5]
|
|
*/
|
|
function flattenDeep(array) {
|
|
var length = array == null ? 0 : array.length;
|
|
return length ? baseFlatten(array, INFINITY) : [];
|
|
}
|
|
|
|
/**
|
|
* Recursively flatten `array` up to `depth` times.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.4.0
|
|
* @category Array
|
|
* @param {Array} array The array to flatten.
|
|
* @param {number} [depth=1] The maximum recursion depth.
|
|
* @returns {Array} Returns the new flattened array.
|
|
* @example
|
|
*
|
|
* var array = [1, [2, [3, [4]], 5]];
|
|
*
|
|
* _.flattenDepth(array, 1);
|
|
* // => [1, 2, [3, [4]], 5]
|
|
*
|
|
* _.flattenDepth(array, 2);
|
|
* // => [1, 2, 3, [4], 5]
|
|
*/
|
|
function flattenDepth(array, depth) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
depth = depth === undefined ? 1 : toInteger(depth);
|
|
return baseFlatten(array, depth);
|
|
}
|
|
|
|
/**
|
|
* The inverse of `_.toPairs`; this method returns an object composed
|
|
* from key-value `pairs`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} pairs The key-value pairs.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* _.fromPairs([['a', 1], ['b', 2]]);
|
|
* // => { 'a': 1, 'b': 2 }
|
|
*/
|
|
function fromPairs(pairs) {
|
|
var index = -1,
|
|
length = pairs == null ? 0 : pairs.length,
|
|
result = {};
|
|
|
|
while (++index < length) {
|
|
var pair = pairs[index];
|
|
result[pair[0]] = pair[1];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the first element of `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @alias first
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @returns {*} Returns the first element of `array`.
|
|
* @example
|
|
*
|
|
* _.head([1, 2, 3]);
|
|
* // => 1
|
|
*
|
|
* _.head([]);
|
|
* // => undefined
|
|
*/
|
|
function head(array) {
|
|
return (array && array.length) ? array[0] : undefined;
|
|
}
|
|
|
|
/**
|
|
* Gets the index at which the first occurrence of `value` is found in `array`
|
|
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons. If `fromIndex` is negative, it's used as the
|
|
* offset from the end of `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} value The value to search for.
|
|
* @param {number} [fromIndex=0] The index to search from.
|
|
* @returns {number} Returns the index of the matched value, else `-1`.
|
|
* @example
|
|
*
|
|
* _.indexOf([1, 2, 1, 2], 2);
|
|
* // => 1
|
|
*
|
|
* // Search from the `fromIndex`.
|
|
* _.indexOf([1, 2, 1, 2], 2, 2);
|
|
* // => 3
|
|
*/
|
|
function indexOf(array, value, fromIndex) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return -1;
|
|
}
|
|
var index = fromIndex == null ? 0 : toInteger(fromIndex);
|
|
if (index < 0) {
|
|
index = nativeMax(length + index, 0);
|
|
}
|
|
return baseIndexOf(array, value, index);
|
|
}
|
|
|
|
/**
|
|
* Gets all but the last element of `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* _.initial([1, 2, 3]);
|
|
* // => [1, 2]
|
|
*/
|
|
function initial(array) {
|
|
var length = array == null ? 0 : array.length;
|
|
return length ? baseSlice(array, 0, -1) : [];
|
|
}
|
|
|
|
/**
|
|
* Creates an array of unique values that are included in all given arrays
|
|
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons. The order and references of result values are
|
|
* determined by the first array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @returns {Array} Returns the new array of intersecting values.
|
|
* @example
|
|
*
|
|
* _.intersection([2, 1], [2, 3]);
|
|
* // => [2]
|
|
*/
|
|
var intersection = baseRest(function(arrays) {
|
|
var mapped = arrayMap(arrays, castArrayLikeObject);
|
|
return (mapped.length && mapped[0] === arrays[0])
|
|
? baseIntersection(mapped)
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.intersection` except that it accepts `iteratee`
|
|
* which is invoked for each element of each `arrays` to generate the criterion
|
|
* by which they're compared. The order and references of result values are
|
|
* determined by the first array. The iteratee is invoked with one argument:
|
|
* (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new array of intersecting values.
|
|
* @example
|
|
*
|
|
* _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
|
|
* // => [2.1]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
|
|
* // => [{ 'x': 1 }]
|
|
*/
|
|
var intersectionBy = baseRest(function(arrays) {
|
|
var iteratee = last(arrays),
|
|
mapped = arrayMap(arrays, castArrayLikeObject);
|
|
|
|
if (iteratee === last(mapped)) {
|
|
iteratee = undefined;
|
|
} else {
|
|
mapped.pop();
|
|
}
|
|
return (mapped.length && mapped[0] === arrays[0])
|
|
? baseIntersection(mapped, getIteratee(iteratee, 2))
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.intersection` except that it accepts `comparator`
|
|
* which is invoked to compare elements of `arrays`. The order and references
|
|
* of result values are determined by the first array. The comparator is
|
|
* invoked with two arguments: (arrVal, othVal).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of intersecting values.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
|
|
* var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
|
|
*
|
|
* _.intersectionWith(objects, others, _.isEqual);
|
|
* // => [{ 'x': 1, 'y': 2 }]
|
|
*/
|
|
var intersectionWith = baseRest(function(arrays) {
|
|
var comparator = last(arrays),
|
|
mapped = arrayMap(arrays, castArrayLikeObject);
|
|
|
|
comparator = typeof comparator == 'function' ? comparator : undefined;
|
|
if (comparator) {
|
|
mapped.pop();
|
|
}
|
|
return (mapped.length && mapped[0] === arrays[0])
|
|
? baseIntersection(mapped, undefined, comparator)
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* Converts all elements in `array` into a string separated by `separator`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to convert.
|
|
* @param {string} [separator=','] The element separator.
|
|
* @returns {string} Returns the joined string.
|
|
* @example
|
|
*
|
|
* _.join(['a', 'b', 'c'], '~');
|
|
* // => 'a~b~c'
|
|
*/
|
|
function join(array, separator) {
|
|
return array == null ? '' : nativeJoin.call(array, separator);
|
|
}
|
|
|
|
/**
|
|
* Gets the last element of `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @returns {*} Returns the last element of `array`.
|
|
* @example
|
|
*
|
|
* _.last([1, 2, 3]);
|
|
* // => 3
|
|
*/
|
|
function last(array) {
|
|
var length = array == null ? 0 : array.length;
|
|
return length ? array[length - 1] : undefined;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.indexOf` except that it iterates over elements of
|
|
* `array` from right to left.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} value The value to search for.
|
|
* @param {number} [fromIndex=array.length-1] The index to search from.
|
|
* @returns {number} Returns the index of the matched value, else `-1`.
|
|
* @example
|
|
*
|
|
* _.lastIndexOf([1, 2, 1, 2], 2);
|
|
* // => 3
|
|
*
|
|
* // Search from the `fromIndex`.
|
|
* _.lastIndexOf([1, 2, 1, 2], 2, 2);
|
|
* // => 1
|
|
*/
|
|
function lastIndexOf(array, value, fromIndex) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return -1;
|
|
}
|
|
var index = length;
|
|
if (fromIndex !== undefined) {
|
|
index = toInteger(fromIndex);
|
|
index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
|
|
}
|
|
return value === value
|
|
? strictLastIndexOf(array, value, index)
|
|
: baseFindIndex(array, baseIsNaN, index, true);
|
|
}
|
|
|
|
/**
|
|
* Gets the element at index `n` of `array`. If `n` is negative, the nth
|
|
* element from the end is returned.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.11.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {number} [n=0] The index of the element to return.
|
|
* @returns {*} Returns the nth element of `array`.
|
|
* @example
|
|
*
|
|
* var array = ['a', 'b', 'c', 'd'];
|
|
*
|
|
* _.nth(array, 1);
|
|
* // => 'b'
|
|
*
|
|
* _.nth(array, -2);
|
|
* // => 'c';
|
|
*/
|
|
function nth(array, n) {
|
|
return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
|
|
}
|
|
|
|
/**
|
|
* Removes all given values from `array` using
|
|
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons.
|
|
*
|
|
* **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
|
|
* to remove elements from an array by predicate.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @param {...*} [values] The values to remove.
|
|
* @returns {Array} Returns `array`.
|
|
* @example
|
|
*
|
|
* var array = ['a', 'b', 'c', 'a', 'b', 'c'];
|
|
*
|
|
* _.pull(array, 'a', 'c');
|
|
* console.log(array);
|
|
* // => ['b', 'b']
|
|
*/
|
|
var pull = baseRest(pullAll);
|
|
|
|
/**
|
|
* This method is like `_.pull` except that it accepts an array of values to remove.
|
|
*
|
|
* **Note:** Unlike `_.difference`, this method mutates `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @param {Array} values The values to remove.
|
|
* @returns {Array} Returns `array`.
|
|
* @example
|
|
*
|
|
* var array = ['a', 'b', 'c', 'a', 'b', 'c'];
|
|
*
|
|
* _.pullAll(array, ['a', 'c']);
|
|
* console.log(array);
|
|
* // => ['b', 'b']
|
|
*/
|
|
function pullAll(array, values) {
|
|
return (array && array.length && values && values.length)
|
|
? basePullAll(array, values)
|
|
: array;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.pullAll` except that it accepts `iteratee` which is
|
|
* invoked for each element of `array` and `values` to generate the criterion
|
|
* by which they're compared. The iteratee is invoked with one argument: (value).
|
|
*
|
|
* **Note:** Unlike `_.differenceBy`, this method mutates `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @param {Array} values The values to remove.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Array} Returns `array`.
|
|
* @example
|
|
*
|
|
* var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
|
|
*
|
|
* _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
|
|
* console.log(array);
|
|
* // => [{ 'x': 2 }]
|
|
*/
|
|
function pullAllBy(array, values, iteratee) {
|
|
return (array && array.length && values && values.length)
|
|
? basePullAll(array, values, getIteratee(iteratee, 2))
|
|
: array;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.pullAll` except that it accepts `comparator` which
|
|
* is invoked to compare elements of `array` to `values`. The comparator is
|
|
* invoked with two arguments: (arrVal, othVal).
|
|
*
|
|
* **Note:** Unlike `_.differenceWith`, this method mutates `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.6.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @param {Array} values The values to remove.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns `array`.
|
|
* @example
|
|
*
|
|
* var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
|
|
*
|
|
* _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
|
|
* console.log(array);
|
|
* // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
|
|
*/
|
|
function pullAllWith(array, values, comparator) {
|
|
return (array && array.length && values && values.length)
|
|
? basePullAll(array, values, undefined, comparator)
|
|
: array;
|
|
}
|
|
|
|
/**
|
|
* Removes elements from `array` corresponding to `indexes` and returns an
|
|
* array of removed elements.
|
|
*
|
|
* **Note:** Unlike `_.at`, this method mutates `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @param {...(number|number[])} [indexes] The indexes of elements to remove.
|
|
* @returns {Array} Returns the new array of removed elements.
|
|
* @example
|
|
*
|
|
* var array = ['a', 'b', 'c', 'd'];
|
|
* var pulled = _.pullAt(array, [1, 3]);
|
|
*
|
|
* console.log(array);
|
|
* // => ['a', 'c']
|
|
*
|
|
* console.log(pulled);
|
|
* // => ['b', 'd']
|
|
*/
|
|
var pullAt = flatRest(function(array, indexes) {
|
|
var length = array == null ? 0 : array.length,
|
|
result = baseAt(array, indexes);
|
|
|
|
basePullAt(array, arrayMap(indexes, function(index) {
|
|
return isIndex(index, length) ? +index : index;
|
|
}).sort(compareAscending));
|
|
|
|
return result;
|
|
});
|
|
|
|
/**
|
|
* Removes all elements from `array` that `predicate` returns truthy for
|
|
* and returns an array of the removed elements. The predicate is invoked
|
|
* with three arguments: (value, index, array).
|
|
*
|
|
* **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
|
|
* to pull elements from an array by value.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the new array of removed elements.
|
|
* @example
|
|
*
|
|
* var array = [1, 2, 3, 4];
|
|
* var evens = _.remove(array, function(n) {
|
|
* return n % 2 == 0;
|
|
* });
|
|
*
|
|
* console.log(array);
|
|
* // => [1, 3]
|
|
*
|
|
* console.log(evens);
|
|
* // => [2, 4]
|
|
*/
|
|
function remove(array, predicate) {
|
|
var result = [];
|
|
if (!(array && array.length)) {
|
|
return result;
|
|
}
|
|
var index = -1,
|
|
indexes = [],
|
|
length = array.length;
|
|
|
|
predicate = getIteratee(predicate, 3);
|
|
while (++index < length) {
|
|
var value = array[index];
|
|
if (predicate(value, index, array)) {
|
|
result.push(value);
|
|
indexes.push(index);
|
|
}
|
|
}
|
|
basePullAt(array, indexes);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reverses `array` so that the first element becomes the last, the second
|
|
* element becomes the second to last, and so on.
|
|
*
|
|
* **Note:** This method mutates `array` and is based on
|
|
* [`Array#reverse`](https://mdn.io/Array/reverse).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to modify.
|
|
* @returns {Array} Returns `array`.
|
|
* @example
|
|
*
|
|
* var array = [1, 2, 3];
|
|
*
|
|
* _.reverse(array);
|
|
* // => [3, 2, 1]
|
|
*
|
|
* console.log(array);
|
|
* // => [3, 2, 1]
|
|
*/
|
|
function reverse(array) {
|
|
return array == null ? array : nativeReverse.call(array);
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` from `start` up to, but not including, `end`.
|
|
*
|
|
* **Note:** This method is used instead of
|
|
* [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
|
|
* returned.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to slice.
|
|
* @param {number} [start=0] The start position.
|
|
* @param {number} [end=array.length] The end position.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
*/
|
|
function slice(array, start, end) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
|
|
start = 0;
|
|
end = length;
|
|
}
|
|
else {
|
|
start = start == null ? 0 : toInteger(start);
|
|
end = end === undefined ? length : toInteger(end);
|
|
}
|
|
return baseSlice(array, start, end);
|
|
}
|
|
|
|
/**
|
|
* Uses a binary search to determine the lowest index at which `value`
|
|
* should be inserted into `array` in order to maintain its sort order.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The sorted array to inspect.
|
|
* @param {*} value The value to evaluate.
|
|
* @returns {number} Returns the index at which `value` should be inserted
|
|
* into `array`.
|
|
* @example
|
|
*
|
|
* _.sortedIndex([30, 50], 40);
|
|
* // => 1
|
|
*/
|
|
function sortedIndex(array, value) {
|
|
return baseSortedIndex(array, value);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.sortedIndex` except that it accepts `iteratee`
|
|
* which is invoked for `value` and each element of `array` to compute their
|
|
* sort ranking. The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The sorted array to inspect.
|
|
* @param {*} value The value to evaluate.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {number} Returns the index at which `value` should be inserted
|
|
* into `array`.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 4 }, { 'x': 5 }];
|
|
*
|
|
* _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
|
|
* // => 0
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.sortedIndexBy(objects, { 'x': 4 }, 'x');
|
|
* // => 0
|
|
*/
|
|
function sortedIndexBy(array, value, iteratee) {
|
|
return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.indexOf` except that it performs a binary
|
|
* search on a sorted `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} value The value to search for.
|
|
* @returns {number} Returns the index of the matched value, else `-1`.
|
|
* @example
|
|
*
|
|
* _.sortedIndexOf([4, 5, 5, 5, 6], 5);
|
|
* // => 1
|
|
*/
|
|
function sortedIndexOf(array, value) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (length) {
|
|
var index = baseSortedIndex(array, value);
|
|
if (index < length && eq(array[index], value)) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.sortedIndex` except that it returns the highest
|
|
* index at which `value` should be inserted into `array` in order to
|
|
* maintain its sort order.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The sorted array to inspect.
|
|
* @param {*} value The value to evaluate.
|
|
* @returns {number} Returns the index at which `value` should be inserted
|
|
* into `array`.
|
|
* @example
|
|
*
|
|
* _.sortedLastIndex([4, 5, 5, 5, 6], 5);
|
|
* // => 4
|
|
*/
|
|
function sortedLastIndex(array, value) {
|
|
return baseSortedIndex(array, value, true);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.sortedLastIndex` except that it accepts `iteratee`
|
|
* which is invoked for `value` and each element of `array` to compute their
|
|
* sort ranking. The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The sorted array to inspect.
|
|
* @param {*} value The value to evaluate.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {number} Returns the index at which `value` should be inserted
|
|
* into `array`.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 4 }, { 'x': 5 }];
|
|
*
|
|
* _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
|
|
* // => 1
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
|
|
* // => 1
|
|
*/
|
|
function sortedLastIndexBy(array, value, iteratee) {
|
|
return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.lastIndexOf` except that it performs a binary
|
|
* search on a sorted `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {*} value The value to search for.
|
|
* @returns {number} Returns the index of the matched value, else `-1`.
|
|
* @example
|
|
*
|
|
* _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
|
|
* // => 3
|
|
*/
|
|
function sortedLastIndexOf(array, value) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (length) {
|
|
var index = baseSortedIndex(array, value, true) - 1;
|
|
if (eq(array[index], value)) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.uniq` except that it's designed and optimized
|
|
* for sorted arrays.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
* @example
|
|
*
|
|
* _.sortedUniq([1, 1, 2]);
|
|
* // => [1, 2]
|
|
*/
|
|
function sortedUniq(array) {
|
|
return (array && array.length)
|
|
? baseSortedUniq(array)
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.uniqBy` except that it's designed and optimized
|
|
* for sorted arrays.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [iteratee] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
* @example
|
|
*
|
|
* _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
|
|
* // => [1.1, 2.3]
|
|
*/
|
|
function sortedUniqBy(array, iteratee) {
|
|
return (array && array.length)
|
|
? baseSortedUniq(array, getIteratee(iteratee, 2))
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* Gets all but the first element of `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* _.tail([1, 2, 3]);
|
|
* // => [2, 3]
|
|
*/
|
|
function tail(array) {
|
|
var length = array == null ? 0 : array.length;
|
|
return length ? baseSlice(array, 1, length) : [];
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` with `n` elements taken from the beginning.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {number} [n=1] The number of elements to take.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* _.take([1, 2, 3]);
|
|
* // => [1]
|
|
*
|
|
* _.take([1, 2, 3], 2);
|
|
* // => [1, 2]
|
|
*
|
|
* _.take([1, 2, 3], 5);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* _.take([1, 2, 3], 0);
|
|
* // => []
|
|
*/
|
|
function take(array, n, guard) {
|
|
if (!(array && array.length)) {
|
|
return [];
|
|
}
|
|
n = (guard || n === undefined) ? 1 : toInteger(n);
|
|
return baseSlice(array, 0, n < 0 ? 0 : n);
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` with `n` elements taken from the end.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {number} [n=1] The number of elements to take.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* _.takeRight([1, 2, 3]);
|
|
* // => [3]
|
|
*
|
|
* _.takeRight([1, 2, 3], 2);
|
|
* // => [2, 3]
|
|
*
|
|
* _.takeRight([1, 2, 3], 5);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* _.takeRight([1, 2, 3], 0);
|
|
* // => []
|
|
*/
|
|
function takeRight(array, n, guard) {
|
|
var length = array == null ? 0 : array.length;
|
|
if (!length) {
|
|
return [];
|
|
}
|
|
n = (guard || n === undefined) ? 1 : toInteger(n);
|
|
n = length - n;
|
|
return baseSlice(array, n < 0 ? 0 : n, length);
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` with elements taken from the end. Elements are
|
|
* taken until `predicate` returns falsey. The predicate is invoked with
|
|
* three arguments: (value, index, array).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': true },
|
|
* { 'user': 'fred', 'active': false },
|
|
* { 'user': 'pebbles', 'active': false }
|
|
* ];
|
|
*
|
|
* _.takeRightWhile(users, function(o) { return !o.active; });
|
|
* // => objects for ['fred', 'pebbles']
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
|
|
* // => objects for ['pebbles']
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.takeRightWhile(users, ['active', false]);
|
|
* // => objects for ['fred', 'pebbles']
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.takeRightWhile(users, 'active');
|
|
* // => []
|
|
*/
|
|
function takeRightWhile(array, predicate) {
|
|
return (array && array.length)
|
|
? baseWhile(array, getIteratee(predicate, 3), false, true)
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* Creates a slice of `array` with elements taken from the beginning. Elements
|
|
* are taken until `predicate` returns falsey. The predicate is invoked with
|
|
* three arguments: (value, index, array).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to query.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the slice of `array`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': false },
|
|
* { 'user': 'fred', 'active': false },
|
|
* { 'user': 'pebbles', 'active': true }
|
|
* ];
|
|
*
|
|
* _.takeWhile(users, function(o) { return !o.active; });
|
|
* // => objects for ['barney', 'fred']
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.takeWhile(users, { 'user': 'barney', 'active': false });
|
|
* // => objects for ['barney']
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.takeWhile(users, ['active', false]);
|
|
* // => objects for ['barney', 'fred']
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.takeWhile(users, 'active');
|
|
* // => []
|
|
*/
|
|
function takeWhile(array, predicate) {
|
|
return (array && array.length)
|
|
? baseWhile(array, getIteratee(predicate, 3))
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* Creates an array of unique values, in order, from all given arrays using
|
|
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @returns {Array} Returns the new array of combined values.
|
|
* @example
|
|
*
|
|
* _.union([2], [1, 2]);
|
|
* // => [2, 1]
|
|
*/
|
|
var union = baseRest(function(arrays) {
|
|
return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.union` except that it accepts `iteratee` which is
|
|
* invoked for each element of each `arrays` to generate the criterion by
|
|
* which uniqueness is computed. Result values are chosen from the first
|
|
* array in which the value occurs. The iteratee is invoked with one argument:
|
|
* (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new array of combined values.
|
|
* @example
|
|
*
|
|
* _.unionBy([2.1], [1.2, 2.3], Math.floor);
|
|
* // => [2.1, 1.2]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
|
|
* // => [{ 'x': 1 }, { 'x': 2 }]
|
|
*/
|
|
var unionBy = baseRest(function(arrays) {
|
|
var iteratee = last(arrays);
|
|
if (isArrayLikeObject(iteratee)) {
|
|
iteratee = undefined;
|
|
}
|
|
return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.union` except that it accepts `comparator` which
|
|
* is invoked to compare elements of `arrays`. Result values are chosen from
|
|
* the first array in which the value occurs. The comparator is invoked
|
|
* with two arguments: (arrVal, othVal).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of combined values.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
|
|
* var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
|
|
*
|
|
* _.unionWith(objects, others, _.isEqual);
|
|
* // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
|
|
*/
|
|
var unionWith = baseRest(function(arrays) {
|
|
var comparator = last(arrays);
|
|
comparator = typeof comparator == 'function' ? comparator : undefined;
|
|
return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
|
|
});
|
|
|
|
/**
|
|
* Creates a duplicate-free version of an array, using
|
|
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons, in which only the first occurrence of each element
|
|
* is kept. The order of result values is determined by the order they occur
|
|
* in the array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
* @example
|
|
*
|
|
* _.uniq([2, 1, 2]);
|
|
* // => [2, 1]
|
|
*/
|
|
function uniq(array) {
|
|
return (array && array.length) ? baseUniq(array) : [];
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.uniq` except that it accepts `iteratee` which is
|
|
* invoked for each element in `array` to generate the criterion by which
|
|
* uniqueness is computed. The order of result values is determined by the
|
|
* order they occur in the array. The iteratee is invoked with one argument:
|
|
* (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
* @example
|
|
*
|
|
* _.uniqBy([2.1, 1.2, 2.3], Math.floor);
|
|
* // => [2.1, 1.2]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
|
|
* // => [{ 'x': 1 }, { 'x': 2 }]
|
|
*/
|
|
function uniqBy(array, iteratee) {
|
|
return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : [];
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.uniq` except that it accepts `comparator` which
|
|
* is invoked to compare elements of `array`. The order of result values is
|
|
* determined by the order they occur in the array.The comparator is invoked
|
|
* with two arguments: (arrVal, othVal).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new duplicate free array.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
|
|
*
|
|
* _.uniqWith(objects, _.isEqual);
|
|
* // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
|
|
*/
|
|
function uniqWith(array, comparator) {
|
|
comparator = typeof comparator == 'function' ? comparator : undefined;
|
|
return (array && array.length) ? baseUniq(array, undefined, comparator) : [];
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.zip` except that it accepts an array of grouped
|
|
* elements and creates an array regrouping the elements to their pre-zip
|
|
* configuration.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.2.0
|
|
* @category Array
|
|
* @param {Array} array The array of grouped elements to process.
|
|
* @returns {Array} Returns the new array of regrouped elements.
|
|
* @example
|
|
*
|
|
* var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
|
|
* // => [['a', 1, true], ['b', 2, false]]
|
|
*
|
|
* _.unzip(zipped);
|
|
* // => [['a', 'b'], [1, 2], [true, false]]
|
|
*/
|
|
function unzip(array) {
|
|
if (!(array && array.length)) {
|
|
return [];
|
|
}
|
|
var length = 0;
|
|
array = arrayFilter(array, function(group) {
|
|
if (isArrayLikeObject(group)) {
|
|
length = nativeMax(group.length, length);
|
|
return true;
|
|
}
|
|
});
|
|
return baseTimes(length, function(index) {
|
|
return arrayMap(array, baseProperty(index));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.unzip` except that it accepts `iteratee` to specify
|
|
* how regrouped values should be combined. The iteratee is invoked with the
|
|
* elements of each group: (...group).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.8.0
|
|
* @category Array
|
|
* @param {Array} array The array of grouped elements to process.
|
|
* @param {Function} [iteratee=_.identity] The function to combine
|
|
* regrouped values.
|
|
* @returns {Array} Returns the new array of regrouped elements.
|
|
* @example
|
|
*
|
|
* var zipped = _.zip([1, 2], [10, 20], [100, 200]);
|
|
* // => [[1, 10, 100], [2, 20, 200]]
|
|
*
|
|
* _.unzipWith(zipped, _.add);
|
|
* // => [3, 30, 300]
|
|
*/
|
|
function unzipWith(array, iteratee) {
|
|
if (!(array && array.length)) {
|
|
return [];
|
|
}
|
|
var result = unzip(array);
|
|
if (iteratee == null) {
|
|
return result;
|
|
}
|
|
return arrayMap(result, function(group) {
|
|
return apply(iteratee, undefined, group);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates an array excluding all given values using
|
|
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* for equality comparisons.
|
|
*
|
|
* **Note:** Unlike `_.pull`, this method returns a new array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {Array} array The array to inspect.
|
|
* @param {...*} [values] The values to exclude.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @see _.difference, _.xor
|
|
* @example
|
|
*
|
|
* _.without([2, 1, 2, 3], 1, 2);
|
|
* // => [3]
|
|
*/
|
|
var without = baseRest(function(array, values) {
|
|
return isArrayLikeObject(array)
|
|
? baseDifference(array, values)
|
|
: [];
|
|
});
|
|
|
|
/**
|
|
* Creates an array of unique values that is the
|
|
* [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
|
|
* of the given arrays. The order of result values is determined by the order
|
|
* they occur in the arrays.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.4.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @see _.difference, _.without
|
|
* @example
|
|
*
|
|
* _.xor([2, 1], [2, 3]);
|
|
* // => [1, 3]
|
|
*/
|
|
var xor = baseRest(function(arrays) {
|
|
return baseXor(arrayFilter(arrays, isArrayLikeObject));
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.xor` except that it accepts `iteratee` which is
|
|
* invoked for each element of each `arrays` to generate the criterion by
|
|
* which by which they're compared. The order of result values is determined
|
|
* by the order they occur in the arrays. The iteratee is invoked with one
|
|
* argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @example
|
|
*
|
|
* _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
|
|
* // => [1.2, 3.4]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
|
|
* // => [{ 'x': 2 }]
|
|
*/
|
|
var xorBy = baseRest(function(arrays) {
|
|
var iteratee = last(arrays);
|
|
if (isArrayLikeObject(iteratee)) {
|
|
iteratee = undefined;
|
|
}
|
|
return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.xor` except that it accepts `comparator` which is
|
|
* invoked to compare elements of `arrays`. The order of result values is
|
|
* determined by the order they occur in the arrays. The comparator is invoked
|
|
* with two arguments: (arrVal, othVal).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to inspect.
|
|
* @param {Function} [comparator] The comparator invoked per element.
|
|
* @returns {Array} Returns the new array of filtered values.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
|
|
* var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
|
|
*
|
|
* _.xorWith(objects, others, _.isEqual);
|
|
* // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
|
|
*/
|
|
var xorWith = baseRest(function(arrays) {
|
|
var comparator = last(arrays);
|
|
comparator = typeof comparator == 'function' ? comparator : undefined;
|
|
return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
|
|
});
|
|
|
|
/**
|
|
* Creates an array of grouped elements, the first of which contains the
|
|
* first elements of the given arrays, the second of which contains the
|
|
* second elements of the given arrays, and so on.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to process.
|
|
* @returns {Array} Returns the new array of grouped elements.
|
|
* @example
|
|
*
|
|
* _.zip(['a', 'b'], [1, 2], [true, false]);
|
|
* // => [['a', 1, true], ['b', 2, false]]
|
|
*/
|
|
var zip = baseRest(unzip);
|
|
|
|
/**
|
|
* This method is like `_.fromPairs` except that it accepts two arrays,
|
|
* one of property identifiers and one of corresponding values.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.4.0
|
|
* @category Array
|
|
* @param {Array} [props=[]] The property identifiers.
|
|
* @param {Array} [values=[]] The property values.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* _.zipObject(['a', 'b'], [1, 2]);
|
|
* // => { 'a': 1, 'b': 2 }
|
|
*/
|
|
function zipObject(props, values) {
|
|
return baseZipObject(props || [], values || [], assignValue);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.zipObject` except that it supports property paths.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.1.0
|
|
* @category Array
|
|
* @param {Array} [props=[]] The property identifiers.
|
|
* @param {Array} [values=[]] The property values.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
|
|
* // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
|
|
*/
|
|
function zipObjectDeep(props, values) {
|
|
return baseZipObject(props || [], values || [], baseSet);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.zip` except that it accepts `iteratee` to specify
|
|
* how grouped values should be combined. The iteratee is invoked with the
|
|
* elements of each group: (...group).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.8.0
|
|
* @category Array
|
|
* @param {...Array} [arrays] The arrays to process.
|
|
* @param {Function} [iteratee=_.identity] The function to combine
|
|
* grouped values.
|
|
* @returns {Array} Returns the new array of grouped elements.
|
|
* @example
|
|
*
|
|
* _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
|
|
* return a + b + c;
|
|
* });
|
|
* // => [111, 222]
|
|
*/
|
|
var zipWith = baseRest(function(arrays) {
|
|
var length = arrays.length,
|
|
iteratee = length > 1 ? arrays[length - 1] : undefined;
|
|
|
|
iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
|
|
return unzipWith(arrays, iteratee);
|
|
});
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates a `lodash` wrapper instance that wraps `value` with explicit method
|
|
* chain sequences enabled. The result of such sequences must be unwrapped
|
|
* with `_#value`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.3.0
|
|
* @category Seq
|
|
* @param {*} value The value to wrap.
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36 },
|
|
* { 'user': 'fred', 'age': 40 },
|
|
* { 'user': 'pebbles', 'age': 1 }
|
|
* ];
|
|
*
|
|
* var youngest = _
|
|
* .chain(users)
|
|
* .sortBy('age')
|
|
* .map(function(o) {
|
|
* return o.user + ' is ' + o.age;
|
|
* })
|
|
* .head()
|
|
* .value();
|
|
* // => 'pebbles is 1'
|
|
*/
|
|
function chain(value) {
|
|
var result = lodash(value);
|
|
result.__chain__ = true;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method invokes `interceptor` and returns `value`. The interceptor
|
|
* is invoked with one argument; (value). The purpose of this method is to
|
|
* "tap into" a method chain sequence in order to modify intermediate results.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Seq
|
|
* @param {*} value The value to provide to `interceptor`.
|
|
* @param {Function} interceptor The function to invoke.
|
|
* @returns {*} Returns `value`.
|
|
* @example
|
|
*
|
|
* _([1, 2, 3])
|
|
* .tap(function(array) {
|
|
* // Mutate input array.
|
|
* array.pop();
|
|
* })
|
|
* .reverse()
|
|
* .value();
|
|
* // => [2, 1]
|
|
*/
|
|
function tap(value, interceptor) {
|
|
interceptor(value);
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.tap` except that it returns the result of `interceptor`.
|
|
* The purpose of this method is to "pass thru" values replacing intermediate
|
|
* results in a method chain sequence.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Seq
|
|
* @param {*} value The value to provide to `interceptor`.
|
|
* @param {Function} interceptor The function to invoke.
|
|
* @returns {*} Returns the result of `interceptor`.
|
|
* @example
|
|
*
|
|
* _(' abc ')
|
|
* .chain()
|
|
* .trim()
|
|
* .thru(function(value) {
|
|
* return [value];
|
|
* })
|
|
* .value();
|
|
* // => ['abc']
|
|
*/
|
|
function thru(value, interceptor) {
|
|
return interceptor(value);
|
|
}
|
|
|
|
/**
|
|
* This method is the wrapper version of `_.at`.
|
|
*
|
|
* @name at
|
|
* @memberOf _
|
|
* @since 1.0.0
|
|
* @category Seq
|
|
* @param {...(string|string[])} [paths] The property paths to pick.
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
|
|
*
|
|
* _(object).at(['a[0].b.c', 'a[1]']).value();
|
|
* // => [3, 4]
|
|
*/
|
|
var wrapperAt = flatRest(function(paths) {
|
|
var length = paths.length,
|
|
start = length ? paths[0] : 0,
|
|
value = this.__wrapped__,
|
|
interceptor = function(object) { return baseAt(object, paths); };
|
|
|
|
if (length > 1 || this.__actions__.length ||
|
|
!(value instanceof LazyWrapper) || !isIndex(start)) {
|
|
return this.thru(interceptor);
|
|
}
|
|
value = value.slice(start, +start + (length ? 1 : 0));
|
|
value.__actions__.push({
|
|
'func': thru,
|
|
'args': [interceptor],
|
|
'thisArg': undefined
|
|
});
|
|
return new LodashWrapper(value, this.__chain__).thru(function(array) {
|
|
if (length && !array.length) {
|
|
array.push(undefined);
|
|
}
|
|
return array;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
|
|
*
|
|
* @name chain
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Seq
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36 },
|
|
* { 'user': 'fred', 'age': 40 }
|
|
* ];
|
|
*
|
|
* // A sequence without explicit chaining.
|
|
* _(users).head();
|
|
* // => { 'user': 'barney', 'age': 36 }
|
|
*
|
|
* // A sequence with explicit chaining.
|
|
* _(users)
|
|
* .chain()
|
|
* .head()
|
|
* .pick('user')
|
|
* .value();
|
|
* // => { 'user': 'barney' }
|
|
*/
|
|
function wrapperChain() {
|
|
return chain(this);
|
|
}
|
|
|
|
/**
|
|
* Executes the chain sequence and returns the wrapped result.
|
|
*
|
|
* @name commit
|
|
* @memberOf _
|
|
* @since 3.2.0
|
|
* @category Seq
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* var array = [1, 2];
|
|
* var wrapped = _(array).push(3);
|
|
*
|
|
* console.log(array);
|
|
* // => [1, 2]
|
|
*
|
|
* wrapped = wrapped.commit();
|
|
* console.log(array);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* wrapped.last();
|
|
* // => 3
|
|
*
|
|
* console.log(array);
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function wrapperCommit() {
|
|
return new LodashWrapper(this.value(), this.__chain__);
|
|
}
|
|
|
|
/**
|
|
* Gets the next value on a wrapped object following the
|
|
* [iterator protocol](https://mdn.io/iteration_protocols#iterator).
|
|
*
|
|
* @name next
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Seq
|
|
* @returns {Object} Returns the next iterator value.
|
|
* @example
|
|
*
|
|
* var wrapped = _([1, 2]);
|
|
*
|
|
* wrapped.next();
|
|
* // => { 'done': false, 'value': 1 }
|
|
*
|
|
* wrapped.next();
|
|
* // => { 'done': false, 'value': 2 }
|
|
*
|
|
* wrapped.next();
|
|
* // => { 'done': true, 'value': undefined }
|
|
*/
|
|
function wrapperNext() {
|
|
if (this.__values__ === undefined) {
|
|
this.__values__ = toArray(this.value());
|
|
}
|
|
var done = this.__index__ >= this.__values__.length,
|
|
value = done ? undefined : this.__values__[this.__index__++];
|
|
|
|
return { 'done': done, 'value': value };
|
|
}
|
|
|
|
/**
|
|
* Enables the wrapper to be iterable.
|
|
*
|
|
* @name Symbol.iterator
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Seq
|
|
* @returns {Object} Returns the wrapper object.
|
|
* @example
|
|
*
|
|
* var wrapped = _([1, 2]);
|
|
*
|
|
* wrapped[Symbol.iterator]() === wrapped;
|
|
* // => true
|
|
*
|
|
* Array.from(wrapped);
|
|
* // => [1, 2]
|
|
*/
|
|
function wrapperToIterator() {
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a clone of the chain sequence planting `value` as the wrapped value.
|
|
*
|
|
* @name plant
|
|
* @memberOf _
|
|
* @since 3.2.0
|
|
* @category Seq
|
|
* @param {*} value The value to plant.
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* function square(n) {
|
|
* return n * n;
|
|
* }
|
|
*
|
|
* var wrapped = _([1, 2]).map(square);
|
|
* var other = wrapped.plant([3, 4]);
|
|
*
|
|
* other.value();
|
|
* // => [9, 16]
|
|
*
|
|
* wrapped.value();
|
|
* // => [1, 4]
|
|
*/
|
|
function wrapperPlant(value) {
|
|
var result,
|
|
parent = this;
|
|
|
|
while (parent instanceof baseLodash) {
|
|
var clone = wrapperClone(parent);
|
|
clone.__index__ = 0;
|
|
clone.__values__ = undefined;
|
|
if (result) {
|
|
previous.__wrapped__ = clone;
|
|
} else {
|
|
result = clone;
|
|
}
|
|
var previous = clone;
|
|
parent = parent.__wrapped__;
|
|
}
|
|
previous.__wrapped__ = value;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method is the wrapper version of `_.reverse`.
|
|
*
|
|
* **Note:** This method mutates the wrapped array.
|
|
*
|
|
* @name reverse
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Seq
|
|
* @returns {Object} Returns the new `lodash` wrapper instance.
|
|
* @example
|
|
*
|
|
* var array = [1, 2, 3];
|
|
*
|
|
* _(array).reverse().value()
|
|
* // => [3, 2, 1]
|
|
*
|
|
* console.log(array);
|
|
* // => [3, 2, 1]
|
|
*/
|
|
function wrapperReverse() {
|
|
var value = this.__wrapped__;
|
|
if (value instanceof LazyWrapper) {
|
|
var wrapped = value;
|
|
if (this.__actions__.length) {
|
|
wrapped = new LazyWrapper(this);
|
|
}
|
|
wrapped = wrapped.reverse();
|
|
wrapped.__actions__.push({
|
|
'func': thru,
|
|
'args': [reverse],
|
|
'thisArg': undefined
|
|
});
|
|
return new LodashWrapper(wrapped, this.__chain__);
|
|
}
|
|
return this.thru(reverse);
|
|
}
|
|
|
|
/**
|
|
* Executes the chain sequence to resolve the unwrapped value.
|
|
*
|
|
* @name value
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @alias toJSON, valueOf
|
|
* @category Seq
|
|
* @returns {*} Returns the resolved unwrapped value.
|
|
* @example
|
|
*
|
|
* _([1, 2, 3]).value();
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function wrapperValue() {
|
|
return baseWrapperValue(this.__wrapped__, this.__actions__);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Creates an object composed of keys generated from the results of running
|
|
* each element of `collection` thru `iteratee`. The corresponding value of
|
|
* each key is the number of times the key was returned by `iteratee`. The
|
|
* iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.5.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee to transform keys.
|
|
* @returns {Object} Returns the composed aggregate object.
|
|
* @example
|
|
*
|
|
* _.countBy([6.1, 4.2, 6.3], Math.floor);
|
|
* // => { '4': 1, '6': 2 }
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.countBy(['one', 'two', 'three'], 'length');
|
|
* // => { '3': 2, '5': 1 }
|
|
*/
|
|
var countBy = createAggregator(function(result, value, key) {
|
|
if (hasOwnProperty.call(result, key)) {
|
|
++result[key];
|
|
} else {
|
|
baseAssignValue(result, key, 1);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Checks if `predicate` returns truthy for **all** elements of `collection`.
|
|
* Iteration is stopped once `predicate` returns falsey. The predicate is
|
|
* invoked with three arguments: (value, index|key, collection).
|
|
*
|
|
* **Note:** This method returns `true` for
|
|
* [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
|
|
* [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
|
|
* elements of empty collections.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {boolean} Returns `true` if all elements pass the predicate check,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.every([true, 1, null, 'yes'], Boolean);
|
|
* // => false
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36, 'active': false },
|
|
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
* ];
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.every(users, { 'user': 'barney', 'active': false });
|
|
* // => false
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.every(users, ['active', false]);
|
|
* // => true
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.every(users, 'active');
|
|
* // => false
|
|
*/
|
|
function every(collection, predicate, guard) {
|
|
var func = isArray(collection) ? arrayEvery : baseEvery;
|
|
if (guard && isIterateeCall(collection, predicate, guard)) {
|
|
predicate = undefined;
|
|
}
|
|
return func(collection, getIteratee(predicate, 3));
|
|
}
|
|
|
|
/**
|
|
* Iterates over elements of `collection`, returning an array of all elements
|
|
* `predicate` returns truthy for. The predicate is invoked with three
|
|
* arguments: (value, index|key, collection).
|
|
*
|
|
* **Note:** Unlike `_.remove`, this method returns a new array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the new filtered array.
|
|
* @see _.reject
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
* ];
|
|
*
|
|
* _.filter(users, function(o) { return !o.active; });
|
|
* // => objects for ['fred']
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.filter(users, { 'age': 36, 'active': true });
|
|
* // => objects for ['barney']
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.filter(users, ['active', false]);
|
|
* // => objects for ['fred']
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.filter(users, 'active');
|
|
* // => objects for ['barney']
|
|
*/
|
|
function filter(collection, predicate) {
|
|
var func = isArray(collection) ? arrayFilter : baseFilter;
|
|
return func(collection, getIteratee(predicate, 3));
|
|
}
|
|
|
|
/**
|
|
* Iterates over elements of `collection`, returning the first element
|
|
* `predicate` returns truthy for. The predicate is invoked with three
|
|
* arguments: (value, index|key, collection).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to inspect.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @param {number} [fromIndex=0] The index to search from.
|
|
* @returns {*} Returns the matched element, else `undefined`.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
* { 'user': 'fred', 'age': 40, 'active': false },
|
|
* { 'user': 'pebbles', 'age': 1, 'active': true }
|
|
* ];
|
|
*
|
|
* _.find(users, function(o) { return o.age < 40; });
|
|
* // => object for 'barney'
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.find(users, { 'age': 1, 'active': true });
|
|
* // => object for 'pebbles'
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.find(users, ['active', false]);
|
|
* // => object for 'fred'
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.find(users, 'active');
|
|
* // => object for 'barney'
|
|
*/
|
|
var find = createFind(findIndex);
|
|
|
|
/**
|
|
* This method is like `_.find` except that it iterates over elements of
|
|
* `collection` from right to left.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to inspect.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @param {number} [fromIndex=collection.length-1] The index to search from.
|
|
* @returns {*} Returns the matched element, else `undefined`.
|
|
* @example
|
|
*
|
|
* _.findLast([1, 2, 3, 4], function(n) {
|
|
* return n % 2 == 1;
|
|
* });
|
|
* // => 3
|
|
*/
|
|
var findLast = createFind(findLastIndex);
|
|
|
|
/**
|
|
* Creates a flattened array of values by running each element in `collection`
|
|
* thru `iteratee` and flattening the mapped results. The iteratee is invoked
|
|
* with three arguments: (value, index|key, collection).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the new flattened array.
|
|
* @example
|
|
*
|
|
* function duplicate(n) {
|
|
* return [n, n];
|
|
* }
|
|
*
|
|
* _.flatMap([1, 2], duplicate);
|
|
* // => [1, 1, 2, 2]
|
|
*/
|
|
function flatMap(collection, iteratee) {
|
|
return baseFlatten(map(collection, iteratee), 1);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.flatMap` except that it recursively flattens the
|
|
* mapped results.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.7.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the new flattened array.
|
|
* @example
|
|
*
|
|
* function duplicate(n) {
|
|
* return [[[n, n]]];
|
|
* }
|
|
*
|
|
* _.flatMapDeep([1, 2], duplicate);
|
|
* // => [1, 1, 2, 2]
|
|
*/
|
|
function flatMapDeep(collection, iteratee) {
|
|
return baseFlatten(map(collection, iteratee), INFINITY);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.flatMap` except that it recursively flattens the
|
|
* mapped results up to `depth` times.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.7.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @param {number} [depth=1] The maximum recursion depth.
|
|
* @returns {Array} Returns the new flattened array.
|
|
* @example
|
|
*
|
|
* function duplicate(n) {
|
|
* return [[[n, n]]];
|
|
* }
|
|
*
|
|
* _.flatMapDepth([1, 2], duplicate, 2);
|
|
* // => [[1, 1], [2, 2]]
|
|
*/
|
|
function flatMapDepth(collection, iteratee, depth) {
|
|
depth = depth === undefined ? 1 : toInteger(depth);
|
|
return baseFlatten(map(collection, iteratee), depth);
|
|
}
|
|
|
|
/**
|
|
* Iterates over elements of `collection` and invokes `iteratee` for each element.
|
|
* The iteratee is invoked with three arguments: (value, index|key, collection).
|
|
* Iteratee functions may exit iteration early by explicitly returning `false`.
|
|
*
|
|
* **Note:** As with other "Collections" methods, objects with a "length"
|
|
* property are iterated like arrays. To avoid this behavior use `_.forIn`
|
|
* or `_.forOwn` for object iteration.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @alias each
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Array|Object} Returns `collection`.
|
|
* @see _.forEachRight
|
|
* @example
|
|
*
|
|
* _.forEach([1, 2], function(value) {
|
|
* console.log(value);
|
|
* });
|
|
* // => Logs `1` then `2`.
|
|
*
|
|
* _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
|
|
* console.log(key);
|
|
* });
|
|
* // => Logs 'a' then 'b' (iteration order is not guaranteed).
|
|
*/
|
|
function forEach(collection, iteratee) {
|
|
var func = isArray(collection) ? arrayEach : baseEach;
|
|
return func(collection, getIteratee(iteratee, 3));
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.forEach` except that it iterates over elements of
|
|
* `collection` from right to left.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @alias eachRight
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Array|Object} Returns `collection`.
|
|
* @see _.forEach
|
|
* @example
|
|
*
|
|
* _.forEachRight([1, 2], function(value) {
|
|
* console.log(value);
|
|
* });
|
|
* // => Logs `2` then `1`.
|
|
*/
|
|
function forEachRight(collection, iteratee) {
|
|
var func = isArray(collection) ? arrayEachRight : baseEachRight;
|
|
return func(collection, getIteratee(iteratee, 3));
|
|
}
|
|
|
|
/**
|
|
* Creates an object composed of keys generated from the results of running
|
|
* each element of `collection` thru `iteratee`. The order of grouped values
|
|
* is determined by the order they occur in `collection`. The corresponding
|
|
* value of each key is an array of elements responsible for generating the
|
|
* key. The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee to transform keys.
|
|
* @returns {Object} Returns the composed aggregate object.
|
|
* @example
|
|
*
|
|
* _.groupBy([6.1, 4.2, 6.3], Math.floor);
|
|
* // => { '4': [4.2], '6': [6.1, 6.3] }
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.groupBy(['one', 'two', 'three'], 'length');
|
|
* // => { '3': ['one', 'two'], '5': ['three'] }
|
|
*/
|
|
var groupBy = createAggregator(function(result, value, key) {
|
|
if (hasOwnProperty.call(result, key)) {
|
|
result[key].push(value);
|
|
} else {
|
|
baseAssignValue(result, key, [value]);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Checks if `value` is in `collection`. If `collection` is a string, it's
|
|
* checked for a substring of `value`, otherwise
|
|
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* is used for equality comparisons. If `fromIndex` is negative, it's used as
|
|
* the offset from the end of `collection`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object|string} collection The collection to inspect.
|
|
* @param {*} value The value to search for.
|
|
* @param {number} [fromIndex=0] The index to search from.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
|
|
* @returns {boolean} Returns `true` if `value` is found, else `false`.
|
|
* @example
|
|
*
|
|
* _.includes([1, 2, 3], 1);
|
|
* // => true
|
|
*
|
|
* _.includes([1, 2, 3], 1, 2);
|
|
* // => false
|
|
*
|
|
* _.includes({ 'a': 1, 'b': 2 }, 1);
|
|
* // => true
|
|
*
|
|
* _.includes('abcd', 'bc');
|
|
* // => true
|
|
*/
|
|
function includes(collection, value, fromIndex, guard) {
|
|
collection = isArrayLike(collection) ? collection : values(collection);
|
|
fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
|
|
|
|
var length = collection.length;
|
|
if (fromIndex < 0) {
|
|
fromIndex = nativeMax(length + fromIndex, 0);
|
|
}
|
|
return isString(collection)
|
|
? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
|
|
: (!!length && baseIndexOf(collection, value, fromIndex) > -1);
|
|
}
|
|
|
|
/**
|
|
* Invokes the method at `path` of each element in `collection`, returning
|
|
* an array of the results of each invoked method. Any additional arguments
|
|
* are provided to each invoked method. If `path` is a function, it's invoked
|
|
* for, and `this` bound to, each element in `collection`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Array|Function|string} path The path of the method to invoke or
|
|
* the function invoked per iteration.
|
|
* @param {...*} [args] The arguments to invoke each method with.
|
|
* @returns {Array} Returns the array of results.
|
|
* @example
|
|
*
|
|
* _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
|
|
* // => [[1, 5, 7], [1, 2, 3]]
|
|
*
|
|
* _.invokeMap([123, 456], String.prototype.split, '');
|
|
* // => [['1', '2', '3'], ['4', '5', '6']]
|
|
*/
|
|
var invokeMap = baseRest(function(collection, path, args) {
|
|
var index = -1,
|
|
isFunc = typeof path == 'function',
|
|
result = isArrayLike(collection) ? Array(collection.length) : [];
|
|
|
|
baseEach(collection, function(value) {
|
|
result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
|
|
});
|
|
return result;
|
|
});
|
|
|
|
/**
|
|
* Creates an object composed of keys generated from the results of running
|
|
* each element of `collection` thru `iteratee`. The corresponding value of
|
|
* each key is the last element responsible for generating the key. The
|
|
* iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee to transform keys.
|
|
* @returns {Object} Returns the composed aggregate object.
|
|
* @example
|
|
*
|
|
* var array = [
|
|
* { 'dir': 'left', 'code': 97 },
|
|
* { 'dir': 'right', 'code': 100 }
|
|
* ];
|
|
*
|
|
* _.keyBy(array, function(o) {
|
|
* return String.fromCharCode(o.code);
|
|
* });
|
|
* // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
|
|
*
|
|
* _.keyBy(array, 'dir');
|
|
* // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
|
|
*/
|
|
var keyBy = createAggregator(function(result, value, key) {
|
|
baseAssignValue(result, key, value);
|
|
});
|
|
|
|
/**
|
|
* Creates an array of values by running each element in `collection` thru
|
|
* `iteratee`. The iteratee is invoked with three arguments:
|
|
* (value, index|key, collection).
|
|
*
|
|
* Many lodash methods are guarded to work as iteratees for methods like
|
|
* `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
|
|
*
|
|
* The guarded methods are:
|
|
* `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
|
|
* `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
|
|
* `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
|
|
* `template`, `trim`, `trimEnd`, `trimStart`, and `words`
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the new mapped array.
|
|
* @example
|
|
*
|
|
* function square(n) {
|
|
* return n * n;
|
|
* }
|
|
*
|
|
* _.map([4, 8], square);
|
|
* // => [16, 64]
|
|
*
|
|
* _.map({ 'a': 4, 'b': 8 }, square);
|
|
* // => [16, 64] (iteration order is not guaranteed)
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney' },
|
|
* { 'user': 'fred' }
|
|
* ];
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.map(users, 'user');
|
|
* // => ['barney', 'fred']
|
|
*/
|
|
function map(collection, iteratee) {
|
|
var func = isArray(collection) ? arrayMap : baseMap;
|
|
return func(collection, getIteratee(iteratee, 3));
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.sortBy` except that it allows specifying the sort
|
|
* orders of the iteratees to sort by. If `orders` is unspecified, all values
|
|
* are sorted in ascending order. Otherwise, specify an order of "desc" for
|
|
* descending or "asc" for ascending sort order of corresponding values.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
|
|
* The iteratees to sort by.
|
|
* @param {string[]} [orders] The sort orders of `iteratees`.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
|
|
* @returns {Array} Returns the new sorted array.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'fred', 'age': 48 },
|
|
* { 'user': 'barney', 'age': 34 },
|
|
* { 'user': 'fred', 'age': 40 },
|
|
* { 'user': 'barney', 'age': 36 }
|
|
* ];
|
|
*
|
|
* // Sort by `user` in ascending order and by `age` in descending order.
|
|
* _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
|
|
* // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
|
|
*/
|
|
function orderBy(collection, iteratees, orders, guard) {
|
|
if (collection == null) {
|
|
return [];
|
|
}
|
|
if (!isArray(iteratees)) {
|
|
iteratees = iteratees == null ? [] : [iteratees];
|
|
}
|
|
orders = guard ? undefined : orders;
|
|
if (!isArray(orders)) {
|
|
orders = orders == null ? [] : [orders];
|
|
}
|
|
return baseOrderBy(collection, iteratees, orders);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of elements split into two groups, the first of which
|
|
* contains elements `predicate` returns truthy for, the second of which
|
|
* contains elements `predicate` returns falsey for. The predicate is
|
|
* invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the array of grouped elements.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36, 'active': false },
|
|
* { 'user': 'fred', 'age': 40, 'active': true },
|
|
* { 'user': 'pebbles', 'age': 1, 'active': false }
|
|
* ];
|
|
*
|
|
* _.partition(users, function(o) { return o.active; });
|
|
* // => objects for [['fred'], ['barney', 'pebbles']]
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.partition(users, { 'age': 1, 'active': false });
|
|
* // => objects for [['pebbles'], ['barney', 'fred']]
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.partition(users, ['active', false]);
|
|
* // => objects for [['barney', 'pebbles'], ['fred']]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.partition(users, 'active');
|
|
* // => objects for [['fred'], ['barney', 'pebbles']]
|
|
*/
|
|
var partition = createAggregator(function(result, value, key) {
|
|
result[key ? 0 : 1].push(value);
|
|
}, function() { return [[], []]; });
|
|
|
|
/**
|
|
* Reduces `collection` to a value which is the accumulated result of running
|
|
* each element in `collection` thru `iteratee`, where each successive
|
|
* invocation is supplied the return value of the previous. If `accumulator`
|
|
* is not given, the first element of `collection` is used as the initial
|
|
* value. The iteratee is invoked with four arguments:
|
|
* (accumulator, value, index|key, collection).
|
|
*
|
|
* Many lodash methods are guarded to work as iteratees for methods like
|
|
* `_.reduce`, `_.reduceRight`, and `_.transform`.
|
|
*
|
|
* The guarded methods are:
|
|
* `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
|
|
* and `sortBy`
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @param {*} [accumulator] The initial value.
|
|
* @returns {*} Returns the accumulated value.
|
|
* @see _.reduceRight
|
|
* @example
|
|
*
|
|
* _.reduce([1, 2], function(sum, n) {
|
|
* return sum + n;
|
|
* }, 0);
|
|
* // => 3
|
|
*
|
|
* _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
|
|
* (result[value] || (result[value] = [])).push(key);
|
|
* return result;
|
|
* }, {});
|
|
* // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
|
|
*/
|
|
function reduce(collection, iteratee, accumulator) {
|
|
var func = isArray(collection) ? arrayReduce : baseReduce,
|
|
initAccum = arguments.length < 3;
|
|
|
|
return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.reduce` except that it iterates over elements of
|
|
* `collection` from right to left.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @param {*} [accumulator] The initial value.
|
|
* @returns {*} Returns the accumulated value.
|
|
* @see _.reduce
|
|
* @example
|
|
*
|
|
* var array = [[0, 1], [2, 3], [4, 5]];
|
|
*
|
|
* _.reduceRight(array, function(flattened, other) {
|
|
* return flattened.concat(other);
|
|
* }, []);
|
|
* // => [4, 5, 2, 3, 0, 1]
|
|
*/
|
|
function reduceRight(collection, iteratee, accumulator) {
|
|
var func = isArray(collection) ? arrayReduceRight : baseReduce,
|
|
initAccum = arguments.length < 3;
|
|
|
|
return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
|
|
}
|
|
|
|
/**
|
|
* The opposite of `_.filter`; this method returns the elements of `collection`
|
|
* that `predicate` does **not** return truthy for.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the new filtered array.
|
|
* @see _.filter
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36, 'active': false },
|
|
* { 'user': 'fred', 'age': 40, 'active': true }
|
|
* ];
|
|
*
|
|
* _.reject(users, function(o) { return !o.active; });
|
|
* // => objects for ['fred']
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.reject(users, { 'age': 40, 'active': true });
|
|
* // => objects for ['barney']
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.reject(users, ['active', false]);
|
|
* // => objects for ['fred']
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.reject(users, 'active');
|
|
* // => objects for ['barney']
|
|
*/
|
|
function reject(collection, predicate) {
|
|
var func = isArray(collection) ? arrayFilter : baseFilter;
|
|
return func(collection, negate(getIteratee(predicate, 3)));
|
|
}
|
|
|
|
/**
|
|
* Gets a random element from `collection`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to sample.
|
|
* @returns {*} Returns the random element.
|
|
* @example
|
|
*
|
|
* _.sample([1, 2, 3, 4]);
|
|
* // => 2
|
|
*/
|
|
function sample(collection) {
|
|
var func = isArray(collection) ? arraySample : baseSample;
|
|
return func(collection);
|
|
}
|
|
|
|
/**
|
|
* Gets `n` random elements at unique keys from `collection` up to the
|
|
* size of `collection`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to sample.
|
|
* @param {number} [n=1] The number of elements to sample.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the random elements.
|
|
* @example
|
|
*
|
|
* _.sampleSize([1, 2, 3], 2);
|
|
* // => [3, 1]
|
|
*
|
|
* _.sampleSize([1, 2, 3], 4);
|
|
* // => [2, 3, 1]
|
|
*/
|
|
function sampleSize(collection, n, guard) {
|
|
if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
|
|
n = 1;
|
|
} else {
|
|
n = toInteger(n);
|
|
}
|
|
var func = isArray(collection) ? arraySampleSize : baseSampleSize;
|
|
return func(collection, n);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of shuffled values, using a version of the
|
|
* [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to shuffle.
|
|
* @returns {Array} Returns the new shuffled array.
|
|
* @example
|
|
*
|
|
* _.shuffle([1, 2, 3, 4]);
|
|
* // => [4, 1, 3, 2]
|
|
*/
|
|
function shuffle(collection) {
|
|
var func = isArray(collection) ? arrayShuffle : baseShuffle;
|
|
return func(collection);
|
|
}
|
|
|
|
/**
|
|
* Gets the size of `collection` by returning its length for array-like
|
|
* values or the number of own enumerable string keyed properties for objects.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object|string} collection The collection to inspect.
|
|
* @returns {number} Returns the collection size.
|
|
* @example
|
|
*
|
|
* _.size([1, 2, 3]);
|
|
* // => 3
|
|
*
|
|
* _.size({ 'a': 1, 'b': 2 });
|
|
* // => 2
|
|
*
|
|
* _.size('pebbles');
|
|
* // => 7
|
|
*/
|
|
function size(collection) {
|
|
if (collection == null) {
|
|
return 0;
|
|
}
|
|
if (isArrayLike(collection)) {
|
|
return isString(collection) ? stringSize(collection) : collection.length;
|
|
}
|
|
var tag = getTag(collection);
|
|
if (tag == mapTag || tag == setTag) {
|
|
return collection.size;
|
|
}
|
|
return baseKeys(collection).length;
|
|
}
|
|
|
|
/**
|
|
* Checks if `predicate` returns truthy for **any** element of `collection`.
|
|
* Iteration is stopped once `predicate` returns truthy. The predicate is
|
|
* invoked with three arguments: (value, index|key, collection).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {boolean} Returns `true` if any element passes the predicate check,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.some([null, 0, 'yes', false], Boolean);
|
|
* // => true
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'active': true },
|
|
* { 'user': 'fred', 'active': false }
|
|
* ];
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.some(users, { 'user': 'barney', 'active': false });
|
|
* // => false
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.some(users, ['active', false]);
|
|
* // => true
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.some(users, 'active');
|
|
* // => true
|
|
*/
|
|
function some(collection, predicate, guard) {
|
|
var func = isArray(collection) ? arraySome : baseSome;
|
|
if (guard && isIterateeCall(collection, predicate, guard)) {
|
|
predicate = undefined;
|
|
}
|
|
return func(collection, getIteratee(predicate, 3));
|
|
}
|
|
|
|
/**
|
|
* Creates an array of elements, sorted in ascending order by the results of
|
|
* running each element in a collection thru each iteratee. This method
|
|
* performs a stable sort, that is, it preserves the original sort order of
|
|
* equal elements. The iteratees are invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Collection
|
|
* @param {Array|Object} collection The collection to iterate over.
|
|
* @param {...(Function|Function[])} [iteratees=[_.identity]]
|
|
* The iteratees to sort by.
|
|
* @returns {Array} Returns the new sorted array.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'fred', 'age': 48 },
|
|
* { 'user': 'barney', 'age': 36 },
|
|
* { 'user': 'fred', 'age': 40 },
|
|
* { 'user': 'barney', 'age': 34 }
|
|
* ];
|
|
*
|
|
* _.sortBy(users, [function(o) { return o.user; }]);
|
|
* // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
|
|
*
|
|
* _.sortBy(users, ['user', 'age']);
|
|
* // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
|
|
*/
|
|
var sortBy = baseRest(function(collection, iteratees) {
|
|
if (collection == null) {
|
|
return [];
|
|
}
|
|
var length = iteratees.length;
|
|
if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
|
|
iteratees = [];
|
|
} else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
|
|
iteratees = [iteratees[0]];
|
|
}
|
|
return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
|
|
});
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Gets the timestamp of the number of milliseconds that have elapsed since
|
|
* the Unix epoch (1 January 1970 00:00:00 UTC).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.4.0
|
|
* @category Date
|
|
* @returns {number} Returns the timestamp.
|
|
* @example
|
|
*
|
|
* _.defer(function(stamp) {
|
|
* console.log(_.now() - stamp);
|
|
* }, _.now());
|
|
* // => Logs the number of milliseconds it took for the deferred invocation.
|
|
*/
|
|
var now = ctxNow || function() {
|
|
return root.Date.now();
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* The opposite of `_.before`; this method creates a function that invokes
|
|
* `func` once it's called `n` or more times.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {number} n The number of calls before `func` is invoked.
|
|
* @param {Function} func The function to restrict.
|
|
* @returns {Function} Returns the new restricted function.
|
|
* @example
|
|
*
|
|
* var saves = ['profile', 'settings'];
|
|
*
|
|
* var done = _.after(saves.length, function() {
|
|
* console.log('done saving!');
|
|
* });
|
|
*
|
|
* _.forEach(saves, function(type) {
|
|
* asyncSave({ 'type': type, 'complete': done });
|
|
* });
|
|
* // => Logs 'done saving!' after the two async saves have completed.
|
|
*/
|
|
function after(n, func) {
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
n = toInteger(n);
|
|
return function() {
|
|
if (--n < 1) {
|
|
return func.apply(this, arguments);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `func`, with up to `n` arguments,
|
|
* ignoring any additional arguments.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to cap arguments for.
|
|
* @param {number} [n=func.length] The arity cap.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Function} Returns the new capped function.
|
|
* @example
|
|
*
|
|
* _.map(['6', '8', '10'], _.ary(parseInt, 1));
|
|
* // => [6, 8, 10]
|
|
*/
|
|
function ary(func, n, guard) {
|
|
n = guard ? undefined : n;
|
|
n = (func && n == null) ? func.length : n;
|
|
return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n);
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `func`, with the `this` binding and arguments
|
|
* of the created function, while it's called less than `n` times. Subsequent
|
|
* calls to the created function return the result of the last `func` invocation.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Function
|
|
* @param {number} n The number of calls at which `func` is no longer invoked.
|
|
* @param {Function} func The function to restrict.
|
|
* @returns {Function} Returns the new restricted function.
|
|
* @example
|
|
*
|
|
* jQuery(element).on('click', _.before(5, addContactToList));
|
|
* // => Allows adding up to 4 contacts to the list.
|
|
*/
|
|
function before(n, func) {
|
|
var result;
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
n = toInteger(n);
|
|
return function() {
|
|
if (--n > 0) {
|
|
result = func.apply(this, arguments);
|
|
}
|
|
if (n <= 1) {
|
|
func = undefined;
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with the `this` binding of `thisArg`
|
|
* and `partials` prepended to the arguments it receives.
|
|
*
|
|
* The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
|
|
* may be used as a placeholder for partially applied arguments.
|
|
*
|
|
* **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
|
|
* property of bound functions.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to bind.
|
|
* @param {*} thisArg The `this` binding of `func`.
|
|
* @param {...*} [partials] The arguments to be partially applied.
|
|
* @returns {Function} Returns the new bound function.
|
|
* @example
|
|
*
|
|
* function greet(greeting, punctuation) {
|
|
* return greeting + ' ' + this.user + punctuation;
|
|
* }
|
|
*
|
|
* var object = { 'user': 'fred' };
|
|
*
|
|
* var bound = _.bind(greet, object, 'hi');
|
|
* bound('!');
|
|
* // => 'hi fred!'
|
|
*
|
|
* // Bound with placeholders.
|
|
* var bound = _.bind(greet, object, _, '!');
|
|
* bound('hi');
|
|
* // => 'hi fred!'
|
|
*/
|
|
var bind = baseRest(function(func, thisArg, partials) {
|
|
var bitmask = WRAP_BIND_FLAG;
|
|
if (partials.length) {
|
|
var holders = replaceHolders(partials, getHolder(bind));
|
|
bitmask |= WRAP_PARTIAL_FLAG;
|
|
}
|
|
return createWrap(func, bitmask, thisArg, partials, holders);
|
|
});
|
|
|
|
/**
|
|
* Creates a function that invokes the method at `object[key]` with `partials`
|
|
* prepended to the arguments it receives.
|
|
*
|
|
* This method differs from `_.bind` by allowing bound functions to reference
|
|
* methods that may be redefined or don't yet exist. See
|
|
* [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
|
|
* for more details.
|
|
*
|
|
* The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
|
|
* builds, may be used as a placeholder for partially applied arguments.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.10.0
|
|
* @category Function
|
|
* @param {Object} object The object to invoke the method on.
|
|
* @param {string} key The key of the method.
|
|
* @param {...*} [partials] The arguments to be partially applied.
|
|
* @returns {Function} Returns the new bound function.
|
|
* @example
|
|
*
|
|
* var object = {
|
|
* 'user': 'fred',
|
|
* 'greet': function(greeting, punctuation) {
|
|
* return greeting + ' ' + this.user + punctuation;
|
|
* }
|
|
* };
|
|
*
|
|
* var bound = _.bindKey(object, 'greet', 'hi');
|
|
* bound('!');
|
|
* // => 'hi fred!'
|
|
*
|
|
* object.greet = function(greeting, punctuation) {
|
|
* return greeting + 'ya ' + this.user + punctuation;
|
|
* };
|
|
*
|
|
* bound('!');
|
|
* // => 'hiya fred!'
|
|
*
|
|
* // Bound with placeholders.
|
|
* var bound = _.bindKey(object, 'greet', _, '!');
|
|
* bound('hi');
|
|
* // => 'hiya fred!'
|
|
*/
|
|
var bindKey = baseRest(function(object, key, partials) {
|
|
var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
|
|
if (partials.length) {
|
|
var holders = replaceHolders(partials, getHolder(bindKey));
|
|
bitmask |= WRAP_PARTIAL_FLAG;
|
|
}
|
|
return createWrap(key, bitmask, object, partials, holders);
|
|
});
|
|
|
|
/**
|
|
* Creates a function that accepts arguments of `func` and either invokes
|
|
* `func` returning its result, if at least `arity` number of arguments have
|
|
* been provided, or returns a function that accepts the remaining `func`
|
|
* arguments, and so on. The arity of `func` may be specified if `func.length`
|
|
* is not sufficient.
|
|
*
|
|
* The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
|
|
* may be used as a placeholder for provided arguments.
|
|
*
|
|
* **Note:** This method doesn't set the "length" property of curried functions.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to curry.
|
|
* @param {number} [arity=func.length] The arity of `func`.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Function} Returns the new curried function.
|
|
* @example
|
|
*
|
|
* var abc = function(a, b, c) {
|
|
* return [a, b, c];
|
|
* };
|
|
*
|
|
* var curried = _.curry(abc);
|
|
*
|
|
* curried(1)(2)(3);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* curried(1, 2)(3);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* curried(1, 2, 3);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* // Curried with placeholders.
|
|
* curried(1)(_, 3)(2);
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function curry(func, arity, guard) {
|
|
arity = guard ? undefined : arity;
|
|
var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
|
|
result.placeholder = curry.placeholder;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.curry` except that arguments are applied to `func`
|
|
* in the manner of `_.partialRight` instead of `_.partial`.
|
|
*
|
|
* The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
|
|
* builds, may be used as a placeholder for provided arguments.
|
|
*
|
|
* **Note:** This method doesn't set the "length" property of curried functions.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to curry.
|
|
* @param {number} [arity=func.length] The arity of `func`.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Function} Returns the new curried function.
|
|
* @example
|
|
*
|
|
* var abc = function(a, b, c) {
|
|
* return [a, b, c];
|
|
* };
|
|
*
|
|
* var curried = _.curryRight(abc);
|
|
*
|
|
* curried(3)(2)(1);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* curried(2, 3)(1);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* curried(1, 2, 3);
|
|
* // => [1, 2, 3]
|
|
*
|
|
* // Curried with placeholders.
|
|
* curried(3)(1, _)(2);
|
|
* // => [1, 2, 3]
|
|
*/
|
|
function curryRight(func, arity, guard) {
|
|
arity = guard ? undefined : arity;
|
|
var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
|
|
result.placeholder = curryRight.placeholder;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a debounced function that delays invoking `func` until after `wait`
|
|
* milliseconds have elapsed since the last time the debounced function was
|
|
* invoked. The debounced function comes with a `cancel` method to cancel
|
|
* delayed `func` invocations and a `flush` method to immediately invoke them.
|
|
* Provide `options` to indicate whether `func` should be invoked on the
|
|
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
|
|
* with the last arguments provided to the debounced function. Subsequent
|
|
* calls to the debounced function return the result of the last `func`
|
|
* invocation.
|
|
*
|
|
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
|
* invoked on the trailing edge of the timeout only if the debounced function
|
|
* is invoked more than once during the `wait` timeout.
|
|
*
|
|
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
|
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
|
|
*
|
|
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
|
* for details over the differences between `_.debounce` and `_.throttle`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to debounce.
|
|
* @param {number} [wait=0] The number of milliseconds to delay.
|
|
* @param {Object} [options={}] The options object.
|
|
* @param {boolean} [options.leading=false]
|
|
* Specify invoking on the leading edge of the timeout.
|
|
* @param {number} [options.maxWait]
|
|
* The maximum time `func` is allowed to be delayed before it's invoked.
|
|
* @param {boolean} [options.trailing=true]
|
|
* Specify invoking on the trailing edge of the timeout.
|
|
* @returns {Function} Returns the new debounced function.
|
|
* @example
|
|
*
|
|
* // Avoid costly calculations while the window size is in flux.
|
|
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
|
|
*
|
|
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
|
|
* jQuery(element).on('click', _.debounce(sendMail, 300, {
|
|
* 'leading': true,
|
|
* 'trailing': false
|
|
* }));
|
|
*
|
|
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
|
|
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
|
|
* var source = new EventSource('/stream');
|
|
* jQuery(source).on('message', debounced);
|
|
*
|
|
* // Cancel the trailing debounced invocation.
|
|
* jQuery(window).on('popstate', debounced.cancel);
|
|
*/
|
|
function debounce(func, wait, options) {
|
|
var lastArgs,
|
|
lastThis,
|
|
maxWait,
|
|
result,
|
|
timerId,
|
|
lastCallTime,
|
|
lastInvokeTime = 0,
|
|
leading = false,
|
|
maxing = false,
|
|
trailing = true;
|
|
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
wait = toNumber(wait) || 0;
|
|
if (isObject(options)) {
|
|
leading = !!options.leading;
|
|
maxing = 'maxWait' in options;
|
|
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
|
|
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
|
}
|
|
|
|
function invokeFunc(time) {
|
|
var args = lastArgs,
|
|
thisArg = lastThis;
|
|
|
|
lastArgs = lastThis = undefined;
|
|
lastInvokeTime = time;
|
|
result = func.apply(thisArg, args);
|
|
return result;
|
|
}
|
|
|
|
function leadingEdge(time) {
|
|
// Reset any `maxWait` timer.
|
|
lastInvokeTime = time;
|
|
// Start the timer for the trailing edge.
|
|
timerId = setTimeout(timerExpired, wait);
|
|
// Invoke the leading edge.
|
|
return leading ? invokeFunc(time) : result;
|
|
}
|
|
|
|
function remainingWait(time) {
|
|
var timeSinceLastCall = time - lastCallTime,
|
|
timeSinceLastInvoke = time - lastInvokeTime,
|
|
result = wait - timeSinceLastCall;
|
|
|
|
return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
|
|
}
|
|
|
|
function shouldInvoke(time) {
|
|
var timeSinceLastCall = time - lastCallTime,
|
|
timeSinceLastInvoke = time - lastInvokeTime;
|
|
|
|
// Either this is the first call, activity has stopped and we're at the
|
|
// trailing edge, the system time has gone backwards and we're treating
|
|
// it as the trailing edge, or we've hit the `maxWait` limit.
|
|
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
|
|
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
|
|
}
|
|
|
|
function timerExpired() {
|
|
var time = now();
|
|
if (shouldInvoke(time)) {
|
|
return trailingEdge(time);
|
|
}
|
|
// Restart the timer.
|
|
timerId = setTimeout(timerExpired, remainingWait(time));
|
|
}
|
|
|
|
function trailingEdge(time) {
|
|
timerId = undefined;
|
|
|
|
// Only invoke if we have `lastArgs` which means `func` has been
|
|
// debounced at least once.
|
|
if (trailing && lastArgs) {
|
|
return invokeFunc(time);
|
|
}
|
|
lastArgs = lastThis = undefined;
|
|
return result;
|
|
}
|
|
|
|
function cancel() {
|
|
if (timerId !== undefined) {
|
|
clearTimeout(timerId);
|
|
}
|
|
lastInvokeTime = 0;
|
|
lastArgs = lastCallTime = lastThis = timerId = undefined;
|
|
}
|
|
|
|
function flush() {
|
|
return timerId === undefined ? result : trailingEdge(now());
|
|
}
|
|
|
|
function debounced() {
|
|
var time = now(),
|
|
isInvoking = shouldInvoke(time);
|
|
|
|
lastArgs = arguments;
|
|
lastThis = this;
|
|
lastCallTime = time;
|
|
|
|
if (isInvoking) {
|
|
if (timerId === undefined) {
|
|
return leadingEdge(lastCallTime);
|
|
}
|
|
if (maxing) {
|
|
// Handle invocations in a tight loop.
|
|
timerId = setTimeout(timerExpired, wait);
|
|
return invokeFunc(lastCallTime);
|
|
}
|
|
}
|
|
if (timerId === undefined) {
|
|
timerId = setTimeout(timerExpired, wait);
|
|
}
|
|
return result;
|
|
}
|
|
debounced.cancel = cancel;
|
|
debounced.flush = flush;
|
|
return debounced;
|
|
}
|
|
|
|
/**
|
|
* Defers invoking the `func` until the current call stack has cleared. Any
|
|
* additional arguments are provided to `func` when it's invoked.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to defer.
|
|
* @param {...*} [args] The arguments to invoke `func` with.
|
|
* @returns {number} Returns the timer id.
|
|
* @example
|
|
*
|
|
* _.defer(function(text) {
|
|
* console.log(text);
|
|
* }, 'deferred');
|
|
* // => Logs 'deferred' after one millisecond.
|
|
*/
|
|
var defer = baseRest(function(func, args) {
|
|
return baseDelay(func, 1, args);
|
|
});
|
|
|
|
/**
|
|
* Invokes `func` after `wait` milliseconds. Any additional arguments are
|
|
* provided to `func` when it's invoked.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to delay.
|
|
* @param {number} wait The number of milliseconds to delay invocation.
|
|
* @param {...*} [args] The arguments to invoke `func` with.
|
|
* @returns {number} Returns the timer id.
|
|
* @example
|
|
*
|
|
* _.delay(function(text) {
|
|
* console.log(text);
|
|
* }, 1000, 'later');
|
|
* // => Logs 'later' after one second.
|
|
*/
|
|
var delay = baseRest(function(func, wait, args) {
|
|
return baseDelay(func, toNumber(wait) || 0, args);
|
|
});
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with arguments reversed.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to flip arguments for.
|
|
* @returns {Function} Returns the new flipped function.
|
|
* @example
|
|
*
|
|
* var flipped = _.flip(function() {
|
|
* return _.toArray(arguments);
|
|
* });
|
|
*
|
|
* flipped('a', 'b', 'c', 'd');
|
|
* // => ['d', 'c', 'b', 'a']
|
|
*/
|
|
function flip(func) {
|
|
return createWrap(func, WRAP_FLIP_FLAG);
|
|
}
|
|
|
|
/**
|
|
* Creates a function that memoizes the result of `func`. If `resolver` is
|
|
* provided, it determines the cache key for storing the result based on the
|
|
* arguments provided to the memoized function. By default, the first argument
|
|
* provided to the memoized function is used as the map cache key. The `func`
|
|
* is invoked with the `this` binding of the memoized function.
|
|
*
|
|
* **Note:** The cache is exposed as the `cache` property on the memoized
|
|
* function. Its creation may be customized by replacing the `_.memoize.Cache`
|
|
* constructor with one whose instances implement the
|
|
* [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
|
|
* method interface of `clear`, `delete`, `get`, `has`, and `set`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to have its output memoized.
|
|
* @param {Function} [resolver] The function to resolve the cache key.
|
|
* @returns {Function} Returns the new memoized function.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': 2 };
|
|
* var other = { 'c': 3, 'd': 4 };
|
|
*
|
|
* var values = _.memoize(_.values);
|
|
* values(object);
|
|
* // => [1, 2]
|
|
*
|
|
* values(other);
|
|
* // => [3, 4]
|
|
*
|
|
* object.a = 2;
|
|
* values(object);
|
|
* // => [1, 2]
|
|
*
|
|
* // Modify the result cache.
|
|
* values.cache.set(object, ['a', 'b']);
|
|
* values(object);
|
|
* // => ['a', 'b']
|
|
*
|
|
* // Replace `_.memoize.Cache`.
|
|
* _.memoize.Cache = WeakMap;
|
|
*/
|
|
function memoize(func, resolver) {
|
|
if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
var memoized = function() {
|
|
var args = arguments,
|
|
key = resolver ? resolver.apply(this, args) : args[0],
|
|
cache = memoized.cache;
|
|
|
|
if (cache.has(key)) {
|
|
return cache.get(key);
|
|
}
|
|
var result = func.apply(this, args);
|
|
memoized.cache = cache.set(key, result) || cache;
|
|
return result;
|
|
};
|
|
memoized.cache = new (memoize.Cache || MapCache);
|
|
return memoized;
|
|
}
|
|
|
|
// Expose `MapCache`.
|
|
memoize.Cache = MapCache;
|
|
|
|
/**
|
|
* Creates a function that negates the result of the predicate `func`. The
|
|
* `func` predicate is invoked with the `this` binding and arguments of the
|
|
* created function.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Function
|
|
* @param {Function} predicate The predicate to negate.
|
|
* @returns {Function} Returns the new negated function.
|
|
* @example
|
|
*
|
|
* function isEven(n) {
|
|
* return n % 2 == 0;
|
|
* }
|
|
*
|
|
* _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
|
|
* // => [1, 3, 5]
|
|
*/
|
|
function negate(predicate) {
|
|
if (typeof predicate != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
return function() {
|
|
var args = arguments;
|
|
switch (args.length) {
|
|
case 0: return !predicate.call(this);
|
|
case 1: return !predicate.call(this, args[0]);
|
|
case 2: return !predicate.call(this, args[0], args[1]);
|
|
case 3: return !predicate.call(this, args[0], args[1], args[2]);
|
|
}
|
|
return !predicate.apply(this, args);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that is restricted to invoking `func` once. Repeat calls
|
|
* to the function return the value of the first invocation. The `func` is
|
|
* invoked with the `this` binding and arguments of the created function.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to restrict.
|
|
* @returns {Function} Returns the new restricted function.
|
|
* @example
|
|
*
|
|
* var initialize = _.once(createApplication);
|
|
* initialize();
|
|
* initialize();
|
|
* // => `createApplication` is invoked once
|
|
*/
|
|
function once(func) {
|
|
return before(2, func);
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with its arguments transformed.
|
|
*
|
|
* @static
|
|
* @since 4.0.0
|
|
* @memberOf _
|
|
* @category Function
|
|
* @param {Function} func The function to wrap.
|
|
* @param {...(Function|Function[])} [transforms=[_.identity]]
|
|
* The argument transforms.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* function doubled(n) {
|
|
* return n * 2;
|
|
* }
|
|
*
|
|
* function square(n) {
|
|
* return n * n;
|
|
* }
|
|
*
|
|
* var func = _.overArgs(function(x, y) {
|
|
* return [x, y];
|
|
* }, [square, doubled]);
|
|
*
|
|
* func(9, 3);
|
|
* // => [81, 6]
|
|
*
|
|
* func(10, 5);
|
|
* // => [100, 10]
|
|
*/
|
|
var overArgs = castRest(function(func, transforms) {
|
|
transforms = (transforms.length == 1 && isArray(transforms[0]))
|
|
? arrayMap(transforms[0], baseUnary(getIteratee()))
|
|
: arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
|
|
|
|
var funcsLength = transforms.length;
|
|
return baseRest(function(args) {
|
|
var index = -1,
|
|
length = nativeMin(args.length, funcsLength);
|
|
|
|
while (++index < length) {
|
|
args[index] = transforms[index].call(this, args[index]);
|
|
}
|
|
return apply(func, this, args);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with `partials` prepended to the
|
|
* arguments it receives. This method is like `_.bind` except it does **not**
|
|
* alter the `this` binding.
|
|
*
|
|
* The `_.partial.placeholder` value, which defaults to `_` in monolithic
|
|
* builds, may be used as a placeholder for partially applied arguments.
|
|
*
|
|
* **Note:** This method doesn't set the "length" property of partially
|
|
* applied functions.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.2.0
|
|
* @category Function
|
|
* @param {Function} func The function to partially apply arguments to.
|
|
* @param {...*} [partials] The arguments to be partially applied.
|
|
* @returns {Function} Returns the new partially applied function.
|
|
* @example
|
|
*
|
|
* function greet(greeting, name) {
|
|
* return greeting + ' ' + name;
|
|
* }
|
|
*
|
|
* var sayHelloTo = _.partial(greet, 'hello');
|
|
* sayHelloTo('fred');
|
|
* // => 'hello fred'
|
|
*
|
|
* // Partially applied with placeholders.
|
|
* var greetFred = _.partial(greet, _, 'fred');
|
|
* greetFred('hi');
|
|
* // => 'hi fred'
|
|
*/
|
|
var partial = baseRest(function(func, partials) {
|
|
var holders = replaceHolders(partials, getHolder(partial));
|
|
return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders);
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.partial` except that partially applied arguments
|
|
* are appended to the arguments it receives.
|
|
*
|
|
* The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
|
|
* builds, may be used as a placeholder for partially applied arguments.
|
|
*
|
|
* **Note:** This method doesn't set the "length" property of partially
|
|
* applied functions.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to partially apply arguments to.
|
|
* @param {...*} [partials] The arguments to be partially applied.
|
|
* @returns {Function} Returns the new partially applied function.
|
|
* @example
|
|
*
|
|
* function greet(greeting, name) {
|
|
* return greeting + ' ' + name;
|
|
* }
|
|
*
|
|
* var greetFred = _.partialRight(greet, 'fred');
|
|
* greetFred('hi');
|
|
* // => 'hi fred'
|
|
*
|
|
* // Partially applied with placeholders.
|
|
* var sayHelloTo = _.partialRight(greet, 'hello', _);
|
|
* sayHelloTo('fred');
|
|
* // => 'hello fred'
|
|
*/
|
|
var partialRight = baseRest(function(func, partials) {
|
|
var holders = replaceHolders(partials, getHolder(partialRight));
|
|
return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders);
|
|
});
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with arguments arranged according
|
|
* to the specified `indexes` where the argument value at the first index is
|
|
* provided as the first argument, the argument value at the second index is
|
|
* provided as the second argument, and so on.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to rearrange arguments for.
|
|
* @param {...(number|number[])} indexes The arranged argument indexes.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var rearged = _.rearg(function(a, b, c) {
|
|
* return [a, b, c];
|
|
* }, [2, 0, 1]);
|
|
*
|
|
* rearged('b', 'c', 'a')
|
|
* // => ['a', 'b', 'c']
|
|
*/
|
|
var rearg = flatRest(function(func, indexes) {
|
|
return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes);
|
|
});
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with the `this` binding of the
|
|
* created function and arguments from `start` and beyond provided as
|
|
* an array.
|
|
*
|
|
* **Note:** This method is based on the
|
|
* [rest parameter](https://mdn.io/rest_parameters).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to apply a rest parameter to.
|
|
* @param {number} [start=func.length-1] The start position of the rest parameter.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var say = _.rest(function(what, names) {
|
|
* return what + ' ' + _.initial(names).join(', ') +
|
|
* (_.size(names) > 1 ? ', & ' : '') + _.last(names);
|
|
* });
|
|
*
|
|
* say('hello', 'fred', 'barney', 'pebbles');
|
|
* // => 'hello fred, barney, & pebbles'
|
|
*/
|
|
function rest(func, start) {
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
start = start === undefined ? start : toInteger(start);
|
|
return baseRest(func, start);
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with the `this` binding of the
|
|
* create function and an array of arguments much like
|
|
* [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
|
|
*
|
|
* **Note:** This method is based on the
|
|
* [spread operator](https://mdn.io/spread_operator).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.2.0
|
|
* @category Function
|
|
* @param {Function} func The function to spread arguments over.
|
|
* @param {number} [start=0] The start position of the spread.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var say = _.spread(function(who, what) {
|
|
* return who + ' says ' + what;
|
|
* });
|
|
*
|
|
* say(['fred', 'hello']);
|
|
* // => 'fred says hello'
|
|
*
|
|
* var numbers = Promise.all([
|
|
* Promise.resolve(40),
|
|
* Promise.resolve(36)
|
|
* ]);
|
|
*
|
|
* numbers.then(_.spread(function(x, y) {
|
|
* return x + y;
|
|
* }));
|
|
* // => a Promise of 76
|
|
*/
|
|
function spread(func, start) {
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
start = start == null ? 0 : nativeMax(toInteger(start), 0);
|
|
return baseRest(function(args) {
|
|
var array = args[start],
|
|
otherArgs = castSlice(args, 0, start);
|
|
|
|
if (array) {
|
|
arrayPush(otherArgs, array);
|
|
}
|
|
return apply(func, this, otherArgs);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a throttled function that only invokes `func` at most once per
|
|
* every `wait` milliseconds. The throttled function comes with a `cancel`
|
|
* method to cancel delayed `func` invocations and a `flush` method to
|
|
* immediately invoke them. Provide `options` to indicate whether `func`
|
|
* should be invoked on the leading and/or trailing edge of the `wait`
|
|
* timeout. The `func` is invoked with the last arguments provided to the
|
|
* throttled function. Subsequent calls to the throttled function return the
|
|
* result of the last `func` invocation.
|
|
*
|
|
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
|
* invoked on the trailing edge of the timeout only if the throttled function
|
|
* is invoked more than once during the `wait` timeout.
|
|
*
|
|
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
|
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
|
|
*
|
|
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
|
* for details over the differences between `_.throttle` and `_.debounce`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to throttle.
|
|
* @param {number} [wait=0] The number of milliseconds to throttle invocations to.
|
|
* @param {Object} [options={}] The options object.
|
|
* @param {boolean} [options.leading=true]
|
|
* Specify invoking on the leading edge of the timeout.
|
|
* @param {boolean} [options.trailing=true]
|
|
* Specify invoking on the trailing edge of the timeout.
|
|
* @returns {Function} Returns the new throttled function.
|
|
* @example
|
|
*
|
|
* // Avoid excessively updating the position while scrolling.
|
|
* jQuery(window).on('scroll', _.throttle(updatePosition, 100));
|
|
*
|
|
* // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
|
|
* var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
|
|
* jQuery(element).on('click', throttled);
|
|
*
|
|
* // Cancel the trailing throttled invocation.
|
|
* jQuery(window).on('popstate', throttled.cancel);
|
|
*/
|
|
function throttle(func, wait, options) {
|
|
var leading = true,
|
|
trailing = true;
|
|
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
if (isObject(options)) {
|
|
leading = 'leading' in options ? !!options.leading : leading;
|
|
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
|
}
|
|
return debounce(func, wait, {
|
|
'leading': leading,
|
|
'maxWait': wait,
|
|
'trailing': trailing
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a function that accepts up to one argument, ignoring any
|
|
* additional arguments.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Function
|
|
* @param {Function} func The function to cap arguments for.
|
|
* @returns {Function} Returns the new capped function.
|
|
* @example
|
|
*
|
|
* _.map(['6', '8', '10'], _.unary(parseInt));
|
|
* // => [6, 8, 10]
|
|
*/
|
|
function unary(func) {
|
|
return ary(func, 1);
|
|
}
|
|
|
|
/**
|
|
* Creates a function that provides `value` to `wrapper` as its first
|
|
* argument. Any additional arguments provided to the function are appended
|
|
* to those provided to the `wrapper`. The wrapper is invoked with the `this`
|
|
* binding of the created function.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {*} value The value to wrap.
|
|
* @param {Function} [wrapper=identity] The wrapper function.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var p = _.wrap(_.escape, function(func, text) {
|
|
* return '<p>' + func(text) + '</p>';
|
|
* });
|
|
*
|
|
* p('fred, barney, & pebbles');
|
|
* // => '<p>fred, barney, & pebbles</p>'
|
|
*/
|
|
function wrap(value, wrapper) {
|
|
return partial(castFunction(wrapper), value);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Casts `value` as an array if it's not one.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.4.0
|
|
* @category Lang
|
|
* @param {*} value The value to inspect.
|
|
* @returns {Array} Returns the cast array.
|
|
* @example
|
|
*
|
|
* _.castArray(1);
|
|
* // => [1]
|
|
*
|
|
* _.castArray({ 'a': 1 });
|
|
* // => [{ 'a': 1 }]
|
|
*
|
|
* _.castArray('abc');
|
|
* // => ['abc']
|
|
*
|
|
* _.castArray(null);
|
|
* // => [null]
|
|
*
|
|
* _.castArray(undefined);
|
|
* // => [undefined]
|
|
*
|
|
* _.castArray();
|
|
* // => []
|
|
*
|
|
* var array = [1, 2, 3];
|
|
* console.log(_.castArray(array) === array);
|
|
* // => true
|
|
*/
|
|
function castArray() {
|
|
if (!arguments.length) {
|
|
return [];
|
|
}
|
|
var value = arguments[0];
|
|
return isArray(value) ? value : [value];
|
|
}
|
|
|
|
/**
|
|
* Creates a shallow clone of `value`.
|
|
*
|
|
* **Note:** This method is loosely based on the
|
|
* [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
|
|
* and supports cloning arrays, array buffers, booleans, date objects, maps,
|
|
* numbers, `Object` objects, regexes, sets, strings, symbols, and typed
|
|
* arrays. The own enumerable properties of `arguments` objects are cloned
|
|
* as plain objects. An empty object is returned for uncloneable values such
|
|
* as error objects, functions, DOM nodes, and WeakMaps.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to clone.
|
|
* @returns {*} Returns the cloned value.
|
|
* @see _.cloneDeep
|
|
* @example
|
|
*
|
|
* var objects = [{ 'a': 1 }, { 'b': 2 }];
|
|
*
|
|
* var shallow = _.clone(objects);
|
|
* console.log(shallow[0] === objects[0]);
|
|
* // => true
|
|
*/
|
|
function clone(value) {
|
|
return baseClone(value, CLONE_SYMBOLS_FLAG);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.clone` except that it accepts `customizer` which
|
|
* is invoked to produce the cloned value. If `customizer` returns `undefined`,
|
|
* cloning is handled by the method instead. The `customizer` is invoked with
|
|
* up to four arguments; (value [, index|key, object, stack]).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to clone.
|
|
* @param {Function} [customizer] The function to customize cloning.
|
|
* @returns {*} Returns the cloned value.
|
|
* @see _.cloneDeepWith
|
|
* @example
|
|
*
|
|
* function customizer(value) {
|
|
* if (_.isElement(value)) {
|
|
* return value.cloneNode(false);
|
|
* }
|
|
* }
|
|
*
|
|
* var el = _.cloneWith(document.body, customizer);
|
|
*
|
|
* console.log(el === document.body);
|
|
* // => false
|
|
* console.log(el.nodeName);
|
|
* // => 'BODY'
|
|
* console.log(el.childNodes.length);
|
|
* // => 0
|
|
*/
|
|
function cloneWith(value, customizer) {
|
|
customizer = typeof customizer == 'function' ? customizer : undefined;
|
|
return baseClone(value, CLONE_SYMBOLS_FLAG, customizer);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.clone` except that it recursively clones `value`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to recursively clone.
|
|
* @returns {*} Returns the deep cloned value.
|
|
* @see _.clone
|
|
* @example
|
|
*
|
|
* var objects = [{ 'a': 1 }, { 'b': 2 }];
|
|
*
|
|
* var deep = _.cloneDeep(objects);
|
|
* console.log(deep[0] === objects[0]);
|
|
* // => false
|
|
*/
|
|
function cloneDeep(value) {
|
|
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.cloneWith` except that it recursively clones `value`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to recursively clone.
|
|
* @param {Function} [customizer] The function to customize cloning.
|
|
* @returns {*} Returns the deep cloned value.
|
|
* @see _.cloneWith
|
|
* @example
|
|
*
|
|
* function customizer(value) {
|
|
* if (_.isElement(value)) {
|
|
* return value.cloneNode(true);
|
|
* }
|
|
* }
|
|
*
|
|
* var el = _.cloneDeepWith(document.body, customizer);
|
|
*
|
|
* console.log(el === document.body);
|
|
* // => false
|
|
* console.log(el.nodeName);
|
|
* // => 'BODY'
|
|
* console.log(el.childNodes.length);
|
|
* // => 20
|
|
*/
|
|
function cloneDeepWith(value, customizer) {
|
|
customizer = typeof customizer == 'function' ? customizer : undefined;
|
|
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer);
|
|
}
|
|
|
|
/**
|
|
* Checks if `object` conforms to `source` by invoking the predicate
|
|
* properties of `source` with the corresponding property values of `object`.
|
|
*
|
|
* **Note:** This method is equivalent to `_.conforms` when `source` is
|
|
* partially applied.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.14.0
|
|
* @category Lang
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Object} source The object of property predicates to conform to.
|
|
* @returns {boolean} Returns `true` if `object` conforms, else `false`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': 2 };
|
|
*
|
|
* _.conformsTo(object, { 'b': function(n) { return n > 1; } });
|
|
* // => true
|
|
*
|
|
* _.conformsTo(object, { 'b': function(n) { return n > 2; } });
|
|
* // => false
|
|
*/
|
|
function conformsTo(object, source) {
|
|
return source == null || baseConformsTo(object, source, keys(source));
|
|
}
|
|
|
|
/**
|
|
* Performs a
|
|
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
|
* comparison between two values to determine if they are equivalent.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1 };
|
|
* var other = { 'a': 1 };
|
|
*
|
|
* _.eq(object, object);
|
|
* // => true
|
|
*
|
|
* _.eq(object, other);
|
|
* // => false
|
|
*
|
|
* _.eq('a', 'a');
|
|
* // => true
|
|
*
|
|
* _.eq('a', Object('a'));
|
|
* // => false
|
|
*
|
|
* _.eq(NaN, NaN);
|
|
* // => true
|
|
*/
|
|
function eq(value, other) {
|
|
return value === other || (value !== value && other !== other);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is greater than `other`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.9.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if `value` is greater than `other`,
|
|
* else `false`.
|
|
* @see _.lt
|
|
* @example
|
|
*
|
|
* _.gt(3, 1);
|
|
* // => true
|
|
*
|
|
* _.gt(3, 3);
|
|
* // => false
|
|
*
|
|
* _.gt(1, 3);
|
|
* // => false
|
|
*/
|
|
var gt = createRelationalOperation(baseGt);
|
|
|
|
/**
|
|
* Checks if `value` is greater than or equal to `other`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.9.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if `value` is greater than or equal to
|
|
* `other`, else `false`.
|
|
* @see _.lte
|
|
* @example
|
|
*
|
|
* _.gte(3, 1);
|
|
* // => true
|
|
*
|
|
* _.gte(3, 3);
|
|
* // => true
|
|
*
|
|
* _.gte(1, 3);
|
|
* // => false
|
|
*/
|
|
var gte = createRelationalOperation(function(value, other) {
|
|
return value >= other;
|
|
});
|
|
|
|
/**
|
|
* Checks if `value` is likely an `arguments` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.isArguments(function() { return arguments; }());
|
|
* // => true
|
|
*
|
|
* _.isArguments([1, 2, 3]);
|
|
* // => false
|
|
*/
|
|
var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
|
|
return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&
|
|
!propertyIsEnumerable.call(value, 'callee');
|
|
};
|
|
|
|
/**
|
|
* Checks if `value` is classified as an `Array` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
|
|
* @example
|
|
*
|
|
* _.isArray([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isArray(document.body.children);
|
|
* // => false
|
|
*
|
|
* _.isArray('abc');
|
|
* // => false
|
|
*
|
|
* _.isArray(_.noop);
|
|
* // => false
|
|
*/
|
|
var isArray = Array.isArray;
|
|
|
|
/**
|
|
* Checks if `value` is classified as an `ArrayBuffer` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.3.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
|
|
* @example
|
|
*
|
|
* _.isArrayBuffer(new ArrayBuffer(2));
|
|
* // => true
|
|
*
|
|
* _.isArrayBuffer(new Array(2));
|
|
* // => false
|
|
*/
|
|
var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer;
|
|
|
|
/**
|
|
* Checks if `value` is array-like. A value is considered array-like if it's
|
|
* not a function and has a `value.length` that's an integer greater than or
|
|
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
|
|
* @example
|
|
*
|
|
* _.isArrayLike([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isArrayLike(document.body.children);
|
|
* // => true
|
|
*
|
|
* _.isArrayLike('abc');
|
|
* // => true
|
|
*
|
|
* _.isArrayLike(_.noop);
|
|
* // => false
|
|
*/
|
|
function isArrayLike(value) {
|
|
return value != null && isLength(value.length) && !isFunction(value);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.isArrayLike` except that it also checks if `value`
|
|
* is an object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an array-like object,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.isArrayLikeObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isArrayLikeObject(document.body.children);
|
|
* // => true
|
|
*
|
|
* _.isArrayLikeObject('abc');
|
|
* // => false
|
|
*
|
|
* _.isArrayLikeObject(_.noop);
|
|
* // => false
|
|
*/
|
|
function isArrayLikeObject(value) {
|
|
return isObjectLike(value) && isArrayLike(value);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a boolean primitive or object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
|
|
* @example
|
|
*
|
|
* _.isBoolean(false);
|
|
* // => true
|
|
*
|
|
* _.isBoolean(null);
|
|
* // => false
|
|
*/
|
|
function isBoolean(value) {
|
|
return value === true || value === false ||
|
|
(isObjectLike(value) && baseGetTag(value) == boolTag);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a buffer.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.3.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
|
|
* @example
|
|
*
|
|
* _.isBuffer(new Buffer(2));
|
|
* // => true
|
|
*
|
|
* _.isBuffer(new Uint8Array(2));
|
|
* // => false
|
|
*/
|
|
var isBuffer = nativeIsBuffer || stubFalse;
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Date` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a date object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isDate(new Date);
|
|
* // => true
|
|
*
|
|
* _.isDate('Mon April 23 2012');
|
|
* // => false
|
|
*/
|
|
var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;
|
|
|
|
/**
|
|
* Checks if `value` is likely a DOM element.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
|
|
* @example
|
|
*
|
|
* _.isElement(document.body);
|
|
* // => true
|
|
*
|
|
* _.isElement('<body>');
|
|
* // => false
|
|
*/
|
|
function isElement(value) {
|
|
return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is an empty object, collection, map, or set.
|
|
*
|
|
* Objects are considered empty if they have no own enumerable string keyed
|
|
* properties.
|
|
*
|
|
* Array-like values such as `arguments` objects, arrays, buffers, strings, or
|
|
* jQuery-like collections are considered empty if they have a `length` of `0`.
|
|
* Similarly, maps and sets are considered empty if they have a `size` of `0`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is empty, else `false`.
|
|
* @example
|
|
*
|
|
* _.isEmpty(null);
|
|
* // => true
|
|
*
|
|
* _.isEmpty(true);
|
|
* // => true
|
|
*
|
|
* _.isEmpty(1);
|
|
* // => true
|
|
*
|
|
* _.isEmpty([1, 2, 3]);
|
|
* // => false
|
|
*
|
|
* _.isEmpty({ 'a': 1 });
|
|
* // => false
|
|
*/
|
|
function isEmpty(value) {
|
|
if (value == null) {
|
|
return true;
|
|
}
|
|
if (isArrayLike(value) &&
|
|
(isArray(value) || typeof value == 'string' || typeof value.splice == 'function' ||
|
|
isBuffer(value) || isTypedArray(value) || isArguments(value))) {
|
|
return !value.length;
|
|
}
|
|
var tag = getTag(value);
|
|
if (tag == mapTag || tag == setTag) {
|
|
return !value.size;
|
|
}
|
|
if (isPrototype(value)) {
|
|
return !baseKeys(value).length;
|
|
}
|
|
for (var key in value) {
|
|
if (hasOwnProperty.call(value, key)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Performs a deep comparison between two values to determine if they are
|
|
* equivalent.
|
|
*
|
|
* **Note:** This method supports comparing arrays, array buffers, booleans,
|
|
* date objects, error objects, maps, numbers, `Object` objects, regexes,
|
|
* sets, strings, symbols, and typed arrays. `Object` objects are compared
|
|
* by their own, not inherited, enumerable properties. Functions and DOM
|
|
* nodes are compared by strict equality, i.e. `===`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1 };
|
|
* var other = { 'a': 1 };
|
|
*
|
|
* _.isEqual(object, other);
|
|
* // => true
|
|
*
|
|
* object === other;
|
|
* // => false
|
|
*/
|
|
function isEqual(value, other) {
|
|
return baseIsEqual(value, other);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.isEqual` except that it accepts `customizer` which
|
|
* is invoked to compare values. If `customizer` returns `undefined`, comparisons
|
|
* are handled by the method instead. The `customizer` is invoked with up to
|
|
* six arguments: (objValue, othValue [, index|key, object, other, stack]).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @param {Function} [customizer] The function to customize comparisons.
|
|
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
|
* @example
|
|
*
|
|
* function isGreeting(value) {
|
|
* return /^h(?:i|ello)$/.test(value);
|
|
* }
|
|
*
|
|
* function customizer(objValue, othValue) {
|
|
* if (isGreeting(objValue) && isGreeting(othValue)) {
|
|
* return true;
|
|
* }
|
|
* }
|
|
*
|
|
* var array = ['hello', 'goodbye'];
|
|
* var other = ['hi', 'goodbye'];
|
|
*
|
|
* _.isEqualWith(array, other, customizer);
|
|
* // => true
|
|
*/
|
|
function isEqualWith(value, other, customizer) {
|
|
customizer = typeof customizer == 'function' ? customizer : undefined;
|
|
var result = customizer ? customizer(value, other) : undefined;
|
|
return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
|
|
* `SyntaxError`, `TypeError`, or `URIError` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an error object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isError(new Error);
|
|
* // => true
|
|
*
|
|
* _.isError(Error);
|
|
* // => false
|
|
*/
|
|
function isError(value) {
|
|
if (!isObjectLike(value)) {
|
|
return false;
|
|
}
|
|
var tag = baseGetTag(value);
|
|
return tag == errorTag || tag == domExcTag ||
|
|
(typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value));
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a finite primitive number.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`Number.isFinite`](https://mdn.io/Number/isFinite).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
|
|
* @example
|
|
*
|
|
* _.isFinite(3);
|
|
* // => true
|
|
*
|
|
* _.isFinite(Number.MIN_VALUE);
|
|
* // => true
|
|
*
|
|
* _.isFinite(Infinity);
|
|
* // => false
|
|
*
|
|
* _.isFinite('3');
|
|
* // => false
|
|
*/
|
|
function isFinite(value) {
|
|
return typeof value == 'number' && nativeIsFinite(value);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Function` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
|
|
* @example
|
|
*
|
|
* _.isFunction(_);
|
|
* // => true
|
|
*
|
|
* _.isFunction(/abc/);
|
|
* // => false
|
|
*/
|
|
function isFunction(value) {
|
|
if (!isObject(value)) {
|
|
return false;
|
|
}
|
|
// The use of `Object#toString` avoids issues with the `typeof` operator
|
|
// in Safari 9 which returns 'object' for typed arrays and other constructors.
|
|
var tag = baseGetTag(value);
|
|
return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is an integer.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`Number.isInteger`](https://mdn.io/Number/isInteger).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an integer, else `false`.
|
|
* @example
|
|
*
|
|
* _.isInteger(3);
|
|
* // => true
|
|
*
|
|
* _.isInteger(Number.MIN_VALUE);
|
|
* // => false
|
|
*
|
|
* _.isInteger(Infinity);
|
|
* // => false
|
|
*
|
|
* _.isInteger('3');
|
|
* // => false
|
|
*/
|
|
function isInteger(value) {
|
|
return typeof value == 'number' && value == toInteger(value);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like length.
|
|
*
|
|
* **Note:** This method is loosely based on
|
|
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
|
|
* @example
|
|
*
|
|
* _.isLength(3);
|
|
* // => true
|
|
*
|
|
* _.isLength(Number.MIN_VALUE);
|
|
* // => false
|
|
*
|
|
* _.isLength(Infinity);
|
|
* // => false
|
|
*
|
|
* _.isLength('3');
|
|
* // => false
|
|
*/
|
|
function isLength(value) {
|
|
return typeof value == 'number' &&
|
|
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the
|
|
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
|
|
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(_.noop);
|
|
* // => true
|
|
*
|
|
* _.isObject(null);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
var type = typeof value;
|
|
return value != null && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is object-like. A value is object-like if it's not `null`
|
|
* and has a `typeof` result of "object".
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObjectLike({});
|
|
* // => true
|
|
*
|
|
* _.isObjectLike([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObjectLike(_.noop);
|
|
* // => false
|
|
*
|
|
* _.isObjectLike(null);
|
|
* // => false
|
|
*/
|
|
function isObjectLike(value) {
|
|
return value != null && typeof value == 'object';
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Map` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.3.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a map, else `false`.
|
|
* @example
|
|
*
|
|
* _.isMap(new Map);
|
|
* // => true
|
|
*
|
|
* _.isMap(new WeakMap);
|
|
* // => false
|
|
*/
|
|
var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap;
|
|
|
|
/**
|
|
* Performs a partial deep comparison between `object` and `source` to
|
|
* determine if `object` contains equivalent property values.
|
|
*
|
|
* **Note:** This method is equivalent to `_.matches` when `source` is
|
|
* partially applied.
|
|
*
|
|
* Partial comparisons will match empty array and empty object `source`
|
|
* values against any array or object value, respectively. See `_.isEqual`
|
|
* for a list of supported value comparisons.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Lang
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Object} source The object of property values to match.
|
|
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': 2 };
|
|
*
|
|
* _.isMatch(object, { 'b': 2 });
|
|
* // => true
|
|
*
|
|
* _.isMatch(object, { 'b': 1 });
|
|
* // => false
|
|
*/
|
|
function isMatch(object, source) {
|
|
return object === source || baseIsMatch(object, source, getMatchData(source));
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.isMatch` except that it accepts `customizer` which
|
|
* is invoked to compare values. If `customizer` returns `undefined`, comparisons
|
|
* are handled by the method instead. The `customizer` is invoked with five
|
|
* arguments: (objValue, srcValue, index|key, object, source).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Object} source The object of property values to match.
|
|
* @param {Function} [customizer] The function to customize comparisons.
|
|
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
|
|
* @example
|
|
*
|
|
* function isGreeting(value) {
|
|
* return /^h(?:i|ello)$/.test(value);
|
|
* }
|
|
*
|
|
* function customizer(objValue, srcValue) {
|
|
* if (isGreeting(objValue) && isGreeting(srcValue)) {
|
|
* return true;
|
|
* }
|
|
* }
|
|
*
|
|
* var object = { 'greeting': 'hello' };
|
|
* var source = { 'greeting': 'hi' };
|
|
*
|
|
* _.isMatchWith(object, source, customizer);
|
|
* // => true
|
|
*/
|
|
function isMatchWith(object, source, customizer) {
|
|
customizer = typeof customizer == 'function' ? customizer : undefined;
|
|
return baseIsMatch(object, source, getMatchData(source), customizer);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is `NaN`.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
|
|
* global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
|
|
* `undefined` and other non-number values.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
|
|
* @example
|
|
*
|
|
* _.isNaN(NaN);
|
|
* // => true
|
|
*
|
|
* _.isNaN(new Number(NaN));
|
|
* // => true
|
|
*
|
|
* isNaN(undefined);
|
|
* // => true
|
|
*
|
|
* _.isNaN(undefined);
|
|
* // => false
|
|
*/
|
|
function isNaN(value) {
|
|
// An `NaN` primitive is the only value that is not equal to itself.
|
|
// Perform the `toStringTag` check first to avoid errors with some
|
|
// ActiveX objects in IE.
|
|
return isNumber(value) && value != +value;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a pristine native function.
|
|
*
|
|
* **Note:** This method can't reliably detect native functions in the presence
|
|
* of the core-js package because core-js circumvents this kind of detection.
|
|
* Despite multiple requests, the core-js maintainer has made it clear: any
|
|
* attempt to fix the detection will be obstructed. As a result, we're left
|
|
* with little choice but to throw an error. Unfortunately, this also affects
|
|
* packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill),
|
|
* which rely on core-js.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a native function,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.isNative(Array.prototype.push);
|
|
* // => true
|
|
*
|
|
* _.isNative(_);
|
|
* // => false
|
|
*/
|
|
function isNative(value) {
|
|
if (isMaskable(value)) {
|
|
throw new Error(CORE_ERROR_TEXT);
|
|
}
|
|
return baseIsNative(value);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is `null`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is `null`, else `false`.
|
|
* @example
|
|
*
|
|
* _.isNull(null);
|
|
* // => true
|
|
*
|
|
* _.isNull(void 0);
|
|
* // => false
|
|
*/
|
|
function isNull(value) {
|
|
return value === null;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is `null` or `undefined`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is nullish, else `false`.
|
|
* @example
|
|
*
|
|
* _.isNil(null);
|
|
* // => true
|
|
*
|
|
* _.isNil(void 0);
|
|
* // => true
|
|
*
|
|
* _.isNil(NaN);
|
|
* // => false
|
|
*/
|
|
function isNil(value) {
|
|
return value == null;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Number` primitive or object.
|
|
*
|
|
* **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
|
|
* classified as numbers, use the `_.isFinite` method.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a number, else `false`.
|
|
* @example
|
|
*
|
|
* _.isNumber(3);
|
|
* // => true
|
|
*
|
|
* _.isNumber(Number.MIN_VALUE);
|
|
* // => true
|
|
*
|
|
* _.isNumber(Infinity);
|
|
* // => true
|
|
*
|
|
* _.isNumber('3');
|
|
* // => false
|
|
*/
|
|
function isNumber(value) {
|
|
return typeof value == 'number' ||
|
|
(isObjectLike(value) && baseGetTag(value) == numberTag);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a plain object, that is, an object created by the
|
|
* `Object` constructor or one with a `[[Prototype]]` of `null`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.8.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* }
|
|
*
|
|
* _.isPlainObject(new Foo);
|
|
* // => false
|
|
*
|
|
* _.isPlainObject([1, 2, 3]);
|
|
* // => false
|
|
*
|
|
* _.isPlainObject({ 'x': 0, 'y': 0 });
|
|
* // => true
|
|
*
|
|
* _.isPlainObject(Object.create(null));
|
|
* // => true
|
|
*/
|
|
function isPlainObject(value) {
|
|
if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
|
|
return false;
|
|
}
|
|
var proto = getPrototype(value);
|
|
if (proto === null) {
|
|
return true;
|
|
}
|
|
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
|
|
return typeof Ctor == 'function' && Ctor instanceof Ctor &&
|
|
funcToString.call(Ctor) == objectCtorString;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `RegExp` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
|
|
* @example
|
|
*
|
|
* _.isRegExp(/abc/);
|
|
* // => true
|
|
*
|
|
* _.isRegExp('/abc/');
|
|
* // => false
|
|
*/
|
|
var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp;
|
|
|
|
/**
|
|
* Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
|
|
* double precision number which isn't the result of a rounded unsafe integer.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.
|
|
* @example
|
|
*
|
|
* _.isSafeInteger(3);
|
|
* // => true
|
|
*
|
|
* _.isSafeInteger(Number.MIN_VALUE);
|
|
* // => false
|
|
*
|
|
* _.isSafeInteger(Infinity);
|
|
* // => false
|
|
*
|
|
* _.isSafeInteger('3');
|
|
* // => false
|
|
*/
|
|
function isSafeInteger(value) {
|
|
return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Set` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.3.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a set, else `false`.
|
|
* @example
|
|
*
|
|
* _.isSet(new Set);
|
|
* // => true
|
|
*
|
|
* _.isSet(new WeakSet);
|
|
* // => false
|
|
*/
|
|
var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet;
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `String` primitive or object.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a string, else `false`.
|
|
* @example
|
|
*
|
|
* _.isString('abc');
|
|
* // => true
|
|
*
|
|
* _.isString(1);
|
|
* // => false
|
|
*/
|
|
function isString(value) {
|
|
return typeof value == 'string' ||
|
|
(!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Symbol` primitive or object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
|
|
* @example
|
|
*
|
|
* _.isSymbol(Symbol.iterator);
|
|
* // => true
|
|
*
|
|
* _.isSymbol('abc');
|
|
* // => false
|
|
*/
|
|
function isSymbol(value) {
|
|
return typeof value == 'symbol' ||
|
|
(isObjectLike(value) && baseGetTag(value) == symbolTag);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a typed array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
|
|
* @example
|
|
*
|
|
* _.isTypedArray(new Uint8Array);
|
|
* // => true
|
|
*
|
|
* _.isTypedArray([]);
|
|
* // => false
|
|
*/
|
|
var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
|
|
|
|
/**
|
|
* Checks if `value` is `undefined`.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
|
|
* @example
|
|
*
|
|
* _.isUndefined(void 0);
|
|
* // => true
|
|
*
|
|
* _.isUndefined(null);
|
|
* // => false
|
|
*/
|
|
function isUndefined(value) {
|
|
return value === undefined;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `WeakMap` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.3.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a weak map, else `false`.
|
|
* @example
|
|
*
|
|
* _.isWeakMap(new WeakMap);
|
|
* // => true
|
|
*
|
|
* _.isWeakMap(new Map);
|
|
* // => false
|
|
*/
|
|
function isWeakMap(value) {
|
|
return isObjectLike(value) && getTag(value) == weakMapTag;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `WeakSet` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.3.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a weak set, else `false`.
|
|
* @example
|
|
*
|
|
* _.isWeakSet(new WeakSet);
|
|
* // => true
|
|
*
|
|
* _.isWeakSet(new Set);
|
|
* // => false
|
|
*/
|
|
function isWeakSet(value) {
|
|
return isObjectLike(value) && baseGetTag(value) == weakSetTag;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is less than `other`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.9.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if `value` is less than `other`,
|
|
* else `false`.
|
|
* @see _.gt
|
|
* @example
|
|
*
|
|
* _.lt(1, 3);
|
|
* // => true
|
|
*
|
|
* _.lt(3, 3);
|
|
* // => false
|
|
*
|
|
* _.lt(3, 1);
|
|
* // => false
|
|
*/
|
|
var lt = createRelationalOperation(baseLt);
|
|
|
|
/**
|
|
* Checks if `value` is less than or equal to `other`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.9.0
|
|
* @category Lang
|
|
* @param {*} value The value to compare.
|
|
* @param {*} other The other value to compare.
|
|
* @returns {boolean} Returns `true` if `value` is less than or equal to
|
|
* `other`, else `false`.
|
|
* @see _.gte
|
|
* @example
|
|
*
|
|
* _.lte(1, 3);
|
|
* // => true
|
|
*
|
|
* _.lte(3, 3);
|
|
* // => true
|
|
*
|
|
* _.lte(3, 1);
|
|
* // => false
|
|
*/
|
|
var lte = createRelationalOperation(function(value, other) {
|
|
return value <= other;
|
|
});
|
|
|
|
/**
|
|
* Converts `value` to an array.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {Array} Returns the converted array.
|
|
* @example
|
|
*
|
|
* _.toArray({ 'a': 1, 'b': 2 });
|
|
* // => [1, 2]
|
|
*
|
|
* _.toArray('abc');
|
|
* // => ['a', 'b', 'c']
|
|
*
|
|
* _.toArray(1);
|
|
* // => []
|
|
*
|
|
* _.toArray(null);
|
|
* // => []
|
|
*/
|
|
function toArray(value) {
|
|
if (!value) {
|
|
return [];
|
|
}
|
|
if (isArrayLike(value)) {
|
|
return isString(value) ? stringToArray(value) : copyArray(value);
|
|
}
|
|
if (symIterator && value[symIterator]) {
|
|
return iteratorToArray(value[symIterator]());
|
|
}
|
|
var tag = getTag(value),
|
|
func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);
|
|
|
|
return func(value);
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a finite number.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.12.0
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {number} Returns the converted number.
|
|
* @example
|
|
*
|
|
* _.toFinite(3.2);
|
|
* // => 3.2
|
|
*
|
|
* _.toFinite(Number.MIN_VALUE);
|
|
* // => 5e-324
|
|
*
|
|
* _.toFinite(Infinity);
|
|
* // => 1.7976931348623157e+308
|
|
*
|
|
* _.toFinite('3.2');
|
|
* // => 3.2
|
|
*/
|
|
function toFinite(value) {
|
|
if (!value) {
|
|
return value === 0 ? value : 0;
|
|
}
|
|
value = toNumber(value);
|
|
if (value === INFINITY || value === -INFINITY) {
|
|
var sign = (value < 0 ? -1 : 1);
|
|
return sign * MAX_INTEGER;
|
|
}
|
|
return value === value ? value : 0;
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to an integer.
|
|
*
|
|
* **Note:** This method is loosely based on
|
|
* [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {number} Returns the converted integer.
|
|
* @example
|
|
*
|
|
* _.toInteger(3.2);
|
|
* // => 3
|
|
*
|
|
* _.toInteger(Number.MIN_VALUE);
|
|
* // => 0
|
|
*
|
|
* _.toInteger(Infinity);
|
|
* // => 1.7976931348623157e+308
|
|
*
|
|
* _.toInteger('3.2');
|
|
* // => 3
|
|
*/
|
|
function toInteger(value) {
|
|
var result = toFinite(value),
|
|
remainder = result % 1;
|
|
|
|
return result === result ? (remainder ? result - remainder : result) : 0;
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to an integer suitable for use as the length of an
|
|
* array-like object.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {number} Returns the converted integer.
|
|
* @example
|
|
*
|
|
* _.toLength(3.2);
|
|
* // => 3
|
|
*
|
|
* _.toLength(Number.MIN_VALUE);
|
|
* // => 0
|
|
*
|
|
* _.toLength(Infinity);
|
|
* // => 4294967295
|
|
*
|
|
* _.toLength('3.2');
|
|
* // => 3
|
|
*/
|
|
function toLength(value) {
|
|
return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a number.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to process.
|
|
* @returns {number} Returns the number.
|
|
* @example
|
|
*
|
|
* _.toNumber(3.2);
|
|
* // => 3.2
|
|
*
|
|
* _.toNumber(Number.MIN_VALUE);
|
|
* // => 5e-324
|
|
*
|
|
* _.toNumber(Infinity);
|
|
* // => Infinity
|
|
*
|
|
* _.toNumber('3.2');
|
|
* // => 3.2
|
|
*/
|
|
function toNumber(value) {
|
|
if (typeof value == 'number') {
|
|
return value;
|
|
}
|
|
if (isSymbol(value)) {
|
|
return NAN;
|
|
}
|
|
if (isObject(value)) {
|
|
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
|
|
value = isObject(other) ? (other + '') : other;
|
|
}
|
|
if (typeof value != 'string') {
|
|
return value === 0 ? value : +value;
|
|
}
|
|
value = value.replace(reTrim, '');
|
|
var isBinary = reIsBinary.test(value);
|
|
return (isBinary || reIsOctal.test(value))
|
|
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
|
|
: (reIsBadHex.test(value) ? NAN : +value);
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a plain object flattening inherited enumerable string
|
|
* keyed properties of `value` to own properties of the plain object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {Object} Returns the converted plain object.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.assign({ 'a': 1 }, new Foo);
|
|
* // => { 'a': 1, 'b': 2 }
|
|
*
|
|
* _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
|
|
* // => { 'a': 1, 'b': 2, 'c': 3 }
|
|
*/
|
|
function toPlainObject(value) {
|
|
return copyObject(value, keysIn(value));
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a safe integer. A safe integer can be compared and
|
|
* represented correctly.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {number} Returns the converted integer.
|
|
* @example
|
|
*
|
|
* _.toSafeInteger(3.2);
|
|
* // => 3
|
|
*
|
|
* _.toSafeInteger(Number.MIN_VALUE);
|
|
* // => 0
|
|
*
|
|
* _.toSafeInteger(Infinity);
|
|
* // => 9007199254740991
|
|
*
|
|
* _.toSafeInteger('3.2');
|
|
* // => 3
|
|
*/
|
|
function toSafeInteger(value) {
|
|
return value
|
|
? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER)
|
|
: (value === 0 ? value : 0);
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a string. An empty string is returned for `null`
|
|
* and `undefined` values. The sign of `-0` is preserved.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to convert.
|
|
* @returns {string} Returns the converted string.
|
|
* @example
|
|
*
|
|
* _.toString(null);
|
|
* // => ''
|
|
*
|
|
* _.toString(-0);
|
|
* // => '-0'
|
|
*
|
|
* _.toString([1, 2, 3]);
|
|
* // => '1,2,3'
|
|
*/
|
|
function toString(value) {
|
|
return value == null ? '' : baseToString(value);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Assigns own enumerable string keyed properties of source objects to the
|
|
* destination object. Source objects are applied from left to right.
|
|
* Subsequent sources overwrite property assignments of previous sources.
|
|
*
|
|
* **Note:** This method mutates `object` and is loosely based on
|
|
* [`Object.assign`](https://mdn.io/Object/assign).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.10.0
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} [sources] The source objects.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.assignIn
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* }
|
|
*
|
|
* function Bar() {
|
|
* this.c = 3;
|
|
* }
|
|
*
|
|
* Foo.prototype.b = 2;
|
|
* Bar.prototype.d = 4;
|
|
*
|
|
* _.assign({ 'a': 0 }, new Foo, new Bar);
|
|
* // => { 'a': 1, 'c': 3 }
|
|
*/
|
|
var assign = createAssigner(function(object, source) {
|
|
if (isPrototype(source) || isArrayLike(source)) {
|
|
copyObject(source, keys(source), object);
|
|
return;
|
|
}
|
|
for (var key in source) {
|
|
if (hasOwnProperty.call(source, key)) {
|
|
assignValue(object, key, source[key]);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.assign` except that it iterates over own and
|
|
* inherited source properties.
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @alias extend
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} [sources] The source objects.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.assign
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* }
|
|
*
|
|
* function Bar() {
|
|
* this.c = 3;
|
|
* }
|
|
*
|
|
* Foo.prototype.b = 2;
|
|
* Bar.prototype.d = 4;
|
|
*
|
|
* _.assignIn({ 'a': 0 }, new Foo, new Bar);
|
|
* // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
|
|
*/
|
|
var assignIn = createAssigner(function(object, source) {
|
|
copyObject(source, keysIn(source), object);
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.assignIn` except that it accepts `customizer`
|
|
* which is invoked to produce the assigned values. If `customizer` returns
|
|
* `undefined`, assignment is handled by the method instead. The `customizer`
|
|
* is invoked with five arguments: (objValue, srcValue, key, object, source).
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @alias extendWith
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} sources The source objects.
|
|
* @param {Function} [customizer] The function to customize assigned values.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.assignWith
|
|
* @example
|
|
*
|
|
* function customizer(objValue, srcValue) {
|
|
* return _.isUndefined(objValue) ? srcValue : objValue;
|
|
* }
|
|
*
|
|
* var defaults = _.partialRight(_.assignInWith, customizer);
|
|
*
|
|
* defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
|
|
* // => { 'a': 1, 'b': 2 }
|
|
*/
|
|
var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
|
|
copyObject(source, keysIn(source), object, customizer);
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.assign` except that it accepts `customizer`
|
|
* which is invoked to produce the assigned values. If `customizer` returns
|
|
* `undefined`, assignment is handled by the method instead. The `customizer`
|
|
* is invoked with five arguments: (objValue, srcValue, key, object, source).
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} sources The source objects.
|
|
* @param {Function} [customizer] The function to customize assigned values.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.assignInWith
|
|
* @example
|
|
*
|
|
* function customizer(objValue, srcValue) {
|
|
* return _.isUndefined(objValue) ? srcValue : objValue;
|
|
* }
|
|
*
|
|
* var defaults = _.partialRight(_.assignWith, customizer);
|
|
*
|
|
* defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
|
|
* // => { 'a': 1, 'b': 2 }
|
|
*/
|
|
var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
|
|
copyObject(source, keys(source), object, customizer);
|
|
});
|
|
|
|
/**
|
|
* Creates an array of values corresponding to `paths` of `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {...(string|string[])} [paths] The property paths to pick.
|
|
* @returns {Array} Returns the picked values.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
|
|
*
|
|
* _.at(object, ['a[0].b.c', 'a[1]']);
|
|
* // => [3, 4]
|
|
*/
|
|
var at = flatRest(baseAt);
|
|
|
|
/**
|
|
* Creates an object that inherits from the `prototype` object. If a
|
|
* `properties` object is given, its own enumerable string keyed properties
|
|
* are assigned to the created object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.3.0
|
|
* @category Object
|
|
* @param {Object} prototype The object to inherit from.
|
|
* @param {Object} [properties] The properties to assign to the object.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* function Shape() {
|
|
* this.x = 0;
|
|
* this.y = 0;
|
|
* }
|
|
*
|
|
* function Circle() {
|
|
* Shape.call(this);
|
|
* }
|
|
*
|
|
* Circle.prototype = _.create(Shape.prototype, {
|
|
* 'constructor': Circle
|
|
* });
|
|
*
|
|
* var circle = new Circle;
|
|
* circle instanceof Circle;
|
|
* // => true
|
|
*
|
|
* circle instanceof Shape;
|
|
* // => true
|
|
*/
|
|
function create(prototype, properties) {
|
|
var result = baseCreate(prototype);
|
|
return properties == null ? result : baseAssign(result, properties);
|
|
}
|
|
|
|
/**
|
|
* Assigns own and inherited enumerable string keyed properties of source
|
|
* objects to the destination object for all destination properties that
|
|
* resolve to `undefined`. Source objects are applied from left to right.
|
|
* Once a property is set, additional values of the same property are ignored.
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} [sources] The source objects.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.defaultsDeep
|
|
* @example
|
|
*
|
|
* _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
|
|
* // => { 'a': 1, 'b': 2 }
|
|
*/
|
|
var defaults = baseRest(function(args) {
|
|
args.push(undefined, customDefaultsAssignIn);
|
|
return apply(assignInWith, undefined, args);
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.defaults` except that it recursively assigns
|
|
* default properties.
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.10.0
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} [sources] The source objects.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.defaults
|
|
* @example
|
|
*
|
|
* _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
|
|
* // => { 'a': { 'b': 2, 'c': 3 } }
|
|
*/
|
|
var defaultsDeep = baseRest(function(args) {
|
|
args.push(undefined, customDefaultsMerge);
|
|
return apply(mergeWith, undefined, args);
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.find` except that it returns the key of the first
|
|
* element `predicate` returns truthy for instead of the element itself.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.1.0
|
|
* @category Object
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {string|undefined} Returns the key of the matched element,
|
|
* else `undefined`.
|
|
* @example
|
|
*
|
|
* var users = {
|
|
* 'barney': { 'age': 36, 'active': true },
|
|
* 'fred': { 'age': 40, 'active': false },
|
|
* 'pebbles': { 'age': 1, 'active': true }
|
|
* };
|
|
*
|
|
* _.findKey(users, function(o) { return o.age < 40; });
|
|
* // => 'barney' (iteration order is not guaranteed)
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.findKey(users, { 'age': 1, 'active': true });
|
|
* // => 'pebbles'
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.findKey(users, ['active', false]);
|
|
* // => 'fred'
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.findKey(users, 'active');
|
|
* // => 'barney'
|
|
*/
|
|
function findKey(object, predicate) {
|
|
return baseFindKey(object, getIteratee(predicate, 3), baseForOwn);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.findKey` except that it iterates over elements of
|
|
* a collection in the opposite order.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to inspect.
|
|
* @param {Function} [predicate=_.identity] The function invoked per iteration.
|
|
* @returns {string|undefined} Returns the key of the matched element,
|
|
* else `undefined`.
|
|
* @example
|
|
*
|
|
* var users = {
|
|
* 'barney': { 'age': 36, 'active': true },
|
|
* 'fred': { 'age': 40, 'active': false },
|
|
* 'pebbles': { 'age': 1, 'active': true }
|
|
* };
|
|
*
|
|
* _.findLastKey(users, function(o) { return o.age < 40; });
|
|
* // => returns 'pebbles' assuming `_.findKey` returns 'barney'
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.findLastKey(users, { 'age': 36, 'active': true });
|
|
* // => 'barney'
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.findLastKey(users, ['active', false]);
|
|
* // => 'fred'
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.findLastKey(users, 'active');
|
|
* // => 'pebbles'
|
|
*/
|
|
function findLastKey(object, predicate) {
|
|
return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight);
|
|
}
|
|
|
|
/**
|
|
* Iterates over own and inherited enumerable string keyed properties of an
|
|
* object and invokes `iteratee` for each property. The iteratee is invoked
|
|
* with three arguments: (value, key, object). Iteratee functions may exit
|
|
* iteration early by explicitly returning `false`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.3.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.forInRight
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.forIn(new Foo, function(value, key) {
|
|
* console.log(key);
|
|
* });
|
|
* // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
|
|
*/
|
|
function forIn(object, iteratee) {
|
|
return object == null
|
|
? object
|
|
: baseFor(object, getIteratee(iteratee, 3), keysIn);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.forIn` except that it iterates over properties of
|
|
* `object` in the opposite order.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.forIn
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.forInRight(new Foo, function(value, key) {
|
|
* console.log(key);
|
|
* });
|
|
* // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
|
|
*/
|
|
function forInRight(object, iteratee) {
|
|
return object == null
|
|
? object
|
|
: baseForRight(object, getIteratee(iteratee, 3), keysIn);
|
|
}
|
|
|
|
/**
|
|
* Iterates over own enumerable string keyed properties of an object and
|
|
* invokes `iteratee` for each property. The iteratee is invoked with three
|
|
* arguments: (value, key, object). Iteratee functions may exit iteration
|
|
* early by explicitly returning `false`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.3.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.forOwnRight
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.forOwn(new Foo, function(value, key) {
|
|
* console.log(key);
|
|
* });
|
|
* // => Logs 'a' then 'b' (iteration order is not guaranteed).
|
|
*/
|
|
function forOwn(object, iteratee) {
|
|
return object && baseForOwn(object, getIteratee(iteratee, 3));
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.forOwn` except that it iterates over properties of
|
|
* `object` in the opposite order.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Object} Returns `object`.
|
|
* @see _.forOwn
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.forOwnRight(new Foo, function(value, key) {
|
|
* console.log(key);
|
|
* });
|
|
* // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
|
|
*/
|
|
function forOwnRight(object, iteratee) {
|
|
return object && baseForOwnRight(object, getIteratee(iteratee, 3));
|
|
}
|
|
|
|
/**
|
|
* Creates an array of function property names from own enumerable properties
|
|
* of `object`.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to inspect.
|
|
* @returns {Array} Returns the function names.
|
|
* @see _.functionsIn
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = _.constant('a');
|
|
* this.b = _.constant('b');
|
|
* }
|
|
*
|
|
* Foo.prototype.c = _.constant('c');
|
|
*
|
|
* _.functions(new Foo);
|
|
* // => ['a', 'b']
|
|
*/
|
|
function functions(object) {
|
|
return object == null ? [] : baseFunctions(object, keys(object));
|
|
}
|
|
|
|
/**
|
|
* Creates an array of function property names from own and inherited
|
|
* enumerable properties of `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to inspect.
|
|
* @returns {Array} Returns the function names.
|
|
* @see _.functions
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = _.constant('a');
|
|
* this.b = _.constant('b');
|
|
* }
|
|
*
|
|
* Foo.prototype.c = _.constant('c');
|
|
*
|
|
* _.functionsIn(new Foo);
|
|
* // => ['a', 'b', 'c']
|
|
*/
|
|
function functionsIn(object) {
|
|
return object == null ? [] : baseFunctions(object, keysIn(object));
|
|
}
|
|
|
|
/**
|
|
* Gets the value at `path` of `object`. If the resolved value is
|
|
* `undefined`, the `defaultValue` is returned in its place.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.7.0
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path of the property to get.
|
|
* @param {*} [defaultValue] The value returned for `undefined` resolved values.
|
|
* @returns {*} Returns the resolved value.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
|
|
*
|
|
* _.get(object, 'a[0].b.c');
|
|
* // => 3
|
|
*
|
|
* _.get(object, ['a', '0', 'b', 'c']);
|
|
* // => 3
|
|
*
|
|
* _.get(object, 'a.b.c', 'default');
|
|
* // => 'default'
|
|
*/
|
|
function get(object, path, defaultValue) {
|
|
var result = object == null ? undefined : baseGet(object, path);
|
|
return result === undefined ? defaultValue : result;
|
|
}
|
|
|
|
/**
|
|
* Checks if `path` is a direct property of `object`.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path to check.
|
|
* @returns {boolean} Returns `true` if `path` exists, else `false`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': { 'b': 2 } };
|
|
* var other = _.create({ 'a': _.create({ 'b': 2 }) });
|
|
*
|
|
* _.has(object, 'a');
|
|
* // => true
|
|
*
|
|
* _.has(object, 'a.b');
|
|
* // => true
|
|
*
|
|
* _.has(object, ['a', 'b']);
|
|
* // => true
|
|
*
|
|
* _.has(other, 'a');
|
|
* // => false
|
|
*/
|
|
function has(object, path) {
|
|
return object != null && hasPath(object, path, baseHas);
|
|
}
|
|
|
|
/**
|
|
* Checks if `path` is a direct or inherited property of `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path to check.
|
|
* @returns {boolean} Returns `true` if `path` exists, else `false`.
|
|
* @example
|
|
*
|
|
* var object = _.create({ 'a': _.create({ 'b': 2 }) });
|
|
*
|
|
* _.hasIn(object, 'a');
|
|
* // => true
|
|
*
|
|
* _.hasIn(object, 'a.b');
|
|
* // => true
|
|
*
|
|
* _.hasIn(object, ['a', 'b']);
|
|
* // => true
|
|
*
|
|
* _.hasIn(object, 'b');
|
|
* // => false
|
|
*/
|
|
function hasIn(object, path) {
|
|
return object != null && hasPath(object, path, baseHasIn);
|
|
}
|
|
|
|
/**
|
|
* Creates an object composed of the inverted keys and values of `object`.
|
|
* If `object` contains duplicate values, subsequent values overwrite
|
|
* property assignments of previous values.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.7.0
|
|
* @category Object
|
|
* @param {Object} object The object to invert.
|
|
* @returns {Object} Returns the new inverted object.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': 2, 'c': 1 };
|
|
*
|
|
* _.invert(object);
|
|
* // => { '1': 'c', '2': 'b' }
|
|
*/
|
|
var invert = createInverter(function(result, value, key) {
|
|
result[value] = key;
|
|
}, constant(identity));
|
|
|
|
/**
|
|
* This method is like `_.invert` except that the inverted object is generated
|
|
* from the results of running each element of `object` thru `iteratee`. The
|
|
* corresponding inverted value of each inverted key is an array of keys
|
|
* responsible for generating the inverted value. The iteratee is invoked
|
|
* with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.1.0
|
|
* @category Object
|
|
* @param {Object} object The object to invert.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {Object} Returns the new inverted object.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': 2, 'c': 1 };
|
|
*
|
|
* _.invertBy(object);
|
|
* // => { '1': ['a', 'c'], '2': ['b'] }
|
|
*
|
|
* _.invertBy(object, function(value) {
|
|
* return 'group' + value;
|
|
* });
|
|
* // => { 'group1': ['a', 'c'], 'group2': ['b'] }
|
|
*/
|
|
var invertBy = createInverter(function(result, value, key) {
|
|
if (hasOwnProperty.call(result, value)) {
|
|
result[value].push(key);
|
|
} else {
|
|
result[value] = [key];
|
|
}
|
|
}, getIteratee);
|
|
|
|
/**
|
|
* Invokes the method at `path` of `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path of the method to invoke.
|
|
* @param {...*} [args] The arguments to invoke the method with.
|
|
* @returns {*} Returns the result of the invoked method.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
|
|
*
|
|
* _.invoke(object, 'a[0].b.c.slice', 1, 3);
|
|
* // => [2, 3]
|
|
*/
|
|
var invoke = baseRest(baseInvoke);
|
|
|
|
/**
|
|
* Creates an array of the own enumerable property names of `object`.
|
|
*
|
|
* **Note:** Non-object values are coerced to objects. See the
|
|
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
|
|
* for more details.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.keys(new Foo);
|
|
* // => ['a', 'b'] (iteration order is not guaranteed)
|
|
*
|
|
* _.keys('hi');
|
|
* // => ['0', '1']
|
|
*/
|
|
function keys(object) {
|
|
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of the own and inherited enumerable property names of `object`.
|
|
*
|
|
* **Note:** Non-object values are coerced to objects.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.keysIn(new Foo);
|
|
* // => ['a', 'b', 'c'] (iteration order is not guaranteed)
|
|
*/
|
|
function keysIn(object) {
|
|
return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
|
|
}
|
|
|
|
/**
|
|
* The opposite of `_.mapValues`; this method creates an object with the
|
|
* same values as `object` and keys generated by running each own enumerable
|
|
* string keyed property of `object` thru `iteratee`. The iteratee is invoked
|
|
* with three arguments: (value, key, object).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.8.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Object} Returns the new mapped object.
|
|
* @see _.mapValues
|
|
* @example
|
|
*
|
|
* _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
|
|
* return key + value;
|
|
* });
|
|
* // => { 'a1': 1, 'b2': 2 }
|
|
*/
|
|
function mapKeys(object, iteratee) {
|
|
var result = {};
|
|
iteratee = getIteratee(iteratee, 3);
|
|
|
|
baseForOwn(object, function(value, key, object) {
|
|
baseAssignValue(result, iteratee(value, key, object), value);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates an object with the same keys as `object` and values generated
|
|
* by running each own enumerable string keyed property of `object` thru
|
|
* `iteratee`. The iteratee is invoked with three arguments:
|
|
* (value, key, object).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.4.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Object} Returns the new mapped object.
|
|
* @see _.mapKeys
|
|
* @example
|
|
*
|
|
* var users = {
|
|
* 'fred': { 'user': 'fred', 'age': 40 },
|
|
* 'pebbles': { 'user': 'pebbles', 'age': 1 }
|
|
* };
|
|
*
|
|
* _.mapValues(users, function(o) { return o.age; });
|
|
* // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.mapValues(users, 'age');
|
|
* // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
|
|
*/
|
|
function mapValues(object, iteratee) {
|
|
var result = {};
|
|
iteratee = getIteratee(iteratee, 3);
|
|
|
|
baseForOwn(object, function(value, key, object) {
|
|
baseAssignValue(result, key, iteratee(value, key, object));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.assign` except that it recursively merges own and
|
|
* inherited enumerable string keyed properties of source objects into the
|
|
* destination object. Source properties that resolve to `undefined` are
|
|
* skipped if a destination value exists. Array and plain object properties
|
|
* are merged recursively. Other objects and value types are overridden by
|
|
* assignment. Source objects are applied from left to right. Subsequent
|
|
* sources overwrite property assignments of previous sources.
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.5.0
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} [sources] The source objects.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* var object = {
|
|
* 'a': [{ 'b': 2 }, { 'd': 4 }]
|
|
* };
|
|
*
|
|
* var other = {
|
|
* 'a': [{ 'c': 3 }, { 'e': 5 }]
|
|
* };
|
|
*
|
|
* _.merge(object, other);
|
|
* // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
|
|
*/
|
|
var merge = createAssigner(function(object, source, srcIndex) {
|
|
baseMerge(object, source, srcIndex);
|
|
});
|
|
|
|
/**
|
|
* This method is like `_.merge` except that it accepts `customizer` which
|
|
* is invoked to produce the merged values of the destination and source
|
|
* properties. If `customizer` returns `undefined`, merging is handled by the
|
|
* method instead. The `customizer` is invoked with six arguments:
|
|
* (objValue, srcValue, key, object, source, stack).
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} sources The source objects.
|
|
* @param {Function} customizer The function to customize assigned values.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* function customizer(objValue, srcValue) {
|
|
* if (_.isArray(objValue)) {
|
|
* return objValue.concat(srcValue);
|
|
* }
|
|
* }
|
|
*
|
|
* var object = { 'a': [1], 'b': [2] };
|
|
* var other = { 'a': [3], 'b': [4] };
|
|
*
|
|
* _.mergeWith(object, other, customizer);
|
|
* // => { 'a': [1, 3], 'b': [2, 4] }
|
|
*/
|
|
var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
|
|
baseMerge(object, source, srcIndex, customizer);
|
|
});
|
|
|
|
/**
|
|
* The opposite of `_.pick`; this method creates an object composed of the
|
|
* own and inherited enumerable property paths of `object` that are not omitted.
|
|
*
|
|
* **Note:** This method is considerably slower than `_.pick`.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The source object.
|
|
* @param {...(string|string[])} [paths] The property paths to omit.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': '2', 'c': 3 };
|
|
*
|
|
* _.omit(object, ['a', 'c']);
|
|
* // => { 'b': '2' }
|
|
*/
|
|
var omit = flatRest(function(object, paths) {
|
|
var result = {};
|
|
if (object == null) {
|
|
return result;
|
|
}
|
|
var isDeep = false;
|
|
paths = arrayMap(paths, function(path) {
|
|
path = castPath(path, object);
|
|
isDeep || (isDeep = path.length > 1);
|
|
return path;
|
|
});
|
|
copyObject(object, getAllKeysIn(object), result);
|
|
if (isDeep) {
|
|
result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone);
|
|
}
|
|
var length = paths.length;
|
|
while (length--) {
|
|
baseUnset(result, paths[length]);
|
|
}
|
|
return result;
|
|
});
|
|
|
|
/**
|
|
* The opposite of `_.pickBy`; this method creates an object composed of
|
|
* the own and inherited enumerable string keyed properties of `object` that
|
|
* `predicate` doesn't return truthy for. The predicate is invoked with two
|
|
* arguments: (value, key).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The source object.
|
|
* @param {Function} [predicate=_.identity] The function invoked per property.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': '2', 'c': 3 };
|
|
*
|
|
* _.omitBy(object, _.isNumber);
|
|
* // => { 'b': '2' }
|
|
*/
|
|
function omitBy(object, predicate) {
|
|
return pickBy(object, negate(getIteratee(predicate)));
|
|
}
|
|
|
|
/**
|
|
* Creates an object composed of the picked `object` properties.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The source object.
|
|
* @param {...(string|string[])} [paths] The property paths to pick.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': '2', 'c': 3 };
|
|
*
|
|
* _.pick(object, ['a', 'c']);
|
|
* // => { 'a': 1, 'c': 3 }
|
|
*/
|
|
var pick = flatRest(function(object, paths) {
|
|
return object == null ? {} : basePick(object, paths);
|
|
});
|
|
|
|
/**
|
|
* Creates an object composed of the `object` properties `predicate` returns
|
|
* truthy for. The predicate is invoked with two arguments: (value, key).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The source object.
|
|
* @param {Function} [predicate=_.identity] The function invoked per property.
|
|
* @returns {Object} Returns the new object.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1, 'b': '2', 'c': 3 };
|
|
*
|
|
* _.pickBy(object, _.isNumber);
|
|
* // => { 'a': 1, 'c': 3 }
|
|
*/
|
|
function pickBy(object, predicate) {
|
|
if (object == null) {
|
|
return {};
|
|
}
|
|
var props = arrayMap(getAllKeysIn(object), function(prop) {
|
|
return [prop];
|
|
});
|
|
predicate = getIteratee(predicate);
|
|
return basePickBy(object, props, function(value, path) {
|
|
return predicate(value, path[0]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.get` except that if the resolved value is a
|
|
* function it's invoked with the `this` binding of its parent object and
|
|
* its result is returned.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @param {Array|string} path The path of the property to resolve.
|
|
* @param {*} [defaultValue] The value returned for `undefined` resolved values.
|
|
* @returns {*} Returns the resolved value.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
|
|
*
|
|
* _.result(object, 'a[0].b.c1');
|
|
* // => 3
|
|
*
|
|
* _.result(object, 'a[0].b.c2');
|
|
* // => 4
|
|
*
|
|
* _.result(object, 'a[0].b.c3', 'default');
|
|
* // => 'default'
|
|
*
|
|
* _.result(object, 'a[0].b.c3', _.constant('default'));
|
|
* // => 'default'
|
|
*/
|
|
function result(object, path, defaultValue) {
|
|
path = castPath(path, object);
|
|
|
|
var index = -1,
|
|
length = path.length;
|
|
|
|
// Ensure the loop is entered when path is empty.
|
|
if (!length) {
|
|
length = 1;
|
|
object = undefined;
|
|
}
|
|
while (++index < length) {
|
|
var value = object == null ? undefined : object[toKey(path[index])];
|
|
if (value === undefined) {
|
|
index = length;
|
|
value = defaultValue;
|
|
}
|
|
object = isFunction(value) ? value.call(object) : value;
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
|
|
* it's created. Arrays are created for missing index properties while objects
|
|
* are created for all other missing properties. Use `_.setWith` to customize
|
|
* `path` creation.
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.7.0
|
|
* @category Object
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to set.
|
|
* @param {*} value The value to set.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
|
|
*
|
|
* _.set(object, 'a[0].b.c', 4);
|
|
* console.log(object.a[0].b.c);
|
|
* // => 4
|
|
*
|
|
* _.set(object, ['x', '0', 'y', 'z'], 5);
|
|
* console.log(object.x[0].y.z);
|
|
* // => 5
|
|
*/
|
|
function set(object, path, value) {
|
|
return object == null ? object : baseSet(object, path, value);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.set` except that it accepts `customizer` which is
|
|
* invoked to produce the objects of `path`. If `customizer` returns `undefined`
|
|
* path creation is handled by the method instead. The `customizer` is invoked
|
|
* with three arguments: (nsValue, key, nsObject).
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to set.
|
|
* @param {*} value The value to set.
|
|
* @param {Function} [customizer] The function to customize assigned values.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* var object = {};
|
|
*
|
|
* _.setWith(object, '[0][1]', 'a', Object);
|
|
* // => { '0': { '1': 'a' } }
|
|
*/
|
|
function setWith(object, path, value, customizer) {
|
|
customizer = typeof customizer == 'function' ? customizer : undefined;
|
|
return object == null ? object : baseSet(object, path, value, customizer);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of own enumerable string keyed-value pairs for `object`
|
|
* which can be consumed by `_.fromPairs`. If `object` is a map or set, its
|
|
* entries are returned.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @alias entries
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the key-value pairs.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.toPairs(new Foo);
|
|
* // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
|
|
*/
|
|
var toPairs = createToPairs(keys);
|
|
|
|
/**
|
|
* Creates an array of own and inherited enumerable string keyed-value pairs
|
|
* for `object` which can be consumed by `_.fromPairs`. If `object` is a map
|
|
* or set, its entries are returned.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @alias entriesIn
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the key-value pairs.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.toPairsIn(new Foo);
|
|
* // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed)
|
|
*/
|
|
var toPairsIn = createToPairs(keysIn);
|
|
|
|
/**
|
|
* An alternative to `_.reduce`; this method transforms `object` to a new
|
|
* `accumulator` object which is the result of running each of its own
|
|
* enumerable string keyed properties thru `iteratee`, with each invocation
|
|
* potentially mutating the `accumulator` object. If `accumulator` is not
|
|
* provided, a new object with the same `[[Prototype]]` will be used. The
|
|
* iteratee is invoked with four arguments: (accumulator, value, key, object).
|
|
* Iteratee functions may exit iteration early by explicitly returning `false`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.3.0
|
|
* @category Object
|
|
* @param {Object} object The object to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @param {*} [accumulator] The custom accumulator value.
|
|
* @returns {*} Returns the accumulated value.
|
|
* @example
|
|
*
|
|
* _.transform([2, 3, 4], function(result, n) {
|
|
* result.push(n *= n);
|
|
* return n % 2 == 0;
|
|
* }, []);
|
|
* // => [4, 9]
|
|
*
|
|
* _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
|
|
* (result[value] || (result[value] = [])).push(key);
|
|
* }, {});
|
|
* // => { '1': ['a', 'c'], '2': ['b'] }
|
|
*/
|
|
function transform(object, iteratee, accumulator) {
|
|
var isArr = isArray(object),
|
|
isArrLike = isArr || isBuffer(object) || isTypedArray(object);
|
|
|
|
iteratee = getIteratee(iteratee, 4);
|
|
if (accumulator == null) {
|
|
var Ctor = object && object.constructor;
|
|
if (isArrLike) {
|
|
accumulator = isArr ? new Ctor : [];
|
|
}
|
|
else if (isObject(object)) {
|
|
accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
|
|
}
|
|
else {
|
|
accumulator = {};
|
|
}
|
|
}
|
|
(isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) {
|
|
return iteratee(accumulator, value, index, object);
|
|
});
|
|
return accumulator;
|
|
}
|
|
|
|
/**
|
|
* Removes the property at `path` of `object`.
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to unset.
|
|
* @returns {boolean} Returns `true` if the property is deleted, else `false`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': 7 } }] };
|
|
* _.unset(object, 'a[0].b.c');
|
|
* // => true
|
|
*
|
|
* console.log(object);
|
|
* // => { 'a': [{ 'b': {} }] };
|
|
*
|
|
* _.unset(object, ['a', '0', 'b', 'c']);
|
|
* // => true
|
|
*
|
|
* console.log(object);
|
|
* // => { 'a': [{ 'b': {} }] };
|
|
*/
|
|
function unset(object, path) {
|
|
return object == null ? true : baseUnset(object, path);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.set` except that accepts `updater` to produce the
|
|
* value to set. Use `_.updateWith` to customize `path` creation. The `updater`
|
|
* is invoked with one argument: (value).
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.6.0
|
|
* @category Object
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to set.
|
|
* @param {Function} updater The function to produce the updated value.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
|
|
*
|
|
* _.update(object, 'a[0].b.c', function(n) { return n * n; });
|
|
* console.log(object.a[0].b.c);
|
|
* // => 9
|
|
*
|
|
* _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
|
|
* console.log(object.x[0].y.z);
|
|
* // => 0
|
|
*/
|
|
function update(object, path, updater) {
|
|
return object == null ? object : baseUpdate(object, path, castFunction(updater));
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.update` except that it accepts `customizer` which is
|
|
* invoked to produce the objects of `path`. If `customizer` returns `undefined`
|
|
* path creation is handled by the method instead. The `customizer` is invoked
|
|
* with three arguments: (nsValue, key, nsObject).
|
|
*
|
|
* **Note:** This method mutates `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.6.0
|
|
* @category Object
|
|
* @param {Object} object The object to modify.
|
|
* @param {Array|string} path The path of the property to set.
|
|
* @param {Function} updater The function to produce the updated value.
|
|
* @param {Function} [customizer] The function to customize assigned values.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* var object = {};
|
|
*
|
|
* _.updateWith(object, '[0][1]', _.constant('a'), Object);
|
|
* // => { '0': { '1': 'a' } }
|
|
*/
|
|
function updateWith(object, path, updater, customizer) {
|
|
customizer = typeof customizer == 'function' ? customizer : undefined;
|
|
return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of the own enumerable string keyed property values of `object`.
|
|
*
|
|
* **Note:** Non-object values are coerced to objects.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property values.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.values(new Foo);
|
|
* // => [1, 2] (iteration order is not guaranteed)
|
|
*
|
|
* _.values('hi');
|
|
* // => ['h', 'i']
|
|
*/
|
|
function values(object) {
|
|
return object == null ? [] : baseValues(object, keys(object));
|
|
}
|
|
|
|
/**
|
|
* Creates an array of the own and inherited enumerable string keyed property
|
|
* values of `object`.
|
|
*
|
|
* **Note:** Non-object values are coerced to objects.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property values.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.valuesIn(new Foo);
|
|
* // => [1, 2, 3] (iteration order is not guaranteed)
|
|
*/
|
|
function valuesIn(object) {
|
|
return object == null ? [] : baseValues(object, keysIn(object));
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Clamps `number` within the inclusive `lower` and `upper` bounds.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Number
|
|
* @param {number} number The number to clamp.
|
|
* @param {number} [lower] The lower bound.
|
|
* @param {number} upper The upper bound.
|
|
* @returns {number} Returns the clamped number.
|
|
* @example
|
|
*
|
|
* _.clamp(-10, -5, 5);
|
|
* // => -5
|
|
*
|
|
* _.clamp(10, -5, 5);
|
|
* // => 5
|
|
*/
|
|
function clamp(number, lower, upper) {
|
|
if (upper === undefined) {
|
|
upper = lower;
|
|
lower = undefined;
|
|
}
|
|
if (upper !== undefined) {
|
|
upper = toNumber(upper);
|
|
upper = upper === upper ? upper : 0;
|
|
}
|
|
if (lower !== undefined) {
|
|
lower = toNumber(lower);
|
|
lower = lower === lower ? lower : 0;
|
|
}
|
|
return baseClamp(toNumber(number), lower, upper);
|
|
}
|
|
|
|
/**
|
|
* Checks if `n` is between `start` and up to, but not including, `end`. If
|
|
* `end` is not specified, it's set to `start` with `start` then set to `0`.
|
|
* If `start` is greater than `end` the params are swapped to support
|
|
* negative ranges.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.3.0
|
|
* @category Number
|
|
* @param {number} number The number to check.
|
|
* @param {number} [start=0] The start of the range.
|
|
* @param {number} end The end of the range.
|
|
* @returns {boolean} Returns `true` if `number` is in the range, else `false`.
|
|
* @see _.range, _.rangeRight
|
|
* @example
|
|
*
|
|
* _.inRange(3, 2, 4);
|
|
* // => true
|
|
*
|
|
* _.inRange(4, 8);
|
|
* // => true
|
|
*
|
|
* _.inRange(4, 2);
|
|
* // => false
|
|
*
|
|
* _.inRange(2, 2);
|
|
* // => false
|
|
*
|
|
* _.inRange(1.2, 2);
|
|
* // => true
|
|
*
|
|
* _.inRange(5.2, 4);
|
|
* // => false
|
|
*
|
|
* _.inRange(-3, -2, -6);
|
|
* // => true
|
|
*/
|
|
function inRange(number, start, end) {
|
|
start = toFinite(start);
|
|
if (end === undefined) {
|
|
end = start;
|
|
start = 0;
|
|
} else {
|
|
end = toFinite(end);
|
|
}
|
|
number = toNumber(number);
|
|
return baseInRange(number, start, end);
|
|
}
|
|
|
|
/**
|
|
* Produces a random number between the inclusive `lower` and `upper` bounds.
|
|
* If only one argument is provided a number between `0` and the given number
|
|
* is returned. If `floating` is `true`, or either `lower` or `upper` are
|
|
* floats, a floating-point number is returned instead of an integer.
|
|
*
|
|
* **Note:** JavaScript follows the IEEE-754 standard for resolving
|
|
* floating-point values which can produce unexpected results.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.7.0
|
|
* @category Number
|
|
* @param {number} [lower=0] The lower bound.
|
|
* @param {number} [upper=1] The upper bound.
|
|
* @param {boolean} [floating] Specify returning a floating-point number.
|
|
* @returns {number} Returns the random number.
|
|
* @example
|
|
*
|
|
* _.random(0, 5);
|
|
* // => an integer between 0 and 5
|
|
*
|
|
* _.random(5);
|
|
* // => also an integer between 0 and 5
|
|
*
|
|
* _.random(5, true);
|
|
* // => a floating-point number between 0 and 5
|
|
*
|
|
* _.random(1.2, 5.2);
|
|
* // => a floating-point number between 1.2 and 5.2
|
|
*/
|
|
function random(lower, upper, floating) {
|
|
if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
|
|
upper = floating = undefined;
|
|
}
|
|
if (floating === undefined) {
|
|
if (typeof upper == 'boolean') {
|
|
floating = upper;
|
|
upper = undefined;
|
|
}
|
|
else if (typeof lower == 'boolean') {
|
|
floating = lower;
|
|
lower = undefined;
|
|
}
|
|
}
|
|
if (lower === undefined && upper === undefined) {
|
|
lower = 0;
|
|
upper = 1;
|
|
}
|
|
else {
|
|
lower = toFinite(lower);
|
|
if (upper === undefined) {
|
|
upper = lower;
|
|
lower = 0;
|
|
} else {
|
|
upper = toFinite(upper);
|
|
}
|
|
}
|
|
if (lower > upper) {
|
|
var temp = lower;
|
|
lower = upper;
|
|
upper = temp;
|
|
}
|
|
if (floating || lower % 1 || upper % 1) {
|
|
var rand = nativeRandom();
|
|
return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
|
|
}
|
|
return baseRandom(lower, upper);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the camel cased string.
|
|
* @example
|
|
*
|
|
* _.camelCase('Foo Bar');
|
|
* // => 'fooBar'
|
|
*
|
|
* _.camelCase('--foo-bar--');
|
|
* // => 'fooBar'
|
|
*
|
|
* _.camelCase('__FOO_BAR__');
|
|
* // => 'fooBar'
|
|
*/
|
|
var camelCase = createCompounder(function(result, word, index) {
|
|
word = word.toLowerCase();
|
|
return result + (index ? capitalize(word) : word);
|
|
});
|
|
|
|
/**
|
|
* Converts the first character of `string` to upper case and the remaining
|
|
* to lower case.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to capitalize.
|
|
* @returns {string} Returns the capitalized string.
|
|
* @example
|
|
*
|
|
* _.capitalize('FRED');
|
|
* // => 'Fred'
|
|
*/
|
|
function capitalize(string) {
|
|
return upperFirst(toString(string).toLowerCase());
|
|
}
|
|
|
|
/**
|
|
* Deburrs `string` by converting
|
|
* [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
|
|
* and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)
|
|
* letters to basic Latin letters and removing
|
|
* [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to deburr.
|
|
* @returns {string} Returns the deburred string.
|
|
* @example
|
|
*
|
|
* _.deburr('déjà vu');
|
|
* // => 'deja vu'
|
|
*/
|
|
function deburr(string) {
|
|
string = toString(string);
|
|
return string && string.replace(reLatin, deburrLetter).replace(reComboMark, '');
|
|
}
|
|
|
|
/**
|
|
* Checks if `string` ends with the given target string.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to inspect.
|
|
* @param {string} [target] The string to search for.
|
|
* @param {number} [position=string.length] The position to search up to.
|
|
* @returns {boolean} Returns `true` if `string` ends with `target`,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.endsWith('abc', 'c');
|
|
* // => true
|
|
*
|
|
* _.endsWith('abc', 'b');
|
|
* // => false
|
|
*
|
|
* _.endsWith('abc', 'b', 2);
|
|
* // => true
|
|
*/
|
|
function endsWith(string, target, position) {
|
|
string = toString(string);
|
|
target = baseToString(target);
|
|
|
|
var length = string.length;
|
|
position = position === undefined
|
|
? length
|
|
: baseClamp(toInteger(position), 0, length);
|
|
|
|
var end = position;
|
|
position -= target.length;
|
|
return position >= 0 && string.slice(position, end) == target;
|
|
}
|
|
|
|
/**
|
|
* Converts the characters "&", "<", ">", '"', and "'" in `string` to their
|
|
* corresponding HTML entities.
|
|
*
|
|
* **Note:** No other characters are escaped. To escape additional
|
|
* characters use a third-party library like [_he_](https://mths.be/he).
|
|
*
|
|
* Though the ">" character is escaped for symmetry, characters like
|
|
* ">" and "/" don't need escaping in HTML and have no special meaning
|
|
* unless they're part of a tag or unquoted attribute value. See
|
|
* [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
|
|
* (under "semi-related fun fact") for more details.
|
|
*
|
|
* When working with HTML you should always
|
|
* [quote attribute values](http://wonko.com/post/html-escaping) to reduce
|
|
* XSS vectors.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category String
|
|
* @param {string} [string=''] The string to escape.
|
|
* @returns {string} Returns the escaped string.
|
|
* @example
|
|
*
|
|
* _.escape('fred, barney, & pebbles');
|
|
* // => 'fred, barney, & pebbles'
|
|
*/
|
|
function escape(string) {
|
|
string = toString(string);
|
|
return (string && reHasUnescapedHtml.test(string))
|
|
? string.replace(reUnescapedHtml, escapeHtmlChar)
|
|
: string;
|
|
}
|
|
|
|
/**
|
|
* Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
|
|
* "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to escape.
|
|
* @returns {string} Returns the escaped string.
|
|
* @example
|
|
*
|
|
* _.escapeRegExp('[lodash](https://lodash.com/)');
|
|
* // => '\[lodash\]\(https://lodash\.com/\)'
|
|
*/
|
|
function escapeRegExp(string) {
|
|
string = toString(string);
|
|
return (string && reHasRegExpChar.test(string))
|
|
? string.replace(reRegExpChar, '\\$&')
|
|
: string;
|
|
}
|
|
|
|
/**
|
|
* Converts `string` to
|
|
* [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the kebab cased string.
|
|
* @example
|
|
*
|
|
* _.kebabCase('Foo Bar');
|
|
* // => 'foo-bar'
|
|
*
|
|
* _.kebabCase('fooBar');
|
|
* // => 'foo-bar'
|
|
*
|
|
* _.kebabCase('__FOO_BAR__');
|
|
* // => 'foo-bar'
|
|
*/
|
|
var kebabCase = createCompounder(function(result, word, index) {
|
|
return result + (index ? '-' : '') + word.toLowerCase();
|
|
});
|
|
|
|
/**
|
|
* Converts `string`, as space separated words, to lower case.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the lower cased string.
|
|
* @example
|
|
*
|
|
* _.lowerCase('--Foo-Bar--');
|
|
* // => 'foo bar'
|
|
*
|
|
* _.lowerCase('fooBar');
|
|
* // => 'foo bar'
|
|
*
|
|
* _.lowerCase('__FOO_BAR__');
|
|
* // => 'foo bar'
|
|
*/
|
|
var lowerCase = createCompounder(function(result, word, index) {
|
|
return result + (index ? ' ' : '') + word.toLowerCase();
|
|
});
|
|
|
|
/**
|
|
* Converts the first character of `string` to lower case.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the converted string.
|
|
* @example
|
|
*
|
|
* _.lowerFirst('Fred');
|
|
* // => 'fred'
|
|
*
|
|
* _.lowerFirst('FRED');
|
|
* // => 'fRED'
|
|
*/
|
|
var lowerFirst = createCaseFirst('toLowerCase');
|
|
|
|
/**
|
|
* Pads `string` on the left and right sides if it's shorter than `length`.
|
|
* Padding characters are truncated if they can't be evenly divided by `length`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to pad.
|
|
* @param {number} [length=0] The padding length.
|
|
* @param {string} [chars=' '] The string used as padding.
|
|
* @returns {string} Returns the padded string.
|
|
* @example
|
|
*
|
|
* _.pad('abc', 8);
|
|
* // => ' abc '
|
|
*
|
|
* _.pad('abc', 8, '_-');
|
|
* // => '_-abc_-_'
|
|
*
|
|
* _.pad('abc', 3);
|
|
* // => 'abc'
|
|
*/
|
|
function pad(string, length, chars) {
|
|
string = toString(string);
|
|
length = toInteger(length);
|
|
|
|
var strLength = length ? stringSize(string) : 0;
|
|
if (!length || strLength >= length) {
|
|
return string;
|
|
}
|
|
var mid = (length - strLength) / 2;
|
|
return (
|
|
createPadding(nativeFloor(mid), chars) +
|
|
string +
|
|
createPadding(nativeCeil(mid), chars)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Pads `string` on the right side if it's shorter than `length`. Padding
|
|
* characters are truncated if they exceed `length`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to pad.
|
|
* @param {number} [length=0] The padding length.
|
|
* @param {string} [chars=' '] The string used as padding.
|
|
* @returns {string} Returns the padded string.
|
|
* @example
|
|
*
|
|
* _.padEnd('abc', 6);
|
|
* // => 'abc '
|
|
*
|
|
* _.padEnd('abc', 6, '_-');
|
|
* // => 'abc_-_'
|
|
*
|
|
* _.padEnd('abc', 3);
|
|
* // => 'abc'
|
|
*/
|
|
function padEnd(string, length, chars) {
|
|
string = toString(string);
|
|
length = toInteger(length);
|
|
|
|
var strLength = length ? stringSize(string) : 0;
|
|
return (length && strLength < length)
|
|
? (string + createPadding(length - strLength, chars))
|
|
: string;
|
|
}
|
|
|
|
/**
|
|
* Pads `string` on the left side if it's shorter than `length`. Padding
|
|
* characters are truncated if they exceed `length`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to pad.
|
|
* @param {number} [length=0] The padding length.
|
|
* @param {string} [chars=' '] The string used as padding.
|
|
* @returns {string} Returns the padded string.
|
|
* @example
|
|
*
|
|
* _.padStart('abc', 6);
|
|
* // => ' abc'
|
|
*
|
|
* _.padStart('abc', 6, '_-');
|
|
* // => '_-_abc'
|
|
*
|
|
* _.padStart('abc', 3);
|
|
* // => 'abc'
|
|
*/
|
|
function padStart(string, length, chars) {
|
|
string = toString(string);
|
|
length = toInteger(length);
|
|
|
|
var strLength = length ? stringSize(string) : 0;
|
|
return (length && strLength < length)
|
|
? (createPadding(length - strLength, chars) + string)
|
|
: string;
|
|
}
|
|
|
|
/**
|
|
* Converts `string` to an integer of the specified radix. If `radix` is
|
|
* `undefined` or `0`, a `radix` of `10` is used unless `value` is a
|
|
* hexadecimal, in which case a `radix` of `16` is used.
|
|
*
|
|
* **Note:** This method aligns with the
|
|
* [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 1.1.0
|
|
* @category String
|
|
* @param {string} string The string to convert.
|
|
* @param {number} [radix=10] The radix to interpret `value` by.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {number} Returns the converted integer.
|
|
* @example
|
|
*
|
|
* _.parseInt('08');
|
|
* // => 8
|
|
*
|
|
* _.map(['6', '08', '10'], _.parseInt);
|
|
* // => [6, 8, 10]
|
|
*/
|
|
function parseInt(string, radix, guard) {
|
|
if (guard || radix == null) {
|
|
radix = 0;
|
|
} else if (radix) {
|
|
radix = +radix;
|
|
}
|
|
return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0);
|
|
}
|
|
|
|
/**
|
|
* Repeats the given string `n` times.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to repeat.
|
|
* @param {number} [n=1] The number of times to repeat the string.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {string} Returns the repeated string.
|
|
* @example
|
|
*
|
|
* _.repeat('*', 3);
|
|
* // => '***'
|
|
*
|
|
* _.repeat('abc', 2);
|
|
* // => 'abcabc'
|
|
*
|
|
* _.repeat('abc', 0);
|
|
* // => ''
|
|
*/
|
|
function repeat(string, n, guard) {
|
|
if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) {
|
|
n = 1;
|
|
} else {
|
|
n = toInteger(n);
|
|
}
|
|
return baseRepeat(toString(string), n);
|
|
}
|
|
|
|
/**
|
|
* Replaces matches for `pattern` in `string` with `replacement`.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`String#replace`](https://mdn.io/String/replace).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to modify.
|
|
* @param {RegExp|string} pattern The pattern to replace.
|
|
* @param {Function|string} replacement The match replacement.
|
|
* @returns {string} Returns the modified string.
|
|
* @example
|
|
*
|
|
* _.replace('Hi Fred', 'Fred', 'Barney');
|
|
* // => 'Hi Barney'
|
|
*/
|
|
function replace() {
|
|
var args = arguments,
|
|
string = toString(args[0]);
|
|
|
|
return args.length < 3 ? string : string.replace(args[1], args[2]);
|
|
}
|
|
|
|
/**
|
|
* Converts `string` to
|
|
* [snake case](https://en.wikipedia.org/wiki/Snake_case).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the snake cased string.
|
|
* @example
|
|
*
|
|
* _.snakeCase('Foo Bar');
|
|
* // => 'foo_bar'
|
|
*
|
|
* _.snakeCase('fooBar');
|
|
* // => 'foo_bar'
|
|
*
|
|
* _.snakeCase('--FOO-BAR--');
|
|
* // => 'foo_bar'
|
|
*/
|
|
var snakeCase = createCompounder(function(result, word, index) {
|
|
return result + (index ? '_' : '') + word.toLowerCase();
|
|
});
|
|
|
|
/**
|
|
* Splits `string` by `separator`.
|
|
*
|
|
* **Note:** This method is based on
|
|
* [`String#split`](https://mdn.io/String/split).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to split.
|
|
* @param {RegExp|string} separator The separator pattern to split by.
|
|
* @param {number} [limit] The length to truncate results to.
|
|
* @returns {Array} Returns the string segments.
|
|
* @example
|
|
*
|
|
* _.split('a-b-c', '-', 2);
|
|
* // => ['a', 'b']
|
|
*/
|
|
function split(string, separator, limit) {
|
|
if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
|
|
separator = limit = undefined;
|
|
}
|
|
limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0;
|
|
if (!limit) {
|
|
return [];
|
|
}
|
|
string = toString(string);
|
|
if (string && (
|
|
typeof separator == 'string' ||
|
|
(separator != null && !isRegExp(separator))
|
|
)) {
|
|
separator = baseToString(separator);
|
|
if (!separator && hasUnicode(string)) {
|
|
return castSlice(stringToArray(string), 0, limit);
|
|
}
|
|
}
|
|
return string.split(separator, limit);
|
|
}
|
|
|
|
/**
|
|
* Converts `string` to
|
|
* [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.1.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the start cased string.
|
|
* @example
|
|
*
|
|
* _.startCase('--foo-bar--');
|
|
* // => 'Foo Bar'
|
|
*
|
|
* _.startCase('fooBar');
|
|
* // => 'Foo Bar'
|
|
*
|
|
* _.startCase('__FOO_BAR__');
|
|
* // => 'FOO BAR'
|
|
*/
|
|
var startCase = createCompounder(function(result, word, index) {
|
|
return result + (index ? ' ' : '') + upperFirst(word);
|
|
});
|
|
|
|
/**
|
|
* Checks if `string` starts with the given target string.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to inspect.
|
|
* @param {string} [target] The string to search for.
|
|
* @param {number} [position=0] The position to search from.
|
|
* @returns {boolean} Returns `true` if `string` starts with `target`,
|
|
* else `false`.
|
|
* @example
|
|
*
|
|
* _.startsWith('abc', 'a');
|
|
* // => true
|
|
*
|
|
* _.startsWith('abc', 'b');
|
|
* // => false
|
|
*
|
|
* _.startsWith('abc', 'b', 1);
|
|
* // => true
|
|
*/
|
|
function startsWith(string, target, position) {
|
|
string = toString(string);
|
|
position = position == null
|
|
? 0
|
|
: baseClamp(toInteger(position), 0, string.length);
|
|
|
|
target = baseToString(target);
|
|
return string.slice(position, position + target.length) == target;
|
|
}
|
|
|
|
/**
|
|
* Creates a compiled template function that can interpolate data properties
|
|
* in "interpolate" delimiters, HTML-escape interpolated data properties in
|
|
* "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
|
|
* properties may be accessed as free variables in the template. If a setting
|
|
* object is given, it takes precedence over `_.templateSettings` values.
|
|
*
|
|
* **Note:** In the development build `_.template` utilizes
|
|
* [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
|
|
* for easier debugging.
|
|
*
|
|
* For more information on precompiling templates see
|
|
* [lodash's custom builds documentation](https://lodash.com/custom-builds).
|
|
*
|
|
* For more information on Chrome extension sandboxes see
|
|
* [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category String
|
|
* @param {string} [string=''] The template string.
|
|
* @param {Object} [options={}] The options object.
|
|
* @param {RegExp} [options.escape=_.templateSettings.escape]
|
|
* The HTML "escape" delimiter.
|
|
* @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
|
|
* The "evaluate" delimiter.
|
|
* @param {Object} [options.imports=_.templateSettings.imports]
|
|
* An object to import into the template as free variables.
|
|
* @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
|
|
* The "interpolate" delimiter.
|
|
* @param {string} [options.sourceURL='lodash.templateSources[n]']
|
|
* The sourceURL of the compiled template.
|
|
* @param {string} [options.variable='obj']
|
|
* The data object variable name.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Function} Returns the compiled template function.
|
|
* @example
|
|
*
|
|
* // Use the "interpolate" delimiter to create a compiled template.
|
|
* var compiled = _.template('hello <%= user %>!');
|
|
* compiled({ 'user': 'fred' });
|
|
* // => 'hello fred!'
|
|
*
|
|
* // Use the HTML "escape" delimiter to escape data property values.
|
|
* var compiled = _.template('<b><%- value %></b>');
|
|
* compiled({ 'value': '<script>' });
|
|
* // => '<b><script></b>'
|
|
*
|
|
* // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
|
|
* var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
|
|
* compiled({ 'users': ['fred', 'barney'] });
|
|
* // => '<li>fred</li><li>barney</li>'
|
|
*
|
|
* // Use the internal `print` function in "evaluate" delimiters.
|
|
* var compiled = _.template('<% print("hello " + user); %>!');
|
|
* compiled({ 'user': 'barney' });
|
|
* // => 'hello barney!'
|
|
*
|
|
* // Use the ES template literal delimiter as an "interpolate" delimiter.
|
|
* // Disable support by replacing the "interpolate" delimiter.
|
|
* var compiled = _.template('hello ${ user }!');
|
|
* compiled({ 'user': 'pebbles' });
|
|
* // => 'hello pebbles!'
|
|
*
|
|
* // Use backslashes to treat delimiters as plain text.
|
|
* var compiled = _.template('<%= "\\<%- value %\\>" %>');
|
|
* compiled({ 'value': 'ignored' });
|
|
* // => '<%- value %>'
|
|
*
|
|
* // Use the `imports` option to import `jQuery` as `jq`.
|
|
* var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
|
|
* var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
|
|
* compiled({ 'users': ['fred', 'barney'] });
|
|
* // => '<li>fred</li><li>barney</li>'
|
|
*
|
|
* // Use the `sourceURL` option to specify a custom sourceURL for the template.
|
|
* var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
|
|
* compiled(data);
|
|
* // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
|
|
*
|
|
* // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
|
|
* var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
|
|
* compiled.source;
|
|
* // => function(data) {
|
|
* // var __t, __p = '';
|
|
* // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
|
|
* // return __p;
|
|
* // }
|
|
*
|
|
* // Use custom template delimiters.
|
|
* _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
|
* var compiled = _.template('hello {{ user }}!');
|
|
* compiled({ 'user': 'mustache' });
|
|
* // => 'hello mustache!'
|
|
*
|
|
* // Use the `source` property to inline compiled templates for meaningful
|
|
* // line numbers in error messages and stack traces.
|
|
* fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\
|
|
* var JST = {\
|
|
* "main": ' + _.template(mainText).source + '\
|
|
* };\
|
|
* ');
|
|
*/
|
|
function template(string, options, guard) {
|
|
// Based on John Resig's `tmpl` implementation
|
|
// (http://ejohn.org/blog/javascript-micro-templating/)
|
|
// and Laura Doktorova's doT.js (https://github.com/olado/doT).
|
|
var settings = lodash.templateSettings;
|
|
|
|
if (guard && isIterateeCall(string, options, guard)) {
|
|
options = undefined;
|
|
}
|
|
string = toString(string);
|
|
options = assignInWith({}, options, settings, customDefaultsAssignIn);
|
|
|
|
var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn),
|
|
importsKeys = keys(imports),
|
|
importsValues = baseValues(imports, importsKeys);
|
|
|
|
var isEscaping,
|
|
isEvaluating,
|
|
index = 0,
|
|
interpolate = options.interpolate || reNoMatch,
|
|
source = "__p += '";
|
|
|
|
// Compile the regexp to match each delimiter.
|
|
var reDelimiters = RegExp(
|
|
(options.escape || reNoMatch).source + '|' +
|
|
interpolate.source + '|' +
|
|
(interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
|
|
(options.evaluate || reNoMatch).source + '|$'
|
|
, 'g');
|
|
|
|
// Use a sourceURL for easier debugging.
|
|
var sourceURL = '//# sourceURL=' +
|
|
('sourceURL' in options
|
|
? options.sourceURL
|
|
: ('lodash.templateSources[' + (++templateCounter) + ']')
|
|
) + '\n';
|
|
|
|
string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
|
|
interpolateValue || (interpolateValue = esTemplateValue);
|
|
|
|
// Escape characters that can't be included in string literals.
|
|
source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
|
|
|
|
// Replace delimiters with snippets.
|
|
if (escapeValue) {
|
|
isEscaping = true;
|
|
source += "' +\n__e(" + escapeValue + ") +\n'";
|
|
}
|
|
if (evaluateValue) {
|
|
isEvaluating = true;
|
|
source += "';\n" + evaluateValue + ";\n__p += '";
|
|
}
|
|
if (interpolateValue) {
|
|
source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
|
|
}
|
|
index = offset + match.length;
|
|
|
|
// The JS engine embedded in Adobe products needs `match` returned in
|
|
// order to produce the correct `offset` value.
|
|
return match;
|
|
});
|
|
|
|
source += "';\n";
|
|
|
|
// If `variable` is not specified wrap a with-statement around the generated
|
|
// code to add the data object to the top of the scope chain.
|
|
var variable = options.variable;
|
|
if (!variable) {
|
|
source = 'with (obj) {\n' + source + '\n}\n';
|
|
}
|
|
// Cleanup code by stripping empty strings.
|
|
source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
|
|
.replace(reEmptyStringMiddle, '$1')
|
|
.replace(reEmptyStringTrailing, '$1;');
|
|
|
|
// Frame code as the function body.
|
|
source = 'function(' + (variable || 'obj') + ') {\n' +
|
|
(variable
|
|
? ''
|
|
: 'obj || (obj = {});\n'
|
|
) +
|
|
"var __t, __p = ''" +
|
|
(isEscaping
|
|
? ', __e = _.escape'
|
|
: ''
|
|
) +
|
|
(isEvaluating
|
|
? ', __j = Array.prototype.join;\n' +
|
|
"function print() { __p += __j.call(arguments, '') }\n"
|
|
: ';\n'
|
|
) +
|
|
source +
|
|
'return __p\n}';
|
|
|
|
var result = attempt(function() {
|
|
return Function(importsKeys, sourceURL + 'return ' + source)
|
|
.apply(undefined, importsValues);
|
|
});
|
|
|
|
// Provide the compiled function's source by its `toString` method or
|
|
// the `source` property as a convenience for inlining compiled templates.
|
|
result.source = source;
|
|
if (isError(result)) {
|
|
throw result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts `string`, as a whole, to lower case just like
|
|
* [String#toLowerCase](https://mdn.io/toLowerCase).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the lower cased string.
|
|
* @example
|
|
*
|
|
* _.toLower('--Foo-Bar--');
|
|
* // => '--foo-bar--'
|
|
*
|
|
* _.toLower('fooBar');
|
|
* // => 'foobar'
|
|
*
|
|
* _.toLower('__FOO_BAR__');
|
|
* // => '__foo_bar__'
|
|
*/
|
|
function toLower(value) {
|
|
return toString(value).toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Converts `string`, as a whole, to upper case just like
|
|
* [String#toUpperCase](https://mdn.io/toUpperCase).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the upper cased string.
|
|
* @example
|
|
*
|
|
* _.toUpper('--foo-bar--');
|
|
* // => '--FOO-BAR--'
|
|
*
|
|
* _.toUpper('fooBar');
|
|
* // => 'FOOBAR'
|
|
*
|
|
* _.toUpper('__foo_bar__');
|
|
* // => '__FOO_BAR__'
|
|
*/
|
|
function toUpper(value) {
|
|
return toString(value).toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Removes leading and trailing whitespace or specified characters from `string`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to trim.
|
|
* @param {string} [chars=whitespace] The characters to trim.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {string} Returns the trimmed string.
|
|
* @example
|
|
*
|
|
* _.trim(' abc ');
|
|
* // => 'abc'
|
|
*
|
|
* _.trim('-_-abc-_-', '_-');
|
|
* // => 'abc'
|
|
*
|
|
* _.map([' foo ', ' bar '], _.trim);
|
|
* // => ['foo', 'bar']
|
|
*/
|
|
function trim(string, chars, guard) {
|
|
string = toString(string);
|
|
if (string && (guard || chars === undefined)) {
|
|
return string.replace(reTrim, '');
|
|
}
|
|
if (!string || !(chars = baseToString(chars))) {
|
|
return string;
|
|
}
|
|
var strSymbols = stringToArray(string),
|
|
chrSymbols = stringToArray(chars),
|
|
start = charsStartIndex(strSymbols, chrSymbols),
|
|
end = charsEndIndex(strSymbols, chrSymbols) + 1;
|
|
|
|
return castSlice(strSymbols, start, end).join('');
|
|
}
|
|
|
|
/**
|
|
* Removes trailing whitespace or specified characters from `string`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to trim.
|
|
* @param {string} [chars=whitespace] The characters to trim.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {string} Returns the trimmed string.
|
|
* @example
|
|
*
|
|
* _.trimEnd(' abc ');
|
|
* // => ' abc'
|
|
*
|
|
* _.trimEnd('-_-abc-_-', '_-');
|
|
* // => '-_-abc'
|
|
*/
|
|
function trimEnd(string, chars, guard) {
|
|
string = toString(string);
|
|
if (string && (guard || chars === undefined)) {
|
|
return string.replace(reTrimEnd, '');
|
|
}
|
|
if (!string || !(chars = baseToString(chars))) {
|
|
return string;
|
|
}
|
|
var strSymbols = stringToArray(string),
|
|
end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;
|
|
|
|
return castSlice(strSymbols, 0, end).join('');
|
|
}
|
|
|
|
/**
|
|
* Removes leading whitespace or specified characters from `string`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to trim.
|
|
* @param {string} [chars=whitespace] The characters to trim.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {string} Returns the trimmed string.
|
|
* @example
|
|
*
|
|
* _.trimStart(' abc ');
|
|
* // => 'abc '
|
|
*
|
|
* _.trimStart('-_-abc-_-', '_-');
|
|
* // => 'abc-_-'
|
|
*/
|
|
function trimStart(string, chars, guard) {
|
|
string = toString(string);
|
|
if (string && (guard || chars === undefined)) {
|
|
return string.replace(reTrimStart, '');
|
|
}
|
|
if (!string || !(chars = baseToString(chars))) {
|
|
return string;
|
|
}
|
|
var strSymbols = stringToArray(string),
|
|
start = charsStartIndex(strSymbols, stringToArray(chars));
|
|
|
|
return castSlice(strSymbols, start).join('');
|
|
}
|
|
|
|
/**
|
|
* Truncates `string` if it's longer than the given maximum string length.
|
|
* The last characters of the truncated string are replaced with the omission
|
|
* string which defaults to "...".
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to truncate.
|
|
* @param {Object} [options={}] The options object.
|
|
* @param {number} [options.length=30] The maximum string length.
|
|
* @param {string} [options.omission='...'] The string to indicate text is omitted.
|
|
* @param {RegExp|string} [options.separator] The separator pattern to truncate to.
|
|
* @returns {string} Returns the truncated string.
|
|
* @example
|
|
*
|
|
* _.truncate('hi-diddly-ho there, neighborino');
|
|
* // => 'hi-diddly-ho there, neighbo...'
|
|
*
|
|
* _.truncate('hi-diddly-ho there, neighborino', {
|
|
* 'length': 24,
|
|
* 'separator': ' '
|
|
* });
|
|
* // => 'hi-diddly-ho there,...'
|
|
*
|
|
* _.truncate('hi-diddly-ho there, neighborino', {
|
|
* 'length': 24,
|
|
* 'separator': /,? +/
|
|
* });
|
|
* // => 'hi-diddly-ho there...'
|
|
*
|
|
* _.truncate('hi-diddly-ho there, neighborino', {
|
|
* 'omission': ' [...]'
|
|
* });
|
|
* // => 'hi-diddly-ho there, neig [...]'
|
|
*/
|
|
function truncate(string, options) {
|
|
var length = DEFAULT_TRUNC_LENGTH,
|
|
omission = DEFAULT_TRUNC_OMISSION;
|
|
|
|
if (isObject(options)) {
|
|
var separator = 'separator' in options ? options.separator : separator;
|
|
length = 'length' in options ? toInteger(options.length) : length;
|
|
omission = 'omission' in options ? baseToString(options.omission) : omission;
|
|
}
|
|
string = toString(string);
|
|
|
|
var strLength = string.length;
|
|
if (hasUnicode(string)) {
|
|
var strSymbols = stringToArray(string);
|
|
strLength = strSymbols.length;
|
|
}
|
|
if (length >= strLength) {
|
|
return string;
|
|
}
|
|
var end = length - stringSize(omission);
|
|
if (end < 1) {
|
|
return omission;
|
|
}
|
|
var result = strSymbols
|
|
? castSlice(strSymbols, 0, end).join('')
|
|
: string.slice(0, end);
|
|
|
|
if (separator === undefined) {
|
|
return result + omission;
|
|
}
|
|
if (strSymbols) {
|
|
end += (result.length - end);
|
|
}
|
|
if (isRegExp(separator)) {
|
|
if (string.slice(end).search(separator)) {
|
|
var match,
|
|
substring = result;
|
|
|
|
if (!separator.global) {
|
|
separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
|
|
}
|
|
separator.lastIndex = 0;
|
|
while ((match = separator.exec(substring))) {
|
|
var newEnd = match.index;
|
|
}
|
|
result = result.slice(0, newEnd === undefined ? end : newEnd);
|
|
}
|
|
} else if (string.indexOf(baseToString(separator), end) != end) {
|
|
var index = result.lastIndexOf(separator);
|
|
if (index > -1) {
|
|
result = result.slice(0, index);
|
|
}
|
|
}
|
|
return result + omission;
|
|
}
|
|
|
|
/**
|
|
* The inverse of `_.escape`; this method converts the HTML entities
|
|
* `&`, `<`, `>`, `"`, and `'` in `string` to
|
|
* their corresponding characters.
|
|
*
|
|
* **Note:** No other HTML entities are unescaped. To unescape additional
|
|
* HTML entities use a third-party library like [_he_](https://mths.be/he).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.6.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to unescape.
|
|
* @returns {string} Returns the unescaped string.
|
|
* @example
|
|
*
|
|
* _.unescape('fred, barney, & pebbles');
|
|
* // => 'fred, barney, & pebbles'
|
|
*/
|
|
function unescape(string) {
|
|
string = toString(string);
|
|
return (string && reHasEscapedHtml.test(string))
|
|
? string.replace(reEscapedHtml, unescapeHtmlChar)
|
|
: string;
|
|
}
|
|
|
|
/**
|
|
* Converts `string`, as space separated words, to upper case.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the upper cased string.
|
|
* @example
|
|
*
|
|
* _.upperCase('--foo-bar');
|
|
* // => 'FOO BAR'
|
|
*
|
|
* _.upperCase('fooBar');
|
|
* // => 'FOO BAR'
|
|
*
|
|
* _.upperCase('__foo_bar__');
|
|
* // => 'FOO BAR'
|
|
*/
|
|
var upperCase = createCompounder(function(result, word, index) {
|
|
return result + (index ? ' ' : '') + word.toUpperCase();
|
|
});
|
|
|
|
/**
|
|
* Converts the first character of `string` to upper case.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to convert.
|
|
* @returns {string} Returns the converted string.
|
|
* @example
|
|
*
|
|
* _.upperFirst('fred');
|
|
* // => 'Fred'
|
|
*
|
|
* _.upperFirst('FRED');
|
|
* // => 'FRED'
|
|
*/
|
|
var upperFirst = createCaseFirst('toUpperCase');
|
|
|
|
/**
|
|
* Splits `string` into an array of its words.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category String
|
|
* @param {string} [string=''] The string to inspect.
|
|
* @param {RegExp|string} [pattern] The pattern to match words.
|
|
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
|
|
* @returns {Array} Returns the words of `string`.
|
|
* @example
|
|
*
|
|
* _.words('fred, barney, & pebbles');
|
|
* // => ['fred', 'barney', 'pebbles']
|
|
*
|
|
* _.words('fred, barney, & pebbles', /[^, ]+/g);
|
|
* // => ['fred', 'barney', '&', 'pebbles']
|
|
*/
|
|
function words(string, pattern, guard) {
|
|
string = toString(string);
|
|
pattern = guard ? undefined : pattern;
|
|
|
|
if (pattern === undefined) {
|
|
return hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string);
|
|
}
|
|
return string.match(pattern) || [];
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Attempts to invoke `func`, returning either the result or the caught error
|
|
* object. Any additional arguments are provided to `func` when it's invoked.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Util
|
|
* @param {Function} func The function to attempt.
|
|
* @param {...*} [args] The arguments to invoke `func` with.
|
|
* @returns {*} Returns the `func` result or error object.
|
|
* @example
|
|
*
|
|
* // Avoid throwing errors for invalid selectors.
|
|
* var elements = _.attempt(function(selector) {
|
|
* return document.querySelectorAll(selector);
|
|
* }, '>_>');
|
|
*
|
|
* if (_.isError(elements)) {
|
|
* elements = [];
|
|
* }
|
|
*/
|
|
var attempt = baseRest(function(func, args) {
|
|
try {
|
|
return apply(func, undefined, args);
|
|
} catch (e) {
|
|
return isError(e) ? e : new Error(e);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Binds methods of an object to the object itself, overwriting the existing
|
|
* method.
|
|
*
|
|
* **Note:** This method doesn't set the "length" property of bound functions.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {Object} object The object to bind and assign the bound methods to.
|
|
* @param {...(string|string[])} methodNames The object method names to bind.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* var view = {
|
|
* 'label': 'docs',
|
|
* 'click': function() {
|
|
* console.log('clicked ' + this.label);
|
|
* }
|
|
* };
|
|
*
|
|
* _.bindAll(view, ['click']);
|
|
* jQuery(element).on('click', view.click);
|
|
* // => Logs 'clicked docs' when clicked.
|
|
*/
|
|
var bindAll = flatRest(function(object, methodNames) {
|
|
arrayEach(methodNames, function(key) {
|
|
key = toKey(key);
|
|
baseAssignValue(object, key, bind(object[key], object));
|
|
});
|
|
return object;
|
|
});
|
|
|
|
/**
|
|
* Creates a function that iterates over `pairs` and invokes the corresponding
|
|
* function of the first predicate to return truthy. The predicate-function
|
|
* pairs are invoked with the `this` binding and arguments of the created
|
|
* function.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {Array} pairs The predicate-function pairs.
|
|
* @returns {Function} Returns the new composite function.
|
|
* @example
|
|
*
|
|
* var func = _.cond([
|
|
* [_.matches({ 'a': 1 }), _.constant('matches A')],
|
|
* [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
|
|
* [_.stubTrue, _.constant('no match')]
|
|
* ]);
|
|
*
|
|
* func({ 'a': 1, 'b': 2 });
|
|
* // => 'matches A'
|
|
*
|
|
* func({ 'a': 0, 'b': 1 });
|
|
* // => 'matches B'
|
|
*
|
|
* func({ 'a': '1', 'b': '2' });
|
|
* // => 'no match'
|
|
*/
|
|
function cond(pairs) {
|
|
var length = pairs == null ? 0 : pairs.length,
|
|
toIteratee = getIteratee();
|
|
|
|
pairs = !length ? [] : arrayMap(pairs, function(pair) {
|
|
if (typeof pair[1] != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
return [toIteratee(pair[0]), pair[1]];
|
|
});
|
|
|
|
return baseRest(function(args) {
|
|
var index = -1;
|
|
while (++index < length) {
|
|
var pair = pairs[index];
|
|
if (apply(pair[0], this, args)) {
|
|
return apply(pair[1], this, args);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes the predicate properties of `source` with
|
|
* the corresponding property values of a given object, returning `true` if
|
|
* all predicates return truthy, else `false`.
|
|
*
|
|
* **Note:** The created function is equivalent to `_.conformsTo` with
|
|
* `source` partially applied.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {Object} source The object of property predicates to conform to.
|
|
* @returns {Function} Returns the new spec function.
|
|
* @example
|
|
*
|
|
* var objects = [
|
|
* { 'a': 2, 'b': 1 },
|
|
* { 'a': 1, 'b': 2 }
|
|
* ];
|
|
*
|
|
* _.filter(objects, _.conforms({ 'b': function(n) { return n > 1; } }));
|
|
* // => [{ 'a': 1, 'b': 2 }]
|
|
*/
|
|
function conforms(source) {
|
|
return baseConforms(baseClone(source, CLONE_DEEP_FLAG));
|
|
}
|
|
|
|
/**
|
|
* Creates a function that returns `value`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.4.0
|
|
* @category Util
|
|
* @param {*} value The value to return from the new function.
|
|
* @returns {Function} Returns the new constant function.
|
|
* @example
|
|
*
|
|
* var objects = _.times(2, _.constant({ 'a': 1 }));
|
|
*
|
|
* console.log(objects);
|
|
* // => [{ 'a': 1 }, { 'a': 1 }]
|
|
*
|
|
* console.log(objects[0] === objects[1]);
|
|
* // => true
|
|
*/
|
|
function constant(value) {
|
|
return function() {
|
|
return value;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Checks `value` to determine whether a default value should be returned in
|
|
* its place. The `defaultValue` is returned if `value` is `NaN`, `null`,
|
|
* or `undefined`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.14.0
|
|
* @category Util
|
|
* @param {*} value The value to check.
|
|
* @param {*} defaultValue The default value.
|
|
* @returns {*} Returns the resolved value.
|
|
* @example
|
|
*
|
|
* _.defaultTo(1, 10);
|
|
* // => 1
|
|
*
|
|
* _.defaultTo(undefined, 10);
|
|
* // => 10
|
|
*/
|
|
function defaultTo(value, defaultValue) {
|
|
return (value == null || value !== value) ? defaultValue : value;
|
|
}
|
|
|
|
/**
|
|
* Creates a function that returns the result of invoking the given functions
|
|
* with the `this` binding of the created function, where each successive
|
|
* invocation is supplied the return value of the previous.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Util
|
|
* @param {...(Function|Function[])} [funcs] The functions to invoke.
|
|
* @returns {Function} Returns the new composite function.
|
|
* @see _.flowRight
|
|
* @example
|
|
*
|
|
* function square(n) {
|
|
* return n * n;
|
|
* }
|
|
*
|
|
* var addSquare = _.flow([_.add, square]);
|
|
* addSquare(1, 2);
|
|
* // => 9
|
|
*/
|
|
var flow = createFlow();
|
|
|
|
/**
|
|
* This method is like `_.flow` except that it creates a function that
|
|
* invokes the given functions from right to left.
|
|
*
|
|
* @static
|
|
* @since 3.0.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {...(Function|Function[])} [funcs] The functions to invoke.
|
|
* @returns {Function} Returns the new composite function.
|
|
* @see _.flow
|
|
* @example
|
|
*
|
|
* function square(n) {
|
|
* return n * n;
|
|
* }
|
|
*
|
|
* var addSquare = _.flowRight([square, _.add]);
|
|
* addSquare(1, 2);
|
|
* // => 9
|
|
*/
|
|
var flowRight = createFlow(true);
|
|
|
|
/**
|
|
* This method returns the first argument it receives.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {*} value Any value.
|
|
* @returns {*} Returns `value`.
|
|
* @example
|
|
*
|
|
* var object = { 'a': 1 };
|
|
*
|
|
* console.log(_.identity(object) === object);
|
|
* // => true
|
|
*/
|
|
function identity(value) {
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with the arguments of the created
|
|
* function. If `func` is a property name, the created function returns the
|
|
* property value for a given element. If `func` is an array or object, the
|
|
* created function returns `true` for elements that contain the equivalent
|
|
* source properties, otherwise it returns `false`.
|
|
*
|
|
* @static
|
|
* @since 4.0.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {*} [func=_.identity] The value to convert to a callback.
|
|
* @returns {Function} Returns the callback.
|
|
* @example
|
|
*
|
|
* var users = [
|
|
* { 'user': 'barney', 'age': 36, 'active': true },
|
|
* { 'user': 'fred', 'age': 40, 'active': false }
|
|
* ];
|
|
*
|
|
* // The `_.matches` iteratee shorthand.
|
|
* _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
|
|
* // => [{ 'user': 'barney', 'age': 36, 'active': true }]
|
|
*
|
|
* // The `_.matchesProperty` iteratee shorthand.
|
|
* _.filter(users, _.iteratee(['user', 'fred']));
|
|
* // => [{ 'user': 'fred', 'age': 40 }]
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.map(users, _.iteratee('user'));
|
|
* // => ['barney', 'fred']
|
|
*
|
|
* // Create custom iteratee shorthands.
|
|
* _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
|
|
* return !_.isRegExp(func) ? iteratee(func) : function(string) {
|
|
* return func.test(string);
|
|
* };
|
|
* });
|
|
*
|
|
* _.filter(['abc', 'def'], /ef/);
|
|
* // => ['def']
|
|
*/
|
|
function iteratee(func) {
|
|
return baseIteratee(typeof func == 'function' ? func : baseClone(func, CLONE_DEEP_FLAG));
|
|
}
|
|
|
|
/**
|
|
* Creates a function that performs a partial deep comparison between a given
|
|
* object and `source`, returning `true` if the given object has equivalent
|
|
* property values, else `false`.
|
|
*
|
|
* **Note:** The created function is equivalent to `_.isMatch` with `source`
|
|
* partially applied.
|
|
*
|
|
* Partial comparisons will match empty array and empty object `source`
|
|
* values against any array or object value, respectively. See `_.isEqual`
|
|
* for a list of supported value comparisons.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Util
|
|
* @param {Object} source The object of property values to match.
|
|
* @returns {Function} Returns the new spec function.
|
|
* @example
|
|
*
|
|
* var objects = [
|
|
* { 'a': 1, 'b': 2, 'c': 3 },
|
|
* { 'a': 4, 'b': 5, 'c': 6 }
|
|
* ];
|
|
*
|
|
* _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
|
|
* // => [{ 'a': 4, 'b': 5, 'c': 6 }]
|
|
*/
|
|
function matches(source) {
|
|
return baseMatches(baseClone(source, CLONE_DEEP_FLAG));
|
|
}
|
|
|
|
/**
|
|
* Creates a function that performs a partial deep comparison between the
|
|
* value at `path` of a given object to `srcValue`, returning `true` if the
|
|
* object value is equivalent, else `false`.
|
|
*
|
|
* **Note:** Partial comparisons will match empty array and empty object
|
|
* `srcValue` values against any array or object value, respectively. See
|
|
* `_.isEqual` for a list of supported value comparisons.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.2.0
|
|
* @category Util
|
|
* @param {Array|string} path The path of the property to get.
|
|
* @param {*} srcValue The value to match.
|
|
* @returns {Function} Returns the new spec function.
|
|
* @example
|
|
*
|
|
* var objects = [
|
|
* { 'a': 1, 'b': 2, 'c': 3 },
|
|
* { 'a': 4, 'b': 5, 'c': 6 }
|
|
* ];
|
|
*
|
|
* _.find(objects, _.matchesProperty('a', 4));
|
|
* // => { 'a': 4, 'b': 5, 'c': 6 }
|
|
*/
|
|
function matchesProperty(path, srcValue) {
|
|
return baseMatchesProperty(path, baseClone(srcValue, CLONE_DEEP_FLAG));
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes the method at `path` of a given object.
|
|
* Any additional arguments are provided to the invoked method.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.7.0
|
|
* @category Util
|
|
* @param {Array|string} path The path of the method to invoke.
|
|
* @param {...*} [args] The arguments to invoke the method with.
|
|
* @returns {Function} Returns the new invoker function.
|
|
* @example
|
|
*
|
|
* var objects = [
|
|
* { 'a': { 'b': _.constant(2) } },
|
|
* { 'a': { 'b': _.constant(1) } }
|
|
* ];
|
|
*
|
|
* _.map(objects, _.method('a.b'));
|
|
* // => [2, 1]
|
|
*
|
|
* _.map(objects, _.method(['a', 'b']));
|
|
* // => [2, 1]
|
|
*/
|
|
var method = baseRest(function(path, args) {
|
|
return function(object) {
|
|
return baseInvoke(object, path, args);
|
|
};
|
|
});
|
|
|
|
/**
|
|
* The opposite of `_.method`; this method creates a function that invokes
|
|
* the method at a given path of `object`. Any additional arguments are
|
|
* provided to the invoked method.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.7.0
|
|
* @category Util
|
|
* @param {Object} object The object to query.
|
|
* @param {...*} [args] The arguments to invoke the method with.
|
|
* @returns {Function} Returns the new invoker function.
|
|
* @example
|
|
*
|
|
* var array = _.times(3, _.constant),
|
|
* object = { 'a': array, 'b': array, 'c': array };
|
|
*
|
|
* _.map(['a[2]', 'c[0]'], _.methodOf(object));
|
|
* // => [2, 0]
|
|
*
|
|
* _.map([['a', '2'], ['c', '0']], _.methodOf(object));
|
|
* // => [2, 0]
|
|
*/
|
|
var methodOf = baseRest(function(object, args) {
|
|
return function(path) {
|
|
return baseInvoke(object, path, args);
|
|
};
|
|
});
|
|
|
|
/**
|
|
* Adds all own enumerable string keyed function properties of a source
|
|
* object to the destination object. If `object` is a function, then methods
|
|
* are added to its prototype as well.
|
|
*
|
|
* **Note:** Use `_.runInContext` to create a pristine `lodash` function to
|
|
* avoid conflicts caused by modifying the original.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {Function|Object} [object=lodash] The destination object.
|
|
* @param {Object} source The object of functions to add.
|
|
* @param {Object} [options={}] The options object.
|
|
* @param {boolean} [options.chain=true] Specify whether mixins are chainable.
|
|
* @returns {Function|Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* function vowels(string) {
|
|
* return _.filter(string, function(v) {
|
|
* return /[aeiou]/i.test(v);
|
|
* });
|
|
* }
|
|
*
|
|
* _.mixin({ 'vowels': vowels });
|
|
* _.vowels('fred');
|
|
* // => ['e']
|
|
*
|
|
* _('fred').vowels().value();
|
|
* // => ['e']
|
|
*
|
|
* _.mixin({ 'vowels': vowels }, { 'chain': false });
|
|
* _('fred').vowels();
|
|
* // => ['e']
|
|
*/
|
|
function mixin(object, source, options) {
|
|
var props = keys(source),
|
|
methodNames = baseFunctions(source, props);
|
|
|
|
if (options == null &&
|
|
!(isObject(source) && (methodNames.length || !props.length))) {
|
|
options = source;
|
|
source = object;
|
|
object = this;
|
|
methodNames = baseFunctions(source, keys(source));
|
|
}
|
|
var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
|
|
isFunc = isFunction(object);
|
|
|
|
arrayEach(methodNames, function(methodName) {
|
|
var func = source[methodName];
|
|
object[methodName] = func;
|
|
if (isFunc) {
|
|
object.prototype[methodName] = function() {
|
|
var chainAll = this.__chain__;
|
|
if (chain || chainAll) {
|
|
var result = object(this.__wrapped__),
|
|
actions = result.__actions__ = copyArray(this.__actions__);
|
|
|
|
actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
|
|
result.__chain__ = chainAll;
|
|
return result;
|
|
}
|
|
return func.apply(object, arrayPush([this.value()], arguments));
|
|
};
|
|
}
|
|
});
|
|
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* Reverts the `_` variable to its previous value and returns a reference to
|
|
* the `lodash` function.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @returns {Function} Returns the `lodash` function.
|
|
* @example
|
|
*
|
|
* var lodash = _.noConflict();
|
|
*/
|
|
function noConflict() {
|
|
if (root._ === this) {
|
|
root._ = oldDash;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* This method returns `undefined`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.3.0
|
|
* @category Util
|
|
* @example
|
|
*
|
|
* _.times(2, _.noop);
|
|
* // => [undefined, undefined]
|
|
*/
|
|
function noop() {
|
|
// No operation performed.
|
|
}
|
|
|
|
/**
|
|
* Creates a function that gets the argument at index `n`. If `n` is negative,
|
|
* the nth argument from the end is returned.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {number} [n=0] The index of the argument to return.
|
|
* @returns {Function} Returns the new pass-thru function.
|
|
* @example
|
|
*
|
|
* var func = _.nthArg(1);
|
|
* func('a', 'b', 'c', 'd');
|
|
* // => 'b'
|
|
*
|
|
* var func = _.nthArg(-2);
|
|
* func('a', 'b', 'c', 'd');
|
|
* // => 'c'
|
|
*/
|
|
function nthArg(n) {
|
|
n = toInteger(n);
|
|
return baseRest(function(args) {
|
|
return baseNth(args, n);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a function that invokes `iteratees` with the arguments it receives
|
|
* and returns their results.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {...(Function|Function[])} [iteratees=[_.identity]]
|
|
* The iteratees to invoke.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var func = _.over([Math.max, Math.min]);
|
|
*
|
|
* func(1, 2, 3, 4);
|
|
* // => [4, 1]
|
|
*/
|
|
var over = createOver(arrayMap);
|
|
|
|
/**
|
|
* Creates a function that checks if **all** of the `predicates` return
|
|
* truthy when invoked with the arguments it receives.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {...(Function|Function[])} [predicates=[_.identity]]
|
|
* The predicates to check.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var func = _.overEvery([Boolean, isFinite]);
|
|
*
|
|
* func('1');
|
|
* // => true
|
|
*
|
|
* func(null);
|
|
* // => false
|
|
*
|
|
* func(NaN);
|
|
* // => false
|
|
*/
|
|
var overEvery = createOver(arrayEvery);
|
|
|
|
/**
|
|
* Creates a function that checks if **any** of the `predicates` return
|
|
* truthy when invoked with the arguments it receives.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {...(Function|Function[])} [predicates=[_.identity]]
|
|
* The predicates to check.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var func = _.overSome([Boolean, isFinite]);
|
|
*
|
|
* func('1');
|
|
* // => true
|
|
*
|
|
* func(null);
|
|
* // => true
|
|
*
|
|
* func(NaN);
|
|
* // => false
|
|
*/
|
|
var overSome = createOver(arraySome);
|
|
|
|
/**
|
|
* Creates a function that returns the value at `path` of a given object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.4.0
|
|
* @category Util
|
|
* @param {Array|string} path The path of the property to get.
|
|
* @returns {Function} Returns the new accessor function.
|
|
* @example
|
|
*
|
|
* var objects = [
|
|
* { 'a': { 'b': 2 } },
|
|
* { 'a': { 'b': 1 } }
|
|
* ];
|
|
*
|
|
* _.map(objects, _.property('a.b'));
|
|
* // => [2, 1]
|
|
*
|
|
* _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
|
|
* // => [1, 2]
|
|
*/
|
|
function property(path) {
|
|
return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
|
|
}
|
|
|
|
/**
|
|
* The opposite of `_.property`; this method creates a function that returns
|
|
* the value at a given path of `object`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.0.0
|
|
* @category Util
|
|
* @param {Object} object The object to query.
|
|
* @returns {Function} Returns the new accessor function.
|
|
* @example
|
|
*
|
|
* var array = [0, 1, 2],
|
|
* object = { 'a': array, 'b': array, 'c': array };
|
|
*
|
|
* _.map(['a[2]', 'c[0]'], _.propertyOf(object));
|
|
* // => [2, 0]
|
|
*
|
|
* _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
|
|
* // => [2, 0]
|
|
*/
|
|
function propertyOf(object) {
|
|
return function(path) {
|
|
return object == null ? undefined : baseGet(object, path);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates an array of numbers (positive and/or negative) progressing from
|
|
* `start` up to, but not including, `end`. A step of `-1` is used if a negative
|
|
* `start` is specified without an `end` or `step`. If `end` is not specified,
|
|
* it's set to `start` with `start` then set to `0`.
|
|
*
|
|
* **Note:** JavaScript follows the IEEE-754 standard for resolving
|
|
* floating-point values which can produce unexpected results.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {number} [start=0] The start of the range.
|
|
* @param {number} end The end of the range.
|
|
* @param {number} [step=1] The value to increment or decrement by.
|
|
* @returns {Array} Returns the range of numbers.
|
|
* @see _.inRange, _.rangeRight
|
|
* @example
|
|
*
|
|
* _.range(4);
|
|
* // => [0, 1, 2, 3]
|
|
*
|
|
* _.range(-4);
|
|
* // => [0, -1, -2, -3]
|
|
*
|
|
* _.range(1, 5);
|
|
* // => [1, 2, 3, 4]
|
|
*
|
|
* _.range(0, 20, 5);
|
|
* // => [0, 5, 10, 15]
|
|
*
|
|
* _.range(0, -4, -1);
|
|
* // => [0, -1, -2, -3]
|
|
*
|
|
* _.range(1, 4, 0);
|
|
* // => [1, 1, 1]
|
|
*
|
|
* _.range(0);
|
|
* // => []
|
|
*/
|
|
var range = createRange();
|
|
|
|
/**
|
|
* This method is like `_.range` except that it populates values in
|
|
* descending order.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {number} [start=0] The start of the range.
|
|
* @param {number} end The end of the range.
|
|
* @param {number} [step=1] The value to increment or decrement by.
|
|
* @returns {Array} Returns the range of numbers.
|
|
* @see _.inRange, _.range
|
|
* @example
|
|
*
|
|
* _.rangeRight(4);
|
|
* // => [3, 2, 1, 0]
|
|
*
|
|
* _.rangeRight(-4);
|
|
* // => [-3, -2, -1, 0]
|
|
*
|
|
* _.rangeRight(1, 5);
|
|
* // => [4, 3, 2, 1]
|
|
*
|
|
* _.rangeRight(0, 20, 5);
|
|
* // => [15, 10, 5, 0]
|
|
*
|
|
* _.rangeRight(0, -4, -1);
|
|
* // => [-3, -2, -1, 0]
|
|
*
|
|
* _.rangeRight(1, 4, 0);
|
|
* // => [1, 1, 1]
|
|
*
|
|
* _.rangeRight(0);
|
|
* // => []
|
|
*/
|
|
var rangeRight = createRange(true);
|
|
|
|
/**
|
|
* This method returns a new empty array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.13.0
|
|
* @category Util
|
|
* @returns {Array} Returns the new empty array.
|
|
* @example
|
|
*
|
|
* var arrays = _.times(2, _.stubArray);
|
|
*
|
|
* console.log(arrays);
|
|
* // => [[], []]
|
|
*
|
|
* console.log(arrays[0] === arrays[1]);
|
|
* // => false
|
|
*/
|
|
function stubArray() {
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* This method returns `false`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.13.0
|
|
* @category Util
|
|
* @returns {boolean} Returns `false`.
|
|
* @example
|
|
*
|
|
* _.times(2, _.stubFalse);
|
|
* // => [false, false]
|
|
*/
|
|
function stubFalse() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* This method returns a new empty object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.13.0
|
|
* @category Util
|
|
* @returns {Object} Returns the new empty object.
|
|
* @example
|
|
*
|
|
* var objects = _.times(2, _.stubObject);
|
|
*
|
|
* console.log(objects);
|
|
* // => [{}, {}]
|
|
*
|
|
* console.log(objects[0] === objects[1]);
|
|
* // => false
|
|
*/
|
|
function stubObject() {
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* This method returns an empty string.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.13.0
|
|
* @category Util
|
|
* @returns {string} Returns the empty string.
|
|
* @example
|
|
*
|
|
* _.times(2, _.stubString);
|
|
* // => ['', '']
|
|
*/
|
|
function stubString() {
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* This method returns `true`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.13.0
|
|
* @category Util
|
|
* @returns {boolean} Returns `true`.
|
|
* @example
|
|
*
|
|
* _.times(2, _.stubTrue);
|
|
* // => [true, true]
|
|
*/
|
|
function stubTrue() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Invokes the iteratee `n` times, returning an array of the results of
|
|
* each invocation. The iteratee is invoked with one argument; (index).
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {number} n The number of times to invoke `iteratee`.
|
|
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
|
|
* @returns {Array} Returns the array of results.
|
|
* @example
|
|
*
|
|
* _.times(3, String);
|
|
* // => ['0', '1', '2']
|
|
*
|
|
* _.times(4, _.constant(0));
|
|
* // => [0, 0, 0, 0]
|
|
*/
|
|
function times(n, iteratee) {
|
|
n = toInteger(n);
|
|
if (n < 1 || n > MAX_SAFE_INTEGER) {
|
|
return [];
|
|
}
|
|
var index = MAX_ARRAY_LENGTH,
|
|
length = nativeMin(n, MAX_ARRAY_LENGTH);
|
|
|
|
iteratee = getIteratee(iteratee);
|
|
n -= MAX_ARRAY_LENGTH;
|
|
|
|
var result = baseTimes(length, iteratee);
|
|
while (++index < n) {
|
|
iteratee(index);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts `value` to a property path array.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Util
|
|
* @param {*} value The value to convert.
|
|
* @returns {Array} Returns the new property path array.
|
|
* @example
|
|
*
|
|
* _.toPath('a.b.c');
|
|
* // => ['a', 'b', 'c']
|
|
*
|
|
* _.toPath('a[0].b.c');
|
|
* // => ['a', '0', 'b', 'c']
|
|
*/
|
|
function toPath(value) {
|
|
if (isArray(value)) {
|
|
return arrayMap(value, toKey);
|
|
}
|
|
return isSymbol(value) ? [value] : copyArray(stringToPath(toString(value)));
|
|
}
|
|
|
|
/**
|
|
* Generates a unique ID. If `prefix` is given, the ID is appended to it.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Util
|
|
* @param {string} [prefix=''] The value to prefix the ID with.
|
|
* @returns {string} Returns the unique ID.
|
|
* @example
|
|
*
|
|
* _.uniqueId('contact_');
|
|
* // => 'contact_104'
|
|
*
|
|
* _.uniqueId();
|
|
* // => '105'
|
|
*/
|
|
function uniqueId(prefix) {
|
|
var id = ++idCounter;
|
|
return toString(prefix) + id;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Adds two numbers.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.4.0
|
|
* @category Math
|
|
* @param {number} augend The first number in an addition.
|
|
* @param {number} addend The second number in an addition.
|
|
* @returns {number} Returns the total.
|
|
* @example
|
|
*
|
|
* _.add(6, 4);
|
|
* // => 10
|
|
*/
|
|
var add = createMathOperation(function(augend, addend) {
|
|
return augend + addend;
|
|
}, 0);
|
|
|
|
/**
|
|
* Computes `number` rounded up to `precision`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.10.0
|
|
* @category Math
|
|
* @param {number} number The number to round up.
|
|
* @param {number} [precision=0] The precision to round up to.
|
|
* @returns {number} Returns the rounded up number.
|
|
* @example
|
|
*
|
|
* _.ceil(4.006);
|
|
* // => 5
|
|
*
|
|
* _.ceil(6.004, 2);
|
|
* // => 6.01
|
|
*
|
|
* _.ceil(6040, -2);
|
|
* // => 6100
|
|
*/
|
|
var ceil = createRound('ceil');
|
|
|
|
/**
|
|
* Divide two numbers.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.7.0
|
|
* @category Math
|
|
* @param {number} dividend The first number in a division.
|
|
* @param {number} divisor The second number in a division.
|
|
* @returns {number} Returns the quotient.
|
|
* @example
|
|
*
|
|
* _.divide(6, 4);
|
|
* // => 1.5
|
|
*/
|
|
var divide = createMathOperation(function(dividend, divisor) {
|
|
return dividend / divisor;
|
|
}, 1);
|
|
|
|
/**
|
|
* Computes `number` rounded down to `precision`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.10.0
|
|
* @category Math
|
|
* @param {number} number The number to round down.
|
|
* @param {number} [precision=0] The precision to round down to.
|
|
* @returns {number} Returns the rounded down number.
|
|
* @example
|
|
*
|
|
* _.floor(4.006);
|
|
* // => 4
|
|
*
|
|
* _.floor(0.046, 2);
|
|
* // => 0.04
|
|
*
|
|
* _.floor(4060, -2);
|
|
* // => 4000
|
|
*/
|
|
var floor = createRound('floor');
|
|
|
|
/**
|
|
* Computes the maximum value of `array`. If `array` is empty or falsey,
|
|
* `undefined` is returned.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @returns {*} Returns the maximum value.
|
|
* @example
|
|
*
|
|
* _.max([4, 2, 8, 6]);
|
|
* // => 8
|
|
*
|
|
* _.max([]);
|
|
* // => undefined
|
|
*/
|
|
function max(array) {
|
|
return (array && array.length)
|
|
? baseExtremum(array, identity, baseGt)
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.max` except that it accepts `iteratee` which is
|
|
* invoked for each element in `array` to generate the criterion by which
|
|
* the value is ranked. The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {*} Returns the maximum value.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'n': 1 }, { 'n': 2 }];
|
|
*
|
|
* _.maxBy(objects, function(o) { return o.n; });
|
|
* // => { 'n': 2 }
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.maxBy(objects, 'n');
|
|
* // => { 'n': 2 }
|
|
*/
|
|
function maxBy(array, iteratee) {
|
|
return (array && array.length)
|
|
? baseExtremum(array, getIteratee(iteratee, 2), baseGt)
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* Computes the mean of the values in `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @returns {number} Returns the mean.
|
|
* @example
|
|
*
|
|
* _.mean([4, 2, 8, 6]);
|
|
* // => 5
|
|
*/
|
|
function mean(array) {
|
|
return baseMean(array, identity);
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.mean` except that it accepts `iteratee` which is
|
|
* invoked for each element in `array` to generate the value to be averaged.
|
|
* The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.7.0
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {number} Returns the mean.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
|
|
*
|
|
* _.meanBy(objects, function(o) { return o.n; });
|
|
* // => 5
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.meanBy(objects, 'n');
|
|
* // => 5
|
|
*/
|
|
function meanBy(array, iteratee) {
|
|
return baseMean(array, getIteratee(iteratee, 2));
|
|
}
|
|
|
|
/**
|
|
* Computes the minimum value of `array`. If `array` is empty or falsey,
|
|
* `undefined` is returned.
|
|
*
|
|
* @static
|
|
* @since 0.1.0
|
|
* @memberOf _
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @returns {*} Returns the minimum value.
|
|
* @example
|
|
*
|
|
* _.min([4, 2, 8, 6]);
|
|
* // => 2
|
|
*
|
|
* _.min([]);
|
|
* // => undefined
|
|
*/
|
|
function min(array) {
|
|
return (array && array.length)
|
|
? baseExtremum(array, identity, baseLt)
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.min` except that it accepts `iteratee` which is
|
|
* invoked for each element in `array` to generate the criterion by which
|
|
* the value is ranked. The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {*} Returns the minimum value.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'n': 1 }, { 'n': 2 }];
|
|
*
|
|
* _.minBy(objects, function(o) { return o.n; });
|
|
* // => { 'n': 1 }
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.minBy(objects, 'n');
|
|
* // => { 'n': 1 }
|
|
*/
|
|
function minBy(array, iteratee) {
|
|
return (array && array.length)
|
|
? baseExtremum(array, getIteratee(iteratee, 2), baseLt)
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* Multiply two numbers.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.7.0
|
|
* @category Math
|
|
* @param {number} multiplier The first number in a multiplication.
|
|
* @param {number} multiplicand The second number in a multiplication.
|
|
* @returns {number} Returns the product.
|
|
* @example
|
|
*
|
|
* _.multiply(6, 4);
|
|
* // => 24
|
|
*/
|
|
var multiply = createMathOperation(function(multiplier, multiplicand) {
|
|
return multiplier * multiplicand;
|
|
}, 1);
|
|
|
|
/**
|
|
* Computes `number` rounded to `precision`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.10.0
|
|
* @category Math
|
|
* @param {number} number The number to round.
|
|
* @param {number} [precision=0] The precision to round to.
|
|
* @returns {number} Returns the rounded number.
|
|
* @example
|
|
*
|
|
* _.round(4.006);
|
|
* // => 4
|
|
*
|
|
* _.round(4.006, 2);
|
|
* // => 4.01
|
|
*
|
|
* _.round(4060, -2);
|
|
* // => 4100
|
|
*/
|
|
var round = createRound('round');
|
|
|
|
/**
|
|
* Subtract two numbers.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Math
|
|
* @param {number} minuend The first number in a subtraction.
|
|
* @param {number} subtrahend The second number in a subtraction.
|
|
* @returns {number} Returns the difference.
|
|
* @example
|
|
*
|
|
* _.subtract(6, 4);
|
|
* // => 2
|
|
*/
|
|
var subtract = createMathOperation(function(minuend, subtrahend) {
|
|
return minuend - subtrahend;
|
|
}, 0);
|
|
|
|
/**
|
|
* Computes the sum of the values in `array`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 3.4.0
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @returns {number} Returns the sum.
|
|
* @example
|
|
*
|
|
* _.sum([4, 2, 8, 6]);
|
|
* // => 20
|
|
*/
|
|
function sum(array) {
|
|
return (array && array.length)
|
|
? baseSum(array, identity)
|
|
: 0;
|
|
}
|
|
|
|
/**
|
|
* This method is like `_.sum` except that it accepts `iteratee` which is
|
|
* invoked for each element in `array` to generate the value to be summed.
|
|
* The iteratee is invoked with one argument: (value).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Math
|
|
* @param {Array} array The array to iterate over.
|
|
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
|
|
* @returns {number} Returns the sum.
|
|
* @example
|
|
*
|
|
* var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
|
|
*
|
|
* _.sumBy(objects, function(o) { return o.n; });
|
|
* // => 20
|
|
*
|
|
* // The `_.property` iteratee shorthand.
|
|
* _.sumBy(objects, 'n');
|
|
* // => 20
|
|
*/
|
|
function sumBy(array, iteratee) {
|
|
return (array && array.length)
|
|
? baseSum(array, getIteratee(iteratee, 2))
|
|
: 0;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Add methods that return wrapped values in chain sequences.
|
|
lodash.after = after;
|
|
lodash.ary = ary;
|
|
lodash.assign = assign;
|
|
lodash.assignIn = assignIn;
|
|
lodash.assignInWith = assignInWith;
|
|
lodash.assignWith = assignWith;
|
|
lodash.at = at;
|
|
lodash.before = before;
|
|
lodash.bind = bind;
|
|
lodash.bindAll = bindAll;
|
|
lodash.bindKey = bindKey;
|
|
lodash.castArray = castArray;
|
|
lodash.chain = chain;
|
|
lodash.chunk = chunk;
|
|
lodash.compact = compact;
|
|
lodash.concat = concat;
|
|
lodash.cond = cond;
|
|
lodash.conforms = conforms;
|
|
lodash.constant = constant;
|
|
lodash.countBy = countBy;
|
|
lodash.create = create;
|
|
lodash.curry = curry;
|
|
lodash.curryRight = curryRight;
|
|
lodash.debounce = debounce;
|
|
lodash.defaults = defaults;
|
|
lodash.defaultsDeep = defaultsDeep;
|
|
lodash.defer = defer;
|
|
lodash.delay = delay;
|
|
lodash.difference = difference;
|
|
lodash.differenceBy = differenceBy;
|
|
lodash.differenceWith = differenceWith;
|
|
lodash.drop = drop;
|
|
lodash.dropRight = dropRight;
|
|
lodash.dropRightWhile = dropRightWhile;
|
|
lodash.dropWhile = dropWhile;
|
|
lodash.fill = fill;
|
|
lodash.filter = filter;
|
|
lodash.flatMap = flatMap;
|
|
lodash.flatMapDeep = flatMapDeep;
|
|
lodash.flatMapDepth = flatMapDepth;
|
|
lodash.flatten = flatten;
|
|
lodash.flattenDeep = flattenDeep;
|
|
lodash.flattenDepth = flattenDepth;
|
|
lodash.flip = flip;
|
|
lodash.flow = flow;
|
|
lodash.flowRight = flowRight;
|
|
lodash.fromPairs = fromPairs;
|
|
lodash.functions = functions;
|
|
lodash.functionsIn = functionsIn;
|
|
lodash.groupBy = groupBy;
|
|
lodash.initial = initial;
|
|
lodash.intersection = intersection;
|
|
lodash.intersectionBy = intersectionBy;
|
|
lodash.intersectionWith = intersectionWith;
|
|
lodash.invert = invert;
|
|
lodash.invertBy = invertBy;
|
|
lodash.invokeMap = invokeMap;
|
|
lodash.iteratee = iteratee;
|
|
lodash.keyBy = keyBy;
|
|
lodash.keys = keys;
|
|
lodash.keysIn = keysIn;
|
|
lodash.map = map;
|
|
lodash.mapKeys = mapKeys;
|
|
lodash.mapValues = mapValues;
|
|
lodash.matches = matches;
|
|
lodash.matchesProperty = matchesProperty;
|
|
lodash.memoize = memoize;
|
|
lodash.merge = merge;
|
|
lodash.mergeWith = mergeWith;
|
|
lodash.method = method;
|
|
lodash.methodOf = methodOf;
|
|
lodash.mixin = mixin;
|
|
lodash.negate = negate;
|
|
lodash.nthArg = nthArg;
|
|
lodash.omit = omit;
|
|
lodash.omitBy = omitBy;
|
|
lodash.once = once;
|
|
lodash.orderBy = orderBy;
|
|
lodash.over = over;
|
|
lodash.overArgs = overArgs;
|
|
lodash.overEvery = overEvery;
|
|
lodash.overSome = overSome;
|
|
lodash.partial = partial;
|
|
lodash.partialRight = partialRight;
|
|
lodash.partition = partition;
|
|
lodash.pick = pick;
|
|
lodash.pickBy = pickBy;
|
|
lodash.property = property;
|
|
lodash.propertyOf = propertyOf;
|
|
lodash.pull = pull;
|
|
lodash.pullAll = pullAll;
|
|
lodash.pullAllBy = pullAllBy;
|
|
lodash.pullAllWith = pullAllWith;
|
|
lodash.pullAt = pullAt;
|
|
lodash.range = range;
|
|
lodash.rangeRight = rangeRight;
|
|
lodash.rearg = rearg;
|
|
lodash.reject = reject;
|
|
lodash.remove = remove;
|
|
lodash.rest = rest;
|
|
lodash.reverse = reverse;
|
|
lodash.sampleSize = sampleSize;
|
|
lodash.set = set;
|
|
lodash.setWith = setWith;
|
|
lodash.shuffle = shuffle;
|
|
lodash.slice = slice;
|
|
lodash.sortBy = sortBy;
|
|
lodash.sortedUniq = sortedUniq;
|
|
lodash.sortedUniqBy = sortedUniqBy;
|
|
lodash.split = split;
|
|
lodash.spread = spread;
|
|
lodash.tail = tail;
|
|
lodash.take = take;
|
|
lodash.takeRight = takeRight;
|
|
lodash.takeRightWhile = takeRightWhile;
|
|
lodash.takeWhile = takeWhile;
|
|
lodash.tap = tap;
|
|
lodash.throttle = throttle;
|
|
lodash.thru = thru;
|
|
lodash.toArray = toArray;
|
|
lodash.toPairs = toPairs;
|
|
lodash.toPairsIn = toPairsIn;
|
|
lodash.toPath = toPath;
|
|
lodash.toPlainObject = toPlainObject;
|
|
lodash.transform = transform;
|
|
lodash.unary = unary;
|
|
lodash.union = union;
|
|
lodash.unionBy = unionBy;
|
|
lodash.unionWith = unionWith;
|
|
lodash.uniq = uniq;
|
|
lodash.uniqBy = uniqBy;
|
|
lodash.uniqWith = uniqWith;
|
|
lodash.unset = unset;
|
|
lodash.unzip = unzip;
|
|
lodash.unzipWith = unzipWith;
|
|
lodash.update = update;
|
|
lodash.updateWith = updateWith;
|
|
lodash.values = values;
|
|
lodash.valuesIn = valuesIn;
|
|
lodash.without = without;
|
|
lodash.words = words;
|
|
lodash.wrap = wrap;
|
|
lodash.xor = xor;
|
|
lodash.xorBy = xorBy;
|
|
lodash.xorWith = xorWith;
|
|
lodash.zip = zip;
|
|
lodash.zipObject = zipObject;
|
|
lodash.zipObjectDeep = zipObjectDeep;
|
|
lodash.zipWith = zipWith;
|
|
|
|
// Add aliases.
|
|
lodash.entries = toPairs;
|
|
lodash.entriesIn = toPairsIn;
|
|
lodash.extend = assignIn;
|
|
lodash.extendWith = assignInWith;
|
|
|
|
// Add methods to `lodash.prototype`.
|
|
mixin(lodash, lodash);
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Add methods that return unwrapped values in chain sequences.
|
|
lodash.add = add;
|
|
lodash.attempt = attempt;
|
|
lodash.camelCase = camelCase;
|
|
lodash.capitalize = capitalize;
|
|
lodash.ceil = ceil;
|
|
lodash.clamp = clamp;
|
|
lodash.clone = clone;
|
|
lodash.cloneDeep = cloneDeep;
|
|
lodash.cloneDeepWith = cloneDeepWith;
|
|
lodash.cloneWith = cloneWith;
|
|
lodash.conformsTo = conformsTo;
|
|
lodash.deburr = deburr;
|
|
lodash.defaultTo = defaultTo;
|
|
lodash.divide = divide;
|
|
lodash.endsWith = endsWith;
|
|
lodash.eq = eq;
|
|
lodash.escape = escape;
|
|
lodash.escapeRegExp = escapeRegExp;
|
|
lodash.every = every;
|
|
lodash.find = find;
|
|
lodash.findIndex = findIndex;
|
|
lodash.findKey = findKey;
|
|
lodash.findLast = findLast;
|
|
lodash.findLastIndex = findLastIndex;
|
|
lodash.findLastKey = findLastKey;
|
|
lodash.floor = floor;
|
|
lodash.forEach = forEach;
|
|
lodash.forEachRight = forEachRight;
|
|
lodash.forIn = forIn;
|
|
lodash.forInRight = forInRight;
|
|
lodash.forOwn = forOwn;
|
|
lodash.forOwnRight = forOwnRight;
|
|
lodash.get = get;
|
|
lodash.gt = gt;
|
|
lodash.gte = gte;
|
|
lodash.has = has;
|
|
lodash.hasIn = hasIn;
|
|
lodash.head = head;
|
|
lodash.identity = identity;
|
|
lodash.includes = includes;
|
|
lodash.indexOf = indexOf;
|
|
lodash.inRange = inRange;
|
|
lodash.invoke = invoke;
|
|
lodash.isArguments = isArguments;
|
|
lodash.isArray = isArray;
|
|
lodash.isArrayBuffer = isArrayBuffer;
|
|
lodash.isArrayLike = isArrayLike;
|
|
lodash.isArrayLikeObject = isArrayLikeObject;
|
|
lodash.isBoolean = isBoolean;
|
|
lodash.isBuffer = isBuffer;
|
|
lodash.isDate = isDate;
|
|
lodash.isElement = isElement;
|
|
lodash.isEmpty = isEmpty;
|
|
lodash.isEqual = isEqual;
|
|
lodash.isEqualWith = isEqualWith;
|
|
lodash.isError = isError;
|
|
lodash.isFinite = isFinite;
|
|
lodash.isFunction = isFunction;
|
|
lodash.isInteger = isInteger;
|
|
lodash.isLength = isLength;
|
|
lodash.isMap = isMap;
|
|
lodash.isMatch = isMatch;
|
|
lodash.isMatchWith = isMatchWith;
|
|
lodash.isNaN = isNaN;
|
|
lodash.isNative = isNative;
|
|
lodash.isNil = isNil;
|
|
lodash.isNull = isNull;
|
|
lodash.isNumber = isNumber;
|
|
lodash.isObject = isObject;
|
|
lodash.isObjectLike = isObjectLike;
|
|
lodash.isPlainObject = isPlainObject;
|
|
lodash.isRegExp = isRegExp;
|
|
lodash.isSafeInteger = isSafeInteger;
|
|
lodash.isSet = isSet;
|
|
lodash.isString = isString;
|
|
lodash.isSymbol = isSymbol;
|
|
lodash.isTypedArray = isTypedArray;
|
|
lodash.isUndefined = isUndefined;
|
|
lodash.isWeakMap = isWeakMap;
|
|
lodash.isWeakSet = isWeakSet;
|
|
lodash.join = join;
|
|
lodash.kebabCase = kebabCase;
|
|
lodash.last = last;
|
|
lodash.lastIndexOf = lastIndexOf;
|
|
lodash.lowerCase = lowerCase;
|
|
lodash.lowerFirst = lowerFirst;
|
|
lodash.lt = lt;
|
|
lodash.lte = lte;
|
|
lodash.max = max;
|
|
lodash.maxBy = maxBy;
|
|
lodash.mean = mean;
|
|
lodash.meanBy = meanBy;
|
|
lodash.min = min;
|
|
lodash.minBy = minBy;
|
|
lodash.stubArray = stubArray;
|
|
lodash.stubFalse = stubFalse;
|
|
lodash.stubObject = stubObject;
|
|
lodash.stubString = stubString;
|
|
lodash.stubTrue = stubTrue;
|
|
lodash.multiply = multiply;
|
|
lodash.nth = nth;
|
|
lodash.noConflict = noConflict;
|
|
lodash.noop = noop;
|
|
lodash.now = now;
|
|
lodash.pad = pad;
|
|
lodash.padEnd = padEnd;
|
|
lodash.padStart = padStart;
|
|
lodash.parseInt = parseInt;
|
|
lodash.random = random;
|
|
lodash.reduce = reduce;
|
|
lodash.reduceRight = reduceRight;
|
|
lodash.repeat = repeat;
|
|
lodash.replace = replace;
|
|
lodash.result = result;
|
|
lodash.round = round;
|
|
lodash.runInContext = runInContext;
|
|
lodash.sample = sample;
|
|
lodash.size = size;
|
|
lodash.snakeCase = snakeCase;
|
|
lodash.some = some;
|
|
lodash.sortedIndex = sortedIndex;
|
|
lodash.sortedIndexBy = sortedIndexBy;
|
|
lodash.sortedIndexOf = sortedIndexOf;
|
|
lodash.sortedLastIndex = sortedLastIndex;
|
|
lodash.sortedLastIndexBy = sortedLastIndexBy;
|
|
lodash.sortedLastIndexOf = sortedLastIndexOf;
|
|
lodash.startCase = startCase;
|
|
lodash.startsWith = startsWith;
|
|
lodash.subtract = subtract;
|
|
lodash.sum = sum;
|
|
lodash.sumBy = sumBy;
|
|
lodash.template = template;
|
|
lodash.times = times;
|
|
lodash.toFinite = toFinite;
|
|
lodash.toInteger = toInteger;
|
|
lodash.toLength = toLength;
|
|
lodash.toLower = toLower;
|
|
lodash.toNumber = toNumber;
|
|
lodash.toSafeInteger = toSafeInteger;
|
|
lodash.toString = toString;
|
|
lodash.toUpper = toUpper;
|
|
lodash.trim = trim;
|
|
lodash.trimEnd = trimEnd;
|
|
lodash.trimStart = trimStart;
|
|
lodash.truncate = truncate;
|
|
lodash.unescape = unescape;
|
|
lodash.uniqueId = uniqueId;
|
|
lodash.upperCase = upperCase;
|
|
lodash.upperFirst = upperFirst;
|
|
|
|
// Add aliases.
|
|
lodash.each = forEach;
|
|
lodash.eachRight = forEachRight;
|
|
lodash.first = head;
|
|
|
|
mixin(lodash, (function() {
|
|
var source = {};
|
|
baseForOwn(lodash, function(func, methodName) {
|
|
if (!hasOwnProperty.call(lodash.prototype, methodName)) {
|
|
source[methodName] = func;
|
|
}
|
|
});
|
|
return source;
|
|
}()), { 'chain': false });
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* The semantic version number.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @type {string}
|
|
*/
|
|
lodash.VERSION = VERSION;
|
|
|
|
// Assign default placeholders.
|
|
arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
|
|
lodash[methodName].placeholder = lodash;
|
|
});
|
|
|
|
// Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
|
|
arrayEach(['drop', 'take'], function(methodName, index) {
|
|
LazyWrapper.prototype[methodName] = function(n) {
|
|
n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
|
|
|
|
var result = (this.__filtered__ && !index)
|
|
? new LazyWrapper(this)
|
|
: this.clone();
|
|
|
|
if (result.__filtered__) {
|
|
result.__takeCount__ = nativeMin(n, result.__takeCount__);
|
|
} else {
|
|
result.__views__.push({
|
|
'size': nativeMin(n, MAX_ARRAY_LENGTH),
|
|
'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
|
|
});
|
|
}
|
|
return result;
|
|
};
|
|
|
|
LazyWrapper.prototype[methodName + 'Right'] = function(n) {
|
|
return this.reverse()[methodName](n).reverse();
|
|
};
|
|
});
|
|
|
|
// Add `LazyWrapper` methods that accept an `iteratee` value.
|
|
arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
|
|
var type = index + 1,
|
|
isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
|
|
|
|
LazyWrapper.prototype[methodName] = function(iteratee) {
|
|
var result = this.clone();
|
|
result.__iteratees__.push({
|
|
'iteratee': getIteratee(iteratee, 3),
|
|
'type': type
|
|
});
|
|
result.__filtered__ = result.__filtered__ || isFilter;
|
|
return result;
|
|
};
|
|
});
|
|
|
|
// Add `LazyWrapper` methods for `_.head` and `_.last`.
|
|
arrayEach(['head', 'last'], function(methodName, index) {
|
|
var takeName = 'take' + (index ? 'Right' : '');
|
|
|
|
LazyWrapper.prototype[methodName] = function() {
|
|
return this[takeName](1).value()[0];
|
|
};
|
|
});
|
|
|
|
// Add `LazyWrapper` methods for `_.initial` and `_.tail`.
|
|
arrayEach(['initial', 'tail'], function(methodName, index) {
|
|
var dropName = 'drop' + (index ? '' : 'Right');
|
|
|
|
LazyWrapper.prototype[methodName] = function() {
|
|
return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
|
|
};
|
|
});
|
|
|
|
LazyWrapper.prototype.compact = function() {
|
|
return this.filter(identity);
|
|
};
|
|
|
|
LazyWrapper.prototype.find = function(predicate) {
|
|
return this.filter(predicate).head();
|
|
};
|
|
|
|
LazyWrapper.prototype.findLast = function(predicate) {
|
|
return this.reverse().find(predicate);
|
|
};
|
|
|
|
LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
|
|
if (typeof path == 'function') {
|
|
return new LazyWrapper(this);
|
|
}
|
|
return this.map(function(value) {
|
|
return baseInvoke(value, path, args);
|
|
});
|
|
});
|
|
|
|
LazyWrapper.prototype.reject = function(predicate) {
|
|
return this.filter(negate(getIteratee(predicate)));
|
|
};
|
|
|
|
LazyWrapper.prototype.slice = function(start, end) {
|
|
start = toInteger(start);
|
|
|
|
var result = this;
|
|
if (result.__filtered__ && (start > 0 || end < 0)) {
|
|
return new LazyWrapper(result);
|
|
}
|
|
if (start < 0) {
|
|
result = result.takeRight(-start);
|
|
} else if (start) {
|
|
result = result.drop(start);
|
|
}
|
|
if (end !== undefined) {
|
|
end = toInteger(end);
|
|
result = end < 0 ? result.dropRight(-end) : result.take(end - start);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
LazyWrapper.prototype.takeRightWhile = function(predicate) {
|
|
return this.reverse().takeWhile(predicate).reverse();
|
|
};
|
|
|
|
LazyWrapper.prototype.toArray = function() {
|
|
return this.take(MAX_ARRAY_LENGTH);
|
|
};
|
|
|
|
// Add `LazyWrapper` methods to `lodash.prototype`.
|
|
baseForOwn(LazyWrapper.prototype, function(func, methodName) {
|
|
var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
|
|
isTaker = /^(?:head|last)$/.test(methodName),
|
|
lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
|
|
retUnwrapped = isTaker || /^find/.test(methodName);
|
|
|
|
if (!lodashFunc) {
|
|
return;
|
|
}
|
|
lodash.prototype[methodName] = function() {
|
|
var value = this.__wrapped__,
|
|
args = isTaker ? [1] : arguments,
|
|
isLazy = value instanceof LazyWrapper,
|
|
iteratee = args[0],
|
|
useLazy = isLazy || isArray(value);
|
|
|
|
var interceptor = function(value) {
|
|
var result = lodashFunc.apply(lodash, arrayPush([value], args));
|
|
return (isTaker && chainAll) ? result[0] : result;
|
|
};
|
|
|
|
if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
|
|
// Avoid lazy use if the iteratee has a "length" value other than `1`.
|
|
isLazy = useLazy = false;
|
|
}
|
|
var chainAll = this.__chain__,
|
|
isHybrid = !!this.__actions__.length,
|
|
isUnwrapped = retUnwrapped && !chainAll,
|
|
onlyLazy = isLazy && !isHybrid;
|
|
|
|
if (!retUnwrapped && useLazy) {
|
|
value = onlyLazy ? value : new LazyWrapper(this);
|
|
var result = func.apply(value, args);
|
|
result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
|
|
return new LodashWrapper(result, chainAll);
|
|
}
|
|
if (isUnwrapped && onlyLazy) {
|
|
return func.apply(this, args);
|
|
}
|
|
result = this.thru(interceptor);
|
|
return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
|
|
};
|
|
});
|
|
|
|
// Add `Array` methods to `lodash.prototype`.
|
|
arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
|
|
var func = arrayProto[methodName],
|
|
chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
|
|
retUnwrapped = /^(?:pop|shift)$/.test(methodName);
|
|
|
|
lodash.prototype[methodName] = function() {
|
|
var args = arguments;
|
|
if (retUnwrapped && !this.__chain__) {
|
|
var value = this.value();
|
|
return func.apply(isArray(value) ? value : [], args);
|
|
}
|
|
return this[chainName](function(value) {
|
|
return func.apply(isArray(value) ? value : [], args);
|
|
});
|
|
};
|
|
});
|
|
|
|
// Map minified method names to their real names.
|
|
baseForOwn(LazyWrapper.prototype, function(func, methodName) {
|
|
var lodashFunc = lodash[methodName];
|
|
if (lodashFunc) {
|
|
var key = (lodashFunc.name + ''),
|
|
names = realNames[key] || (realNames[key] = []);
|
|
|
|
names.push({ 'name': methodName, 'func': lodashFunc });
|
|
}
|
|
});
|
|
|
|
realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
|
|
'name': 'wrapper',
|
|
'func': undefined
|
|
}];
|
|
|
|
// Add methods to `LazyWrapper`.
|
|
LazyWrapper.prototype.clone = lazyClone;
|
|
LazyWrapper.prototype.reverse = lazyReverse;
|
|
LazyWrapper.prototype.value = lazyValue;
|
|
|
|
// Add chain sequence methods to the `lodash` wrapper.
|
|
lodash.prototype.at = wrapperAt;
|
|
lodash.prototype.chain = wrapperChain;
|
|
lodash.prototype.commit = wrapperCommit;
|
|
lodash.prototype.next = wrapperNext;
|
|
lodash.prototype.plant = wrapperPlant;
|
|
lodash.prototype.reverse = wrapperReverse;
|
|
lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
|
|
|
|
// Add lazy aliases.
|
|
lodash.prototype.first = lodash.prototype.head;
|
|
|
|
if (symIterator) {
|
|
lodash.prototype[symIterator] = wrapperToIterator;
|
|
}
|
|
return lodash;
|
|
});
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
// Export lodash.
|
|
var _ = runInContext();
|
|
|
|
// Some AMD build optimizers, like r.js, check for condition patterns like:
|
|
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
|
|
// Expose Lodash on the global object to prevent errors when Lodash is
|
|
// loaded by a script tag in the presence of an AMD loader.
|
|
// See http://requirejs.org/docs/errors.html#mismatch for more details.
|
|
// Use `_.noConflict` to remove Lodash from the global object.
|
|
root._ = _;
|
|
|
|
// Define as an anonymous module so, through path mapping, it can be
|
|
// referenced as the "underscore" module.
|
|
define(function() {
|
|
return _;
|
|
});
|
|
}
|
|
// Check for `exports` after `define` in case a build optimizer adds it.
|
|
else if (freeModule) {
|
|
// Export for Node.js.
|
|
(freeModule.exports = _)._ = _;
|
|
// Export for CommonJS support.
|
|
freeExports._ = _;
|
|
}
|
|
else {
|
|
// Export to the global object.
|
|
root._ = _;
|
|
}
|
|
}.call(this));
|
|
|
|
/*!
|
|
* jQuery JavaScript Library v3.3.1
|
|
* https://jquery.com/
|
|
*
|
|
* Includes Sizzle.js
|
|
* https://sizzlejs.com/
|
|
*
|
|
* Copyright JS Foundation and other contributors
|
|
* Released under the MIT license
|
|
* https://jquery.org/license
|
|
*
|
|
* Date: 2018-01-20T17:24Z
|
|
*/
|
|
( function( global, factory ) {
|
|
|
|
"use strict";
|
|
|
|
if ( typeof module === "object" && typeof module.exports === "object" ) {
|
|
|
|
// For CommonJS and CommonJS-like environments where a proper `window`
|
|
// is present, execute the factory and get jQuery.
|
|
// For environments that do not have a `window` with a `document`
|
|
// (such as Node.js), expose a factory as module.exports.
|
|
// This accentuates the need for the creation of a real `window`.
|
|
// e.g. var jQuery = require("jquery")(window);
|
|
// See ticket #14549 for more info.
|
|
module.exports = global.document ?
|
|
factory( global, true ) :
|
|
function( w ) {
|
|
if ( !w.document ) {
|
|
throw new Error( "jQuery requires a window with a document" );
|
|
}
|
|
return factory( w );
|
|
};
|
|
} else {
|
|
factory( global );
|
|
}
|
|
|
|
// Pass this if window is not defined yet
|
|
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
|
|
|
|
// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
|
|
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
|
|
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
|
|
// enough that all such attempts are guarded in a try block.
|
|
"use strict";
|
|
|
|
var arr = [];
|
|
|
|
var document = window.document;
|
|
|
|
var getProto = Object.getPrototypeOf;
|
|
|
|
var slice = arr.slice;
|
|
|
|
var concat = arr.concat;
|
|
|
|
var push = arr.push;
|
|
|
|
var indexOf = arr.indexOf;
|
|
|
|
var class2type = {};
|
|
|
|
var toString = class2type.toString;
|
|
|
|
var hasOwn = class2type.hasOwnProperty;
|
|
|
|
var fnToString = hasOwn.toString;
|
|
|
|
var ObjectFunctionString = fnToString.call( Object );
|
|
|
|
var support = {};
|
|
|
|
var isFunction = function isFunction( obj ) {
|
|
|
|
// Support: Chrome <=57, Firefox <=52
|
|
// In some browsers, typeof returns "function" for HTML <object> elements
|
|
// (i.e., `typeof document.createElement( "object" ) === "function"`).
|
|
// We don't want to classify *any* DOM node as a function.
|
|
return typeof obj === "function" && typeof obj.nodeType !== "number";
|
|
};
|
|
|
|
|
|
var isWindow = function isWindow( obj ) {
|
|
return obj != null && obj === obj.window;
|
|
};
|
|
|
|
|
|
|
|
|
|
var preservedScriptAttributes = {
|
|
type: true,
|
|
src: true,
|
|
noModule: true
|
|
};
|
|
|
|
function DOMEval( code, doc, node ) {
|
|
doc = doc || document;
|
|
|
|
var i,
|
|
script = doc.createElement( "script" );
|
|
|
|
script.text = code;
|
|
if ( node ) {
|
|
for ( i in preservedScriptAttributes ) {
|
|
if ( node[ i ] ) {
|
|
script[ i ] = node[ i ];
|
|
}
|
|
}
|
|
}
|
|
doc.head.appendChild( script ).parentNode.removeChild( script );
|
|
}
|
|
|
|
|
|
function toType( obj ) {
|
|
if ( obj == null ) {
|
|
return obj + "";
|
|
}
|
|
|
|
// Support: Android <=2.3 only (functionish RegExp)
|
|
return typeof obj === "object" || typeof obj === "function" ?
|
|
class2type[ toString.call( obj ) ] || "object" :
|
|
typeof obj;
|
|
}
|
|
/* global Symbol */
|
|
// Defining this global in .eslintrc.json would create a danger of using the global
|
|
// unguarded in another place, it seems safer to define global only for this module
|
|
|
|
|
|
|
|
var
|
|
version = "3.3.1",
|
|
|
|
// Define a local copy of jQuery
|
|
jQuery = function( selector, context ) {
|
|
|
|
// The jQuery object is actually just the init constructor 'enhanced'
|
|
// Need init if jQuery is called (just allow error to be thrown if not included)
|
|
return new jQuery.fn.init( selector, context );
|
|
},
|
|
|
|
// Support: Android <=4.0 only
|
|
// Make sure we trim BOM and NBSP
|
|
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
|
|
|
jQuery.fn = jQuery.prototype = {
|
|
|
|
// The current version of jQuery being used
|
|
jquery: version,
|
|
|
|
constructor: jQuery,
|
|
|
|
// The default length of a jQuery object is 0
|
|
length: 0,
|
|
|
|
toArray: function() {
|
|
return slice.call( this );
|
|
},
|
|
|
|
// Get the Nth element in the matched element set OR
|
|
// Get the whole matched element set as a clean array
|
|
get: function( num ) {
|
|
|
|
// Return all the elements in a clean array
|
|
if ( num == null ) {
|
|
return slice.call( this );
|
|
}
|
|
|
|
// Return just the one element from the set
|
|
return num < 0 ? this[ num + this.length ] : this[ num ];
|
|
},
|
|
|
|
// Take an array of elements and push it onto the stack
|
|
// (returning the new matched element set)
|
|
pushStack: function( elems ) {
|
|
|
|
// Build a new jQuery matched element set
|
|
var ret = jQuery.merge( this.constructor(), elems );
|
|
|
|
// Add the old object onto the stack (as a reference)
|
|
ret.prevObject = this;
|
|
|
|
// Return the newly-formed element set
|
|
return ret;
|
|
},
|
|
|
|
// Execute a callback for every element in the matched set.
|
|
each: function( callback ) {
|
|
return jQuery.each( this, callback );
|
|
},
|
|
|
|
map: function( callback ) {
|
|
return this.pushStack( jQuery.map( this, function( elem, i ) {
|
|
return callback.call( elem, i, elem );
|
|
} ) );
|
|
},
|
|
|
|
slice: function() {
|
|
return this.pushStack( slice.apply( this, arguments ) );
|
|
},
|
|
|
|
first: function() {
|
|
return this.eq( 0 );
|
|
},
|
|
|
|
last: function() {
|
|
return this.eq( -1 );
|
|
},
|
|
|
|
eq: function( i ) {
|
|
var len = this.length,
|
|
j = +i + ( i < 0 ? len : 0 );
|
|
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
|
|
},
|
|
|
|
end: function() {
|
|
return this.prevObject || this.constructor();
|
|
},
|
|
|
|
// For internal use only.
|
|
// Behaves like an Array's method, not like a jQuery method.
|
|
push: push,
|
|
sort: arr.sort,
|
|
splice: arr.splice
|
|
};
|
|
|
|
jQuery.extend = jQuery.fn.extend = function() {
|
|
var options, name, src, copy, copyIsArray, clone,
|
|
target = arguments[ 0 ] || {},
|
|
i = 1,
|
|
length = arguments.length,
|
|
deep = false;
|
|
|
|
// Handle a deep copy situation
|
|
if ( typeof target === "boolean" ) {
|
|
deep = target;
|
|
|
|
// Skip the boolean and the target
|
|
target = arguments[ i ] || {};
|
|
i++;
|
|
}
|
|
|
|
// Handle case when target is a string or something (possible in deep copy)
|
|
if ( typeof target !== "object" && !isFunction( target ) ) {
|
|
target = {};
|
|
}
|
|
|
|
// Extend jQuery itself if only one argument is passed
|
|
if ( i === length ) {
|
|
target = this;
|
|
i--;
|
|
}
|
|
|
|
for ( ; i < length; i++ ) {
|
|
|
|
// Only deal with non-null/undefined values
|
|
if ( ( options = arguments[ i ] ) != null ) {
|
|
|
|
// Extend the base object
|
|
for ( name in options ) {
|
|
src = target[ name ];
|
|
copy = options[ name ];
|
|
|
|
// Prevent never-ending loop
|
|
if ( target === copy ) {
|
|
continue;
|
|
}
|
|
|
|
// Recurse if we're merging plain objects or arrays
|
|
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
|
|
( copyIsArray = Array.isArray( copy ) ) ) ) {
|
|
|
|
if ( copyIsArray ) {
|
|
copyIsArray = false;
|
|
clone = src && Array.isArray( src ) ? src : [];
|
|
|
|
} else {
|
|
clone = src && jQuery.isPlainObject( src ) ? src : {};
|
|
}
|
|
|
|
// Never move original objects, clone them
|
|
target[ name ] = jQuery.extend( deep, clone, copy );
|
|
|
|
// Don't bring in undefined values
|
|
} else if ( copy !== undefined ) {
|
|
target[ name ] = copy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the modified object
|
|
return target;
|
|
};
|
|
|
|
jQuery.extend( {
|
|
|
|
// Unique for each copy of jQuery on the page
|
|
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
|
|
|
|
// Assume jQuery is ready without the ready module
|
|
isReady: true,
|
|
|
|
error: function( msg ) {
|
|
throw new Error( msg );
|
|
},
|
|
|
|
noop: function() {},
|
|
|
|
isPlainObject: function( obj ) {
|
|
var proto, Ctor;
|
|
|
|
// Detect obvious negatives
|
|
// Use toString instead of jQuery.type to catch host objects
|
|
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
|
|
return false;
|
|
}
|
|
|
|
proto = getProto( obj );
|
|
|
|
// Objects with no prototype (e.g., `Object.create( null )`) are plain
|
|
if ( !proto ) {
|
|
return true;
|
|
}
|
|
|
|
// Objects with prototype are plain iff they were constructed by a global Object function
|
|
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
|
|
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
|
|
},
|
|
|
|
isEmptyObject: function( obj ) {
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
// See https://github.com/eslint/eslint/issues/6125
|
|
var name;
|
|
|
|
for ( name in obj ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
// Evaluates a script in a global context
|
|
globalEval: function( code ) {
|
|
DOMEval( code );
|
|
},
|
|
|
|
each: function( obj, callback ) {
|
|
var length, i = 0;
|
|
|
|
if ( isArrayLike( obj ) ) {
|
|
length = obj.length;
|
|
for ( ; i < length; i++ ) {
|
|
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for ( i in obj ) {
|
|
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
// Support: Android <=4.0 only
|
|
trim: function( text ) {
|
|
return text == null ?
|
|
"" :
|
|
( text + "" ).replace( rtrim, "" );
|
|
},
|
|
|
|
// results is for internal usage only
|
|
makeArray: function( arr, results ) {
|
|
var ret = results || [];
|
|
|
|
if ( arr != null ) {
|
|
if ( isArrayLike( Object( arr ) ) ) {
|
|
jQuery.merge( ret,
|
|
typeof arr === "string" ?
|
|
[ arr ] : arr
|
|
);
|
|
} else {
|
|
push.call( ret, arr );
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
inArray: function( elem, arr, i ) {
|
|
return arr == null ? -1 : indexOf.call( arr, elem, i );
|
|
},
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
merge: function( first, second ) {
|
|
var len = +second.length,
|
|
j = 0,
|
|
i = first.length;
|
|
|
|
for ( ; j < len; j++ ) {
|
|
first[ i++ ] = second[ j ];
|
|
}
|
|
|
|
first.length = i;
|
|
|
|
return first;
|
|
},
|
|
|
|
grep: function( elems, callback, invert ) {
|
|
var callbackInverse,
|
|
matches = [],
|
|
i = 0,
|
|
length = elems.length,
|
|
callbackExpect = !invert;
|
|
|
|
// Go through the array, only saving the items
|
|
// that pass the validator function
|
|
for ( ; i < length; i++ ) {
|
|
callbackInverse = !callback( elems[ i ], i );
|
|
if ( callbackInverse !== callbackExpect ) {
|
|
matches.push( elems[ i ] );
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
},
|
|
|
|
// arg is for internal usage only
|
|
map: function( elems, callback, arg ) {
|
|
var length, value,
|
|
i = 0,
|
|
ret = [];
|
|
|
|
// Go through the array, translating each of the items to their new values
|
|
if ( isArrayLike( elems ) ) {
|
|
length = elems.length;
|
|
for ( ; i < length; i++ ) {
|
|
value = callback( elems[ i ], i, arg );
|
|
|
|
if ( value != null ) {
|
|
ret.push( value );
|
|
}
|
|
}
|
|
|
|
// Go through every key on the object,
|
|
} else {
|
|
for ( i in elems ) {
|
|
value = callback( elems[ i ], i, arg );
|
|
|
|
if ( value != null ) {
|
|
ret.push( value );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flatten any nested arrays
|
|
return concat.apply( [], ret );
|
|
},
|
|
|
|
// A global GUID counter for objects
|
|
guid: 1,
|
|
|
|
// jQuery.support is not used in Core but other projects attach their
|
|
// properties to it so it needs to exist.
|
|
support: support
|
|
} );
|
|
|
|
if ( typeof Symbol === "function" ) {
|
|
jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
|
|
}
|
|
|
|
// Populate the class2type map
|
|
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
|
|
function( i, name ) {
|
|
class2type[ "[object " + name + "]" ] = name.toLowerCase();
|
|
} );
|
|
|
|
function isArrayLike( obj ) {
|
|
|
|
// Support: real iOS 8.2 only (not reproducible in simulator)
|
|
// `in` check used to prevent JIT error (gh-2145)
|
|
// hasOwn isn't used here due to false negatives
|
|
// regarding Nodelist length in IE
|
|
var length = !!obj && "length" in obj && obj.length,
|
|
type = toType( obj );
|
|
|
|
if ( isFunction( obj ) || isWindow( obj ) ) {
|
|
return false;
|
|
}
|
|
|
|
return type === "array" || length === 0 ||
|
|
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
|
|
}
|
|
var Sizzle =
|
|
/*!
|
|
* Sizzle CSS Selector Engine v2.3.3
|
|
* https://sizzlejs.com/
|
|
*
|
|
* Copyright jQuery Foundation and other contributors
|
|
* Released under the MIT license
|
|
* http://jquery.org/license
|
|
*
|
|
* Date: 2016-08-08
|
|
*/
|
|
(function( window ) {
|
|
|
|
var i,
|
|
support,
|
|
Expr,
|
|
getText,
|
|
isXML,
|
|
tokenize,
|
|
compile,
|
|
select,
|
|
outermostContext,
|
|
sortInput,
|
|
hasDuplicate,
|
|
|
|
// Local document vars
|
|
setDocument,
|
|
document,
|
|
docElem,
|
|
documentIsHTML,
|
|
rbuggyQSA,
|
|
rbuggyMatches,
|
|
matches,
|
|
contains,
|
|
|
|
// Instance-specific data
|
|
expando = "sizzle" + 1 * new Date(),
|
|
preferredDoc = window.document,
|
|
dirruns = 0,
|
|
done = 0,
|
|
classCache = createCache(),
|
|
tokenCache = createCache(),
|
|
compilerCache = createCache(),
|
|
sortOrder = function( a, b ) {
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
}
|
|
return 0;
|
|
},
|
|
|
|
// Instance methods
|
|
hasOwn = ({}).hasOwnProperty,
|
|
arr = [],
|
|
pop = arr.pop,
|
|
push_native = arr.push,
|
|
push = arr.push,
|
|
slice = arr.slice,
|
|
// Use a stripped-down indexOf as it's faster than native
|
|
// https://jsperf.com/thor-indexof-vs-for/5
|
|
indexOf = function( list, elem ) {
|
|
var i = 0,
|
|
len = list.length;
|
|
for ( ; i < len; i++ ) {
|
|
if ( list[i] === elem ) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
|
|
|
|
// Regular expressions
|
|
|
|
// http://www.w3.org/TR/css3-selectors/#whitespace
|
|
whitespace = "[\\x20\\t\\r\\n\\f]",
|
|
|
|
// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
|
|
identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
|
|
|
|
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
|
|
attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
|
|
// Operator (capture 2)
|
|
"*([*^$|!~]?=)" + whitespace +
|
|
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
|
|
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
|
|
"*\\]",
|
|
|
|
pseudos = ":(" + identifier + ")(?:\\((" +
|
|
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
|
|
// 1. quoted (capture 3; capture 4 or capture 5)
|
|
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
|
|
// 2. simple (capture 6)
|
|
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
|
|
// 3. anything else (capture 2)
|
|
".*" +
|
|
")\\)|)",
|
|
|
|
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
|
|
rwhitespace = new RegExp( whitespace + "+", "g" ),
|
|
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
|
|
|
|
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
|
|
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
|
|
|
|
rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
|
|
|
|
rpseudo = new RegExp( pseudos ),
|
|
ridentifier = new RegExp( "^" + identifier + "$" ),
|
|
|
|
matchExpr = {
|
|
"ID": new RegExp( "^#(" + identifier + ")" ),
|
|
"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
|
|
"TAG": new RegExp( "^(" + identifier + "|[*])" ),
|
|
"ATTR": new RegExp( "^" + attributes ),
|
|
"PSEUDO": new RegExp( "^" + pseudos ),
|
|
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
|
|
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
|
|
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
|
|
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
|
|
// For use in libraries implementing .is()
|
|
// We use this for POS matching in `select`
|
|
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
|
|
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
|
|
},
|
|
|
|
rinputs = /^(?:input|select|textarea|button)$/i,
|
|
rheader = /^h\d$/i,
|
|
|
|
rnative = /^[^{]+\{\s*\[native \w/,
|
|
|
|
// Easily-parseable/retrievable ID or TAG or CLASS selectors
|
|
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
|
|
|
|
rsibling = /[+~]/,
|
|
|
|
// CSS escapes
|
|
// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
|
|
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
|
|
funescape = function( _, escaped, escapedWhitespace ) {
|
|
var high = "0x" + escaped - 0x10000;
|
|
// NaN means non-codepoint
|
|
// Support: Firefox<24
|
|
// Workaround erroneous numeric interpretation of +"0x"
|
|
return high !== high || escapedWhitespace ?
|
|
escaped :
|
|
high < 0 ?
|
|
// BMP codepoint
|
|
String.fromCharCode( high + 0x10000 ) :
|
|
// Supplemental Plane codepoint (surrogate pair)
|
|
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
|
|
},
|
|
|
|
// CSS string/identifier serialization
|
|
// https://drafts.csswg.org/cssom/#common-serializing-idioms
|
|
rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
|
|
fcssescape = function( ch, asCodePoint ) {
|
|
if ( asCodePoint ) {
|
|
|
|
// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
|
|
if ( ch === "\0" ) {
|
|
return "\uFFFD";
|
|
}
|
|
|
|
// Control characters and (dependent upon position) numbers get escaped as code points
|
|
return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
|
|
}
|
|
|
|
// Other potentially-special ASCII characters get backslash-escaped
|
|
return "\\" + ch;
|
|
},
|
|
|
|
// Used for iframes
|
|
// See setDocument()
|
|
// Removing the function wrapper causes a "Permission Denied"
|
|
// error in IE
|
|
unloadHandler = function() {
|
|
setDocument();
|
|
},
|
|
|
|
disabledAncestor = addCombinator(
|
|
function( elem ) {
|
|
return elem.disabled === true && ("form" in elem || "label" in elem);
|
|
},
|
|
{ dir: "parentNode", next: "legend" }
|
|
);
|
|
|
|
// Optimize for push.apply( _, NodeList )
|
|
try {
|
|
push.apply(
|
|
(arr = slice.call( preferredDoc.childNodes )),
|
|
preferredDoc.childNodes
|
|
);
|
|
// Support: Android<4.0
|
|
// Detect silently failing push.apply
|
|
arr[ preferredDoc.childNodes.length ].nodeType;
|
|
} catch ( e ) {
|
|
push = { apply: arr.length ?
|
|
|
|
// Leverage slice if possible
|
|
function( target, els ) {
|
|
push_native.apply( target, slice.call(els) );
|
|
} :
|
|
|
|
// Support: IE<9
|
|
// Otherwise append directly
|
|
function( target, els ) {
|
|
var j = target.length,
|
|
i = 0;
|
|
// Can't trust NodeList.length
|
|
while ( (target[j++] = els[i++]) ) {}
|
|
target.length = j - 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
function Sizzle( selector, context, results, seed ) {
|
|
var m, i, elem, nid, match, groups, newSelector,
|
|
newContext = context && context.ownerDocument,
|
|
|
|
// nodeType defaults to 9, since context defaults to document
|
|
nodeType = context ? context.nodeType : 9;
|
|
|
|
results = results || [];
|
|
|
|
// Return early from calls with invalid selector or context
|
|
if ( typeof selector !== "string" || !selector ||
|
|
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
|
|
|
|
return results;
|
|
}
|
|
|
|
// Try to shortcut find operations (as opposed to filters) in HTML documents
|
|
if ( !seed ) {
|
|
|
|
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
|
|
setDocument( context );
|
|
}
|
|
context = context || document;
|
|
|
|
if ( documentIsHTML ) {
|
|
|
|
// If the selector is sufficiently simple, try using a "get*By*" DOM method
|
|
// (excepting DocumentFragment context, where the methods don't exist)
|
|
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
|
|
|
|
// ID selector
|
|
if ( (m = match[1]) ) {
|
|
|
|
// Document context
|
|
if ( nodeType === 9 ) {
|
|
if ( (elem = context.getElementById( m )) ) {
|
|
|
|
// Support: IE, Opera, Webkit
|
|
// TODO: identify versions
|
|
// getElementById can match elements by name instead of ID
|
|
if ( elem.id === m ) {
|
|
results.push( elem );
|
|
return results;
|
|
}
|
|
} else {
|
|
return results;
|
|
}
|
|
|
|
// Element context
|
|
} else {
|
|
|
|
// Support: IE, Opera, Webkit
|
|
// TODO: identify versions
|
|
// getElementById can match elements by name instead of ID
|
|
if ( newContext && (elem = newContext.getElementById( m )) &&
|
|
contains( context, elem ) &&
|
|
elem.id === m ) {
|
|
|
|
results.push( elem );
|
|
return results;
|
|
}
|
|
}
|
|
|
|
// Type selector
|
|
} else if ( match[2] ) {
|
|
push.apply( results, context.getElementsByTagName( selector ) );
|
|
return results;
|
|
|
|
// Class selector
|
|
} else if ( (m = match[3]) && support.getElementsByClassName &&
|
|
context.getElementsByClassName ) {
|
|
|
|
push.apply( results, context.getElementsByClassName( m ) );
|
|
return results;
|
|
}
|
|
}
|
|
|
|
// Take advantage of querySelectorAll
|
|
if ( support.qsa &&
|
|
!compilerCache[ selector + " " ] &&
|
|
(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
|
|
|
|
if ( nodeType !== 1 ) {
|
|
newContext = context;
|
|
newSelector = selector;
|
|
|
|
// qSA looks outside Element context, which is not what we want
|
|
// Thanks to Andrew Dupont for this workaround technique
|
|
// Support: IE <=8
|
|
// Exclude object elements
|
|
} else if ( context.nodeName.toLowerCase() !== "object" ) {
|
|
|
|
// Capture the context ID, setting it first if necessary
|
|
if ( (nid = context.getAttribute( "id" )) ) {
|
|
nid = nid.replace( rcssescape, fcssescape );
|
|
} else {
|
|
context.setAttribute( "id", (nid = expando) );
|
|
}
|
|
|
|
// Prefix every selector in the list
|
|
groups = tokenize( selector );
|
|
i = groups.length;
|
|
while ( i-- ) {
|
|
groups[i] = "#" + nid + " " + toSelector( groups[i] );
|
|
}
|
|
newSelector = groups.join( "," );
|
|
|
|
// Expand context for sibling selectors
|
|
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
|
context;
|
|
}
|
|
|
|
if ( newSelector ) {
|
|
try {
|
|
push.apply( results,
|
|
newContext.querySelectorAll( newSelector )
|
|
);
|
|
return results;
|
|
} catch ( qsaError ) {
|
|
} finally {
|
|
if ( nid === expando ) {
|
|
context.removeAttribute( "id" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// All others
|
|
return select( selector.replace( rtrim, "$1" ), context, results, seed );
|
|
}
|
|
|
|
/**
|
|
* Create key-value caches of limited size
|
|
* @returns {function(string, object)} Returns the Object data after storing it on itself with
|
|
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
|
|
* deleting the oldest entry
|
|
*/
|
|
function createCache() {
|
|
var keys = [];
|
|
|
|
function cache( key, value ) {
|
|
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
|
|
if ( keys.push( key + " " ) > Expr.cacheLength ) {
|
|
// Only keep the most recent entries
|
|
delete cache[ keys.shift() ];
|
|
}
|
|
return (cache[ key + " " ] = value);
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
/**
|
|
* Mark a function for special use by Sizzle
|
|
* @param {Function} fn The function to mark
|
|
*/
|
|
function markFunction( fn ) {
|
|
fn[ expando ] = true;
|
|
return fn;
|
|
}
|
|
|
|
/**
|
|
* Support testing using an element
|
|
* @param {Function} fn Passed the created element and returns a boolean result
|
|
*/
|
|
function assert( fn ) {
|
|
var el = document.createElement("fieldset");
|
|
|
|
try {
|
|
return !!fn( el );
|
|
} catch (e) {
|
|
return false;
|
|
} finally {
|
|
// Remove from its parent by default
|
|
if ( el.parentNode ) {
|
|
el.parentNode.removeChild( el );
|
|
}
|
|
// release memory in IE
|
|
el = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the same handler for all of the specified attrs
|
|
* @param {String} attrs Pipe-separated list of attributes
|
|
* @param {Function} handler The method that will be applied
|
|
*/
|
|
function addHandle( attrs, handler ) {
|
|
var arr = attrs.split("|"),
|
|
i = arr.length;
|
|
|
|
while ( i-- ) {
|
|
Expr.attrHandle[ arr[i] ] = handler;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks document order of two siblings
|
|
* @param {Element} a
|
|
* @param {Element} b
|
|
* @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
|
|
*/
|
|
function siblingCheck( a, b ) {
|
|
var cur = b && a,
|
|
diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
|
|
a.sourceIndex - b.sourceIndex;
|
|
|
|
// Use IE sourceIndex if available on both nodes
|
|
if ( diff ) {
|
|
return diff;
|
|
}
|
|
|
|
// Check if b follows a
|
|
if ( cur ) {
|
|
while ( (cur = cur.nextSibling) ) {
|
|
if ( cur === b ) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return a ? 1 : -1;
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for input types
|
|
* @param {String} type
|
|
*/
|
|
function createInputPseudo( type ) {
|
|
return function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return name === "input" && elem.type === type;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for buttons
|
|
* @param {String} type
|
|
*/
|
|
function createButtonPseudo( type ) {
|
|
return function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return (name === "input" || name === "button") && elem.type === type;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for :enabled/:disabled
|
|
* @param {Boolean} disabled true for :disabled; false for :enabled
|
|
*/
|
|
function createDisabledPseudo( disabled ) {
|
|
|
|
// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
|
|
return function( elem ) {
|
|
|
|
// Only certain elements can match :enabled or :disabled
|
|
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
|
|
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
|
|
if ( "form" in elem ) {
|
|
|
|
// Check for inherited disabledness on relevant non-disabled elements:
|
|
// * listed form-associated elements in a disabled fieldset
|
|
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
|
|
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
|
|
// * option elements in a disabled optgroup
|
|
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
|
|
// All such elements have a "form" property.
|
|
if ( elem.parentNode && elem.disabled === false ) {
|
|
|
|
// Option elements defer to a parent optgroup if present
|
|
if ( "label" in elem ) {
|
|
if ( "label" in elem.parentNode ) {
|
|
return elem.parentNode.disabled === disabled;
|
|
} else {
|
|
return elem.disabled === disabled;
|
|
}
|
|
}
|
|
|
|
// Support: IE 6 - 11
|
|
// Use the isDisabled shortcut property to check for disabled fieldset ancestors
|
|
return elem.isDisabled === disabled ||
|
|
|
|
// Where there is no isDisabled, check manually
|
|
/* jshint -W018 */
|
|
elem.isDisabled !== !disabled &&
|
|
disabledAncestor( elem ) === disabled;
|
|
}
|
|
|
|
return elem.disabled === disabled;
|
|
|
|
// Try to winnow out elements that can't be disabled before trusting the disabled property.
|
|
// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
|
|
// even exist on them, let alone have a boolean value.
|
|
} else if ( "label" in elem ) {
|
|
return elem.disabled === disabled;
|
|
}
|
|
|
|
// Remaining elements are neither :enabled nor :disabled
|
|
return false;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for positionals
|
|
* @param {Function} fn
|
|
*/
|
|
function createPositionalPseudo( fn ) {
|
|
return markFunction(function( argument ) {
|
|
argument = +argument;
|
|
return markFunction(function( seed, matches ) {
|
|
var j,
|
|
matchIndexes = fn( [], seed.length, argument ),
|
|
i = matchIndexes.length;
|
|
|
|
// Match elements found at the specified indexes
|
|
while ( i-- ) {
|
|
if ( seed[ (j = matchIndexes[i]) ] ) {
|
|
seed[j] = !(matches[j] = seed[j]);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks a node for validity as a Sizzle context
|
|
* @param {Element|Object=} context
|
|
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
|
|
*/
|
|
function testContext( context ) {
|
|
return context && typeof context.getElementsByTagName !== "undefined" && context;
|
|
}
|
|
|
|
// Expose support vars for convenience
|
|
support = Sizzle.support = {};
|
|
|
|
/**
|
|
* Detects XML nodes
|
|
* @param {Element|Object} elem An element or a document
|
|
* @returns {Boolean} True iff elem is a non-HTML XML node
|
|
*/
|
|
isXML = Sizzle.isXML = function( elem ) {
|
|
// documentElement is verified for cases where it doesn't yet exist
|
|
// (such as loading iframes in IE - #4833)
|
|
var documentElement = elem && (elem.ownerDocument || elem).documentElement;
|
|
return documentElement ? documentElement.nodeName !== "HTML" : false;
|
|
};
|
|
|
|
/**
|
|
* Sets document-related variables once based on the current document
|
|
* @param {Element|Object} [doc] An element or document object to use to set the document
|
|
* @returns {Object} Returns the current document
|
|
*/
|
|
setDocument = Sizzle.setDocument = function( node ) {
|
|
var hasCompare, subWindow,
|
|
doc = node ? node.ownerDocument || node : preferredDoc;
|
|
|
|
// Return early if doc is invalid or already selected
|
|
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
|
|
return document;
|
|
}
|
|
|
|
// Update global variables
|
|
document = doc;
|
|
docElem = document.documentElement;
|
|
documentIsHTML = !isXML( document );
|
|
|
|
// Support: IE 9-11, Edge
|
|
// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
|
|
if ( preferredDoc !== document &&
|
|
(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
|
|
|
|
// Support: IE 11, Edge
|
|
if ( subWindow.addEventListener ) {
|
|
subWindow.addEventListener( "unload", unloadHandler, false );
|
|
|
|
// Support: IE 9 - 10 only
|
|
} else if ( subWindow.attachEvent ) {
|
|
subWindow.attachEvent( "onunload", unloadHandler );
|
|
}
|
|
}
|
|
|
|
/* Attributes
|
|
---------------------------------------------------------------------- */
|
|
|
|
// Support: IE<8
|
|
// Verify that getAttribute really returns attributes and not properties
|
|
// (excepting IE8 booleans)
|
|
support.attributes = assert(function( el ) {
|
|
el.className = "i";
|
|
return !el.getAttribute("className");
|
|
});
|
|
|
|
/* getElement(s)By*
|
|
---------------------------------------------------------------------- */
|
|
|
|
// Check if getElementsByTagName("*") returns only elements
|
|
support.getElementsByTagName = assert(function( el ) {
|
|
el.appendChild( document.createComment("") );
|
|
return !el.getElementsByTagName("*").length;
|
|
});
|
|
|
|
// Support: IE<9
|
|
support.getElementsByClassName = rnative.test( document.getElementsByClassName );
|
|
|
|
// Support: IE<10
|
|
// Check if getElementById returns elements by name
|
|
// The broken getElementById methods don't pick up programmatically-set names,
|
|
// so use a roundabout getElementsByName test
|
|
support.getById = assert(function( el ) {
|
|
docElem.appendChild( el ).id = expando;
|
|
return !document.getElementsByName || !document.getElementsByName( expando ).length;
|
|
});
|
|
|
|
// ID filter and find
|
|
if ( support.getById ) {
|
|
Expr.filter["ID"] = function( id ) {
|
|
var attrId = id.replace( runescape, funescape );
|
|
return function( elem ) {
|
|
return elem.getAttribute("id") === attrId;
|
|
};
|
|
};
|
|
Expr.find["ID"] = function( id, context ) {
|
|
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
|
|
var elem = context.getElementById( id );
|
|
return elem ? [ elem ] : [];
|
|
}
|
|
};
|
|
} else {
|
|
Expr.filter["ID"] = function( id ) {
|
|
var attrId = id.replace( runescape, funescape );
|
|
return function( elem ) {
|
|
var node = typeof elem.getAttributeNode !== "undefined" &&
|
|
elem.getAttributeNode("id");
|
|
return node && node.value === attrId;
|
|
};
|
|
};
|
|
|
|
// Support: IE 6 - 7 only
|
|
// getElementById is not reliable as a find shortcut
|
|
Expr.find["ID"] = function( id, context ) {
|
|
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
|
|
var node, i, elems,
|
|
elem = context.getElementById( id );
|
|
|
|
if ( elem ) {
|
|
|
|
// Verify the id attribute
|
|
node = elem.getAttributeNode("id");
|
|
if ( node && node.value === id ) {
|
|
return [ elem ];
|
|
}
|
|
|
|
// Fall back on getElementsByName
|
|
elems = context.getElementsByName( id );
|
|
i = 0;
|
|
while ( (elem = elems[i++]) ) {
|
|
node = elem.getAttributeNode("id");
|
|
if ( node && node.value === id ) {
|
|
return [ elem ];
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
};
|
|
}
|
|
|
|
// Tag
|
|
Expr.find["TAG"] = support.getElementsByTagName ?
|
|
function( tag, context ) {
|
|
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
|
return context.getElementsByTagName( tag );
|
|
|
|
// DocumentFragment nodes don't have gEBTN
|
|
} else if ( support.qsa ) {
|
|
return context.querySelectorAll( tag );
|
|
}
|
|
} :
|
|
|
|
function( tag, context ) {
|
|
var elem,
|
|
tmp = [],
|
|
i = 0,
|
|
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
|
|
results = context.getElementsByTagName( tag );
|
|
|
|
// Filter out possible comments
|
|
if ( tag === "*" ) {
|
|
while ( (elem = results[i++]) ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
tmp.push( elem );
|
|
}
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
return results;
|
|
};
|
|
|
|
// Class
|
|
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
|
|
if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
|
|
return context.getElementsByClassName( className );
|
|
}
|
|
};
|
|
|
|
/* QSA/matchesSelector
|
|
---------------------------------------------------------------------- */
|
|
|
|
// QSA and matchesSelector support
|
|
|
|
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
|
|
rbuggyMatches = [];
|
|
|
|
// qSa(:focus) reports false when true (Chrome 21)
|
|
// We allow this because of a bug in IE8/9 that throws an error
|
|
// whenever `document.activeElement` is accessed on an iframe
|
|
// So, we allow :focus to pass through QSA all the time to avoid the IE error
|
|
// See https://bugs.jquery.com/ticket/13378
|
|
rbuggyQSA = [];
|
|
|
|
if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
|
|
// Build QSA regex
|
|
// Regex strategy adopted from Diego Perini
|
|
assert(function( el ) {
|
|
// Select is set to empty string on purpose
|
|
// This is to test IE's treatment of not explicitly
|
|
// setting a boolean content attribute,
|
|
// since its presence should be enough
|
|
// https://bugs.jquery.com/ticket/12359
|
|
docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
|
|
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
|
|
"<option selected=''></option></select>";
|
|
|
|
// Support: IE8, Opera 11-12.16
|
|
// Nothing should be selected when empty strings follow ^= or $= or *=
|
|
// The test attribute must be unknown in Opera but "safe" for WinRT
|
|
// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
|
|
if ( el.querySelectorAll("[msallowcapture^='']").length ) {
|
|
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
|
|
}
|
|
|
|
// Support: IE8
|
|
// Boolean attributes and "value" are not treated correctly
|
|
if ( !el.querySelectorAll("[selected]").length ) {
|
|
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
|
|
}
|
|
|
|
// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
|
|
if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
|
|
rbuggyQSA.push("~=");
|
|
}
|
|
|
|
// Webkit/Opera - :checked should return selected option elements
|
|
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
|
|
// IE8 throws error here and will not see later tests
|
|
if ( !el.querySelectorAll(":checked").length ) {
|
|
rbuggyQSA.push(":checked");
|
|
}
|
|
|
|
// Support: Safari 8+, iOS 8+
|
|
// https://bugs.webkit.org/show_bug.cgi?id=136851
|
|
// In-page `selector#id sibling-combinator selector` fails
|
|
if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
|
|
rbuggyQSA.push(".#.+[+~]");
|
|
}
|
|
});
|
|
|
|
assert(function( el ) {
|
|
el.innerHTML = "<a href='' disabled='disabled'></a>" +
|
|
"<select disabled='disabled'><option/></select>";
|
|
|
|
// Support: Windows 8 Native Apps
|
|
// The type and name attributes are restricted during .innerHTML assignment
|
|
var input = document.createElement("input");
|
|
input.setAttribute( "type", "hidden" );
|
|
el.appendChild( input ).setAttribute( "name", "D" );
|
|
|
|
// Support: IE8
|
|
// Enforce case-sensitivity of name attribute
|
|
if ( el.querySelectorAll("[name=d]").length ) {
|
|
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
|
|
}
|
|
|
|
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
|
|
// IE8 throws error here and will not see later tests
|
|
if ( el.querySelectorAll(":enabled").length !== 2 ) {
|
|
rbuggyQSA.push( ":enabled", ":disabled" );
|
|
}
|
|
|
|
// Support: IE9-11+
|
|
// IE's :disabled selector does not pick up the children of disabled fieldsets
|
|
docElem.appendChild( el ).disabled = true;
|
|
if ( el.querySelectorAll(":disabled").length !== 2 ) {
|
|
rbuggyQSA.push( ":enabled", ":disabled" );
|
|
}
|
|
|
|
// Opera 10-11 does not throw on post-comma invalid pseudos
|
|
el.querySelectorAll("*,:x");
|
|
rbuggyQSA.push(",.*:");
|
|
});
|
|
}
|
|
|
|
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
|
|
docElem.webkitMatchesSelector ||
|
|
docElem.mozMatchesSelector ||
|
|
docElem.oMatchesSelector ||
|
|
docElem.msMatchesSelector) )) ) {
|
|
|
|
assert(function( el ) {
|
|
// Check to see if it's possible to do matchesSelector
|
|
// on a disconnected node (IE 9)
|
|
support.disconnectedMatch = matches.call( el, "*" );
|
|
|
|
// This should fail with an exception
|
|
// Gecko does not error, returns false instead
|
|
matches.call( el, "[s!='']:x" );
|
|
rbuggyMatches.push( "!=", pseudos );
|
|
});
|
|
}
|
|
|
|
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
|
|
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
|
|
|
|
/* Contains
|
|
---------------------------------------------------------------------- */
|
|
hasCompare = rnative.test( docElem.compareDocumentPosition );
|
|
|
|
// Element contains another
|
|
// Purposefully self-exclusive
|
|
// As in, an element does not contain itself
|
|
contains = hasCompare || rnative.test( docElem.contains ) ?
|
|
function( a, b ) {
|
|
var adown = a.nodeType === 9 ? a.documentElement : a,
|
|
bup = b && b.parentNode;
|
|
return a === bup || !!( bup && bup.nodeType === 1 && (
|
|
adown.contains ?
|
|
adown.contains( bup ) :
|
|
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
|
|
));
|
|
} :
|
|
function( a, b ) {
|
|
if ( b ) {
|
|
while ( (b = b.parentNode) ) {
|
|
if ( b === a ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/* Sorting
|
|
---------------------------------------------------------------------- */
|
|
|
|
// Document order sorting
|
|
sortOrder = hasCompare ?
|
|
function( a, b ) {
|
|
|
|
// Flag for duplicate removal
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
return 0;
|
|
}
|
|
|
|
// Sort on method existence if only one input has compareDocumentPosition
|
|
var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
|
|
if ( compare ) {
|
|
return compare;
|
|
}
|
|
|
|
// Calculate position if both inputs belong to the same document
|
|
compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
|
|
a.compareDocumentPosition( b ) :
|
|
|
|
// Otherwise we know they are disconnected
|
|
1;
|
|
|
|
// Disconnected nodes
|
|
if ( compare & 1 ||
|
|
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
|
|
|
|
// Choose the first element that is related to our preferred document
|
|
if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
|
|
return -1;
|
|
}
|
|
if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
|
|
return 1;
|
|
}
|
|
|
|
// Maintain original order
|
|
return sortInput ?
|
|
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
|
|
0;
|
|
}
|
|
|
|
return compare & 4 ? -1 : 1;
|
|
} :
|
|
function( a, b ) {
|
|
// Exit early if the nodes are identical
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
return 0;
|
|
}
|
|
|
|
var cur,
|
|
i = 0,
|
|
aup = a.parentNode,
|
|
bup = b.parentNode,
|
|
ap = [ a ],
|
|
bp = [ b ];
|
|
|
|
// Parentless nodes are either documents or disconnected
|
|
if ( !aup || !bup ) {
|
|
return a === document ? -1 :
|
|
b === document ? 1 :
|
|
aup ? -1 :
|
|
bup ? 1 :
|
|
sortInput ?
|
|
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
|
|
0;
|
|
|
|
// If the nodes are siblings, we can do a quick check
|
|
} else if ( aup === bup ) {
|
|
return siblingCheck( a, b );
|
|
}
|
|
|
|
// Otherwise we need full lists of their ancestors for comparison
|
|
cur = a;
|
|
while ( (cur = cur.parentNode) ) {
|
|
ap.unshift( cur );
|
|
}
|
|
cur = b;
|
|
while ( (cur = cur.parentNode) ) {
|
|
bp.unshift( cur );
|
|
}
|
|
|
|
// Walk down the tree looking for a discrepancy
|
|
while ( ap[i] === bp[i] ) {
|
|
i++;
|
|
}
|
|
|
|
return i ?
|
|
// Do a sibling check if the nodes have a common ancestor
|
|
siblingCheck( ap[i], bp[i] ) :
|
|
|
|
// Otherwise nodes in our document sort first
|
|
ap[i] === preferredDoc ? -1 :
|
|
bp[i] === preferredDoc ? 1 :
|
|
0;
|
|
};
|
|
|
|
return document;
|
|
};
|
|
|
|
Sizzle.matches = function( expr, elements ) {
|
|
return Sizzle( expr, null, null, elements );
|
|
};
|
|
|
|
Sizzle.matchesSelector = function( elem, expr ) {
|
|
// Set document vars if needed
|
|
if ( ( elem.ownerDocument || elem ) !== document ) {
|
|
setDocument( elem );
|
|
}
|
|
|
|
// Make sure that attribute selectors are quoted
|
|
expr = expr.replace( rattributeQuotes, "='$1']" );
|
|
|
|
if ( support.matchesSelector && documentIsHTML &&
|
|
!compilerCache[ expr + " " ] &&
|
|
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
|
|
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
|
|
|
|
try {
|
|
var ret = matches.call( elem, expr );
|
|
|
|
// IE 9's matchesSelector returns false on disconnected nodes
|
|
if ( ret || support.disconnectedMatch ||
|
|
// As well, disconnected nodes are said to be in a document
|
|
// fragment in IE 9
|
|
elem.document && elem.document.nodeType !== 11 ) {
|
|
return ret;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
return Sizzle( expr, document, null, [ elem ] ).length > 0;
|
|
};
|
|
|
|
Sizzle.contains = function( context, elem ) {
|
|
// Set document vars if needed
|
|
if ( ( context.ownerDocument || context ) !== document ) {
|
|
setDocument( context );
|
|
}
|
|
return contains( context, elem );
|
|
};
|
|
|
|
Sizzle.attr = function( elem, name ) {
|
|
// Set document vars if needed
|
|
if ( ( elem.ownerDocument || elem ) !== document ) {
|
|
setDocument( elem );
|
|
}
|
|
|
|
var fn = Expr.attrHandle[ name.toLowerCase() ],
|
|
// Don't get fooled by Object.prototype properties (jQuery #13807)
|
|
val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
|
|
fn( elem, name, !documentIsHTML ) :
|
|
undefined;
|
|
|
|
return val !== undefined ?
|
|
val :
|
|
support.attributes || !documentIsHTML ?
|
|
elem.getAttribute( name ) :
|
|
(val = elem.getAttributeNode(name)) && val.specified ?
|
|
val.value :
|
|
null;
|
|
};
|
|
|
|
Sizzle.escape = function( sel ) {
|
|
return (sel + "").replace( rcssescape, fcssescape );
|
|
};
|
|
|
|
Sizzle.error = function( msg ) {
|
|
throw new Error( "Syntax error, unrecognized expression: " + msg );
|
|
};
|
|
|
|
/**
|
|
* Document sorting and removing duplicates
|
|
* @param {ArrayLike} results
|
|
*/
|
|
Sizzle.uniqueSort = function( results ) {
|
|
var elem,
|
|
duplicates = [],
|
|
j = 0,
|
|
i = 0;
|
|
|
|
// Unless we *know* we can detect duplicates, assume their presence
|
|
hasDuplicate = !support.detectDuplicates;
|
|
sortInput = !support.sortStable && results.slice( 0 );
|
|
results.sort( sortOrder );
|
|
|
|
if ( hasDuplicate ) {
|
|
while ( (elem = results[i++]) ) {
|
|
if ( elem === results[ i ] ) {
|
|
j = duplicates.push( i );
|
|
}
|
|
}
|
|
while ( j-- ) {
|
|
results.splice( duplicates[ j ], 1 );
|
|
}
|
|
}
|
|
|
|
// Clear input after sorting to release objects
|
|
// See https://github.com/jquery/sizzle/pull/225
|
|
sortInput = null;
|
|
|
|
return results;
|
|
};
|
|
|
|
/**
|
|
* Utility function for retrieving the text value of an array of DOM nodes
|
|
* @param {Array|Element} elem
|
|
*/
|
|
getText = Sizzle.getText = function( elem ) {
|
|
var node,
|
|
ret = "",
|
|
i = 0,
|
|
nodeType = elem.nodeType;
|
|
|
|
if ( !nodeType ) {
|
|
// If no nodeType, this is expected to be an array
|
|
while ( (node = elem[i++]) ) {
|
|
// Do not traverse comment nodes
|
|
ret += getText( node );
|
|
}
|
|
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
|
|
// Use textContent for elements
|
|
// innerText usage removed for consistency of new lines (jQuery #11153)
|
|
if ( typeof elem.textContent === "string" ) {
|
|
return elem.textContent;
|
|
} else {
|
|
// Traverse its children
|
|
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
|
|
ret += getText( elem );
|
|
}
|
|
}
|
|
} else if ( nodeType === 3 || nodeType === 4 ) {
|
|
return elem.nodeValue;
|
|
}
|
|
// Do not include comment or processing instruction nodes
|
|
|
|
return ret;
|
|
};
|
|
|
|
Expr = Sizzle.selectors = {
|
|
|
|
// Can be adjusted by the user
|
|
cacheLength: 50,
|
|
|
|
createPseudo: markFunction,
|
|
|
|
match: matchExpr,
|
|
|
|
attrHandle: {},
|
|
|
|
find: {},
|
|
|
|
relative: {
|
|
">": { dir: "parentNode", first: true },
|
|
" ": { dir: "parentNode" },
|
|
"+": { dir: "previousSibling", first: true },
|
|
"~": { dir: "previousSibling" }
|
|
},
|
|
|
|
preFilter: {
|
|
"ATTR": function( match ) {
|
|
match[1] = match[1].replace( runescape, funescape );
|
|
|
|
// Move the given value to match[3] whether quoted or unquoted
|
|
match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
|
|
|
|
if ( match[2] === "~=" ) {
|
|
match[3] = " " + match[3] + " ";
|
|
}
|
|
|
|
return match.slice( 0, 4 );
|
|
},
|
|
|
|
"CHILD": function( match ) {
|
|
/* matches from matchExpr["CHILD"]
|
|
1 type (only|nth|...)
|
|
2 what (child|of-type)
|
|
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
|
|
4 xn-component of xn+y argument ([+-]?\d*n|)
|
|
5 sign of xn-component
|
|
6 x of xn-component
|
|
7 sign of y-component
|
|
8 y of y-component
|
|
*/
|
|
match[1] = match[1].toLowerCase();
|
|
|
|
if ( match[1].slice( 0, 3 ) === "nth" ) {
|
|
// nth-* requires argument
|
|
if ( !match[3] ) {
|
|
Sizzle.error( match[0] );
|
|
}
|
|
|
|
// numeric x and y parameters for Expr.filter.CHILD
|
|
// remember that false/true cast respectively to 0/1
|
|
match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
|
|
match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
|
|
|
|
// other types prohibit arguments
|
|
} else if ( match[3] ) {
|
|
Sizzle.error( match[0] );
|
|
}
|
|
|
|
return match;
|
|
},
|
|
|
|
"PSEUDO": function( match ) {
|
|
var excess,
|
|
unquoted = !match[6] && match[2];
|
|
|
|
if ( matchExpr["CHILD"].test( match[0] ) ) {
|
|
return null;
|
|
}
|
|
|
|
// Accept quoted arguments as-is
|
|
if ( match[3] ) {
|
|
match[2] = match[4] || match[5] || "";
|
|
|
|
// Strip excess characters from unquoted arguments
|
|
} else if ( unquoted && rpseudo.test( unquoted ) &&
|
|
// Get excess from tokenize (recursively)
|
|
(excess = tokenize( unquoted, true )) &&
|
|
// advance to the next closing parenthesis
|
|
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
|
|
|
|
// excess is a negative index
|
|
match[0] = match[0].slice( 0, excess );
|
|
match[2] = unquoted.slice( 0, excess );
|
|
}
|
|
|
|
// Return only captures needed by the pseudo filter method (type and argument)
|
|
return match.slice( 0, 3 );
|
|
}
|
|
},
|
|
|
|
filter: {
|
|
|
|
"TAG": function( nodeNameSelector ) {
|
|
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
|
|
return nodeNameSelector === "*" ?
|
|
function() { return true; } :
|
|
function( elem ) {
|
|
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
|
|
};
|
|
},
|
|
|
|
"CLASS": function( className ) {
|
|
var pattern = classCache[ className + " " ];
|
|
|
|
return pattern ||
|
|
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
|
|
classCache( className, function( elem ) {
|
|
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
|
|
});
|
|
},
|
|
|
|
"ATTR": function( name, operator, check ) {
|
|
return function( elem ) {
|
|
var result = Sizzle.attr( elem, name );
|
|
|
|
if ( result == null ) {
|
|
return operator === "!=";
|
|
}
|
|
if ( !operator ) {
|
|
return true;
|
|
}
|
|
|
|
result += "";
|
|
|
|
return operator === "=" ? result === check :
|
|
operator === "!=" ? result !== check :
|
|
operator === "^=" ? check && result.indexOf( check ) === 0 :
|
|
operator === "*=" ? check && result.indexOf( check ) > -1 :
|
|
operator === "$=" ? check && result.slice( -check.length ) === check :
|
|
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
|
|
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
|
|
false;
|
|
};
|
|
},
|
|
|
|
"CHILD": function( type, what, argument, first, last ) {
|
|
var simple = type.slice( 0, 3 ) !== "nth",
|
|
forward = type.slice( -4 ) !== "last",
|
|
ofType = what === "of-type";
|
|
|
|
return first === 1 && last === 0 ?
|
|
|
|
// Shortcut for :nth-*(n)
|
|
function( elem ) {
|
|
return !!elem.parentNode;
|
|
} :
|
|
|
|
function( elem, context, xml ) {
|
|
var cache, uniqueCache, outerCache, node, nodeIndex, start,
|
|
dir = simple !== forward ? "nextSibling" : "previousSibling",
|
|
parent = elem.parentNode,
|
|
name = ofType && elem.nodeName.toLowerCase(),
|
|
useCache = !xml && !ofType,
|
|
diff = false;
|
|
|
|
if ( parent ) {
|
|
|
|
// :(first|last|only)-(child|of-type)
|
|
if ( simple ) {
|
|
while ( dir ) {
|
|
node = elem;
|
|
while ( (node = node[ dir ]) ) {
|
|
if ( ofType ?
|
|
node.nodeName.toLowerCase() === name :
|
|
node.nodeType === 1 ) {
|
|
|
|
return false;
|
|
}
|
|
}
|
|
// Reverse direction for :only-* (if we haven't yet done so)
|
|
start = dir = type === "only" && !start && "nextSibling";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
start = [ forward ? parent.firstChild : parent.lastChild ];
|
|
|
|
// non-xml :nth-child(...) stores cache data on `parent`
|
|
if ( forward && useCache ) {
|
|
|
|
// Seek `elem` from a previously-cached index
|
|
|
|
// ...in a gzip-friendly way
|
|
node = parent;
|
|
outerCache = node[ expando ] || (node[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ node.uniqueID ] ||
|
|
(outerCache[ node.uniqueID ] = {});
|
|
|
|
cache = uniqueCache[ type ] || [];
|
|
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
|
|
diff = nodeIndex && cache[ 2 ];
|
|
node = nodeIndex && parent.childNodes[ nodeIndex ];
|
|
|
|
while ( (node = ++nodeIndex && node && node[ dir ] ||
|
|
|
|
// Fallback to seeking `elem` from the start
|
|
(diff = nodeIndex = 0) || start.pop()) ) {
|
|
|
|
// When found, cache indexes on `parent` and break
|
|
if ( node.nodeType === 1 && ++diff && node === elem ) {
|
|
uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Use previously-cached element index if available
|
|
if ( useCache ) {
|
|
// ...in a gzip-friendly way
|
|
node = elem;
|
|
outerCache = node[ expando ] || (node[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ node.uniqueID ] ||
|
|
(outerCache[ node.uniqueID ] = {});
|
|
|
|
cache = uniqueCache[ type ] || [];
|
|
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
|
|
diff = nodeIndex;
|
|
}
|
|
|
|
// xml :nth-child(...)
|
|
// or :nth-last-child(...) or :nth(-last)?-of-type(...)
|
|
if ( diff === false ) {
|
|
// Use the same loop as above to seek `elem` from the start
|
|
while ( (node = ++nodeIndex && node && node[ dir ] ||
|
|
(diff = nodeIndex = 0) || start.pop()) ) {
|
|
|
|
if ( ( ofType ?
|
|
node.nodeName.toLowerCase() === name :
|
|
node.nodeType === 1 ) &&
|
|
++diff ) {
|
|
|
|
// Cache the index of each encountered element
|
|
if ( useCache ) {
|
|
outerCache = node[ expando ] || (node[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ node.uniqueID ] ||
|
|
(outerCache[ node.uniqueID ] = {});
|
|
|
|
uniqueCache[ type ] = [ dirruns, diff ];
|
|
}
|
|
|
|
if ( node === elem ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Incorporate the offset, then check against cycle size
|
|
diff -= last;
|
|
return diff === first || ( diff % first === 0 && diff / first >= 0 );
|
|
}
|
|
};
|
|
},
|
|
|
|
"PSEUDO": function( pseudo, argument ) {
|
|
// pseudo-class names are case-insensitive
|
|
// http://www.w3.org/TR/selectors/#pseudo-classes
|
|
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
|
|
// Remember that setFilters inherits from pseudos
|
|
var args,
|
|
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
|
|
Sizzle.error( "unsupported pseudo: " + pseudo );
|
|
|
|
// The user may use createPseudo to indicate that
|
|
// arguments are needed to create the filter function
|
|
// just as Sizzle does
|
|
if ( fn[ expando ] ) {
|
|
return fn( argument );
|
|
}
|
|
|
|
// But maintain support for old signatures
|
|
if ( fn.length > 1 ) {
|
|
args = [ pseudo, pseudo, "", argument ];
|
|
return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
|
|
markFunction(function( seed, matches ) {
|
|
var idx,
|
|
matched = fn( seed, argument ),
|
|
i = matched.length;
|
|
while ( i-- ) {
|
|
idx = indexOf( seed, matched[i] );
|
|
seed[ idx ] = !( matches[ idx ] = matched[i] );
|
|
}
|
|
}) :
|
|
function( elem ) {
|
|
return fn( elem, 0, args );
|
|
};
|
|
}
|
|
|
|
return fn;
|
|
}
|
|
},
|
|
|
|
pseudos: {
|
|
// Potentially complex pseudos
|
|
"not": markFunction(function( selector ) {
|
|
// Trim the selector passed to compile
|
|
// to avoid treating leading and trailing
|
|
// spaces as combinators
|
|
var input = [],
|
|
results = [],
|
|
matcher = compile( selector.replace( rtrim, "$1" ) );
|
|
|
|
return matcher[ expando ] ?
|
|
markFunction(function( seed, matches, context, xml ) {
|
|
var elem,
|
|
unmatched = matcher( seed, null, xml, [] ),
|
|
i = seed.length;
|
|
|
|
// Match elements unmatched by `matcher`
|
|
while ( i-- ) {
|
|
if ( (elem = unmatched[i]) ) {
|
|
seed[i] = !(matches[i] = elem);
|
|
}
|
|
}
|
|
}) :
|
|
function( elem, context, xml ) {
|
|
input[0] = elem;
|
|
matcher( input, null, xml, results );
|
|
// Don't keep the element (issue #299)
|
|
input[0] = null;
|
|
return !results.pop();
|
|
};
|
|
}),
|
|
|
|
"has": markFunction(function( selector ) {
|
|
return function( elem ) {
|
|
return Sizzle( selector, elem ).length > 0;
|
|
};
|
|
}),
|
|
|
|
"contains": markFunction(function( text ) {
|
|
text = text.replace( runescape, funescape );
|
|
return function( elem ) {
|
|
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
|
|
};
|
|
}),
|
|
|
|
// "Whether an element is represented by a :lang() selector
|
|
// is based solely on the element's language value
|
|
// being equal to the identifier C,
|
|
// or beginning with the identifier C immediately followed by "-".
|
|
// The matching of C against the element's language value is performed case-insensitively.
|
|
// The identifier C does not have to be a valid language name."
|
|
// http://www.w3.org/TR/selectors/#lang-pseudo
|
|
"lang": markFunction( function( lang ) {
|
|
// lang value must be a valid identifier
|
|
if ( !ridentifier.test(lang || "") ) {
|
|
Sizzle.error( "unsupported lang: " + lang );
|
|
}
|
|
lang = lang.replace( runescape, funescape ).toLowerCase();
|
|
return function( elem ) {
|
|
var elemLang;
|
|
do {
|
|
if ( (elemLang = documentIsHTML ?
|
|
elem.lang :
|
|
elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
|
|
|
|
elemLang = elemLang.toLowerCase();
|
|
return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
|
|
}
|
|
} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
|
|
return false;
|
|
};
|
|
}),
|
|
|
|
// Miscellaneous
|
|
"target": function( elem ) {
|
|
var hash = window.location && window.location.hash;
|
|
return hash && hash.slice( 1 ) === elem.id;
|
|
},
|
|
|
|
"root": function( elem ) {
|
|
return elem === docElem;
|
|
},
|
|
|
|
"focus": function( elem ) {
|
|
return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
|
|
},
|
|
|
|
// Boolean properties
|
|
"enabled": createDisabledPseudo( false ),
|
|
"disabled": createDisabledPseudo( true ),
|
|
|
|
"checked": function( elem ) {
|
|
// In CSS3, :checked should return both checked and selected elements
|
|
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
|
|
var nodeName = elem.nodeName.toLowerCase();
|
|
return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
|
|
},
|
|
|
|
"selected": function( elem ) {
|
|
// Accessing this property makes selected-by-default
|
|
// options in Safari work properly
|
|
if ( elem.parentNode ) {
|
|
elem.parentNode.selectedIndex;
|
|
}
|
|
|
|
return elem.selected === true;
|
|
},
|
|
|
|
// Contents
|
|
"empty": function( elem ) {
|
|
// http://www.w3.org/TR/selectors/#empty-pseudo
|
|
// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
|
|
// but not by others (comment: 8; processing instruction: 7; etc.)
|
|
// nodeType < 6 works because attributes (2) do not appear as children
|
|
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
|
|
if ( elem.nodeType < 6 ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
"parent": function( elem ) {
|
|
return !Expr.pseudos["empty"]( elem );
|
|
},
|
|
|
|
// Element/input types
|
|
"header": function( elem ) {
|
|
return rheader.test( elem.nodeName );
|
|
},
|
|
|
|
"input": function( elem ) {
|
|
return rinputs.test( elem.nodeName );
|
|
},
|
|
|
|
"button": function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return name === "input" && elem.type === "button" || name === "button";
|
|
},
|
|
|
|
"text": function( elem ) {
|
|
var attr;
|
|
return elem.nodeName.toLowerCase() === "input" &&
|
|
elem.type === "text" &&
|
|
|
|
// Support: IE<8
|
|
// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
|
|
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
|
|
},
|
|
|
|
// Position-in-collection
|
|
"first": createPositionalPseudo(function() {
|
|
return [ 0 ];
|
|
}),
|
|
|
|
"last": createPositionalPseudo(function( matchIndexes, length ) {
|
|
return [ length - 1 ];
|
|
}),
|
|
|
|
"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
|
|
return [ argument < 0 ? argument + length : argument ];
|
|
}),
|
|
|
|
"even": createPositionalPseudo(function( matchIndexes, length ) {
|
|
var i = 0;
|
|
for ( ; i < length; i += 2 ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
}),
|
|
|
|
"odd": createPositionalPseudo(function( matchIndexes, length ) {
|
|
var i = 1;
|
|
for ( ; i < length; i += 2 ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
}),
|
|
|
|
"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
|
|
var i = argument < 0 ? argument + length : argument;
|
|
for ( ; --i >= 0; ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
}),
|
|
|
|
"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
|
|
var i = argument < 0 ? argument + length : argument;
|
|
for ( ; ++i < length; ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
})
|
|
}
|
|
};
|
|
|
|
Expr.pseudos["nth"] = Expr.pseudos["eq"];
|
|
|
|
// Add button/input type pseudos
|
|
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
|
|
Expr.pseudos[ i ] = createInputPseudo( i );
|
|
}
|
|
for ( i in { submit: true, reset: true } ) {
|
|
Expr.pseudos[ i ] = createButtonPseudo( i );
|
|
}
|
|
|
|
// Easy API for creating new setFilters
|
|
function setFilters() {}
|
|
setFilters.prototype = Expr.filters = Expr.pseudos;
|
|
Expr.setFilters = new setFilters();
|
|
|
|
tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
|
|
var matched, match, tokens, type,
|
|
soFar, groups, preFilters,
|
|
cached = tokenCache[ selector + " " ];
|
|
|
|
if ( cached ) {
|
|
return parseOnly ? 0 : cached.slice( 0 );
|
|
}
|
|
|
|
soFar = selector;
|
|
groups = [];
|
|
preFilters = Expr.preFilter;
|
|
|
|
while ( soFar ) {
|
|
|
|
// Comma and first run
|
|
if ( !matched || (match = rcomma.exec( soFar )) ) {
|
|
if ( match ) {
|
|
// Don't consume trailing commas as valid
|
|
soFar = soFar.slice( match[0].length ) || soFar;
|
|
}
|
|
groups.push( (tokens = []) );
|
|
}
|
|
|
|
matched = false;
|
|
|
|
// Combinators
|
|
if ( (match = rcombinators.exec( soFar )) ) {
|
|
matched = match.shift();
|
|
tokens.push({
|
|
value: matched,
|
|
// Cast descendant combinators to space
|
|
type: match[0].replace( rtrim, " " )
|
|
});
|
|
soFar = soFar.slice( matched.length );
|
|
}
|
|
|
|
// Filters
|
|
for ( type in Expr.filter ) {
|
|
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
|
|
(match = preFilters[ type ]( match ))) ) {
|
|
matched = match.shift();
|
|
tokens.push({
|
|
value: matched,
|
|
type: type,
|
|
matches: match
|
|
});
|
|
soFar = soFar.slice( matched.length );
|
|
}
|
|
}
|
|
|
|
if ( !matched ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return the length of the invalid excess
|
|
// if we're just parsing
|
|
// Otherwise, throw an error or return tokens
|
|
return parseOnly ?
|
|
soFar.length :
|
|
soFar ?
|
|
Sizzle.error( selector ) :
|
|
// Cache the tokens
|
|
tokenCache( selector, groups ).slice( 0 );
|
|
};
|
|
|
|
function toSelector( tokens ) {
|
|
var i = 0,
|
|
len = tokens.length,
|
|
selector = "";
|
|
for ( ; i < len; i++ ) {
|
|
selector += tokens[i].value;
|
|
}
|
|
return selector;
|
|
}
|
|
|
|
function addCombinator( matcher, combinator, base ) {
|
|
var dir = combinator.dir,
|
|
skip = combinator.next,
|
|
key = skip || dir,
|
|
checkNonElements = base && key === "parentNode",
|
|
doneName = done++;
|
|
|
|
return combinator.first ?
|
|
// Check against closest ancestor/preceding element
|
|
function( elem, context, xml ) {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 || checkNonElements ) {
|
|
return matcher( elem, context, xml );
|
|
}
|
|
}
|
|
return false;
|
|
} :
|
|
|
|
// Check against all ancestor/preceding elements
|
|
function( elem, context, xml ) {
|
|
var oldCache, uniqueCache, outerCache,
|
|
newCache = [ dirruns, doneName ];
|
|
|
|
// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
|
|
if ( xml ) {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 || checkNonElements ) {
|
|
if ( matcher( elem, context, xml ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 || checkNonElements ) {
|
|
outerCache = elem[ expando ] || (elem[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
|
|
|
|
if ( skip && skip === elem.nodeName.toLowerCase() ) {
|
|
elem = elem[ dir ] || elem;
|
|
} else if ( (oldCache = uniqueCache[ key ]) &&
|
|
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
|
|
|
|
// Assign to newCache so results back-propagate to previous elements
|
|
return (newCache[ 2 ] = oldCache[ 2 ]);
|
|
} else {
|
|
// Reuse newcache so results back-propagate to previous elements
|
|
uniqueCache[ key ] = newCache;
|
|
|
|
// A match means we're done; a fail means we have to keep checking
|
|
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
function elementMatcher( matchers ) {
|
|
return matchers.length > 1 ?
|
|
function( elem, context, xml ) {
|
|
var i = matchers.length;
|
|
while ( i-- ) {
|
|
if ( !matchers[i]( elem, context, xml ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} :
|
|
matchers[0];
|
|
}
|
|
|
|
function multipleContexts( selector, contexts, results ) {
|
|
var i = 0,
|
|
len = contexts.length;
|
|
for ( ; i < len; i++ ) {
|
|
Sizzle( selector, contexts[i], results );
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function condense( unmatched, map, filter, context, xml ) {
|
|
var elem,
|
|
newUnmatched = [],
|
|
i = 0,
|
|
len = unmatched.length,
|
|
mapped = map != null;
|
|
|
|
for ( ; i < len; i++ ) {
|
|
if ( (elem = unmatched[i]) ) {
|
|
if ( !filter || filter( elem, context, xml ) ) {
|
|
newUnmatched.push( elem );
|
|
if ( mapped ) {
|
|
map.push( i );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return newUnmatched;
|
|
}
|
|
|
|
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
|
|
if ( postFilter && !postFilter[ expando ] ) {
|
|
postFilter = setMatcher( postFilter );
|
|
}
|
|
if ( postFinder && !postFinder[ expando ] ) {
|
|
postFinder = setMatcher( postFinder, postSelector );
|
|
}
|
|
return markFunction(function( seed, results, context, xml ) {
|
|
var temp, i, elem,
|
|
preMap = [],
|
|
postMap = [],
|
|
preexisting = results.length,
|
|
|
|
// Get initial elements from seed or context
|
|
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
|
|
|
|
// Prefilter to get matcher input, preserving a map for seed-results synchronization
|
|
matcherIn = preFilter && ( seed || !selector ) ?
|
|
condense( elems, preMap, preFilter, context, xml ) :
|
|
elems,
|
|
|
|
matcherOut = matcher ?
|
|
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
|
|
postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
|
|
|
|
// ...intermediate processing is necessary
|
|
[] :
|
|
|
|
// ...otherwise use results directly
|
|
results :
|
|
matcherIn;
|
|
|
|
// Find primary matches
|
|
if ( matcher ) {
|
|
matcher( matcherIn, matcherOut, context, xml );
|
|
}
|
|
|
|
// Apply postFilter
|
|
if ( postFilter ) {
|
|
temp = condense( matcherOut, postMap );
|
|
postFilter( temp, [], context, xml );
|
|
|
|
// Un-match failing elements by moving them back to matcherIn
|
|
i = temp.length;
|
|
while ( i-- ) {
|
|
if ( (elem = temp[i]) ) {
|
|
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( seed ) {
|
|
if ( postFinder || preFilter ) {
|
|
if ( postFinder ) {
|
|
// Get the final matcherOut by condensing this intermediate into postFinder contexts
|
|
temp = [];
|
|
i = matcherOut.length;
|
|
while ( i-- ) {
|
|
if ( (elem = matcherOut[i]) ) {
|
|
// Restore matcherIn since elem is not yet a final match
|
|
temp.push( (matcherIn[i] = elem) );
|
|
}
|
|
}
|
|
postFinder( null, (matcherOut = []), temp, xml );
|
|
}
|
|
|
|
// Move matched elements from seed to results to keep them synchronized
|
|
i = matcherOut.length;
|
|
while ( i-- ) {
|
|
if ( (elem = matcherOut[i]) &&
|
|
(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
|
|
|
|
seed[temp] = !(results[temp] = elem);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add elements to results, through postFinder if defined
|
|
} else {
|
|
matcherOut = condense(
|
|
matcherOut === results ?
|
|
matcherOut.splice( preexisting, matcherOut.length ) :
|
|
matcherOut
|
|
);
|
|
if ( postFinder ) {
|
|
postFinder( null, results, matcherOut, xml );
|
|
} else {
|
|
push.apply( results, matcherOut );
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function matcherFromTokens( tokens ) {
|
|
var checkContext, matcher, j,
|
|
len = tokens.length,
|
|
leadingRelative = Expr.relative[ tokens[0].type ],
|
|
implicitRelative = leadingRelative || Expr.relative[" "],
|
|
i = leadingRelative ? 1 : 0,
|
|
|
|
// The foundational matcher ensures that elements are reachable from top-level context(s)
|
|
matchContext = addCombinator( function( elem ) {
|
|
return elem === checkContext;
|
|
}, implicitRelative, true ),
|
|
matchAnyContext = addCombinator( function( elem ) {
|
|
return indexOf( checkContext, elem ) > -1;
|
|
}, implicitRelative, true ),
|
|
matchers = [ function( elem, context, xml ) {
|
|
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
|
|
(checkContext = context).nodeType ?
|
|
matchContext( elem, context, xml ) :
|
|
matchAnyContext( elem, context, xml ) );
|
|
// Avoid hanging onto element (issue #299)
|
|
checkContext = null;
|
|
return ret;
|
|
} ];
|
|
|
|
for ( ; i < len; i++ ) {
|
|
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
|
|
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
|
|
} else {
|
|
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
|
|
|
|
// Return special upon seeing a positional matcher
|
|
if ( matcher[ expando ] ) {
|
|
// Find the next relative operator (if any) for proper handling
|
|
j = ++i;
|
|
for ( ; j < len; j++ ) {
|
|
if ( Expr.relative[ tokens[j].type ] ) {
|
|
break;
|
|
}
|
|
}
|
|
return setMatcher(
|
|
i > 1 && elementMatcher( matchers ),
|
|
i > 1 && toSelector(
|
|
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
|
|
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
|
|
).replace( rtrim, "$1" ),
|
|
matcher,
|
|
i < j && matcherFromTokens( tokens.slice( i, j ) ),
|
|
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
|
|
j < len && toSelector( tokens )
|
|
);
|
|
}
|
|
matchers.push( matcher );
|
|
}
|
|
}
|
|
|
|
return elementMatcher( matchers );
|
|
}
|
|
|
|
function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
|
|
var bySet = setMatchers.length > 0,
|
|
byElement = elementMatchers.length > 0,
|
|
superMatcher = function( seed, context, xml, results, outermost ) {
|
|
var elem, j, matcher,
|
|
matchedCount = 0,
|
|
i = "0",
|
|
unmatched = seed && [],
|
|
setMatched = [],
|
|
contextBackup = outermostContext,
|
|
// We must always have either seed elements or outermost context
|
|
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
|
|
// Use integer dirruns iff this is the outermost matcher
|
|
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
|
|
len = elems.length;
|
|
|
|
if ( outermost ) {
|
|
outermostContext = context === document || context || outermost;
|
|
}
|
|
|
|
// Add elements passing elementMatchers directly to results
|
|
// Support: IE<9, Safari
|
|
// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
|
|
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
|
|
if ( byElement && elem ) {
|
|
j = 0;
|
|
if ( !context && elem.ownerDocument !== document ) {
|
|
setDocument( elem );
|
|
xml = !documentIsHTML;
|
|
}
|
|
while ( (matcher = elementMatchers[j++]) ) {
|
|
if ( matcher( elem, context || document, xml) ) {
|
|
results.push( elem );
|
|
break;
|
|
}
|
|
}
|
|
if ( outermost ) {
|
|
dirruns = dirrunsUnique;
|
|
}
|
|
}
|
|
|
|
// Track unmatched elements for set filters
|
|
if ( bySet ) {
|
|
// They will have gone through all possible matchers
|
|
if ( (elem = !matcher && elem) ) {
|
|
matchedCount--;
|
|
}
|
|
|
|
// Lengthen the array for every element, matched or not
|
|
if ( seed ) {
|
|
unmatched.push( elem );
|
|
}
|
|
}
|
|
}
|
|
|
|
// `i` is now the count of elements visited above, and adding it to `matchedCount`
|
|
// makes the latter nonnegative.
|
|
matchedCount += i;
|
|
|
|
// Apply set filters to unmatched elements
|
|
// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
|
|
// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
|
|
// no element matchers and no seed.
|
|
// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
|
|
// case, which will result in a "00" `matchedCount` that differs from `i` but is also
|
|
// numerically zero.
|
|
if ( bySet && i !== matchedCount ) {
|
|
j = 0;
|
|
while ( (matcher = setMatchers[j++]) ) {
|
|
matcher( unmatched, setMatched, context, xml );
|
|
}
|
|
|
|
if ( seed ) {
|
|
// Reintegrate element matches to eliminate the need for sorting
|
|
if ( matchedCount > 0 ) {
|
|
while ( i-- ) {
|
|
if ( !(unmatched[i] || setMatched[i]) ) {
|
|
setMatched[i] = pop.call( results );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Discard index placeholder values to get only actual matches
|
|
setMatched = condense( setMatched );
|
|
}
|
|
|
|
// Add matches to results
|
|
push.apply( results, setMatched );
|
|
|
|
// Seedless set matches succeeding multiple successful matchers stipulate sorting
|
|
if ( outermost && !seed && setMatched.length > 0 &&
|
|
( matchedCount + setMatchers.length ) > 1 ) {
|
|
|
|
Sizzle.uniqueSort( results );
|
|
}
|
|
}
|
|
|
|
// Override manipulation of globals by nested matchers
|
|
if ( outermost ) {
|
|
dirruns = dirrunsUnique;
|
|
outermostContext = contextBackup;
|
|
}
|
|
|
|
return unmatched;
|
|
};
|
|
|
|
return bySet ?
|
|
markFunction( superMatcher ) :
|
|
superMatcher;
|
|
}
|
|
|
|
compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
|
|
var i,
|
|
setMatchers = [],
|
|
elementMatchers = [],
|
|
cached = compilerCache[ selector + " " ];
|
|
|
|
if ( !cached ) {
|
|
// Generate a function of recursive functions that can be used to check each element
|
|
if ( !match ) {
|
|
match = tokenize( selector );
|
|
}
|
|
i = match.length;
|
|
while ( i-- ) {
|
|
cached = matcherFromTokens( match[i] );
|
|
if ( cached[ expando ] ) {
|
|
setMatchers.push( cached );
|
|
} else {
|
|
elementMatchers.push( cached );
|
|
}
|
|
}
|
|
|
|
// Cache the compiled function
|
|
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
|
|
|
|
// Save selector and tokenization
|
|
cached.selector = selector;
|
|
}
|
|
return cached;
|
|
};
|
|
|
|
/**
|
|
* A low-level selection function that works with Sizzle's compiled
|
|
* selector functions
|
|
* @param {String|Function} selector A selector or a pre-compiled
|
|
* selector function built with Sizzle.compile
|
|
* @param {Element} context
|
|
* @param {Array} [results]
|
|
* @param {Array} [seed] A set of elements to match against
|
|
*/
|
|
select = Sizzle.select = function( selector, context, results, seed ) {
|
|
var i, tokens, token, type, find,
|
|
compiled = typeof selector === "function" && selector,
|
|
match = !seed && tokenize( (selector = compiled.selector || selector) );
|
|
|
|
results = results || [];
|
|
|
|
// Try to minimize operations if there is only one selector in the list and no seed
|
|
// (the latter of which guarantees us context)
|
|
if ( match.length === 1 ) {
|
|
|
|
// Reduce context if the leading compound selector is an ID
|
|
tokens = match[0] = match[0].slice( 0 );
|
|
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
|
|
context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) {
|
|
|
|
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
|
|
if ( !context ) {
|
|
return results;
|
|
|
|
// Precompiled matchers will still verify ancestry, so step up a level
|
|
} else if ( compiled ) {
|
|
context = context.parentNode;
|
|
}
|
|
|
|
selector = selector.slice( tokens.shift().value.length );
|
|
}
|
|
|
|
// Fetch a seed set for right-to-left matching
|
|
i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
|
|
while ( i-- ) {
|
|
token = tokens[i];
|
|
|
|
// Abort if we hit a combinator
|
|
if ( Expr.relative[ (type = token.type) ] ) {
|
|
break;
|
|
}
|
|
if ( (find = Expr.find[ type ]) ) {
|
|
// Search, expanding context for leading sibling combinators
|
|
if ( (seed = find(
|
|
token.matches[0].replace( runescape, funescape ),
|
|
rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
|
|
)) ) {
|
|
|
|
// If seed is empty or no tokens remain, we can return early
|
|
tokens.splice( i, 1 );
|
|
selector = seed.length && toSelector( tokens );
|
|
if ( !selector ) {
|
|
push.apply( results, seed );
|
|
return results;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compile and execute a filtering function if one is not provided
|
|
// Provide `match` to avoid retokenization if we modified the selector above
|
|
( compiled || compile( selector, match ) )(
|
|
seed,
|
|
context,
|
|
!documentIsHTML,
|
|
results,
|
|
!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
|
|
);
|
|
return results;
|
|
};
|
|
|
|
// One-time assignments
|
|
|
|
// Sort stability
|
|
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
|
|
|
|
// Support: Chrome 14-35+
|
|
// Always assume duplicates if they aren't passed to the comparison function
|
|
support.detectDuplicates = !!hasDuplicate;
|
|
|
|
// Initialize against the default document
|
|
setDocument();
|
|
|
|
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
|
|
// Detached nodes confoundingly follow *each other*
|
|
support.sortDetached = assert(function( el ) {
|
|
// Should return 1, but returns 4 (following)
|
|
return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
|
|
});
|
|
|
|
// Support: IE<8
|
|
// Prevent attribute/property "interpolation"
|
|
// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
|
|
if ( !assert(function( el ) {
|
|
el.innerHTML = "<a href='#'></a>";
|
|
return el.firstChild.getAttribute("href") === "#" ;
|
|
}) ) {
|
|
addHandle( "type|href|height|width", function( elem, name, isXML ) {
|
|
if ( !isXML ) {
|
|
return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
|
|
}
|
|
});
|
|
}
|
|
|
|
// Support: IE<9
|
|
// Use defaultValue in place of getAttribute("value")
|
|
if ( !support.attributes || !assert(function( el ) {
|
|
el.innerHTML = "<input/>";
|
|
el.firstChild.setAttribute( "value", "" );
|
|
return el.firstChild.getAttribute( "value" ) === "";
|
|
}) ) {
|
|
addHandle( "value", function( elem, name, isXML ) {
|
|
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
|
|
return elem.defaultValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Support: IE<9
|
|
// Use getAttributeNode to fetch booleans when getAttribute lies
|
|
if ( !assert(function( el ) {
|
|
return el.getAttribute("disabled") == null;
|
|
}) ) {
|
|
addHandle( booleans, function( elem, name, isXML ) {
|
|
var val;
|
|
if ( !isXML ) {
|
|
return elem[ name ] === true ? name.toLowerCase() :
|
|
(val = elem.getAttributeNode( name )) && val.specified ?
|
|
val.value :
|
|
null;
|
|
}
|
|
});
|
|
}
|
|
|
|
return Sizzle;
|
|
|
|
})( window );
|
|
|
|
|
|
|
|
jQuery.find = Sizzle;
|
|
jQuery.expr = Sizzle.selectors;
|
|
|
|
// Deprecated
|
|
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
|
|
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
|
|
jQuery.text = Sizzle.getText;
|
|
jQuery.isXMLDoc = Sizzle.isXML;
|
|
jQuery.contains = Sizzle.contains;
|
|
jQuery.escapeSelector = Sizzle.escape;
|
|
|
|
|
|
|
|
|
|
var dir = function( elem, dir, until ) {
|
|
var matched = [],
|
|
truncate = until !== undefined;
|
|
|
|
while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
if ( truncate && jQuery( elem ).is( until ) ) {
|
|
break;
|
|
}
|
|
matched.push( elem );
|
|
}
|
|
}
|
|
return matched;
|
|
};
|
|
|
|
|
|
var siblings = function( n, elem ) {
|
|
var matched = [];
|
|
|
|
for ( ; n; n = n.nextSibling ) {
|
|
if ( n.nodeType === 1 && n !== elem ) {
|
|
matched.push( n );
|
|
}
|
|
}
|
|
|
|
return matched;
|
|
};
|
|
|
|
|
|
var rneedsContext = jQuery.expr.match.needsContext;
|
|
|
|
|
|
|
|
function nodeName( elem, name ) {
|
|
|
|
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
|
|
|
|
};
|
|
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
|
|
|
|
|
|
|
|
// Implement the identical functionality for filter and not
|
|
function winnow( elements, qualifier, not ) {
|
|
if ( isFunction( qualifier ) ) {
|
|
return jQuery.grep( elements, function( elem, i ) {
|
|
return !!qualifier.call( elem, i, elem ) !== not;
|
|
} );
|
|
}
|
|
|
|
// Single element
|
|
if ( qualifier.nodeType ) {
|
|
return jQuery.grep( elements, function( elem ) {
|
|
return ( elem === qualifier ) !== not;
|
|
} );
|
|
}
|
|
|
|
// Arraylike of elements (jQuery, arguments, Array)
|
|
if ( typeof qualifier !== "string" ) {
|
|
return jQuery.grep( elements, function( elem ) {
|
|
return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
|
|
} );
|
|
}
|
|
|
|
// Filtered directly for both simple and complex selectors
|
|
return jQuery.filter( qualifier, elements, not );
|
|
}
|
|
|
|
jQuery.filter = function( expr, elems, not ) {
|
|
var elem = elems[ 0 ];
|
|
|
|
if ( not ) {
|
|
expr = ":not(" + expr + ")";
|
|
}
|
|
|
|
if ( elems.length === 1 && elem.nodeType === 1 ) {
|
|
return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
|
|
}
|
|
|
|
return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
|
|
return elem.nodeType === 1;
|
|
} ) );
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
find: function( selector ) {
|
|
var i, ret,
|
|
len = this.length,
|
|
self = this;
|
|
|
|
if ( typeof selector !== "string" ) {
|
|
return this.pushStack( jQuery( selector ).filter( function() {
|
|
for ( i = 0; i < len; i++ ) {
|
|
if ( jQuery.contains( self[ i ], this ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
} ) );
|
|
}
|
|
|
|
ret = this.pushStack( [] );
|
|
|
|
for ( i = 0; i < len; i++ ) {
|
|
jQuery.find( selector, self[ i ], ret );
|
|
}
|
|
|
|
return len > 1 ? jQuery.uniqueSort( ret ) : ret;
|
|
},
|
|
filter: function( selector ) {
|
|
return this.pushStack( winnow( this, selector || [], false ) );
|
|
},
|
|
not: function( selector ) {
|
|
return this.pushStack( winnow( this, selector || [], true ) );
|
|
},
|
|
is: function( selector ) {
|
|
return !!winnow(
|
|
this,
|
|
|
|
// If this is a positional/relative selector, check membership in the returned set
|
|
// so $("p:first").is("p:last") won't return true for a doc with two "p".
|
|
typeof selector === "string" && rneedsContext.test( selector ) ?
|
|
jQuery( selector ) :
|
|
selector || [],
|
|
false
|
|
).length;
|
|
}
|
|
} );
|
|
|
|
|
|
// Initialize a jQuery object
|
|
|
|
|
|
// A central reference to the root jQuery(document)
|
|
var rootjQuery,
|
|
|
|
// A simple way to check for HTML strings
|
|
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
|
|
// Strict HTML recognition (#11290: must start with <)
|
|
// Shortcut simple #id case for speed
|
|
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
|
|
|
init = jQuery.fn.init = function( selector, context, root ) {
|
|
var match, elem;
|
|
|
|
// HANDLE: $(""), $(null), $(undefined), $(false)
|
|
if ( !selector ) {
|
|
return this;
|
|
}
|
|
|
|
// Method init() accepts an alternate rootjQuery
|
|
// so migrate can support jQuery.sub (gh-2101)
|
|
root = root || rootjQuery;
|
|
|
|
// Handle HTML strings
|
|
if ( typeof selector === "string" ) {
|
|
if ( selector[ 0 ] === "<" &&
|
|
selector[ selector.length - 1 ] === ">" &&
|
|
selector.length >= 3 ) {
|
|
|
|
// Assume that strings that start and end with <> are HTML and skip the regex check
|
|
match = [ null, selector, null ];
|
|
|
|
} else {
|
|
match = rquickExpr.exec( selector );
|
|
}
|
|
|
|
// Match html or make sure no context is specified for #id
|
|
if ( match && ( match[ 1 ] || !context ) ) {
|
|
|
|
// HANDLE: $(html) -> $(array)
|
|
if ( match[ 1 ] ) {
|
|
context = context instanceof jQuery ? context[ 0 ] : context;
|
|
|
|
// Option to run scripts is true for back-compat
|
|
// Intentionally let the error be thrown if parseHTML is not present
|
|
jQuery.merge( this, jQuery.parseHTML(
|
|
match[ 1 ],
|
|
context && context.nodeType ? context.ownerDocument || context : document,
|
|
true
|
|
) );
|
|
|
|
// HANDLE: $(html, props)
|
|
if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
|
|
for ( match in context ) {
|
|
|
|
// Properties of context are called as methods if possible
|
|
if ( isFunction( this[ match ] ) ) {
|
|
this[ match ]( context[ match ] );
|
|
|
|
// ...and otherwise set as attributes
|
|
} else {
|
|
this.attr( match, context[ match ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
|
|
// HANDLE: $(#id)
|
|
} else {
|
|
elem = document.getElementById( match[ 2 ] );
|
|
|
|
if ( elem ) {
|
|
|
|
// Inject the element directly into the jQuery object
|
|
this[ 0 ] = elem;
|
|
this.length = 1;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// HANDLE: $(expr, $(...))
|
|
} else if ( !context || context.jquery ) {
|
|
return ( context || root ).find( selector );
|
|
|
|
// HANDLE: $(expr, context)
|
|
// (which is just equivalent to: $(context).find(expr)
|
|
} else {
|
|
return this.constructor( context ).find( selector );
|
|
}
|
|
|
|
// HANDLE: $(DOMElement)
|
|
} else if ( selector.nodeType ) {
|
|
this[ 0 ] = selector;
|
|
this.length = 1;
|
|
return this;
|
|
|
|
// HANDLE: $(function)
|
|
// Shortcut for document ready
|
|
} else if ( isFunction( selector ) ) {
|
|
return root.ready !== undefined ?
|
|
root.ready( selector ) :
|
|
|
|
// Execute immediately if ready is not present
|
|
selector( jQuery );
|
|
}
|
|
|
|
return jQuery.makeArray( selector, this );
|
|
};
|
|
|
|
// Give the init function the jQuery prototype for later instantiation
|
|
init.prototype = jQuery.fn;
|
|
|
|
// Initialize central reference
|
|
rootjQuery = jQuery( document );
|
|
|
|
|
|
var rparentsprev = /^(?:parents|prev(?:Until|All))/,
|
|
|
|
// Methods guaranteed to produce a unique set when starting from a unique set
|
|
guaranteedUnique = {
|
|
children: true,
|
|
contents: true,
|
|
next: true,
|
|
prev: true
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
has: function( target ) {
|
|
var targets = jQuery( target, this ),
|
|
l = targets.length;
|
|
|
|
return this.filter( function() {
|
|
var i = 0;
|
|
for ( ; i < l; i++ ) {
|
|
if ( jQuery.contains( this, targets[ i ] ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
} );
|
|
},
|
|
|
|
closest: function( selectors, context ) {
|
|
var cur,
|
|
i = 0,
|
|
l = this.length,
|
|
matched = [],
|
|
targets = typeof selectors !== "string" && jQuery( selectors );
|
|
|
|
// Positional selectors never match, since there's no _selection_ context
|
|
if ( !rneedsContext.test( selectors ) ) {
|
|
for ( ; i < l; i++ ) {
|
|
for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
|
|
|
|
// Always skip document fragments
|
|
if ( cur.nodeType < 11 && ( targets ?
|
|
targets.index( cur ) > -1 :
|
|
|
|
// Don't pass non-elements to Sizzle
|
|
cur.nodeType === 1 &&
|
|
jQuery.find.matchesSelector( cur, selectors ) ) ) {
|
|
|
|
matched.push( cur );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
|
|
},
|
|
|
|
// Determine the position of an element within the set
|
|
index: function( elem ) {
|
|
|
|
// No argument, return index in parent
|
|
if ( !elem ) {
|
|
return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
|
|
}
|
|
|
|
// Index in selector
|
|
if ( typeof elem === "string" ) {
|
|
return indexOf.call( jQuery( elem ), this[ 0 ] );
|
|
}
|
|
|
|
// Locate the position of the desired element
|
|
return indexOf.call( this,
|
|
|
|
// If it receives a jQuery object, the first element is used
|
|
elem.jquery ? elem[ 0 ] : elem
|
|
);
|
|
},
|
|
|
|
add: function( selector, context ) {
|
|
return this.pushStack(
|
|
jQuery.uniqueSort(
|
|
jQuery.merge( this.get(), jQuery( selector, context ) )
|
|
)
|
|
);
|
|
},
|
|
|
|
addBack: function( selector ) {
|
|
return this.add( selector == null ?
|
|
this.prevObject : this.prevObject.filter( selector )
|
|
);
|
|
}
|
|
} );
|
|
|
|
function sibling( cur, dir ) {
|
|
while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
|
|
return cur;
|
|
}
|
|
|
|
jQuery.each( {
|
|
parent: function( elem ) {
|
|
var parent = elem.parentNode;
|
|
return parent && parent.nodeType !== 11 ? parent : null;
|
|
},
|
|
parents: function( elem ) {
|
|
return dir( elem, "parentNode" );
|
|
},
|
|
parentsUntil: function( elem, i, until ) {
|
|
return dir( elem, "parentNode", until );
|
|
},
|
|
next: function( elem ) {
|
|
return sibling( elem, "nextSibling" );
|
|
},
|
|
prev: function( elem ) {
|
|
return sibling( elem, "previousSibling" );
|
|
},
|
|
nextAll: function( elem ) {
|
|
return dir( elem, "nextSibling" );
|
|
},
|
|
prevAll: function( elem ) {
|
|
return dir( elem, "previousSibling" );
|
|
},
|
|
nextUntil: function( elem, i, until ) {
|
|
return dir( elem, "nextSibling", until );
|
|
},
|
|
prevUntil: function( elem, i, until ) {
|
|
return dir( elem, "previousSibling", until );
|
|
},
|
|
siblings: function( elem ) {
|
|
return siblings( ( elem.parentNode || {} ).firstChild, elem );
|
|
},
|
|
children: function( elem ) {
|
|
return siblings( elem.firstChild );
|
|
},
|
|
contents: function( elem ) {
|
|
if ( nodeName( elem, "iframe" ) ) {
|
|
return elem.contentDocument;
|
|
}
|
|
|
|
// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
|
|
// Treat the template element as a regular one in browsers that
|
|
// don't support it.
|
|
if ( nodeName( elem, "template" ) ) {
|
|
elem = elem.content || elem;
|
|
}
|
|
|
|
return jQuery.merge( [], elem.childNodes );
|
|
}
|
|
}, function( name, fn ) {
|
|
jQuery.fn[ name ] = function( until, selector ) {
|
|
var matched = jQuery.map( this, fn, until );
|
|
|
|
if ( name.slice( -5 ) !== "Until" ) {
|
|
selector = until;
|
|
}
|
|
|
|
if ( selector && typeof selector === "string" ) {
|
|
matched = jQuery.filter( selector, matched );
|
|
}
|
|
|
|
if ( this.length > 1 ) {
|
|
|
|
// Remove duplicates
|
|
if ( !guaranteedUnique[ name ] ) {
|
|
jQuery.uniqueSort( matched );
|
|
}
|
|
|
|
// Reverse order for parents* and prev-derivatives
|
|
if ( rparentsprev.test( name ) ) {
|
|
matched.reverse();
|
|
}
|
|
}
|
|
|
|
return this.pushStack( matched );
|
|
};
|
|
} );
|
|
var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
|
|
|
|
|
|
|
|
// Convert String-formatted options into Object-formatted ones
|
|
function createOptions( options ) {
|
|
var object = {};
|
|
jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
|
|
object[ flag ] = true;
|
|
} );
|
|
return object;
|
|
}
|
|
|
|
/*
|
|
* Create a callback list using the following parameters:
|
|
*
|
|
* options: an optional list of space-separated options that will change how
|
|
* the callback list behaves or a more traditional option object
|
|
*
|
|
* By default a callback list will act like an event callback list and can be
|
|
* "fired" multiple times.
|
|
*
|
|
* Possible options:
|
|
*
|
|
* once: will ensure the callback list can only be fired once (like a Deferred)
|
|
*
|
|
* memory: will keep track of previous values and will call any callback added
|
|
* after the list has been fired right away with the latest "memorized"
|
|
* values (like a Deferred)
|
|
*
|
|
* unique: will ensure a callback can only be added once (no duplicate in the list)
|
|
*
|
|
* stopOnFalse: interrupt callings when a callback returns false
|
|
*
|
|
*/
|
|
jQuery.Callbacks = function( options ) {
|
|
|
|
// Convert options from String-formatted to Object-formatted if needed
|
|
// (we check in cache first)
|
|
options = typeof options === "string" ?
|
|
createOptions( options ) :
|
|
jQuery.extend( {}, options );
|
|
|
|
var // Flag to know if list is currently firing
|
|
firing,
|
|
|
|
// Last fire value for non-forgettable lists
|
|
memory,
|
|
|
|
// Flag to know if list was already fired
|
|
fired,
|
|
|
|
// Flag to prevent firing
|
|
locked,
|
|
|
|
// Actual callback list
|
|
list = [],
|
|
|
|
// Queue of execution data for repeatable lists
|
|
queue = [],
|
|
|
|
// Index of currently firing callback (modified by add/remove as needed)
|
|
firingIndex = -1,
|
|
|
|
// Fire callbacks
|
|
fire = function() {
|
|
|
|
// Enforce single-firing
|
|
locked = locked || options.once;
|
|
|
|
// Execute callbacks for all pending executions,
|
|
// respecting firingIndex overrides and runtime changes
|
|
fired = firing = true;
|
|
for ( ; queue.length; firingIndex = -1 ) {
|
|
memory = queue.shift();
|
|
while ( ++firingIndex < list.length ) {
|
|
|
|
// Run callback and check for early termination
|
|
if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
|
|
options.stopOnFalse ) {
|
|
|
|
// Jump to end and forget the data so .add doesn't re-fire
|
|
firingIndex = list.length;
|
|
memory = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forget the data if we're done with it
|
|
if ( !options.memory ) {
|
|
memory = false;
|
|
}
|
|
|
|
firing = false;
|
|
|
|
// Clean up if we're done firing for good
|
|
if ( locked ) {
|
|
|
|
// Keep an empty list if we have data for future add calls
|
|
if ( memory ) {
|
|
list = [];
|
|
|
|
// Otherwise, this object is spent
|
|
} else {
|
|
list = "";
|
|
}
|
|
}
|
|
},
|
|
|
|
// Actual Callbacks object
|
|
self = {
|
|
|
|
// Add a callback or a collection of callbacks to the list
|
|
add: function() {
|
|
if ( list ) {
|
|
|
|
// If we have memory from a past run, we should fire after adding
|
|
if ( memory && !firing ) {
|
|
firingIndex = list.length - 1;
|
|
queue.push( memory );
|
|
}
|
|
|
|
( function add( args ) {
|
|
jQuery.each( args, function( _, arg ) {
|
|
if ( isFunction( arg ) ) {
|
|
if ( !options.unique || !self.has( arg ) ) {
|
|
list.push( arg );
|
|
}
|
|
} else if ( arg && arg.length && toType( arg ) !== "string" ) {
|
|
|
|
// Inspect recursively
|
|
add( arg );
|
|
}
|
|
} );
|
|
} )( arguments );
|
|
|
|
if ( memory && !firing ) {
|
|
fire();
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Remove a callback from the list
|
|
remove: function() {
|
|
jQuery.each( arguments, function( _, arg ) {
|
|
var index;
|
|
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
|
|
list.splice( index, 1 );
|
|
|
|
// Handle firing indexes
|
|
if ( index <= firingIndex ) {
|
|
firingIndex--;
|
|
}
|
|
}
|
|
} );
|
|
return this;
|
|
},
|
|
|
|
// Check if a given callback is in the list.
|
|
// If no argument is given, return whether or not list has callbacks attached.
|
|
has: function( fn ) {
|
|
return fn ?
|
|
jQuery.inArray( fn, list ) > -1 :
|
|
list.length > 0;
|
|
},
|
|
|
|
// Remove all callbacks from the list
|
|
empty: function() {
|
|
if ( list ) {
|
|
list = [];
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Disable .fire and .add
|
|
// Abort any current/pending executions
|
|
// Clear all callbacks and values
|
|
disable: function() {
|
|
locked = queue = [];
|
|
list = memory = "";
|
|
return this;
|
|
},
|
|
disabled: function() {
|
|
return !list;
|
|
},
|
|
|
|
// Disable .fire
|
|
// Also disable .add unless we have memory (since it would have no effect)
|
|
// Abort any pending executions
|
|
lock: function() {
|
|
locked = queue = [];
|
|
if ( !memory && !firing ) {
|
|
list = memory = "";
|
|
}
|
|
return this;
|
|
},
|
|
locked: function() {
|
|
return !!locked;
|
|
},
|
|
|
|
// Call all callbacks with the given context and arguments
|
|
fireWith: function( context, args ) {
|
|
if ( !locked ) {
|
|
args = args || [];
|
|
args = [ context, args.slice ? args.slice() : args ];
|
|
queue.push( args );
|
|
if ( !firing ) {
|
|
fire();
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Call all the callbacks with the given arguments
|
|
fire: function() {
|
|
self.fireWith( this, arguments );
|
|
return this;
|
|
},
|
|
|
|
// To know if the callbacks have already been called at least once
|
|
fired: function() {
|
|
return !!fired;
|
|
}
|
|
};
|
|
|
|
return self;
|
|
};
|
|
|
|
|
|
function Identity( v ) {
|
|
return v;
|
|
}
|
|
function Thrower( ex ) {
|
|
throw ex;
|
|
}
|
|
|
|
function adoptValue( value, resolve, reject, noValue ) {
|
|
var method;
|
|
|
|
try {
|
|
|
|
// Check for promise aspect first to privilege synchronous behavior
|
|
if ( value && isFunction( ( method = value.promise ) ) ) {
|
|
method.call( value ).done( resolve ).fail( reject );
|
|
|
|
// Other thenables
|
|
} else if ( value && isFunction( ( method = value.then ) ) ) {
|
|
method.call( value, resolve, reject );
|
|
|
|
// Other non-thenables
|
|
} else {
|
|
|
|
// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
|
|
// * false: [ value ].slice( 0 ) => resolve( value )
|
|
// * true: [ value ].slice( 1 ) => resolve()
|
|
resolve.apply( undefined, [ value ].slice( noValue ) );
|
|
}
|
|
|
|
// For Promises/A+, convert exceptions into rejections
|
|
// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
|
|
// Deferred#then to conditionally suppress rejection.
|
|
} catch ( value ) {
|
|
|
|
// Support: Android 4.0 only
|
|
// Strict mode functions invoked without .call/.apply get global-object context
|
|
reject.apply( undefined, [ value ] );
|
|
}
|
|
}
|
|
|
|
jQuery.extend( {
|
|
|
|
Deferred: function( func ) {
|
|
var tuples = [
|
|
|
|
// action, add listener, callbacks,
|
|
// ... .then handlers, argument index, [final state]
|
|
[ "notify", "progress", jQuery.Callbacks( "memory" ),
|
|
jQuery.Callbacks( "memory" ), 2 ],
|
|
[ "resolve", "done", jQuery.Callbacks( "once memory" ),
|
|
jQuery.Callbacks( "once memory" ), 0, "resolved" ],
|
|
[ "reject", "fail", jQuery.Callbacks( "once memory" ),
|
|
jQuery.Callbacks( "once memory" ), 1, "rejected" ]
|
|
],
|
|
state = "pending",
|
|
promise = {
|
|
state: function() {
|
|
return state;
|
|
},
|
|
always: function() {
|
|
deferred.done( arguments ).fail( arguments );
|
|
return this;
|
|
},
|
|
"catch": function( fn ) {
|
|
return promise.then( null, fn );
|
|
},
|
|
|
|
// Keep pipe for back-compat
|
|
pipe: function( /* fnDone, fnFail, fnProgress */ ) {
|
|
var fns = arguments;
|
|
|
|
return jQuery.Deferred( function( newDefer ) {
|
|
jQuery.each( tuples, function( i, tuple ) {
|
|
|
|
// Map tuples (progress, done, fail) to arguments (done, fail, progress)
|
|
var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
|
|
|
|
// deferred.progress(function() { bind to newDefer or newDefer.notify })
|
|
// deferred.done(function() { bind to newDefer or newDefer.resolve })
|
|
// deferred.fail(function() { bind to newDefer or newDefer.reject })
|
|
deferred[ tuple[ 1 ] ]( function() {
|
|
var returned = fn && fn.apply( this, arguments );
|
|
if ( returned && isFunction( returned.promise ) ) {
|
|
returned.promise()
|
|
.progress( newDefer.notify )
|
|
.done( newDefer.resolve )
|
|
.fail( newDefer.reject );
|
|
} else {
|
|
newDefer[ tuple[ 0 ] + "With" ](
|
|
this,
|
|
fn ? [ returned ] : arguments
|
|
);
|
|
}
|
|
} );
|
|
} );
|
|
fns = null;
|
|
} ).promise();
|
|
},
|
|
then: function( onFulfilled, onRejected, onProgress ) {
|
|
var maxDepth = 0;
|
|
function resolve( depth, deferred, handler, special ) {
|
|
return function() {
|
|
var that = this,
|
|
args = arguments,
|
|
mightThrow = function() {
|
|
var returned, then;
|
|
|
|
// Support: Promises/A+ section 2.3.3.3.3
|
|
// https://promisesaplus.com/#point-59
|
|
// Ignore double-resolution attempts
|
|
if ( depth < maxDepth ) {
|
|
return;
|
|
}
|
|
|
|
returned = handler.apply( that, args );
|
|
|
|
// Support: Promises/A+ section 2.3.1
|
|
// https://promisesaplus.com/#point-48
|
|
if ( returned === deferred.promise() ) {
|
|
throw new TypeError( "Thenable self-resolution" );
|
|
}
|
|
|
|
// Support: Promises/A+ sections 2.3.3.1, 3.5
|
|
// https://promisesaplus.com/#point-54
|
|
// https://promisesaplus.com/#point-75
|
|
// Retrieve `then` only once
|
|
then = returned &&
|
|
|
|
// Support: Promises/A+ section 2.3.4
|
|
// https://promisesaplus.com/#point-64
|
|
// Only check objects and functions for thenability
|
|
( typeof returned === "object" ||
|
|
typeof returned === "function" ) &&
|
|
returned.then;
|
|
|
|
// Handle a returned thenable
|
|
if ( isFunction( then ) ) {
|
|
|
|
// Special processors (notify) just wait for resolution
|
|
if ( special ) {
|
|
then.call(
|
|
returned,
|
|
resolve( maxDepth, deferred, Identity, special ),
|
|
resolve( maxDepth, deferred, Thrower, special )
|
|
);
|
|
|
|
// Normal processors (resolve) also hook into progress
|
|
} else {
|
|
|
|
// ...and disregard older resolution values
|
|
maxDepth++;
|
|
|
|
then.call(
|
|
returned,
|
|
resolve( maxDepth, deferred, Identity, special ),
|
|
resolve( maxDepth, deferred, Thrower, special ),
|
|
resolve( maxDepth, deferred, Identity,
|
|
deferred.notifyWith )
|
|
);
|
|
}
|
|
|
|
// Handle all other returned values
|
|
} else {
|
|
|
|
// Only substitute handlers pass on context
|
|
// and multiple values (non-spec behavior)
|
|
if ( handler !== Identity ) {
|
|
that = undefined;
|
|
args = [ returned ];
|
|
}
|
|
|
|
// Process the value(s)
|
|
// Default process is resolve
|
|
( special || deferred.resolveWith )( that, args );
|
|
}
|
|
},
|
|
|
|
// Only normal processors (resolve) catch and reject exceptions
|
|
process = special ?
|
|
mightThrow :
|
|
function() {
|
|
try {
|
|
mightThrow();
|
|
} catch ( e ) {
|
|
|
|
if ( jQuery.Deferred.exceptionHook ) {
|
|
jQuery.Deferred.exceptionHook( e,
|
|
process.stackTrace );
|
|
}
|
|
|
|
// Support: Promises/A+ section 2.3.3.3.4.1
|
|
// https://promisesaplus.com/#point-61
|
|
// Ignore post-resolution exceptions
|
|
if ( depth + 1 >= maxDepth ) {
|
|
|
|
// Only substitute handlers pass on context
|
|
// and multiple values (non-spec behavior)
|
|
if ( handler !== Thrower ) {
|
|
that = undefined;
|
|
args = [ e ];
|
|
}
|
|
|
|
deferred.rejectWith( that, args );
|
|
}
|
|
}
|
|
};
|
|
|
|
// Support: Promises/A+ section 2.3.3.3.1
|
|
// https://promisesaplus.com/#point-57
|
|
// Re-resolve promises immediately to dodge false rejection from
|
|
// subsequent errors
|
|
if ( depth ) {
|
|
process();
|
|
} else {
|
|
|
|
// Call an optional hook to record the stack, in case of exception
|
|
// since it's otherwise lost when execution goes async
|
|
if ( jQuery.Deferred.getStackHook ) {
|
|
process.stackTrace = jQuery.Deferred.getStackHook();
|
|
}
|
|
window.setTimeout( process );
|
|
}
|
|
};
|
|
}
|
|
|
|
return jQuery.Deferred( function( newDefer ) {
|
|
|
|
// progress_handlers.add( ... )
|
|
tuples[ 0 ][ 3 ].add(
|
|
resolve(
|
|
0,
|
|
newDefer,
|
|
isFunction( onProgress ) ?
|
|
onProgress :
|
|
Identity,
|
|
newDefer.notifyWith
|
|
)
|
|
);
|
|
|
|
// fulfilled_handlers.add( ... )
|
|
tuples[ 1 ][ 3 ].add(
|
|
resolve(
|
|
0,
|
|
newDefer,
|
|
isFunction( onFulfilled ) ?
|
|
onFulfilled :
|
|
Identity
|
|
)
|
|
);
|
|
|
|
// rejected_handlers.add( ... )
|
|
tuples[ 2 ][ 3 ].add(
|
|
resolve(
|
|
0,
|
|
newDefer,
|
|
isFunction( onRejected ) ?
|
|
onRejected :
|
|
Thrower
|
|
)
|
|
);
|
|
} ).promise();
|
|
},
|
|
|
|
// Get a promise for this deferred
|
|
// If obj is provided, the promise aspect is added to the object
|
|
promise: function( obj ) {
|
|
return obj != null ? jQuery.extend( obj, promise ) : promise;
|
|
}
|
|
},
|
|
deferred = {};
|
|
|
|
// Add list-specific methods
|
|
jQuery.each( tuples, function( i, tuple ) {
|
|
var list = tuple[ 2 ],
|
|
stateString = tuple[ 5 ];
|
|
|
|
// promise.progress = list.add
|
|
// promise.done = list.add
|
|
// promise.fail = list.add
|
|
promise[ tuple[ 1 ] ] = list.add;
|
|
|
|
// Handle state
|
|
if ( stateString ) {
|
|
list.add(
|
|
function() {
|
|
|
|
// state = "resolved" (i.e., fulfilled)
|
|
// state = "rejected"
|
|
state = stateString;
|
|
},
|
|
|
|
// rejected_callbacks.disable
|
|
// fulfilled_callbacks.disable
|
|
tuples[ 3 - i ][ 2 ].disable,
|
|
|
|
// rejected_handlers.disable
|
|
// fulfilled_handlers.disable
|
|
tuples[ 3 - i ][ 3 ].disable,
|
|
|
|
// progress_callbacks.lock
|
|
tuples[ 0 ][ 2 ].lock,
|
|
|
|
// progress_handlers.lock
|
|
tuples[ 0 ][ 3 ].lock
|
|
);
|
|
}
|
|
|
|
// progress_handlers.fire
|
|
// fulfilled_handlers.fire
|
|
// rejected_handlers.fire
|
|
list.add( tuple[ 3 ].fire );
|
|
|
|
// deferred.notify = function() { deferred.notifyWith(...) }
|
|
// deferred.resolve = function() { deferred.resolveWith(...) }
|
|
// deferred.reject = function() { deferred.rejectWith(...) }
|
|
deferred[ tuple[ 0 ] ] = function() {
|
|
deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
|
|
return this;
|
|
};
|
|
|
|
// deferred.notifyWith = list.fireWith
|
|
// deferred.resolveWith = list.fireWith
|
|
// deferred.rejectWith = list.fireWith
|
|
deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
|
|
} );
|
|
|
|
// Make the deferred a promise
|
|
promise.promise( deferred );
|
|
|
|
// Call given func if any
|
|
if ( func ) {
|
|
func.call( deferred, deferred );
|
|
}
|
|
|
|
// All done!
|
|
return deferred;
|
|
},
|
|
|
|
// Deferred helper
|
|
when: function( singleValue ) {
|
|
var
|
|
|
|
// count of uncompleted subordinates
|
|
remaining = arguments.length,
|
|
|
|
// count of unprocessed arguments
|
|
i = remaining,
|
|
|
|
// subordinate fulfillment data
|
|
resolveContexts = Array( i ),
|
|
resolveValues = slice.call( arguments ),
|
|
|
|
// the master Deferred
|
|
master = jQuery.Deferred(),
|
|
|
|
// subordinate callback factory
|
|
updateFunc = function( i ) {
|
|
return function( value ) {
|
|
resolveContexts[ i ] = this;
|
|
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
|
|
if ( !( --remaining ) ) {
|
|
master.resolveWith( resolveContexts, resolveValues );
|
|
}
|
|
};
|
|
};
|
|
|
|
// Single- and empty arguments are adopted like Promise.resolve
|
|
if ( remaining <= 1 ) {
|
|
adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
|
|
!remaining );
|
|
|
|
// Use .then() to unwrap secondary thenables (cf. gh-3000)
|
|
if ( master.state() === "pending" ||
|
|
isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
|
|
|
|
return master.then();
|
|
}
|
|
}
|
|
|
|
// Multiple arguments are aggregated like Promise.all array elements
|
|
while ( i-- ) {
|
|
adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
|
|
}
|
|
|
|
return master.promise();
|
|
}
|
|
} );
|
|
|
|
|
|
// These usually indicate a programmer mistake during development,
|
|
// warn about them ASAP rather than swallowing them by default.
|
|
var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
|
|
|
|
jQuery.Deferred.exceptionHook = function( error, stack ) {
|
|
|
|
// Support: IE 8 - 9 only
|
|
// Console exists when dev tools are open, which can happen at any time
|
|
if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
|
|
window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
jQuery.readyException = function( error ) {
|
|
window.setTimeout( function() {
|
|
throw error;
|
|
} );
|
|
};
|
|
|
|
|
|
|
|
|
|
// The deferred used on DOM ready
|
|
var readyList = jQuery.Deferred();
|
|
|
|
jQuery.fn.ready = function( fn ) {
|
|
|
|
readyList
|
|
.then( fn )
|
|
|
|
// Wrap jQuery.readyException in a function so that the lookup
|
|
// happens at the time of error handling instead of callback
|
|
// registration.
|
|
.catch( function( error ) {
|
|
jQuery.readyException( error );
|
|
} );
|
|
|
|
return this;
|
|
};
|
|
|
|
jQuery.extend( {
|
|
|
|
// Is the DOM ready to be used? Set to true once it occurs.
|
|
isReady: false,
|
|
|
|
// A counter to track how many items to wait for before
|
|
// the ready event fires. See #6781
|
|
readyWait: 1,
|
|
|
|
// Handle when the DOM is ready
|
|
ready: function( wait ) {
|
|
|
|
// Abort if there are pending holds or we're already ready
|
|
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
|
|
return;
|
|
}
|
|
|
|
// Remember that the DOM is ready
|
|
jQuery.isReady = true;
|
|
|
|
// If a normal DOM Ready event fired, decrement, and wait if need be
|
|
if ( wait !== true && --jQuery.readyWait > 0 ) {
|
|
return;
|
|
}
|
|
|
|
// If there are functions bound, to execute
|
|
readyList.resolveWith( document, [ jQuery ] );
|
|
}
|
|
} );
|
|
|
|
jQuery.ready.then = readyList.then;
|
|
|
|
// The ready event handler and self cleanup method
|
|
function completed() {
|
|
document.removeEventListener( "DOMContentLoaded", completed );
|
|
window.removeEventListener( "load", completed );
|
|
jQuery.ready();
|
|
}
|
|
|
|
// Catch cases where $(document).ready() is called
|
|
// after the browser event has already occurred.
|
|
// Support: IE <=9 - 10 only
|
|
// Older IE sometimes signals "interactive" too soon
|
|
if ( document.readyState === "complete" ||
|
|
( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
|
|
|
|
// Handle it asynchronously to allow scripts the opportunity to delay ready
|
|
window.setTimeout( jQuery.ready );
|
|
|
|
} else {
|
|
|
|
// Use the handy event callback
|
|
document.addEventListener( "DOMContentLoaded", completed );
|
|
|
|
// A fallback to window.onload, that will always work
|
|
window.addEventListener( "load", completed );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Multifunctional method to get and set values of a collection
|
|
// The value/s can optionally be executed if it's a function
|
|
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
|
|
var i = 0,
|
|
len = elems.length,
|
|
bulk = key == null;
|
|
|
|
// Sets many values
|
|
if ( toType( key ) === "object" ) {
|
|
chainable = true;
|
|
for ( i in key ) {
|
|
access( elems, fn, i, key[ i ], true, emptyGet, raw );
|
|
}
|
|
|
|
// Sets one value
|
|
} else if ( value !== undefined ) {
|
|
chainable = true;
|
|
|
|
if ( !isFunction( value ) ) {
|
|
raw = true;
|
|
}
|
|
|
|
if ( bulk ) {
|
|
|
|
// Bulk operations run against the entire set
|
|
if ( raw ) {
|
|
fn.call( elems, value );
|
|
fn = null;
|
|
|
|
// ...except when executing function values
|
|
} else {
|
|
bulk = fn;
|
|
fn = function( elem, key, value ) {
|
|
return bulk.call( jQuery( elem ), value );
|
|
};
|
|
}
|
|
}
|
|
|
|
if ( fn ) {
|
|
for ( ; i < len; i++ ) {
|
|
fn(
|
|
elems[ i ], key, raw ?
|
|
value :
|
|
value.call( elems[ i ], i, fn( elems[ i ], key ) )
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( chainable ) {
|
|
return elems;
|
|
}
|
|
|
|
// Gets
|
|
if ( bulk ) {
|
|
return fn.call( elems );
|
|
}
|
|
|
|
return len ? fn( elems[ 0 ], key ) : emptyGet;
|
|
};
|
|
|
|
|
|
// Matches dashed string for camelizing
|
|
var rmsPrefix = /^-ms-/,
|
|
rdashAlpha = /-([a-z])/g;
|
|
|
|
// Used by camelCase as callback to replace()
|
|
function fcamelCase( all, letter ) {
|
|
return letter.toUpperCase();
|
|
}
|
|
|
|
// Convert dashed to camelCase; used by the css and data modules
|
|
// Support: IE <=9 - 11, Edge 12 - 15
|
|
// Microsoft forgot to hump their vendor prefix (#9572)
|
|
function camelCase( string ) {
|
|
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
|
}
|
|
var acceptData = function( owner ) {
|
|
|
|
// Accepts only:
|
|
// - Node
|
|
// - Node.ELEMENT_NODE
|
|
// - Node.DOCUMENT_NODE
|
|
// - Object
|
|
// - Any
|
|
return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
|
|
};
|
|
|
|
|
|
|
|
|
|
function Data() {
|
|
this.expando = jQuery.expando + Data.uid++;
|
|
}
|
|
|
|
Data.uid = 1;
|
|
|
|
Data.prototype = {
|
|
|
|
cache: function( owner ) {
|
|
|
|
// Check if the owner object already has a cache
|
|
var value = owner[ this.expando ];
|
|
|
|
// If not, create one
|
|
if ( !value ) {
|
|
value = {};
|
|
|
|
// We can accept data for non-element nodes in modern browsers,
|
|
// but we should not, see #8335.
|
|
// Always return an empty object.
|
|
if ( acceptData( owner ) ) {
|
|
|
|
// If it is a node unlikely to be stringify-ed or looped over
|
|
// use plain assignment
|
|
if ( owner.nodeType ) {
|
|
owner[ this.expando ] = value;
|
|
|
|
// Otherwise secure it in a non-enumerable property
|
|
// configurable must be true to allow the property to be
|
|
// deleted when data is removed
|
|
} else {
|
|
Object.defineProperty( owner, this.expando, {
|
|
value: value,
|
|
configurable: true
|
|
} );
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
},
|
|
set: function( owner, data, value ) {
|
|
var prop,
|
|
cache = this.cache( owner );
|
|
|
|
// Handle: [ owner, key, value ] args
|
|
// Always use camelCase key (gh-2257)
|
|
if ( typeof data === "string" ) {
|
|
cache[ camelCase( data ) ] = value;
|
|
|
|
// Handle: [ owner, { properties } ] args
|
|
} else {
|
|
|
|
// Copy the properties one-by-one to the cache object
|
|
for ( prop in data ) {
|
|
cache[ camelCase( prop ) ] = data[ prop ];
|
|
}
|
|
}
|
|
return cache;
|
|
},
|
|
get: function( owner, key ) {
|
|
return key === undefined ?
|
|
this.cache( owner ) :
|
|
|
|
// Always use camelCase key (gh-2257)
|
|
owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
|
|
},
|
|
access: function( owner, key, value ) {
|
|
|
|
// In cases where either:
|
|
//
|
|
// 1. No key was specified
|
|
// 2. A string key was specified, but no value provided
|
|
//
|
|
// Take the "read" path and allow the get method to determine
|
|
// which value to return, respectively either:
|
|
//
|
|
// 1. The entire cache object
|
|
// 2. The data stored at the key
|
|
//
|
|
if ( key === undefined ||
|
|
( ( key && typeof key === "string" ) && value === undefined ) ) {
|
|
|
|
return this.get( owner, key );
|
|
}
|
|
|
|
// When the key is not a string, or both a key and value
|
|
// are specified, set or extend (existing objects) with either:
|
|
//
|
|
// 1. An object of properties
|
|
// 2. A key and value
|
|
//
|
|
this.set( owner, key, value );
|
|
|
|
// Since the "set" path can have two possible entry points
|
|
// return the expected data based on which path was taken[*]
|
|
return value !== undefined ? value : key;
|
|
},
|
|
remove: function( owner, key ) {
|
|
var i,
|
|
cache = owner[ this.expando ];
|
|
|
|
if ( cache === undefined ) {
|
|
return;
|
|
}
|
|
|
|
if ( key !== undefined ) {
|
|
|
|
// Support array or space separated string of keys
|
|
if ( Array.isArray( key ) ) {
|
|
|
|
// If key is an array of keys...
|
|
// We always set camelCase keys, so remove that.
|
|
key = key.map( camelCase );
|
|
} else {
|
|
key = camelCase( key );
|
|
|
|
// If a key with the spaces exists, use it.
|
|
// Otherwise, create an array by matching non-whitespace
|
|
key = key in cache ?
|
|
[ key ] :
|
|
( key.match( rnothtmlwhite ) || [] );
|
|
}
|
|
|
|
i = key.length;
|
|
|
|
while ( i-- ) {
|
|
delete cache[ key[ i ] ];
|
|
}
|
|
}
|
|
|
|
// Remove the expando if there's no more data
|
|
if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
|
|
|
|
// Support: Chrome <=35 - 45
|
|
// Webkit & Blink performance suffers when deleting properties
|
|
// from DOM nodes, so set to undefined instead
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
|
|
if ( owner.nodeType ) {
|
|
owner[ this.expando ] = undefined;
|
|
} else {
|
|
delete owner[ this.expando ];
|
|
}
|
|
}
|
|
},
|
|
hasData: function( owner ) {
|
|
var cache = owner[ this.expando ];
|
|
return cache !== undefined && !jQuery.isEmptyObject( cache );
|
|
}
|
|
};
|
|
var dataPriv = new Data();
|
|
|
|
var dataUser = new Data();
|
|
|
|
|
|
|
|
// Implementation Summary
|
|
//
|
|
// 1. Enforce API surface and semantic compatibility with 1.9.x branch
|
|
// 2. Improve the module's maintainability by reducing the storage
|
|
// paths to a single mechanism.
|
|
// 3. Use the same single mechanism to support "private" and "user" data.
|
|
// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
|
|
// 5. Avoid exposing implementation details on user objects (eg. expando properties)
|
|
// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
|
|
|
|
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
|
|
rmultiDash = /[A-Z]/g;
|
|
|
|
function getData( data ) {
|
|
if ( data === "true" ) {
|
|
return true;
|
|
}
|
|
|
|
if ( data === "false" ) {
|
|
return false;
|
|
}
|
|
|
|
if ( data === "null" ) {
|
|
return null;
|
|
}
|
|
|
|
// Only convert to a number if it doesn't change the string
|
|
if ( data === +data + "" ) {
|
|
return +data;
|
|
}
|
|
|
|
if ( rbrace.test( data ) ) {
|
|
return JSON.parse( data );
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function dataAttr( elem, key, data ) {
|
|
var name;
|
|
|
|
// If nothing was found internally, try to fetch any
|
|
// data from the HTML5 data-* attribute
|
|
if ( data === undefined && elem.nodeType === 1 ) {
|
|
name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
|
|
data = elem.getAttribute( name );
|
|
|
|
if ( typeof data === "string" ) {
|
|
try {
|
|
data = getData( data );
|
|
} catch ( e ) {}
|
|
|
|
// Make sure we set the data so it isn't changed later
|
|
dataUser.set( elem, key, data );
|
|
} else {
|
|
data = undefined;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
jQuery.extend( {
|
|
hasData: function( elem ) {
|
|
return dataUser.hasData( elem ) || dataPriv.hasData( elem );
|
|
},
|
|
|
|
data: function( elem, name, data ) {
|
|
return dataUser.access( elem, name, data );
|
|
},
|
|
|
|
removeData: function( elem, name ) {
|
|
dataUser.remove( elem, name );
|
|
},
|
|
|
|
// TODO: Now that all calls to _data and _removeData have been replaced
|
|
// with direct calls to dataPriv methods, these can be deprecated.
|
|
_data: function( elem, name, data ) {
|
|
return dataPriv.access( elem, name, data );
|
|
},
|
|
|
|
_removeData: function( elem, name ) {
|
|
dataPriv.remove( elem, name );
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
data: function( key, value ) {
|
|
var i, name, data,
|
|
elem = this[ 0 ],
|
|
attrs = elem && elem.attributes;
|
|
|
|
// Gets all values
|
|
if ( key === undefined ) {
|
|
if ( this.length ) {
|
|
data = dataUser.get( elem );
|
|
|
|
if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
|
|
i = attrs.length;
|
|
while ( i-- ) {
|
|
|
|
// Support: IE 11 only
|
|
// The attrs elements can be null (#14894)
|
|
if ( attrs[ i ] ) {
|
|
name = attrs[ i ].name;
|
|
if ( name.indexOf( "data-" ) === 0 ) {
|
|
name = camelCase( name.slice( 5 ) );
|
|
dataAttr( elem, name, data[ name ] );
|
|
}
|
|
}
|
|
}
|
|
dataPriv.set( elem, "hasDataAttrs", true );
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// Sets multiple values
|
|
if ( typeof key === "object" ) {
|
|
return this.each( function() {
|
|
dataUser.set( this, key );
|
|
} );
|
|
}
|
|
|
|
return access( this, function( value ) {
|
|
var data;
|
|
|
|
// The calling jQuery object (element matches) is not empty
|
|
// (and therefore has an element appears at this[ 0 ]) and the
|
|
// `value` parameter was not undefined. An empty jQuery object
|
|
// will result in `undefined` for elem = this[ 0 ] which will
|
|
// throw an exception if an attempt to read a data cache is made.
|
|
if ( elem && value === undefined ) {
|
|
|
|
// Attempt to get data from the cache
|
|
// The key will always be camelCased in Data
|
|
data = dataUser.get( elem, key );
|
|
if ( data !== undefined ) {
|
|
return data;
|
|
}
|
|
|
|
// Attempt to "discover" the data in
|
|
// HTML5 custom data-* attrs
|
|
data = dataAttr( elem, key );
|
|
if ( data !== undefined ) {
|
|
return data;
|
|
}
|
|
|
|
// We tried really hard, but the data doesn't exist.
|
|
return;
|
|
}
|
|
|
|
// Set the data...
|
|
this.each( function() {
|
|
|
|
// We always store the camelCased key
|
|
dataUser.set( this, key, value );
|
|
} );
|
|
}, null, value, arguments.length > 1, null, true );
|
|
},
|
|
|
|
removeData: function( key ) {
|
|
return this.each( function() {
|
|
dataUser.remove( this, key );
|
|
} );
|
|
}
|
|
} );
|
|
|
|
|
|
jQuery.extend( {
|
|
queue: function( elem, type, data ) {
|
|
var queue;
|
|
|
|
if ( elem ) {
|
|
type = ( type || "fx" ) + "queue";
|
|
queue = dataPriv.get( elem, type );
|
|
|
|
// Speed up dequeue by getting out quickly if this is just a lookup
|
|
if ( data ) {
|
|
if ( !queue || Array.isArray( data ) ) {
|
|
queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
|
|
} else {
|
|
queue.push( data );
|
|
}
|
|
}
|
|
return queue || [];
|
|
}
|
|
},
|
|
|
|
dequeue: function( elem, type ) {
|
|
type = type || "fx";
|
|
|
|
var queue = jQuery.queue( elem, type ),
|
|
startLength = queue.length,
|
|
fn = queue.shift(),
|
|
hooks = jQuery._queueHooks( elem, type ),
|
|
next = function() {
|
|
jQuery.dequeue( elem, type );
|
|
};
|
|
|
|
// If the fx queue is dequeued, always remove the progress sentinel
|
|
if ( fn === "inprogress" ) {
|
|
fn = queue.shift();
|
|
startLength--;
|
|
}
|
|
|
|
if ( fn ) {
|
|
|
|
// Add a progress sentinel to prevent the fx queue from being
|
|
// automatically dequeued
|
|
if ( type === "fx" ) {
|
|
queue.unshift( "inprogress" );
|
|
}
|
|
|
|
// Clear up the last queue stop function
|
|
delete hooks.stop;
|
|
fn.call( elem, next, hooks );
|
|
}
|
|
|
|
if ( !startLength && hooks ) {
|
|
hooks.empty.fire();
|
|
}
|
|
},
|
|
|
|
// Not public - generate a queueHooks object, or return the current one
|
|
_queueHooks: function( elem, type ) {
|
|
var key = type + "queueHooks";
|
|
return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
|
|
empty: jQuery.Callbacks( "once memory" ).add( function() {
|
|
dataPriv.remove( elem, [ type + "queue", key ] );
|
|
} )
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
queue: function( type, data ) {
|
|
var setter = 2;
|
|
|
|
if ( typeof type !== "string" ) {
|
|
data = type;
|
|
type = "fx";
|
|
setter--;
|
|
}
|
|
|
|
if ( arguments.length < setter ) {
|
|
return jQuery.queue( this[ 0 ], type );
|
|
}
|
|
|
|
return data === undefined ?
|
|
this :
|
|
this.each( function() {
|
|
var queue = jQuery.queue( this, type, data );
|
|
|
|
// Ensure a hooks for this queue
|
|
jQuery._queueHooks( this, type );
|
|
|
|
if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
|
|
jQuery.dequeue( this, type );
|
|
}
|
|
} );
|
|
},
|
|
dequeue: function( type ) {
|
|
return this.each( function() {
|
|
jQuery.dequeue( this, type );
|
|
} );
|
|
},
|
|
clearQueue: function( type ) {
|
|
return this.queue( type || "fx", [] );
|
|
},
|
|
|
|
// Get a promise resolved when queues of a certain type
|
|
// are emptied (fx is the type by default)
|
|
promise: function( type, obj ) {
|
|
var tmp,
|
|
count = 1,
|
|
defer = jQuery.Deferred(),
|
|
elements = this,
|
|
i = this.length,
|
|
resolve = function() {
|
|
if ( !( --count ) ) {
|
|
defer.resolveWith( elements, [ elements ] );
|
|
}
|
|
};
|
|
|
|
if ( typeof type !== "string" ) {
|
|
obj = type;
|
|
type = undefined;
|
|
}
|
|
type = type || "fx";
|
|
|
|
while ( i-- ) {
|
|
tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
|
|
if ( tmp && tmp.empty ) {
|
|
count++;
|
|
tmp.empty.add( resolve );
|
|
}
|
|
}
|
|
resolve();
|
|
return defer.promise( obj );
|
|
}
|
|
} );
|
|
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
|
|
|
|
var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
|
|
|
|
|
|
var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
|
|
|
|
var isHiddenWithinTree = function( elem, el ) {
|
|
|
|
// isHiddenWithinTree might be called from jQuery#filter function;
|
|
// in that case, element will be second argument
|
|
elem = el || elem;
|
|
|
|
// Inline style trumps all
|
|
return elem.style.display === "none" ||
|
|
elem.style.display === "" &&
|
|
|
|
// Otherwise, check computed style
|
|
// Support: Firefox <=43 - 45
|
|
// Disconnected elements can have computed display: none, so first confirm that elem is
|
|
// in the document.
|
|
jQuery.contains( elem.ownerDocument, elem ) &&
|
|
|
|
jQuery.css( elem, "display" ) === "none";
|
|
};
|
|
|
|
var swap = function( elem, options, callback, args ) {
|
|
var ret, name,
|
|
old = {};
|
|
|
|
// Remember the old values, and insert the new ones
|
|
for ( name in options ) {
|
|
old[ name ] = elem.style[ name ];
|
|
elem.style[ name ] = options[ name ];
|
|
}
|
|
|
|
ret = callback.apply( elem, args || [] );
|
|
|
|
// Revert the old values
|
|
for ( name in options ) {
|
|
elem.style[ name ] = old[ name ];
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
|
|
|
|
|
|
function adjustCSS( elem, prop, valueParts, tween ) {
|
|
var adjusted, scale,
|
|
maxIterations = 20,
|
|
currentValue = tween ?
|
|
function() {
|
|
return tween.cur();
|
|
} :
|
|
function() {
|
|
return jQuery.css( elem, prop, "" );
|
|
},
|
|
initial = currentValue(),
|
|
unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
|
|
|
|
// Starting value computation is required for potential unit mismatches
|
|
initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
|
|
rcssNum.exec( jQuery.css( elem, prop ) );
|
|
|
|
if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
|
|
|
|
// Support: Firefox <=54
|
|
// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
|
|
initial = initial / 2;
|
|
|
|
// Trust units reported by jQuery.css
|
|
unit = unit || initialInUnit[ 3 ];
|
|
|
|
// Iteratively approximate from a nonzero starting point
|
|
initialInUnit = +initial || 1;
|
|
|
|
while ( maxIterations-- ) {
|
|
|
|
// Evaluate and update our best guess (doubling guesses that zero out).
|
|
// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
|
|
jQuery.style( elem, prop, initialInUnit + unit );
|
|
if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
|
|
maxIterations = 0;
|
|
}
|
|
initialInUnit = initialInUnit / scale;
|
|
|
|
}
|
|
|
|
initialInUnit = initialInUnit * 2;
|
|
jQuery.style( elem, prop, initialInUnit + unit );
|
|
|
|
// Make sure we update the tween properties later on
|
|
valueParts = valueParts || [];
|
|
}
|
|
|
|
if ( valueParts ) {
|
|
initialInUnit = +initialInUnit || +initial || 0;
|
|
|
|
// Apply relative offset (+=/-=) if specified
|
|
adjusted = valueParts[ 1 ] ?
|
|
initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
|
|
+valueParts[ 2 ];
|
|
if ( tween ) {
|
|
tween.unit = unit;
|
|
tween.start = initialInUnit;
|
|
tween.end = adjusted;
|
|
}
|
|
}
|
|
return adjusted;
|
|
}
|
|
|
|
|
|
var defaultDisplayMap = {};
|
|
|
|
function getDefaultDisplay( elem ) {
|
|
var temp,
|
|
doc = elem.ownerDocument,
|
|
nodeName = elem.nodeName,
|
|
display = defaultDisplayMap[ nodeName ];
|
|
|
|
if ( display ) {
|
|
return display;
|
|
}
|
|
|
|
temp = doc.body.appendChild( doc.createElement( nodeName ) );
|
|
display = jQuery.css( temp, "display" );
|
|
|
|
temp.parentNode.removeChild( temp );
|
|
|
|
if ( display === "none" ) {
|
|
display = "block";
|
|
}
|
|
defaultDisplayMap[ nodeName ] = display;
|
|
|
|
return display;
|
|
}
|
|
|
|
function showHide( elements, show ) {
|
|
var display, elem,
|
|
values = [],
|
|
index = 0,
|
|
length = elements.length;
|
|
|
|
// Determine new display value for elements that need to change
|
|
for ( ; index < length; index++ ) {
|
|
elem = elements[ index ];
|
|
if ( !elem.style ) {
|
|
continue;
|
|
}
|
|
|
|
display = elem.style.display;
|
|
if ( show ) {
|
|
|
|
// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
|
|
// check is required in this first loop unless we have a nonempty display value (either
|
|
// inline or about-to-be-restored)
|
|
if ( display === "none" ) {
|
|
values[ index ] = dataPriv.get( elem, "display" ) || null;
|
|
if ( !values[ index ] ) {
|
|
elem.style.display = "";
|
|
}
|
|
}
|
|
if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
|
|
values[ index ] = getDefaultDisplay( elem );
|
|
}
|
|
} else {
|
|
if ( display !== "none" ) {
|
|
values[ index ] = "none";
|
|
|
|
// Remember what we're overwriting
|
|
dataPriv.set( elem, "display", display );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the display of the elements in a second loop to avoid constant reflow
|
|
for ( index = 0; index < length; index++ ) {
|
|
if ( values[ index ] != null ) {
|
|
elements[ index ].style.display = values[ index ];
|
|
}
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
jQuery.fn.extend( {
|
|
show: function() {
|
|
return showHide( this, true );
|
|
},
|
|
hide: function() {
|
|
return showHide( this );
|
|
},
|
|
toggle: function( state ) {
|
|
if ( typeof state === "boolean" ) {
|
|
return state ? this.show() : this.hide();
|
|
}
|
|
|
|
return this.each( function() {
|
|
if ( isHiddenWithinTree( this ) ) {
|
|
jQuery( this ).show();
|
|
} else {
|
|
jQuery( this ).hide();
|
|
}
|
|
} );
|
|
}
|
|
} );
|
|
var rcheckableType = ( /^(?:checkbox|radio)$/i );
|
|
|
|
var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );
|
|
|
|
var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
|
|
|
|
|
|
|
// We have to close these tags to support XHTML (#13200)
|
|
var wrapMap = {
|
|
|
|
// Support: IE <=9 only
|
|
option: [ 1, "<select multiple='multiple'>", "</select>" ],
|
|
|
|
// XHTML parsers do not magically insert elements in the
|
|
// same way that tag soup parsers do. So we cannot shorten
|
|
// this by omitting <tbody> or other required elements.
|
|
thead: [ 1, "<table>", "</table>" ],
|
|
col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
|
|
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
|
|
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
|
|
|
|
_default: [ 0, "", "" ]
|
|
};
|
|
|
|
// Support: IE <=9 only
|
|
wrapMap.optgroup = wrapMap.option;
|
|
|
|
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
|
|
wrapMap.th = wrapMap.td;
|
|
|
|
|
|
function getAll( context, tag ) {
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// Use typeof to avoid zero-argument method invocation on host objects (#15151)
|
|
var ret;
|
|
|
|
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
|
ret = context.getElementsByTagName( tag || "*" );
|
|
|
|
} else if ( typeof context.querySelectorAll !== "undefined" ) {
|
|
ret = context.querySelectorAll( tag || "*" );
|
|
|
|
} else {
|
|
ret = [];
|
|
}
|
|
|
|
if ( tag === undefined || tag && nodeName( context, tag ) ) {
|
|
return jQuery.merge( [ context ], ret );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// Mark scripts as having already been evaluated
|
|
function setGlobalEval( elems, refElements ) {
|
|
var i = 0,
|
|
l = elems.length;
|
|
|
|
for ( ; i < l; i++ ) {
|
|
dataPriv.set(
|
|
elems[ i ],
|
|
"globalEval",
|
|
!refElements || dataPriv.get( refElements[ i ], "globalEval" )
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
var rhtml = /<|&#?\w+;/;
|
|
|
|
function buildFragment( elems, context, scripts, selection, ignored ) {
|
|
var elem, tmp, tag, wrap, contains, j,
|
|
fragment = context.createDocumentFragment(),
|
|
nodes = [],
|
|
i = 0,
|
|
l = elems.length;
|
|
|
|
for ( ; i < l; i++ ) {
|
|
elem = elems[ i ];
|
|
|
|
if ( elem || elem === 0 ) {
|
|
|
|
// Add nodes directly
|
|
if ( toType( elem ) === "object" ) {
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
|
|
|
|
// Convert non-html into a text node
|
|
} else if ( !rhtml.test( elem ) ) {
|
|
nodes.push( context.createTextNode( elem ) );
|
|
|
|
// Convert html into DOM nodes
|
|
} else {
|
|
tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
|
|
|
|
// Deserialize a standard representation
|
|
tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
|
|
wrap = wrapMap[ tag ] || wrapMap._default;
|
|
tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
|
|
|
|
// Descend through wrappers to the right content
|
|
j = wrap[ 0 ];
|
|
while ( j-- ) {
|
|
tmp = tmp.lastChild;
|
|
}
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
jQuery.merge( nodes, tmp.childNodes );
|
|
|
|
// Remember the top-level container
|
|
tmp = fragment.firstChild;
|
|
|
|
// Ensure the created nodes are orphaned (#12392)
|
|
tmp.textContent = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove wrapper from fragment
|
|
fragment.textContent = "";
|
|
|
|
i = 0;
|
|
while ( ( elem = nodes[ i++ ] ) ) {
|
|
|
|
// Skip elements already in the context collection (trac-4087)
|
|
if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
|
|
if ( ignored ) {
|
|
ignored.push( elem );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
contains = jQuery.contains( elem.ownerDocument, elem );
|
|
|
|
// Append to fragment
|
|
tmp = getAll( fragment.appendChild( elem ), "script" );
|
|
|
|
// Preserve script evaluation history
|
|
if ( contains ) {
|
|
setGlobalEval( tmp );
|
|
}
|
|
|
|
// Capture executables
|
|
if ( scripts ) {
|
|
j = 0;
|
|
while ( ( elem = tmp[ j++ ] ) ) {
|
|
if ( rscriptType.test( elem.type || "" ) ) {
|
|
scripts.push( elem );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fragment;
|
|
}
|
|
|
|
|
|
( function() {
|
|
var fragment = document.createDocumentFragment(),
|
|
div = fragment.appendChild( document.createElement( "div" ) ),
|
|
input = document.createElement( "input" );
|
|
|
|
// Support: Android 4.0 - 4.3 only
|
|
// Check state lost if the name is set (#11217)
|
|
// Support: Windows Web Apps (WWA)
|
|
// `name` and `type` must use .setAttribute for WWA (#14901)
|
|
input.setAttribute( "type", "radio" );
|
|
input.setAttribute( "checked", "checked" );
|
|
input.setAttribute( "name", "t" );
|
|
|
|
div.appendChild( input );
|
|
|
|
// Support: Android <=4.1 only
|
|
// Older WebKit doesn't clone checked state correctly in fragments
|
|
support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
|
|
|
|
// Support: IE <=11 only
|
|
// Make sure textarea (and checkbox) defaultValue is properly cloned
|
|
div.innerHTML = "<textarea>x</textarea>";
|
|
support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
|
|
} )();
|
|
var documentElement = document.documentElement;
|
|
|
|
|
|
|
|
var
|
|
rkeyEvent = /^key/,
|
|
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
|
|
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
|
|
|
|
function returnTrue() {
|
|
return true;
|
|
}
|
|
|
|
function returnFalse() {
|
|
return false;
|
|
}
|
|
|
|
// Support: IE <=9 only
|
|
// See #13393 for more info
|
|
function safeActiveElement() {
|
|
try {
|
|
return document.activeElement;
|
|
} catch ( err ) { }
|
|
}
|
|
|
|
function on( elem, types, selector, data, fn, one ) {
|
|
var origFn, type;
|
|
|
|
// Types can be a map of types/handlers
|
|
if ( typeof types === "object" ) {
|
|
|
|
// ( types-Object, selector, data )
|
|
if ( typeof selector !== "string" ) {
|
|
|
|
// ( types-Object, data )
|
|
data = data || selector;
|
|
selector = undefined;
|
|
}
|
|
for ( type in types ) {
|
|
on( elem, type, selector, data, types[ type ], one );
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
if ( data == null && fn == null ) {
|
|
|
|
// ( types, fn )
|
|
fn = selector;
|
|
data = selector = undefined;
|
|
} else if ( fn == null ) {
|
|
if ( typeof selector === "string" ) {
|
|
|
|
// ( types, selector, fn )
|
|
fn = data;
|
|
data = undefined;
|
|
} else {
|
|
|
|
// ( types, data, fn )
|
|
fn = data;
|
|
data = selector;
|
|
selector = undefined;
|
|
}
|
|
}
|
|
if ( fn === false ) {
|
|
fn = returnFalse;
|
|
} else if ( !fn ) {
|
|
return elem;
|
|
}
|
|
|
|
if ( one === 1 ) {
|
|
origFn = fn;
|
|
fn = function( event ) {
|
|
|
|
// Can use an empty set, since event contains the info
|
|
jQuery().off( event );
|
|
return origFn.apply( this, arguments );
|
|
};
|
|
|
|
// Use same guid so caller can remove using origFn
|
|
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
|
|
}
|
|
return elem.each( function() {
|
|
jQuery.event.add( this, types, fn, data, selector );
|
|
} );
|
|
}
|
|
|
|
/*
|
|
* Helper functions for managing events -- not part of the public interface.
|
|
* Props to Dean Edwards' addEvent library for many of the ideas.
|
|
*/
|
|
jQuery.event = {
|
|
|
|
global: {},
|
|
|
|
add: function( elem, types, handler, data, selector ) {
|
|
|
|
var handleObjIn, eventHandle, tmp,
|
|
events, t, handleObj,
|
|
special, handlers, type, namespaces, origType,
|
|
elemData = dataPriv.get( elem );
|
|
|
|
// Don't attach events to noData or text/comment nodes (but allow plain objects)
|
|
if ( !elemData ) {
|
|
return;
|
|
}
|
|
|
|
// Caller can pass in an object of custom data in lieu of the handler
|
|
if ( handler.handler ) {
|
|
handleObjIn = handler;
|
|
handler = handleObjIn.handler;
|
|
selector = handleObjIn.selector;
|
|
}
|
|
|
|
// Ensure that invalid selectors throw exceptions at attach time
|
|
// Evaluate against documentElement in case elem is a non-element node (e.g., document)
|
|
if ( selector ) {
|
|
jQuery.find.matchesSelector( documentElement, selector );
|
|
}
|
|
|
|
// Make sure that the handler has a unique ID, used to find/remove it later
|
|
if ( !handler.guid ) {
|
|
handler.guid = jQuery.guid++;
|
|
}
|
|
|
|
// Init the element's event structure and main handler, if this is the first
|
|
if ( !( events = elemData.events ) ) {
|
|
events = elemData.events = {};
|
|
}
|
|
if ( !( eventHandle = elemData.handle ) ) {
|
|
eventHandle = elemData.handle = function( e ) {
|
|
|
|
// Discard the second event of a jQuery.event.trigger() and
|
|
// when an event is called after a page has unloaded
|
|
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
|
|
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
|
|
};
|
|
}
|
|
|
|
// Handle multiple events separated by a space
|
|
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
|
|
t = types.length;
|
|
while ( t-- ) {
|
|
tmp = rtypenamespace.exec( types[ t ] ) || [];
|
|
type = origType = tmp[ 1 ];
|
|
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
|
|
|
|
// There *must* be a type, no attaching namespace-only handlers
|
|
if ( !type ) {
|
|
continue;
|
|
}
|
|
|
|
// If event changes its type, use the special event handlers for the changed type
|
|
special = jQuery.event.special[ type ] || {};
|
|
|
|
// If selector defined, determine special event api type, otherwise given type
|
|
type = ( selector ? special.delegateType : special.bindType ) || type;
|
|
|
|
// Update special based on newly reset type
|
|
special = jQuery.event.special[ type ] || {};
|
|
|
|
// handleObj is passed to all event handlers
|
|
handleObj = jQuery.extend( {
|
|
type: type,
|
|
origType: origType,
|
|
data: data,
|
|
handler: handler,
|
|
guid: handler.guid,
|
|
selector: selector,
|
|
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
|
|
namespace: namespaces.join( "." )
|
|
}, handleObjIn );
|
|
|
|
// Init the event handler queue if we're the first
|
|
if ( !( handlers = events[ type ] ) ) {
|
|
handlers = events[ type ] = [];
|
|
handlers.delegateCount = 0;
|
|
|
|
// Only use addEventListener if the special events handler returns false
|
|
if ( !special.setup ||
|
|
special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
|
|
|
|
if ( elem.addEventListener ) {
|
|
elem.addEventListener( type, eventHandle );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( special.add ) {
|
|
special.add.call( elem, handleObj );
|
|
|
|
if ( !handleObj.handler.guid ) {
|
|
handleObj.handler.guid = handler.guid;
|
|
}
|
|
}
|
|
|
|
// Add to the element's handler list, delegates in front
|
|
if ( selector ) {
|
|
handlers.splice( handlers.delegateCount++, 0, handleObj );
|
|
} else {
|
|
handlers.push( handleObj );
|
|
}
|
|
|
|
// Keep track of which events have ever been used, for event optimization
|
|
jQuery.event.global[ type ] = true;
|
|
}
|
|
|
|
},
|
|
|
|
// Detach an event or set of events from an element
|
|
remove: function( elem, types, handler, selector, mappedTypes ) {
|
|
|
|
var j, origCount, tmp,
|
|
events, t, handleObj,
|
|
special, handlers, type, namespaces, origType,
|
|
elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
|
|
|
|
if ( !elemData || !( events = elemData.events ) ) {
|
|
return;
|
|
}
|
|
|
|
// Once for each type.namespace in types; type may be omitted
|
|
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
|
|
t = types.length;
|
|
while ( t-- ) {
|
|
tmp = rtypenamespace.exec( types[ t ] ) || [];
|
|
type = origType = tmp[ 1 ];
|
|
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
|
|
|
|
// Unbind all events (on this namespace, if provided) for the element
|
|
if ( !type ) {
|
|
for ( type in events ) {
|
|
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
special = jQuery.event.special[ type ] || {};
|
|
type = ( selector ? special.delegateType : special.bindType ) || type;
|
|
handlers = events[ type ] || [];
|
|
tmp = tmp[ 2 ] &&
|
|
new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
|
|
|
|
// Remove matching events
|
|
origCount = j = handlers.length;
|
|
while ( j-- ) {
|
|
handleObj = handlers[ j ];
|
|
|
|
if ( ( mappedTypes || origType === handleObj.origType ) &&
|
|
( !handler || handler.guid === handleObj.guid ) &&
|
|
( !tmp || tmp.test( handleObj.namespace ) ) &&
|
|
( !selector || selector === handleObj.selector ||
|
|
selector === "**" && handleObj.selector ) ) {
|
|
handlers.splice( j, 1 );
|
|
|
|
if ( handleObj.selector ) {
|
|
handlers.delegateCount--;
|
|
}
|
|
if ( special.remove ) {
|
|
special.remove.call( elem, handleObj );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove generic event handler if we removed something and no more handlers exist
|
|
// (avoids potential for endless recursion during removal of special event handlers)
|
|
if ( origCount && !handlers.length ) {
|
|
if ( !special.teardown ||
|
|
special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
|
|
|
|
jQuery.removeEvent( elem, type, elemData.handle );
|
|
}
|
|
|
|
delete events[ type ];
|
|
}
|
|
}
|
|
|
|
// Remove data and the expando if it's no longer used
|
|
if ( jQuery.isEmptyObject( events ) ) {
|
|
dataPriv.remove( elem, "handle events" );
|
|
}
|
|
},
|
|
|
|
dispatch: function( nativeEvent ) {
|
|
|
|
// Make a writable jQuery.Event from the native event object
|
|
var event = jQuery.event.fix( nativeEvent );
|
|
|
|
var i, j, ret, matched, handleObj, handlerQueue,
|
|
args = new Array( arguments.length ),
|
|
handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
|
|
special = jQuery.event.special[ event.type ] || {};
|
|
|
|
// Use the fix-ed jQuery.Event rather than the (read-only) native event
|
|
args[ 0 ] = event;
|
|
|
|
for ( i = 1; i < arguments.length; i++ ) {
|
|
args[ i ] = arguments[ i ];
|
|
}
|
|
|
|
event.delegateTarget = this;
|
|
|
|
// Call the preDispatch hook for the mapped type, and let it bail if desired
|
|
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
|
|
return;
|
|
}
|
|
|
|
// Determine handlers
|
|
handlerQueue = jQuery.event.handlers.call( this, event, handlers );
|
|
|
|
// Run delegates first; they may want to stop propagation beneath us
|
|
i = 0;
|
|
while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
|
|
event.currentTarget = matched.elem;
|
|
|
|
j = 0;
|
|
while ( ( handleObj = matched.handlers[ j++ ] ) &&
|
|
!event.isImmediatePropagationStopped() ) {
|
|
|
|
// Triggered event must either 1) have no namespace, or 2) have namespace(s)
|
|
// a subset or equal to those in the bound event (both can have no namespace).
|
|
if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
|
|
|
|
event.handleObj = handleObj;
|
|
event.data = handleObj.data;
|
|
|
|
ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
|
|
handleObj.handler ).apply( matched.elem, args );
|
|
|
|
if ( ret !== undefined ) {
|
|
if ( ( event.result = ret ) === false ) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call the postDispatch hook for the mapped type
|
|
if ( special.postDispatch ) {
|
|
special.postDispatch.call( this, event );
|
|
}
|
|
|
|
return event.result;
|
|
},
|
|
|
|
handlers: function( event, handlers ) {
|
|
var i, handleObj, sel, matchedHandlers, matchedSelectors,
|
|
handlerQueue = [],
|
|
delegateCount = handlers.delegateCount,
|
|
cur = event.target;
|
|
|
|
// Find delegate handlers
|
|
if ( delegateCount &&
|
|
|
|
// Support: IE <=9
|
|
// Black-hole SVG <use> instance trees (trac-13180)
|
|
cur.nodeType &&
|
|
|
|
// Support: Firefox <=42
|
|
// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
|
|
// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
|
|
// Support: IE 11 only
|
|
// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
|
|
!( event.type === "click" && event.button >= 1 ) ) {
|
|
|
|
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
|
|
|
// Don't check non-elements (#13208)
|
|
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
|
|
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
|
|
matchedHandlers = [];
|
|
matchedSelectors = {};
|
|
for ( i = 0; i < delegateCount; i++ ) {
|
|
handleObj = handlers[ i ];
|
|
|
|
// Don't conflict with Object.prototype properties (#13203)
|
|
sel = handleObj.selector + " ";
|
|
|
|
if ( matchedSelectors[ sel ] === undefined ) {
|
|
matchedSelectors[ sel ] = handleObj.needsContext ?
|
|
jQuery( sel, this ).index( cur ) > -1 :
|
|
jQuery.find( sel, this, null, [ cur ] ).length;
|
|
}
|
|
if ( matchedSelectors[ sel ] ) {
|
|
matchedHandlers.push( handleObj );
|
|
}
|
|
}
|
|
if ( matchedHandlers.length ) {
|
|
handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the remaining (directly-bound) handlers
|
|
cur = this;
|
|
if ( delegateCount < handlers.length ) {
|
|
handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
|
|
}
|
|
|
|
return handlerQueue;
|
|
},
|
|
|
|
addProp: function( name, hook ) {
|
|
Object.defineProperty( jQuery.Event.prototype, name, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
|
|
get: isFunction( hook ) ?
|
|
function() {
|
|
if ( this.originalEvent ) {
|
|
return hook( this.originalEvent );
|
|
}
|
|
} :
|
|
function() {
|
|
if ( this.originalEvent ) {
|
|
return this.originalEvent[ name ];
|
|
}
|
|
},
|
|
|
|
set: function( value ) {
|
|
Object.defineProperty( this, name, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: value
|
|
} );
|
|
}
|
|
} );
|
|
},
|
|
|
|
fix: function( originalEvent ) {
|
|
return originalEvent[ jQuery.expando ] ?
|
|
originalEvent :
|
|
new jQuery.Event( originalEvent );
|
|
},
|
|
|
|
special: {
|
|
load: {
|
|
|
|
// Prevent triggered image.load events from bubbling to window.load
|
|
noBubble: true
|
|
},
|
|
focus: {
|
|
|
|
// Fire native event if possible so blur/focus sequence is correct
|
|
trigger: function() {
|
|
if ( this !== safeActiveElement() && this.focus ) {
|
|
this.focus();
|
|
return false;
|
|
}
|
|
},
|
|
delegateType: "focusin"
|
|
},
|
|
blur: {
|
|
trigger: function() {
|
|
if ( this === safeActiveElement() && this.blur ) {
|
|
this.blur();
|
|
return false;
|
|
}
|
|
},
|
|
delegateType: "focusout"
|
|
},
|
|
click: {
|
|
|
|
// For checkbox, fire native event so checked state will be right
|
|
trigger: function() {
|
|
if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) {
|
|
this.click();
|
|
return false;
|
|
}
|
|
},
|
|
|
|
// For cross-browser consistency, don't fire native .click() on links
|
|
_default: function( event ) {
|
|
return nodeName( event.target, "a" );
|
|
}
|
|
},
|
|
|
|
beforeunload: {
|
|
postDispatch: function( event ) {
|
|
|
|
// Support: Firefox 20+
|
|
// Firefox doesn't alert if the returnValue field is not set.
|
|
if ( event.result !== undefined && event.originalEvent ) {
|
|
event.originalEvent.returnValue = event.result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
jQuery.removeEvent = function( elem, type, handle ) {
|
|
|
|
// This "if" is needed for plain objects
|
|
if ( elem.removeEventListener ) {
|
|
elem.removeEventListener( type, handle );
|
|
}
|
|
};
|
|
|
|
jQuery.Event = function( src, props ) {
|
|
|
|
// Allow instantiation without the 'new' keyword
|
|
if ( !( this instanceof jQuery.Event ) ) {
|
|
return new jQuery.Event( src, props );
|
|
}
|
|
|
|
// Event object
|
|
if ( src && src.type ) {
|
|
this.originalEvent = src;
|
|
this.type = src.type;
|
|
|
|
// Events bubbling up the document may have been marked as prevented
|
|
// by a handler lower down the tree; reflect the correct value.
|
|
this.isDefaultPrevented = src.defaultPrevented ||
|
|
src.defaultPrevented === undefined &&
|
|
|
|
// Support: Android <=2.3 only
|
|
src.returnValue === false ?
|
|
returnTrue :
|
|
returnFalse;
|
|
|
|
// Create target properties
|
|
// Support: Safari <=6 - 7 only
|
|
// Target should not be a text node (#504, #13143)
|
|
this.target = ( src.target && src.target.nodeType === 3 ) ?
|
|
src.target.parentNode :
|
|
src.target;
|
|
|
|
this.currentTarget = src.currentTarget;
|
|
this.relatedTarget = src.relatedTarget;
|
|
|
|
// Event type
|
|
} else {
|
|
this.type = src;
|
|
}
|
|
|
|
// Put explicitly provided properties onto the event object
|
|
if ( props ) {
|
|
jQuery.extend( this, props );
|
|
}
|
|
|
|
// Create a timestamp if incoming event doesn't have one
|
|
this.timeStamp = src && src.timeStamp || Date.now();
|
|
|
|
// Mark it as fixed
|
|
this[ jQuery.expando ] = true;
|
|
};
|
|
|
|
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
|
|
// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
|
|
jQuery.Event.prototype = {
|
|
constructor: jQuery.Event,
|
|
isDefaultPrevented: returnFalse,
|
|
isPropagationStopped: returnFalse,
|
|
isImmediatePropagationStopped: returnFalse,
|
|
isSimulated: false,
|
|
|
|
preventDefault: function() {
|
|
var e = this.originalEvent;
|
|
|
|
this.isDefaultPrevented = returnTrue;
|
|
|
|
if ( e && !this.isSimulated ) {
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
stopPropagation: function() {
|
|
var e = this.originalEvent;
|
|
|
|
this.isPropagationStopped = returnTrue;
|
|
|
|
if ( e && !this.isSimulated ) {
|
|
e.stopPropagation();
|
|
}
|
|
},
|
|
stopImmediatePropagation: function() {
|
|
var e = this.originalEvent;
|
|
|
|
this.isImmediatePropagationStopped = returnTrue;
|
|
|
|
if ( e && !this.isSimulated ) {
|
|
e.stopImmediatePropagation();
|
|
}
|
|
|
|
this.stopPropagation();
|
|
}
|
|
};
|
|
|
|
// Includes all common event props including KeyEvent and MouseEvent specific props
|
|
jQuery.each( {
|
|
altKey: true,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
changedTouches: true,
|
|
ctrlKey: true,
|
|
detail: true,
|
|
eventPhase: true,
|
|
metaKey: true,
|
|
pageX: true,
|
|
pageY: true,
|
|
shiftKey: true,
|
|
view: true,
|
|
"char": true,
|
|
charCode: true,
|
|
key: true,
|
|
keyCode: true,
|
|
button: true,
|
|
buttons: true,
|
|
clientX: true,
|
|
clientY: true,
|
|
offsetX: true,
|
|
offsetY: true,
|
|
pointerId: true,
|
|
pointerType: true,
|
|
screenX: true,
|
|
screenY: true,
|
|
targetTouches: true,
|
|
toElement: true,
|
|
touches: true,
|
|
|
|
which: function( event ) {
|
|
var button = event.button;
|
|
|
|
// Add which for key events
|
|
if ( event.which == null && rkeyEvent.test( event.type ) ) {
|
|
return event.charCode != null ? event.charCode : event.keyCode;
|
|
}
|
|
|
|
// Add which for click: 1 === left; 2 === middle; 3 === right
|
|
if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
|
|
if ( button & 1 ) {
|
|
return 1;
|
|
}
|
|
|
|
if ( button & 2 ) {
|
|
return 3;
|
|
}
|
|
|
|
if ( button & 4 ) {
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return event.which;
|
|
}
|
|
}, jQuery.event.addProp );
|
|
|
|
// Create mouseenter/leave events using mouseover/out and event-time checks
|
|
// so that event delegation works in jQuery.
|
|
// Do the same for pointerenter/pointerleave and pointerover/pointerout
|
|
//
|
|
// Support: Safari 7 only
|
|
// Safari sends mouseenter too often; see:
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
|
|
// for the description of the bug (it existed in older Chrome versions as well).
|
|
jQuery.each( {
|
|
mouseenter: "mouseover",
|
|
mouseleave: "mouseout",
|
|
pointerenter: "pointerover",
|
|
pointerleave: "pointerout"
|
|
}, function( orig, fix ) {
|
|
jQuery.event.special[ orig ] = {
|
|
delegateType: fix,
|
|
bindType: fix,
|
|
|
|
handle: function( event ) {
|
|
var ret,
|
|
target = this,
|
|
related = event.relatedTarget,
|
|
handleObj = event.handleObj;
|
|
|
|
// For mouseenter/leave call the handler if related is outside the target.
|
|
// NB: No relatedTarget if the mouse left/entered the browser window
|
|
if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
|
|
event.type = handleObj.origType;
|
|
ret = handleObj.handler.apply( this, arguments );
|
|
event.type = fix;
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
on: function( types, selector, data, fn ) {
|
|
return on( this, types, selector, data, fn );
|
|
},
|
|
one: function( types, selector, data, fn ) {
|
|
return on( this, types, selector, data, fn, 1 );
|
|
},
|
|
off: function( types, selector, fn ) {
|
|
var handleObj, type;
|
|
if ( types && types.preventDefault && types.handleObj ) {
|
|
|
|
// ( event ) dispatched jQuery.Event
|
|
handleObj = types.handleObj;
|
|
jQuery( types.delegateTarget ).off(
|
|
handleObj.namespace ?
|
|
handleObj.origType + "." + handleObj.namespace :
|
|
handleObj.origType,
|
|
handleObj.selector,
|
|
handleObj.handler
|
|
);
|
|
return this;
|
|
}
|
|
if ( typeof types === "object" ) {
|
|
|
|
// ( types-object [, selector] )
|
|
for ( type in types ) {
|
|
this.off( type, selector, types[ type ] );
|
|
}
|
|
return this;
|
|
}
|
|
if ( selector === false || typeof selector === "function" ) {
|
|
|
|
// ( types [, fn] )
|
|
fn = selector;
|
|
selector = undefined;
|
|
}
|
|
if ( fn === false ) {
|
|
fn = returnFalse;
|
|
}
|
|
return this.each( function() {
|
|
jQuery.event.remove( this, types, fn, selector );
|
|
} );
|
|
}
|
|
} );
|
|
|
|
|
|
var
|
|
|
|
/* eslint-disable max-len */
|
|
|
|
// See https://github.com/eslint/eslint/issues/3229
|
|
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
|
|
|
|
/* eslint-enable */
|
|
|
|
// Support: IE <=10 - 11, Edge 12 - 13 only
|
|
// In IE/Edge using regex groups here causes severe slowdowns.
|
|
// See https://connect.microsoft.com/IE/feedback/details/1736512/
|
|
rnoInnerhtml = /<script|<style|<link/i,
|
|
|
|
// checked="checked" or checked
|
|
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
|
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
|
|
|
|
// Prefer a tbody over its parent table for containing new rows
|
|
function manipulationTarget( elem, content ) {
|
|
if ( nodeName( elem, "table" ) &&
|
|
nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
|
|
|
|
return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
// Replace/restore the type attribute of script elements for safe DOM manipulation
|
|
function disableScript( elem ) {
|
|
elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
|
|
return elem;
|
|
}
|
|
function restoreScript( elem ) {
|
|
if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
|
|
elem.type = elem.type.slice( 5 );
|
|
} else {
|
|
elem.removeAttribute( "type" );
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
function cloneCopyEvent( src, dest ) {
|
|
var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
|
|
|
|
if ( dest.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
// 1. Copy private data: events, handlers, etc.
|
|
if ( dataPriv.hasData( src ) ) {
|
|
pdataOld = dataPriv.access( src );
|
|
pdataCur = dataPriv.set( dest, pdataOld );
|
|
events = pdataOld.events;
|
|
|
|
if ( events ) {
|
|
delete pdataCur.handle;
|
|
pdataCur.events = {};
|
|
|
|
for ( type in events ) {
|
|
for ( i = 0, l = events[ type ].length; i < l; i++ ) {
|
|
jQuery.event.add( dest, type, events[ type ][ i ] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Copy user data
|
|
if ( dataUser.hasData( src ) ) {
|
|
udataOld = dataUser.access( src );
|
|
udataCur = jQuery.extend( {}, udataOld );
|
|
|
|
dataUser.set( dest, udataCur );
|
|
}
|
|
}
|
|
|
|
// Fix IE bugs, see support tests
|
|
function fixInput( src, dest ) {
|
|
var nodeName = dest.nodeName.toLowerCase();
|
|
|
|
// Fails to persist the checked state of a cloned checkbox or radio button.
|
|
if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
|
|
dest.checked = src.checked;
|
|
|
|
// Fails to return the selected option to the default selected state when cloning options
|
|
} else if ( nodeName === "input" || nodeName === "textarea" ) {
|
|
dest.defaultValue = src.defaultValue;
|
|
}
|
|
}
|
|
|
|
function domManip( collection, args, callback, ignored ) {
|
|
|
|
// Flatten any nested arrays
|
|
args = concat.apply( [], args );
|
|
|
|
var fragment, first, scripts, hasScripts, node, doc,
|
|
i = 0,
|
|
l = collection.length,
|
|
iNoClone = l - 1,
|
|
value = args[ 0 ],
|
|
valueIsFunction = isFunction( value );
|
|
|
|
// We can't cloneNode fragments that contain checked, in WebKit
|
|
if ( valueIsFunction ||
|
|
( l > 1 && typeof value === "string" &&
|
|
!support.checkClone && rchecked.test( value ) ) ) {
|
|
return collection.each( function( index ) {
|
|
var self = collection.eq( index );
|
|
if ( valueIsFunction ) {
|
|
args[ 0 ] = value.call( this, index, self.html() );
|
|
}
|
|
domManip( self, args, callback, ignored );
|
|
} );
|
|
}
|
|
|
|
if ( l ) {
|
|
fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
|
|
first = fragment.firstChild;
|
|
|
|
if ( fragment.childNodes.length === 1 ) {
|
|
fragment = first;
|
|
}
|
|
|
|
// Require either new content or an interest in ignored elements to invoke the callback
|
|
if ( first || ignored ) {
|
|
scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
|
|
hasScripts = scripts.length;
|
|
|
|
// Use the original fragment for the last item
|
|
// instead of the first because it can end up
|
|
// being emptied incorrectly in certain situations (#8070).
|
|
for ( ; i < l; i++ ) {
|
|
node = fragment;
|
|
|
|
if ( i !== iNoClone ) {
|
|
node = jQuery.clone( node, true, true );
|
|
|
|
// Keep references to cloned scripts for later restoration
|
|
if ( hasScripts ) {
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
jQuery.merge( scripts, getAll( node, "script" ) );
|
|
}
|
|
}
|
|
|
|
callback.call( collection[ i ], node, i );
|
|
}
|
|
|
|
if ( hasScripts ) {
|
|
doc = scripts[ scripts.length - 1 ].ownerDocument;
|
|
|
|
// Reenable scripts
|
|
jQuery.map( scripts, restoreScript );
|
|
|
|
// Evaluate executable scripts on first document insertion
|
|
for ( i = 0; i < hasScripts; i++ ) {
|
|
node = scripts[ i ];
|
|
if ( rscriptType.test( node.type || "" ) &&
|
|
!dataPriv.access( node, "globalEval" ) &&
|
|
jQuery.contains( doc, node ) ) {
|
|
|
|
if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) {
|
|
|
|
// Optional AJAX dependency, but won't run scripts if not present
|
|
if ( jQuery._evalUrl ) {
|
|
jQuery._evalUrl( node.src );
|
|
}
|
|
} else {
|
|
DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return collection;
|
|
}
|
|
|
|
function remove( elem, selector, keepData ) {
|
|
var node,
|
|
nodes = selector ? jQuery.filter( selector, elem ) : elem,
|
|
i = 0;
|
|
|
|
for ( ; ( node = nodes[ i ] ) != null; i++ ) {
|
|
if ( !keepData && node.nodeType === 1 ) {
|
|
jQuery.cleanData( getAll( node ) );
|
|
}
|
|
|
|
if ( node.parentNode ) {
|
|
if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
|
|
setGlobalEval( getAll( node, "script" ) );
|
|
}
|
|
node.parentNode.removeChild( node );
|
|
}
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
jQuery.extend( {
|
|
htmlPrefilter: function( html ) {
|
|
return html.replace( rxhtmlTag, "<$1></$2>" );
|
|
},
|
|
|
|
clone: function( elem, dataAndEvents, deepDataAndEvents ) {
|
|
var i, l, srcElements, destElements,
|
|
clone = elem.cloneNode( true ),
|
|
inPage = jQuery.contains( elem.ownerDocument, elem );
|
|
|
|
// Fix IE cloning issues
|
|
if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
|
|
!jQuery.isXMLDoc( elem ) ) {
|
|
|
|
// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
|
|
destElements = getAll( clone );
|
|
srcElements = getAll( elem );
|
|
|
|
for ( i = 0, l = srcElements.length; i < l; i++ ) {
|
|
fixInput( srcElements[ i ], destElements[ i ] );
|
|
}
|
|
}
|
|
|
|
// Copy the events from the original to the clone
|
|
if ( dataAndEvents ) {
|
|
if ( deepDataAndEvents ) {
|
|
srcElements = srcElements || getAll( elem );
|
|
destElements = destElements || getAll( clone );
|
|
|
|
for ( i = 0, l = srcElements.length; i < l; i++ ) {
|
|
cloneCopyEvent( srcElements[ i ], destElements[ i ] );
|
|
}
|
|
} else {
|
|
cloneCopyEvent( elem, clone );
|
|
}
|
|
}
|
|
|
|
// Preserve script evaluation history
|
|
destElements = getAll( clone, "script" );
|
|
if ( destElements.length > 0 ) {
|
|
setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
|
|
}
|
|
|
|
// Return the cloned set
|
|
return clone;
|
|
},
|
|
|
|
cleanData: function( elems ) {
|
|
var data, elem, type,
|
|
special = jQuery.event.special,
|
|
i = 0;
|
|
|
|
for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
|
|
if ( acceptData( elem ) ) {
|
|
if ( ( data = elem[ dataPriv.expando ] ) ) {
|
|
if ( data.events ) {
|
|
for ( type in data.events ) {
|
|
if ( special[ type ] ) {
|
|
jQuery.event.remove( elem, type );
|
|
|
|
// This is a shortcut to avoid jQuery.event.remove's overhead
|
|
} else {
|
|
jQuery.removeEvent( elem, type, data.handle );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Support: Chrome <=35 - 45+
|
|
// Assign undefined instead of using delete, see Data#remove
|
|
elem[ dataPriv.expando ] = undefined;
|
|
}
|
|
if ( elem[ dataUser.expando ] ) {
|
|
|
|
// Support: Chrome <=35 - 45+
|
|
// Assign undefined instead of using delete, see Data#remove
|
|
elem[ dataUser.expando ] = undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
detach: function( selector ) {
|
|
return remove( this, selector, true );
|
|
},
|
|
|
|
remove: function( selector ) {
|
|
return remove( this, selector );
|
|
},
|
|
|
|
text: function( value ) {
|
|
return access( this, function( value ) {
|
|
return value === undefined ?
|
|
jQuery.text( this ) :
|
|
this.empty().each( function() {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
|
|
this.textContent = value;
|
|
}
|
|
} );
|
|
}, null, value, arguments.length );
|
|
},
|
|
|
|
append: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
|
|
var target = manipulationTarget( this, elem );
|
|
target.appendChild( elem );
|
|
}
|
|
} );
|
|
},
|
|
|
|
prepend: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
|
|
var target = manipulationTarget( this, elem );
|
|
target.insertBefore( elem, target.firstChild );
|
|
}
|
|
} );
|
|
},
|
|
|
|
before: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.parentNode ) {
|
|
this.parentNode.insertBefore( elem, this );
|
|
}
|
|
} );
|
|
},
|
|
|
|
after: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.parentNode ) {
|
|
this.parentNode.insertBefore( elem, this.nextSibling );
|
|
}
|
|
} );
|
|
},
|
|
|
|
empty: function() {
|
|
var elem,
|
|
i = 0;
|
|
|
|
for ( ; ( elem = this[ i ] ) != null; i++ ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
|
|
// Prevent memory leaks
|
|
jQuery.cleanData( getAll( elem, false ) );
|
|
|
|
// Remove any remaining nodes
|
|
elem.textContent = "";
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
clone: function( dataAndEvents, deepDataAndEvents ) {
|
|
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
|
|
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
|
|
|
|
return this.map( function() {
|
|
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
|
|
} );
|
|
},
|
|
|
|
html: function( value ) {
|
|
return access( this, function( value ) {
|
|
var elem = this[ 0 ] || {},
|
|
i = 0,
|
|
l = this.length;
|
|
|
|
if ( value === undefined && elem.nodeType === 1 ) {
|
|
return elem.innerHTML;
|
|
}
|
|
|
|
// See if we can take a shortcut and just use innerHTML
|
|
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
|
|
!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
|
|
|
|
value = jQuery.htmlPrefilter( value );
|
|
|
|
try {
|
|
for ( ; i < l; i++ ) {
|
|
elem = this[ i ] || {};
|
|
|
|
// Remove element nodes and prevent memory leaks
|
|
if ( elem.nodeType === 1 ) {
|
|
jQuery.cleanData( getAll( elem, false ) );
|
|
elem.innerHTML = value;
|
|
}
|
|
}
|
|
|
|
elem = 0;
|
|
|
|
// If using innerHTML throws an exception, use the fallback method
|
|
} catch ( e ) {}
|
|
}
|
|
|
|
if ( elem ) {
|
|
this.empty().append( value );
|
|
}
|
|
}, null, value, arguments.length );
|
|
},
|
|
|
|
replaceWith: function() {
|
|
var ignored = [];
|
|
|
|
// Make the changes, replacing each non-ignored context element with the new content
|
|
return domManip( this, arguments, function( elem ) {
|
|
var parent = this.parentNode;
|
|
|
|
if ( jQuery.inArray( this, ignored ) < 0 ) {
|
|
jQuery.cleanData( getAll( this ) );
|
|
if ( parent ) {
|
|
parent.replaceChild( elem, this );
|
|
}
|
|
}
|
|
|
|
// Force callback invocation
|
|
}, ignored );
|
|
}
|
|
} );
|
|
|
|
jQuery.each( {
|
|
appendTo: "append",
|
|
prependTo: "prepend",
|
|
insertBefore: "before",
|
|
insertAfter: "after",
|
|
replaceAll: "replaceWith"
|
|
}, function( name, original ) {
|
|
jQuery.fn[ name ] = function( selector ) {
|
|
var elems,
|
|
ret = [],
|
|
insert = jQuery( selector ),
|
|
last = insert.length - 1,
|
|
i = 0;
|
|
|
|
for ( ; i <= last; i++ ) {
|
|
elems = i === last ? this : this.clone( true );
|
|
jQuery( insert[ i ] )[ original ]( elems );
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// .get() because push.apply(_, arraylike) throws on ancient WebKit
|
|
push.apply( ret, elems.get() );
|
|
}
|
|
|
|
return this.pushStack( ret );
|
|
};
|
|
} );
|
|
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
|
|
|
var getStyles = function( elem ) {
|
|
|
|
// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
|
|
// IE throws on elements created in popups
|
|
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
|
var view = elem.ownerDocument.defaultView;
|
|
|
|
if ( !view || !view.opener ) {
|
|
view = window;
|
|
}
|
|
|
|
return view.getComputedStyle( elem );
|
|
};
|
|
|
|
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|
|
|
|
|
|
|
( function() {
|
|
|
|
// Executing both pixelPosition & boxSizingReliable tests require only one layout
|
|
// so they're executed at the same time to save the second computation.
|
|
function computeStyleTests() {
|
|
|
|
// This is a singleton, we need to execute it only once
|
|
if ( !div ) {
|
|
return;
|
|
}
|
|
|
|
container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
|
|
"margin-top:1px;padding:0;border:0";
|
|
div.style.cssText =
|
|
"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
|
|
"margin:auto;border:1px;padding:1px;" +
|
|
"width:60%;top:1%";
|
|
documentElement.appendChild( container ).appendChild( div );
|
|
|
|
var divStyle = window.getComputedStyle( div );
|
|
pixelPositionVal = divStyle.top !== "1%";
|
|
|
|
// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
|
|
reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
|
|
|
|
// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
|
|
// Some styles come back with percentage values, even though they shouldn't
|
|
div.style.right = "60%";
|
|
pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
|
|
|
|
// Support: IE 9 - 11 only
|
|
// Detect misreporting of content dimensions for box-sizing:border-box elements
|
|
boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
|
|
|
|
// Support: IE 9 only
|
|
// Detect overflow:scroll screwiness (gh-3699)
|
|
div.style.position = "absolute";
|
|
scrollboxSizeVal = div.offsetWidth === 36 || "absolute";
|
|
|
|
documentElement.removeChild( container );
|
|
|
|
// Nullify the div so it wouldn't be stored in the memory and
|
|
// it will also be a sign that checks already performed
|
|
div = null;
|
|
}
|
|
|
|
function roundPixelMeasures( measure ) {
|
|
return Math.round( parseFloat( measure ) );
|
|
}
|
|
|
|
var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
|
|
reliableMarginLeftVal,
|
|
container = document.createElement( "div" ),
|
|
div = document.createElement( "div" );
|
|
|
|
// Finish early in limited (non-browser) environments
|
|
if ( !div.style ) {
|
|
return;
|
|
}
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// Style of cloned element affects source element cloned (#8908)
|
|
div.style.backgroundClip = "content-box";
|
|
div.cloneNode( true ).style.backgroundClip = "";
|
|
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
|
|
|
jQuery.extend( support, {
|
|
boxSizingReliable: function() {
|
|
computeStyleTests();
|
|
return boxSizingReliableVal;
|
|
},
|
|
pixelBoxStyles: function() {
|
|
computeStyleTests();
|
|
return pixelBoxStylesVal;
|
|
},
|
|
pixelPosition: function() {
|
|
computeStyleTests();
|
|
return pixelPositionVal;
|
|
},
|
|
reliableMarginLeft: function() {
|
|
computeStyleTests();
|
|
return reliableMarginLeftVal;
|
|
},
|
|
scrollboxSize: function() {
|
|
computeStyleTests();
|
|
return scrollboxSizeVal;
|
|
}
|
|
} );
|
|
} )();
|
|
|
|
|
|
function curCSS( elem, name, computed ) {
|
|
var width, minWidth, maxWidth, ret,
|
|
|
|
// Support: Firefox 51+
|
|
// Retrieving style before computed somehow
|
|
// fixes an issue with getting wrong values
|
|
// on detached elements
|
|
style = elem.style;
|
|
|
|
computed = computed || getStyles( elem );
|
|
|
|
// getPropertyValue is needed for:
|
|
// .css('filter') (IE 9 only, #12537)
|
|
// .css('--customProperty) (#3144)
|
|
if ( computed ) {
|
|
ret = computed.getPropertyValue( name ) || computed[ name ];
|
|
|
|
if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
|
|
ret = jQuery.style( elem, name );
|
|
}
|
|
|
|
// A tribute to the "awesome hack by Dean Edwards"
|
|
// Android Browser returns percentage for some values,
|
|
// but width seems to be reliably pixels.
|
|
// This is against the CSSOM draft spec:
|
|
// https://drafts.csswg.org/cssom/#resolved-values
|
|
if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
|
|
|
|
// Remember the original values
|
|
width = style.width;
|
|
minWidth = style.minWidth;
|
|
maxWidth = style.maxWidth;
|
|
|
|
// Put in the new values to get a computed value out
|
|
style.minWidth = style.maxWidth = style.width = ret;
|
|
ret = computed.width;
|
|
|
|
// Revert the changed values
|
|
style.width = width;
|
|
style.minWidth = minWidth;
|
|
style.maxWidth = maxWidth;
|
|
}
|
|
}
|
|
|
|
return ret !== undefined ?
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// IE returns zIndex value as an integer.
|
|
ret + "" :
|
|
ret;
|
|
}
|
|
|
|
|
|
function addGetHookIf( conditionFn, hookFn ) {
|
|
|
|
// Define the hook, we'll check on the first run if it's really needed.
|
|
return {
|
|
get: function() {
|
|
if ( conditionFn() ) {
|
|
|
|
// Hook not needed (or it's not possible to use it due
|
|
// to missing dependency), remove it.
|
|
delete this.get;
|
|
return;
|
|
}
|
|
|
|
// Hook needed; redefine it so that the support test is not executed again.
|
|
return ( this.get = hookFn ).apply( this, arguments );
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
var
|
|
|
|
// Swappable if display is none or starts with table
|
|
// except "table", "table-cell", or "table-caption"
|
|
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
|
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
|
rcustomProp = /^--/,
|
|
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
|
cssNormalTransform = {
|
|
letterSpacing: "0",
|
|
fontWeight: "400"
|
|
},
|
|
|
|
cssPrefixes = [ "Webkit", "Moz", "ms" ],
|
|
emptyStyle = document.createElement( "div" ).style;
|
|
|
|
// Return a css property mapped to a potentially vendor prefixed property
|
|
function vendorPropName( name ) {
|
|
|
|
// Shortcut for names that are not vendor prefixed
|
|
if ( name in emptyStyle ) {
|
|
return name;
|
|
}
|
|
|
|
// Check for vendor prefixed names
|
|
var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
|
|
i = cssPrefixes.length;
|
|
|
|
while ( i-- ) {
|
|
name = cssPrefixes[ i ] + capName;
|
|
if ( name in emptyStyle ) {
|
|
return name;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return a property mapped along what jQuery.cssProps suggests or to
|
|
// a vendor prefixed property.
|
|
function finalPropName( name ) {
|
|
var ret = jQuery.cssProps[ name ];
|
|
if ( !ret ) {
|
|
ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function setPositiveNumber( elem, value, subtract ) {
|
|
|
|
// Any relative (+/-) values have already been
|
|
// normalized at this point
|
|
var matches = rcssNum.exec( value );
|
|
return matches ?
|
|
|
|
// Guard against undefined "subtract", e.g., when used as in cssHooks
|
|
Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
|
|
value;
|
|
}
|
|
|
|
function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
|
|
var i = dimension === "width" ? 1 : 0,
|
|
extra = 0,
|
|
delta = 0;
|
|
|
|
// Adjustment may not be necessary
|
|
if ( box === ( isBorderBox ? "border" : "content" ) ) {
|
|
return 0;
|
|
}
|
|
|
|
for ( ; i < 4; i += 2 ) {
|
|
|
|
// Both box models exclude margin
|
|
if ( box === "margin" ) {
|
|
delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
|
|
}
|
|
|
|
// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
|
|
if ( !isBorderBox ) {
|
|
|
|
// Add padding
|
|
delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
|
|
|
|
// For "border" or "margin", add border
|
|
if ( box !== "padding" ) {
|
|
delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
|
|
|
|
// But still keep track of it otherwise
|
|
} else {
|
|
extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
|
|
}
|
|
|
|
// If we get here with a border-box (content + padding + border), we're seeking "content" or
|
|
// "padding" or "margin"
|
|
} else {
|
|
|
|
// For "content", subtract padding
|
|
if ( box === "content" ) {
|
|
delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
|
|
}
|
|
|
|
// For "content" or "padding", subtract border
|
|
if ( box !== "margin" ) {
|
|
delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Account for positive content-box scroll gutter when requested by providing computedVal
|
|
if ( !isBorderBox && computedVal >= 0 ) {
|
|
|
|
// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
|
|
// Assuming integer scroll gutter, subtract the rest and round down
|
|
delta += Math.max( 0, Math.ceil(
|
|
elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
|
|
computedVal -
|
|
delta -
|
|
extra -
|
|
0.5
|
|
) );
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
function getWidthOrHeight( elem, dimension, extra ) {
|
|
|
|
// Start with computed style
|
|
var styles = getStyles( elem ),
|
|
val = curCSS( elem, dimension, styles ),
|
|
isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
|
|
valueIsBorderBox = isBorderBox;
|
|
|
|
// Support: Firefox <=54
|
|
// Return a confounding non-pixel value or feign ignorance, as appropriate.
|
|
if ( rnumnonpx.test( val ) ) {
|
|
if ( !extra ) {
|
|
return val;
|
|
}
|
|
val = "auto";
|
|
}
|
|
|
|
// Check for style in case a browser which returns unreliable values
|
|
// for getComputedStyle silently falls back to the reliable elem.style
|
|
valueIsBorderBox = valueIsBorderBox &&
|
|
( support.boxSizingReliable() || val === elem.style[ dimension ] );
|
|
|
|
// Fall back to offsetWidth/offsetHeight when value is "auto"
|
|
// This happens for inline elements with no explicit setting (gh-3571)
|
|
// Support: Android <=4.1 - 4.3 only
|
|
// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
|
|
if ( val === "auto" ||
|
|
!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) {
|
|
|
|
val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
|
|
|
|
// offsetWidth/offsetHeight provide border-box values
|
|
valueIsBorderBox = true;
|
|
}
|
|
|
|
// Normalize "" and auto
|
|
val = parseFloat( val ) || 0;
|
|
|
|
// Adjust for the element's box model
|
|
return ( val +
|
|
boxModelAdjustment(
|
|
elem,
|
|
dimension,
|
|
extra || ( isBorderBox ? "border" : "content" ),
|
|
valueIsBorderBox,
|
|
styles,
|
|
|
|
// Provide the current computed size to request scroll gutter calculation (gh-3589)
|
|
val
|
|
)
|
|
) + "px";
|
|
}
|
|
|
|
jQuery.extend( {
|
|
|
|
// Add in style property hooks for overriding the default
|
|
// behavior of getting and setting a style property
|
|
cssHooks: {
|
|
opacity: {
|
|
get: function( elem, computed ) {
|
|
if ( computed ) {
|
|
|
|
// We should always get a number back from opacity
|
|
var ret = curCSS( elem, "opacity" );
|
|
return ret === "" ? "1" : ret;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Don't automatically add "px" to these possibly-unitless properties
|
|
cssNumber: {
|
|
"animationIterationCount": true,
|
|
"columnCount": true,
|
|
"fillOpacity": true,
|
|
"flexGrow": true,
|
|
"flexShrink": true,
|
|
"fontWeight": true,
|
|
"lineHeight": true,
|
|
"opacity": true,
|
|
"order": true,
|
|
"orphans": true,
|
|
"widows": true,
|
|
"zIndex": true,
|
|
"zoom": true
|
|
},
|
|
|
|
// Add in properties whose names you wish to fix before
|
|
// setting or getting the value
|
|
cssProps: {},
|
|
|
|
// Get and set the style property on a DOM Node
|
|
style: function( elem, name, value, extra ) {
|
|
|
|
// Don't set styles on text and comment nodes
|
|
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
|
|
return;
|
|
}
|
|
|
|
// Make sure that we're working with the right name
|
|
var ret, type, hooks,
|
|
origName = camelCase( name ),
|
|
isCustomProp = rcustomProp.test( name ),
|
|
style = elem.style;
|
|
|
|
// Make sure that we're working with the right name. We don't
|
|
// want to query the value if it is a CSS custom property
|
|
// since they are user-defined.
|
|
if ( !isCustomProp ) {
|
|
name = finalPropName( origName );
|
|
}
|
|
|
|
// Gets hook for the prefixed version, then unprefixed version
|
|
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
|
|
|
|
// Check if we're setting a value
|
|
if ( value !== undefined ) {
|
|
type = typeof value;
|
|
|
|
// Convert "+=" or "-=" to relative numbers (#7345)
|
|
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
|
|
value = adjustCSS( elem, name, ret );
|
|
|
|
// Fixes bug #9237
|
|
type = "number";
|
|
}
|
|
|
|
// Make sure that null and NaN values aren't set (#7116)
|
|
if ( value == null || value !== value ) {
|
|
return;
|
|
}
|
|
|
|
// If a number was passed in, add the unit (except for certain CSS properties)
|
|
if ( type === "number" ) {
|
|
value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
|
|
}
|
|
|
|
// background-* props affect original clone's values
|
|
if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
|
|
style[ name ] = "inherit";
|
|
}
|
|
|
|
// If a hook was provided, use that value, otherwise just set the specified value
|
|
if ( !hooks || !( "set" in hooks ) ||
|
|
( value = hooks.set( elem, value, extra ) ) !== undefined ) {
|
|
|
|
if ( isCustomProp ) {
|
|
style.setProperty( name, value );
|
|
} else {
|
|
style[ name ] = value;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// If a hook was provided get the non-computed value from there
|
|
if ( hooks && "get" in hooks &&
|
|
( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Otherwise just get the value from the style object
|
|
return style[ name ];
|
|
}
|
|
},
|
|
|
|
css: function( elem, name, extra, styles ) {
|
|
var val, num, hooks,
|
|
origName = camelCase( name ),
|
|
isCustomProp = rcustomProp.test( name );
|
|
|
|
// Make sure that we're working with the right name. We don't
|
|
// want to modify the value if it is a CSS custom property
|
|
// since they are user-defined.
|
|
if ( !isCustomProp ) {
|
|
name = finalPropName( origName );
|
|
}
|
|
|
|
// Try prefixed name followed by the unprefixed name
|
|
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
|
|
|
|
// If a hook was provided get the computed value from there
|
|
if ( hooks && "get" in hooks ) {
|
|
val = hooks.get( elem, true, extra );
|
|
}
|
|
|
|
// Otherwise, if a way to get the computed value exists, use that
|
|
if ( val === undefined ) {
|
|
val = curCSS( elem, name, styles );
|
|
}
|
|
|
|
// Convert "normal" to computed value
|
|
if ( val === "normal" && name in cssNormalTransform ) {
|
|
val = cssNormalTransform[ name ];
|
|
}
|
|
|
|
// Make numeric if forced or a qualifier was provided and val looks numeric
|
|
if ( extra === "" || extra ) {
|
|
num = parseFloat( val );
|
|
return extra === true || isFinite( num ) ? num || 0 : val;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
} );
|
|
|
|
jQuery.each( [ "height", "width" ], function( i, dimension ) {
|
|
jQuery.cssHooks[ dimension ] = {
|
|
get: function( elem, computed, extra ) {
|
|
if ( computed ) {
|
|
|
|
// Certain elements can have dimension info if we invisibly show them
|
|
// but it must have a current display style that would benefit
|
|
return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
|
|
|
|
// Support: Safari 8+
|
|
// Table columns in Safari have non-zero offsetWidth & zero
|
|
// getBoundingClientRect().width unless display is changed.
|
|
// Support: IE <=11 only
|
|
// Running getBoundingClientRect on a disconnected node
|
|
// in IE throws an error.
|
|
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
|
|
swap( elem, cssShow, function() {
|
|
return getWidthOrHeight( elem, dimension, extra );
|
|
} ) :
|
|
getWidthOrHeight( elem, dimension, extra );
|
|
}
|
|
},
|
|
|
|
set: function( elem, value, extra ) {
|
|
var matches,
|
|
styles = getStyles( elem ),
|
|
isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
|
|
subtract = extra && boxModelAdjustment(
|
|
elem,
|
|
dimension,
|
|
extra,
|
|
isBorderBox,
|
|
styles
|
|
);
|
|
|
|
// Account for unreliable border-box dimensions by comparing offset* to computed and
|
|
// faking a content-box to get border and padding (gh-3699)
|
|
if ( isBorderBox && support.scrollboxSize() === styles.position ) {
|
|
subtract -= Math.ceil(
|
|
elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
|
|
parseFloat( styles[ dimension ] ) -
|
|
boxModelAdjustment( elem, dimension, "border", false, styles ) -
|
|
0.5
|
|
);
|
|
}
|
|
|
|
// Convert to pixels if value adjustment is needed
|
|
if ( subtract && ( matches = rcssNum.exec( value ) ) &&
|
|
( matches[ 3 ] || "px" ) !== "px" ) {
|
|
|
|
elem.style[ dimension ] = value;
|
|
value = jQuery.css( elem, dimension );
|
|
}
|
|
|
|
return setPositiveNumber( elem, value, subtract );
|
|
}
|
|
};
|
|
} );
|
|
|
|
jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
|
|
function( elem, computed ) {
|
|
if ( computed ) {
|
|
return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
|
|
elem.getBoundingClientRect().left -
|
|
swap( elem, { marginLeft: 0 }, function() {
|
|
return elem.getBoundingClientRect().left;
|
|
} )
|
|
) + "px";
|
|
}
|
|
}
|
|
);
|
|
|
|
// These hooks are used by animate to expand properties
|
|
jQuery.each( {
|
|
margin: "",
|
|
padding: "",
|
|
border: "Width"
|
|
}, function( prefix, suffix ) {
|
|
jQuery.cssHooks[ prefix + suffix ] = {
|
|
expand: function( value ) {
|
|
var i = 0,
|
|
expanded = {},
|
|
|
|
// Assumes a single number if not a string
|
|
parts = typeof value === "string" ? value.split( " " ) : [ value ];
|
|
|
|
for ( ; i < 4; i++ ) {
|
|
expanded[ prefix + cssExpand[ i ] + suffix ] =
|
|
parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
|
|
}
|
|
|
|
return expanded;
|
|
}
|
|
};
|
|
|
|
if ( prefix !== "margin" ) {
|
|
jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
css: function( name, value ) {
|
|
return access( this, function( elem, name, value ) {
|
|
var styles, len,
|
|
map = {},
|
|
i = 0;
|
|
|
|
if ( Array.isArray( name ) ) {
|
|
styles = getStyles( elem );
|
|
len = name.length;
|
|
|
|
for ( ; i < len; i++ ) {
|
|
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
return value !== undefined ?
|
|
jQuery.style( elem, name, value ) :
|
|
jQuery.css( elem, name );
|
|
}, name, value, arguments.length > 1 );
|
|
}
|
|
} );
|
|
|
|
|
|
function Tween( elem, options, prop, end, easing ) {
|
|
return new Tween.prototype.init( elem, options, prop, end, easing );
|
|
}
|
|
jQuery.Tween = Tween;
|
|
|
|
Tween.prototype = {
|
|
constructor: Tween,
|
|
init: function( elem, options, prop, end, easing, unit ) {
|
|
this.elem = elem;
|
|
this.prop = prop;
|
|
this.easing = easing || jQuery.easing._default;
|
|
this.options = options;
|
|
this.start = this.now = this.cur();
|
|
this.end = end;
|
|
this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
|
|
},
|
|
cur: function() {
|
|
var hooks = Tween.propHooks[ this.prop ];
|
|
|
|
return hooks && hooks.get ?
|
|
hooks.get( this ) :
|
|
Tween.propHooks._default.get( this );
|
|
},
|
|
run: function( percent ) {
|
|
var eased,
|
|
hooks = Tween.propHooks[ this.prop ];
|
|
|
|
if ( this.options.duration ) {
|
|
this.pos = eased = jQuery.easing[ this.easing ](
|
|
percent, this.options.duration * percent, 0, 1, this.options.duration
|
|
);
|
|
} else {
|
|
this.pos = eased = percent;
|
|
}
|
|
this.now = ( this.end - this.start ) * eased + this.start;
|
|
|
|
if ( this.options.step ) {
|
|
this.options.step.call( this.elem, this.now, this );
|
|
}
|
|
|
|
if ( hooks && hooks.set ) {
|
|
hooks.set( this );
|
|
} else {
|
|
Tween.propHooks._default.set( this );
|
|
}
|
|
return this;
|
|
}
|
|
};
|
|
|
|
Tween.prototype.init.prototype = Tween.prototype;
|
|
|
|
Tween.propHooks = {
|
|
_default: {
|
|
get: function( tween ) {
|
|
var result;
|
|
|
|
// Use a property on the element directly when it is not a DOM element,
|
|
// or when there is no matching style property that exists.
|
|
if ( tween.elem.nodeType !== 1 ||
|
|
tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
|
|
return tween.elem[ tween.prop ];
|
|
}
|
|
|
|
// Passing an empty string as a 3rd parameter to .css will automatically
|
|
// attempt a parseFloat and fallback to a string if the parse fails.
|
|
// Simple values such as "10px" are parsed to Float;
|
|
// complex values such as "rotate(1rad)" are returned as-is.
|
|
result = jQuery.css( tween.elem, tween.prop, "" );
|
|
|
|
// Empty strings, null, undefined and "auto" are converted to 0.
|
|
return !result || result === "auto" ? 0 : result;
|
|
},
|
|
set: function( tween ) {
|
|
|
|
// Use step hook for back compat.
|
|
// Use cssHook if its there.
|
|
// Use .style if available and use plain properties where available.
|
|
if ( jQuery.fx.step[ tween.prop ] ) {
|
|
jQuery.fx.step[ tween.prop ]( tween );
|
|
} else if ( tween.elem.nodeType === 1 &&
|
|
( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
|
|
jQuery.cssHooks[ tween.prop ] ) ) {
|
|
jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
|
|
} else {
|
|
tween.elem[ tween.prop ] = tween.now;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Support: IE <=9 only
|
|
// Panic based approach to setting things on disconnected nodes
|
|
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
|
|
set: function( tween ) {
|
|
if ( tween.elem.nodeType && tween.elem.parentNode ) {
|
|
tween.elem[ tween.prop ] = tween.now;
|
|
}
|
|
}
|
|
};
|
|
|
|
jQuery.easing = {
|
|
linear: function( p ) {
|
|
return p;
|
|
},
|
|
swing: function( p ) {
|
|
return 0.5 - Math.cos( p * Math.PI ) / 2;
|
|
},
|
|
_default: "swing"
|
|
};
|
|
|
|
jQuery.fx = Tween.prototype.init;
|
|
|
|
// Back compat <1.8 extension point
|
|
jQuery.fx.step = {};
|
|
|
|
|
|
|
|
|
|
var
|
|
fxNow, inProgress,
|
|
rfxtypes = /^(?:toggle|show|hide)$/,
|
|
rrun = /queueHooks$/;
|
|
|
|
function schedule() {
|
|
if ( inProgress ) {
|
|
if ( document.hidden === false && window.requestAnimationFrame ) {
|
|
window.requestAnimationFrame( schedule );
|
|
} else {
|
|
window.setTimeout( schedule, jQuery.fx.interval );
|
|
}
|
|
|
|
jQuery.fx.tick();
|
|
}
|
|
}
|
|
|
|
// Animations created synchronously will run synchronously
|
|
function createFxNow() {
|
|
window.setTimeout( function() {
|
|
fxNow = undefined;
|
|
} );
|
|
return ( fxNow = Date.now() );
|
|
}
|
|
|
|
// Generate parameters to create a standard animation
|
|
function genFx( type, includeWidth ) {
|
|
var which,
|
|
i = 0,
|
|
attrs = { height: type };
|
|
|
|
// If we include width, step value is 1 to do all cssExpand values,
|
|
// otherwise step value is 2 to skip over Left and Right
|
|
includeWidth = includeWidth ? 1 : 0;
|
|
for ( ; i < 4; i += 2 - includeWidth ) {
|
|
which = cssExpand[ i ];
|
|
attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
|
|
}
|
|
|
|
if ( includeWidth ) {
|
|
attrs.opacity = attrs.width = type;
|
|
}
|
|
|
|
return attrs;
|
|
}
|
|
|
|
function createTween( value, prop, animation ) {
|
|
var tween,
|
|
collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
|
|
index = 0,
|
|
length = collection.length;
|
|
for ( ; index < length; index++ ) {
|
|
if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
|
|
|
|
// We're done with this property
|
|
return tween;
|
|
}
|
|
}
|
|
}
|
|
|
|
function defaultPrefilter( elem, props, opts ) {
|
|
var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
|
|
isBox = "width" in props || "height" in props,
|
|
anim = this,
|
|
orig = {},
|
|
style = elem.style,
|
|
hidden = elem.nodeType && isHiddenWithinTree( elem ),
|
|
dataShow = dataPriv.get( elem, "fxshow" );
|
|
|
|
// Queue-skipping animations hijack the fx hooks
|
|
if ( !opts.queue ) {
|
|
hooks = jQuery._queueHooks( elem, "fx" );
|
|
if ( hooks.unqueued == null ) {
|
|
hooks.unqueued = 0;
|
|
oldfire = hooks.empty.fire;
|
|
hooks.empty.fire = function() {
|
|
if ( !hooks.unqueued ) {
|
|
oldfire();
|
|
}
|
|
};
|
|
}
|
|
hooks.unqueued++;
|
|
|
|
anim.always( function() {
|
|
|
|
// Ensure the complete handler is called before this completes
|
|
anim.always( function() {
|
|
hooks.unqueued--;
|
|
if ( !jQuery.queue( elem, "fx" ).length ) {
|
|
hooks.empty.fire();
|
|
}
|
|
} );
|
|
} );
|
|
}
|
|
|
|
// Detect show/hide animations
|
|
for ( prop in props ) {
|
|
value = props[ prop ];
|
|
if ( rfxtypes.test( value ) ) {
|
|
delete props[ prop ];
|
|
toggle = toggle || value === "toggle";
|
|
if ( value === ( hidden ? "hide" : "show" ) ) {
|
|
|
|
// Pretend to be hidden if this is a "show" and
|
|
// there is still data from a stopped show/hide
|
|
if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
|
|
hidden = true;
|
|
|
|
// Ignore all other no-op show/hide data
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
|
|
}
|
|
}
|
|
|
|
// Bail out if this is a no-op like .hide().hide()
|
|
propTween = !jQuery.isEmptyObject( props );
|
|
if ( !propTween && jQuery.isEmptyObject( orig ) ) {
|
|
return;
|
|
}
|
|
|
|
// Restrict "overflow" and "display" styles during box animations
|
|
if ( isBox && elem.nodeType === 1 ) {
|
|
|
|
// Support: IE <=9 - 11, Edge 12 - 15
|
|
// Record all 3 overflow attributes because IE does not infer the shorthand
|
|
// from identically-valued overflowX and overflowY and Edge just mirrors
|
|
// the overflowX value there.
|
|
opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
|
|
|
|
// Identify a display type, preferring old show/hide data over the CSS cascade
|
|
restoreDisplay = dataShow && dataShow.display;
|
|
if ( restoreDisplay == null ) {
|
|
restoreDisplay = dataPriv.get( elem, "display" );
|
|
}
|
|
display = jQuery.css( elem, "display" );
|
|
if ( display === "none" ) {
|
|
if ( restoreDisplay ) {
|
|
display = restoreDisplay;
|
|
} else {
|
|
|
|
// Get nonempty value(s) by temporarily forcing visibility
|
|
showHide( [ elem ], true );
|
|
restoreDisplay = elem.style.display || restoreDisplay;
|
|
display = jQuery.css( elem, "display" );
|
|
showHide( [ elem ] );
|
|
}
|
|
}
|
|
|
|
// Animate inline elements as inline-block
|
|
if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
|
|
if ( jQuery.css( elem, "float" ) === "none" ) {
|
|
|
|
// Restore the original display value at the end of pure show/hide animations
|
|
if ( !propTween ) {
|
|
anim.done( function() {
|
|
style.display = restoreDisplay;
|
|
} );
|
|
if ( restoreDisplay == null ) {
|
|
display = style.display;
|
|
restoreDisplay = display === "none" ? "" : display;
|
|
}
|
|
}
|
|
style.display = "inline-block";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( opts.overflow ) {
|
|
style.overflow = "hidden";
|
|
anim.always( function() {
|
|
style.overflow = opts.overflow[ 0 ];
|
|
style.overflowX = opts.overflow[ 1 ];
|
|
style.overflowY = opts.overflow[ 2 ];
|
|
} );
|
|
}
|
|
|
|
// Implement show/hide animations
|
|
propTween = false;
|
|
for ( prop in orig ) {
|
|
|
|
// General show/hide setup for this element animation
|
|
if ( !propTween ) {
|
|
if ( dataShow ) {
|
|
if ( "hidden" in dataShow ) {
|
|
hidden = dataShow.hidden;
|
|
}
|
|
} else {
|
|
dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
|
|
}
|
|
|
|
// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
|
|
if ( toggle ) {
|
|
dataShow.hidden = !hidden;
|
|
}
|
|
|
|
// Show elements before animating them
|
|
if ( hidden ) {
|
|
showHide( [ elem ], true );
|
|
}
|
|
|
|
/* eslint-disable no-loop-func */
|
|
|
|
anim.done( function() {
|
|
|
|
/* eslint-enable no-loop-func */
|
|
|
|
// The final step of a "hide" animation is actually hiding the element
|
|
if ( !hidden ) {
|
|
showHide( [ elem ] );
|
|
}
|
|
dataPriv.remove( elem, "fxshow" );
|
|
for ( prop in orig ) {
|
|
jQuery.style( elem, prop, orig[ prop ] );
|
|
}
|
|
} );
|
|
}
|
|
|
|
// Per-property setup
|
|
propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
|
|
if ( !( prop in dataShow ) ) {
|
|
dataShow[ prop ] = propTween.start;
|
|
if ( hidden ) {
|
|
propTween.end = propTween.start;
|
|
propTween.start = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function propFilter( props, specialEasing ) {
|
|
var index, name, easing, value, hooks;
|
|
|
|
// camelCase, specialEasing and expand cssHook pass
|
|
for ( index in props ) {
|
|
name = camelCase( index );
|
|
easing = specialEasing[ name ];
|
|
value = props[ index ];
|
|
if ( Array.isArray( value ) ) {
|
|
easing = value[ 1 ];
|
|
value = props[ index ] = value[ 0 ];
|
|
}
|
|
|
|
if ( index !== name ) {
|
|
props[ name ] = value;
|
|
delete props[ index ];
|
|
}
|
|
|
|
hooks = jQuery.cssHooks[ name ];
|
|
if ( hooks && "expand" in hooks ) {
|
|
value = hooks.expand( value );
|
|
delete props[ name ];
|
|
|
|
// Not quite $.extend, this won't overwrite existing keys.
|
|
// Reusing 'index' because we have the correct "name"
|
|
for ( index in value ) {
|
|
if ( !( index in props ) ) {
|
|
props[ index ] = value[ index ];
|
|
specialEasing[ index ] = easing;
|
|
}
|
|
}
|
|
} else {
|
|
specialEasing[ name ] = easing;
|
|
}
|
|
}
|
|
}
|
|
|
|
function Animation( elem, properties, options ) {
|
|
var result,
|
|
stopped,
|
|
index = 0,
|
|
length = Animation.prefilters.length,
|
|
deferred = jQuery.Deferred().always( function() {
|
|
|
|
// Don't match elem in the :animated selector
|
|
delete tick.elem;
|
|
} ),
|
|
tick = function() {
|
|
if ( stopped ) {
|
|
return false;
|
|
}
|
|
var currentTime = fxNow || createFxNow(),
|
|
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
|
|
|
|
// Support: Android 2.3 only
|
|
// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
|
|
temp = remaining / animation.duration || 0,
|
|
percent = 1 - temp,
|
|
index = 0,
|
|
length = animation.tweens.length;
|
|
|
|
for ( ; index < length; index++ ) {
|
|
animation.tweens[ index ].run( percent );
|
|
}
|
|
|
|
deferred.notifyWith( elem, [ animation, percent, remaining ] );
|
|
|
|
// If there's more to do, yield
|
|
if ( percent < 1 && length ) {
|
|
return remaining;
|
|
}
|
|
|
|
// If this was an empty animation, synthesize a final progress notification
|
|
if ( !length ) {
|
|
deferred.notifyWith( elem, [ animation, 1, 0 ] );
|
|
}
|
|
|
|
// Resolve the animation and report its conclusion
|
|
deferred.resolveWith( elem, [ animation ] );
|
|
return false;
|
|
},
|
|
animation = deferred.promise( {
|
|
elem: elem,
|
|
props: jQuery.extend( {}, properties ),
|
|
opts: jQuery.extend( true, {
|
|
specialEasing: {},
|
|
easing: jQuery.easing._default
|
|
}, options ),
|
|
originalProperties: properties,
|
|
originalOptions: options,
|
|
startTime: fxNow || createFxNow(),
|
|
duration: options.duration,
|
|
tweens: [],
|
|
createTween: function( prop, end ) {
|
|
var tween = jQuery.Tween( elem, animation.opts, prop, end,
|
|
animation.opts.specialEasing[ prop ] || animation.opts.easing );
|
|
animation.tweens.push( tween );
|
|
return tween;
|
|
},
|
|
stop: function( gotoEnd ) {
|
|
var index = 0,
|
|
|
|
// If we are going to the end, we want to run all the tweens
|
|
// otherwise we skip this part
|
|
length = gotoEnd ? animation.tweens.length : 0;
|
|
if ( stopped ) {
|
|
return this;
|
|
}
|
|
stopped = true;
|
|
for ( ; index < length; index++ ) {
|
|
animation.tweens[ index ].run( 1 );
|
|
}
|
|
|
|
// Resolve when we played the last frame; otherwise, reject
|
|
if ( gotoEnd ) {
|
|
deferred.notifyWith( elem, [ animation, 1, 0 ] );
|
|
deferred.resolveWith( elem, [ animation, gotoEnd ] );
|
|
} else {
|
|
deferred.rejectWith( elem, [ animation, gotoEnd ] );
|
|
}
|
|
return this;
|
|
}
|
|
} ),
|
|
props = animation.props;
|
|
|
|
propFilter( props, animation.opts.specialEasing );
|
|
|
|
for ( ; index < length; index++ ) {
|
|
result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
|
|
if ( result ) {
|
|
if ( isFunction( result.stop ) ) {
|
|
jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
|
|
result.stop.bind( result );
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
jQuery.map( props, createTween, animation );
|
|
|
|
if ( isFunction( animation.opts.start ) ) {
|
|
animation.opts.start.call( elem, animation );
|
|
}
|
|
|
|
// Attach callbacks from options
|
|
animation
|
|
.progress( animation.opts.progress )
|
|
.done( animation.opts.done, animation.opts.complete )
|
|
.fail( animation.opts.fail )
|
|
.always( animation.opts.always );
|
|
|
|
jQuery.fx.timer(
|
|
jQuery.extend( tick, {
|
|
elem: elem,
|
|
anim: animation,
|
|
queue: animation.opts.queue
|
|
} )
|
|
);
|
|
|
|
return animation;
|
|
}
|
|
|
|
jQuery.Animation = jQuery.extend( Animation, {
|
|
|
|
tweeners: {
|
|
"*": [ function( prop, value ) {
|
|
var tween = this.createTween( prop, value );
|
|
adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
|
|
return tween;
|
|
} ]
|
|
},
|
|
|
|
tweener: function( props, callback ) {
|
|
if ( isFunction( props ) ) {
|
|
callback = props;
|
|
props = [ "*" ];
|
|
} else {
|
|
props = props.match( rnothtmlwhite );
|
|
}
|
|
|
|
var prop,
|
|
index = 0,
|
|
length = props.length;
|
|
|
|
for ( ; index < length; index++ ) {
|
|
prop = props[ index ];
|
|
Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
|
|
Animation.tweeners[ prop ].unshift( callback );
|
|
}
|
|
},
|
|
|
|
prefilters: [ defaultPrefilter ],
|
|
|
|
prefilter: function( callback, prepend ) {
|
|
if ( prepend ) {
|
|
Animation.prefilters.unshift( callback );
|
|
} else {
|
|
Animation.prefilters.push( callback );
|
|
}
|
|
}
|
|
} );
|
|
|
|
jQuery.speed = function( speed, easing, fn ) {
|
|
var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
|
|
complete: fn || !fn && easing ||
|
|
isFunction( speed ) && speed,
|
|
duration: speed,
|
|
easing: fn && easing || easing && !isFunction( easing ) && easing
|
|
};
|
|
|
|
// Go to the end state if fx are off
|
|
if ( jQuery.fx.off ) {
|
|
opt.duration = 0;
|
|
|
|
} else {
|
|
if ( typeof opt.duration !== "number" ) {
|
|
if ( opt.duration in jQuery.fx.speeds ) {
|
|
opt.duration = jQuery.fx.speeds[ opt.duration ];
|
|
|
|
} else {
|
|
opt.duration = jQuery.fx.speeds._default;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normalize opt.queue - true/undefined/null -> "fx"
|
|
if ( opt.queue == null || opt.queue === true ) {
|
|
opt.queue = "fx";
|
|
}
|
|
|
|
// Queueing
|
|
opt.old = opt.complete;
|
|
|
|
opt.complete = function() {
|
|
if ( isFunction( opt.old ) ) {
|
|
opt.old.call( this );
|
|
}
|
|
|
|
if ( opt.queue ) {
|
|
jQuery.dequeue( this, opt.queue );
|
|
}
|
|
};
|
|
|
|
return opt;
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
fadeTo: function( speed, to, easing, callback ) {
|
|
|
|
// Show any hidden elements after setting opacity to 0
|
|
return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
|
|
|
|
// Animate to the value specified
|
|
.end().animate( { opacity: to }, speed, easing, callback );
|
|
},
|
|
animate: function( prop, speed, easing, callback ) {
|
|
var empty = jQuery.isEmptyObject( prop ),
|
|
optall = jQuery.speed( speed, easing, callback ),
|
|
doAnimation = function() {
|
|
|
|
// Operate on a copy of prop so per-property easing won't be lost
|
|
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
|
|
|
|
// Empty animations, or finishing resolves immediately
|
|
if ( empty || dataPriv.get( this, "finish" ) ) {
|
|
anim.stop( true );
|
|
}
|
|
};
|
|
doAnimation.finish = doAnimation;
|
|
|
|
return empty || optall.queue === false ?
|
|
this.each( doAnimation ) :
|
|
this.queue( optall.queue, doAnimation );
|
|
},
|
|
stop: function( type, clearQueue, gotoEnd ) {
|
|
var stopQueue = function( hooks ) {
|
|
var stop = hooks.stop;
|
|
delete hooks.stop;
|
|
stop( gotoEnd );
|
|
};
|
|
|
|
if ( typeof type !== "string" ) {
|
|
gotoEnd = clearQueue;
|
|
clearQueue = type;
|
|
type = undefined;
|
|
}
|
|
if ( clearQueue && type !== false ) {
|
|
this.queue( type || "fx", [] );
|
|
}
|
|
|
|
return this.each( function() {
|
|
var dequeue = true,
|
|
index = type != null && type + "queueHooks",
|
|
timers = jQuery.timers,
|
|
data = dataPriv.get( this );
|
|
|
|
if ( index ) {
|
|
if ( data[ index ] && data[ index ].stop ) {
|
|
stopQueue( data[ index ] );
|
|
}
|
|
} else {
|
|
for ( index in data ) {
|
|
if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
|
|
stopQueue( data[ index ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( index = timers.length; index--; ) {
|
|
if ( timers[ index ].elem === this &&
|
|
( type == null || timers[ index ].queue === type ) ) {
|
|
|
|
timers[ index ].anim.stop( gotoEnd );
|
|
dequeue = false;
|
|
timers.splice( index, 1 );
|
|
}
|
|
}
|
|
|
|
// Start the next in the queue if the last step wasn't forced.
|
|
// Timers currently will call their complete callbacks, which
|
|
// will dequeue but only if they were gotoEnd.
|
|
if ( dequeue || !gotoEnd ) {
|
|
jQuery.dequeue( this, type );
|
|
}
|
|
} );
|
|
},
|
|
finish: function( type ) {
|
|
if ( type !== false ) {
|
|
type = type || "fx";
|
|
}
|
|
return this.each( function() {
|
|
var index,
|
|
data = dataPriv.get( this ),
|
|
queue = data[ type + "queue" ],
|
|
hooks = data[ type + "queueHooks" ],
|
|
timers = jQuery.timers,
|
|
length = queue ? queue.length : 0;
|
|
|
|
// Enable finishing flag on private data
|
|
data.finish = true;
|
|
|
|
// Empty the queue first
|
|
jQuery.queue( this, type, [] );
|
|
|
|
if ( hooks && hooks.stop ) {
|
|
hooks.stop.call( this, true );
|
|
}
|
|
|
|
// Look for any active animations, and finish them
|
|
for ( index = timers.length; index--; ) {
|
|
if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
|
|
timers[ index ].anim.stop( true );
|
|
timers.splice( index, 1 );
|
|
}
|
|
}
|
|
|
|
// Look for any animations in the old queue and finish them
|
|
for ( index = 0; index < length; index++ ) {
|
|
if ( queue[ index ] && queue[ index ].finish ) {
|
|
queue[ index ].finish.call( this );
|
|
}
|
|
}
|
|
|
|
// Turn off finishing flag
|
|
delete data.finish;
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
|
|
var cssFn = jQuery.fn[ name ];
|
|
jQuery.fn[ name ] = function( speed, easing, callback ) {
|
|
return speed == null || typeof speed === "boolean" ?
|
|
cssFn.apply( this, arguments ) :
|
|
this.animate( genFx( name, true ), speed, easing, callback );
|
|
};
|
|
} );
|
|
|
|
// Generate shortcuts for custom animations
|
|
jQuery.each( {
|
|
slideDown: genFx( "show" ),
|
|
slideUp: genFx( "hide" ),
|
|
slideToggle: genFx( "toggle" ),
|
|
fadeIn: { opacity: "show" },
|
|
fadeOut: { opacity: "hide" },
|
|
fadeToggle: { opacity: "toggle" }
|
|
}, function( name, props ) {
|
|
jQuery.fn[ name ] = function( speed, easing, callback ) {
|
|
return this.animate( props, speed, easing, callback );
|
|
};
|
|
} );
|
|
|
|
jQuery.timers = [];
|
|
jQuery.fx.tick = function() {
|
|
var timer,
|
|
i = 0,
|
|
timers = jQuery.timers;
|
|
|
|
fxNow = Date.now();
|
|
|
|
for ( ; i < timers.length; i++ ) {
|
|
timer = timers[ i ];
|
|
|
|
// Run the timer and safely remove it when done (allowing for external removal)
|
|
if ( !timer() && timers[ i ] === timer ) {
|
|
timers.splice( i--, 1 );
|
|
}
|
|
}
|
|
|
|
if ( !timers.length ) {
|
|
jQuery.fx.stop();
|
|
}
|
|
fxNow = undefined;
|
|
};
|
|
|
|
jQuery.fx.timer = function( timer ) {
|
|
jQuery.timers.push( timer );
|
|
jQuery.fx.start();
|
|
};
|
|
|
|
jQuery.fx.interval = 13;
|
|
jQuery.fx.start = function() {
|
|
if ( inProgress ) {
|
|
return;
|
|
}
|
|
|
|
inProgress = true;
|
|
schedule();
|
|
};
|
|
|
|
jQuery.fx.stop = function() {
|
|
inProgress = null;
|
|
};
|
|
|
|
jQuery.fx.speeds = {
|
|
slow: 600,
|
|
fast: 200,
|
|
|
|
// Default speed
|
|
_default: 400
|
|
};
|
|
|
|
|
|
// Based off of the plugin by Clint Helfers, with permission.
|
|
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
|
|
jQuery.fn.delay = function( time, type ) {
|
|
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
|
type = type || "fx";
|
|
|
|
return this.queue( type, function( next, hooks ) {
|
|
var timeout = window.setTimeout( next, time );
|
|
hooks.stop = function() {
|
|
window.clearTimeout( timeout );
|
|
};
|
|
} );
|
|
};
|
|
|
|
|
|
( function() {
|
|
var input = document.createElement( "input" ),
|
|
select = document.createElement( "select" ),
|
|
opt = select.appendChild( document.createElement( "option" ) );
|
|
|
|
input.type = "checkbox";
|
|
|
|
// Support: Android <=4.3 only
|
|
// Default value for a checkbox should be "on"
|
|
support.checkOn = input.value !== "";
|
|
|
|
// Support: IE <=11 only
|
|
// Must access selectedIndex to make default options select
|
|
support.optSelected = opt.selected;
|
|
|
|
// Support: IE <=11 only
|
|
// An input loses its value after becoming a radio
|
|
input = document.createElement( "input" );
|
|
input.value = "t";
|
|
input.type = "radio";
|
|
support.radioValue = input.value === "t";
|
|
} )();
|
|
|
|
|
|
var boolHook,
|
|
attrHandle = jQuery.expr.attrHandle;
|
|
|
|
jQuery.fn.extend( {
|
|
attr: function( name, value ) {
|
|
return access( this, jQuery.attr, name, value, arguments.length > 1 );
|
|
},
|
|
|
|
removeAttr: function( name ) {
|
|
return this.each( function() {
|
|
jQuery.removeAttr( this, name );
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.extend( {
|
|
attr: function( elem, name, value ) {
|
|
var ret, hooks,
|
|
nType = elem.nodeType;
|
|
|
|
// Don't get/set attributes on text, comment and attribute nodes
|
|
if ( nType === 3 || nType === 8 || nType === 2 ) {
|
|
return;
|
|
}
|
|
|
|
// Fallback to prop when attributes are not supported
|
|
if ( typeof elem.getAttribute === "undefined" ) {
|
|
return jQuery.prop( elem, name, value );
|
|
}
|
|
|
|
// Attribute hooks are determined by the lowercase version
|
|
// Grab necessary hook if one is defined
|
|
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
|
|
hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
|
|
( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
|
|
}
|
|
|
|
if ( value !== undefined ) {
|
|
if ( value === null ) {
|
|
jQuery.removeAttr( elem, name );
|
|
return;
|
|
}
|
|
|
|
if ( hooks && "set" in hooks &&
|
|
( ret = hooks.set( elem, value, name ) ) !== undefined ) {
|
|
return ret;
|
|
}
|
|
|
|
elem.setAttribute( name, value + "" );
|
|
return value;
|
|
}
|
|
|
|
if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
|
|
return ret;
|
|
}
|
|
|
|
ret = jQuery.find.attr( elem, name );
|
|
|
|
// Non-existent attributes return null, we normalize to undefined
|
|
return ret == null ? undefined : ret;
|
|
},
|
|
|
|
attrHooks: {
|
|
type: {
|
|
set: function( elem, value ) {
|
|
if ( !support.radioValue && value === "radio" &&
|
|
nodeName( elem, "input" ) ) {
|
|
var val = elem.value;
|
|
elem.setAttribute( "type", value );
|
|
if ( val ) {
|
|
elem.value = val;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
removeAttr: function( elem, value ) {
|
|
var name,
|
|
i = 0,
|
|
|
|
// Attribute names can contain non-HTML whitespace characters
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
|
attrNames = value && value.match( rnothtmlwhite );
|
|
|
|
if ( attrNames && elem.nodeType === 1 ) {
|
|
while ( ( name = attrNames[ i++ ] ) ) {
|
|
elem.removeAttribute( name );
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
// Hooks for boolean attributes
|
|
boolHook = {
|
|
set: function( elem, value, name ) {
|
|
if ( value === false ) {
|
|
|
|
// Remove boolean attributes when set to false
|
|
jQuery.removeAttr( elem, name );
|
|
} else {
|
|
elem.setAttribute( name, name );
|
|
}
|
|
return name;
|
|
}
|
|
};
|
|
|
|
jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
|
|
var getter = attrHandle[ name ] || jQuery.find.attr;
|
|
|
|
attrHandle[ name ] = function( elem, name, isXML ) {
|
|
var ret, handle,
|
|
lowercaseName = name.toLowerCase();
|
|
|
|
if ( !isXML ) {
|
|
|
|
// Avoid an infinite loop by temporarily removing this function from the getter
|
|
handle = attrHandle[ lowercaseName ];
|
|
attrHandle[ lowercaseName ] = ret;
|
|
ret = getter( elem, name, isXML ) != null ?
|
|
lowercaseName :
|
|
null;
|
|
attrHandle[ lowercaseName ] = handle;
|
|
}
|
|
return ret;
|
|
};
|
|
} );
|
|
|
|
|
|
|
|
|
|
var rfocusable = /^(?:input|select|textarea|button)$/i,
|
|
rclickable = /^(?:a|area)$/i;
|
|
|
|
jQuery.fn.extend( {
|
|
prop: function( name, value ) {
|
|
return access( this, jQuery.prop, name, value, arguments.length > 1 );
|
|
},
|
|
|
|
removeProp: function( name ) {
|
|
return this.each( function() {
|
|
delete this[ jQuery.propFix[ name ] || name ];
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.extend( {
|
|
prop: function( elem, name, value ) {
|
|
var ret, hooks,
|
|
nType = elem.nodeType;
|
|
|
|
// Don't get/set properties on text, comment and attribute nodes
|
|
if ( nType === 3 || nType === 8 || nType === 2 ) {
|
|
return;
|
|
}
|
|
|
|
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
|
|
|
|
// Fix name and attach hooks
|
|
name = jQuery.propFix[ name ] || name;
|
|
hooks = jQuery.propHooks[ name ];
|
|
}
|
|
|
|
if ( value !== undefined ) {
|
|
if ( hooks && "set" in hooks &&
|
|
( ret = hooks.set( elem, value, name ) ) !== undefined ) {
|
|
return ret;
|
|
}
|
|
|
|
return ( elem[ name ] = value );
|
|
}
|
|
|
|
if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
|
|
return ret;
|
|
}
|
|
|
|
return elem[ name ];
|
|
},
|
|
|
|
propHooks: {
|
|
tabIndex: {
|
|
get: function( elem ) {
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// elem.tabIndex doesn't always return the
|
|
// correct value when it hasn't been explicitly set
|
|
// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
|
|
// Use proper attribute retrieval(#12072)
|
|
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
|
|
|
if ( tabindex ) {
|
|
return parseInt( tabindex, 10 );
|
|
}
|
|
|
|
if (
|
|
rfocusable.test( elem.nodeName ) ||
|
|
rclickable.test( elem.nodeName ) &&
|
|
elem.href
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
},
|
|
|
|
propFix: {
|
|
"for": "htmlFor",
|
|
"class": "className"
|
|
}
|
|
} );
|
|
|
|
// Support: IE <=11 only
|
|
// Accessing the selectedIndex property
|
|
// forces the browser to respect setting selected
|
|
// on the option
|
|
// The getter ensures a default option is selected
|
|
// when in an optgroup
|
|
// eslint rule "no-unused-expressions" is disabled for this code
|
|
// since it considers such accessions noop
|
|
if ( !support.optSelected ) {
|
|
jQuery.propHooks.selected = {
|
|
get: function( elem ) {
|
|
|
|
/* eslint no-unused-expressions: "off" */
|
|
|
|
var parent = elem.parentNode;
|
|
if ( parent && parent.parentNode ) {
|
|
parent.parentNode.selectedIndex;
|
|
}
|
|
return null;
|
|
},
|
|
set: function( elem ) {
|
|
|
|
/* eslint no-unused-expressions: "off" */
|
|
|
|
var parent = elem.parentNode;
|
|
if ( parent ) {
|
|
parent.selectedIndex;
|
|
|
|
if ( parent.parentNode ) {
|
|
parent.parentNode.selectedIndex;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
jQuery.each( [
|
|
"tabIndex",
|
|
"readOnly",
|
|
"maxLength",
|
|
"cellSpacing",
|
|
"cellPadding",
|
|
"rowSpan",
|
|
"colSpan",
|
|
"useMap",
|
|
"frameBorder",
|
|
"contentEditable"
|
|
], function() {
|
|
jQuery.propFix[ this.toLowerCase() ] = this;
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Strip and collapse whitespace according to HTML spec
|
|
// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
|
|
function stripAndCollapse( value ) {
|
|
var tokens = value.match( rnothtmlwhite ) || [];
|
|
return tokens.join( " " );
|
|
}
|
|
|
|
|
|
function getClass( elem ) {
|
|
return elem.getAttribute && elem.getAttribute( "class" ) || "";
|
|
}
|
|
|
|
function classesToArray( value ) {
|
|
if ( Array.isArray( value ) ) {
|
|
return value;
|
|
}
|
|
if ( typeof value === "string" ) {
|
|
return value.match( rnothtmlwhite ) || [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
jQuery.fn.extend( {
|
|
addClass: function( value ) {
|
|
var classes, elem, cur, curValue, clazz, j, finalValue,
|
|
i = 0;
|
|
|
|
if ( isFunction( value ) ) {
|
|
return this.each( function( j ) {
|
|
jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
|
|
} );
|
|
}
|
|
|
|
classes = classesToArray( value );
|
|
|
|
if ( classes.length ) {
|
|
while ( ( elem = this[ i++ ] ) ) {
|
|
curValue = getClass( elem );
|
|
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
|
|
|
if ( cur ) {
|
|
j = 0;
|
|
while ( ( clazz = classes[ j++ ] ) ) {
|
|
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
|
|
cur += clazz + " ";
|
|
}
|
|
}
|
|
|
|
// Only assign if different to avoid unneeded rendering.
|
|
finalValue = stripAndCollapse( cur );
|
|
if ( curValue !== finalValue ) {
|
|
elem.setAttribute( "class", finalValue );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
removeClass: function( value ) {
|
|
var classes, elem, cur, curValue, clazz, j, finalValue,
|
|
i = 0;
|
|
|
|
if ( isFunction( value ) ) {
|
|
return this.each( function( j ) {
|
|
jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
|
|
} );
|
|
}
|
|
|
|
if ( !arguments.length ) {
|
|
return this.attr( "class", "" );
|
|
}
|
|
|
|
classes = classesToArray( value );
|
|
|
|
if ( classes.length ) {
|
|
while ( ( elem = this[ i++ ] ) ) {
|
|
curValue = getClass( elem );
|
|
|
|
// This expression is here for better compressibility (see addClass)
|
|
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
|
|
|
if ( cur ) {
|
|
j = 0;
|
|
while ( ( clazz = classes[ j++ ] ) ) {
|
|
|
|
// Remove *all* instances
|
|
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
|
|
cur = cur.replace( " " + clazz + " ", " " );
|
|
}
|
|
}
|
|
|
|
// Only assign if different to avoid unneeded rendering.
|
|
finalValue = stripAndCollapse( cur );
|
|
if ( curValue !== finalValue ) {
|
|
elem.setAttribute( "class", finalValue );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
toggleClass: function( value, stateVal ) {
|
|
var type = typeof value,
|
|
isValidValue = type === "string" || Array.isArray( value );
|
|
|
|
if ( typeof stateVal === "boolean" && isValidValue ) {
|
|
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
|
}
|
|
|
|
if ( isFunction( value ) ) {
|
|
return this.each( function( i ) {
|
|
jQuery( this ).toggleClass(
|
|
value.call( this, i, getClass( this ), stateVal ),
|
|
stateVal
|
|
);
|
|
} );
|
|
}
|
|
|
|
return this.each( function() {
|
|
var className, i, self, classNames;
|
|
|
|
if ( isValidValue ) {
|
|
|
|
// Toggle individual class names
|
|
i = 0;
|
|
self = jQuery( this );
|
|
classNames = classesToArray( value );
|
|
|
|
while ( ( className = classNames[ i++ ] ) ) {
|
|
|
|
// Check each className given, space separated list
|
|
if ( self.hasClass( className ) ) {
|
|
self.removeClass( className );
|
|
} else {
|
|
self.addClass( className );
|
|
}
|
|
}
|
|
|
|
// Toggle whole class name
|
|
} else if ( value === undefined || type === "boolean" ) {
|
|
className = getClass( this );
|
|
if ( className ) {
|
|
|
|
// Store className if set
|
|
dataPriv.set( this, "__className__", className );
|
|
}
|
|
|
|
// If the element has a class name or if we're passed `false`,
|
|
// then remove the whole classname (if there was one, the above saved it).
|
|
// Otherwise bring back whatever was previously saved (if anything),
|
|
// falling back to the empty string if nothing was stored.
|
|
if ( this.setAttribute ) {
|
|
this.setAttribute( "class",
|
|
className || value === false ?
|
|
"" :
|
|
dataPriv.get( this, "__className__" ) || ""
|
|
);
|
|
}
|
|
}
|
|
} );
|
|
},
|
|
|
|
hasClass: function( selector ) {
|
|
var className, elem,
|
|
i = 0;
|
|
|
|
className = " " + selector + " ";
|
|
while ( ( elem = this[ i++ ] ) ) {
|
|
if ( elem.nodeType === 1 &&
|
|
( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
var rreturn = /\r/g;
|
|
|
|
jQuery.fn.extend( {
|
|
val: function( value ) {
|
|
var hooks, ret, valueIsFunction,
|
|
elem = this[ 0 ];
|
|
|
|
if ( !arguments.length ) {
|
|
if ( elem ) {
|
|
hooks = jQuery.valHooks[ elem.type ] ||
|
|
jQuery.valHooks[ elem.nodeName.toLowerCase() ];
|
|
|
|
if ( hooks &&
|
|
"get" in hooks &&
|
|
( ret = hooks.get( elem, "value" ) ) !== undefined
|
|
) {
|
|
return ret;
|
|
}
|
|
|
|
ret = elem.value;
|
|
|
|
// Handle most common string cases
|
|
if ( typeof ret === "string" ) {
|
|
return ret.replace( rreturn, "" );
|
|
}
|
|
|
|
// Handle cases where value is null/undef or number
|
|
return ret == null ? "" : ret;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
valueIsFunction = isFunction( value );
|
|
|
|
return this.each( function( i ) {
|
|
var val;
|
|
|
|
if ( this.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
if ( valueIsFunction ) {
|
|
val = value.call( this, i, jQuery( this ).val() );
|
|
} else {
|
|
val = value;
|
|
}
|
|
|
|
// Treat null/undefined as ""; convert numbers to string
|
|
if ( val == null ) {
|
|
val = "";
|
|
|
|
} else if ( typeof val === "number" ) {
|
|
val += "";
|
|
|
|
} else if ( Array.isArray( val ) ) {
|
|
val = jQuery.map( val, function( value ) {
|
|
return value == null ? "" : value + "";
|
|
} );
|
|
}
|
|
|
|
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
|
|
|
|
// If set returns undefined, fall back to normal setting
|
|
if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
|
|
this.value = val;
|
|
}
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.extend( {
|
|
valHooks: {
|
|
option: {
|
|
get: function( elem ) {
|
|
|
|
var val = jQuery.find.attr( elem, "value" );
|
|
return val != null ?
|
|
val :
|
|
|
|
// Support: IE <=10 - 11 only
|
|
// option.text throws exceptions (#14686, #14858)
|
|
// Strip and collapse whitespace
|
|
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
|
stripAndCollapse( jQuery.text( elem ) );
|
|
}
|
|
},
|
|
select: {
|
|
get: function( elem ) {
|
|
var value, option, i,
|
|
options = elem.options,
|
|
index = elem.selectedIndex,
|
|
one = elem.type === "select-one",
|
|
values = one ? null : [],
|
|
max = one ? index + 1 : options.length;
|
|
|
|
if ( index < 0 ) {
|
|
i = max;
|
|
|
|
} else {
|
|
i = one ? index : 0;
|
|
}
|
|
|
|
// Loop through all the selected options
|
|
for ( ; i < max; i++ ) {
|
|
option = options[ i ];
|
|
|
|
// Support: IE <=9 only
|
|
// IE8-9 doesn't update selected after form reset (#2551)
|
|
if ( ( option.selected || i === index ) &&
|
|
|
|
// Don't return options that are disabled or in a disabled optgroup
|
|
!option.disabled &&
|
|
( !option.parentNode.disabled ||
|
|
!nodeName( option.parentNode, "optgroup" ) ) ) {
|
|
|
|
// Get the specific value for the option
|
|
value = jQuery( option ).val();
|
|
|
|
// We don't need an array for one selects
|
|
if ( one ) {
|
|
return value;
|
|
}
|
|
|
|
// Multi-Selects return an array
|
|
values.push( value );
|
|
}
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
set: function( elem, value ) {
|
|
var optionSet, option,
|
|
options = elem.options,
|
|
values = jQuery.makeArray( value ),
|
|
i = options.length;
|
|
|
|
while ( i-- ) {
|
|
option = options[ i ];
|
|
|
|
/* eslint-disable no-cond-assign */
|
|
|
|
if ( option.selected =
|
|
jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
|
|
) {
|
|
optionSet = true;
|
|
}
|
|
|
|
/* eslint-enable no-cond-assign */
|
|
}
|
|
|
|
// Force browsers to behave consistently when non-matching value is set
|
|
if ( !optionSet ) {
|
|
elem.selectedIndex = -1;
|
|
}
|
|
return values;
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
// Radios and checkboxes getter/setter
|
|
jQuery.each( [ "radio", "checkbox" ], function() {
|
|
jQuery.valHooks[ this ] = {
|
|
set: function( elem, value ) {
|
|
if ( Array.isArray( value ) ) {
|
|
return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
|
|
}
|
|
}
|
|
};
|
|
if ( !support.checkOn ) {
|
|
jQuery.valHooks[ this ].get = function( elem ) {
|
|
return elem.getAttribute( "value" ) === null ? "on" : elem.value;
|
|
};
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Return jQuery for attributes-only inclusion
|
|
|
|
|
|
support.focusin = "onfocusin" in window;
|
|
|
|
|
|
var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
|
|
stopPropagationCallback = function( e ) {
|
|
e.stopPropagation();
|
|
};
|
|
|
|
jQuery.extend( jQuery.event, {
|
|
|
|
trigger: function( event, data, elem, onlyHandlers ) {
|
|
|
|
var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
|
|
eventPath = [ elem || document ],
|
|
type = hasOwn.call( event, "type" ) ? event.type : event,
|
|
namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
|
|
|
|
cur = lastElement = tmp = elem = elem || document;
|
|
|
|
// Don't do events on text and comment nodes
|
|
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
|
|
return;
|
|
}
|
|
|
|
// focus/blur morphs to focusin/out; ensure we're not firing them right now
|
|
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( type.indexOf( "." ) > -1 ) {
|
|
|
|
// Namespaced trigger; create a regexp to match event type in handle()
|
|
namespaces = type.split( "." );
|
|
type = namespaces.shift();
|
|
namespaces.sort();
|
|
}
|
|
ontype = type.indexOf( ":" ) < 0 && "on" + type;
|
|
|
|
// Caller can pass in a jQuery.Event object, Object, or just an event type string
|
|
event = event[ jQuery.expando ] ?
|
|
event :
|
|
new jQuery.Event( type, typeof event === "object" && event );
|
|
|
|
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
|
|
event.isTrigger = onlyHandlers ? 2 : 3;
|
|
event.namespace = namespaces.join( "." );
|
|
event.rnamespace = event.namespace ?
|
|
new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
|
|
null;
|
|
|
|
// Clean up the event in case it is being reused
|
|
event.result = undefined;
|
|
if ( !event.target ) {
|
|
event.target = elem;
|
|
}
|
|
|
|
// Clone any incoming data and prepend the event, creating the handler arg list
|
|
data = data == null ?
|
|
[ event ] :
|
|
jQuery.makeArray( data, [ event ] );
|
|
|
|
// Allow special events to draw outside the lines
|
|
special = jQuery.event.special[ type ] || {};
|
|
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
|
|
return;
|
|
}
|
|
|
|
// Determine event propagation path in advance, per W3C events spec (#9951)
|
|
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
|
|
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
|
|
|
bubbleType = special.delegateType || type;
|
|
if ( !rfocusMorph.test( bubbleType + type ) ) {
|
|
cur = cur.parentNode;
|
|
}
|
|
for ( ; cur; cur = cur.parentNode ) {
|
|
eventPath.push( cur );
|
|
tmp = cur;
|
|
}
|
|
|
|
// Only add window if we got to document (e.g., not plain obj or detached DOM)
|
|
if ( tmp === ( elem.ownerDocument || document ) ) {
|
|
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
|
|
}
|
|
}
|
|
|
|
// Fire handlers on the event path
|
|
i = 0;
|
|
while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
|
|
lastElement = cur;
|
|
event.type = i > 1 ?
|
|
bubbleType :
|
|
special.bindType || type;
|
|
|
|
// jQuery handler
|
|
handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
|
|
dataPriv.get( cur, "handle" );
|
|
if ( handle ) {
|
|
handle.apply( cur, data );
|
|
}
|
|
|
|
// Native handler
|
|
handle = ontype && cur[ ontype ];
|
|
if ( handle && handle.apply && acceptData( cur ) ) {
|
|
event.result = handle.apply( cur, data );
|
|
if ( event.result === false ) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
event.type = type;
|
|
|
|
// If nobody prevented the default action, do it now
|
|
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
|
|
|
|
if ( ( !special._default ||
|
|
special._default.apply( eventPath.pop(), data ) === false ) &&
|
|
acceptData( elem ) ) {
|
|
|
|
// Call a native DOM method on the target with the same name as the event.
|
|
// Don't do default actions on window, that's where global variables be (#6170)
|
|
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
|
|
|
|
// Don't re-trigger an onFOO event when we call its FOO() method
|
|
tmp = elem[ ontype ];
|
|
|
|
if ( tmp ) {
|
|
elem[ ontype ] = null;
|
|
}
|
|
|
|
// Prevent re-triggering of the same event, since we already bubbled it above
|
|
jQuery.event.triggered = type;
|
|
|
|
if ( event.isPropagationStopped() ) {
|
|
lastElement.addEventListener( type, stopPropagationCallback );
|
|
}
|
|
|
|
elem[ type ]();
|
|
|
|
if ( event.isPropagationStopped() ) {
|
|
lastElement.removeEventListener( type, stopPropagationCallback );
|
|
}
|
|
|
|
jQuery.event.triggered = undefined;
|
|
|
|
if ( tmp ) {
|
|
elem[ ontype ] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return event.result;
|
|
},
|
|
|
|
// Piggyback on a donor event to simulate a different one
|
|
// Used only for `focus(in | out)` events
|
|
simulate: function( type, elem, event ) {
|
|
var e = jQuery.extend(
|
|
new jQuery.Event(),
|
|
event,
|
|
{
|
|
type: type,
|
|
isSimulated: true
|
|
}
|
|
);
|
|
|
|
jQuery.event.trigger( e, null, elem );
|
|
}
|
|
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
trigger: function( type, data ) {
|
|
return this.each( function() {
|
|
jQuery.event.trigger( type, data, this );
|
|
} );
|
|
},
|
|
triggerHandler: function( type, data ) {
|
|
var elem = this[ 0 ];
|
|
if ( elem ) {
|
|
return jQuery.event.trigger( type, data, elem, true );
|
|
}
|
|
}
|
|
} );
|
|
|
|
|
|
// Support: Firefox <=44
|
|
// Firefox doesn't have focus(in | out) events
|
|
// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
|
|
//
|
|
// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
|
|
// focus(in | out) events fire after focus & blur events,
|
|
// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
|
|
// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
|
|
if ( !support.focusin ) {
|
|
jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
|
|
|
|
// Attach a single capturing handler on the document while someone wants focusin/focusout
|
|
var handler = function( event ) {
|
|
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
|
|
};
|
|
|
|
jQuery.event.special[ fix ] = {
|
|
setup: function() {
|
|
var doc = this.ownerDocument || this,
|
|
attaches = dataPriv.access( doc, fix );
|
|
|
|
if ( !attaches ) {
|
|
doc.addEventListener( orig, handler, true );
|
|
}
|
|
dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
|
|
},
|
|
teardown: function() {
|
|
var doc = this.ownerDocument || this,
|
|
attaches = dataPriv.access( doc, fix ) - 1;
|
|
|
|
if ( !attaches ) {
|
|
doc.removeEventListener( orig, handler, true );
|
|
dataPriv.remove( doc, fix );
|
|
|
|
} else {
|
|
dataPriv.access( doc, fix, attaches );
|
|
}
|
|
}
|
|
};
|
|
} );
|
|
}
|
|
var location = window.location;
|
|
|
|
var nonce = Date.now();
|
|
|
|
var rquery = ( /\?/ );
|
|
|
|
|
|
|
|
// Cross-browser xml parsing
|
|
jQuery.parseXML = function( data ) {
|
|
var xml;
|
|
if ( !data || typeof data !== "string" ) {
|
|
return null;
|
|
}
|
|
|
|
// Support: IE 9 - 11 only
|
|
// IE throws on parseFromString with invalid input.
|
|
try {
|
|
xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
|
|
} catch ( e ) {
|
|
xml = undefined;
|
|
}
|
|
|
|
if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
|
|
jQuery.error( "Invalid XML: " + data );
|
|
}
|
|
return xml;
|
|
};
|
|
|
|
|
|
var
|
|
rbracket = /\[\]$/,
|
|
rCRLF = /\r?\n/g,
|
|
rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
|
|
rsubmittable = /^(?:input|select|textarea|keygen)/i;
|
|
|
|
function buildParams( prefix, obj, traditional, add ) {
|
|
var name;
|
|
|
|
if ( Array.isArray( obj ) ) {
|
|
|
|
// Serialize array item.
|
|
jQuery.each( obj, function( i, v ) {
|
|
if ( traditional || rbracket.test( prefix ) ) {
|
|
|
|
// Treat each array item as a scalar.
|
|
add( prefix, v );
|
|
|
|
} else {
|
|
|
|
// Item is non-scalar (array or object), encode its numeric index.
|
|
buildParams(
|
|
prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
|
|
v,
|
|
traditional,
|
|
add
|
|
);
|
|
}
|
|
} );
|
|
|
|
} else if ( !traditional && toType( obj ) === "object" ) {
|
|
|
|
// Serialize object item.
|
|
for ( name in obj ) {
|
|
buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
|
|
}
|
|
|
|
} else {
|
|
|
|
// Serialize scalar item.
|
|
add( prefix, obj );
|
|
}
|
|
}
|
|
|
|
// Serialize an array of form elements or a set of
|
|
// key/values into a query string
|
|
jQuery.param = function( a, traditional ) {
|
|
var prefix,
|
|
s = [],
|
|
add = function( key, valueOrFunction ) {
|
|
|
|
// If value is a function, invoke it and use its return value
|
|
var value = isFunction( valueOrFunction ) ?
|
|
valueOrFunction() :
|
|
valueOrFunction;
|
|
|
|
s[ s.length ] = encodeURIComponent( key ) + "=" +
|
|
encodeURIComponent( value == null ? "" : value );
|
|
};
|
|
|
|
// If an array was passed in, assume that it is an array of form elements.
|
|
if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
|
|
|
|
// Serialize the form elements
|
|
jQuery.each( a, function() {
|
|
add( this.name, this.value );
|
|
} );
|
|
|
|
} else {
|
|
|
|
// If traditional, encode the "old" way (the way 1.3.2 or older
|
|
// did it), otherwise encode params recursively.
|
|
for ( prefix in a ) {
|
|
buildParams( prefix, a[ prefix ], traditional, add );
|
|
}
|
|
}
|
|
|
|
// Return the resulting serialization
|
|
return s.join( "&" );
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
serialize: function() {
|
|
return jQuery.param( this.serializeArray() );
|
|
},
|
|
serializeArray: function() {
|
|
return this.map( function() {
|
|
|
|
// Can add propHook for "elements" to filter or add form elements
|
|
var elements = jQuery.prop( this, "elements" );
|
|
return elements ? jQuery.makeArray( elements ) : this;
|
|
} )
|
|
.filter( function() {
|
|
var type = this.type;
|
|
|
|
// Use .is( ":disabled" ) so that fieldset[disabled] works
|
|
return this.name && !jQuery( this ).is( ":disabled" ) &&
|
|
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
|
|
( this.checked || !rcheckableType.test( type ) );
|
|
} )
|
|
.map( function( i, elem ) {
|
|
var val = jQuery( this ).val();
|
|
|
|
if ( val == null ) {
|
|
return null;
|
|
}
|
|
|
|
if ( Array.isArray( val ) ) {
|
|
return jQuery.map( val, function( val ) {
|
|
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
|
|
} );
|
|
}
|
|
|
|
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
|
|
} ).get();
|
|
}
|
|
} );
|
|
|
|
|
|
var
|
|
r20 = /%20/g,
|
|
rhash = /#.*$/,
|
|
rantiCache = /([?&])_=[^&]*/,
|
|
rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
|
|
|
|
// #7653, #8125, #8152: local protocol detection
|
|
rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
|
|
rnoContent = /^(?:GET|HEAD)$/,
|
|
rprotocol = /^\/\//,
|
|
|
|
/* Prefilters
|
|
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
|
|
* 2) These are called:
|
|
* - BEFORE asking for a transport
|
|
* - AFTER param serialization (s.data is a string if s.processData is true)
|
|
* 3) key is the dataType
|
|
* 4) the catchall symbol "*" can be used
|
|
* 5) execution will start with transport dataType and THEN continue down to "*" if needed
|
|
*/
|
|
prefilters = {},
|
|
|
|
/* Transports bindings
|
|
* 1) key is the dataType
|
|
* 2) the catchall symbol "*" can be used
|
|
* 3) selection will start with transport dataType and THEN go to "*" if needed
|
|
*/
|
|
transports = {},
|
|
|
|
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
|
|
allTypes = "*/".concat( "*" ),
|
|
|
|
// Anchor tag for parsing the document origin
|
|
originAnchor = document.createElement( "a" );
|
|
originAnchor.href = location.href;
|
|
|
|
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
|
|
function addToPrefiltersOrTransports( structure ) {
|
|
|
|
// dataTypeExpression is optional and defaults to "*"
|
|
return function( dataTypeExpression, func ) {
|
|
|
|
if ( typeof dataTypeExpression !== "string" ) {
|
|
func = dataTypeExpression;
|
|
dataTypeExpression = "*";
|
|
}
|
|
|
|
var dataType,
|
|
i = 0,
|
|
dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
|
|
|
|
if ( isFunction( func ) ) {
|
|
|
|
// For each dataType in the dataTypeExpression
|
|
while ( ( dataType = dataTypes[ i++ ] ) ) {
|
|
|
|
// Prepend if requested
|
|
if ( dataType[ 0 ] === "+" ) {
|
|
dataType = dataType.slice( 1 ) || "*";
|
|
( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
|
|
|
|
// Otherwise append
|
|
} else {
|
|
( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Base inspection function for prefilters and transports
|
|
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
|
|
|
|
var inspected = {},
|
|
seekingTransport = ( structure === transports );
|
|
|
|
function inspect( dataType ) {
|
|
var selected;
|
|
inspected[ dataType ] = true;
|
|
jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
|
|
var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
|
|
if ( typeof dataTypeOrTransport === "string" &&
|
|
!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
|
|
|
|
options.dataTypes.unshift( dataTypeOrTransport );
|
|
inspect( dataTypeOrTransport );
|
|
return false;
|
|
} else if ( seekingTransport ) {
|
|
return !( selected = dataTypeOrTransport );
|
|
}
|
|
} );
|
|
return selected;
|
|
}
|
|
|
|
return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
|
|
}
|
|
|
|
// A special extend for ajax options
|
|
// that takes "flat" options (not to be deep extended)
|
|
// Fixes #9887
|
|
function ajaxExtend( target, src ) {
|
|
var key, deep,
|
|
flatOptions = jQuery.ajaxSettings.flatOptions || {};
|
|
|
|
for ( key in src ) {
|
|
if ( src[ key ] !== undefined ) {
|
|
( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
|
|
}
|
|
}
|
|
if ( deep ) {
|
|
jQuery.extend( true, target, deep );
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/* Handles responses to an ajax request:
|
|
* - finds the right dataType (mediates between content-type and expected dataType)
|
|
* - returns the corresponding response
|
|
*/
|
|
function ajaxHandleResponses( s, jqXHR, responses ) {
|
|
|
|
var ct, type, finalDataType, firstDataType,
|
|
contents = s.contents,
|
|
dataTypes = s.dataTypes;
|
|
|
|
// Remove auto dataType and get content-type in the process
|
|
while ( dataTypes[ 0 ] === "*" ) {
|
|
dataTypes.shift();
|
|
if ( ct === undefined ) {
|
|
ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
|
|
}
|
|
}
|
|
|
|
// Check if we're dealing with a known content-type
|
|
if ( ct ) {
|
|
for ( type in contents ) {
|
|
if ( contents[ type ] && contents[ type ].test( ct ) ) {
|
|
dataTypes.unshift( type );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check to see if we have a response for the expected dataType
|
|
if ( dataTypes[ 0 ] in responses ) {
|
|
finalDataType = dataTypes[ 0 ];
|
|
} else {
|
|
|
|
// Try convertible dataTypes
|
|
for ( type in responses ) {
|
|
if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
|
|
finalDataType = type;
|
|
break;
|
|
}
|
|
if ( !firstDataType ) {
|
|
firstDataType = type;
|
|
}
|
|
}
|
|
|
|
// Or just use first one
|
|
finalDataType = finalDataType || firstDataType;
|
|
}
|
|
|
|
// If we found a dataType
|
|
// We add the dataType to the list if needed
|
|
// and return the corresponding response
|
|
if ( finalDataType ) {
|
|
if ( finalDataType !== dataTypes[ 0 ] ) {
|
|
dataTypes.unshift( finalDataType );
|
|
}
|
|
return responses[ finalDataType ];
|
|
}
|
|
}
|
|
|
|
/* Chain conversions given the request and the original response
|
|
* Also sets the responseXXX fields on the jqXHR instance
|
|
*/
|
|
function ajaxConvert( s, response, jqXHR, isSuccess ) {
|
|
var conv2, current, conv, tmp, prev,
|
|
converters = {},
|
|
|
|
// Work with a copy of dataTypes in case we need to modify it for conversion
|
|
dataTypes = s.dataTypes.slice();
|
|
|
|
// Create converters map with lowercased keys
|
|
if ( dataTypes[ 1 ] ) {
|
|
for ( conv in s.converters ) {
|
|
converters[ conv.toLowerCase() ] = s.converters[ conv ];
|
|
}
|
|
}
|
|
|
|
current = dataTypes.shift();
|
|
|
|
// Convert to each sequential dataType
|
|
while ( current ) {
|
|
|
|
if ( s.responseFields[ current ] ) {
|
|
jqXHR[ s.responseFields[ current ] ] = response;
|
|
}
|
|
|
|
// Apply the dataFilter if provided
|
|
if ( !prev && isSuccess && s.dataFilter ) {
|
|
response = s.dataFilter( response, s.dataType );
|
|
}
|
|
|
|
prev = current;
|
|
current = dataTypes.shift();
|
|
|
|
if ( current ) {
|
|
|
|
// There's only work to do if current dataType is non-auto
|
|
if ( current === "*" ) {
|
|
|
|
current = prev;
|
|
|
|
// Convert response if prev dataType is non-auto and differs from current
|
|
} else if ( prev !== "*" && prev !== current ) {
|
|
|
|
// Seek a direct converter
|
|
conv = converters[ prev + " " + current ] || converters[ "* " + current ];
|
|
|
|
// If none found, seek a pair
|
|
if ( !conv ) {
|
|
for ( conv2 in converters ) {
|
|
|
|
// If conv2 outputs current
|
|
tmp = conv2.split( " " );
|
|
if ( tmp[ 1 ] === current ) {
|
|
|
|
// If prev can be converted to accepted input
|
|
conv = converters[ prev + " " + tmp[ 0 ] ] ||
|
|
converters[ "* " + tmp[ 0 ] ];
|
|
if ( conv ) {
|
|
|
|
// Condense equivalence converters
|
|
if ( conv === true ) {
|
|
conv = converters[ conv2 ];
|
|
|
|
// Otherwise, insert the intermediate dataType
|
|
} else if ( converters[ conv2 ] !== true ) {
|
|
current = tmp[ 0 ];
|
|
dataTypes.unshift( tmp[ 1 ] );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply converter (if not an equivalence)
|
|
if ( conv !== true ) {
|
|
|
|
// Unless errors are allowed to bubble, catch and return them
|
|
if ( conv && s.throws ) {
|
|
response = conv( response );
|
|
} else {
|
|
try {
|
|
response = conv( response );
|
|
} catch ( e ) {
|
|
return {
|
|
state: "parsererror",
|
|
error: conv ? e : "No conversion from " + prev + " to " + current
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return { state: "success", data: response };
|
|
}
|
|
|
|
jQuery.extend( {
|
|
|
|
// Counter for holding the number of active queries
|
|
active: 0,
|
|
|
|
// Last-Modified header cache for next request
|
|
lastModified: {},
|
|
etag: {},
|
|
|
|
ajaxSettings: {
|
|
url: location.href,
|
|
type: "GET",
|
|
isLocal: rlocalProtocol.test( location.protocol ),
|
|
global: true,
|
|
processData: true,
|
|
async: true,
|
|
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
/*
|
|
timeout: 0,
|
|
data: null,
|
|
dataType: null,
|
|
username: null,
|
|
password: null,
|
|
cache: null,
|
|
throws: false,
|
|
traditional: false,
|
|
headers: {},
|
|
*/
|
|
|
|
accepts: {
|
|
"*": allTypes,
|
|
text: "text/plain",
|
|
html: "text/html",
|
|
xml: "application/xml, text/xml",
|
|
json: "application/json, text/javascript"
|
|
},
|
|
|
|
contents: {
|
|
xml: /\bxml\b/,
|
|
html: /\bhtml/,
|
|
json: /\bjson\b/
|
|
},
|
|
|
|
responseFields: {
|
|
xml: "responseXML",
|
|
text: "responseText",
|
|
json: "responseJSON"
|
|
},
|
|
|
|
// Data converters
|
|
// Keys separate source (or catchall "*") and destination types with a single space
|
|
converters: {
|
|
|
|
// Convert anything to text
|
|
"* text": String,
|
|
|
|
// Text to html (true = no transformation)
|
|
"text html": true,
|
|
|
|
// Evaluate text as a json expression
|
|
"text json": JSON.parse,
|
|
|
|
// Parse text as xml
|
|
"text xml": jQuery.parseXML
|
|
},
|
|
|
|
// For options that shouldn't be deep extended:
|
|
// you can add your own custom options here if
|
|
// and when you create one that shouldn't be
|
|
// deep extended (see ajaxExtend)
|
|
flatOptions: {
|
|
url: true,
|
|
context: true
|
|
}
|
|
},
|
|
|
|
// Creates a full fledged settings object into target
|
|
// with both ajaxSettings and settings fields.
|
|
// If target is omitted, writes into ajaxSettings.
|
|
ajaxSetup: function( target, settings ) {
|
|
return settings ?
|
|
|
|
// Building a settings object
|
|
ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
|
|
|
|
// Extending ajaxSettings
|
|
ajaxExtend( jQuery.ajaxSettings, target );
|
|
},
|
|
|
|
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
|
|
ajaxTransport: addToPrefiltersOrTransports( transports ),
|
|
|
|
// Main method
|
|
ajax: function( url, options ) {
|
|
|
|
// If url is an object, simulate pre-1.5 signature
|
|
if ( typeof url === "object" ) {
|
|
options = url;
|
|
url = undefined;
|
|
}
|
|
|
|
// Force options to be an object
|
|
options = options || {};
|
|
|
|
var transport,
|
|
|
|
// URL without anti-cache param
|
|
cacheURL,
|
|
|
|
// Response headers
|
|
responseHeadersString,
|
|
responseHeaders,
|
|
|
|
// timeout handle
|
|
timeoutTimer,
|
|
|
|
// Url cleanup var
|
|
urlAnchor,
|
|
|
|
// Request state (becomes false upon send and true upon completion)
|
|
completed,
|
|
|
|
// To know if global events are to be dispatched
|
|
fireGlobals,
|
|
|
|
// Loop variable
|
|
i,
|
|
|
|
// uncached part of the url
|
|
uncached,
|
|
|
|
// Create the final options object
|
|
s = jQuery.ajaxSetup( {}, options ),
|
|
|
|
// Callbacks context
|
|
callbackContext = s.context || s,
|
|
|
|
// Context for global events is callbackContext if it is a DOM node or jQuery collection
|
|
globalEventContext = s.context &&
|
|
( callbackContext.nodeType || callbackContext.jquery ) ?
|
|
jQuery( callbackContext ) :
|
|
jQuery.event,
|
|
|
|
// Deferreds
|
|
deferred = jQuery.Deferred(),
|
|
completeDeferred = jQuery.Callbacks( "once memory" ),
|
|
|
|
// Status-dependent callbacks
|
|
statusCode = s.statusCode || {},
|
|
|
|
// Headers (they are sent all at once)
|
|
requestHeaders = {},
|
|
requestHeadersNames = {},
|
|
|
|
// Default abort message
|
|
strAbort = "canceled",
|
|
|
|
// Fake xhr
|
|
jqXHR = {
|
|
readyState: 0,
|
|
|
|
// Builds headers hashtable if needed
|
|
getResponseHeader: function( key ) {
|
|
var match;
|
|
if ( completed ) {
|
|
if ( !responseHeaders ) {
|
|
responseHeaders = {};
|
|
while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
|
|
responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
|
|
}
|
|
}
|
|
match = responseHeaders[ key.toLowerCase() ];
|
|
}
|
|
return match == null ? null : match;
|
|
},
|
|
|
|
// Raw string
|
|
getAllResponseHeaders: function() {
|
|
return completed ? responseHeadersString : null;
|
|
},
|
|
|
|
// Caches the header
|
|
setRequestHeader: function( name, value ) {
|
|
if ( completed == null ) {
|
|
name = requestHeadersNames[ name.toLowerCase() ] =
|
|
requestHeadersNames[ name.toLowerCase() ] || name;
|
|
requestHeaders[ name ] = value;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Overrides response content-type header
|
|
overrideMimeType: function( type ) {
|
|
if ( completed == null ) {
|
|
s.mimeType = type;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Status-dependent callbacks
|
|
statusCode: function( map ) {
|
|
var code;
|
|
if ( map ) {
|
|
if ( completed ) {
|
|
|
|
// Execute the appropriate callbacks
|
|
jqXHR.always( map[ jqXHR.status ] );
|
|
} else {
|
|
|
|
// Lazy-add the new callbacks in a way that preserves old ones
|
|
for ( code in map ) {
|
|
statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Cancel the request
|
|
abort: function( statusText ) {
|
|
var finalText = statusText || strAbort;
|
|
if ( transport ) {
|
|
transport.abort( finalText );
|
|
}
|
|
done( 0, finalText );
|
|
return this;
|
|
}
|
|
};
|
|
|
|
// Attach deferreds
|
|
deferred.promise( jqXHR );
|
|
|
|
// Add protocol if not provided (prefilters might expect it)
|
|
// Handle falsy url in the settings object (#10093: consistency with old signature)
|
|
// We also use the url parameter if available
|
|
s.url = ( ( url || s.url || location.href ) + "" )
|
|
.replace( rprotocol, location.protocol + "//" );
|
|
|
|
// Alias method option to type as per ticket #12004
|
|
s.type = options.method || options.type || s.method || s.type;
|
|
|
|
// Extract dataTypes list
|
|
s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
|
|
|
|
// A cross-domain request is in order when the origin doesn't match the current origin.
|
|
if ( s.crossDomain == null ) {
|
|
urlAnchor = document.createElement( "a" );
|
|
|
|
// Support: IE <=8 - 11, Edge 12 - 15
|
|
// IE throws exception on accessing the href property if url is malformed,
|
|
// e.g. http://example.com:80x/
|
|
try {
|
|
urlAnchor.href = s.url;
|
|
|
|
// Support: IE <=8 - 11 only
|
|
// Anchor's host property isn't correctly set when s.url is relative
|
|
urlAnchor.href = urlAnchor.href;
|
|
s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
|
|
urlAnchor.protocol + "//" + urlAnchor.host;
|
|
} catch ( e ) {
|
|
|
|
// If there is an error parsing the URL, assume it is crossDomain,
|
|
// it can be rejected by the transport if it is invalid
|
|
s.crossDomain = true;
|
|
}
|
|
}
|
|
|
|
// Convert data if not already a string
|
|
if ( s.data && s.processData && typeof s.data !== "string" ) {
|
|
s.data = jQuery.param( s.data, s.traditional );
|
|
}
|
|
|
|
// Apply prefilters
|
|
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
|
|
|
|
// If request was aborted inside a prefilter, stop there
|
|
if ( completed ) {
|
|
return jqXHR;
|
|
}
|
|
|
|
// We can fire global events as of now if asked to
|
|
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
|
|
fireGlobals = jQuery.event && s.global;
|
|
|
|
// Watch for a new set of requests
|
|
if ( fireGlobals && jQuery.active++ === 0 ) {
|
|
jQuery.event.trigger( "ajaxStart" );
|
|
}
|
|
|
|
// Uppercase the type
|
|
s.type = s.type.toUpperCase();
|
|
|
|
// Determine if request has content
|
|
s.hasContent = !rnoContent.test( s.type );
|
|
|
|
// Save the URL in case we're toying with the If-Modified-Since
|
|
// and/or If-None-Match header later on
|
|
// Remove hash to simplify url manipulation
|
|
cacheURL = s.url.replace( rhash, "" );
|
|
|
|
// More options handling for requests with no content
|
|
if ( !s.hasContent ) {
|
|
|
|
// Remember the hash so we can put it back
|
|
uncached = s.url.slice( cacheURL.length );
|
|
|
|
// If data is available and should be processed, append data to url
|
|
if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
|
|
cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
|
|
|
|
// #9682: remove data so that it's not used in an eventual retry
|
|
delete s.data;
|
|
}
|
|
|
|
// Add or update anti-cache param if needed
|
|
if ( s.cache === false ) {
|
|
cacheURL = cacheURL.replace( rantiCache, "$1" );
|
|
uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
|
|
}
|
|
|
|
// Put hash and anti-cache on the URL that will be requested (gh-1732)
|
|
s.url = cacheURL + uncached;
|
|
|
|
// Change '%20' to '+' if this is encoded form body content (gh-2658)
|
|
} else if ( s.data && s.processData &&
|
|
( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
|
|
s.data = s.data.replace( r20, "+" );
|
|
}
|
|
|
|
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
|
|
if ( s.ifModified ) {
|
|
if ( jQuery.lastModified[ cacheURL ] ) {
|
|
jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
|
|
}
|
|
if ( jQuery.etag[ cacheURL ] ) {
|
|
jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
|
|
}
|
|
}
|
|
|
|
// Set the correct header, if data is being sent
|
|
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
|
|
jqXHR.setRequestHeader( "Content-Type", s.contentType );
|
|
}
|
|
|
|
// Set the Accepts header for the server, depending on the dataType
|
|
jqXHR.setRequestHeader(
|
|
"Accept",
|
|
s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
|
|
s.accepts[ s.dataTypes[ 0 ] ] +
|
|
( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
|
|
s.accepts[ "*" ]
|
|
);
|
|
|
|
// Check for headers option
|
|
for ( i in s.headers ) {
|
|
jqXHR.setRequestHeader( i, s.headers[ i ] );
|
|
}
|
|
|
|
// Allow custom headers/mimetypes and early abort
|
|
if ( s.beforeSend &&
|
|
( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
|
|
|
|
// Abort if not done already and return
|
|
return jqXHR.abort();
|
|
}
|
|
|
|
// Aborting is no longer a cancellation
|
|
strAbort = "abort";
|
|
|
|
// Install callbacks on deferreds
|
|
completeDeferred.add( s.complete );
|
|
jqXHR.done( s.success );
|
|
jqXHR.fail( s.error );
|
|
|
|
// Get transport
|
|
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
|
|
|
|
// If no transport, we auto-abort
|
|
if ( !transport ) {
|
|
done( -1, "No Transport" );
|
|
} else {
|
|
jqXHR.readyState = 1;
|
|
|
|
// Send global event
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
|
|
}
|
|
|
|
// If request was aborted inside ajaxSend, stop there
|
|
if ( completed ) {
|
|
return jqXHR;
|
|
}
|
|
|
|
// Timeout
|
|
if ( s.async && s.timeout > 0 ) {
|
|
timeoutTimer = window.setTimeout( function() {
|
|
jqXHR.abort( "timeout" );
|
|
}, s.timeout );
|
|
}
|
|
|
|
try {
|
|
completed = false;
|
|
transport.send( requestHeaders, done );
|
|
} catch ( e ) {
|
|
|
|
// Rethrow post-completion exceptions
|
|
if ( completed ) {
|
|
throw e;
|
|
}
|
|
|
|
// Propagate others as results
|
|
done( -1, e );
|
|
}
|
|
}
|
|
|
|
// Callback for when everything is done
|
|
function done( status, nativeStatusText, responses, headers ) {
|
|
var isSuccess, success, error, response, modified,
|
|
statusText = nativeStatusText;
|
|
|
|
// Ignore repeat invocations
|
|
if ( completed ) {
|
|
return;
|
|
}
|
|
|
|
completed = true;
|
|
|
|
// Clear timeout if it exists
|
|
if ( timeoutTimer ) {
|
|
window.clearTimeout( timeoutTimer );
|
|
}
|
|
|
|
// Dereference transport for early garbage collection
|
|
// (no matter how long the jqXHR object will be used)
|
|
transport = undefined;
|
|
|
|
// Cache response headers
|
|
responseHeadersString = headers || "";
|
|
|
|
// Set readyState
|
|
jqXHR.readyState = status > 0 ? 4 : 0;
|
|
|
|
// Determine if successful
|
|
isSuccess = status >= 200 && status < 300 || status === 304;
|
|
|
|
// Get response data
|
|
if ( responses ) {
|
|
response = ajaxHandleResponses( s, jqXHR, responses );
|
|
}
|
|
|
|
// Convert no matter what (that way responseXXX fields are always set)
|
|
response = ajaxConvert( s, response, jqXHR, isSuccess );
|
|
|
|
// If successful, handle type chaining
|
|
if ( isSuccess ) {
|
|
|
|
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
|
|
if ( s.ifModified ) {
|
|
modified = jqXHR.getResponseHeader( "Last-Modified" );
|
|
if ( modified ) {
|
|
jQuery.lastModified[ cacheURL ] = modified;
|
|
}
|
|
modified = jqXHR.getResponseHeader( "etag" );
|
|
if ( modified ) {
|
|
jQuery.etag[ cacheURL ] = modified;
|
|
}
|
|
}
|
|
|
|
// if no content
|
|
if ( status === 204 || s.type === "HEAD" ) {
|
|
statusText = "nocontent";
|
|
|
|
// if not modified
|
|
} else if ( status === 304 ) {
|
|
statusText = "notmodified";
|
|
|
|
// If we have data, let's convert it
|
|
} else {
|
|
statusText = response.state;
|
|
success = response.data;
|
|
error = response.error;
|
|
isSuccess = !error;
|
|
}
|
|
} else {
|
|
|
|
// Extract error from statusText and normalize for non-aborts
|
|
error = statusText;
|
|
if ( status || !statusText ) {
|
|
statusText = "error";
|
|
if ( status < 0 ) {
|
|
status = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set data for the fake xhr object
|
|
jqXHR.status = status;
|
|
jqXHR.statusText = ( nativeStatusText || statusText ) + "";
|
|
|
|
// Success/Error
|
|
if ( isSuccess ) {
|
|
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
|
|
} else {
|
|
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
|
|
}
|
|
|
|
// Status-dependent callbacks
|
|
jqXHR.statusCode( statusCode );
|
|
statusCode = undefined;
|
|
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
|
|
[ jqXHR, s, isSuccess ? success : error ] );
|
|
}
|
|
|
|
// Complete
|
|
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
|
|
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
|
|
|
|
// Handle the global AJAX counter
|
|
if ( !( --jQuery.active ) ) {
|
|
jQuery.event.trigger( "ajaxStop" );
|
|
}
|
|
}
|
|
}
|
|
|
|
return jqXHR;
|
|
},
|
|
|
|
getJSON: function( url, data, callback ) {
|
|
return jQuery.get( url, data, callback, "json" );
|
|
},
|
|
|
|
getScript: function( url, callback ) {
|
|
return jQuery.get( url, undefined, callback, "script" );
|
|
}
|
|
} );
|
|
|
|
jQuery.each( [ "get", "post" ], function( i, method ) {
|
|
jQuery[ method ] = function( url, data, callback, type ) {
|
|
|
|
// Shift arguments if data argument was omitted
|
|
if ( isFunction( data ) ) {
|
|
type = type || callback;
|
|
callback = data;
|
|
data = undefined;
|
|
}
|
|
|
|
// The url can be an options object (which then must have .url)
|
|
return jQuery.ajax( jQuery.extend( {
|
|
url: url,
|
|
type: method,
|
|
dataType: type,
|
|
data: data,
|
|
success: callback
|
|
}, jQuery.isPlainObject( url ) && url ) );
|
|
};
|
|
} );
|
|
|
|
|
|
jQuery._evalUrl = function( url ) {
|
|
return jQuery.ajax( {
|
|
url: url,
|
|
|
|
// Make this explicit, since user can override this through ajaxSetup (#11264)
|
|
type: "GET",
|
|
dataType: "script",
|
|
cache: true,
|
|
async: false,
|
|
global: false,
|
|
"throws": true
|
|
} );
|
|
};
|
|
|
|
|
|
jQuery.fn.extend( {
|
|
wrapAll: function( html ) {
|
|
var wrap;
|
|
|
|
if ( this[ 0 ] ) {
|
|
if ( isFunction( html ) ) {
|
|
html = html.call( this[ 0 ] );
|
|
}
|
|
|
|
// The elements to wrap the target around
|
|
wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
|
|
|
|
if ( this[ 0 ].parentNode ) {
|
|
wrap.insertBefore( this[ 0 ] );
|
|
}
|
|
|
|
wrap.map( function() {
|
|
var elem = this;
|
|
|
|
while ( elem.firstElementChild ) {
|
|
elem = elem.firstElementChild;
|
|
}
|
|
|
|
return elem;
|
|
} ).append( this );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
wrapInner: function( html ) {
|
|
if ( isFunction( html ) ) {
|
|
return this.each( function( i ) {
|
|
jQuery( this ).wrapInner( html.call( this, i ) );
|
|
} );
|
|
}
|
|
|
|
return this.each( function() {
|
|
var self = jQuery( this ),
|
|
contents = self.contents();
|
|
|
|
if ( contents.length ) {
|
|
contents.wrapAll( html );
|
|
|
|
} else {
|
|
self.append( html );
|
|
}
|
|
} );
|
|
},
|
|
|
|
wrap: function( html ) {
|
|
var htmlIsFunction = isFunction( html );
|
|
|
|
return this.each( function( i ) {
|
|
jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
|
|
} );
|
|
},
|
|
|
|
unwrap: function( selector ) {
|
|
this.parent( selector ).not( "body" ).each( function() {
|
|
jQuery( this ).replaceWith( this.childNodes );
|
|
} );
|
|
return this;
|
|
}
|
|
} );
|
|
|
|
|
|
jQuery.expr.pseudos.hidden = function( elem ) {
|
|
return !jQuery.expr.pseudos.visible( elem );
|
|
};
|
|
jQuery.expr.pseudos.visible = function( elem ) {
|
|
return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
|
|
};
|
|
|
|
|
|
|
|
|
|
jQuery.ajaxSettings.xhr = function() {
|
|
try {
|
|
return new window.XMLHttpRequest();
|
|
} catch ( e ) {}
|
|
};
|
|
|
|
var xhrSuccessStatus = {
|
|
|
|
// File protocol always yields status code 0, assume 200
|
|
0: 200,
|
|
|
|
// Support: IE <=9 only
|
|
// #1450: sometimes IE returns 1223 when it should be 204
|
|
1223: 204
|
|
},
|
|
xhrSupported = jQuery.ajaxSettings.xhr();
|
|
|
|
support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
|
|
support.ajax = xhrSupported = !!xhrSupported;
|
|
|
|
jQuery.ajaxTransport( function( options ) {
|
|
var callback, errorCallback;
|
|
|
|
// Cross domain only allowed if supported through XMLHttpRequest
|
|
if ( support.cors || xhrSupported && !options.crossDomain ) {
|
|
return {
|
|
send: function( headers, complete ) {
|
|
var i,
|
|
xhr = options.xhr();
|
|
|
|
xhr.open(
|
|
options.type,
|
|
options.url,
|
|
options.async,
|
|
options.username,
|
|
options.password
|
|
);
|
|
|
|
// Apply custom fields if provided
|
|
if ( options.xhrFields ) {
|
|
for ( i in options.xhrFields ) {
|
|
xhr[ i ] = options.xhrFields[ i ];
|
|
}
|
|
}
|
|
|
|
// Override mime type if needed
|
|
if ( options.mimeType && xhr.overrideMimeType ) {
|
|
xhr.overrideMimeType( options.mimeType );
|
|
}
|
|
|
|
// X-Requested-With header
|
|
// For cross-domain requests, seeing as conditions for a preflight are
|
|
// akin to a jigsaw puzzle, we simply never set it to be sure.
|
|
// (it can always be set on a per-request basis or even using ajaxSetup)
|
|
// For same-domain requests, won't change header if already provided.
|
|
if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
|
|
headers[ "X-Requested-With" ] = "XMLHttpRequest";
|
|
}
|
|
|
|
// Set headers
|
|
for ( i in headers ) {
|
|
xhr.setRequestHeader( i, headers[ i ] );
|
|
}
|
|
|
|
// Callback
|
|
callback = function( type ) {
|
|
return function() {
|
|
if ( callback ) {
|
|
callback = errorCallback = xhr.onload =
|
|
xhr.onerror = xhr.onabort = xhr.ontimeout =
|
|
xhr.onreadystatechange = null;
|
|
|
|
if ( type === "abort" ) {
|
|
xhr.abort();
|
|
} else if ( type === "error" ) {
|
|
|
|
// Support: IE <=9 only
|
|
// On a manual native abort, IE9 throws
|
|
// errors on any property access that is not readyState
|
|
if ( typeof xhr.status !== "number" ) {
|
|
complete( 0, "error" );
|
|
} else {
|
|
complete(
|
|
|
|
// File: protocol always yields status 0; see #8605, #14207
|
|
xhr.status,
|
|
xhr.statusText
|
|
);
|
|
}
|
|
} else {
|
|
complete(
|
|
xhrSuccessStatus[ xhr.status ] || xhr.status,
|
|
xhr.statusText,
|
|
|
|
// Support: IE <=9 only
|
|
// IE9 has no XHR2 but throws on binary (trac-11426)
|
|
// For XHR2 non-text, let the caller handle it (gh-2498)
|
|
( xhr.responseType || "text" ) !== "text" ||
|
|
typeof xhr.responseText !== "string" ?
|
|
{ binary: xhr.response } :
|
|
{ text: xhr.responseText },
|
|
xhr.getAllResponseHeaders()
|
|
);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
// Listen to events
|
|
xhr.onload = callback();
|
|
errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
|
|
|
|
// Support: IE 9 only
|
|
// Use onreadystatechange to replace onabort
|
|
// to handle uncaught aborts
|
|
if ( xhr.onabort !== undefined ) {
|
|
xhr.onabort = errorCallback;
|
|
} else {
|
|
xhr.onreadystatechange = function() {
|
|
|
|
// Check readyState before timeout as it changes
|
|
if ( xhr.readyState === 4 ) {
|
|
|
|
// Allow onerror to be called first,
|
|
// but that will not handle a native abort
|
|
// Also, save errorCallback to a variable
|
|
// as xhr.onerror cannot be accessed
|
|
window.setTimeout( function() {
|
|
if ( callback ) {
|
|
errorCallback();
|
|
}
|
|
} );
|
|
}
|
|
};
|
|
}
|
|
|
|
// Create the abort callback
|
|
callback = callback( "abort" );
|
|
|
|
try {
|
|
|
|
// Do send the request (this may raise an exception)
|
|
xhr.send( options.hasContent && options.data || null );
|
|
} catch ( e ) {
|
|
|
|
// #14683: Only rethrow if this hasn't been notified as an error yet
|
|
if ( callback ) {
|
|
throw e;
|
|
}
|
|
}
|
|
},
|
|
|
|
abort: function() {
|
|
if ( callback ) {
|
|
callback();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
|
|
jQuery.ajaxPrefilter( function( s ) {
|
|
if ( s.crossDomain ) {
|
|
s.contents.script = false;
|
|
}
|
|
} );
|
|
|
|
// Install script dataType
|
|
jQuery.ajaxSetup( {
|
|
accepts: {
|
|
script: "text/javascript, application/javascript, " +
|
|
"application/ecmascript, application/x-ecmascript"
|
|
},
|
|
contents: {
|
|
script: /\b(?:java|ecma)script\b/
|
|
},
|
|
converters: {
|
|
"text script": function( text ) {
|
|
jQuery.globalEval( text );
|
|
return text;
|
|
}
|
|
}
|
|
} );
|
|
|
|
// Handle cache's special case and crossDomain
|
|
jQuery.ajaxPrefilter( "script", function( s ) {
|
|
if ( s.cache === undefined ) {
|
|
s.cache = false;
|
|
}
|
|
if ( s.crossDomain ) {
|
|
s.type = "GET";
|
|
}
|
|
} );
|
|
|
|
// Bind script tag hack transport
|
|
jQuery.ajaxTransport( "script", function( s ) {
|
|
|
|
// This transport only deals with cross domain requests
|
|
if ( s.crossDomain ) {
|
|
var script, callback;
|
|
return {
|
|
send: function( _, complete ) {
|
|
script = jQuery( "<script>" ).prop( {
|
|
charset: s.scriptCharset,
|
|
src: s.url
|
|
} ).on(
|
|
"load error",
|
|
callback = function( evt ) {
|
|
script.remove();
|
|
callback = null;
|
|
if ( evt ) {
|
|
complete( evt.type === "error" ? 404 : 200, evt.type );
|
|
}
|
|
}
|
|
);
|
|
|
|
// Use native DOM manipulation to avoid our domManip AJAX trickery
|
|
document.head.appendChild( script[ 0 ] );
|
|
},
|
|
abort: function() {
|
|
if ( callback ) {
|
|
callback();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
var oldCallbacks = [],
|
|
rjsonp = /(=)\?(?=&|$)|\?\?/;
|
|
|
|
// Default jsonp settings
|
|
jQuery.ajaxSetup( {
|
|
jsonp: "callback",
|
|
jsonpCallback: function() {
|
|
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
|
|
this[ callback ] = true;
|
|
return callback;
|
|
}
|
|
} );
|
|
|
|
// Detect, normalize options and install callbacks for jsonp requests
|
|
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
|
|
|
|
var callbackName, overwritten, responseContainer,
|
|
jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
|
|
"url" :
|
|
typeof s.data === "string" &&
|
|
( s.contentType || "" )
|
|
.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
|
|
rjsonp.test( s.data ) && "data"
|
|
);
|
|
|
|
// Handle iff the expected data type is "jsonp" or we have a parameter to set
|
|
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
|
|
|
|
// Get callback name, remembering preexisting value associated with it
|
|
callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
|
|
s.jsonpCallback() :
|
|
s.jsonpCallback;
|
|
|
|
// Insert callback into url or form data
|
|
if ( jsonProp ) {
|
|
s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
|
|
} else if ( s.jsonp !== false ) {
|
|
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
|
|
}
|
|
|
|
// Use data converter to retrieve json after script execution
|
|
s.converters[ "script json" ] = function() {
|
|
if ( !responseContainer ) {
|
|
jQuery.error( callbackName + " was not called" );
|
|
}
|
|
return responseContainer[ 0 ];
|
|
};
|
|
|
|
// Force json dataType
|
|
s.dataTypes[ 0 ] = "json";
|
|
|
|
// Install callback
|
|
overwritten = window[ callbackName ];
|
|
window[ callbackName ] = function() {
|
|
responseContainer = arguments;
|
|
};
|
|
|
|
// Clean-up function (fires after converters)
|
|
jqXHR.always( function() {
|
|
|
|
// If previous value didn't exist - remove it
|
|
if ( overwritten === undefined ) {
|
|
jQuery( window ).removeProp( callbackName );
|
|
|
|
// Otherwise restore preexisting value
|
|
} else {
|
|
window[ callbackName ] = overwritten;
|
|
}
|
|
|
|
// Save back as free
|
|
if ( s[ callbackName ] ) {
|
|
|
|
// Make sure that re-using the options doesn't screw things around
|
|
s.jsonpCallback = originalSettings.jsonpCallback;
|
|
|
|
// Save the callback name for future use
|
|
oldCallbacks.push( callbackName );
|
|
}
|
|
|
|
// Call if it was a function and we have a response
|
|
if ( responseContainer && isFunction( overwritten ) ) {
|
|
overwritten( responseContainer[ 0 ] );
|
|
}
|
|
|
|
responseContainer = overwritten = undefined;
|
|
} );
|
|
|
|
// Delegate to script
|
|
return "script";
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Support: Safari 8 only
|
|
// In Safari 8 documents created via document.implementation.createHTMLDocument
|
|
// collapse sibling forms: the second one becomes a child of the first one.
|
|
// Because of that, this security measure has to be disabled in Safari 8.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=137337
|
|
support.createHTMLDocument = ( function() {
|
|
var body = document.implementation.createHTMLDocument( "" ).body;
|
|
body.innerHTML = "<form></form><form></form>";
|
|
return body.childNodes.length === 2;
|
|
} )();
|
|
|
|
|
|
// Argument "data" should be string of html
|
|
// context (optional): If specified, the fragment will be created in this context,
|
|
// defaults to document
|
|
// keepScripts (optional): If true, will include scripts passed in the html string
|
|
jQuery.parseHTML = function( data, context, keepScripts ) {
|
|
if ( typeof data !== "string" ) {
|
|
return [];
|
|
}
|
|
if ( typeof context === "boolean" ) {
|
|
keepScripts = context;
|
|
context = false;
|
|
}
|
|
|
|
var base, parsed, scripts;
|
|
|
|
if ( !context ) {
|
|
|
|
// Stop scripts or inline event handlers from being executed immediately
|
|
// by using document.implementation
|
|
if ( support.createHTMLDocument ) {
|
|
context = document.implementation.createHTMLDocument( "" );
|
|
|
|
// Set the base href for the created document
|
|
// so any parsed elements with URLs
|
|
// are based on the document's URL (gh-2965)
|
|
base = context.createElement( "base" );
|
|
base.href = document.location.href;
|
|
context.head.appendChild( base );
|
|
} else {
|
|
context = document;
|
|
}
|
|
}
|
|
|
|
parsed = rsingleTag.exec( data );
|
|
scripts = !keepScripts && [];
|
|
|
|
// Single tag
|
|
if ( parsed ) {
|
|
return [ context.createElement( parsed[ 1 ] ) ];
|
|
}
|
|
|
|
parsed = buildFragment( [ data ], context, scripts );
|
|
|
|
if ( scripts && scripts.length ) {
|
|
jQuery( scripts ).remove();
|
|
}
|
|
|
|
return jQuery.merge( [], parsed.childNodes );
|
|
};
|
|
|
|
|
|
/**
|
|
* Load a url into a page
|
|
*/
|
|
jQuery.fn.load = function( url, params, callback ) {
|
|
var selector, type, response,
|
|
self = this,
|
|
off = url.indexOf( " " );
|
|
|
|
if ( off > -1 ) {
|
|
selector = stripAndCollapse( url.slice( off ) );
|
|
url = url.slice( 0, off );
|
|
}
|
|
|
|
// If it's a function
|
|
if ( isFunction( params ) ) {
|
|
|
|
// We assume that it's the callback
|
|
callback = params;
|
|
params = undefined;
|
|
|
|
// Otherwise, build a param string
|
|
} else if ( params && typeof params === "object" ) {
|
|
type = "POST";
|
|
}
|
|
|
|
// If we have elements to modify, make the request
|
|
if ( self.length > 0 ) {
|
|
jQuery.ajax( {
|
|
url: url,
|
|
|
|
// If "type" variable is undefined, then "GET" method will be used.
|
|
// Make value of this field explicit since
|
|
// user can override it through ajaxSetup method
|
|
type: type || "GET",
|
|
dataType: "html",
|
|
data: params
|
|
} ).done( function( responseText ) {
|
|
|
|
// Save response for use in complete callback
|
|
response = arguments;
|
|
|
|
self.html( selector ?
|
|
|
|
// If a selector was specified, locate the right elements in a dummy div
|
|
// Exclude scripts to avoid IE 'Permission Denied' errors
|
|
jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
|
|
|
|
// Otherwise use the full result
|
|
responseText );
|
|
|
|
// If the request succeeds, this function gets "data", "status", "jqXHR"
|
|
// but they are ignored because response was set above.
|
|
// If it fails, this function gets "jqXHR", "status", "error"
|
|
} ).always( callback && function( jqXHR, status ) {
|
|
self.each( function() {
|
|
callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
|
|
} );
|
|
} );
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
|
|
|
|
|
|
// Attach a bunch of functions for handling common AJAX events
|
|
jQuery.each( [
|
|
"ajaxStart",
|
|
"ajaxStop",
|
|
"ajaxComplete",
|
|
"ajaxError",
|
|
"ajaxSuccess",
|
|
"ajaxSend"
|
|
], function( i, type ) {
|
|
jQuery.fn[ type ] = function( fn ) {
|
|
return this.on( type, fn );
|
|
};
|
|
} );
|
|
|
|
|
|
|
|
|
|
jQuery.expr.pseudos.animated = function( elem ) {
|
|
return jQuery.grep( jQuery.timers, function( fn ) {
|
|
return elem === fn.elem;
|
|
} ).length;
|
|
};
|
|
|
|
|
|
|
|
|
|
jQuery.offset = {
|
|
setOffset: function( elem, options, i ) {
|
|
var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
|
|
position = jQuery.css( elem, "position" ),
|
|
curElem = jQuery( elem ),
|
|
props = {};
|
|
|
|
// Set position first, in-case top/left are set even on static elem
|
|
if ( position === "static" ) {
|
|
elem.style.position = "relative";
|
|
}
|
|
|
|
curOffset = curElem.offset();
|
|
curCSSTop = jQuery.css( elem, "top" );
|
|
curCSSLeft = jQuery.css( elem, "left" );
|
|
calculatePosition = ( position === "absolute" || position === "fixed" ) &&
|
|
( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
|
|
|
|
// Need to be able to calculate position if either
|
|
// top or left is auto and position is either absolute or fixed
|
|
if ( calculatePosition ) {
|
|
curPosition = curElem.position();
|
|
curTop = curPosition.top;
|
|
curLeft = curPosition.left;
|
|
|
|
} else {
|
|
curTop = parseFloat( curCSSTop ) || 0;
|
|
curLeft = parseFloat( curCSSLeft ) || 0;
|
|
}
|
|
|
|
if ( isFunction( options ) ) {
|
|
|
|
// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
|
|
options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
|
|
}
|
|
|
|
if ( options.top != null ) {
|
|
props.top = ( options.top - curOffset.top ) + curTop;
|
|
}
|
|
if ( options.left != null ) {
|
|
props.left = ( options.left - curOffset.left ) + curLeft;
|
|
}
|
|
|
|
if ( "using" in options ) {
|
|
options.using.call( elem, props );
|
|
|
|
} else {
|
|
curElem.css( props );
|
|
}
|
|
}
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
// offset() relates an element's border box to the document origin
|
|
offset: function( options ) {
|
|
|
|
// Preserve chaining for setter
|
|
if ( arguments.length ) {
|
|
return options === undefined ?
|
|
this :
|
|
this.each( function( i ) {
|
|
jQuery.offset.setOffset( this, options, i );
|
|
} );
|
|
}
|
|
|
|
var rect, win,
|
|
elem = this[ 0 ];
|
|
|
|
if ( !elem ) {
|
|
return;
|
|
}
|
|
|
|
// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
|
|
// Support: IE <=11 only
|
|
// Running getBoundingClientRect on a
|
|
// disconnected node in IE throws an error
|
|
if ( !elem.getClientRects().length ) {
|
|
return { top: 0, left: 0 };
|
|
}
|
|
|
|
// Get document-relative position by adding viewport scroll to viewport-relative gBCR
|
|
rect = elem.getBoundingClientRect();
|
|
win = elem.ownerDocument.defaultView;
|
|
return {
|
|
top: rect.top + win.pageYOffset,
|
|
left: rect.left + win.pageXOffset
|
|
};
|
|
},
|
|
|
|
// position() relates an element's margin box to its offset parent's padding box
|
|
// This corresponds to the behavior of CSS absolute positioning
|
|
position: function() {
|
|
if ( !this[ 0 ] ) {
|
|
return;
|
|
}
|
|
|
|
var offsetParent, offset, doc,
|
|
elem = this[ 0 ],
|
|
parentOffset = { top: 0, left: 0 };
|
|
|
|
// position:fixed elements are offset from the viewport, which itself always has zero offset
|
|
if ( jQuery.css( elem, "position" ) === "fixed" ) {
|
|
|
|
// Assume position:fixed implies availability of getBoundingClientRect
|
|
offset = elem.getBoundingClientRect();
|
|
|
|
} else {
|
|
offset = this.offset();
|
|
|
|
// Account for the *real* offset parent, which can be the document or its root element
|
|
// when a statically positioned element is identified
|
|
doc = elem.ownerDocument;
|
|
offsetParent = elem.offsetParent || doc.documentElement;
|
|
while ( offsetParent &&
|
|
( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
|
|
jQuery.css( offsetParent, "position" ) === "static" ) {
|
|
|
|
offsetParent = offsetParent.parentNode;
|
|
}
|
|
if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
|
|
|
|
// Incorporate borders into its offset, since they are outside its content origin
|
|
parentOffset = jQuery( offsetParent ).offset();
|
|
parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
|
|
parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
|
|
}
|
|
}
|
|
|
|
// Subtract parent offsets and element margins
|
|
return {
|
|
top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
|
|
left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
|
|
};
|
|
},
|
|
|
|
// This method will return documentElement in the following cases:
|
|
// 1) For the element inside the iframe without offsetParent, this method will return
|
|
// documentElement of the parent window
|
|
// 2) For the hidden or detached element
|
|
// 3) For body or html element, i.e. in case of the html node - it will return itself
|
|
//
|
|
// but those exceptions were never presented as a real life use-cases
|
|
// and might be considered as more preferable results.
|
|
//
|
|
// This logic, however, is not guaranteed and can change at any point in the future
|
|
offsetParent: function() {
|
|
return this.map( function() {
|
|
var offsetParent = this.offsetParent;
|
|
|
|
while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
|
|
return offsetParent || documentElement;
|
|
} );
|
|
}
|
|
} );
|
|
|
|
// Create scrollLeft and scrollTop methods
|
|
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
|
|
var top = "pageYOffset" === prop;
|
|
|
|
jQuery.fn[ method ] = function( val ) {
|
|
return access( this, function( elem, method, val ) {
|
|
|
|
// Coalesce documents and windows
|
|
var win;
|
|
if ( isWindow( elem ) ) {
|
|
win = elem;
|
|
} else if ( elem.nodeType === 9 ) {
|
|
win = elem.defaultView;
|
|
}
|
|
|
|
if ( val === undefined ) {
|
|
return win ? win[ prop ] : elem[ method ];
|
|
}
|
|
|
|
if ( win ) {
|
|
win.scrollTo(
|
|
!top ? val : win.pageXOffset,
|
|
top ? val : win.pageYOffset
|
|
);
|
|
|
|
} else {
|
|
elem[ method ] = val;
|
|
}
|
|
}, method, val, arguments.length );
|
|
};
|
|
} );
|
|
|
|
// Support: Safari <=7 - 9.1, Chrome <=37 - 49
|
|
// Add the top/left cssHooks using jQuery.fn.position
|
|
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
|
|
// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
|
|
// getComputedStyle returns percent when specified for top/left/bottom/right;
|
|
// rather than make the css module depend on the offset module, just check for it here
|
|
jQuery.each( [ "top", "left" ], function( i, prop ) {
|
|
jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
|
|
function( elem, computed ) {
|
|
if ( computed ) {
|
|
computed = curCSS( elem, prop );
|
|
|
|
// If curCSS returns percentage, fallback to offset
|
|
return rnumnonpx.test( computed ) ?
|
|
jQuery( elem ).position()[ prop ] + "px" :
|
|
computed;
|
|
}
|
|
}
|
|
);
|
|
} );
|
|
|
|
|
|
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
|
|
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
|
|
jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
|
|
function( defaultExtra, funcName ) {
|
|
|
|
// Margin is only for outerHeight, outerWidth
|
|
jQuery.fn[ funcName ] = function( margin, value ) {
|
|
var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
|
|
extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
|
|
|
|
return access( this, function( elem, type, value ) {
|
|
var doc;
|
|
|
|
if ( isWindow( elem ) ) {
|
|
|
|
// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
|
|
return funcName.indexOf( "outer" ) === 0 ?
|
|
elem[ "inner" + name ] :
|
|
elem.document.documentElement[ "client" + name ];
|
|
}
|
|
|
|
// Get document width or height
|
|
if ( elem.nodeType === 9 ) {
|
|
doc = elem.documentElement;
|
|
|
|
// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
|
|
// whichever is greatest
|
|
return Math.max(
|
|
elem.body[ "scroll" + name ], doc[ "scroll" + name ],
|
|
elem.body[ "offset" + name ], doc[ "offset" + name ],
|
|
doc[ "client" + name ]
|
|
);
|
|
}
|
|
|
|
return value === undefined ?
|
|
|
|
// Get width or height on the element, requesting but not forcing parseFloat
|
|
jQuery.css( elem, type, extra ) :
|
|
|
|
// Set width or height on the element
|
|
jQuery.style( elem, type, value, extra );
|
|
}, type, chainable ? margin : undefined, chainable );
|
|
};
|
|
} );
|
|
} );
|
|
|
|
|
|
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
|
|
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
|
|
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
|
|
function( i, name ) {
|
|
|
|
// Handle event binding
|
|
jQuery.fn[ name ] = function( data, fn ) {
|
|
return arguments.length > 0 ?
|
|
this.on( name, null, data, fn ) :
|
|
this.trigger( name );
|
|
};
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
hover: function( fnOver, fnOut ) {
|
|
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
bind: function( types, data, fn ) {
|
|
return this.on( types, null, data, fn );
|
|
},
|
|
unbind: function( types, fn ) {
|
|
return this.off( types, null, fn );
|
|
},
|
|
|
|
delegate: function( selector, types, data, fn ) {
|
|
return this.on( types, selector, data, fn );
|
|
},
|
|
undelegate: function( selector, types, fn ) {
|
|
|
|
// ( namespace ) or ( selector, types [, fn] )
|
|
return arguments.length === 1 ?
|
|
this.off( selector, "**" ) :
|
|
this.off( types, selector || "**", fn );
|
|
}
|
|
} );
|
|
|
|
// Bind a function to a context, optionally partially applying any
|
|
// arguments.
|
|
// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
|
|
// However, it is not slated for removal any time soon
|
|
jQuery.proxy = function( fn, context ) {
|
|
var tmp, args, proxy;
|
|
|
|
if ( typeof context === "string" ) {
|
|
tmp = fn[ context ];
|
|
context = fn;
|
|
fn = tmp;
|
|
}
|
|
|
|
// Quick check to determine if target is callable, in the spec
|
|
// this throws a TypeError, but we will just return undefined.
|
|
if ( !isFunction( fn ) ) {
|
|
return undefined;
|
|
}
|
|
|
|
// Simulated bind
|
|
args = slice.call( arguments, 2 );
|
|
proxy = function() {
|
|
return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
|
|
};
|
|
|
|
// Set the guid of unique handler to the same of original handler, so it can be removed
|
|
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
|
|
|
|
return proxy;
|
|
};
|
|
|
|
jQuery.holdReady = function( hold ) {
|
|
if ( hold ) {
|
|
jQuery.readyWait++;
|
|
} else {
|
|
jQuery.ready( true );
|
|
}
|
|
};
|
|
jQuery.isArray = Array.isArray;
|
|
jQuery.parseJSON = JSON.parse;
|
|
jQuery.nodeName = nodeName;
|
|
jQuery.isFunction = isFunction;
|
|
jQuery.isWindow = isWindow;
|
|
jQuery.camelCase = camelCase;
|
|
jQuery.type = toType;
|
|
|
|
jQuery.now = Date.now;
|
|
|
|
jQuery.isNumeric = function( obj ) {
|
|
|
|
// As of jQuery 3.0, isNumeric is limited to
|
|
// strings and numbers (primitives or objects)
|
|
// that can be coerced to finite numbers (gh-2662)
|
|
var type = jQuery.type( obj );
|
|
return ( type === "number" || type === "string" ) &&
|
|
|
|
// parseFloat NaNs numeric-cast false positives ("")
|
|
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
|
|
// subtraction forces infinities to NaN
|
|
!isNaN( obj - parseFloat( obj ) );
|
|
};
|
|
|
|
|
|
|
|
|
|
// Register as a named AMD module, since jQuery can be concatenated with other
|
|
// files that may use define, but not via a proper concatenation script that
|
|
// understands anonymous AMD modules. A named AMD is safest and most robust
|
|
// way to register. Lowercase jquery is used because AMD module names are
|
|
// derived from file names, and jQuery is normally delivered in a lowercase
|
|
// file name. Do this after creating the global so that if an AMD module wants
|
|
// to call noConflict to hide this version of jQuery, it will work.
|
|
|
|
// Note that for maximum portability, libraries that are not jQuery should
|
|
// declare themselves as anonymous modules, and avoid setting a global if an
|
|
// AMD loader is present. jQuery is a special case. For more information, see
|
|
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
|
|
|
|
if ( typeof define === "function" && define.amd ) {
|
|
define( "jquery", [], function() {
|
|
return jQuery;
|
|
} );
|
|
}
|
|
|
|
|
|
|
|
|
|
var
|
|
|
|
// Map over jQuery in case of overwrite
|
|
_jQuery = window.jQuery,
|
|
|
|
// Map over the $ in case of overwrite
|
|
_$ = window.$;
|
|
|
|
jQuery.noConflict = function( deep ) {
|
|
if ( window.$ === jQuery ) {
|
|
window.$ = _$;
|
|
}
|
|
|
|
if ( deep && window.jQuery === jQuery ) {
|
|
window.jQuery = _jQuery;
|
|
}
|
|
|
|
return jQuery;
|
|
};
|
|
|
|
// Expose jQuery and $ identifiers, even in AMD
|
|
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
|
// and CommonJS for browser emulators (#13566)
|
|
if ( !noGlobal ) {
|
|
window.jQuery = window.$ = jQuery;
|
|
}
|
|
|
|
|
|
|
|
|
|
return jQuery;
|
|
} );
|
|
|
|
//! moment.js
|
|
|
|
;(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
global.moment = factory()
|
|
}(this, (function () { 'use strict';
|
|
|
|
var hookCallback;
|
|
|
|
function hooks () {
|
|
return hookCallback.apply(null, arguments);
|
|
}
|
|
|
|
// This is done to register the method called with moment()
|
|
// without creating circular dependencies.
|
|
function setHookCallback (callback) {
|
|
hookCallback = callback;
|
|
}
|
|
|
|
function isArray(input) {
|
|
return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
|
|
}
|
|
|
|
function isObject(input) {
|
|
// IE8 will treat undefined and null as object if it wasn't for
|
|
// input != null
|
|
return input != null && Object.prototype.toString.call(input) === '[object Object]';
|
|
}
|
|
|
|
function isObjectEmpty(obj) {
|
|
if (Object.getOwnPropertyNames) {
|
|
return (Object.getOwnPropertyNames(obj).length === 0);
|
|
} else {
|
|
var k;
|
|
for (k in obj) {
|
|
if (obj.hasOwnProperty(k)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function isUndefined(input) {
|
|
return input === void 0;
|
|
}
|
|
|
|
function isNumber(input) {
|
|
return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
|
|
}
|
|
|
|
function isDate(input) {
|
|
return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
}
|
|
|
|
function map(arr, fn) {
|
|
var res = [], i;
|
|
for (i = 0; i < arr.length; ++i) {
|
|
res.push(fn(arr[i], i));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function hasOwnProp(a, b) {
|
|
return Object.prototype.hasOwnProperty.call(a, b);
|
|
}
|
|
|
|
function extend(a, b) {
|
|
for (var i in b) {
|
|
if (hasOwnProp(b, i)) {
|
|
a[i] = b[i];
|
|
}
|
|
}
|
|
|
|
if (hasOwnProp(b, 'toString')) {
|
|
a.toString = b.toString;
|
|
}
|
|
|
|
if (hasOwnProp(b, 'valueOf')) {
|
|
a.valueOf = b.valueOf;
|
|
}
|
|
|
|
return a;
|
|
}
|
|
|
|
function createUTC (input, format, locale, strict) {
|
|
return createLocalOrUTC(input, format, locale, strict, true).utc();
|
|
}
|
|
|
|
function defaultParsingFlags() {
|
|
// We need to deep clone this object.
|
|
return {
|
|
empty : false,
|
|
unusedTokens : [],
|
|
unusedInput : [],
|
|
overflow : -2,
|
|
charsLeftOver : 0,
|
|
nullInput : false,
|
|
invalidMonth : null,
|
|
invalidFormat : false,
|
|
userInvalidated : false,
|
|
iso : false,
|
|
parsedDateParts : [],
|
|
meridiem : null,
|
|
rfc2822 : false,
|
|
weekdayMismatch : false
|
|
};
|
|
}
|
|
|
|
function getParsingFlags(m) {
|
|
if (m._pf == null) {
|
|
m._pf = defaultParsingFlags();
|
|
}
|
|
return m._pf;
|
|
}
|
|
|
|
var some;
|
|
if (Array.prototype.some) {
|
|
some = Array.prototype.some;
|
|
} else {
|
|
some = function (fun) {
|
|
var t = Object(this);
|
|
var len = t.length >>> 0;
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (i in t && fun.call(this, t[i], i, t)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
}
|
|
|
|
function isValid(m) {
|
|
if (m._isValid == null) {
|
|
var flags = getParsingFlags(m);
|
|
var parsedParts = some.call(flags.parsedDateParts, function (i) {
|
|
return i != null;
|
|
});
|
|
var isNowValid = !isNaN(m._d.getTime()) &&
|
|
flags.overflow < 0 &&
|
|
!flags.empty &&
|
|
!flags.invalidMonth &&
|
|
!flags.invalidWeekday &&
|
|
!flags.weekdayMismatch &&
|
|
!flags.nullInput &&
|
|
!flags.invalidFormat &&
|
|
!flags.userInvalidated &&
|
|
(!flags.meridiem || (flags.meridiem && parsedParts));
|
|
|
|
if (m._strict) {
|
|
isNowValid = isNowValid &&
|
|
flags.charsLeftOver === 0 &&
|
|
flags.unusedTokens.length === 0 &&
|
|
flags.bigHour === undefined;
|
|
}
|
|
|
|
if (Object.isFrozen == null || !Object.isFrozen(m)) {
|
|
m._isValid = isNowValid;
|
|
}
|
|
else {
|
|
return isNowValid;
|
|
}
|
|
}
|
|
return m._isValid;
|
|
}
|
|
|
|
function createInvalid (flags) {
|
|
var m = createUTC(NaN);
|
|
if (flags != null) {
|
|
extend(getParsingFlags(m), flags);
|
|
}
|
|
else {
|
|
getParsingFlags(m).userInvalidated = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
// Plugins that add properties should also add the key here (null value),
|
|
// so we can properly clone ourselves.
|
|
var momentProperties = hooks.momentProperties = [];
|
|
|
|
function copyConfig(to, from) {
|
|
var i, prop, val;
|
|
|
|
if (!isUndefined(from._isAMomentObject)) {
|
|
to._isAMomentObject = from._isAMomentObject;
|
|
}
|
|
if (!isUndefined(from._i)) {
|
|
to._i = from._i;
|
|
}
|
|
if (!isUndefined(from._f)) {
|
|
to._f = from._f;
|
|
}
|
|
if (!isUndefined(from._l)) {
|
|
to._l = from._l;
|
|
}
|
|
if (!isUndefined(from._strict)) {
|
|
to._strict = from._strict;
|
|
}
|
|
if (!isUndefined(from._tzm)) {
|
|
to._tzm = from._tzm;
|
|
}
|
|
if (!isUndefined(from._isUTC)) {
|
|
to._isUTC = from._isUTC;
|
|
}
|
|
if (!isUndefined(from._offset)) {
|
|
to._offset = from._offset;
|
|
}
|
|
if (!isUndefined(from._pf)) {
|
|
to._pf = getParsingFlags(from);
|
|
}
|
|
if (!isUndefined(from._locale)) {
|
|
to._locale = from._locale;
|
|
}
|
|
|
|
if (momentProperties.length > 0) {
|
|
for (i = 0; i < momentProperties.length; i++) {
|
|
prop = momentProperties[i];
|
|
val = from[prop];
|
|
if (!isUndefined(val)) {
|
|
to[prop] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
return to;
|
|
}
|
|
|
|
var updateInProgress = false;
|
|
|
|
// Moment prototype object
|
|
function Moment(config) {
|
|
copyConfig(this, config);
|
|
this._d = new Date(config._d != null ? config._d.getTime() : NaN);
|
|
if (!this.isValid()) {
|
|
this._d = new Date(NaN);
|
|
}
|
|
// Prevent infinite loop in case updateOffset creates new moment
|
|
// objects.
|
|
if (updateInProgress === false) {
|
|
updateInProgress = true;
|
|
hooks.updateOffset(this);
|
|
updateInProgress = false;
|
|
}
|
|
}
|
|
|
|
function isMoment (obj) {
|
|
return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
|
|
}
|
|
|
|
function absFloor (number) {
|
|
if (number < 0) {
|
|
// -0 -> 0
|
|
return Math.ceil(number) || 0;
|
|
} else {
|
|
return Math.floor(number);
|
|
}
|
|
}
|
|
|
|
function toInt(argumentForCoercion) {
|
|
var coercedNumber = +argumentForCoercion,
|
|
value = 0;
|
|
|
|
if (coercedNumber !== 0 && isFinite(coercedNumber)) {
|
|
value = absFloor(coercedNumber);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// compare two arrays, return the number of differences
|
|
function compareArrays(array1, array2, dontConvert) {
|
|
var len = Math.min(array1.length, array2.length),
|
|
lengthDiff = Math.abs(array1.length - array2.length),
|
|
diffs = 0,
|
|
i;
|
|
for (i = 0; i < len; i++) {
|
|
if ((dontConvert && array1[i] !== array2[i]) ||
|
|
(!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
|
|
diffs++;
|
|
}
|
|
}
|
|
return diffs + lengthDiff;
|
|
}
|
|
|
|
function warn(msg) {
|
|
if (hooks.suppressDeprecationWarnings === false &&
|
|
(typeof console !== 'undefined') && console.warn) {
|
|
console.warn('Deprecation warning: ' + msg);
|
|
}
|
|
}
|
|
|
|
function deprecate(msg, fn) {
|
|
var firstTime = true;
|
|
|
|
return extend(function () {
|
|
if (hooks.deprecationHandler != null) {
|
|
hooks.deprecationHandler(null, msg);
|
|
}
|
|
if (firstTime) {
|
|
var args = [];
|
|
var arg;
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
arg = '';
|
|
if (typeof arguments[i] === 'object') {
|
|
arg += '\n[' + i + '] ';
|
|
for (var key in arguments[0]) {
|
|
arg += key + ': ' + arguments[0][key] + ', ';
|
|
}
|
|
arg = arg.slice(0, -2); // Remove trailing comma and space
|
|
} else {
|
|
arg = arguments[i];
|
|
}
|
|
args.push(arg);
|
|
}
|
|
warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
|
|
firstTime = false;
|
|
}
|
|
return fn.apply(this, arguments);
|
|
}, fn);
|
|
}
|
|
|
|
var deprecations = {};
|
|
|
|
function deprecateSimple(name, msg) {
|
|
if (hooks.deprecationHandler != null) {
|
|
hooks.deprecationHandler(name, msg);
|
|
}
|
|
if (!deprecations[name]) {
|
|
warn(msg);
|
|
deprecations[name] = true;
|
|
}
|
|
}
|
|
|
|
hooks.suppressDeprecationWarnings = false;
|
|
hooks.deprecationHandler = null;
|
|
|
|
function isFunction(input) {
|
|
return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
|
|
}
|
|
|
|
function set (config) {
|
|
var prop, i;
|
|
for (i in config) {
|
|
prop = config[i];
|
|
if (isFunction(prop)) {
|
|
this[i] = prop;
|
|
} else {
|
|
this['_' + i] = prop;
|
|
}
|
|
}
|
|
this._config = config;
|
|
// Lenient ordinal parsing accepts just a number in addition to
|
|
// number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
|
|
// TODO: Remove "ordinalParse" fallback in next major release.
|
|
this._dayOfMonthOrdinalParseLenient = new RegExp(
|
|
(this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
|
|
'|' + (/\d{1,2}/).source);
|
|
}
|
|
|
|
function mergeConfigs(parentConfig, childConfig) {
|
|
var res = extend({}, parentConfig), prop;
|
|
for (prop in childConfig) {
|
|
if (hasOwnProp(childConfig, prop)) {
|
|
if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
|
|
res[prop] = {};
|
|
extend(res[prop], parentConfig[prop]);
|
|
extend(res[prop], childConfig[prop]);
|
|
} else if (childConfig[prop] != null) {
|
|
res[prop] = childConfig[prop];
|
|
} else {
|
|
delete res[prop];
|
|
}
|
|
}
|
|
}
|
|
for (prop in parentConfig) {
|
|
if (hasOwnProp(parentConfig, prop) &&
|
|
!hasOwnProp(childConfig, prop) &&
|
|
isObject(parentConfig[prop])) {
|
|
// make sure changes to properties don't modify parent config
|
|
res[prop] = extend({}, res[prop]);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function Locale(config) {
|
|
if (config != null) {
|
|
this.set(config);
|
|
}
|
|
}
|
|
|
|
var keys;
|
|
|
|
if (Object.keys) {
|
|
keys = Object.keys;
|
|
} else {
|
|
keys = function (obj) {
|
|
var i, res = [];
|
|
for (i in obj) {
|
|
if (hasOwnProp(obj, i)) {
|
|
res.push(i);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
}
|
|
|
|
var defaultCalendar = {
|
|
sameDay : '[Today at] LT',
|
|
nextDay : '[Tomorrow at] LT',
|
|
nextWeek : 'dddd [at] LT',
|
|
lastDay : '[Yesterday at] LT',
|
|
lastWeek : '[Last] dddd [at] LT',
|
|
sameElse : 'L'
|
|
};
|
|
|
|
function calendar (key, mom, now) {
|
|
var output = this._calendar[key] || this._calendar['sameElse'];
|
|
return isFunction(output) ? output.call(mom, now) : output;
|
|
}
|
|
|
|
var defaultLongDateFormat = {
|
|
LTS : 'h:mm:ss A',
|
|
LT : 'h:mm A',
|
|
L : 'MM/DD/YYYY',
|
|
LL : 'MMMM D, YYYY',
|
|
LLL : 'MMMM D, YYYY h:mm A',
|
|
LLLL : 'dddd, MMMM D, YYYY h:mm A'
|
|
};
|
|
|
|
function longDateFormat (key) {
|
|
var format = this._longDateFormat[key],
|
|
formatUpper = this._longDateFormat[key.toUpperCase()];
|
|
|
|
if (format || !formatUpper) {
|
|
return format;
|
|
}
|
|
|
|
this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
|
|
return val.slice(1);
|
|
});
|
|
|
|
return this._longDateFormat[key];
|
|
}
|
|
|
|
var defaultInvalidDate = 'Invalid date';
|
|
|
|
function invalidDate () {
|
|
return this._invalidDate;
|
|
}
|
|
|
|
var defaultOrdinal = '%d';
|
|
var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
|
|
|
|
function ordinal (number) {
|
|
return this._ordinal.replace('%d', number);
|
|
}
|
|
|
|
var defaultRelativeTime = {
|
|
future : 'in %s',
|
|
past : '%s ago',
|
|
s : 'a few seconds',
|
|
ss : '%d seconds',
|
|
m : 'a minute',
|
|
mm : '%d minutes',
|
|
h : 'an hour',
|
|
hh : '%d hours',
|
|
d : 'a day',
|
|
dd : '%d days',
|
|
M : 'a month',
|
|
MM : '%d months',
|
|
y : 'a year',
|
|
yy : '%d years'
|
|
};
|
|
|
|
function relativeTime (number, withoutSuffix, string, isFuture) {
|
|
var output = this._relativeTime[string];
|
|
return (isFunction(output)) ?
|
|
output(number, withoutSuffix, string, isFuture) :
|
|
output.replace(/%d/i, number);
|
|
}
|
|
|
|
function pastFuture (diff, output) {
|
|
var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
|
|
return isFunction(format) ? format(output) : format.replace(/%s/i, output);
|
|
}
|
|
|
|
var aliases = {};
|
|
|
|
function addUnitAlias (unit, shorthand) {
|
|
var lowerCase = unit.toLowerCase();
|
|
aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
|
|
}
|
|
|
|
function normalizeUnits(units) {
|
|
return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
|
|
}
|
|
|
|
function normalizeObjectUnits(inputObject) {
|
|
var normalizedInput = {},
|
|
normalizedProp,
|
|
prop;
|
|
|
|
for (prop in inputObject) {
|
|
if (hasOwnProp(inputObject, prop)) {
|
|
normalizedProp = normalizeUnits(prop);
|
|
if (normalizedProp) {
|
|
normalizedInput[normalizedProp] = inputObject[prop];
|
|
}
|
|
}
|
|
}
|
|
|
|
return normalizedInput;
|
|
}
|
|
|
|
var priorities = {};
|
|
|
|
function addUnitPriority(unit, priority) {
|
|
priorities[unit] = priority;
|
|
}
|
|
|
|
function getPrioritizedUnits(unitsObj) {
|
|
var units = [];
|
|
for (var u in unitsObj) {
|
|
units.push({unit: u, priority: priorities[u]});
|
|
}
|
|
units.sort(function (a, b) {
|
|
return a.priority - b.priority;
|
|
});
|
|
return units;
|
|
}
|
|
|
|
function zeroFill(number, targetLength, forceSign) {
|
|
var absNumber = '' + Math.abs(number),
|
|
zerosToFill = targetLength - absNumber.length,
|
|
sign = number >= 0;
|
|
return (sign ? (forceSign ? '+' : '') : '-') +
|
|
Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
|
|
}
|
|
|
|
var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
|
|
|
|
var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
|
|
|
|
var formatFunctions = {};
|
|
|
|
var formatTokenFunctions = {};
|
|
|
|
// token: 'M'
|
|
// padded: ['MM', 2]
|
|
// ordinal: 'Mo'
|
|
// callback: function () { this.month() + 1 }
|
|
function addFormatToken (token, padded, ordinal, callback) {
|
|
var func = callback;
|
|
if (typeof callback === 'string') {
|
|
func = function () {
|
|
return this[callback]();
|
|
};
|
|
}
|
|
if (token) {
|
|
formatTokenFunctions[token] = func;
|
|
}
|
|
if (padded) {
|
|
formatTokenFunctions[padded[0]] = function () {
|
|
return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
|
|
};
|
|
}
|
|
if (ordinal) {
|
|
formatTokenFunctions[ordinal] = function () {
|
|
return this.localeData().ordinal(func.apply(this, arguments), token);
|
|
};
|
|
}
|
|
}
|
|
|
|
function removeFormattingTokens(input) {
|
|
if (input.match(/\[[\s\S]/)) {
|
|
return input.replace(/^\[|\]$/g, '');
|
|
}
|
|
return input.replace(/\\/g, '');
|
|
}
|
|
|
|
function makeFormatFunction(format) {
|
|
var array = format.match(formattingTokens), i, length;
|
|
|
|
for (i = 0, length = array.length; i < length; i++) {
|
|
if (formatTokenFunctions[array[i]]) {
|
|
array[i] = formatTokenFunctions[array[i]];
|
|
} else {
|
|
array[i] = removeFormattingTokens(array[i]);
|
|
}
|
|
}
|
|
|
|
return function (mom) {
|
|
var output = '', i;
|
|
for (i = 0; i < length; i++) {
|
|
output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
|
|
}
|
|
return output;
|
|
};
|
|
}
|
|
|
|
// format date using native date object
|
|
function formatMoment(m, format) {
|
|
if (!m.isValid()) {
|
|
return m.localeData().invalidDate();
|
|
}
|
|
|
|
format = expandFormat(format, m.localeData());
|
|
formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
|
|
|
|
return formatFunctions[format](m);
|
|
}
|
|
|
|
function expandFormat(format, locale) {
|
|
var i = 5;
|
|
|
|
function replaceLongDateFormatTokens(input) {
|
|
return locale.longDateFormat(input) || input;
|
|
}
|
|
|
|
localFormattingTokens.lastIndex = 0;
|
|
while (i >= 0 && localFormattingTokens.test(format)) {
|
|
format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
|
|
localFormattingTokens.lastIndex = 0;
|
|
i -= 1;
|
|
}
|
|
|
|
return format;
|
|
}
|
|
|
|
var match1 = /\d/; // 0 - 9
|
|
var match2 = /\d\d/; // 00 - 99
|
|
var match3 = /\d{3}/; // 000 - 999
|
|
var match4 = /\d{4}/; // 0000 - 9999
|
|
var match6 = /[+-]?\d{6}/; // -999999 - 999999
|
|
var match1to2 = /\d\d?/; // 0 - 99
|
|
var match3to4 = /\d\d\d\d?/; // 999 - 9999
|
|
var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
|
|
var match1to3 = /\d{1,3}/; // 0 - 999
|
|
var match1to4 = /\d{1,4}/; // 0 - 9999
|
|
var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
|
|
|
|
var matchUnsigned = /\d+/; // 0 - inf
|
|
var matchSigned = /[+-]?\d+/; // -inf - inf
|
|
|
|
var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
|
|
var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
|
|
|
|
var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
|
|
|
|
// any word (or two) characters or numbers including two/three word month in arabic.
|
|
// includes scottish gaelic two word and hyphenated months
|
|
var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
|
|
|
|
var regexes = {};
|
|
|
|
function addRegexToken (token, regex, strictRegex) {
|
|
regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
|
|
return (isStrict && strictRegex) ? strictRegex : regex;
|
|
};
|
|
}
|
|
|
|
function getParseRegexForToken (token, config) {
|
|
if (!hasOwnProp(regexes, token)) {
|
|
return new RegExp(unescapeFormat(token));
|
|
}
|
|
|
|
return regexes[token](config._strict, config._locale);
|
|
}
|
|
|
|
// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
|
|
function unescapeFormat(s) {
|
|
return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
|
|
return p1 || p2 || p3 || p4;
|
|
}));
|
|
}
|
|
|
|
function regexEscape(s) {
|
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
}
|
|
|
|
var tokens = {};
|
|
|
|
function addParseToken (token, callback) {
|
|
var i, func = callback;
|
|
if (typeof token === 'string') {
|
|
token = [token];
|
|
}
|
|
if (isNumber(callback)) {
|
|
func = function (input, array) {
|
|
array[callback] = toInt(input);
|
|
};
|
|
}
|
|
for (i = 0; i < token.length; i++) {
|
|
tokens[token[i]] = func;
|
|
}
|
|
}
|
|
|
|
function addWeekParseToken (token, callback) {
|
|
addParseToken(token, function (input, array, config, token) {
|
|
config._w = config._w || {};
|
|
callback(input, config._w, config, token);
|
|
});
|
|
}
|
|
|
|
function addTimeToArrayFromToken(token, input, config) {
|
|
if (input != null && hasOwnProp(tokens, token)) {
|
|
tokens[token](input, config._a, config, token);
|
|
}
|
|
}
|
|
|
|
var YEAR = 0;
|
|
var MONTH = 1;
|
|
var DATE = 2;
|
|
var HOUR = 3;
|
|
var MINUTE = 4;
|
|
var SECOND = 5;
|
|
var MILLISECOND = 6;
|
|
var WEEK = 7;
|
|
var WEEKDAY = 8;
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('Y', 0, 0, function () {
|
|
var y = this.year();
|
|
return y <= 9999 ? '' + y : '+' + y;
|
|
});
|
|
|
|
addFormatToken(0, ['YY', 2], 0, function () {
|
|
return this.year() % 100;
|
|
});
|
|
|
|
addFormatToken(0, ['YYYY', 4], 0, 'year');
|
|
addFormatToken(0, ['YYYYY', 5], 0, 'year');
|
|
addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('year', 'y');
|
|
|
|
// PRIORITIES
|
|
|
|
addUnitPriority('year', 1);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Y', matchSigned);
|
|
addRegexToken('YY', match1to2, match2);
|
|
addRegexToken('YYYY', match1to4, match4);
|
|
addRegexToken('YYYYY', match1to6, match6);
|
|
addRegexToken('YYYYYY', match1to6, match6);
|
|
|
|
addParseToken(['YYYYY', 'YYYYYY'], YEAR);
|
|
addParseToken('YYYY', function (input, array) {
|
|
array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
|
|
});
|
|
addParseToken('YY', function (input, array) {
|
|
array[YEAR] = hooks.parseTwoDigitYear(input);
|
|
});
|
|
addParseToken('Y', function (input, array) {
|
|
array[YEAR] = parseInt(input, 10);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
function daysInYear(year) {
|
|
return isLeapYear(year) ? 366 : 365;
|
|
}
|
|
|
|
function isLeapYear(year) {
|
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
}
|
|
|
|
// HOOKS
|
|
|
|
hooks.parseTwoDigitYear = function (input) {
|
|
return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
|
|
};
|
|
|
|
// MOMENTS
|
|
|
|
var getSetYear = makeGetSet('FullYear', true);
|
|
|
|
function getIsLeapYear () {
|
|
return isLeapYear(this.year());
|
|
}
|
|
|
|
function makeGetSet (unit, keepTime) {
|
|
return function (value) {
|
|
if (value != null) {
|
|
set$1(this, unit, value);
|
|
hooks.updateOffset(this, keepTime);
|
|
return this;
|
|
} else {
|
|
return get(this, unit);
|
|
}
|
|
};
|
|
}
|
|
|
|
function get (mom, unit) {
|
|
return mom.isValid() ?
|
|
mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
|
|
}
|
|
|
|
function set$1 (mom, unit, value) {
|
|
if (mom.isValid() && !isNaN(value)) {
|
|
if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
|
|
}
|
|
else {
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function stringGet (units) {
|
|
units = normalizeUnits(units);
|
|
if (isFunction(this[units])) {
|
|
return this[units]();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
|
|
function stringSet (units, value) {
|
|
if (typeof units === 'object') {
|
|
units = normalizeObjectUnits(units);
|
|
var prioritized = getPrioritizedUnits(units);
|
|
for (var i = 0; i < prioritized.length; i++) {
|
|
this[prioritized[i].unit](units[prioritized[i].unit]);
|
|
}
|
|
} else {
|
|
units = normalizeUnits(units);
|
|
if (isFunction(this[units])) {
|
|
return this[units](value);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function mod(n, x) {
|
|
return ((n % x) + x) % x;
|
|
}
|
|
|
|
var indexOf;
|
|
|
|
if (Array.prototype.indexOf) {
|
|
indexOf = Array.prototype.indexOf;
|
|
} else {
|
|
indexOf = function (o) {
|
|
// I know
|
|
var i;
|
|
for (i = 0; i < this.length; ++i) {
|
|
if (this[i] === o) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
function daysInMonth(year, month) {
|
|
if (isNaN(year) || isNaN(month)) {
|
|
return NaN;
|
|
}
|
|
var modMonth = mod(month, 12);
|
|
year += (month - modMonth) / 12;
|
|
return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('M', ['MM', 2], 'Mo', function () {
|
|
return this.month() + 1;
|
|
});
|
|
|
|
addFormatToken('MMM', 0, 0, function (format) {
|
|
return this.localeData().monthsShort(this, format);
|
|
});
|
|
|
|
addFormatToken('MMMM', 0, 0, function (format) {
|
|
return this.localeData().months(this, format);
|
|
});
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('month', 'M');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('month', 8);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('M', match1to2);
|
|
addRegexToken('MM', match1to2, match2);
|
|
addRegexToken('MMM', function (isStrict, locale) {
|
|
return locale.monthsShortRegex(isStrict);
|
|
});
|
|
addRegexToken('MMMM', function (isStrict, locale) {
|
|
return locale.monthsRegex(isStrict);
|
|
});
|
|
|
|
addParseToken(['M', 'MM'], function (input, array) {
|
|
array[MONTH] = toInt(input) - 1;
|
|
});
|
|
|
|
addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
|
|
var month = config._locale.monthsParse(input, token, config._strict);
|
|
// if we didn't find a month name, mark the date as invalid.
|
|
if (month != null) {
|
|
array[MONTH] = month;
|
|
} else {
|
|
getParsingFlags(config).invalidMonth = input;
|
|
}
|
|
});
|
|
|
|
// LOCALES
|
|
|
|
var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
|
|
var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
|
|
function localeMonths (m, format) {
|
|
if (!m) {
|
|
return isArray(this._months) ? this._months :
|
|
this._months['standalone'];
|
|
}
|
|
return isArray(this._months) ? this._months[m.month()] :
|
|
this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
|
|
}
|
|
|
|
var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
|
|
function localeMonthsShort (m, format) {
|
|
if (!m) {
|
|
return isArray(this._monthsShort) ? this._monthsShort :
|
|
this._monthsShort['standalone'];
|
|
}
|
|
return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
|
|
this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
|
|
}
|
|
|
|
function handleStrictParse(monthName, format, strict) {
|
|
var i, ii, mom, llc = monthName.toLocaleLowerCase();
|
|
if (!this._monthsParse) {
|
|
// this is not used
|
|
this._monthsParse = [];
|
|
this._longMonthsParse = [];
|
|
this._shortMonthsParse = [];
|
|
for (i = 0; i < 12; ++i) {
|
|
mom = createUTC([2000, i]);
|
|
this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
|
|
this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
|
|
}
|
|
}
|
|
|
|
if (strict) {
|
|
if (format === 'MMM') {
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
} else {
|
|
if (format === 'MMM') {
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function localeMonthsParse (monthName, format, strict) {
|
|
var i, mom, regex;
|
|
|
|
if (this._monthsParseExact) {
|
|
return handleStrictParse.call(this, monthName, format, strict);
|
|
}
|
|
|
|
if (!this._monthsParse) {
|
|
this._monthsParse = [];
|
|
this._longMonthsParse = [];
|
|
this._shortMonthsParse = [];
|
|
}
|
|
|
|
// TODO: add sorting
|
|
// Sorting makes sure if one month (or abbr) is a prefix of another
|
|
// see sorting in computeMonthsParse
|
|
for (i = 0; i < 12; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, i]);
|
|
if (strict && !this._longMonthsParse[i]) {
|
|
this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
|
|
this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
|
|
}
|
|
if (!strict && !this._monthsParse[i]) {
|
|
regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
|
|
this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
|
|
}
|
|
// test the regex
|
|
if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
|
|
return i;
|
|
} else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
|
|
return i;
|
|
} else if (!strict && this._monthsParse[i].test(monthName)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function setMonth (mom, value) {
|
|
var dayOfMonth;
|
|
|
|
if (!mom.isValid()) {
|
|
// No op
|
|
return mom;
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
if (/^\d+$/.test(value)) {
|
|
value = toInt(value);
|
|
} else {
|
|
value = mom.localeData().monthsParse(value);
|
|
// TODO: Another silent failure?
|
|
if (!isNumber(value)) {
|
|
return mom;
|
|
}
|
|
}
|
|
}
|
|
|
|
dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
|
|
return mom;
|
|
}
|
|
|
|
function getSetMonth (value) {
|
|
if (value != null) {
|
|
setMonth(this, value);
|
|
hooks.updateOffset(this, true);
|
|
return this;
|
|
} else {
|
|
return get(this, 'Month');
|
|
}
|
|
}
|
|
|
|
function getDaysInMonth () {
|
|
return daysInMonth(this.year(), this.month());
|
|
}
|
|
|
|
var defaultMonthsShortRegex = matchWord;
|
|
function monthsShortRegex (isStrict) {
|
|
if (this._monthsParseExact) {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
computeMonthsParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._monthsShortStrictRegex;
|
|
} else {
|
|
return this._monthsShortRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_monthsShortRegex')) {
|
|
this._monthsShortRegex = defaultMonthsShortRegex;
|
|
}
|
|
return this._monthsShortStrictRegex && isStrict ?
|
|
this._monthsShortStrictRegex : this._monthsShortRegex;
|
|
}
|
|
}
|
|
|
|
var defaultMonthsRegex = matchWord;
|
|
function monthsRegex (isStrict) {
|
|
if (this._monthsParseExact) {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
computeMonthsParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._monthsStrictRegex;
|
|
} else {
|
|
return this._monthsRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
this._monthsRegex = defaultMonthsRegex;
|
|
}
|
|
return this._monthsStrictRegex && isStrict ?
|
|
this._monthsStrictRegex : this._monthsRegex;
|
|
}
|
|
}
|
|
|
|
function computeMonthsParse () {
|
|
function cmpLenRev(a, b) {
|
|
return b.length - a.length;
|
|
}
|
|
|
|
var shortPieces = [], longPieces = [], mixedPieces = [],
|
|
i, mom;
|
|
for (i = 0; i < 12; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, i]);
|
|
shortPieces.push(this.monthsShort(mom, ''));
|
|
longPieces.push(this.months(mom, ''));
|
|
mixedPieces.push(this.months(mom, ''));
|
|
mixedPieces.push(this.monthsShort(mom, ''));
|
|
}
|
|
// Sorting makes sure if one month (or abbr) is a prefix of another it
|
|
// will match the longer piece.
|
|
shortPieces.sort(cmpLenRev);
|
|
longPieces.sort(cmpLenRev);
|
|
mixedPieces.sort(cmpLenRev);
|
|
for (i = 0; i < 12; i++) {
|
|
shortPieces[i] = regexEscape(shortPieces[i]);
|
|
longPieces[i] = regexEscape(longPieces[i]);
|
|
}
|
|
for (i = 0; i < 24; i++) {
|
|
mixedPieces[i] = regexEscape(mixedPieces[i]);
|
|
}
|
|
|
|
this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
|
|
this._monthsShortRegex = this._monthsRegex;
|
|
this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
|
|
this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
|
|
}
|
|
|
|
function createDate (y, m, d, h, M, s, ms) {
|
|
// can't just apply() to create a date:
|
|
// https://stackoverflow.com/q/181348
|
|
var date = new Date(y, m, d, h, M, s, ms);
|
|
|
|
// the date constructor remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
|
|
date.setFullYear(y);
|
|
}
|
|
return date;
|
|
}
|
|
|
|
function createUTCDate (y) {
|
|
var date = new Date(Date.UTC.apply(null, arguments));
|
|
|
|
// the Date.UTC function remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
|
|
date.setUTCFullYear(y);
|
|
}
|
|
return date;
|
|
}
|
|
|
|
// start-of-first-week - start-of-year
|
|
function firstWeekOffset(year, dow, doy) {
|
|
var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
|
|
fwd = 7 + dow - doy,
|
|
// first-week day local weekday -- which local weekday is fwd
|
|
fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
|
|
|
|
return -fwdlw + fwd - 1;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
|
|
function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
|
|
var localWeekday = (7 + weekday - dow) % 7,
|
|
weekOffset = firstWeekOffset(year, dow, doy),
|
|
dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
|
|
resYear, resDayOfYear;
|
|
|
|
if (dayOfYear <= 0) {
|
|
resYear = year - 1;
|
|
resDayOfYear = daysInYear(resYear) + dayOfYear;
|
|
} else if (dayOfYear > daysInYear(year)) {
|
|
resYear = year + 1;
|
|
resDayOfYear = dayOfYear - daysInYear(year);
|
|
} else {
|
|
resYear = year;
|
|
resDayOfYear = dayOfYear;
|
|
}
|
|
|
|
return {
|
|
year: resYear,
|
|
dayOfYear: resDayOfYear
|
|
};
|
|
}
|
|
|
|
function weekOfYear(mom, dow, doy) {
|
|
var weekOffset = firstWeekOffset(mom.year(), dow, doy),
|
|
week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
|
|
resWeek, resYear;
|
|
|
|
if (week < 1) {
|
|
resYear = mom.year() - 1;
|
|
resWeek = week + weeksInYear(resYear, dow, doy);
|
|
} else if (week > weeksInYear(mom.year(), dow, doy)) {
|
|
resWeek = week - weeksInYear(mom.year(), dow, doy);
|
|
resYear = mom.year() + 1;
|
|
} else {
|
|
resYear = mom.year();
|
|
resWeek = week;
|
|
}
|
|
|
|
return {
|
|
week: resWeek,
|
|
year: resYear
|
|
};
|
|
}
|
|
|
|
function weeksInYear(year, dow, doy) {
|
|
var weekOffset = firstWeekOffset(year, dow, doy),
|
|
weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
|
|
return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('w', ['ww', 2], 'wo', 'week');
|
|
addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('week', 'w');
|
|
addUnitAlias('isoWeek', 'W');
|
|
|
|
// PRIORITIES
|
|
|
|
addUnitPriority('week', 5);
|
|
addUnitPriority('isoWeek', 5);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('w', match1to2);
|
|
addRegexToken('ww', match1to2, match2);
|
|
addRegexToken('W', match1to2);
|
|
addRegexToken('WW', match1to2, match2);
|
|
|
|
addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
|
|
week[token.substr(0, 1)] = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// LOCALES
|
|
|
|
function localeWeek (mom) {
|
|
return weekOfYear(mom, this._week.dow, this._week.doy).week;
|
|
}
|
|
|
|
var defaultLocaleWeek = {
|
|
dow : 0, // Sunday is the first day of the week.
|
|
doy : 6 // The week that contains Jan 1st is the first week of the year.
|
|
};
|
|
|
|
function localeFirstDayOfWeek () {
|
|
return this._week.dow;
|
|
}
|
|
|
|
function localeFirstDayOfYear () {
|
|
return this._week.doy;
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function getSetWeek (input) {
|
|
var week = this.localeData().week(this);
|
|
return input == null ? week : this.add((input - week) * 7, 'd');
|
|
}
|
|
|
|
function getSetISOWeek (input) {
|
|
var week = weekOfYear(this, 1, 4).week;
|
|
return input == null ? week : this.add((input - week) * 7, 'd');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('d', 0, 'do', 'day');
|
|
|
|
addFormatToken('dd', 0, 0, function (format) {
|
|
return this.localeData().weekdaysMin(this, format);
|
|
});
|
|
|
|
addFormatToken('ddd', 0, 0, function (format) {
|
|
return this.localeData().weekdaysShort(this, format);
|
|
});
|
|
|
|
addFormatToken('dddd', 0, 0, function (format) {
|
|
return this.localeData().weekdays(this, format);
|
|
});
|
|
|
|
addFormatToken('e', 0, 0, 'weekday');
|
|
addFormatToken('E', 0, 0, 'isoWeekday');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('day', 'd');
|
|
addUnitAlias('weekday', 'e');
|
|
addUnitAlias('isoWeekday', 'E');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('day', 11);
|
|
addUnitPriority('weekday', 11);
|
|
addUnitPriority('isoWeekday', 11);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('d', match1to2);
|
|
addRegexToken('e', match1to2);
|
|
addRegexToken('E', match1to2);
|
|
addRegexToken('dd', function (isStrict, locale) {
|
|
return locale.weekdaysMinRegex(isStrict);
|
|
});
|
|
addRegexToken('ddd', function (isStrict, locale) {
|
|
return locale.weekdaysShortRegex(isStrict);
|
|
});
|
|
addRegexToken('dddd', function (isStrict, locale) {
|
|
return locale.weekdaysRegex(isStrict);
|
|
});
|
|
|
|
addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
|
|
var weekday = config._locale.weekdaysParse(input, token, config._strict);
|
|
// if we didn't get a weekday name, mark the date as invalid
|
|
if (weekday != null) {
|
|
week.d = weekday;
|
|
} else {
|
|
getParsingFlags(config).invalidWeekday = input;
|
|
}
|
|
});
|
|
|
|
addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
|
|
week[token] = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
function parseWeekday(input, locale) {
|
|
if (typeof input !== 'string') {
|
|
return input;
|
|
}
|
|
|
|
if (!isNaN(input)) {
|
|
return parseInt(input, 10);
|
|
}
|
|
|
|
input = locale.weekdaysParse(input);
|
|
if (typeof input === 'number') {
|
|
return input;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function parseIsoWeekday(input, locale) {
|
|
if (typeof input === 'string') {
|
|
return locale.weekdaysParse(input) % 7 || 7;
|
|
}
|
|
return isNaN(input) ? null : input;
|
|
}
|
|
|
|
// LOCALES
|
|
|
|
var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
|
|
function localeWeekdays (m, format) {
|
|
if (!m) {
|
|
return isArray(this._weekdays) ? this._weekdays :
|
|
this._weekdays['standalone'];
|
|
}
|
|
return isArray(this._weekdays) ? this._weekdays[m.day()] :
|
|
this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
|
|
}
|
|
|
|
var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
|
|
function localeWeekdaysShort (m) {
|
|
return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
|
|
}
|
|
|
|
var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
|
|
function localeWeekdaysMin (m) {
|
|
return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
|
|
}
|
|
|
|
function handleStrictParse$1(weekdayName, format, strict) {
|
|
var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
|
|
if (!this._weekdaysParse) {
|
|
this._weekdaysParse = [];
|
|
this._shortWeekdaysParse = [];
|
|
this._minWeekdaysParse = [];
|
|
|
|
for (i = 0; i < 7; ++i) {
|
|
mom = createUTC([2000, 1]).day(i);
|
|
this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
|
|
this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
|
|
this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
|
|
}
|
|
}
|
|
|
|
if (strict) {
|
|
if (format === 'dddd') {
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else if (format === 'ddd') {
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
} else {
|
|
if (format === 'dddd') {
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else if (format === 'ddd') {
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function localeWeekdaysParse (weekdayName, format, strict) {
|
|
var i, mom, regex;
|
|
|
|
if (this._weekdaysParseExact) {
|
|
return handleStrictParse$1.call(this, weekdayName, format, strict);
|
|
}
|
|
|
|
if (!this._weekdaysParse) {
|
|
this._weekdaysParse = [];
|
|
this._minWeekdaysParse = [];
|
|
this._shortWeekdaysParse = [];
|
|
this._fullWeekdaysParse = [];
|
|
}
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
// make the regex if we don't have it already
|
|
|
|
mom = createUTC([2000, 1]).day(i);
|
|
if (strict && !this._fullWeekdaysParse[i]) {
|
|
this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
|
|
this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
|
|
this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
|
|
}
|
|
if (!this._weekdaysParse[i]) {
|
|
regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
|
|
this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
|
|
}
|
|
// test the regex
|
|
if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function getSetDayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
|
|
if (input != null) {
|
|
input = parseWeekday(input, this.localeData());
|
|
return this.add(input - day, 'd');
|
|
} else {
|
|
return day;
|
|
}
|
|
}
|
|
|
|
function getSetLocaleDayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
|
|
return input == null ? weekday : this.add(input - weekday, 'd');
|
|
}
|
|
|
|
function getSetISODayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
|
|
// behaves the same as moment#day except
|
|
// as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
|
|
// as a setter, sunday should belong to the previous week.
|
|
|
|
if (input != null) {
|
|
var weekday = parseIsoWeekday(input, this.localeData());
|
|
return this.day(this.day() % 7 ? weekday : weekday - 7);
|
|
} else {
|
|
return this.day() || 7;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysRegex = matchWord;
|
|
function weekdaysRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysStrictRegex;
|
|
} else {
|
|
return this._weekdaysRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
this._weekdaysRegex = defaultWeekdaysRegex;
|
|
}
|
|
return this._weekdaysStrictRegex && isStrict ?
|
|
this._weekdaysStrictRegex : this._weekdaysRegex;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysShortRegex = matchWord;
|
|
function weekdaysShortRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysShortStrictRegex;
|
|
} else {
|
|
return this._weekdaysShortRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysShortRegex')) {
|
|
this._weekdaysShortRegex = defaultWeekdaysShortRegex;
|
|
}
|
|
return this._weekdaysShortStrictRegex && isStrict ?
|
|
this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysMinRegex = matchWord;
|
|
function weekdaysMinRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysMinStrictRegex;
|
|
} else {
|
|
return this._weekdaysMinRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysMinRegex')) {
|
|
this._weekdaysMinRegex = defaultWeekdaysMinRegex;
|
|
}
|
|
return this._weekdaysMinStrictRegex && isStrict ?
|
|
this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
|
|
}
|
|
}
|
|
|
|
|
|
function computeWeekdaysParse () {
|
|
function cmpLenRev(a, b) {
|
|
return b.length - a.length;
|
|
}
|
|
|
|
var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
|
|
i, mom, minp, shortp, longp;
|
|
for (i = 0; i < 7; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, 1]).day(i);
|
|
minp = this.weekdaysMin(mom, '');
|
|
shortp = this.weekdaysShort(mom, '');
|
|
longp = this.weekdays(mom, '');
|
|
minPieces.push(minp);
|
|
shortPieces.push(shortp);
|
|
longPieces.push(longp);
|
|
mixedPieces.push(minp);
|
|
mixedPieces.push(shortp);
|
|
mixedPieces.push(longp);
|
|
}
|
|
// Sorting makes sure if one weekday (or abbr) is a prefix of another it
|
|
// will match the longer piece.
|
|
minPieces.sort(cmpLenRev);
|
|
shortPieces.sort(cmpLenRev);
|
|
longPieces.sort(cmpLenRev);
|
|
mixedPieces.sort(cmpLenRev);
|
|
for (i = 0; i < 7; i++) {
|
|
shortPieces[i] = regexEscape(shortPieces[i]);
|
|
longPieces[i] = regexEscape(longPieces[i]);
|
|
mixedPieces[i] = regexEscape(mixedPieces[i]);
|
|
}
|
|
|
|
this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
|
|
this._weekdaysShortRegex = this._weekdaysRegex;
|
|
this._weekdaysMinRegex = this._weekdaysRegex;
|
|
|
|
this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
|
|
this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
|
|
this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
function hFormat() {
|
|
return this.hours() % 12 || 12;
|
|
}
|
|
|
|
function kFormat() {
|
|
return this.hours() || 24;
|
|
}
|
|
|
|
addFormatToken('H', ['HH', 2], 0, 'hour');
|
|
addFormatToken('h', ['hh', 2], 0, hFormat);
|
|
addFormatToken('k', ['kk', 2], 0, kFormat);
|
|
|
|
addFormatToken('hmm', 0, 0, function () {
|
|
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
|
|
});
|
|
|
|
addFormatToken('hmmss', 0, 0, function () {
|
|
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
|
|
zeroFill(this.seconds(), 2);
|
|
});
|
|
|
|
addFormatToken('Hmm', 0, 0, function () {
|
|
return '' + this.hours() + zeroFill(this.minutes(), 2);
|
|
});
|
|
|
|
addFormatToken('Hmmss', 0, 0, function () {
|
|
return '' + this.hours() + zeroFill(this.minutes(), 2) +
|
|
zeroFill(this.seconds(), 2);
|
|
});
|
|
|
|
function meridiem (token, lowercase) {
|
|
addFormatToken(token, 0, 0, function () {
|
|
return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
|
|
});
|
|
}
|
|
|
|
meridiem('a', true);
|
|
meridiem('A', false);
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('hour', 'h');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('hour', 13);
|
|
|
|
// PARSING
|
|
|
|
function matchMeridiem (isStrict, locale) {
|
|
return locale._meridiemParse;
|
|
}
|
|
|
|
addRegexToken('a', matchMeridiem);
|
|
addRegexToken('A', matchMeridiem);
|
|
addRegexToken('H', match1to2);
|
|
addRegexToken('h', match1to2);
|
|
addRegexToken('k', match1to2);
|
|
addRegexToken('HH', match1to2, match2);
|
|
addRegexToken('hh', match1to2, match2);
|
|
addRegexToken('kk', match1to2, match2);
|
|
|
|
addRegexToken('hmm', match3to4);
|
|
addRegexToken('hmmss', match5to6);
|
|
addRegexToken('Hmm', match3to4);
|
|
addRegexToken('Hmmss', match5to6);
|
|
|
|
addParseToken(['H', 'HH'], HOUR);
|
|
addParseToken(['k', 'kk'], function (input, array, config) {
|
|
var kInput = toInt(input);
|
|
array[HOUR] = kInput === 24 ? 0 : kInput;
|
|
});
|
|
addParseToken(['a', 'A'], function (input, array, config) {
|
|
config._isPm = config._locale.isPM(input);
|
|
config._meridiem = input;
|
|
});
|
|
addParseToken(['h', 'hh'], function (input, array, config) {
|
|
array[HOUR] = toInt(input);
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('hmm', function (input, array, config) {
|
|
var pos = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos));
|
|
array[MINUTE] = toInt(input.substr(pos));
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('hmmss', function (input, array, config) {
|
|
var pos1 = input.length - 4;
|
|
var pos2 = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos1));
|
|
array[MINUTE] = toInt(input.substr(pos1, 2));
|
|
array[SECOND] = toInt(input.substr(pos2));
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('Hmm', function (input, array, config) {
|
|
var pos = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos));
|
|
array[MINUTE] = toInt(input.substr(pos));
|
|
});
|
|
addParseToken('Hmmss', function (input, array, config) {
|
|
var pos1 = input.length - 4;
|
|
var pos2 = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos1));
|
|
array[MINUTE] = toInt(input.substr(pos1, 2));
|
|
array[SECOND] = toInt(input.substr(pos2));
|
|
});
|
|
|
|
// LOCALES
|
|
|
|
function localeIsPM (input) {
|
|
// IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
|
|
// Using charAt should be more compatible.
|
|
return ((input + '').toLowerCase().charAt(0) === 'p');
|
|
}
|
|
|
|
var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
|
|
function localeMeridiem (hours, minutes, isLower) {
|
|
if (hours > 11) {
|
|
return isLower ? 'pm' : 'PM';
|
|
} else {
|
|
return isLower ? 'am' : 'AM';
|
|
}
|
|
}
|
|
|
|
|
|
// MOMENTS
|
|
|
|
// Setting the hour should keep the time, because the user explicitly
|
|
// specified which hour he wants. So trying to maintain the same hour (in
|
|
// a new timezone) makes sense. Adding/subtracting hours does not follow
|
|
// this rule.
|
|
var getSetHour = makeGetSet('Hours', true);
|
|
|
|
var baseConfig = {
|
|
calendar: defaultCalendar,
|
|
longDateFormat: defaultLongDateFormat,
|
|
invalidDate: defaultInvalidDate,
|
|
ordinal: defaultOrdinal,
|
|
dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
|
|
relativeTime: defaultRelativeTime,
|
|
|
|
months: defaultLocaleMonths,
|
|
monthsShort: defaultLocaleMonthsShort,
|
|
|
|
week: defaultLocaleWeek,
|
|
|
|
weekdays: defaultLocaleWeekdays,
|
|
weekdaysMin: defaultLocaleWeekdaysMin,
|
|
weekdaysShort: defaultLocaleWeekdaysShort,
|
|
|
|
meridiemParse: defaultLocaleMeridiemParse
|
|
};
|
|
|
|
// internal storage for locale config files
|
|
var locales = {};
|
|
var localeFamilies = {};
|
|
var globalLocale;
|
|
|
|
function normalizeLocale(key) {
|
|
return key ? key.toLowerCase().replace('_', '-') : key;
|
|
}
|
|
|
|
// pick the locale from the array
|
|
// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
|
|
// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
|
|
function chooseLocale(names) {
|
|
var i = 0, j, next, locale, split;
|
|
|
|
while (i < names.length) {
|
|
split = normalizeLocale(names[i]).split('-');
|
|
j = split.length;
|
|
next = normalizeLocale(names[i + 1]);
|
|
next = next ? next.split('-') : null;
|
|
while (j > 0) {
|
|
locale = loadLocale(split.slice(0, j).join('-'));
|
|
if (locale) {
|
|
return locale;
|
|
}
|
|
if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
|
|
//the next array item is better than a shallower substring of this one
|
|
break;
|
|
}
|
|
j--;
|
|
}
|
|
i++;
|
|
}
|
|
return globalLocale;
|
|
}
|
|
|
|
function loadLocale(name) {
|
|
var oldLocale = null;
|
|
// TODO: Find a better way to register and load all the locales in Node
|
|
if (!locales[name] && (typeof module !== 'undefined') &&
|
|
module && module.exports) {
|
|
try {
|
|
oldLocale = globalLocale._abbr;
|
|
var aliasedRequire = require;
|
|
aliasedRequire('./locale/' + name);
|
|
getSetGlobalLocale(oldLocale);
|
|
} catch (e) {}
|
|
}
|
|
return locales[name];
|
|
}
|
|
|
|
// This function will load locale and then set the global locale. If
|
|
// no arguments are passed in, it will simply return the current global
|
|
// locale key.
|
|
function getSetGlobalLocale (key, values) {
|
|
var data;
|
|
if (key) {
|
|
if (isUndefined(values)) {
|
|
data = getLocale(key);
|
|
}
|
|
else {
|
|
data = defineLocale(key, values);
|
|
}
|
|
|
|
if (data) {
|
|
// moment.duration._locale = moment._locale = data;
|
|
globalLocale = data;
|
|
}
|
|
else {
|
|
if ((typeof console !== 'undefined') && console.warn) {
|
|
//warn user if arguments are passed but the locale could not be set
|
|
console.warn('Locale ' + key + ' not found. Did you forget to load it?');
|
|
}
|
|
}
|
|
}
|
|
|
|
return globalLocale._abbr;
|
|
}
|
|
|
|
function defineLocale (name, config) {
|
|
if (config !== null) {
|
|
var locale, parentConfig = baseConfig;
|
|
config.abbr = name;
|
|
if (locales[name] != null) {
|
|
deprecateSimple('defineLocaleOverride',
|
|
'use moment.updateLocale(localeName, config) to change ' +
|
|
'an existing locale. moment.defineLocale(localeName, ' +
|
|
'config) should only be used for creating a new locale ' +
|
|
'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
|
|
parentConfig = locales[name]._config;
|
|
} else if (config.parentLocale != null) {
|
|
if (locales[config.parentLocale] != null) {
|
|
parentConfig = locales[config.parentLocale]._config;
|
|
} else {
|
|
locale = loadLocale(config.parentLocale);
|
|
if (locale != null) {
|
|
parentConfig = locale._config;
|
|
} else {
|
|
if (!localeFamilies[config.parentLocale]) {
|
|
localeFamilies[config.parentLocale] = [];
|
|
}
|
|
localeFamilies[config.parentLocale].push({
|
|
name: name,
|
|
config: config
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
locales[name] = new Locale(mergeConfigs(parentConfig, config));
|
|
|
|
if (localeFamilies[name]) {
|
|
localeFamilies[name].forEach(function (x) {
|
|
defineLocale(x.name, x.config);
|
|
});
|
|
}
|
|
|
|
// backwards compat for now: also set the locale
|
|
// make sure we set the locale AFTER all child locales have been
|
|
// created, so we won't end up with the child locale set.
|
|
getSetGlobalLocale(name);
|
|
|
|
|
|
return locales[name];
|
|
} else {
|
|
// useful for testing
|
|
delete locales[name];
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function updateLocale(name, config) {
|
|
if (config != null) {
|
|
var locale, tmpLocale, parentConfig = baseConfig;
|
|
// MERGE
|
|
tmpLocale = loadLocale(name);
|
|
if (tmpLocale != null) {
|
|
parentConfig = tmpLocale._config;
|
|
}
|
|
config = mergeConfigs(parentConfig, config);
|
|
locale = new Locale(config);
|
|
locale.parentLocale = locales[name];
|
|
locales[name] = locale;
|
|
|
|
// backwards compat for now: also set the locale
|
|
getSetGlobalLocale(name);
|
|
} else {
|
|
// pass null for config to unupdate, useful for tests
|
|
if (locales[name] != null) {
|
|
if (locales[name].parentLocale != null) {
|
|
locales[name] = locales[name].parentLocale;
|
|
} else if (locales[name] != null) {
|
|
delete locales[name];
|
|
}
|
|
}
|
|
}
|
|
return locales[name];
|
|
}
|
|
|
|
// returns locale data
|
|
function getLocale (key) {
|
|
var locale;
|
|
|
|
if (key && key._locale && key._locale._abbr) {
|
|
key = key._locale._abbr;
|
|
}
|
|
|
|
if (!key) {
|
|
return globalLocale;
|
|
}
|
|
|
|
if (!isArray(key)) {
|
|
//short-circuit everything else
|
|
locale = loadLocale(key);
|
|
if (locale) {
|
|
return locale;
|
|
}
|
|
key = [key];
|
|
}
|
|
|
|
return chooseLocale(key);
|
|
}
|
|
|
|
function listLocales() {
|
|
return keys(locales);
|
|
}
|
|
|
|
function checkOverflow (m) {
|
|
var overflow;
|
|
var a = m._a;
|
|
|
|
if (a && getParsingFlags(m).overflow === -2) {
|
|
overflow =
|
|
a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
|
|
a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
|
|
a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
|
|
a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
|
|
a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
|
|
a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
|
|
-1;
|
|
|
|
if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
|
|
overflow = DATE;
|
|
}
|
|
if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
|
|
overflow = WEEK;
|
|
}
|
|
if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
|
|
overflow = WEEKDAY;
|
|
}
|
|
|
|
getParsingFlags(m).overflow = overflow;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
// Pick the first defined of two or three arguments.
|
|
function defaults(a, b, c) {
|
|
if (a != null) {
|
|
return a;
|
|
}
|
|
if (b != null) {
|
|
return b;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
function currentDateArray(config) {
|
|
// hooks is actually the exported moment object
|
|
var nowValue = new Date(hooks.now());
|
|
if (config._useUTC) {
|
|
return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
|
|
}
|
|
return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
|
|
}
|
|
|
|
// convert an array to a date.
|
|
// the array should mirror the parameters below
|
|
// note: all values past the year are optional and will default to the lowest possible value.
|
|
// [year, month, day , hour, minute, second, millisecond]
|
|
function configFromArray (config) {
|
|
var i, date, input = [], currentDate, expectedWeekday, yearToUse;
|
|
|
|
if (config._d) {
|
|
return;
|
|
}
|
|
|
|
currentDate = currentDateArray(config);
|
|
|
|
//compute day of the year from weeks and weekdays
|
|
if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
|
|
dayOfYearFromWeekInfo(config);
|
|
}
|
|
|
|
//if the day of the year is set, figure out what it is
|
|
if (config._dayOfYear != null) {
|
|
yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
|
|
|
|
if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
|
|
getParsingFlags(config)._overflowDayOfYear = true;
|
|
}
|
|
|
|
date = createUTCDate(yearToUse, 0, config._dayOfYear);
|
|
config._a[MONTH] = date.getUTCMonth();
|
|
config._a[DATE] = date.getUTCDate();
|
|
}
|
|
|
|
// Default to current date.
|
|
// * if no year, month, day of month are given, default to today
|
|
// * if day of month is given, default month and year
|
|
// * if month is given, default only year
|
|
// * if year is given, don't default anything
|
|
for (i = 0; i < 3 && config._a[i] == null; ++i) {
|
|
config._a[i] = input[i] = currentDate[i];
|
|
}
|
|
|
|
// Zero out whatever was not defaulted, including time
|
|
for (; i < 7; i++) {
|
|
config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
|
|
}
|
|
|
|
// Check for 24:00:00.000
|
|
if (config._a[HOUR] === 24 &&
|
|
config._a[MINUTE] === 0 &&
|
|
config._a[SECOND] === 0 &&
|
|
config._a[MILLISECOND] === 0) {
|
|
config._nextDay = true;
|
|
config._a[HOUR] = 0;
|
|
}
|
|
|
|
config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
|
|
expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
|
|
|
|
// Apply timezone offset from input. The actual utcOffset can be changed
|
|
// with parseZone.
|
|
if (config._tzm != null) {
|
|
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
|
|
}
|
|
|
|
if (config._nextDay) {
|
|
config._a[HOUR] = 24;
|
|
}
|
|
|
|
// check for mismatching day of week
|
|
if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
|
|
getParsingFlags(config).weekdayMismatch = true;
|
|
}
|
|
}
|
|
|
|
function dayOfYearFromWeekInfo(config) {
|
|
var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
|
|
|
|
w = config._w;
|
|
if (w.GG != null || w.W != null || w.E != null) {
|
|
dow = 1;
|
|
doy = 4;
|
|
|
|
// TODO: We need to take the current isoWeekYear, but that depends on
|
|
// how we interpret now (local, utc, fixed offset). So create
|
|
// a now version of current config (take local/utc/offset flags, and
|
|
// create now).
|
|
weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
|
|
week = defaults(w.W, 1);
|
|
weekday = defaults(w.E, 1);
|
|
if (weekday < 1 || weekday > 7) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else {
|
|
dow = config._locale._week.dow;
|
|
doy = config._locale._week.doy;
|
|
|
|
var curWeek = weekOfYear(createLocal(), dow, doy);
|
|
|
|
weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
|
|
|
|
// Default to current week.
|
|
week = defaults(w.w, curWeek.week);
|
|
|
|
if (w.d != null) {
|
|
// weekday -- low day numbers are considered next week
|
|
weekday = w.d;
|
|
if (weekday < 0 || weekday > 6) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else if (w.e != null) {
|
|
// local weekday -- counting starts from begining of week
|
|
weekday = w.e + dow;
|
|
if (w.e < 0 || w.e > 6) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else {
|
|
// default to begining of week
|
|
weekday = dow;
|
|
}
|
|
}
|
|
if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
|
|
getParsingFlags(config)._overflowWeeks = true;
|
|
} else if (weekdayOverflow != null) {
|
|
getParsingFlags(config)._overflowWeekday = true;
|
|
} else {
|
|
temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
|
|
config._a[YEAR] = temp.year;
|
|
config._dayOfYear = temp.dayOfYear;
|
|
}
|
|
}
|
|
|
|
// iso 8601 regex
|
|
// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
|
|
var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
|
|
var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
|
|
|
|
var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
|
|
|
|
var isoDates = [
|
|
['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
|
|
['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
|
|
['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
|
|
['GGGG-[W]WW', /\d{4}-W\d\d/, false],
|
|
['YYYY-DDD', /\d{4}-\d{3}/],
|
|
['YYYY-MM', /\d{4}-\d\d/, false],
|
|
['YYYYYYMMDD', /[+-]\d{10}/],
|
|
['YYYYMMDD', /\d{8}/],
|
|
// YYYYMM is NOT allowed by the standard
|
|
['GGGG[W]WWE', /\d{4}W\d{3}/],
|
|
['GGGG[W]WW', /\d{4}W\d{2}/, false],
|
|
['YYYYDDD', /\d{7}/]
|
|
];
|
|
|
|
// iso time formats and regexes
|
|
var isoTimes = [
|
|
['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
|
|
['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
|
|
['HH:mm:ss', /\d\d:\d\d:\d\d/],
|
|
['HH:mm', /\d\d:\d\d/],
|
|
['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
|
|
['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
|
|
['HHmmss', /\d\d\d\d\d\d/],
|
|
['HHmm', /\d\d\d\d/],
|
|
['HH', /\d\d/]
|
|
];
|
|
|
|
var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
|
|
|
|
// date from iso format
|
|
function configFromISO(config) {
|
|
var i, l,
|
|
string = config._i,
|
|
match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
|
|
allowTime, dateFormat, timeFormat, tzFormat;
|
|
|
|
if (match) {
|
|
getParsingFlags(config).iso = true;
|
|
|
|
for (i = 0, l = isoDates.length; i < l; i++) {
|
|
if (isoDates[i][1].exec(match[1])) {
|
|
dateFormat = isoDates[i][0];
|
|
allowTime = isoDates[i][2] !== false;
|
|
break;
|
|
}
|
|
}
|
|
if (dateFormat == null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
if (match[3]) {
|
|
for (i = 0, l = isoTimes.length; i < l; i++) {
|
|
if (isoTimes[i][1].exec(match[3])) {
|
|
// match[2] should be 'T' or space
|
|
timeFormat = (match[2] || ' ') + isoTimes[i][0];
|
|
break;
|
|
}
|
|
}
|
|
if (timeFormat == null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
}
|
|
if (!allowTime && timeFormat != null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
if (match[4]) {
|
|
if (tzRegex.exec(match[4])) {
|
|
tzFormat = 'Z';
|
|
} else {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
}
|
|
config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
|
|
configFromStringAndFormat(config);
|
|
} else {
|
|
config._isValid = false;
|
|
}
|
|
}
|
|
|
|
// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
|
|
var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
|
|
|
|
function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
|
|
var result = [
|
|
untruncateYear(yearStr),
|
|
defaultLocaleMonthsShort.indexOf(monthStr),
|
|
parseInt(dayStr, 10),
|
|
parseInt(hourStr, 10),
|
|
parseInt(minuteStr, 10)
|
|
];
|
|
|
|
if (secondStr) {
|
|
result.push(parseInt(secondStr, 10));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function untruncateYear(yearStr) {
|
|
var year = parseInt(yearStr, 10);
|
|
if (year <= 49) {
|
|
return 2000 + year;
|
|
} else if (year <= 999) {
|
|
return 1900 + year;
|
|
}
|
|
return year;
|
|
}
|
|
|
|
function preprocessRFC2822(s) {
|
|
// Remove comments and folding whitespace and replace multiple-spaces with a single space
|
|
return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
|
|
}
|
|
|
|
function checkWeekday(weekdayStr, parsedInput, config) {
|
|
if (weekdayStr) {
|
|
// TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
|
|
var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
|
|
weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
|
|
if (weekdayProvided !== weekdayActual) {
|
|
getParsingFlags(config).weekdayMismatch = true;
|
|
config._isValid = false;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var obsOffsets = {
|
|
UT: 0,
|
|
GMT: 0,
|
|
EDT: -4 * 60,
|
|
EST: -5 * 60,
|
|
CDT: -5 * 60,
|
|
CST: -6 * 60,
|
|
MDT: -6 * 60,
|
|
MST: -7 * 60,
|
|
PDT: -7 * 60,
|
|
PST: -8 * 60
|
|
};
|
|
|
|
function calculateOffset(obsOffset, militaryOffset, numOffset) {
|
|
if (obsOffset) {
|
|
return obsOffsets[obsOffset];
|
|
} else if (militaryOffset) {
|
|
// the only allowed military tz is Z
|
|
return 0;
|
|
} else {
|
|
var hm = parseInt(numOffset, 10);
|
|
var m = hm % 100, h = (hm - m) / 100;
|
|
return h * 60 + m;
|
|
}
|
|
}
|
|
|
|
// date and time from ref 2822 format
|
|
function configFromRFC2822(config) {
|
|
var match = rfc2822.exec(preprocessRFC2822(config._i));
|
|
if (match) {
|
|
var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
|
|
if (!checkWeekday(match[1], parsedArray, config)) {
|
|
return;
|
|
}
|
|
|
|
config._a = parsedArray;
|
|
config._tzm = calculateOffset(match[8], match[9], match[10]);
|
|
|
|
config._d = createUTCDate.apply(null, config._a);
|
|
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
|
|
|
|
getParsingFlags(config).rfc2822 = true;
|
|
} else {
|
|
config._isValid = false;
|
|
}
|
|
}
|
|
|
|
// date from iso format or fallback
|
|
function configFromString(config) {
|
|
var matched = aspNetJsonRegex.exec(config._i);
|
|
|
|
if (matched !== null) {
|
|
config._d = new Date(+matched[1]);
|
|
return;
|
|
}
|
|
|
|
configFromISO(config);
|
|
if (config._isValid === false) {
|
|
delete config._isValid;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
configFromRFC2822(config);
|
|
if (config._isValid === false) {
|
|
delete config._isValid;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Final attempt, use Input Fallback
|
|
hooks.createFromInputFallback(config);
|
|
}
|
|
|
|
hooks.createFromInputFallback = deprecate(
|
|
'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
|
|
'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
|
|
'discouraged and will be removed in an upcoming major release. Please refer to ' +
|
|
'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
|
|
function (config) {
|
|
config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
|
|
}
|
|
);
|
|
|
|
// constant that refers to the ISO standard
|
|
hooks.ISO_8601 = function () {};
|
|
|
|
// constant that refers to the RFC 2822 form
|
|
hooks.RFC_2822 = function () {};
|
|
|
|
// date from string and format string
|
|
function configFromStringAndFormat(config) {
|
|
// TODO: Move this to another part of the creation flow to prevent circular deps
|
|
if (config._f === hooks.ISO_8601) {
|
|
configFromISO(config);
|
|
return;
|
|
}
|
|
if (config._f === hooks.RFC_2822) {
|
|
configFromRFC2822(config);
|
|
return;
|
|
}
|
|
config._a = [];
|
|
getParsingFlags(config).empty = true;
|
|
|
|
// This array is used to make a Date, either with `new Date` or `Date.UTC`
|
|
var string = '' + config._i,
|
|
i, parsedInput, tokens, token, skipped,
|
|
stringLength = string.length,
|
|
totalParsedInputLength = 0;
|
|
|
|
tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
|
|
|
|
for (i = 0; i < tokens.length; i++) {
|
|
token = tokens[i];
|
|
parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
|
|
// console.log('token', token, 'parsedInput', parsedInput,
|
|
// 'regex', getParseRegexForToken(token, config));
|
|
if (parsedInput) {
|
|
skipped = string.substr(0, string.indexOf(parsedInput));
|
|
if (skipped.length > 0) {
|
|
getParsingFlags(config).unusedInput.push(skipped);
|
|
}
|
|
string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
|
|
totalParsedInputLength += parsedInput.length;
|
|
}
|
|
// don't parse if it's not a known token
|
|
if (formatTokenFunctions[token]) {
|
|
if (parsedInput) {
|
|
getParsingFlags(config).empty = false;
|
|
}
|
|
else {
|
|
getParsingFlags(config).unusedTokens.push(token);
|
|
}
|
|
addTimeToArrayFromToken(token, parsedInput, config);
|
|
}
|
|
else if (config._strict && !parsedInput) {
|
|
getParsingFlags(config).unusedTokens.push(token);
|
|
}
|
|
}
|
|
|
|
// add remaining unparsed input length to the string
|
|
getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
|
|
if (string.length > 0) {
|
|
getParsingFlags(config).unusedInput.push(string);
|
|
}
|
|
|
|
// clear _12h flag if hour is <= 12
|
|
if (config._a[HOUR] <= 12 &&
|
|
getParsingFlags(config).bigHour === true &&
|
|
config._a[HOUR] > 0) {
|
|
getParsingFlags(config).bigHour = undefined;
|
|
}
|
|
|
|
getParsingFlags(config).parsedDateParts = config._a.slice(0);
|
|
getParsingFlags(config).meridiem = config._meridiem;
|
|
// handle meridiem
|
|
config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
|
|
|
|
configFromArray(config);
|
|
checkOverflow(config);
|
|
}
|
|
|
|
|
|
function meridiemFixWrap (locale, hour, meridiem) {
|
|
var isPm;
|
|
|
|
if (meridiem == null) {
|
|
// nothing to do
|
|
return hour;
|
|
}
|
|
if (locale.meridiemHour != null) {
|
|
return locale.meridiemHour(hour, meridiem);
|
|
} else if (locale.isPM != null) {
|
|
// Fallback
|
|
isPm = locale.isPM(meridiem);
|
|
if (isPm && hour < 12) {
|
|
hour += 12;
|
|
}
|
|
if (!isPm && hour === 12) {
|
|
hour = 0;
|
|
}
|
|
return hour;
|
|
} else {
|
|
// this is not supposed to happen
|
|
return hour;
|
|
}
|
|
}
|
|
|
|
// date from string and array of format strings
|
|
function configFromStringAndArray(config) {
|
|
var tempConfig,
|
|
bestMoment,
|
|
|
|
scoreToBeat,
|
|
i,
|
|
currentScore;
|
|
|
|
if (config._f.length === 0) {
|
|
getParsingFlags(config).invalidFormat = true;
|
|
config._d = new Date(NaN);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < config._f.length; i++) {
|
|
currentScore = 0;
|
|
tempConfig = copyConfig({}, config);
|
|
if (config._useUTC != null) {
|
|
tempConfig._useUTC = config._useUTC;
|
|
}
|
|
tempConfig._f = config._f[i];
|
|
configFromStringAndFormat(tempConfig);
|
|
|
|
if (!isValid(tempConfig)) {
|
|
continue;
|
|
}
|
|
|
|
// if there is any input that was not parsed add a penalty for that format
|
|
currentScore += getParsingFlags(tempConfig).charsLeftOver;
|
|
|
|
//or tokens
|
|
currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
|
|
|
|
getParsingFlags(tempConfig).score = currentScore;
|
|
|
|
if (scoreToBeat == null || currentScore < scoreToBeat) {
|
|
scoreToBeat = currentScore;
|
|
bestMoment = tempConfig;
|
|
}
|
|
}
|
|
|
|
extend(config, bestMoment || tempConfig);
|
|
}
|
|
|
|
function configFromObject(config) {
|
|
if (config._d) {
|
|
return;
|
|
}
|
|
|
|
var i = normalizeObjectUnits(config._i);
|
|
config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
|
|
return obj && parseInt(obj, 10);
|
|
});
|
|
|
|
configFromArray(config);
|
|
}
|
|
|
|
function createFromConfig (config) {
|
|
var res = new Moment(checkOverflow(prepareConfig(config)));
|
|
if (res._nextDay) {
|
|
// Adding is smart enough around DST
|
|
res.add(1, 'd');
|
|
res._nextDay = undefined;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
function prepareConfig (config) {
|
|
var input = config._i,
|
|
format = config._f;
|
|
|
|
config._locale = config._locale || getLocale(config._l);
|
|
|
|
if (input === null || (format === undefined && input === '')) {
|
|
return createInvalid({nullInput: true});
|
|
}
|
|
|
|
if (typeof input === 'string') {
|
|
config._i = input = config._locale.preparse(input);
|
|
}
|
|
|
|
if (isMoment(input)) {
|
|
return new Moment(checkOverflow(input));
|
|
} else if (isDate(input)) {
|
|
config._d = input;
|
|
} else if (isArray(format)) {
|
|
configFromStringAndArray(config);
|
|
} else if (format) {
|
|
configFromStringAndFormat(config);
|
|
} else {
|
|
configFromInput(config);
|
|
}
|
|
|
|
if (!isValid(config)) {
|
|
config._d = null;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
function configFromInput(config) {
|
|
var input = config._i;
|
|
if (isUndefined(input)) {
|
|
config._d = new Date(hooks.now());
|
|
} else if (isDate(input)) {
|
|
config._d = new Date(input.valueOf());
|
|
} else if (typeof input === 'string') {
|
|
configFromString(config);
|
|
} else if (isArray(input)) {
|
|
config._a = map(input.slice(0), function (obj) {
|
|
return parseInt(obj, 10);
|
|
});
|
|
configFromArray(config);
|
|
} else if (isObject(input)) {
|
|
configFromObject(config);
|
|
} else if (isNumber(input)) {
|
|
// from milliseconds
|
|
config._d = new Date(input);
|
|
} else {
|
|
hooks.createFromInputFallback(config);
|
|
}
|
|
}
|
|
|
|
function createLocalOrUTC (input, format, locale, strict, isUTC) {
|
|
var c = {};
|
|
|
|
if (locale === true || locale === false) {
|
|
strict = locale;
|
|
locale = undefined;
|
|
}
|
|
|
|
if ((isObject(input) && isObjectEmpty(input)) ||
|
|
(isArray(input) && input.length === 0)) {
|
|
input = undefined;
|
|
}
|
|
// object construction must be done this way.
|
|
// https://github.com/moment/moment/issues/1423
|
|
c._isAMomentObject = true;
|
|
c._useUTC = c._isUTC = isUTC;
|
|
c._l = locale;
|
|
c._i = input;
|
|
c._f = format;
|
|
c._strict = strict;
|
|
|
|
return createFromConfig(c);
|
|
}
|
|
|
|
function createLocal (input, format, locale, strict) {
|
|
return createLocalOrUTC(input, format, locale, strict, false);
|
|
}
|
|
|
|
var prototypeMin = deprecate(
|
|
'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
|
|
function () {
|
|
var other = createLocal.apply(null, arguments);
|
|
if (this.isValid() && other.isValid()) {
|
|
return other < this ? this : other;
|
|
} else {
|
|
return createInvalid();
|
|
}
|
|
}
|
|
);
|
|
|
|
var prototypeMax = deprecate(
|
|
'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
|
|
function () {
|
|
var other = createLocal.apply(null, arguments);
|
|
if (this.isValid() && other.isValid()) {
|
|
return other > this ? this : other;
|
|
} else {
|
|
return createInvalid();
|
|
}
|
|
}
|
|
);
|
|
|
|
// Pick a moment m from moments so that m[fn](other) is true for all
|
|
// other. This relies on the function fn to be transitive.
|
|
//
|
|
// moments should either be an array of moment objects or an array, whose
|
|
// first element is an array of moment objects.
|
|
function pickBy(fn, moments) {
|
|
var res, i;
|
|
if (moments.length === 1 && isArray(moments[0])) {
|
|
moments = moments[0];
|
|
}
|
|
if (!moments.length) {
|
|
return createLocal();
|
|
}
|
|
res = moments[0];
|
|
for (i = 1; i < moments.length; ++i) {
|
|
if (!moments[i].isValid() || moments[i][fn](res)) {
|
|
res = moments[i];
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// TODO: Use [].sort instead?
|
|
function min () {
|
|
var args = [].slice.call(arguments, 0);
|
|
|
|
return pickBy('isBefore', args);
|
|
}
|
|
|
|
function max () {
|
|
var args = [].slice.call(arguments, 0);
|
|
|
|
return pickBy('isAfter', args);
|
|
}
|
|
|
|
var now = function () {
|
|
return Date.now ? Date.now() : +(new Date());
|
|
};
|
|
|
|
var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
|
|
|
|
function isDurationValid(m) {
|
|
for (var key in m) {
|
|
if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var unitHasDecimal = false;
|
|
for (var i = 0; i < ordering.length; ++i) {
|
|
if (m[ordering[i]]) {
|
|
if (unitHasDecimal) {
|
|
return false; // only allow non-integers for smallest unit
|
|
}
|
|
if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
|
|
unitHasDecimal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function isValid$1() {
|
|
return this._isValid;
|
|
}
|
|
|
|
function createInvalid$1() {
|
|
return createDuration(NaN);
|
|
}
|
|
|
|
function Duration (duration) {
|
|
var normalizedInput = normalizeObjectUnits(duration),
|
|
years = normalizedInput.year || 0,
|
|
quarters = normalizedInput.quarter || 0,
|
|
months = normalizedInput.month || 0,
|
|
weeks = normalizedInput.week || 0,
|
|
days = normalizedInput.day || 0,
|
|
hours = normalizedInput.hour || 0,
|
|
minutes = normalizedInput.minute || 0,
|
|
seconds = normalizedInput.second || 0,
|
|
milliseconds = normalizedInput.millisecond || 0;
|
|
|
|
this._isValid = isDurationValid(normalizedInput);
|
|
|
|
// representation for dateAddRemove
|
|
this._milliseconds = +milliseconds +
|
|
seconds * 1e3 + // 1000
|
|
minutes * 6e4 + // 1000 * 60
|
|
hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
|
|
// Because of dateAddRemove treats 24 hours as different from a
|
|
// day when working around DST, we need to store them separately
|
|
this._days = +days +
|
|
weeks * 7;
|
|
// It is impossible to translate months into days without knowing
|
|
// which months you are are talking about, so we have to store
|
|
// it separately.
|
|
this._months = +months +
|
|
quarters * 3 +
|
|
years * 12;
|
|
|
|
this._data = {};
|
|
|
|
this._locale = getLocale();
|
|
|
|
this._bubble();
|
|
}
|
|
|
|
function isDuration (obj) {
|
|
return obj instanceof Duration;
|
|
}
|
|
|
|
function absRound (number) {
|
|
if (number < 0) {
|
|
return Math.round(-1 * number) * -1;
|
|
} else {
|
|
return Math.round(number);
|
|
}
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
function offset (token, separator) {
|
|
addFormatToken(token, 0, 0, function () {
|
|
var offset = this.utcOffset();
|
|
var sign = '+';
|
|
if (offset < 0) {
|
|
offset = -offset;
|
|
sign = '-';
|
|
}
|
|
return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
|
|
});
|
|
}
|
|
|
|
offset('Z', ':');
|
|
offset('ZZ', '');
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Z', matchShortOffset);
|
|
addRegexToken('ZZ', matchShortOffset);
|
|
addParseToken(['Z', 'ZZ'], function (input, array, config) {
|
|
config._useUTC = true;
|
|
config._tzm = offsetFromString(matchShortOffset, input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// timezone chunker
|
|
// '+10:00' > ['10', '00']
|
|
// '-1530' > ['-15', '30']
|
|
var chunkOffset = /([\+\-]|\d\d)/gi;
|
|
|
|
function offsetFromString(matcher, string) {
|
|
var matches = (string || '').match(matcher);
|
|
|
|
if (matches === null) {
|
|
return null;
|
|
}
|
|
|
|
var chunk = matches[matches.length - 1] || [];
|
|
var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
|
|
var minutes = +(parts[1] * 60) + toInt(parts[2]);
|
|
|
|
return minutes === 0 ?
|
|
0 :
|
|
parts[0] === '+' ? minutes : -minutes;
|
|
}
|
|
|
|
// Return a moment from input, that is local/utc/zone equivalent to model.
|
|
function cloneWithOffset(input, model) {
|
|
var res, diff;
|
|
if (model._isUTC) {
|
|
res = model.clone();
|
|
diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
|
|
// Use low-level api, because this fn is low-level api.
|
|
res._d.setTime(res._d.valueOf() + diff);
|
|
hooks.updateOffset(res, false);
|
|
return res;
|
|
} else {
|
|
return createLocal(input).local();
|
|
}
|
|
}
|
|
|
|
function getDateOffset (m) {
|
|
// On Firefox.24 Date#getTimezoneOffset returns a floating point.
|
|
// https://github.com/moment/moment/pull/1871
|
|
return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
|
|
}
|
|
|
|
// HOOKS
|
|
|
|
// This function will be called whenever a moment is mutated.
|
|
// It is intended to keep the offset in sync with the timezone.
|
|
hooks.updateOffset = function () {};
|
|
|
|
// MOMENTS
|
|
|
|
// keepLocalTime = true means only change the timezone, without
|
|
// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
|
|
// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
|
|
// +0200, so we adjust the time as needed, to be valid.
|
|
//
|
|
// Keeping the time actually adds/subtracts (one hour)
|
|
// from the actual represented time. That is why we call updateOffset
|
|
// a second time. In case it wants us to change the offset again
|
|
// _changeInProgress == true case, then we have to adjust, because
|
|
// there is no such time in the given timezone.
|
|
function getSetOffset (input, keepLocalTime, keepMinutes) {
|
|
var offset = this._offset || 0,
|
|
localAdjust;
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
if (input != null) {
|
|
if (typeof input === 'string') {
|
|
input = offsetFromString(matchShortOffset, input);
|
|
if (input === null) {
|
|
return this;
|
|
}
|
|
} else if (Math.abs(input) < 16 && !keepMinutes) {
|
|
input = input * 60;
|
|
}
|
|
if (!this._isUTC && keepLocalTime) {
|
|
localAdjust = getDateOffset(this);
|
|
}
|
|
this._offset = input;
|
|
this._isUTC = true;
|
|
if (localAdjust != null) {
|
|
this.add(localAdjust, 'm');
|
|
}
|
|
if (offset !== input) {
|
|
if (!keepLocalTime || this._changeInProgress) {
|
|
addSubtract(this, createDuration(input - offset, 'm'), 1, false);
|
|
} else if (!this._changeInProgress) {
|
|
this._changeInProgress = true;
|
|
hooks.updateOffset(this, true);
|
|
this._changeInProgress = null;
|
|
}
|
|
}
|
|
return this;
|
|
} else {
|
|
return this._isUTC ? offset : getDateOffset(this);
|
|
}
|
|
}
|
|
|
|
function getSetZone (input, keepLocalTime) {
|
|
if (input != null) {
|
|
if (typeof input !== 'string') {
|
|
input = -input;
|
|
}
|
|
|
|
this.utcOffset(input, keepLocalTime);
|
|
|
|
return this;
|
|
} else {
|
|
return -this.utcOffset();
|
|
}
|
|
}
|
|
|
|
function setOffsetToUTC (keepLocalTime) {
|
|
return this.utcOffset(0, keepLocalTime);
|
|
}
|
|
|
|
function setOffsetToLocal (keepLocalTime) {
|
|
if (this._isUTC) {
|
|
this.utcOffset(0, keepLocalTime);
|
|
this._isUTC = false;
|
|
|
|
if (keepLocalTime) {
|
|
this.subtract(getDateOffset(this), 'm');
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function setOffsetToParsedOffset () {
|
|
if (this._tzm != null) {
|
|
this.utcOffset(this._tzm, false, true);
|
|
} else if (typeof this._i === 'string') {
|
|
var tZone = offsetFromString(matchOffset, this._i);
|
|
if (tZone != null) {
|
|
this.utcOffset(tZone);
|
|
}
|
|
else {
|
|
this.utcOffset(0, true);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function hasAlignedHourOffset (input) {
|
|
if (!this.isValid()) {
|
|
return false;
|
|
}
|
|
input = input ? createLocal(input).utcOffset() : 0;
|
|
|
|
return (this.utcOffset() - input) % 60 === 0;
|
|
}
|
|
|
|
function isDaylightSavingTime () {
|
|
return (
|
|
this.utcOffset() > this.clone().month(0).utcOffset() ||
|
|
this.utcOffset() > this.clone().month(5).utcOffset()
|
|
);
|
|
}
|
|
|
|
function isDaylightSavingTimeShifted () {
|
|
if (!isUndefined(this._isDSTShifted)) {
|
|
return this._isDSTShifted;
|
|
}
|
|
|
|
var c = {};
|
|
|
|
copyConfig(c, this);
|
|
c = prepareConfig(c);
|
|
|
|
if (c._a) {
|
|
var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
|
|
this._isDSTShifted = this.isValid() &&
|
|
compareArrays(c._a, other.toArray()) > 0;
|
|
} else {
|
|
this._isDSTShifted = false;
|
|
}
|
|
|
|
return this._isDSTShifted;
|
|
}
|
|
|
|
function isLocal () {
|
|
return this.isValid() ? !this._isUTC : false;
|
|
}
|
|
|
|
function isUtcOffset () {
|
|
return this.isValid() ? this._isUTC : false;
|
|
}
|
|
|
|
function isUtc () {
|
|
return this.isValid() ? this._isUTC && this._offset === 0 : false;
|
|
}
|
|
|
|
// ASP.NET json date format regex
|
|
var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
|
|
|
|
// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
|
|
// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
|
|
// and further modified to allow for strings containing both week and day
|
|
var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
|
|
|
|
function createDuration (input, key) {
|
|
var duration = input,
|
|
// matching against regexp is expensive, do it on demand
|
|
match = null,
|
|
sign,
|
|
ret,
|
|
diffRes;
|
|
|
|
if (isDuration(input)) {
|
|
duration = {
|
|
ms : input._milliseconds,
|
|
d : input._days,
|
|
M : input._months
|
|
};
|
|
} else if (isNumber(input)) {
|
|
duration = {};
|
|
if (key) {
|
|
duration[key] = input;
|
|
} else {
|
|
duration.milliseconds = input;
|
|
}
|
|
} else if (!!(match = aspNetRegex.exec(input))) {
|
|
sign = (match[1] === '-') ? -1 : 1;
|
|
duration = {
|
|
y : 0,
|
|
d : toInt(match[DATE]) * sign,
|
|
h : toInt(match[HOUR]) * sign,
|
|
m : toInt(match[MINUTE]) * sign,
|
|
s : toInt(match[SECOND]) * sign,
|
|
ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
|
|
};
|
|
} else if (!!(match = isoRegex.exec(input))) {
|
|
sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1;
|
|
duration = {
|
|
y : parseIso(match[2], sign),
|
|
M : parseIso(match[3], sign),
|
|
w : parseIso(match[4], sign),
|
|
d : parseIso(match[5], sign),
|
|
h : parseIso(match[6], sign),
|
|
m : parseIso(match[7], sign),
|
|
s : parseIso(match[8], sign)
|
|
};
|
|
} else if (duration == null) {// checks for null or undefined
|
|
duration = {};
|
|
} else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
|
|
diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
|
|
|
|
duration = {};
|
|
duration.ms = diffRes.milliseconds;
|
|
duration.M = diffRes.months;
|
|
}
|
|
|
|
ret = new Duration(duration);
|
|
|
|
if (isDuration(input) && hasOwnProp(input, '_locale')) {
|
|
ret._locale = input._locale;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
createDuration.fn = Duration.prototype;
|
|
createDuration.invalid = createInvalid$1;
|
|
|
|
function parseIso (inp, sign) {
|
|
// We'd normally use ~~inp for this, but unfortunately it also
|
|
// converts floats to ints.
|
|
// inp may be undefined, so careful calling replace on it.
|
|
var res = inp && parseFloat(inp.replace(',', '.'));
|
|
// apply sign while we're at it
|
|
return (isNaN(res) ? 0 : res) * sign;
|
|
}
|
|
|
|
function positiveMomentsDifference(base, other) {
|
|
var res = {milliseconds: 0, months: 0};
|
|
|
|
res.months = other.month() - base.month() +
|
|
(other.year() - base.year()) * 12;
|
|
if (base.clone().add(res.months, 'M').isAfter(other)) {
|
|
--res.months;
|
|
}
|
|
|
|
res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
|
|
|
|
return res;
|
|
}
|
|
|
|
function momentsDifference(base, other) {
|
|
var res;
|
|
if (!(base.isValid() && other.isValid())) {
|
|
return {milliseconds: 0, months: 0};
|
|
}
|
|
|
|
other = cloneWithOffset(other, base);
|
|
if (base.isBefore(other)) {
|
|
res = positiveMomentsDifference(base, other);
|
|
} else {
|
|
res = positiveMomentsDifference(other, base);
|
|
res.milliseconds = -res.milliseconds;
|
|
res.months = -res.months;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// TODO: remove 'name' arg after deprecation is removed
|
|
function createAdder(direction, name) {
|
|
return function (val, period) {
|
|
var dur, tmp;
|
|
//invert the arguments, but complain about it
|
|
if (period !== null && !isNaN(+period)) {
|
|
deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
|
|
'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
|
|
tmp = val; val = period; period = tmp;
|
|
}
|
|
|
|
val = typeof val === 'string' ? +val : val;
|
|
dur = createDuration(val, period);
|
|
addSubtract(this, dur, direction);
|
|
return this;
|
|
};
|
|
}
|
|
|
|
function addSubtract (mom, duration, isAdding, updateOffset) {
|
|
var milliseconds = duration._milliseconds,
|
|
days = absRound(duration._days),
|
|
months = absRound(duration._months);
|
|
|
|
if (!mom.isValid()) {
|
|
// No op
|
|
return;
|
|
}
|
|
|
|
updateOffset = updateOffset == null ? true : updateOffset;
|
|
|
|
if (months) {
|
|
setMonth(mom, get(mom, 'Month') + months * isAdding);
|
|
}
|
|
if (days) {
|
|
set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
|
|
}
|
|
if (milliseconds) {
|
|
mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
|
|
}
|
|
if (updateOffset) {
|
|
hooks.updateOffset(mom, days || months);
|
|
}
|
|
}
|
|
|
|
var add = createAdder(1, 'add');
|
|
var subtract = createAdder(-1, 'subtract');
|
|
|
|
function getCalendarFormat(myMoment, now) {
|
|
var diff = myMoment.diff(now, 'days', true);
|
|
return diff < -6 ? 'sameElse' :
|
|
diff < -1 ? 'lastWeek' :
|
|
diff < 0 ? 'lastDay' :
|
|
diff < 1 ? 'sameDay' :
|
|
diff < 2 ? 'nextDay' :
|
|
diff < 7 ? 'nextWeek' : 'sameElse';
|
|
}
|
|
|
|
function calendar$1 (time, formats) {
|
|
// We want to compare the start of today, vs this.
|
|
// Getting start-of-today depends on whether we're local/utc/offset or not.
|
|
var now = time || createLocal(),
|
|
sod = cloneWithOffset(now, this).startOf('day'),
|
|
format = hooks.calendarFormat(this, sod) || 'sameElse';
|
|
|
|
var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
|
|
|
|
return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
|
|
}
|
|
|
|
function clone () {
|
|
return new Moment(this);
|
|
}
|
|
|
|
function isAfter (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input);
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() > localInput.valueOf();
|
|
} else {
|
|
return localInput.valueOf() < this.clone().startOf(units).valueOf();
|
|
}
|
|
}
|
|
|
|
function isBefore (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input);
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() < localInput.valueOf();
|
|
} else {
|
|
return this.clone().endOf(units).valueOf() < localInput.valueOf();
|
|
}
|
|
}
|
|
|
|
function isBetween (from, to, units, inclusivity) {
|
|
inclusivity = inclusivity || '()';
|
|
return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
|
|
(inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
|
|
}
|
|
|
|
function isSame (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input),
|
|
inputMs;
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(units || 'millisecond');
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() === localInput.valueOf();
|
|
} else {
|
|
inputMs = localInput.valueOf();
|
|
return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
|
|
}
|
|
}
|
|
|
|
function isSameOrAfter (input, units) {
|
|
return this.isSame(input, units) || this.isAfter(input,units);
|
|
}
|
|
|
|
function isSameOrBefore (input, units) {
|
|
return this.isSame(input, units) || this.isBefore(input,units);
|
|
}
|
|
|
|
function diff (input, units, asFloat) {
|
|
var that,
|
|
zoneDelta,
|
|
output;
|
|
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
|
|
that = cloneWithOffset(input, this);
|
|
|
|
if (!that.isValid()) {
|
|
return NaN;
|
|
}
|
|
|
|
zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
|
|
|
|
units = normalizeUnits(units);
|
|
|
|
switch (units) {
|
|
case 'year': output = monthDiff(this, that) / 12; break;
|
|
case 'month': output = monthDiff(this, that); break;
|
|
case 'quarter': output = monthDiff(this, that) / 3; break;
|
|
case 'second': output = (this - that) / 1e3; break; // 1000
|
|
case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
|
|
case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
|
|
case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
|
|
case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
|
|
default: output = this - that;
|
|
}
|
|
|
|
return asFloat ? output : absFloor(output);
|
|
}
|
|
|
|
function monthDiff (a, b) {
|
|
// difference in months
|
|
var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
|
|
// b is in (anchor - 1 month, anchor + 1 month)
|
|
anchor = a.clone().add(wholeMonthDiff, 'months'),
|
|
anchor2, adjust;
|
|
|
|
if (b - anchor < 0) {
|
|
anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
|
|
// linear across the month
|
|
adjust = (b - anchor) / (anchor - anchor2);
|
|
} else {
|
|
anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
|
|
// linear across the month
|
|
adjust = (b - anchor) / (anchor2 - anchor);
|
|
}
|
|
|
|
//check for negative zero, return zero if negative zero
|
|
return -(wholeMonthDiff + adjust) || 0;
|
|
}
|
|
|
|
hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
|
|
hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
|
|
|
|
function toString () {
|
|
return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
|
|
}
|
|
|
|
function toISOString(keepOffset) {
|
|
if (!this.isValid()) {
|
|
return null;
|
|
}
|
|
var utc = keepOffset !== true;
|
|
var m = utc ? this.clone().utc() : this;
|
|
if (m.year() < 0 || m.year() > 9999) {
|
|
return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
}
|
|
if (isFunction(Date.prototype.toISOString)) {
|
|
// native implementation is ~50x faster, use it when we can
|
|
if (utc) {
|
|
return this.toDate().toISOString();
|
|
} else {
|
|
return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z'));
|
|
}
|
|
}
|
|
return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
}
|
|
|
|
/**
|
|
* Return a human readable representation of a moment that can
|
|
* also be evaluated to get a new moment which is the same
|
|
*
|
|
* @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
|
|
*/
|
|
function inspect () {
|
|
if (!this.isValid()) {
|
|
return 'moment.invalid(/* ' + this._i + ' */)';
|
|
}
|
|
var func = 'moment';
|
|
var zone = '';
|
|
if (!this.isLocal()) {
|
|
func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
|
|
zone = 'Z';
|
|
}
|
|
var prefix = '[' + func + '("]';
|
|
var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
|
|
var datetime = '-MM-DD[T]HH:mm:ss.SSS';
|
|
var suffix = zone + '[")]';
|
|
|
|
return this.format(prefix + year + datetime + suffix);
|
|
}
|
|
|
|
function format (inputString) {
|
|
if (!inputString) {
|
|
inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
|
|
}
|
|
var output = formatMoment(this, inputString);
|
|
return this.localeData().postformat(output);
|
|
}
|
|
|
|
function from (time, withoutSuffix) {
|
|
if (this.isValid() &&
|
|
((isMoment(time) && time.isValid()) ||
|
|
createLocal(time).isValid())) {
|
|
return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
|
|
} else {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
}
|
|
|
|
function fromNow (withoutSuffix) {
|
|
return this.from(createLocal(), withoutSuffix);
|
|
}
|
|
|
|
function to (time, withoutSuffix) {
|
|
if (this.isValid() &&
|
|
((isMoment(time) && time.isValid()) ||
|
|
createLocal(time).isValid())) {
|
|
return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
|
|
} else {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
}
|
|
|
|
function toNow (withoutSuffix) {
|
|
return this.to(createLocal(), withoutSuffix);
|
|
}
|
|
|
|
// If passed a locale key, it will set the locale for this
|
|
// instance. Otherwise, it will return the locale configuration
|
|
// variables for this instance.
|
|
function locale (key) {
|
|
var newLocaleData;
|
|
|
|
if (key === undefined) {
|
|
return this._locale._abbr;
|
|
} else {
|
|
newLocaleData = getLocale(key);
|
|
if (newLocaleData != null) {
|
|
this._locale = newLocaleData;
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
var lang = deprecate(
|
|
'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
|
|
function (key) {
|
|
if (key === undefined) {
|
|
return this.localeData();
|
|
} else {
|
|
return this.locale(key);
|
|
}
|
|
}
|
|
);
|
|
|
|
function localeData () {
|
|
return this._locale;
|
|
}
|
|
|
|
function startOf (units) {
|
|
units = normalizeUnits(units);
|
|
// the following switch intentionally omits break keywords
|
|
// to utilize falling through the cases.
|
|
switch (units) {
|
|
case 'year':
|
|
this.month(0);
|
|
/* falls through */
|
|
case 'quarter':
|
|
case 'month':
|
|
this.date(1);
|
|
/* falls through */
|
|
case 'week':
|
|
case 'isoWeek':
|
|
case 'day':
|
|
case 'date':
|
|
this.hours(0);
|
|
/* falls through */
|
|
case 'hour':
|
|
this.minutes(0);
|
|
/* falls through */
|
|
case 'minute':
|
|
this.seconds(0);
|
|
/* falls through */
|
|
case 'second':
|
|
this.milliseconds(0);
|
|
}
|
|
|
|
// weeks are a special case
|
|
if (units === 'week') {
|
|
this.weekday(0);
|
|
}
|
|
if (units === 'isoWeek') {
|
|
this.isoWeekday(1);
|
|
}
|
|
|
|
// quarters are also special
|
|
if (units === 'quarter') {
|
|
this.month(Math.floor(this.month() / 3) * 3);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
function endOf (units) {
|
|
units = normalizeUnits(units);
|
|
if (units === undefined || units === 'millisecond') {
|
|
return this;
|
|
}
|
|
|
|
// 'date' is an alias for 'day', so it should be considered as such.
|
|
if (units === 'date') {
|
|
units = 'day';
|
|
}
|
|
|
|
return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
|
|
}
|
|
|
|
function valueOf () {
|
|
return this._d.valueOf() - ((this._offset || 0) * 60000);
|
|
}
|
|
|
|
function unix () {
|
|
return Math.floor(this.valueOf() / 1000);
|
|
}
|
|
|
|
function toDate () {
|
|
return new Date(this.valueOf());
|
|
}
|
|
|
|
function toArray () {
|
|
var m = this;
|
|
return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
|
|
}
|
|
|
|
function toObject () {
|
|
var m = this;
|
|
return {
|
|
years: m.year(),
|
|
months: m.month(),
|
|
date: m.date(),
|
|
hours: m.hours(),
|
|
minutes: m.minutes(),
|
|
seconds: m.seconds(),
|
|
milliseconds: m.milliseconds()
|
|
};
|
|
}
|
|
|
|
function toJSON () {
|
|
// new Date(NaN).toJSON() === null
|
|
return this.isValid() ? this.toISOString() : null;
|
|
}
|
|
|
|
function isValid$2 () {
|
|
return isValid(this);
|
|
}
|
|
|
|
function parsingFlags () {
|
|
return extend({}, getParsingFlags(this));
|
|
}
|
|
|
|
function invalidAt () {
|
|
return getParsingFlags(this).overflow;
|
|
}
|
|
|
|
function creationData() {
|
|
return {
|
|
input: this._i,
|
|
format: this._f,
|
|
locale: this._locale,
|
|
isUTC: this._isUTC,
|
|
strict: this._strict
|
|
};
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken(0, ['gg', 2], 0, function () {
|
|
return this.weekYear() % 100;
|
|
});
|
|
|
|
addFormatToken(0, ['GG', 2], 0, function () {
|
|
return this.isoWeekYear() % 100;
|
|
});
|
|
|
|
function addWeekYearFormatToken (token, getter) {
|
|
addFormatToken(0, [token, token.length], 0, getter);
|
|
}
|
|
|
|
addWeekYearFormatToken('gggg', 'weekYear');
|
|
addWeekYearFormatToken('ggggg', 'weekYear');
|
|
addWeekYearFormatToken('GGGG', 'isoWeekYear');
|
|
addWeekYearFormatToken('GGGGG', 'isoWeekYear');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('weekYear', 'gg');
|
|
addUnitAlias('isoWeekYear', 'GG');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('weekYear', 1);
|
|
addUnitPriority('isoWeekYear', 1);
|
|
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('G', matchSigned);
|
|
addRegexToken('g', matchSigned);
|
|
addRegexToken('GG', match1to2, match2);
|
|
addRegexToken('gg', match1to2, match2);
|
|
addRegexToken('GGGG', match1to4, match4);
|
|
addRegexToken('gggg', match1to4, match4);
|
|
addRegexToken('GGGGG', match1to6, match6);
|
|
addRegexToken('ggggg', match1to6, match6);
|
|
|
|
addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
|
|
week[token.substr(0, 2)] = toInt(input);
|
|
});
|
|
|
|
addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
|
|
week[token] = hooks.parseTwoDigitYear(input);
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
function getSetWeekYear (input) {
|
|
return getSetWeekYearHelper.call(this,
|
|
input,
|
|
this.week(),
|
|
this.weekday(),
|
|
this.localeData()._week.dow,
|
|
this.localeData()._week.doy);
|
|
}
|
|
|
|
function getSetISOWeekYear (input) {
|
|
return getSetWeekYearHelper.call(this,
|
|
input, this.isoWeek(), this.isoWeekday(), 1, 4);
|
|
}
|
|
|
|
function getISOWeeksInYear () {
|
|
return weeksInYear(this.year(), 1, 4);
|
|
}
|
|
|
|
function getWeeksInYear () {
|
|
var weekInfo = this.localeData()._week;
|
|
return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
|
|
}
|
|
|
|
function getSetWeekYearHelper(input, week, weekday, dow, doy) {
|
|
var weeksTarget;
|
|
if (input == null) {
|
|
return weekOfYear(this, dow, doy).year;
|
|
} else {
|
|
weeksTarget = weeksInYear(input, dow, doy);
|
|
if (week > weeksTarget) {
|
|
week = weeksTarget;
|
|
}
|
|
return setWeekAll.call(this, input, week, weekday, dow, doy);
|
|
}
|
|
}
|
|
|
|
function setWeekAll(weekYear, week, weekday, dow, doy) {
|
|
var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
|
|
date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
|
|
|
|
this.year(date.getUTCFullYear());
|
|
this.month(date.getUTCMonth());
|
|
this.date(date.getUTCDate());
|
|
return this;
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('Q', 0, 'Qo', 'quarter');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('quarter', 'Q');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('quarter', 7);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Q', match1);
|
|
addParseToken('Q', function (input, array) {
|
|
array[MONTH] = (toInt(input) - 1) * 3;
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
function getSetQuarter (input) {
|
|
return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('D', ['DD', 2], 'Do', 'date');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('date', 'D');
|
|
|
|
// PRIOROITY
|
|
addUnitPriority('date', 9);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('D', match1to2);
|
|
addRegexToken('DD', match1to2, match2);
|
|
addRegexToken('Do', function (isStrict, locale) {
|
|
// TODO: Remove "ordinalParse" fallback in next major release.
|
|
return isStrict ?
|
|
(locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
|
|
locale._dayOfMonthOrdinalParseLenient;
|
|
});
|
|
|
|
addParseToken(['D', 'DD'], DATE);
|
|
addParseToken('Do', function (input, array) {
|
|
array[DATE] = toInt(input.match(match1to2)[0]);
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
var getSetDayOfMonth = makeGetSet('Date', true);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('dayOfYear', 'DDD');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('dayOfYear', 4);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('DDD', match1to3);
|
|
addRegexToken('DDDD', match3);
|
|
addParseToken(['DDD', 'DDDD'], function (input, array, config) {
|
|
config._dayOfYear = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// MOMENTS
|
|
|
|
function getSetDayOfYear (input) {
|
|
var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
|
|
return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('m', ['mm', 2], 0, 'minute');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('minute', 'm');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('minute', 14);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('m', match1to2);
|
|
addRegexToken('mm', match1to2, match2);
|
|
addParseToken(['m', 'mm'], MINUTE);
|
|
|
|
// MOMENTS
|
|
|
|
var getSetMinute = makeGetSet('Minutes', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('s', ['ss', 2], 0, 'second');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('second', 's');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('second', 15);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('s', match1to2);
|
|
addRegexToken('ss', match1to2, match2);
|
|
addParseToken(['s', 'ss'], SECOND);
|
|
|
|
// MOMENTS
|
|
|
|
var getSetSecond = makeGetSet('Seconds', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('S', 0, 0, function () {
|
|
return ~~(this.millisecond() / 100);
|
|
});
|
|
|
|
addFormatToken(0, ['SS', 2], 0, function () {
|
|
return ~~(this.millisecond() / 10);
|
|
});
|
|
|
|
addFormatToken(0, ['SSS', 3], 0, 'millisecond');
|
|
addFormatToken(0, ['SSSS', 4], 0, function () {
|
|
return this.millisecond() * 10;
|
|
});
|
|
addFormatToken(0, ['SSSSS', 5], 0, function () {
|
|
return this.millisecond() * 100;
|
|
});
|
|
addFormatToken(0, ['SSSSSS', 6], 0, function () {
|
|
return this.millisecond() * 1000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSS', 7], 0, function () {
|
|
return this.millisecond() * 10000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
|
|
return this.millisecond() * 100000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
|
|
return this.millisecond() * 1000000;
|
|
});
|
|
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('millisecond', 'ms');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('millisecond', 16);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('S', match1to3, match1);
|
|
addRegexToken('SS', match1to3, match2);
|
|
addRegexToken('SSS', match1to3, match3);
|
|
|
|
var token;
|
|
for (token = 'SSSS'; token.length <= 9; token += 'S') {
|
|
addRegexToken(token, matchUnsigned);
|
|
}
|
|
|
|
function parseMs(input, array) {
|
|
array[MILLISECOND] = toInt(('0.' + input) * 1000);
|
|
}
|
|
|
|
for (token = 'S'; token.length <= 9; token += 'S') {
|
|
addParseToken(token, parseMs);
|
|
}
|
|
// MOMENTS
|
|
|
|
var getSetMillisecond = makeGetSet('Milliseconds', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('z', 0, 0, 'zoneAbbr');
|
|
addFormatToken('zz', 0, 0, 'zoneName');
|
|
|
|
// MOMENTS
|
|
|
|
function getZoneAbbr () {
|
|
return this._isUTC ? 'UTC' : '';
|
|
}
|
|
|
|
function getZoneName () {
|
|
return this._isUTC ? 'Coordinated Universal Time' : '';
|
|
}
|
|
|
|
var proto = Moment.prototype;
|
|
|
|
proto.add = add;
|
|
proto.calendar = calendar$1;
|
|
proto.clone = clone;
|
|
proto.diff = diff;
|
|
proto.endOf = endOf;
|
|
proto.format = format;
|
|
proto.from = from;
|
|
proto.fromNow = fromNow;
|
|
proto.to = to;
|
|
proto.toNow = toNow;
|
|
proto.get = stringGet;
|
|
proto.invalidAt = invalidAt;
|
|
proto.isAfter = isAfter;
|
|
proto.isBefore = isBefore;
|
|
proto.isBetween = isBetween;
|
|
proto.isSame = isSame;
|
|
proto.isSameOrAfter = isSameOrAfter;
|
|
proto.isSameOrBefore = isSameOrBefore;
|
|
proto.isValid = isValid$2;
|
|
proto.lang = lang;
|
|
proto.locale = locale;
|
|
proto.localeData = localeData;
|
|
proto.max = prototypeMax;
|
|
proto.min = prototypeMin;
|
|
proto.parsingFlags = parsingFlags;
|
|
proto.set = stringSet;
|
|
proto.startOf = startOf;
|
|
proto.subtract = subtract;
|
|
proto.toArray = toArray;
|
|
proto.toObject = toObject;
|
|
proto.toDate = toDate;
|
|
proto.toISOString = toISOString;
|
|
proto.inspect = inspect;
|
|
proto.toJSON = toJSON;
|
|
proto.toString = toString;
|
|
proto.unix = unix;
|
|
proto.valueOf = valueOf;
|
|
proto.creationData = creationData;
|
|
proto.year = getSetYear;
|
|
proto.isLeapYear = getIsLeapYear;
|
|
proto.weekYear = getSetWeekYear;
|
|
proto.isoWeekYear = getSetISOWeekYear;
|
|
proto.quarter = proto.quarters = getSetQuarter;
|
|
proto.month = getSetMonth;
|
|
proto.daysInMonth = getDaysInMonth;
|
|
proto.week = proto.weeks = getSetWeek;
|
|
proto.isoWeek = proto.isoWeeks = getSetISOWeek;
|
|
proto.weeksInYear = getWeeksInYear;
|
|
proto.isoWeeksInYear = getISOWeeksInYear;
|
|
proto.date = getSetDayOfMonth;
|
|
proto.day = proto.days = getSetDayOfWeek;
|
|
proto.weekday = getSetLocaleDayOfWeek;
|
|
proto.isoWeekday = getSetISODayOfWeek;
|
|
proto.dayOfYear = getSetDayOfYear;
|
|
proto.hour = proto.hours = getSetHour;
|
|
proto.minute = proto.minutes = getSetMinute;
|
|
proto.second = proto.seconds = getSetSecond;
|
|
proto.millisecond = proto.milliseconds = getSetMillisecond;
|
|
proto.utcOffset = getSetOffset;
|
|
proto.utc = setOffsetToUTC;
|
|
proto.local = setOffsetToLocal;
|
|
proto.parseZone = setOffsetToParsedOffset;
|
|
proto.hasAlignedHourOffset = hasAlignedHourOffset;
|
|
proto.isDST = isDaylightSavingTime;
|
|
proto.isLocal = isLocal;
|
|
proto.isUtcOffset = isUtcOffset;
|
|
proto.isUtc = isUtc;
|
|
proto.isUTC = isUtc;
|
|
proto.zoneAbbr = getZoneAbbr;
|
|
proto.zoneName = getZoneName;
|
|
proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
|
|
proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
|
|
proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
|
|
proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
|
|
proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
|
|
|
|
function createUnix (input) {
|
|
return createLocal(input * 1000);
|
|
}
|
|
|
|
function createInZone () {
|
|
return createLocal.apply(null, arguments).parseZone();
|
|
}
|
|
|
|
function preParsePostFormat (string) {
|
|
return string;
|
|
}
|
|
|
|
var proto$1 = Locale.prototype;
|
|
|
|
proto$1.calendar = calendar;
|
|
proto$1.longDateFormat = longDateFormat;
|
|
proto$1.invalidDate = invalidDate;
|
|
proto$1.ordinal = ordinal;
|
|
proto$1.preparse = preParsePostFormat;
|
|
proto$1.postformat = preParsePostFormat;
|
|
proto$1.relativeTime = relativeTime;
|
|
proto$1.pastFuture = pastFuture;
|
|
proto$1.set = set;
|
|
|
|
proto$1.months = localeMonths;
|
|
proto$1.monthsShort = localeMonthsShort;
|
|
proto$1.monthsParse = localeMonthsParse;
|
|
proto$1.monthsRegex = monthsRegex;
|
|
proto$1.monthsShortRegex = monthsShortRegex;
|
|
proto$1.week = localeWeek;
|
|
proto$1.firstDayOfYear = localeFirstDayOfYear;
|
|
proto$1.firstDayOfWeek = localeFirstDayOfWeek;
|
|
|
|
proto$1.weekdays = localeWeekdays;
|
|
proto$1.weekdaysMin = localeWeekdaysMin;
|
|
proto$1.weekdaysShort = localeWeekdaysShort;
|
|
proto$1.weekdaysParse = localeWeekdaysParse;
|
|
|
|
proto$1.weekdaysRegex = weekdaysRegex;
|
|
proto$1.weekdaysShortRegex = weekdaysShortRegex;
|
|
proto$1.weekdaysMinRegex = weekdaysMinRegex;
|
|
|
|
proto$1.isPM = localeIsPM;
|
|
proto$1.meridiem = localeMeridiem;
|
|
|
|
function get$1 (format, index, field, setter) {
|
|
var locale = getLocale();
|
|
var utc = createUTC().set(setter, index);
|
|
return locale[field](utc, format);
|
|
}
|
|
|
|
function listMonthsImpl (format, index, field) {
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
|
|
if (index != null) {
|
|
return get$1(format, index, field, 'month');
|
|
}
|
|
|
|
var i;
|
|
var out = [];
|
|
for (i = 0; i < 12; i++) {
|
|
out[i] = get$1(format, i, field, 'month');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ()
|
|
// (5)
|
|
// (fmt, 5)
|
|
// (fmt)
|
|
// (true)
|
|
// (true, 5)
|
|
// (true, fmt, 5)
|
|
// (true, fmt)
|
|
function listWeekdaysImpl (localeSorted, format, index, field) {
|
|
if (typeof localeSorted === 'boolean') {
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
} else {
|
|
format = localeSorted;
|
|
index = format;
|
|
localeSorted = false;
|
|
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
}
|
|
|
|
var locale = getLocale(),
|
|
shift = localeSorted ? locale._week.dow : 0;
|
|
|
|
if (index != null) {
|
|
return get$1(format, (index + shift) % 7, field, 'day');
|
|
}
|
|
|
|
var i;
|
|
var out = [];
|
|
for (i = 0; i < 7; i++) {
|
|
out[i] = get$1(format, (i + shift) % 7, field, 'day');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function listMonths (format, index) {
|
|
return listMonthsImpl(format, index, 'months');
|
|
}
|
|
|
|
function listMonthsShort (format, index) {
|
|
return listMonthsImpl(format, index, 'monthsShort');
|
|
}
|
|
|
|
function listWeekdays (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
|
|
}
|
|
|
|
function listWeekdaysShort (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
|
|
}
|
|
|
|
function listWeekdaysMin (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
|
|
}
|
|
|
|
getSetGlobalLocale('en', {
|
|
dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
|
|
ordinal : function (number) {
|
|
var b = number % 10,
|
|
output = (toInt(number % 100 / 10) === 1) ? 'th' :
|
|
(b === 1) ? 'st' :
|
|
(b === 2) ? 'nd' :
|
|
(b === 3) ? 'rd' : 'th';
|
|
return number + output;
|
|
}
|
|
});
|
|
|
|
// Side effect imports
|
|
|
|
hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
|
|
hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
|
|
|
|
var mathAbs = Math.abs;
|
|
|
|
function abs () {
|
|
var data = this._data;
|
|
|
|
this._milliseconds = mathAbs(this._milliseconds);
|
|
this._days = mathAbs(this._days);
|
|
this._months = mathAbs(this._months);
|
|
|
|
data.milliseconds = mathAbs(data.milliseconds);
|
|
data.seconds = mathAbs(data.seconds);
|
|
data.minutes = mathAbs(data.minutes);
|
|
data.hours = mathAbs(data.hours);
|
|
data.months = mathAbs(data.months);
|
|
data.years = mathAbs(data.years);
|
|
|
|
return this;
|
|
}
|
|
|
|
function addSubtract$1 (duration, input, value, direction) {
|
|
var other = createDuration(input, value);
|
|
|
|
duration._milliseconds += direction * other._milliseconds;
|
|
duration._days += direction * other._days;
|
|
duration._months += direction * other._months;
|
|
|
|
return duration._bubble();
|
|
}
|
|
|
|
// supports only 2.0-style add(1, 's') or add(duration)
|
|
function add$1 (input, value) {
|
|
return addSubtract$1(this, input, value, 1);
|
|
}
|
|
|
|
// supports only 2.0-style subtract(1, 's') or subtract(duration)
|
|
function subtract$1 (input, value) {
|
|
return addSubtract$1(this, input, value, -1);
|
|
}
|
|
|
|
function absCeil (number) {
|
|
if (number < 0) {
|
|
return Math.floor(number);
|
|
} else {
|
|
return Math.ceil(number);
|
|
}
|
|
}
|
|
|
|
function bubble () {
|
|
var milliseconds = this._milliseconds;
|
|
var days = this._days;
|
|
var months = this._months;
|
|
var data = this._data;
|
|
var seconds, minutes, hours, years, monthsFromDays;
|
|
|
|
// if we have a mix of positive and negative values, bubble down first
|
|
// check: https://github.com/moment/moment/issues/2166
|
|
if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
|
|
(milliseconds <= 0 && days <= 0 && months <= 0))) {
|
|
milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
|
|
days = 0;
|
|
months = 0;
|
|
}
|
|
|
|
// The following code bubbles up values, see the tests for
|
|
// examples of what that means.
|
|
data.milliseconds = milliseconds % 1000;
|
|
|
|
seconds = absFloor(milliseconds / 1000);
|
|
data.seconds = seconds % 60;
|
|
|
|
minutes = absFloor(seconds / 60);
|
|
data.minutes = minutes % 60;
|
|
|
|
hours = absFloor(minutes / 60);
|
|
data.hours = hours % 24;
|
|
|
|
days += absFloor(hours / 24);
|
|
|
|
// convert days to months
|
|
monthsFromDays = absFloor(daysToMonths(days));
|
|
months += monthsFromDays;
|
|
days -= absCeil(monthsToDays(monthsFromDays));
|
|
|
|
// 12 months -> 1 year
|
|
years = absFloor(months / 12);
|
|
months %= 12;
|
|
|
|
data.days = days;
|
|
data.months = months;
|
|
data.years = years;
|
|
|
|
return this;
|
|
}
|
|
|
|
function daysToMonths (days) {
|
|
// 400 years have 146097 days (taking into account leap year rules)
|
|
// 400 years have 12 months === 4800
|
|
return days * 4800 / 146097;
|
|
}
|
|
|
|
function monthsToDays (months) {
|
|
// the reverse of daysToMonths
|
|
return months * 146097 / 4800;
|
|
}
|
|
|
|
function as (units) {
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
var days;
|
|
var months;
|
|
var milliseconds = this._milliseconds;
|
|
|
|
units = normalizeUnits(units);
|
|
|
|
if (units === 'month' || units === 'year') {
|
|
days = this._days + milliseconds / 864e5;
|
|
months = this._months + daysToMonths(days);
|
|
return units === 'month' ? months : months / 12;
|
|
} else {
|
|
// handle milliseconds separately because of floating point math errors (issue #1867)
|
|
days = this._days + Math.round(monthsToDays(this._months));
|
|
switch (units) {
|
|
case 'week' : return days / 7 + milliseconds / 6048e5;
|
|
case 'day' : return days + milliseconds / 864e5;
|
|
case 'hour' : return days * 24 + milliseconds / 36e5;
|
|
case 'minute' : return days * 1440 + milliseconds / 6e4;
|
|
case 'second' : return days * 86400 + milliseconds / 1000;
|
|
// Math.floor prevents floating point math errors here
|
|
case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
|
|
default: throw new Error('Unknown unit ' + units);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Use this.as('ms')?
|
|
function valueOf$1 () {
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
return (
|
|
this._milliseconds +
|
|
this._days * 864e5 +
|
|
(this._months % 12) * 2592e6 +
|
|
toInt(this._months / 12) * 31536e6
|
|
);
|
|
}
|
|
|
|
function makeAs (alias) {
|
|
return function () {
|
|
return this.as(alias);
|
|
};
|
|
}
|
|
|
|
var asMilliseconds = makeAs('ms');
|
|
var asSeconds = makeAs('s');
|
|
var asMinutes = makeAs('m');
|
|
var asHours = makeAs('h');
|
|
var asDays = makeAs('d');
|
|
var asWeeks = makeAs('w');
|
|
var asMonths = makeAs('M');
|
|
var asYears = makeAs('y');
|
|
|
|
function clone$1 () {
|
|
return createDuration(this);
|
|
}
|
|
|
|
function get$2 (units) {
|
|
units = normalizeUnits(units);
|
|
return this.isValid() ? this[units + 's']() : NaN;
|
|
}
|
|
|
|
function makeGetter(name) {
|
|
return function () {
|
|
return this.isValid() ? this._data[name] : NaN;
|
|
};
|
|
}
|
|
|
|
var milliseconds = makeGetter('milliseconds');
|
|
var seconds = makeGetter('seconds');
|
|
var minutes = makeGetter('minutes');
|
|
var hours = makeGetter('hours');
|
|
var days = makeGetter('days');
|
|
var months = makeGetter('months');
|
|
var years = makeGetter('years');
|
|
|
|
function weeks () {
|
|
return absFloor(this.days() / 7);
|
|
}
|
|
|
|
var round = Math.round;
|
|
var thresholds = {
|
|
ss: 44, // a few seconds to seconds
|
|
s : 45, // seconds to minute
|
|
m : 45, // minutes to hour
|
|
h : 22, // hours to day
|
|
d : 26, // days to month
|
|
M : 11 // months to year
|
|
};
|
|
|
|
// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
|
|
function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
|
|
return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
|
|
}
|
|
|
|
function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
|
|
var duration = createDuration(posNegDuration).abs();
|
|
var seconds = round(duration.as('s'));
|
|
var minutes = round(duration.as('m'));
|
|
var hours = round(duration.as('h'));
|
|
var days = round(duration.as('d'));
|
|
var months = round(duration.as('M'));
|
|
var years = round(duration.as('y'));
|
|
|
|
var a = seconds <= thresholds.ss && ['s', seconds] ||
|
|
seconds < thresholds.s && ['ss', seconds] ||
|
|
minutes <= 1 && ['m'] ||
|
|
minutes < thresholds.m && ['mm', minutes] ||
|
|
hours <= 1 && ['h'] ||
|
|
hours < thresholds.h && ['hh', hours] ||
|
|
days <= 1 && ['d'] ||
|
|
days < thresholds.d && ['dd', days] ||
|
|
months <= 1 && ['M'] ||
|
|
months < thresholds.M && ['MM', months] ||
|
|
years <= 1 && ['y'] || ['yy', years];
|
|
|
|
a[2] = withoutSuffix;
|
|
a[3] = +posNegDuration > 0;
|
|
a[4] = locale;
|
|
return substituteTimeAgo.apply(null, a);
|
|
}
|
|
|
|
// This function allows you to set the rounding function for relative time strings
|
|
function getSetRelativeTimeRounding (roundingFunction) {
|
|
if (roundingFunction === undefined) {
|
|
return round;
|
|
}
|
|
if (typeof(roundingFunction) === 'function') {
|
|
round = roundingFunction;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This function allows you to set a threshold for relative time strings
|
|
function getSetRelativeTimeThreshold (threshold, limit) {
|
|
if (thresholds[threshold] === undefined) {
|
|
return false;
|
|
}
|
|
if (limit === undefined) {
|
|
return thresholds[threshold];
|
|
}
|
|
thresholds[threshold] = limit;
|
|
if (threshold === 's') {
|
|
thresholds.ss = limit - 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function humanize (withSuffix) {
|
|
if (!this.isValid()) {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
|
|
var locale = this.localeData();
|
|
var output = relativeTime$1(this, !withSuffix, locale);
|
|
|
|
if (withSuffix) {
|
|
output = locale.pastFuture(+this, output);
|
|
}
|
|
|
|
return locale.postformat(output);
|
|
}
|
|
|
|
var abs$1 = Math.abs;
|
|
|
|
function sign(x) {
|
|
return ((x > 0) - (x < 0)) || +x;
|
|
}
|
|
|
|
function toISOString$1() {
|
|
// for ISO strings we do not use the normal bubbling rules:
|
|
// * milliseconds bubble up until they become hours
|
|
// * days do not bubble at all
|
|
// * months bubble up until they become years
|
|
// This is because there is no context-free conversion between hours and days
|
|
// (think of clock changes)
|
|
// and also not between days and months (28-31 days per month)
|
|
if (!this.isValid()) {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
|
|
var seconds = abs$1(this._milliseconds) / 1000;
|
|
var days = abs$1(this._days);
|
|
var months = abs$1(this._months);
|
|
var minutes, hours, years;
|
|
|
|
// 3600 seconds -> 60 minutes -> 1 hour
|
|
minutes = absFloor(seconds / 60);
|
|
hours = absFloor(minutes / 60);
|
|
seconds %= 60;
|
|
minutes %= 60;
|
|
|
|
// 12 months -> 1 year
|
|
years = absFloor(months / 12);
|
|
months %= 12;
|
|
|
|
|
|
// inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
|
|
var Y = years;
|
|
var M = months;
|
|
var D = days;
|
|
var h = hours;
|
|
var m = minutes;
|
|
var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
|
|
var total = this.asSeconds();
|
|
|
|
if (!total) {
|
|
// this is the same as C#'s (Noda) and python (isodate)...
|
|
// but not other JS (goog.date)
|
|
return 'P0D';
|
|
}
|
|
|
|
var totalSign = total < 0 ? '-' : '';
|
|
var ymSign = sign(this._months) !== sign(total) ? '-' : '';
|
|
var daysSign = sign(this._days) !== sign(total) ? '-' : '';
|
|
var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
|
|
|
|
return totalSign + 'P' +
|
|
(Y ? ymSign + Y + 'Y' : '') +
|
|
(M ? ymSign + M + 'M' : '') +
|
|
(D ? daysSign + D + 'D' : '') +
|
|
((h || m || s) ? 'T' : '') +
|
|
(h ? hmsSign + h + 'H' : '') +
|
|
(m ? hmsSign + m + 'M' : '') +
|
|
(s ? hmsSign + s + 'S' : '');
|
|
}
|
|
|
|
var proto$2 = Duration.prototype;
|
|
|
|
proto$2.isValid = isValid$1;
|
|
proto$2.abs = abs;
|
|
proto$2.add = add$1;
|
|
proto$2.subtract = subtract$1;
|
|
proto$2.as = as;
|
|
proto$2.asMilliseconds = asMilliseconds;
|
|
proto$2.asSeconds = asSeconds;
|
|
proto$2.asMinutes = asMinutes;
|
|
proto$2.asHours = asHours;
|
|
proto$2.asDays = asDays;
|
|
proto$2.asWeeks = asWeeks;
|
|
proto$2.asMonths = asMonths;
|
|
proto$2.asYears = asYears;
|
|
proto$2.valueOf = valueOf$1;
|
|
proto$2._bubble = bubble;
|
|
proto$2.clone = clone$1;
|
|
proto$2.get = get$2;
|
|
proto$2.milliseconds = milliseconds;
|
|
proto$2.seconds = seconds;
|
|
proto$2.minutes = minutes;
|
|
proto$2.hours = hours;
|
|
proto$2.days = days;
|
|
proto$2.weeks = weeks;
|
|
proto$2.months = months;
|
|
proto$2.years = years;
|
|
proto$2.humanize = humanize;
|
|
proto$2.toISOString = toISOString$1;
|
|
proto$2.toString = toISOString$1;
|
|
proto$2.toJSON = toISOString$1;
|
|
proto$2.locale = locale;
|
|
proto$2.localeData = localeData;
|
|
|
|
proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
|
|
proto$2.lang = lang;
|
|
|
|
// Side effect imports
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('X', 0, 0, 'unix');
|
|
addFormatToken('x', 0, 0, 'valueOf');
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('x', matchSigned);
|
|
addRegexToken('X', matchTimestamp);
|
|
addParseToken('X', function (input, array, config) {
|
|
config._d = new Date(parseFloat(input, 10) * 1000);
|
|
});
|
|
addParseToken('x', function (input, array, config) {
|
|
config._d = new Date(toInt(input));
|
|
});
|
|
|
|
// Side effect imports
|
|
|
|
|
|
hooks.version = '2.21.0';
|
|
|
|
setHookCallback(createLocal);
|
|
|
|
hooks.fn = proto;
|
|
hooks.min = min;
|
|
hooks.max = max;
|
|
hooks.now = now;
|
|
hooks.utc = createUTC;
|
|
hooks.unix = createUnix;
|
|
hooks.months = listMonths;
|
|
hooks.isDate = isDate;
|
|
hooks.locale = getSetGlobalLocale;
|
|
hooks.invalid = createInvalid;
|
|
hooks.duration = createDuration;
|
|
hooks.isMoment = isMoment;
|
|
hooks.weekdays = listWeekdays;
|
|
hooks.parseZone = createInZone;
|
|
hooks.localeData = getLocale;
|
|
hooks.isDuration = isDuration;
|
|
hooks.monthsShort = listMonthsShort;
|
|
hooks.weekdaysMin = listWeekdaysMin;
|
|
hooks.defineLocale = defineLocale;
|
|
hooks.updateLocale = updateLocale;
|
|
hooks.locales = listLocales;
|
|
hooks.weekdaysShort = listWeekdaysShort;
|
|
hooks.normalizeUnits = normalizeUnits;
|
|
hooks.relativeTimeRounding = getSetRelativeTimeRounding;
|
|
hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
|
|
hooks.calendarFormat = getCalendarFormat;
|
|
hooks.prototype = proto;
|
|
|
|
// currently HTML5 input type only supports 24-hour formats
|
|
hooks.HTML5_FMT = {
|
|
DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
|
|
DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
|
|
DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
|
|
DATE: 'YYYY-MM-DD', // <input type="date" />
|
|
TIME: 'HH:mm', // <input type="time" />
|
|
TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
|
|
TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
|
|
WEEK: 'YYYY-[W]WW', // <input type="week" />
|
|
MONTH: 'YYYY-MM' // <input type="month" />
|
|
};
|
|
|
|
return hooks;
|
|
|
|
})));
|
|
|
|
/*!
|
|
* Bootstrap v3.3.5 (http://getbootstrap.com)
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under the MIT license
|
|
*/
|
|
|
|
if (typeof jQuery === 'undefined') {
|
|
throw new Error('Bootstrap\'s JavaScript requires jQuery')
|
|
}
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
var version = $.fn.jquery.split(' ')[0].split('.')
|
|
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
|
|
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
|
|
}
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: transition.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#transitions
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
|
// ============================================================
|
|
|
|
function transitionEnd() {
|
|
var el = document.createElement('bootstrap')
|
|
|
|
var transEndEventNames = {
|
|
WebkitTransition : 'webkitTransitionEnd',
|
|
MozTransition : 'transitionend',
|
|
OTransition : 'oTransitionEnd otransitionend',
|
|
transition : 'transitionend'
|
|
}
|
|
|
|
for (var name in transEndEventNames) {
|
|
if (el.style[name] !== undefined) {
|
|
return { end: transEndEventNames[name] }
|
|
}
|
|
}
|
|
|
|
return false // explicit for ie8 ( ._.)
|
|
}
|
|
|
|
// http://blog.alexmaccaw.com/css-transitions
|
|
$.fn.emulateTransitionEnd = function (duration) {
|
|
var called = false
|
|
var $el = this
|
|
$(this).one('bsTransitionEnd', function () { called = true })
|
|
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
|
|
setTimeout(callback, duration)
|
|
return this
|
|
}
|
|
|
|
$(function () {
|
|
$.support.transition = transitionEnd()
|
|
|
|
if (!$.support.transition) return
|
|
|
|
$.event.special.bsTransitionEnd = {
|
|
bindType: $.support.transition.end,
|
|
delegateType: $.support.transition.end,
|
|
handle: function (e) {
|
|
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
|
|
}
|
|
}
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: alert.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#alerts
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// ALERT CLASS DEFINITION
|
|
// ======================
|
|
|
|
var dismiss = '[data-dismiss="alert"]'
|
|
var Alert = function (el) {
|
|
$(el).on('click', dismiss, this.close)
|
|
}
|
|
|
|
Alert.VERSION = '3.3.5'
|
|
|
|
Alert.TRANSITION_DURATION = 150
|
|
|
|
Alert.prototype.close = function (e) {
|
|
var $this = $(this)
|
|
var selector = $this.attr('data-target')
|
|
|
|
if (!selector) {
|
|
selector = $this.attr('href')
|
|
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
|
}
|
|
|
|
var $parent = $(selector)
|
|
|
|
if (e) e.preventDefault()
|
|
|
|
if (!$parent.length) {
|
|
$parent = $this.closest('.alert')
|
|
}
|
|
|
|
$parent.trigger(e = $.Event('close.bs.alert'))
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$parent.removeClass('in')
|
|
|
|
function removeElement() {
|
|
// detach from parent, fire event then clean up data
|
|
$parent.detach().trigger('closed.bs.alert').remove()
|
|
}
|
|
|
|
$.support.transition && $parent.hasClass('fade') ?
|
|
$parent
|
|
.one('bsTransitionEnd', removeElement)
|
|
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
|
|
removeElement()
|
|
}
|
|
|
|
|
|
// ALERT PLUGIN DEFINITION
|
|
// =======================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.alert')
|
|
|
|
if (!data) $this.data('bs.alert', (data = new Alert(this)))
|
|
if (typeof option == 'string') data[option].call($this)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.alert
|
|
|
|
$.fn.alert = Plugin
|
|
$.fn.alert.Constructor = Alert
|
|
|
|
|
|
// ALERT NO CONFLICT
|
|
// =================
|
|
|
|
$.fn.alert.noConflict = function () {
|
|
$.fn.alert = old
|
|
return this
|
|
}
|
|
|
|
|
|
// ALERT DATA-API
|
|
// ==============
|
|
|
|
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: button.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#buttons
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// BUTTON PUBLIC CLASS DEFINITION
|
|
// ==============================
|
|
|
|
var Button = function (element, options) {
|
|
this.$element = $(element)
|
|
this.options = $.extend({}, Button.DEFAULTS, options)
|
|
this.isLoading = false
|
|
}
|
|
|
|
Button.VERSION = '3.3.5'
|
|
|
|
Button.DEFAULTS = {
|
|
loadingText: 'loading...'
|
|
}
|
|
|
|
Button.prototype.setState = function (state) {
|
|
var d = 'disabled'
|
|
var $el = this.$element
|
|
var val = $el.is('input') ? 'val' : 'html'
|
|
var data = $el.data()
|
|
|
|
state += 'Text'
|
|
|
|
if (data.resetText == null) $el.data('resetText', $el[val]())
|
|
|
|
// push to event loop to allow forms to submit
|
|
setTimeout($.proxy(function () {
|
|
$el[val](data[state] == null ? this.options[state] : data[state])
|
|
|
|
if (state == 'loadingText') {
|
|
this.isLoading = true
|
|
$el.addClass(d).attr(d, d)
|
|
} else if (this.isLoading) {
|
|
this.isLoading = false
|
|
$el.removeClass(d).removeAttr(d)
|
|
}
|
|
}, this), 0)
|
|
}
|
|
|
|
Button.prototype.toggle = function () {
|
|
var changed = true
|
|
var $parent = this.$element.closest('[data-toggle="buttons"]')
|
|
|
|
if ($parent.length) {
|
|
var $input = this.$element.find('input')
|
|
if ($input.prop('type') == 'radio') {
|
|
if ($input.prop('checked')) changed = false
|
|
$parent.find('.active').removeClass('active')
|
|
this.$element.addClass('active')
|
|
} else if ($input.prop('type') == 'checkbox') {
|
|
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
|
|
this.$element.toggleClass('active')
|
|
}
|
|
$input.prop('checked', this.$element.hasClass('active'))
|
|
if (changed) $input.trigger('change')
|
|
} else {
|
|
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
|
|
this.$element.toggleClass('active')
|
|
}
|
|
}
|
|
|
|
|
|
// BUTTON PLUGIN DEFINITION
|
|
// ========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.button')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data) $this.data('bs.button', (data = new Button(this, options)))
|
|
|
|
if (option == 'toggle') data.toggle()
|
|
else if (option) data.setState(option)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.button
|
|
|
|
$.fn.button = Plugin
|
|
$.fn.button.Constructor = Button
|
|
|
|
|
|
// BUTTON NO CONFLICT
|
|
// ==================
|
|
|
|
$.fn.button.noConflict = function () {
|
|
$.fn.button = old
|
|
return this
|
|
}
|
|
|
|
|
|
// BUTTON DATA-API
|
|
// ===============
|
|
|
|
$(document)
|
|
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
|
|
var $btn = $(e.target)
|
|
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
|
|
Plugin.call($btn, 'toggle')
|
|
if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault()
|
|
})
|
|
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
|
|
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: carousel.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#carousel
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// CAROUSEL CLASS DEFINITION
|
|
// =========================
|
|
|
|
var Carousel = function (element, options) {
|
|
this.$element = $(element)
|
|
this.$indicators = this.$element.find('.carousel-indicators')
|
|
this.options = options
|
|
this.paused = null
|
|
this.sliding = null
|
|
this.interval = null
|
|
this.$active = null
|
|
this.$items = null
|
|
|
|
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
|
|
|
|
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
|
|
.on('mouseenter.bs.carousel', $.proxy(this.pause, this))
|
|
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
|
|
}
|
|
|
|
Carousel.VERSION = '3.3.5'
|
|
|
|
Carousel.TRANSITION_DURATION = 600
|
|
|
|
Carousel.DEFAULTS = {
|
|
interval: 5000,
|
|
pause: 'hover',
|
|
wrap: true,
|
|
keyboard: true
|
|
}
|
|
|
|
Carousel.prototype.keydown = function (e) {
|
|
if (/input|textarea/i.test(e.target.tagName)) return
|
|
switch (e.which) {
|
|
case 37: this.prev(); break
|
|
case 39: this.next(); break
|
|
default: return
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
Carousel.prototype.cycle = function (e) {
|
|
e || (this.paused = false)
|
|
|
|
this.interval && clearInterval(this.interval)
|
|
|
|
this.options.interval
|
|
&& !this.paused
|
|
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
|
|
|
|
return this
|
|
}
|
|
|
|
Carousel.prototype.getItemIndex = function (item) {
|
|
this.$items = item.parent().children('.item')
|
|
return this.$items.index(item || this.$active)
|
|
}
|
|
|
|
Carousel.prototype.getItemForDirection = function (direction, active) {
|
|
var activeIndex = this.getItemIndex(active)
|
|
var willWrap = (direction == 'prev' && activeIndex === 0)
|
|
|| (direction == 'next' && activeIndex == (this.$items.length - 1))
|
|
if (willWrap && !this.options.wrap) return active
|
|
var delta = direction == 'prev' ? -1 : 1
|
|
var itemIndex = (activeIndex + delta) % this.$items.length
|
|
return this.$items.eq(itemIndex)
|
|
}
|
|
|
|
Carousel.prototype.to = function (pos) {
|
|
var that = this
|
|
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
|
|
|
|
if (pos > (this.$items.length - 1) || pos < 0) return
|
|
|
|
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
|
|
if (activeIndex == pos) return this.pause().cycle()
|
|
|
|
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
|
|
}
|
|
|
|
Carousel.prototype.pause = function (e) {
|
|
e || (this.paused = true)
|
|
|
|
if (this.$element.find('.next, .prev').length && $.support.transition) {
|
|
this.$element.trigger($.support.transition.end)
|
|
this.cycle(true)
|
|
}
|
|
|
|
this.interval = clearInterval(this.interval)
|
|
|
|
return this
|
|
}
|
|
|
|
Carousel.prototype.next = function () {
|
|
if (this.sliding) return
|
|
return this.slide('next')
|
|
}
|
|
|
|
Carousel.prototype.prev = function () {
|
|
if (this.sliding) return
|
|
return this.slide('prev')
|
|
}
|
|
|
|
Carousel.prototype.slide = function (type, next) {
|
|
var $active = this.$element.find('.item.active')
|
|
var $next = next || this.getItemForDirection(type, $active)
|
|
var isCycling = this.interval
|
|
var direction = type == 'next' ? 'left' : 'right'
|
|
var that = this
|
|
|
|
if ($next.hasClass('active')) return (this.sliding = false)
|
|
|
|
var relatedTarget = $next[0]
|
|
var slideEvent = $.Event('slide.bs.carousel', {
|
|
relatedTarget: relatedTarget,
|
|
direction: direction
|
|
})
|
|
this.$element.trigger(slideEvent)
|
|
if (slideEvent.isDefaultPrevented()) return
|
|
|
|
this.sliding = true
|
|
|
|
isCycling && this.pause()
|
|
|
|
if (this.$indicators.length) {
|
|
this.$indicators.find('.active').removeClass('active')
|
|
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
|
|
$nextIndicator && $nextIndicator.addClass('active')
|
|
}
|
|
|
|
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
|
|
if ($.support.transition && this.$element.hasClass('slide')) {
|
|
$next.addClass(type)
|
|
$next[0].offsetWidth // force reflow
|
|
$active.addClass(direction)
|
|
$next.addClass(direction)
|
|
$active
|
|
.one('bsTransitionEnd', function () {
|
|
$next.removeClass([type, direction].join(' ')).addClass('active')
|
|
$active.removeClass(['active', direction].join(' '))
|
|
that.sliding = false
|
|
setTimeout(function () {
|
|
that.$element.trigger(slidEvent)
|
|
}, 0)
|
|
})
|
|
.emulateTransitionEnd(Carousel.TRANSITION_DURATION)
|
|
} else {
|
|
$active.removeClass('active')
|
|
$next.addClass('active')
|
|
this.sliding = false
|
|
this.$element.trigger(slidEvent)
|
|
}
|
|
|
|
isCycling && this.cycle()
|
|
|
|
return this
|
|
}
|
|
|
|
|
|
// CAROUSEL PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.carousel')
|
|
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
var action = typeof option == 'string' ? option : options.slide
|
|
|
|
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
|
|
if (typeof option == 'number') data.to(option)
|
|
else if (action) data[action]()
|
|
else if (options.interval) data.pause().cycle()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.carousel
|
|
|
|
$.fn.carousel = Plugin
|
|
$.fn.carousel.Constructor = Carousel
|
|
|
|
|
|
// CAROUSEL NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.carousel.noConflict = function () {
|
|
$.fn.carousel = old
|
|
return this
|
|
}
|
|
|
|
|
|
// CAROUSEL DATA-API
|
|
// =================
|
|
|
|
var clickHandler = function (e) {
|
|
var href
|
|
var $this = $(this)
|
|
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
|
if (!$target.hasClass('carousel')) return
|
|
var options = $.extend({}, $target.data(), $this.data())
|
|
var slideIndex = $this.attr('data-slide-to')
|
|
if (slideIndex) options.interval = false
|
|
|
|
Plugin.call($target, options)
|
|
|
|
if (slideIndex) {
|
|
$target.data('bs.carousel').to(slideIndex)
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
$(document)
|
|
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
|
|
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
|
|
|
|
$(window).on('load', function () {
|
|
$('[data-ride="carousel"]').each(function () {
|
|
var $carousel = $(this)
|
|
Plugin.call($carousel, $carousel.data())
|
|
})
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: collapse.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#collapse
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// COLLAPSE PUBLIC CLASS DEFINITION
|
|
// ================================
|
|
|
|
var Collapse = function (element, options) {
|
|
this.$element = $(element)
|
|
this.options = $.extend({}, Collapse.DEFAULTS, options)
|
|
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
|
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
|
|
this.transitioning = null
|
|
|
|
if (this.options.parent) {
|
|
this.$parent = this.getParent()
|
|
} else {
|
|
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
|
|
}
|
|
|
|
if (this.options.toggle) this.toggle()
|
|
}
|
|
|
|
Collapse.VERSION = '3.3.5'
|
|
|
|
Collapse.TRANSITION_DURATION = 350
|
|
|
|
Collapse.DEFAULTS = {
|
|
toggle: true
|
|
}
|
|
|
|
Collapse.prototype.dimension = function () {
|
|
var hasWidth = this.$element.hasClass('width')
|
|
return hasWidth ? 'width' : 'height'
|
|
}
|
|
|
|
Collapse.prototype.show = function () {
|
|
if (this.transitioning || this.$element.hasClass('in')) return
|
|
|
|
var activesData
|
|
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
|
|
|
|
if (actives && actives.length) {
|
|
activesData = actives.data('bs.collapse')
|
|
if (activesData && activesData.transitioning) return
|
|
}
|
|
|
|
var startEvent = $.Event('show.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
if (actives && actives.length) {
|
|
Plugin.call(actives, 'hide')
|
|
activesData || actives.data('bs.collapse', null)
|
|
}
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element
|
|
.removeClass('collapse')
|
|
.addClass('collapsing')[dimension](0)
|
|
.attr('aria-expanded', true)
|
|
|
|
this.$trigger
|
|
.removeClass('collapsed')
|
|
.attr('aria-expanded', true)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse in')[dimension]('')
|
|
this.transitioning = 0
|
|
this.$element
|
|
.trigger('shown.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
|
|
|
|
this.$element
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
|
|
}
|
|
|
|
Collapse.prototype.hide = function () {
|
|
if (this.transitioning || !this.$element.hasClass('in')) return
|
|
|
|
var startEvent = $.Event('hide.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
|
|
|
|
this.$element
|
|
.addClass('collapsing')
|
|
.removeClass('collapse in')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.$trigger
|
|
.addClass('collapsed')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.transitioning = 0
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse')
|
|
.trigger('hidden.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
this.$element
|
|
[dimension](0)
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
|
|
}
|
|
|
|
Collapse.prototype.toggle = function () {
|
|
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
|
}
|
|
|
|
Collapse.prototype.getParent = function () {
|
|
return $(this.options.parent)
|
|
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
|
.each($.proxy(function (i, element) {
|
|
var $element = $(element)
|
|
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
|
|
}, this))
|
|
.end()
|
|
}
|
|
|
|
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
|
|
var isOpen = $element.hasClass('in')
|
|
|
|
$element.attr('aria-expanded', isOpen)
|
|
$trigger
|
|
.toggleClass('collapsed', !isOpen)
|
|
.attr('aria-expanded', isOpen)
|
|
}
|
|
|
|
function getTargetFromTrigger($trigger) {
|
|
var href
|
|
var target = $trigger.attr('data-target')
|
|
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
|
|
|
return $(target)
|
|
}
|
|
|
|
|
|
// COLLAPSE PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.collapse')
|
|
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
|
|
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
|
|
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.collapse
|
|
|
|
$.fn.collapse = Plugin
|
|
$.fn.collapse.Constructor = Collapse
|
|
|
|
|
|
// COLLAPSE NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.collapse.noConflict = function () {
|
|
$.fn.collapse = old
|
|
return this
|
|
}
|
|
|
|
|
|
// COLLAPSE DATA-API
|
|
// =================
|
|
|
|
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
|
|
var $this = $(this)
|
|
|
|
if (!$this.attr('data-target')) e.preventDefault()
|
|
|
|
var $target = getTargetFromTrigger($this)
|
|
var data = $target.data('bs.collapse')
|
|
var option = data ? 'toggle' : $this.data()
|
|
|
|
Plugin.call($target, option)
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: dropdown.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#dropdowns
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// DROPDOWN CLASS DEFINITION
|
|
// =========================
|
|
|
|
var backdrop = '.dropdown-backdrop'
|
|
var toggle = '[data-toggle="dropdown"]'
|
|
var Dropdown = function (element) {
|
|
$(element).on('click.bs.dropdown', this.toggle)
|
|
}
|
|
|
|
Dropdown.VERSION = '3.3.5'
|
|
|
|
function getParent($this) {
|
|
var selector = $this.attr('data-target')
|
|
|
|
if (!selector) {
|
|
selector = $this.attr('href')
|
|
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
|
}
|
|
|
|
var $parent = selector && $(selector)
|
|
|
|
return $parent && $parent.length ? $parent : $this.parent()
|
|
}
|
|
|
|
function clearMenus(e) {
|
|
if (e && e.which === 3) return
|
|
$(backdrop).remove()
|
|
$(toggle).each(function () {
|
|
var $this = $(this)
|
|
var $parent = getParent($this)
|
|
var relatedTarget = { relatedTarget: this }
|
|
|
|
if (!$parent.hasClass('open')) return
|
|
|
|
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
|
|
|
|
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$this.attr('aria-expanded', 'false')
|
|
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
|
|
})
|
|
}
|
|
|
|
Dropdown.prototype.toggle = function (e) {
|
|
var $this = $(this)
|
|
|
|
if ($this.is('.disabled, :disabled')) return
|
|
|
|
var $parent = getParent($this)
|
|
var isActive = $parent.hasClass('open')
|
|
|
|
clearMenus()
|
|
|
|
if (!isActive) {
|
|
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
|
|
// if mobile we use a backdrop because click events don't delegate
|
|
$(document.createElement('div'))
|
|
.addClass('dropdown-backdrop')
|
|
.insertAfter($(this))
|
|
.on('click', clearMenus)
|
|
}
|
|
|
|
var relatedTarget = { relatedTarget: this }
|
|
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$this
|
|
.trigger('focus')
|
|
.attr('aria-expanded', 'true')
|
|
|
|
$parent
|
|
.toggleClass('open')
|
|
.trigger('shown.bs.dropdown', relatedTarget)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
Dropdown.prototype.keydown = function (e) {
|
|
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
|
|
|
|
var $this = $(this)
|
|
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
if ($this.is('.disabled, :disabled')) return
|
|
|
|
var $parent = getParent($this)
|
|
var isActive = $parent.hasClass('open')
|
|
|
|
if (!isActive && e.which != 27 || isActive && e.which == 27) {
|
|
if (e.which == 27) $parent.find(toggle).trigger('focus')
|
|
return $this.trigger('click')
|
|
}
|
|
|
|
var desc = ' li:not(.disabled):visible a'
|
|
var $items = $parent.find('.dropdown-menu' + desc)
|
|
|
|
if (!$items.length) return
|
|
|
|
var index = $items.index(e.target)
|
|
|
|
if (e.which == 38 && index > 0) index-- // up
|
|
if (e.which == 40 && index < $items.length - 1) index++ // down
|
|
if (!~index) index = 0
|
|
|
|
$items.eq(index).trigger('focus')
|
|
}
|
|
|
|
|
|
// DROPDOWN PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.dropdown')
|
|
|
|
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
|
|
if (typeof option == 'string') data[option].call($this)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.dropdown
|
|
|
|
$.fn.dropdown = Plugin
|
|
$.fn.dropdown.Constructor = Dropdown
|
|
|
|
|
|
// DROPDOWN NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.dropdown.noConflict = function () {
|
|
$.fn.dropdown = old
|
|
return this
|
|
}
|
|
|
|
|
|
// APPLY TO STANDARD DROPDOWN ELEMENTS
|
|
// ===================================
|
|
|
|
$(document)
|
|
.on('click.bs.dropdown.data-api', clearMenus)
|
|
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
|
|
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
|
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
|
|
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: modal.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#modals
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// MODAL CLASS DEFINITION
|
|
// ======================
|
|
|
|
var Modal = function (element, options) {
|
|
this.options = options
|
|
this.$body = $(document.body)
|
|
this.$element = $(element)
|
|
this.$dialog = this.$element.find('.modal-dialog')
|
|
this.$backdrop = null
|
|
this.isShown = null
|
|
this.originalBodyPad = null
|
|
this.scrollbarWidth = 0
|
|
this.ignoreBackdropClick = false
|
|
|
|
if (this.options.remote) {
|
|
this.$element
|
|
.find('.modal-content')
|
|
.load(this.options.remote, $.proxy(function () {
|
|
this.$element.trigger('loaded.bs.modal')
|
|
}, this))
|
|
}
|
|
}
|
|
|
|
Modal.VERSION = '3.3.5'
|
|
|
|
Modal.TRANSITION_DURATION = 300
|
|
Modal.BACKDROP_TRANSITION_DURATION = 150
|
|
|
|
Modal.DEFAULTS = {
|
|
backdrop: true,
|
|
keyboard: true,
|
|
show: true
|
|
}
|
|
|
|
Modal.prototype.toggle = function (_relatedTarget) {
|
|
return this.isShown ? this.hide() : this.show(_relatedTarget)
|
|
}
|
|
|
|
Modal.prototype.show = function (_relatedTarget) {
|
|
var that = this
|
|
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (this.isShown || e.isDefaultPrevented()) return
|
|
|
|
this.isShown = true
|
|
|
|
this.checkScrollbar()
|
|
this.setScrollbar()
|
|
this.$body.addClass('modal-open')
|
|
|
|
this.escape()
|
|
this.resize()
|
|
|
|
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
|
|
|
|
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
|
|
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
|
|
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
|
|
})
|
|
})
|
|
|
|
this.backdrop(function () {
|
|
var transition = $.support.transition && that.$element.hasClass('fade')
|
|
|
|
if (!that.$element.parent().length) {
|
|
that.$element.appendTo(that.$body) // don't move modals dom position
|
|
}
|
|
|
|
that.$element
|
|
.show()
|
|
.scrollTop(0)
|
|
|
|
that.adjustDialog()
|
|
|
|
if (transition) {
|
|
that.$element[0].offsetWidth // force reflow
|
|
}
|
|
|
|
that.$element.addClass('in')
|
|
|
|
that.enforceFocus()
|
|
|
|
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
|
|
|
|
transition ?
|
|
that.$dialog // wait for modal to slide in
|
|
.one('bsTransitionEnd', function () {
|
|
that.$element.trigger('focus').trigger(e)
|
|
})
|
|
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
|
that.$element.trigger('focus').trigger(e)
|
|
})
|
|
}
|
|
|
|
Modal.prototype.hide = function (e) {
|
|
if (e) e.preventDefault()
|
|
|
|
e = $.Event('hide.bs.modal')
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (!this.isShown || e.isDefaultPrevented()) return
|
|
|
|
this.isShown = false
|
|
|
|
this.escape()
|
|
this.resize()
|
|
|
|
$(document).off('focusin.bs.modal')
|
|
|
|
this.$element
|
|
.removeClass('in')
|
|
.off('click.dismiss.bs.modal')
|
|
.off('mouseup.dismiss.bs.modal')
|
|
|
|
this.$dialog.off('mousedown.dismiss.bs.modal')
|
|
|
|
$.support.transition && this.$element.hasClass('fade') ?
|
|
this.$element
|
|
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
|
|
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
|
this.hideModal()
|
|
}
|
|
|
|
Modal.prototype.enforceFocus = function () {
|
|
$(document)
|
|
.off('focusin.bs.modal') // guard against infinite focus loop
|
|
.on('focusin.bs.modal', $.proxy(function (e) {
|
|
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
|
|
this.$element.trigger('focus')
|
|
}
|
|
}, this))
|
|
}
|
|
|
|
Modal.prototype.escape = function () {
|
|
if (this.isShown && this.options.keyboard) {
|
|
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
|
|
e.which == 27 && this.hide()
|
|
}, this))
|
|
} else if (!this.isShown) {
|
|
this.$element.off('keydown.dismiss.bs.modal')
|
|
}
|
|
}
|
|
|
|
Modal.prototype.resize = function () {
|
|
if (this.isShown) {
|
|
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
|
|
} else {
|
|
$(window).off('resize.bs.modal')
|
|
}
|
|
}
|
|
|
|
Modal.prototype.hideModal = function () {
|
|
var that = this
|
|
this.$element.hide()
|
|
this.backdrop(function () {
|
|
that.$body.removeClass('modal-open')
|
|
that.resetAdjustments()
|
|
that.resetScrollbar()
|
|
that.$element.trigger('hidden.bs.modal')
|
|
})
|
|
}
|
|
|
|
Modal.prototype.removeBackdrop = function () {
|
|
this.$backdrop && this.$backdrop.remove()
|
|
this.$backdrop = null
|
|
}
|
|
|
|
Modal.prototype.backdrop = function (callback) {
|
|
var that = this
|
|
var animate = this.$element.hasClass('fade') ? 'fade' : ''
|
|
|
|
if (this.isShown && this.options.backdrop) {
|
|
var doAnimate = $.support.transition && animate
|
|
|
|
this.$backdrop = $(document.createElement('div'))
|
|
.addClass('modal-backdrop ' + animate)
|
|
.appendTo(this.$body)
|
|
|
|
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
|
|
if (this.ignoreBackdropClick) {
|
|
this.ignoreBackdropClick = false
|
|
return
|
|
}
|
|
if (e.target !== e.currentTarget) return
|
|
this.options.backdrop == 'static'
|
|
? this.$element[0].focus()
|
|
: this.hide()
|
|
}, this))
|
|
|
|
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
|
|
|
this.$backdrop.addClass('in')
|
|
|
|
if (!callback) return
|
|
|
|
doAnimate ?
|
|
this.$backdrop
|
|
.one('bsTransitionEnd', callback)
|
|
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
|
callback()
|
|
|
|
} else if (!this.isShown && this.$backdrop) {
|
|
this.$backdrop.removeClass('in')
|
|
|
|
var callbackRemove = function () {
|
|
that.removeBackdrop()
|
|
callback && callback()
|
|
}
|
|
$.support.transition && this.$element.hasClass('fade') ?
|
|
this.$backdrop
|
|
.one('bsTransitionEnd', callbackRemove)
|
|
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
|
callbackRemove()
|
|
|
|
} else if (callback) {
|
|
callback()
|
|
}
|
|
}
|
|
|
|
// these following methods are used to handle overflowing modals
|
|
|
|
Modal.prototype.handleUpdate = function () {
|
|
this.adjustDialog()
|
|
}
|
|
|
|
Modal.prototype.adjustDialog = function () {
|
|
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
|
|
|
this.$element.css({
|
|
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
|
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
|
})
|
|
}
|
|
|
|
Modal.prototype.resetAdjustments = function () {
|
|
this.$element.css({
|
|
paddingLeft: '',
|
|
paddingRight: ''
|
|
})
|
|
}
|
|
|
|
Modal.prototype.checkScrollbar = function () {
|
|
var fullWindowWidth = window.innerWidth
|
|
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
|
|
var documentElementRect = document.documentElement.getBoundingClientRect()
|
|
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
|
|
}
|
|
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
|
|
this.scrollbarWidth = this.measureScrollbar()
|
|
}
|
|
|
|
Modal.prototype.setScrollbar = function () {
|
|
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
|
this.originalBodyPad = document.body.style.paddingRight || ''
|
|
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
|
|
}
|
|
|
|
Modal.prototype.resetScrollbar = function () {
|
|
this.$body.css('padding-right', this.originalBodyPad)
|
|
}
|
|
|
|
Modal.prototype.measureScrollbar = function () { // thx walsh
|
|
var scrollDiv = document.createElement('div')
|
|
scrollDiv.className = 'modal-scrollbar-measure'
|
|
this.$body.append(scrollDiv)
|
|
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
|
|
this.$body[0].removeChild(scrollDiv)
|
|
return scrollbarWidth
|
|
}
|
|
|
|
|
|
// MODAL PLUGIN DEFINITION
|
|
// =======================
|
|
|
|
function Plugin(option, _relatedTarget) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.modal')
|
|
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
|
|
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
|
if (typeof option == 'string') data[option](_relatedTarget)
|
|
else if (options.show) data.show(_relatedTarget)
|
|
})
|
|
}
|
|
|
|
var old = $.fn.modal
|
|
|
|
$.fn.modal = Plugin
|
|
$.fn.modal.Constructor = Modal
|
|
|
|
|
|
// MODAL NO CONFLICT
|
|
// =================
|
|
|
|
$.fn.modal.noConflict = function () {
|
|
$.fn.modal = old
|
|
return this
|
|
}
|
|
|
|
|
|
// MODAL DATA-API
|
|
// ==============
|
|
|
|
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
|
var $this = $(this)
|
|
var href = $this.attr('href')
|
|
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
|
|
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
|
|
|
if ($this.is('a')) e.preventDefault()
|
|
|
|
$target.one('show.bs.modal', function (showEvent) {
|
|
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
|
|
$target.one('hidden.bs.modal', function () {
|
|
$this.is(':visible') && $this.trigger('focus')
|
|
})
|
|
})
|
|
Plugin.call($target, option, this)
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: tooltip.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#tooltip
|
|
* Inspired by the original jQuery.tipsy by Jason Frame
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// TOOLTIP PUBLIC CLASS DEFINITION
|
|
// ===============================
|
|
|
|
var Tooltip = function (element, options) {
|
|
this.type = null
|
|
this.options = null
|
|
this.enabled = null
|
|
this.timeout = null
|
|
this.hoverState = null
|
|
this.$element = null
|
|
this.inState = null
|
|
|
|
this.init('tooltip', element, options)
|
|
}
|
|
|
|
Tooltip.VERSION = '3.3.5'
|
|
|
|
Tooltip.TRANSITION_DURATION = 150
|
|
|
|
Tooltip.DEFAULTS = {
|
|
animation: true,
|
|
placement: 'top',
|
|
selector: false,
|
|
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
|
|
trigger: 'hover focus',
|
|
title: '',
|
|
delay: 0,
|
|
html: false,
|
|
container: false,
|
|
viewport: {
|
|
selector: 'body',
|
|
padding: 0
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.init = function (type, element, options) {
|
|
this.enabled = true
|
|
this.type = type
|
|
this.$element = $(element)
|
|
this.options = this.getOptions(options)
|
|
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
|
this.inState = { click: false, hover: false, focus: false }
|
|
|
|
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
|
|
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
|
|
}
|
|
|
|
var triggers = this.options.trigger.split(' ')
|
|
|
|
for (var i = triggers.length; i--;) {
|
|
var trigger = triggers[i]
|
|
|
|
if (trigger == 'click') {
|
|
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
|
|
} else if (trigger != 'manual') {
|
|
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
|
|
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
|
|
|
|
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
|
|
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
|
|
}
|
|
}
|
|
|
|
this.options.selector ?
|
|
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
|
|
this.fixTitle()
|
|
}
|
|
|
|
Tooltip.prototype.getDefaults = function () {
|
|
return Tooltip.DEFAULTS
|
|
}
|
|
|
|
Tooltip.prototype.getOptions = function (options) {
|
|
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
|
|
|
|
if (options.delay && typeof options.delay == 'number') {
|
|
options.delay = {
|
|
show: options.delay,
|
|
hide: options.delay
|
|
}
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
Tooltip.prototype.getDelegateOptions = function () {
|
|
var options = {}
|
|
var defaults = this.getDefaults()
|
|
|
|
this._options && $.each(this._options, function (key, value) {
|
|
if (defaults[key] != value) options[key] = value
|
|
})
|
|
|
|
return options
|
|
}
|
|
|
|
Tooltip.prototype.enter = function (obj) {
|
|
var self = obj instanceof this.constructor ?
|
|
obj : $(obj.currentTarget).data('bs.' + this.type)
|
|
|
|
if (!self) {
|
|
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
|
|
$(obj.currentTarget).data('bs.' + this.type, self)
|
|
}
|
|
|
|
if (obj instanceof $.Event) {
|
|
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
|
|
}
|
|
|
|
if (self.tip().hasClass('in') || self.hoverState == 'in') {
|
|
self.hoverState = 'in'
|
|
return
|
|
}
|
|
|
|
clearTimeout(self.timeout)
|
|
|
|
self.hoverState = 'in'
|
|
|
|
if (!self.options.delay || !self.options.delay.show) return self.show()
|
|
|
|
self.timeout = setTimeout(function () {
|
|
if (self.hoverState == 'in') self.show()
|
|
}, self.options.delay.show)
|
|
}
|
|
|
|
Tooltip.prototype.isInStateTrue = function () {
|
|
for (var key in this.inState) {
|
|
if (this.inState[key]) return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
Tooltip.prototype.leave = function (obj) {
|
|
var self = obj instanceof this.constructor ?
|
|
obj : $(obj.currentTarget).data('bs.' + this.type)
|
|
|
|
if (!self) {
|
|
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
|
|
$(obj.currentTarget).data('bs.' + this.type, self)
|
|
}
|
|
|
|
if (obj instanceof $.Event) {
|
|
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
|
|
}
|
|
|
|
if (self.isInStateTrue()) return
|
|
|
|
clearTimeout(self.timeout)
|
|
|
|
self.hoverState = 'out'
|
|
|
|
if (!self.options.delay || !self.options.delay.hide) return self.hide()
|
|
|
|
self.timeout = setTimeout(function () {
|
|
if (self.hoverState == 'out') self.hide()
|
|
}, self.options.delay.hide)
|
|
}
|
|
|
|
Tooltip.prototype.show = function () {
|
|
var e = $.Event('show.bs.' + this.type)
|
|
|
|
if (this.hasContent() && this.enabled) {
|
|
this.$element.trigger(e)
|
|
|
|
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
|
|
if (e.isDefaultPrevented() || !inDom) return
|
|
var that = this
|
|
|
|
var $tip = this.tip()
|
|
|
|
var tipId = this.getUID(this.type)
|
|
|
|
this.setContent()
|
|
$tip.attr('id', tipId)
|
|
this.$element.attr('aria-describedby', tipId)
|
|
|
|
if (this.options.animation) $tip.addClass('fade')
|
|
|
|
var placement = typeof this.options.placement == 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement
|
|
|
|
var autoToken = /\s?auto?\s?/i
|
|
var autoPlace = autoToken.test(placement)
|
|
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
|
|
|
|
$tip
|
|
.detach()
|
|
.css({ top: 0, left: 0, display: 'block' })
|
|
.addClass(placement)
|
|
.data('bs.' + this.type, this)
|
|
|
|
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
|
|
this.$element.trigger('inserted.bs.' + this.type)
|
|
|
|
var pos = this.getPosition()
|
|
var actualWidth = $tip[0].offsetWidth
|
|
var actualHeight = $tip[0].offsetHeight
|
|
|
|
if (autoPlace) {
|
|
var orgPlacement = placement
|
|
var viewportDim = this.getPosition(this.$viewport)
|
|
|
|
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
|
|
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
|
|
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
|
|
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
|
|
placement
|
|
|
|
$tip
|
|
.removeClass(orgPlacement)
|
|
.addClass(placement)
|
|
}
|
|
|
|
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
|
|
|
|
this.applyPlacement(calculatedOffset, placement)
|
|
|
|
var complete = function () {
|
|
var prevHoverState = that.hoverState
|
|
that.$element.trigger('shown.bs.' + that.type)
|
|
that.hoverState = null
|
|
|
|
if (prevHoverState == 'out') that.leave(that)
|
|
}
|
|
|
|
$.support.transition && this.$tip.hasClass('fade') ?
|
|
$tip
|
|
.one('bsTransitionEnd', complete)
|
|
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
|
|
complete()
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.applyPlacement = function (offset, placement) {
|
|
var $tip = this.tip()
|
|
var width = $tip[0].offsetWidth
|
|
var height = $tip[0].offsetHeight
|
|
|
|
// manually read margins because getBoundingClientRect includes difference
|
|
var marginTop = parseInt($tip.css('margin-top'), 10)
|
|
var marginLeft = parseInt($tip.css('margin-left'), 10)
|
|
|
|
// we must check for NaN for ie 8/9
|
|
if (isNaN(marginTop)) marginTop = 0
|
|
if (isNaN(marginLeft)) marginLeft = 0
|
|
|
|
offset.top += marginTop
|
|
offset.left += marginLeft
|
|
|
|
// $.fn.offset doesn't round pixel values
|
|
// so we use setOffset directly with our own function B-0
|
|
$.offset.setOffset($tip[0], $.extend({
|
|
using: function (props) {
|
|
$tip.css({
|
|
top: Math.round(props.top),
|
|
left: Math.round(props.left)
|
|
})
|
|
}
|
|
}, offset), 0)
|
|
|
|
$tip.addClass('in')
|
|
|
|
// check to see if placing tip in new offset caused the tip to resize itself
|
|
var actualWidth = $tip[0].offsetWidth
|
|
var actualHeight = $tip[0].offsetHeight
|
|
|
|
if (placement == 'top' && actualHeight != height) {
|
|
offset.top = offset.top + height - actualHeight
|
|
}
|
|
|
|
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
|
|
|
|
if (delta.left) offset.left += delta.left
|
|
else offset.top += delta.top
|
|
|
|
var isVertical = /top|bottom/.test(placement)
|
|
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
|
|
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
|
|
|
|
$tip.offset(offset)
|
|
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
|
|
}
|
|
|
|
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
|
|
this.arrow()
|
|
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
|
|
.css(isVertical ? 'top' : 'left', '')
|
|
}
|
|
|
|
Tooltip.prototype.setContent = function () {
|
|
var $tip = this.tip()
|
|
var title = this.getTitle()
|
|
|
|
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
|
|
$tip.removeClass('fade in top bottom left right')
|
|
}
|
|
|
|
Tooltip.prototype.hide = function (callback) {
|
|
var that = this
|
|
var $tip = $(this.$tip)
|
|
var e = $.Event('hide.bs.' + this.type)
|
|
|
|
function complete() {
|
|
if (that.hoverState != 'in') $tip.detach()
|
|
that.$element
|
|
.removeAttr('aria-describedby')
|
|
.trigger('hidden.bs.' + that.type)
|
|
callback && callback()
|
|
}
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
$tip.removeClass('in')
|
|
|
|
$.support.transition && $tip.hasClass('fade') ?
|
|
$tip
|
|
.one('bsTransitionEnd', complete)
|
|
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
|
|
complete()
|
|
|
|
this.hoverState = null
|
|
|
|
return this
|
|
}
|
|
|
|
Tooltip.prototype.fixTitle = function () {
|
|
var $e = this.$element
|
|
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
|
|
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.hasContent = function () {
|
|
return this.getTitle()
|
|
}
|
|
|
|
Tooltip.prototype.getPosition = function ($element) {
|
|
$element = $element || this.$element
|
|
|
|
var el = $element[0]
|
|
var isBody = el.tagName == 'BODY'
|
|
|
|
var elRect = el.getBoundingClientRect()
|
|
if (elRect.width == null) {
|
|
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
|
|
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
|
|
}
|
|
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
|
|
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
|
|
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
|
|
|
|
return $.extend({}, elRect, scroll, outerDims, elOffset)
|
|
}
|
|
|
|
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
|
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
|
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
|
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
|
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
|
|
|
}
|
|
|
|
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
|
|
var delta = { top: 0, left: 0 }
|
|
if (!this.$viewport) return delta
|
|
|
|
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
|
|
var viewportDimensions = this.getPosition(this.$viewport)
|
|
|
|
if (/right|left/.test(placement)) {
|
|
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
|
|
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
|
|
if (topEdgeOffset < viewportDimensions.top) { // top overflow
|
|
delta.top = viewportDimensions.top - topEdgeOffset
|
|
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
|
|
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
|
|
}
|
|
} else {
|
|
var leftEdgeOffset = pos.left - viewportPadding
|
|
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
|
|
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
|
|
delta.left = viewportDimensions.left - leftEdgeOffset
|
|
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
|
|
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
|
|
}
|
|
}
|
|
|
|
return delta
|
|
}
|
|
|
|
Tooltip.prototype.getTitle = function () {
|
|
var title
|
|
var $e = this.$element
|
|
var o = this.options
|
|
|
|
title = $e.attr('data-original-title')
|
|
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
|
|
|
|
return title
|
|
}
|
|
|
|
Tooltip.prototype.getUID = function (prefix) {
|
|
do prefix += ~~(Math.random() * 1000000)
|
|
while (document.getElementById(prefix))
|
|
return prefix
|
|
}
|
|
|
|
Tooltip.prototype.tip = function () {
|
|
if (!this.$tip) {
|
|
this.$tip = $(this.options.template)
|
|
if (this.$tip.length != 1) {
|
|
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
|
|
}
|
|
}
|
|
return this.$tip
|
|
}
|
|
|
|
Tooltip.prototype.arrow = function () {
|
|
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
|
|
}
|
|
|
|
Tooltip.prototype.enable = function () {
|
|
this.enabled = true
|
|
}
|
|
|
|
Tooltip.prototype.disable = function () {
|
|
this.enabled = false
|
|
}
|
|
|
|
Tooltip.prototype.toggleEnabled = function () {
|
|
this.enabled = !this.enabled
|
|
}
|
|
|
|
Tooltip.prototype.toggle = function (e) {
|
|
var self = this
|
|
if (e) {
|
|
self = $(e.currentTarget).data('bs.' + this.type)
|
|
if (!self) {
|
|
self = new this.constructor(e.currentTarget, this.getDelegateOptions())
|
|
$(e.currentTarget).data('bs.' + this.type, self)
|
|
}
|
|
}
|
|
|
|
if (e) {
|
|
self.inState.click = !self.inState.click
|
|
if (self.isInStateTrue()) self.enter(self)
|
|
else self.leave(self)
|
|
} else {
|
|
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
|
|
}
|
|
}
|
|
|
|
Tooltip.prototype.destroy = function () {
|
|
var that = this
|
|
clearTimeout(this.timeout)
|
|
this.hide(function () {
|
|
that.$element.off('.' + that.type).removeData('bs.' + that.type)
|
|
if (that.$tip) {
|
|
that.$tip.detach()
|
|
}
|
|
that.$tip = null
|
|
that.$arrow = null
|
|
that.$viewport = null
|
|
})
|
|
}
|
|
|
|
|
|
// TOOLTIP PLUGIN DEFINITION
|
|
// =========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.tooltip')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data && /destroy|hide/.test(option)) return
|
|
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.tooltip
|
|
|
|
$.fn.tooltip = Plugin
|
|
$.fn.tooltip.Constructor = Tooltip
|
|
|
|
|
|
// TOOLTIP NO CONFLICT
|
|
// ===================
|
|
|
|
$.fn.tooltip.noConflict = function () {
|
|
$.fn.tooltip = old
|
|
return this
|
|
}
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: popover.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#popovers
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// POPOVER PUBLIC CLASS DEFINITION
|
|
// ===============================
|
|
|
|
var Popover = function (element, options) {
|
|
this.init('popover', element, options)
|
|
}
|
|
|
|
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
|
|
|
Popover.VERSION = '3.3.5'
|
|
|
|
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
|
placement: 'right',
|
|
trigger: 'click',
|
|
content: '',
|
|
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
|
})
|
|
|
|
|
|
// NOTE: POPOVER EXTENDS tooltip.js
|
|
// ================================
|
|
|
|
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
|
|
|
|
Popover.prototype.constructor = Popover
|
|
|
|
Popover.prototype.getDefaults = function () {
|
|
return Popover.DEFAULTS
|
|
}
|
|
|
|
Popover.prototype.setContent = function () {
|
|
var $tip = this.tip()
|
|
var title = this.getTitle()
|
|
var content = this.getContent()
|
|
|
|
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
|
|
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
|
|
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
|
|
](content)
|
|
|
|
$tip.removeClass('fade top bottom left right in')
|
|
|
|
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
|
|
// this manually by checking the contents.
|
|
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
|
|
}
|
|
|
|
Popover.prototype.hasContent = function () {
|
|
return this.getTitle() || this.getContent()
|
|
}
|
|
|
|
Popover.prototype.getContent = function () {
|
|
var $e = this.$element
|
|
var o = this.options
|
|
|
|
return $e.attr('data-content')
|
|
|| (typeof o.content == 'function' ?
|
|
o.content.call($e[0]) :
|
|
o.content)
|
|
}
|
|
|
|
Popover.prototype.arrow = function () {
|
|
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
|
|
}
|
|
|
|
|
|
// POPOVER PLUGIN DEFINITION
|
|
// =========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.popover')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data && /destroy|hide/.test(option)) return
|
|
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.popover
|
|
|
|
$.fn.popover = Plugin
|
|
$.fn.popover.Constructor = Popover
|
|
|
|
|
|
// POPOVER NO CONFLICT
|
|
// ===================
|
|
|
|
$.fn.popover.noConflict = function () {
|
|
$.fn.popover = old
|
|
return this
|
|
}
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: scrollspy.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#scrollspy
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// SCROLLSPY CLASS DEFINITION
|
|
// ==========================
|
|
|
|
function ScrollSpy(element, options) {
|
|
this.$body = $(document.body)
|
|
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
|
|
this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
|
|
this.selector = (this.options.target || '') + ' .nav li > a'
|
|
this.offsets = []
|
|
this.targets = []
|
|
this.activeTarget = null
|
|
this.scrollHeight = 0
|
|
|
|
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
|
|
this.refresh()
|
|
this.process()
|
|
}
|
|
|
|
ScrollSpy.VERSION = '3.3.5'
|
|
|
|
ScrollSpy.DEFAULTS = {
|
|
offset: 10
|
|
}
|
|
|
|
ScrollSpy.prototype.getScrollHeight = function () {
|
|
return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
|
|
}
|
|
|
|
ScrollSpy.prototype.refresh = function () {
|
|
var that = this
|
|
var offsetMethod = 'offset'
|
|
var offsetBase = 0
|
|
|
|
this.offsets = []
|
|
this.targets = []
|
|
this.scrollHeight = this.getScrollHeight()
|
|
|
|
if (!$.isWindow(this.$scrollElement[0])) {
|
|
offsetMethod = 'position'
|
|
offsetBase = this.$scrollElement.scrollTop()
|
|
}
|
|
|
|
this.$body
|
|
.find(this.selector)
|
|
.map(function () {
|
|
var $el = $(this)
|
|
var href = $el.data('target') || $el.attr('href')
|
|
var $href = /^#./.test(href) && $(href)
|
|
|
|
return ($href
|
|
&& $href.length
|
|
&& $href.is(':visible')
|
|
&& [[$href[offsetMethod]().top + offsetBase, href]]) || null
|
|
})
|
|
.sort(function (a, b) { return a[0] - b[0] })
|
|
.each(function () {
|
|
that.offsets.push(this[0])
|
|
that.targets.push(this[1])
|
|
})
|
|
}
|
|
|
|
ScrollSpy.prototype.process = function () {
|
|
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
|
|
var scrollHeight = this.getScrollHeight()
|
|
var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
|
|
var offsets = this.offsets
|
|
var targets = this.targets
|
|
var activeTarget = this.activeTarget
|
|
var i
|
|
|
|
if (this.scrollHeight != scrollHeight) {
|
|
this.refresh()
|
|
}
|
|
|
|
if (scrollTop >= maxScroll) {
|
|
return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
|
|
}
|
|
|
|
if (activeTarget && scrollTop < offsets[0]) {
|
|
this.activeTarget = null
|
|
return this.clear()
|
|
}
|
|
|
|
for (i = offsets.length; i--;) {
|
|
activeTarget != targets[i]
|
|
&& scrollTop >= offsets[i]
|
|
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
|
|
&& this.activate(targets[i])
|
|
}
|
|
}
|
|
|
|
ScrollSpy.prototype.activate = function (target) {
|
|
this.activeTarget = target
|
|
|
|
this.clear()
|
|
|
|
var selector = this.selector +
|
|
'[data-target="' + target + '"],' +
|
|
this.selector + '[href="' + target + '"]'
|
|
|
|
var active = $(selector)
|
|
.parents('li')
|
|
.addClass('active')
|
|
|
|
if (active.parent('.dropdown-menu').length) {
|
|
active = active
|
|
.closest('li.dropdown')
|
|
.addClass('active')
|
|
}
|
|
|
|
active.trigger('activate.bs.scrollspy')
|
|
}
|
|
|
|
ScrollSpy.prototype.clear = function () {
|
|
$(this.selector)
|
|
.parentsUntil(this.options.target, '.active')
|
|
.removeClass('active')
|
|
}
|
|
|
|
|
|
// SCROLLSPY PLUGIN DEFINITION
|
|
// ===========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.scrollspy')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.scrollspy
|
|
|
|
$.fn.scrollspy = Plugin
|
|
$.fn.scrollspy.Constructor = ScrollSpy
|
|
|
|
|
|
// SCROLLSPY NO CONFLICT
|
|
// =====================
|
|
|
|
$.fn.scrollspy.noConflict = function () {
|
|
$.fn.scrollspy = old
|
|
return this
|
|
}
|
|
|
|
|
|
// SCROLLSPY DATA-API
|
|
// ==================
|
|
|
|
$(window).on('load.bs.scrollspy.data-api', function () {
|
|
$('[data-spy="scroll"]').each(function () {
|
|
var $spy = $(this)
|
|
Plugin.call($spy, $spy.data())
|
|
})
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: tab.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#tabs
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// TAB CLASS DEFINITION
|
|
// ====================
|
|
|
|
var Tab = function (element) {
|
|
// jscs:disable requireDollarBeforejQueryAssignment
|
|
this.element = $(element)
|
|
// jscs:enable requireDollarBeforejQueryAssignment
|
|
}
|
|
|
|
Tab.VERSION = '3.3.5'
|
|
|
|
Tab.TRANSITION_DURATION = 150
|
|
|
|
Tab.prototype.show = function () {
|
|
var $this = this.element
|
|
var $ul = $this.closest('ul:not(.dropdown-menu)')
|
|
var selector = $this.data('target')
|
|
|
|
if (!selector) {
|
|
selector = $this.attr('href')
|
|
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
|
}
|
|
|
|
if ($this.parent('li').hasClass('active')) return
|
|
|
|
var $previous = $ul.find('.active:last a')
|
|
var hideEvent = $.Event('hide.bs.tab', {
|
|
relatedTarget: $this[0]
|
|
})
|
|
var showEvent = $.Event('show.bs.tab', {
|
|
relatedTarget: $previous[0]
|
|
})
|
|
|
|
$previous.trigger(hideEvent)
|
|
$this.trigger(showEvent)
|
|
|
|
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
|
|
|
|
var $target = $(selector)
|
|
|
|
this.activate($this.closest('li'), $ul)
|
|
this.activate($target, $target.parent(), function () {
|
|
$previous.trigger({
|
|
type: 'hidden.bs.tab',
|
|
relatedTarget: $this[0]
|
|
})
|
|
$this.trigger({
|
|
type: 'shown.bs.tab',
|
|
relatedTarget: $previous[0]
|
|
})
|
|
})
|
|
}
|
|
|
|
Tab.prototype.activate = function (element, container, callback) {
|
|
var $active = container.find('> .active')
|
|
var transition = callback
|
|
&& $.support.transition
|
|
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
|
|
|
|
function next() {
|
|
$active
|
|
.removeClass('active')
|
|
.find('> .dropdown-menu > .active')
|
|
.removeClass('active')
|
|
.end()
|
|
.find('[data-toggle="tab"]')
|
|
.attr('aria-expanded', false)
|
|
|
|
element
|
|
.addClass('active')
|
|
.find('[data-toggle="tab"]')
|
|
.attr('aria-expanded', true)
|
|
|
|
if (transition) {
|
|
element[0].offsetWidth // reflow for transition
|
|
element.addClass('in')
|
|
} else {
|
|
element.removeClass('fade')
|
|
}
|
|
|
|
if (element.parent('.dropdown-menu').length) {
|
|
element
|
|
.closest('li.dropdown')
|
|
.addClass('active')
|
|
.end()
|
|
.find('[data-toggle="tab"]')
|
|
.attr('aria-expanded', true)
|
|
}
|
|
|
|
callback && callback()
|
|
}
|
|
|
|
$active.length && transition ?
|
|
$active
|
|
.one('bsTransitionEnd', next)
|
|
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
|
|
next()
|
|
|
|
$active.removeClass('in')
|
|
}
|
|
|
|
|
|
// TAB PLUGIN DEFINITION
|
|
// =====================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.tab')
|
|
|
|
if (!data) $this.data('bs.tab', (data = new Tab(this)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.tab
|
|
|
|
$.fn.tab = Plugin
|
|
$.fn.tab.Constructor = Tab
|
|
|
|
|
|
// TAB NO CONFLICT
|
|
// ===============
|
|
|
|
$.fn.tab.noConflict = function () {
|
|
$.fn.tab = old
|
|
return this
|
|
}
|
|
|
|
|
|
// TAB DATA-API
|
|
// ============
|
|
|
|
var clickHandler = function (e) {
|
|
e.preventDefault()
|
|
Plugin.call($(this), 'show')
|
|
}
|
|
|
|
$(document)
|
|
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
|
|
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: affix.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#affix
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// AFFIX CLASS DEFINITION
|
|
// ======================
|
|
|
|
var Affix = function (element, options) {
|
|
this.options = $.extend({}, Affix.DEFAULTS, options)
|
|
|
|
this.$target = $(this.options.target)
|
|
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
|
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
|
|
|
this.$element = $(element)
|
|
this.affixed = null
|
|
this.unpin = null
|
|
this.pinnedOffset = null
|
|
|
|
this.checkPosition()
|
|
}
|
|
|
|
Affix.VERSION = '3.3.5'
|
|
|
|
Affix.RESET = 'affix affix-top affix-bottom'
|
|
|
|
Affix.DEFAULTS = {
|
|
offset: 0,
|
|
target: window
|
|
}
|
|
|
|
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
|
|
var scrollTop = this.$target.scrollTop()
|
|
var position = this.$element.offset()
|
|
var targetHeight = this.$target.height()
|
|
|
|
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
|
|
|
|
if (this.affixed == 'bottom') {
|
|
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
|
|
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
|
|
}
|
|
|
|
var initializing = this.affixed == null
|
|
var colliderTop = initializing ? scrollTop : position.top
|
|
var colliderHeight = initializing ? targetHeight : height
|
|
|
|
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
|
|
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
|
|
|
|
return false
|
|
}
|
|
|
|
Affix.prototype.getPinnedOffset = function () {
|
|
if (this.pinnedOffset) return this.pinnedOffset
|
|
this.$element.removeClass(Affix.RESET).addClass('affix')
|
|
var scrollTop = this.$target.scrollTop()
|
|
var position = this.$element.offset()
|
|
return (this.pinnedOffset = position.top - scrollTop)
|
|
}
|
|
|
|
Affix.prototype.checkPositionWithEventLoop = function () {
|
|
setTimeout($.proxy(this.checkPosition, this), 1)
|
|
}
|
|
|
|
Affix.prototype.checkPosition = function () {
|
|
if (!this.$element.is(':visible')) return
|
|
|
|
var height = this.$element.height()
|
|
var offset = this.options.offset
|
|
var offsetTop = offset.top
|
|
var offsetBottom = offset.bottom
|
|
var scrollHeight = Math.max($(document).height(), $(document.body).height())
|
|
|
|
if (typeof offset != 'object') offsetBottom = offsetTop = offset
|
|
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
|
|
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
|
|
|
|
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
|
|
|
|
if (this.affixed != affix) {
|
|
if (this.unpin != null) this.$element.css('top', '')
|
|
|
|
var affixType = 'affix' + (affix ? '-' + affix : '')
|
|
var e = $.Event(affixType + '.bs.affix')
|
|
|
|
this.$element.trigger(e)
|
|
|
|
if (e.isDefaultPrevented()) return
|
|
|
|
this.affixed = affix
|
|
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
|
|
|
|
this.$element
|
|
.removeClass(Affix.RESET)
|
|
.addClass(affixType)
|
|
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
|
|
}
|
|
|
|
if (affix == 'bottom') {
|
|
this.$element.offset({
|
|
top: scrollHeight - height - offsetBottom
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
// AFFIX PLUGIN DEFINITION
|
|
// =======================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.affix')
|
|
var options = typeof option == 'object' && option
|
|
|
|
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.affix
|
|
|
|
$.fn.affix = Plugin
|
|
$.fn.affix.Constructor = Affix
|
|
|
|
|
|
// AFFIX NO CONFLICT
|
|
// =================
|
|
|
|
$.fn.affix.noConflict = function () {
|
|
$.fn.affix = old
|
|
return this
|
|
}
|
|
|
|
|
|
// AFFIX DATA-API
|
|
// ==============
|
|
|
|
$(window).on('load', function () {
|
|
$('[data-spy="affix"]').each(function () {
|
|
var $spy = $(this)
|
|
var data = $spy.data()
|
|
|
|
data.offset = data.offset || {}
|
|
|
|
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
|
|
if (data.offsetTop != null) data.offset.top = data.offsetTop
|
|
|
|
Plugin.call($spy, data)
|
|
})
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: collapse.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#collapse
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// COLLAPSE PUBLIC CLASS DEFINITION
|
|
// ================================
|
|
|
|
var Collapse = function (element, options) {
|
|
this.$element = $(element)
|
|
this.options = $.extend({}, Collapse.DEFAULTS, options)
|
|
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
|
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
|
|
this.transitioning = null
|
|
|
|
if (this.options.parent) {
|
|
this.$parent = this.getParent()
|
|
} else {
|
|
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
|
|
}
|
|
|
|
if (this.options.toggle) this.toggle()
|
|
}
|
|
|
|
Collapse.VERSION = '3.3.5'
|
|
|
|
Collapse.TRANSITION_DURATION = 350
|
|
|
|
Collapse.DEFAULTS = {
|
|
toggle: true
|
|
}
|
|
|
|
Collapse.prototype.dimension = function () {
|
|
var hasWidth = this.$element.hasClass('width')
|
|
return hasWidth ? 'width' : 'height'
|
|
}
|
|
|
|
Collapse.prototype.show = function () {
|
|
if (this.transitioning || this.$element.hasClass('in')) return
|
|
|
|
var activesData
|
|
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
|
|
|
|
if (actives && actives.length) {
|
|
activesData = actives.data('bs.collapse')
|
|
if (activesData && activesData.transitioning) return
|
|
}
|
|
|
|
var startEvent = $.Event('show.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
if (actives && actives.length) {
|
|
Plugin.call(actives, 'hide')
|
|
activesData || actives.data('bs.collapse', null)
|
|
}
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element
|
|
.removeClass('collapse')
|
|
.addClass('collapsing')[dimension](0)
|
|
.attr('aria-expanded', true)
|
|
|
|
this.$trigger
|
|
.removeClass('collapsed')
|
|
.attr('aria-expanded', true)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse in')[dimension]('')
|
|
this.transitioning = 0
|
|
this.$element
|
|
.trigger('shown.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
|
|
|
|
this.$element
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
|
|
}
|
|
|
|
Collapse.prototype.hide = function () {
|
|
if (this.transitioning || !this.$element.hasClass('in')) return
|
|
|
|
var startEvent = $.Event('hide.bs.collapse')
|
|
this.$element.trigger(startEvent)
|
|
if (startEvent.isDefaultPrevented()) return
|
|
|
|
var dimension = this.dimension()
|
|
|
|
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
|
|
|
|
this.$element
|
|
.addClass('collapsing')
|
|
.removeClass('collapse in')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.$trigger
|
|
.addClass('collapsed')
|
|
.attr('aria-expanded', false)
|
|
|
|
this.transitioning = 1
|
|
|
|
var complete = function () {
|
|
this.transitioning = 0
|
|
this.$element
|
|
.removeClass('collapsing')
|
|
.addClass('collapse')
|
|
.trigger('hidden.bs.collapse')
|
|
}
|
|
|
|
if (!$.support.transition) return complete.call(this)
|
|
|
|
this.$element
|
|
[dimension](0)
|
|
.one('bsTransitionEnd', $.proxy(complete, this))
|
|
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
|
|
}
|
|
|
|
Collapse.prototype.toggle = function () {
|
|
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
|
}
|
|
|
|
Collapse.prototype.getParent = function () {
|
|
return $(this.options.parent)
|
|
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
|
.each($.proxy(function (i, element) {
|
|
var $element = $(element)
|
|
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
|
|
}, this))
|
|
.end()
|
|
}
|
|
|
|
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
|
|
var isOpen = $element.hasClass('in')
|
|
|
|
$element.attr('aria-expanded', isOpen)
|
|
$trigger
|
|
.toggleClass('collapsed', !isOpen)
|
|
.attr('aria-expanded', isOpen)
|
|
}
|
|
|
|
function getTargetFromTrigger($trigger) {
|
|
var href
|
|
var target = $trigger.attr('data-target')
|
|
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
|
|
|
return $(target)
|
|
}
|
|
|
|
|
|
// COLLAPSE PLUGIN DEFINITION
|
|
// ==========================
|
|
|
|
function Plugin(option) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
var data = $this.data('bs.collapse')
|
|
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
|
|
|
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
|
|
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
var old = $.fn.collapse
|
|
|
|
$.fn.collapse = Plugin
|
|
$.fn.collapse.Constructor = Collapse
|
|
|
|
|
|
// COLLAPSE NO CONFLICT
|
|
// ====================
|
|
|
|
$.fn.collapse.noConflict = function () {
|
|
$.fn.collapse = old
|
|
return this
|
|
}
|
|
|
|
|
|
// COLLAPSE DATA-API
|
|
// =================
|
|
|
|
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
|
|
var $this = $(this)
|
|
|
|
if (!$this.attr('data-target')) e.preventDefault()
|
|
|
|
var $target = getTargetFromTrigger($this)
|
|
var data = $target.data('bs.collapse')
|
|
var option = data ? 'toggle' : $this.data()
|
|
|
|
Plugin.call($target, option)
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/* ========================================================================
|
|
* Bootstrap: transition.js v3.3.5
|
|
* http://getbootstrap.com/javascript/#transitions
|
|
* ========================================================================
|
|
* Copyright 2011-2015 Twitter, Inc.
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* ======================================================================== */
|
|
|
|
|
|
+function ($) {
|
|
'use strict';
|
|
|
|
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
|
// ============================================================
|
|
|
|
function transitionEnd() {
|
|
var el = document.createElement('bootstrap')
|
|
|
|
var transEndEventNames = {
|
|
WebkitTransition : 'webkitTransitionEnd',
|
|
MozTransition : 'transitionend',
|
|
OTransition : 'oTransitionEnd otransitionend',
|
|
transition : 'transitionend'
|
|
}
|
|
|
|
for (var name in transEndEventNames) {
|
|
if (el.style[name] !== undefined) {
|
|
return { end: transEndEventNames[name] }
|
|
}
|
|
}
|
|
|
|
return false // explicit for ie8 ( ._.)
|
|
}
|
|
|
|
// http://blog.alexmaccaw.com/css-transitions
|
|
$.fn.emulateTransitionEnd = function (duration) {
|
|
var called = false
|
|
var $el = this
|
|
$(this).one('bsTransitionEnd', function () { called = true })
|
|
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
|
|
setTimeout(callback, duration)
|
|
return this
|
|
}
|
|
|
|
$(function () {
|
|
$.support.transition = transitionEnd()
|
|
|
|
if (!$.support.transition) return
|
|
|
|
$.event.special.bsTransitionEnd = {
|
|
bindType: $.support.transition.end,
|
|
delegateType: $.support.transition.end,
|
|
handle: function (e) {
|
|
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
|
|
}
|
|
}
|
|
})
|
|
|
|
}(jQuery);
|
|
|
|
/**!
|
|
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
|
* @version 1.14.1
|
|
* @license
|
|
* Copyright (c) 2016 Federico Zivolo and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global.Popper = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
|
|
var timeoutDuration = 0;
|
|
for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
|
|
if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
|
|
timeoutDuration = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
function microtaskDebounce(fn) {
|
|
var called = false;
|
|
return function () {
|
|
if (called) {
|
|
return;
|
|
}
|
|
called = true;
|
|
window.Promise.resolve().then(function () {
|
|
called = false;
|
|
fn();
|
|
});
|
|
};
|
|
}
|
|
|
|
function taskDebounce(fn) {
|
|
var scheduled = false;
|
|
return function () {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
setTimeout(function () {
|
|
scheduled = false;
|
|
fn();
|
|
}, timeoutDuration);
|
|
}
|
|
};
|
|
}
|
|
|
|
var supportsMicroTasks = isBrowser && window.Promise;
|
|
|
|
/**
|
|
* Create a debounced version of a method, that's asynchronously deferred
|
|
* but called in the minimum time possible.
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Function} fn
|
|
* @returns {Function}
|
|
*/
|
|
var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
|
|
|
|
/**
|
|
* Check if the given variable is a function
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Any} functionToCheck - variable to check
|
|
* @returns {Boolean} answer to: is a function?
|
|
*/
|
|
function isFunction(functionToCheck) {
|
|
var getType = {};
|
|
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
|
}
|
|
|
|
/**
|
|
* Get CSS computed property of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Eement} element
|
|
* @argument {String} property
|
|
*/
|
|
function getStyleComputedProperty(element, property) {
|
|
if (element.nodeType !== 1) {
|
|
return [];
|
|
}
|
|
// NOTE: 1 DOM access here
|
|
var css = getComputedStyle(element, null);
|
|
return property ? css[property] : css;
|
|
}
|
|
|
|
/**
|
|
* Returns the parentNode or the host of the element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} parent
|
|
*/
|
|
function getParentNode(element) {
|
|
if (element.nodeName === 'HTML') {
|
|
return element;
|
|
}
|
|
return element.parentNode || element.host;
|
|
}
|
|
|
|
/**
|
|
* Returns the scrolling parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} scroll parent
|
|
*/
|
|
function getScrollParent(element) {
|
|
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
|
|
if (!element) {
|
|
return document.body;
|
|
}
|
|
|
|
switch (element.nodeName) {
|
|
case 'HTML':
|
|
case 'BODY':
|
|
return element.ownerDocument.body;
|
|
case '#document':
|
|
return element.body;
|
|
}
|
|
|
|
// Firefox want us to check `-x` and `-y` variations as well
|
|
|
|
var _getStyleComputedProp = getStyleComputedProperty(element),
|
|
overflow = _getStyleComputedProp.overflow,
|
|
overflowX = _getStyleComputedProp.overflowX,
|
|
overflowY = _getStyleComputedProp.overflowY;
|
|
|
|
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
|
|
return element;
|
|
}
|
|
|
|
return getScrollParent(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Tells if you are running Internet Explorer
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {number} version to check
|
|
* @returns {Boolean} isIE
|
|
*/
|
|
var cache = {};
|
|
|
|
var isIE = function () {
|
|
var version = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all';
|
|
|
|
version = version.toString();
|
|
if (cache.hasOwnProperty(version)) {
|
|
return cache[version];
|
|
}
|
|
switch (version) {
|
|
case '11':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1;
|
|
break;
|
|
case '10':
|
|
cache[version] = navigator.appVersion.indexOf('MSIE 10') !== -1;
|
|
break;
|
|
case 'all':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1 || navigator.userAgent.indexOf('MSIE') !== -1;
|
|
break;
|
|
}
|
|
|
|
//Set IE
|
|
cache.all = cache.all || Object.keys(cache).some(function (key) {
|
|
return cache[key];
|
|
});
|
|
return cache[version];
|
|
};
|
|
|
|
/**
|
|
* Returns the offset parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} offset parent
|
|
*/
|
|
function getOffsetParent(element) {
|
|
if (!element) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
var noOffsetParent = isIE(10) ? document.body : null;
|
|
|
|
// NOTE: 1 DOM access here
|
|
var offsetParent = element.offsetParent;
|
|
// Skip hidden elements which don't have an offsetParent
|
|
while (offsetParent === noOffsetParent && element.nextElementSibling) {
|
|
offsetParent = (element = element.nextElementSibling).offsetParent;
|
|
}
|
|
|
|
var nodeName = offsetParent && offsetParent.nodeName;
|
|
|
|
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return element ? element.ownerDocument.documentElement : document.documentElement;
|
|
}
|
|
|
|
// .offsetParent will return the closest TD or TABLE in case
|
|
// no offsetParent is present, I hate this job...
|
|
if (['TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
|
|
return getOffsetParent(offsetParent);
|
|
}
|
|
|
|
return offsetParent;
|
|
}
|
|
|
|
function isOffsetContainer(element) {
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY') {
|
|
return false;
|
|
}
|
|
return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
|
|
}
|
|
|
|
/**
|
|
* Finds the root node (document, shadowDOM root) of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} node
|
|
* @returns {Element} root node
|
|
*/
|
|
function getRoot(node) {
|
|
if (node.parentNode !== null) {
|
|
return getRoot(node.parentNode);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Finds the offset parent common to the two provided nodes
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element1
|
|
* @argument {Element} element2
|
|
* @returns {Element} common offset parent
|
|
*/
|
|
function findCommonOffsetParent(element1, element2) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
// Here we make sure to give as "start" the element that comes first in the DOM
|
|
var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
var start = order ? element1 : element2;
|
|
var end = order ? element2 : element1;
|
|
|
|
// Get common ancestor container
|
|
var range = document.createRange();
|
|
range.setStart(start, 0);
|
|
range.setEnd(end, 0);
|
|
var commonAncestorContainer = range.commonAncestorContainer;
|
|
|
|
// Both nodes are inside #document
|
|
|
|
if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
|
|
if (isOffsetContainer(commonAncestorContainer)) {
|
|
return commonAncestorContainer;
|
|
}
|
|
|
|
return getOffsetParent(commonAncestorContainer);
|
|
}
|
|
|
|
// one of the nodes is inside shadowDOM, find which one
|
|
var element1root = getRoot(element1);
|
|
if (element1root.host) {
|
|
return findCommonOffsetParent(element1root.host, element2);
|
|
} else {
|
|
return findCommonOffsetParent(element1, getRoot(element2).host);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the scroll value of the given element in the given side (top and left)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {String} side `top` or `left`
|
|
* @returns {number} amount of scrolled pixels
|
|
*/
|
|
function getScroll(element) {
|
|
var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
|
|
|
|
var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
var html = element.ownerDocument.documentElement;
|
|
var scrollingElement = element.ownerDocument.scrollingElement || html;
|
|
return scrollingElement[upperSide];
|
|
}
|
|
|
|
return element[upperSide];
|
|
}
|
|
|
|
/*
|
|
* Sum or subtract the element scroll values (left and top) from a given rect object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} rect - Rect object you want to change
|
|
* @param {HTMLElement} element - The element from the function reads the scroll values
|
|
* @param {Boolean} subtract - set to true if you want to subtract the scroll values
|
|
* @return {Object} rect - The modifier rect object
|
|
*/
|
|
function includeScroll(rect, element) {
|
|
var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
var modifier = subtract ? -1 : 1;
|
|
rect.top += scrollTop * modifier;
|
|
rect.bottom += scrollTop * modifier;
|
|
rect.left += scrollLeft * modifier;
|
|
rect.right += scrollLeft * modifier;
|
|
return rect;
|
|
}
|
|
|
|
/*
|
|
* Helper to detect borders of a given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {CSSStyleDeclaration} styles
|
|
* Result of `getStyleComputedProperty` on the given element
|
|
* @param {String} axis - `x` or `y`
|
|
* @return {number} borders - The borders size of the given axis
|
|
*/
|
|
|
|
function getBordersSize(styles, axis) {
|
|
var sideA = axis === 'x' ? 'Left' : 'Top';
|
|
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
|
|
|
|
return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10);
|
|
}
|
|
|
|
function getSize(axis, body, html, computedStyle) {
|
|
return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0);
|
|
}
|
|
|
|
function getWindowSizes() {
|
|
var body = document.body;
|
|
var html = document.documentElement;
|
|
var computedStyle = isIE(10) && getComputedStyle(html);
|
|
|
|
return {
|
|
height: getSize('Height', body, html, computedStyle),
|
|
width: getSize('Width', body, html, computedStyle)
|
|
};
|
|
}
|
|
|
|
var classCallCheck = function (instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
};
|
|
|
|
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 defineProperty = function (obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
|
|
var _extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Given element offsets, generate an output similar to getBoundingClientRect
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} offsets
|
|
* @returns {Object} ClientRect like output
|
|
*/
|
|
function getClientRect(offsets) {
|
|
return _extends({}, offsets, {
|
|
right: offsets.left + offsets.width,
|
|
bottom: offsets.top + offsets.height
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get bounding client rect of given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} element
|
|
* @return {Object} client rect
|
|
*/
|
|
function getBoundingClientRect(element) {
|
|
var rect = {};
|
|
|
|
// IE10 10 FIX: Please, don't ask, the element isn't
|
|
// considered in DOM in some circumstances...
|
|
// This isn't reproducible in IE10 compatibility mode of IE11
|
|
try {
|
|
if (isIE(10)) {
|
|
rect = element.getBoundingClientRect();
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
rect.top += scrollTop;
|
|
rect.left += scrollLeft;
|
|
rect.bottom += scrollTop;
|
|
rect.right += scrollLeft;
|
|
} else {
|
|
rect = element.getBoundingClientRect();
|
|
}
|
|
} catch (e) {}
|
|
|
|
var result = {
|
|
left: rect.left,
|
|
top: rect.top,
|
|
width: rect.right - rect.left,
|
|
height: rect.bottom - rect.top
|
|
};
|
|
|
|
// subtract scrollbar size from sizes
|
|
var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {};
|
|
var width = sizes.width || element.clientWidth || result.right - result.left;
|
|
var height = sizes.height || element.clientHeight || result.bottom - result.top;
|
|
|
|
var horizScrollbar = element.offsetWidth - width;
|
|
var vertScrollbar = element.offsetHeight - height;
|
|
|
|
// if an hypothetical scrollbar is detected, we must be sure it's not a `border`
|
|
// we make this check conditional for performance reasons
|
|
if (horizScrollbar || vertScrollbar) {
|
|
var styles = getStyleComputedProperty(element);
|
|
horizScrollbar -= getBordersSize(styles, 'x');
|
|
vertScrollbar -= getBordersSize(styles, 'y');
|
|
|
|
result.width -= horizScrollbar;
|
|
result.height -= vertScrollbar;
|
|
}
|
|
|
|
return getClientRect(result);
|
|
}
|
|
|
|
function getOffsetRectRelativeToArbitraryNode(children, parent) {
|
|
var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var isIE10 = isIE(10);
|
|
var isHTML = parent.nodeName === 'HTML';
|
|
var childrenRect = getBoundingClientRect(children);
|
|
var parentRect = getBoundingClientRect(parent);
|
|
var scrollParent = getScrollParent(children);
|
|
|
|
var styles = getStyleComputedProperty(parent);
|
|
var borderTopWidth = parseFloat(styles.borderTopWidth, 10);
|
|
var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
|
|
|
|
// In cases where the parent is fixed, we must ignore negative scroll in offset calc
|
|
if (fixedPosition && parent.nodeName === 'HTML') {
|
|
parentRect.top = Math.max(parentRect.top, 0);
|
|
parentRect.left = Math.max(parentRect.left, 0);
|
|
}
|
|
var offsets = getClientRect({
|
|
top: childrenRect.top - parentRect.top - borderTopWidth,
|
|
left: childrenRect.left - parentRect.left - borderLeftWidth,
|
|
width: childrenRect.width,
|
|
height: childrenRect.height
|
|
});
|
|
offsets.marginTop = 0;
|
|
offsets.marginLeft = 0;
|
|
|
|
// Subtract margins of documentElement in case it's being used as parent
|
|
// we do this only on HTML because it's the only element that behaves
|
|
// differently when margins are applied to it. The margins are included in
|
|
// the box of the documentElement, in the other cases not.
|
|
if (!isIE10 && isHTML) {
|
|
var marginTop = parseFloat(styles.marginTop, 10);
|
|
var marginLeft = parseFloat(styles.marginLeft, 10);
|
|
|
|
offsets.top -= borderTopWidth - marginTop;
|
|
offsets.bottom -= borderTopWidth - marginTop;
|
|
offsets.left -= borderLeftWidth - marginLeft;
|
|
offsets.right -= borderLeftWidth - marginLeft;
|
|
|
|
// Attach marginTop and marginLeft because in some circumstances we may need them
|
|
offsets.marginTop = marginTop;
|
|
offsets.marginLeft = marginLeft;
|
|
}
|
|
|
|
if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
|
|
offsets = includeScroll(offsets, parent);
|
|
}
|
|
|
|
return offsets;
|
|
}
|
|
|
|
function getViewportOffsetRectRelativeToArtbitraryNode(element) {
|
|
var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var html = element.ownerDocument.documentElement;
|
|
var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
|
|
var width = Math.max(html.clientWidth, window.innerWidth || 0);
|
|
var height = Math.max(html.clientHeight, window.innerHeight || 0);
|
|
|
|
var scrollTop = !excludeScroll ? getScroll(html) : 0;
|
|
var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0;
|
|
|
|
var offset = {
|
|
top: scrollTop - relativeOffset.top + relativeOffset.marginTop,
|
|
left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
|
|
width: width,
|
|
height: height
|
|
};
|
|
|
|
return getClientRect(offset);
|
|
}
|
|
|
|
/**
|
|
* Check if the given element is fixed or is inside a fixed parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {Element} customContainer
|
|
* @returns {Boolean} answer to "isFixed?"
|
|
*/
|
|
function isFixed(element) {
|
|
var nodeName = element.nodeName;
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return false;
|
|
}
|
|
if (getStyleComputedProperty(element, 'position') === 'fixed') {
|
|
return true;
|
|
}
|
|
return isFixed(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Finds the first parent of an element that has a transformed property defined
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} first transformed parent or documentElement
|
|
*/
|
|
|
|
function getFixedPositionOffsetParent(element) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element || !element.parentElement || isIE()) {
|
|
return document.documentElement;
|
|
}
|
|
var el = element.parentElement;
|
|
while (el && getStyleComputedProperty(el, 'transform') === 'none') {
|
|
el = el.parentElement;
|
|
}
|
|
return el || document.documentElement;
|
|
}
|
|
|
|
/**
|
|
* Computed the boundaries limits and return them
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} popper
|
|
* @param {HTMLElement} reference
|
|
* @param {number} padding
|
|
* @param {HTMLElement} boundariesElement - Element used to define the boundaries
|
|
* @param {Boolean} fixedPosition - Is in fixed position mode
|
|
* @returns {Object} Coordinates of the boundaries
|
|
*/
|
|
function getBoundaries(popper, reference, padding, boundariesElement) {
|
|
var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
|
|
// NOTE: 1 DOM access here
|
|
|
|
var boundaries = { top: 0, left: 0 };
|
|
var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
|
|
// Handle viewport case
|
|
if (boundariesElement === 'viewport') {
|
|
boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition);
|
|
} else {
|
|
// Handle other cases based on DOM element used as boundaries
|
|
var boundariesNode = void 0;
|
|
if (boundariesElement === 'scrollParent') {
|
|
boundariesNode = getScrollParent(getParentNode(reference));
|
|
if (boundariesNode.nodeName === 'BODY') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
}
|
|
} else if (boundariesElement === 'window') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
} else {
|
|
boundariesNode = boundariesElement;
|
|
}
|
|
|
|
var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition);
|
|
|
|
// In case of HTML, we need a different computation
|
|
if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
|
|
var _getWindowSizes = getWindowSizes(),
|
|
height = _getWindowSizes.height,
|
|
width = _getWindowSizes.width;
|
|
|
|
boundaries.top += offsets.top - offsets.marginTop;
|
|
boundaries.bottom = height + offsets.top;
|
|
boundaries.left += offsets.left - offsets.marginLeft;
|
|
boundaries.right = width + offsets.left;
|
|
} else {
|
|
// for all the other DOM elements, this one is good
|
|
boundaries = offsets;
|
|
}
|
|
}
|
|
|
|
// Add paddings
|
|
boundaries.left += padding;
|
|
boundaries.top += padding;
|
|
boundaries.right -= padding;
|
|
boundaries.bottom -= padding;
|
|
|
|
return boundaries;
|
|
}
|
|
|
|
function getArea(_ref) {
|
|
var width = _ref.width,
|
|
height = _ref.height;
|
|
|
|
return width * height;
|
|
}
|
|
|
|
/**
|
|
* Utility used to transform the `auto` placement to the placement with more
|
|
* available space.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) {
|
|
var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
|
|
|
|
if (placement.indexOf('auto') === -1) {
|
|
return placement;
|
|
}
|
|
|
|
var boundaries = getBoundaries(popper, reference, padding, boundariesElement);
|
|
|
|
var rects = {
|
|
top: {
|
|
width: boundaries.width,
|
|
height: refRect.top - boundaries.top
|
|
},
|
|
right: {
|
|
width: boundaries.right - refRect.right,
|
|
height: boundaries.height
|
|
},
|
|
bottom: {
|
|
width: boundaries.width,
|
|
height: boundaries.bottom - refRect.bottom
|
|
},
|
|
left: {
|
|
width: refRect.left - boundaries.left,
|
|
height: boundaries.height
|
|
}
|
|
};
|
|
|
|
var sortedAreas = Object.keys(rects).map(function (key) {
|
|
return _extends({
|
|
key: key
|
|
}, rects[key], {
|
|
area: getArea(rects[key])
|
|
});
|
|
}).sort(function (a, b) {
|
|
return b.area - a.area;
|
|
});
|
|
|
|
var filteredAreas = sortedAreas.filter(function (_ref2) {
|
|
var width = _ref2.width,
|
|
height = _ref2.height;
|
|
return width >= popper.clientWidth && height >= popper.clientHeight;
|
|
});
|
|
|
|
var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
|
|
|
|
var variation = placement.split('-')[1];
|
|
|
|
return computedPlacement + (variation ? '-' + variation : '');
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the reference element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} state
|
|
* @param {Element} popper - the popper element
|
|
* @param {Element} reference - the reference element (the popper will be relative to this)
|
|
* @param {Element} fixedPosition - is in fixed position mode
|
|
* @returns {Object} An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getReferenceOffsets(state, popper, reference) {
|
|
var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
|
|
}
|
|
|
|
/**
|
|
* Get the outer sizes of the given element (offset size + margins)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Object} object containing width and height properties
|
|
*/
|
|
function getOuterSizes(element) {
|
|
var styles = getComputedStyle(element);
|
|
var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
|
|
var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
|
|
var result = {
|
|
width: element.offsetWidth + y,
|
|
height: element.offsetHeight + x
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement
|
|
* @returns {String} flipped placement
|
|
*/
|
|
function getOppositePlacement(placement) {
|
|
var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
|
|
return placement.replace(/left|right|bottom|top/g, function (matched) {
|
|
return hash[matched];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} position - CSS position the Popper will get applied
|
|
* @param {HTMLElement} popper - the popper element
|
|
* @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
|
|
* @param {String} placement - one of the valid placement options
|
|
* @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getPopperOffsets(popper, referenceOffsets, placement) {
|
|
placement = placement.split('-')[0];
|
|
|
|
// Get popper node sizes
|
|
var popperRect = getOuterSizes(popper);
|
|
|
|
// Add position, width and height to our offsets object
|
|
var popperOffsets = {
|
|
width: popperRect.width,
|
|
height: popperRect.height
|
|
};
|
|
|
|
// depending by the popper placement we have to compute its offsets slightly differently
|
|
var isHoriz = ['right', 'left'].indexOf(placement) !== -1;
|
|
var mainSide = isHoriz ? 'top' : 'left';
|
|
var secondarySide = isHoriz ? 'left' : 'top';
|
|
var measurement = isHoriz ? 'height' : 'width';
|
|
var secondaryMeasurement = !isHoriz ? 'height' : 'width';
|
|
|
|
popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
|
|
if (placement === secondarySide) {
|
|
popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
|
|
} else {
|
|
popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
|
|
}
|
|
|
|
return popperOffsets;
|
|
}
|
|
|
|
/**
|
|
* Mimics the `find` method of Array
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function find(arr, check) {
|
|
// use native find if supported
|
|
if (Array.prototype.find) {
|
|
return arr.find(check);
|
|
}
|
|
|
|
// use `filter` to obtain the same behavior of `find`
|
|
return arr.filter(check)[0];
|
|
}
|
|
|
|
/**
|
|
* Return the index of the matching object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function findIndex(arr, prop, value) {
|
|
// use native findIndex if supported
|
|
if (Array.prototype.findIndex) {
|
|
return arr.findIndex(function (cur) {
|
|
return cur[prop] === value;
|
|
});
|
|
}
|
|
|
|
// use `find` + `indexOf` if `findIndex` isn't supported
|
|
var match = find(arr, function (obj) {
|
|
return obj[prop] === value;
|
|
});
|
|
return arr.indexOf(match);
|
|
}
|
|
|
|
/**
|
|
* Loop trough the list of modifiers and run them in order,
|
|
* each of them will then edit the data object.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {dataObject} data
|
|
* @param {Array} modifiers
|
|
* @param {String} ends - Optional modifier name used as stopper
|
|
* @returns {dataObject}
|
|
*/
|
|
function runModifiers(modifiers, data, ends) {
|
|
var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
|
|
|
|
modifiersToRun.forEach(function (modifier) {
|
|
if (modifier['function']) {
|
|
// eslint-disable-line dot-notation
|
|
console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
|
|
}
|
|
var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
|
|
if (modifier.enabled && isFunction(fn)) {
|
|
// Add properties to offsets to make them a complete clientRect object
|
|
// we do this before each modifier to make sure the previous one doesn't
|
|
// mess with these values
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
data.offsets.reference = getClientRect(data.offsets.reference);
|
|
|
|
data = fn(data, modifier);
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Updates the position of the popper, computing the new offsets and applying
|
|
* the new style.<br />
|
|
* Prefer `scheduleUpdate` over `update` because of performance reasons.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function update() {
|
|
// if popper is destroyed, don't perform any further update
|
|
if (this.state.isDestroyed) {
|
|
return;
|
|
}
|
|
|
|
var data = {
|
|
instance: this,
|
|
styles: {},
|
|
arrowStyles: {},
|
|
attributes: {},
|
|
flipped: false,
|
|
offsets: {}
|
|
};
|
|
|
|
// compute reference element offsets
|
|
data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed);
|
|
|
|
// compute auto placement, store placement inside the data object,
|
|
// modifiers will be able to edit `placement` if needed
|
|
// and refer to originalPlacement to know the original value
|
|
data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding);
|
|
|
|
// store the computed placement inside `originalPlacement`
|
|
data.originalPlacement = data.placement;
|
|
|
|
data.positionFixed = this.options.positionFixed;
|
|
|
|
// compute the popper offsets
|
|
data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement);
|
|
data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute';
|
|
|
|
// run the modifiers
|
|
data = runModifiers(this.modifiers, data);
|
|
|
|
// the first `update` will call `onCreate` callback
|
|
// the other ones will call `onUpdate` callback
|
|
if (!this.state.isCreated) {
|
|
this.state.isCreated = true;
|
|
this.options.onCreate(data);
|
|
} else {
|
|
this.options.onUpdate(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier is enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierEnabled(modifiers, modifierName) {
|
|
return modifiers.some(function (_ref) {
|
|
var name = _ref.name,
|
|
enabled = _ref.enabled;
|
|
return enabled && name === modifierName;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the prefixed supported property name
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} property (camelCase)
|
|
* @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
|
|
*/
|
|
function getSupportedPropertyName(property) {
|
|
var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O'];
|
|
var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
|
|
|
|
for (var i = 0; i < prefixes.length; i++) {
|
|
var prefix = prefixes[i];
|
|
var toCheck = prefix ? '' + prefix + upperProp : property;
|
|
if (typeof document.body.style[toCheck] !== 'undefined') {
|
|
return toCheck;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Destroy the popper
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function destroy() {
|
|
this.state.isDestroyed = true;
|
|
|
|
// touch DOM only if `applyStyle` modifier is enabled
|
|
if (isModifierEnabled(this.modifiers, 'applyStyle')) {
|
|
this.popper.removeAttribute('x-placement');
|
|
this.popper.style.position = '';
|
|
this.popper.style.top = '';
|
|
this.popper.style.left = '';
|
|
this.popper.style.right = '';
|
|
this.popper.style.bottom = '';
|
|
this.popper.style.willChange = '';
|
|
this.popper.style[getSupportedPropertyName('transform')] = '';
|
|
}
|
|
|
|
this.disableEventListeners();
|
|
|
|
// remove the popper if user explicity asked for the deletion on destroy
|
|
// do not use `remove` because IE11 doesn't support it
|
|
if (this.options.removeOnDestroy) {
|
|
this.popper.parentNode.removeChild(this.popper);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the window associated with the element
|
|
* @argument {Element} element
|
|
* @returns {Window}
|
|
*/
|
|
function getWindow(element) {
|
|
var ownerDocument = element.ownerDocument;
|
|
return ownerDocument ? ownerDocument.defaultView : window;
|
|
}
|
|
|
|
function attachToScrollParents(scrollParent, event, callback, scrollParents) {
|
|
var isBody = scrollParent.nodeName === 'BODY';
|
|
var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
|
|
target.addEventListener(event, callback, { passive: true });
|
|
|
|
if (!isBody) {
|
|
attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
|
|
}
|
|
scrollParents.push(target);
|
|
}
|
|
|
|
/**
|
|
* Setup needed event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function setupEventListeners(reference, options, state, updateBound) {
|
|
// Resize event listener on window
|
|
state.updateBound = updateBound;
|
|
getWindow(reference).addEventListener('resize', state.updateBound, { passive: true });
|
|
|
|
// Scroll event listener on scroll parents
|
|
var scrollElement = getScrollParent(reference);
|
|
attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
|
|
state.scrollElement = scrollElement;
|
|
state.eventsEnabled = true;
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* It will add resize/scroll events and start recalculating
|
|
* position of the popper element when they are triggered.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function enableEventListeners() {
|
|
if (!this.state.eventsEnabled) {
|
|
this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function removeEventListeners(reference, state) {
|
|
// Remove resize event listener on window
|
|
getWindow(reference).removeEventListener('resize', state.updateBound);
|
|
|
|
// Remove scroll event listener on scroll parents
|
|
state.scrollParents.forEach(function (target) {
|
|
target.removeEventListener('scroll', state.updateBound);
|
|
});
|
|
|
|
// Reset state
|
|
state.updateBound = null;
|
|
state.scrollParents = [];
|
|
state.scrollElement = null;
|
|
state.eventsEnabled = false;
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* It will remove resize/scroll events and won't recalculate popper position
|
|
* when they are triggered. It also won't trigger onUpdate callback anymore,
|
|
* unless you call `update` method manually.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function disableEventListeners() {
|
|
if (this.state.eventsEnabled) {
|
|
cancelAnimationFrame(this.scheduleUpdate);
|
|
this.state = removeEventListeners(this.reference, this.state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tells if a given input is a number
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {*} input to check
|
|
* @return {Boolean}
|
|
*/
|
|
function isNumeric(n) {
|
|
return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
|
|
}
|
|
|
|
/**
|
|
* Set the style to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the style to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setStyles(element, styles) {
|
|
Object.keys(styles).forEach(function (prop) {
|
|
var unit = '';
|
|
// add unit if the value is numeric and is one of the following
|
|
if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
|
|
unit = 'px';
|
|
}
|
|
element.style[prop] = styles[prop] + unit;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the attributes to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the attributes to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setAttributes(element, attributes) {
|
|
Object.keys(attributes).forEach(function (prop) {
|
|
var value = attributes[prop];
|
|
if (value !== false) {
|
|
element.setAttribute(prop, attributes[prop]);
|
|
} else {
|
|
element.removeAttribute(prop);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} data.styles - List of style properties - values to apply to popper element
|
|
* @argument {Object} data.attributes - List of attribute properties - values to apply to popper element
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The same data object
|
|
*/
|
|
function applyStyle(data) {
|
|
// any property present in `data.styles` will be applied to the popper,
|
|
// in this way we can make the 3rd party modifiers add custom styles to it
|
|
// Be aware, modifiers could override the properties defined in the previous
|
|
// lines of this modifier!
|
|
setStyles(data.instance.popper, data.styles);
|
|
|
|
// any property present in `data.attributes` will be applied to the popper,
|
|
// they will be set as HTML attributes of the element
|
|
setAttributes(data.instance.popper, data.attributes);
|
|
|
|
// if arrowElement is defined and arrowStyles has some properties
|
|
if (data.arrowElement && Object.keys(data.arrowStyles).length) {
|
|
setStyles(data.arrowElement, data.arrowStyles);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Set the x-placement attribute before everything else because it could be used
|
|
* to add margins to the popper margins needs to be calculated to get the
|
|
* correct popper offsets.
|
|
* @method
|
|
* @memberof Popper.modifiers
|
|
* @param {HTMLElement} reference - The reference element used to position the popper
|
|
* @param {HTMLElement} popper - The HTML element used as popper
|
|
* @param {Object} options - Popper.js options
|
|
*/
|
|
function applyStyleOnLoad(reference, popper, options, modifierOptions, state) {
|
|
// compute reference element offsets
|
|
var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed);
|
|
|
|
// compute auto placement, store placement inside the data object,
|
|
// modifiers will be able to edit `placement` if needed
|
|
// and refer to originalPlacement to know the original value
|
|
var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding);
|
|
|
|
popper.setAttribute('x-placement', placement);
|
|
|
|
// Apply `position` to popper before anything else because
|
|
// without the position applied we can't guarantee correct computations
|
|
setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' });
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeStyle(data, options) {
|
|
var x = options.x,
|
|
y = options.y;
|
|
var popper = data.offsets.popper;
|
|
|
|
// Remove this legacy support in Popper.js v2
|
|
|
|
var legacyGpuAccelerationOption = find(data.instance.modifiers, function (modifier) {
|
|
return modifier.name === 'applyStyle';
|
|
}).gpuAcceleration;
|
|
if (legacyGpuAccelerationOption !== undefined) {
|
|
console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');
|
|
}
|
|
var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration;
|
|
|
|
var offsetParent = getOffsetParent(data.instance.popper);
|
|
var offsetParentRect = getBoundingClientRect(offsetParent);
|
|
|
|
// Styles
|
|
var styles = {
|
|
position: popper.position
|
|
};
|
|
|
|
// floor sides to avoid blurry text
|
|
var offsets = {
|
|
left: Math.floor(popper.left),
|
|
top: Math.floor(popper.top),
|
|
bottom: Math.floor(popper.bottom),
|
|
right: Math.floor(popper.right)
|
|
};
|
|
|
|
var sideA = x === 'bottom' ? 'top' : 'bottom';
|
|
var sideB = y === 'right' ? 'left' : 'right';
|
|
|
|
// if gpuAcceleration is set to `true` and transform is supported,
|
|
// we use `translate3d` to apply the position to the popper we
|
|
// automatically use the supported prefixed version if needed
|
|
var prefixedProperty = getSupportedPropertyName('transform');
|
|
|
|
// now, let's make a step back and look at this code closely (wtf?)
|
|
// If the content of the popper grows once it's been positioned, it
|
|
// may happen that the popper gets misplaced because of the new content
|
|
// overflowing its reference element
|
|
// To avoid this problem, we provide two options (x and y), which allow
|
|
// the consumer to define the offset origin.
|
|
// If we position a popper on top of a reference element, we can set
|
|
// `x` to `top` to make the popper grow towards its top instead of
|
|
// its bottom.
|
|
var left = void 0,
|
|
top = void 0;
|
|
if (sideA === 'bottom') {
|
|
top = -offsetParentRect.height + offsets.bottom;
|
|
} else {
|
|
top = offsets.top;
|
|
}
|
|
if (sideB === 'right') {
|
|
left = -offsetParentRect.width + offsets.right;
|
|
} else {
|
|
left = offsets.left;
|
|
}
|
|
if (gpuAcceleration && prefixedProperty) {
|
|
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
|
|
styles[sideA] = 0;
|
|
styles[sideB] = 0;
|
|
styles.willChange = 'transform';
|
|
} else {
|
|
// othwerise, we use the standard `top`, `left`, `bottom` and `right` properties
|
|
var invertTop = sideA === 'bottom' ? -1 : 1;
|
|
var invertLeft = sideB === 'right' ? -1 : 1;
|
|
styles[sideA] = top * invertTop;
|
|
styles[sideB] = left * invertLeft;
|
|
styles.willChange = sideA + ', ' + sideB;
|
|
}
|
|
|
|
// Attributes
|
|
var attributes = {
|
|
'x-placement': data.placement
|
|
};
|
|
|
|
// Update `data` attributes, styles and arrowStyles
|
|
data.attributes = _extends({}, attributes, data.attributes);
|
|
data.styles = _extends({}, styles, data.styles);
|
|
data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier depends from another one.<br />
|
|
* It checks if the needed modifier is listed and enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Array} modifiers - list of modifiers
|
|
* @param {String} requestingName - name of requesting modifier
|
|
* @param {String} requestedName - name of requested modifier
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierRequired(modifiers, requestingName, requestedName) {
|
|
var requesting = find(modifiers, function (_ref) {
|
|
var name = _ref.name;
|
|
return name === requestingName;
|
|
});
|
|
|
|
var isRequired = !!requesting && modifiers.some(function (modifier) {
|
|
return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
|
|
});
|
|
|
|
if (!isRequired) {
|
|
var _requesting = '`' + requestingName + '`';
|
|
var requested = '`' + requestedName + '`';
|
|
console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!');
|
|
}
|
|
return isRequired;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function arrow(data, options) {
|
|
var _data$offsets$arrow;
|
|
|
|
// arrow depends on keepTogether in order to work
|
|
if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) {
|
|
return data;
|
|
}
|
|
|
|
var arrowElement = options.element;
|
|
|
|
// if arrowElement is a string, suppose it's a CSS selector
|
|
if (typeof arrowElement === 'string') {
|
|
arrowElement = data.instance.popper.querySelector(arrowElement);
|
|
|
|
// if arrowElement is not found, don't run the modifier
|
|
if (!arrowElement) {
|
|
return data;
|
|
}
|
|
} else {
|
|
// if the arrowElement isn't a query selector we must check that the
|
|
// provided DOM node is child of its popper node
|
|
if (!data.instance.popper.contains(arrowElement)) {
|
|
console.warn('WARNING: `arrow.element` must be child of its popper element!');
|
|
return data;
|
|
}
|
|
}
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var isVertical = ['left', 'right'].indexOf(placement) !== -1;
|
|
|
|
var len = isVertical ? 'height' : 'width';
|
|
var sideCapitalized = isVertical ? 'Top' : 'Left';
|
|
var side = sideCapitalized.toLowerCase();
|
|
var altSide = isVertical ? 'left' : 'top';
|
|
var opSide = isVertical ? 'bottom' : 'right';
|
|
var arrowElementSize = getOuterSizes(arrowElement)[len];
|
|
|
|
//
|
|
// extends keepTogether behavior making sure the popper and its
|
|
// reference have enough pixels in conjuction
|
|
//
|
|
|
|
// top/left side
|
|
if (reference[opSide] - arrowElementSize < popper[side]) {
|
|
data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize);
|
|
}
|
|
// bottom/right side
|
|
if (reference[side] + arrowElementSize > popper[opSide]) {
|
|
data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide];
|
|
}
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
|
|
// compute center of the popper
|
|
var center = reference[side] + reference[len] / 2 - arrowElementSize / 2;
|
|
|
|
// Compute the sideValue using the updated popper offsets
|
|
// take popper margin in account because we don't have this info available
|
|
var css = getStyleComputedProperty(data.instance.popper);
|
|
var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10);
|
|
var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10);
|
|
var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide;
|
|
|
|
// prevent arrowElement from being placed not contiguously to its popper
|
|
sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0);
|
|
|
|
data.arrowElement = arrowElement;
|
|
data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement variation of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement variation
|
|
* @returns {String} flipped placement variation
|
|
*/
|
|
function getOppositeVariation(variation) {
|
|
if (variation === 'end') {
|
|
return 'start';
|
|
} else if (variation === 'start') {
|
|
return 'end';
|
|
}
|
|
return variation;
|
|
}
|
|
|
|
/**
|
|
* List of accepted placements to use as values of the `placement` option.<br />
|
|
* Valid placements are:
|
|
* - `auto`
|
|
* - `top`
|
|
* - `right`
|
|
* - `bottom`
|
|
* - `left`
|
|
*
|
|
* Each placement can have a variation from this list:
|
|
* - `-start`
|
|
* - `-end`
|
|
*
|
|
* Variations are interpreted easily if you think of them as the left to right
|
|
* written languages. Horizontally (`top` and `bottom`), `start` is left and `end`
|
|
* is right.<br />
|
|
* Vertically (`left` and `right`), `start` is top and `end` is bottom.
|
|
*
|
|
* Some valid examples are:
|
|
* - `top-end` (on top of reference, right aligned)
|
|
* - `right-start` (on right of reference, top aligned)
|
|
* - `bottom` (on bottom, centered)
|
|
* - `auto-right` (on the side with more space available, alignment depends by placement)
|
|
*
|
|
* @static
|
|
* @type {Array}
|
|
* @enum {String}
|
|
* @readonly
|
|
* @method placements
|
|
* @memberof Popper
|
|
*/
|
|
var placements = ['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start'];
|
|
|
|
// Get rid of `auto` `auto-start` and `auto-end`
|
|
var validPlacements = placements.slice(3);
|
|
|
|
/**
|
|
* Given an initial placement, returns all the subsequent placements
|
|
* clockwise (or counter-clockwise).
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement - A valid placement (it accepts variations)
|
|
* @argument {Boolean} counter - Set to true to walk the placements counterclockwise
|
|
* @returns {Array} placements including their variations
|
|
*/
|
|
function clockwise(placement) {
|
|
var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var index = validPlacements.indexOf(placement);
|
|
var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index));
|
|
return counter ? arr.reverse() : arr;
|
|
}
|
|
|
|
var BEHAVIORS = {
|
|
FLIP: 'flip',
|
|
CLOCKWISE: 'clockwise',
|
|
COUNTERCLOCKWISE: 'counterclockwise'
|
|
};
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function flip(data, options) {
|
|
// if `inner` modifier is enabled, we can't use the `flip` modifier
|
|
if (isModifierEnabled(data.instance.modifiers, 'inner')) {
|
|
return data;
|
|
}
|
|
|
|
if (data.flipped && data.placement === data.originalPlacement) {
|
|
// seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
|
|
return data;
|
|
}
|
|
|
|
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed);
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var placementOpposite = getOppositePlacement(placement);
|
|
var variation = data.placement.split('-')[1] || '';
|
|
|
|
var flipOrder = [];
|
|
|
|
switch (options.behavior) {
|
|
case BEHAVIORS.FLIP:
|
|
flipOrder = [placement, placementOpposite];
|
|
break;
|
|
case BEHAVIORS.CLOCKWISE:
|
|
flipOrder = clockwise(placement);
|
|
break;
|
|
case BEHAVIORS.COUNTERCLOCKWISE:
|
|
flipOrder = clockwise(placement, true);
|
|
break;
|
|
default:
|
|
flipOrder = options.behavior;
|
|
}
|
|
|
|
flipOrder.forEach(function (step, index) {
|
|
if (placement !== step || flipOrder.length === index + 1) {
|
|
return data;
|
|
}
|
|
|
|
placement = data.placement.split('-')[0];
|
|
placementOpposite = getOppositePlacement(placement);
|
|
|
|
var popperOffsets = data.offsets.popper;
|
|
var refOffsets = data.offsets.reference;
|
|
|
|
// using floor because the reference offsets may contain decimals we are not going to consider here
|
|
var floor = Math.floor;
|
|
var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom);
|
|
|
|
var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left);
|
|
var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right);
|
|
var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top);
|
|
var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom);
|
|
|
|
var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom;
|
|
|
|
// flip the variation if required
|
|
var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
|
|
var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom);
|
|
|
|
if (overlapsRef || overflowsBoundaries || flippedVariation) {
|
|
// this boolean to detect any flip loop
|
|
data.flipped = true;
|
|
|
|
if (overlapsRef || overflowsBoundaries) {
|
|
placement = flipOrder[index + 1];
|
|
}
|
|
|
|
if (flippedVariation) {
|
|
variation = getOppositeVariation(variation);
|
|
}
|
|
|
|
data.placement = placement + (variation ? '-' + variation : '');
|
|
|
|
// this object contains `position`, we want to preserve it along with
|
|
// any additional property we may add in the future
|
|
data.offsets.popper = _extends({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement));
|
|
|
|
data = runModifiers(data.instance.modifiers, data, 'flip');
|
|
}
|
|
});
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function keepTogether(data) {
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var floor = Math.floor;
|
|
var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
|
|
var side = isVertical ? 'right' : 'bottom';
|
|
var opSide = isVertical ? 'left' : 'top';
|
|
var measurement = isVertical ? 'width' : 'height';
|
|
|
|
if (popper[side] < floor(reference[opSide])) {
|
|
data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement];
|
|
}
|
|
if (popper[opSide] > floor(reference[side])) {
|
|
data.offsets.popper[opSide] = floor(reference[side]);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Converts a string containing value + unit into a px value number
|
|
* @function
|
|
* @memberof {modifiers~offset}
|
|
* @private
|
|
* @argument {String} str - Value + unit string
|
|
* @argument {String} measurement - `height` or `width`
|
|
* @argument {Object} popperOffsets
|
|
* @argument {Object} referenceOffsets
|
|
* @returns {Number|String}
|
|
* Value in pixels, or original string if no values were extracted
|
|
*/
|
|
function toValue(str, measurement, popperOffsets, referenceOffsets) {
|
|
// separate value from unit
|
|
var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/);
|
|
var value = +split[1];
|
|
var unit = split[2];
|
|
|
|
// If it's not a number it's an operator, I guess
|
|
if (!value) {
|
|
return str;
|
|
}
|
|
|
|
if (unit.indexOf('%') === 0) {
|
|
var element = void 0;
|
|
switch (unit) {
|
|
case '%p':
|
|
element = popperOffsets;
|
|
break;
|
|
case '%':
|
|
case '%r':
|
|
default:
|
|
element = referenceOffsets;
|
|
}
|
|
|
|
var rect = getClientRect(element);
|
|
return rect[measurement] / 100 * value;
|
|
} else if (unit === 'vh' || unit === 'vw') {
|
|
// if is a vh or vw, we calculate the size based on the viewport
|
|
var size = void 0;
|
|
if (unit === 'vh') {
|
|
size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
|
} else {
|
|
size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
|
}
|
|
return size / 100 * value;
|
|
} else {
|
|
// if is an explicit pixel unit, we get rid of the unit and keep the value
|
|
// if is an implicit unit, it's px, and we return just the value
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse an `offset` string to extrapolate `x` and `y` numeric offsets.
|
|
* @function
|
|
* @memberof {modifiers~offset}
|
|
* @private
|
|
* @argument {String} offset
|
|
* @argument {Object} popperOffsets
|
|
* @argument {Object} referenceOffsets
|
|
* @argument {String} basePlacement
|
|
* @returns {Array} a two cells array with x and y offsets in numbers
|
|
*/
|
|
function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) {
|
|
var offsets = [0, 0];
|
|
|
|
// Use height if placement is left or right and index is 0 otherwise use width
|
|
// in this way the first offset will use an axis and the second one
|
|
// will use the other one
|
|
var useHeight = ['right', 'left'].indexOf(basePlacement) !== -1;
|
|
|
|
// Split the offset string to obtain a list of values and operands
|
|
// The regex addresses values with the plus or minus sign in front (+10, -20, etc)
|
|
var fragments = offset.split(/(\+|\-)/).map(function (frag) {
|
|
return frag.trim();
|
|
});
|
|
|
|
// Detect if the offset string contains a pair of values or a single one
|
|
// they could be separated by comma or space
|
|
var divider = fragments.indexOf(find(fragments, function (frag) {
|
|
return frag.search(/,|\s/) !== -1;
|
|
}));
|
|
|
|
if (fragments[divider] && fragments[divider].indexOf(',') === -1) {
|
|
console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');
|
|
}
|
|
|
|
// If divider is found, we divide the list of values and operands to divide
|
|
// them by ofset X and Y.
|
|
var splitRegex = /\s*,\s*|\s+/;
|
|
var ops = divider !== -1 ? [fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]), [fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))] : [fragments];
|
|
|
|
// Convert the values with units to absolute pixels to allow our computations
|
|
ops = ops.map(function (op, index) {
|
|
// Most of the units rely on the orientation of the popper
|
|
var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width';
|
|
var mergeWithPrevious = false;
|
|
return op
|
|
// This aggregates any `+` or `-` sign that aren't considered operators
|
|
// e.g.: 10 + +5 => [10, +, +5]
|
|
.reduce(function (a, b) {
|
|
if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) {
|
|
a[a.length - 1] = b;
|
|
mergeWithPrevious = true;
|
|
return a;
|
|
} else if (mergeWithPrevious) {
|
|
a[a.length - 1] += b;
|
|
mergeWithPrevious = false;
|
|
return a;
|
|
} else {
|
|
return a.concat(b);
|
|
}
|
|
}, [])
|
|
// Here we convert the string values into number values (in px)
|
|
.map(function (str) {
|
|
return toValue(str, measurement, popperOffsets, referenceOffsets);
|
|
});
|
|
});
|
|
|
|
// Loop trough the offsets arrays and execute the operations
|
|
ops.forEach(function (op, index) {
|
|
op.forEach(function (frag, index2) {
|
|
if (isNumeric(frag)) {
|
|
offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1);
|
|
}
|
|
});
|
|
});
|
|
return offsets;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @argument {Number|String} options.offset=0
|
|
* The offset value as described in the modifier description
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function offset(data, _ref) {
|
|
var offset = _ref.offset;
|
|
var placement = data.placement,
|
|
_data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var basePlacement = placement.split('-')[0];
|
|
|
|
var offsets = void 0;
|
|
if (isNumeric(+offset)) {
|
|
offsets = [+offset, 0];
|
|
} else {
|
|
offsets = parseOffset(offset, popper, reference, basePlacement);
|
|
}
|
|
|
|
if (basePlacement === 'left') {
|
|
popper.top += offsets[0];
|
|
popper.left -= offsets[1];
|
|
} else if (basePlacement === 'right') {
|
|
popper.top += offsets[0];
|
|
popper.left += offsets[1];
|
|
} else if (basePlacement === 'top') {
|
|
popper.left += offsets[0];
|
|
popper.top -= offsets[1];
|
|
} else if (basePlacement === 'bottom') {
|
|
popper.left += offsets[0];
|
|
popper.top += offsets[1];
|
|
}
|
|
|
|
data.popper = popper;
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function preventOverflow(data, options) {
|
|
var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper);
|
|
|
|
// If offsetParent is the reference element, we really want to
|
|
// go one step up and use the next offsetParent as reference to
|
|
// avoid to make this modifier completely useless and look like broken
|
|
if (data.instance.reference === boundariesElement) {
|
|
boundariesElement = getOffsetParent(boundariesElement);
|
|
}
|
|
|
|
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed);
|
|
options.boundaries = boundaries;
|
|
|
|
var order = options.priority;
|
|
var popper = data.offsets.popper;
|
|
|
|
var check = {
|
|
primary: function primary(placement) {
|
|
var value = popper[placement];
|
|
if (popper[placement] < boundaries[placement] && !options.escapeWithReference) {
|
|
value = Math.max(popper[placement], boundaries[placement]);
|
|
}
|
|
return defineProperty({}, placement, value);
|
|
},
|
|
secondary: function secondary(placement) {
|
|
var mainSide = placement === 'right' ? 'left' : 'top';
|
|
var value = popper[mainSide];
|
|
if (popper[placement] > boundaries[placement] && !options.escapeWithReference) {
|
|
value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height));
|
|
}
|
|
return defineProperty({}, mainSide, value);
|
|
}
|
|
};
|
|
|
|
order.forEach(function (placement) {
|
|
var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary';
|
|
popper = _extends({}, popper, check[side](placement));
|
|
});
|
|
|
|
data.offsets.popper = popper;
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function shift(data) {
|
|
var placement = data.placement;
|
|
var basePlacement = placement.split('-')[0];
|
|
var shiftvariation = placement.split('-')[1];
|
|
|
|
// if shift shiftvariation is specified, run the modifier
|
|
if (shiftvariation) {
|
|
var _data$offsets = data.offsets,
|
|
reference = _data$offsets.reference,
|
|
popper = _data$offsets.popper;
|
|
|
|
var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1;
|
|
var side = isVertical ? 'left' : 'top';
|
|
var measurement = isVertical ? 'width' : 'height';
|
|
|
|
var shiftOffsets = {
|
|
start: defineProperty({}, side, reference[side]),
|
|
end: defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement])
|
|
};
|
|
|
|
data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function hide(data) {
|
|
if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) {
|
|
return data;
|
|
}
|
|
|
|
var refRect = data.offsets.reference;
|
|
var bound = find(data.instance.modifiers, function (modifier) {
|
|
return modifier.name === 'preventOverflow';
|
|
}).boundaries;
|
|
|
|
if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) {
|
|
// Avoid unnecessary DOM access if visibility hasn't changed
|
|
if (data.hide === true) {
|
|
return data;
|
|
}
|
|
|
|
data.hide = true;
|
|
data.attributes['x-out-of-boundaries'] = '';
|
|
} else {
|
|
// Avoid unnecessary DOM access if visibility hasn't changed
|
|
if (data.hide === false) {
|
|
return data;
|
|
}
|
|
|
|
data.hide = false;
|
|
data.attributes['x-out-of-boundaries'] = false;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function inner(data) {
|
|
var placement = data.placement;
|
|
var basePlacement = placement.split('-')[0];
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1;
|
|
|
|
var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1;
|
|
|
|
popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0);
|
|
|
|
data.placement = getOppositePlacement(placement);
|
|
data.offsets.popper = getClientRect(popper);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Modifier function, each modifier can have a function of this type assigned
|
|
* to its `fn` property.<br />
|
|
* These functions will be called on each update, this means that you must
|
|
* make sure they are performant enough to avoid performance bottlenecks.
|
|
*
|
|
* @function ModifierFn
|
|
* @argument {dataObject} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {dataObject} The data object, properly modified
|
|
*/
|
|
|
|
/**
|
|
* Modifiers are plugins used to alter the behavior of your poppers.<br />
|
|
* Popper.js uses a set of 9 modifiers to provide all the basic functionalities
|
|
* needed by the library.
|
|
*
|
|
* Usually you don't want to override the `order`, `fn` and `onLoad` props.
|
|
* All the other properties are configurations that could be tweaked.
|
|
* @namespace modifiers
|
|
*/
|
|
var modifiers = {
|
|
/**
|
|
* Modifier used to shift the popper on the start or end of its reference
|
|
* element.<br />
|
|
* It will read the variation of the `placement` property.<br />
|
|
* It can be one either `-end` or `-start`.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
shift: {
|
|
/** @prop {number} order=100 - Index used to define the order of execution */
|
|
order: 100,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: shift
|
|
},
|
|
|
|
/**
|
|
* The `offset` modifier can shift your popper on both its axis.
|
|
*
|
|
* It accepts the following units:
|
|
* - `px` or unitless, interpreted as pixels
|
|
* - `%` or `%r`, percentage relative to the length of the reference element
|
|
* - `%p`, percentage relative to the length of the popper element
|
|
* - `vw`, CSS viewport width unit
|
|
* - `vh`, CSS viewport height unit
|
|
*
|
|
* For length is intended the main axis relative to the placement of the popper.<br />
|
|
* This means that if the placement is `top` or `bottom`, the length will be the
|
|
* `width`. In case of `left` or `right`, it will be the height.
|
|
*
|
|
* You can provide a single value (as `Number` or `String`), or a pair of values
|
|
* as `String` divided by a comma or one (or more) white spaces.<br />
|
|
* The latter is a deprecated method because it leads to confusion and will be
|
|
* removed in v2.<br />
|
|
* Additionally, it accepts additions and subtractions between different units.
|
|
* Note that multiplications and divisions aren't supported.
|
|
*
|
|
* Valid examples are:
|
|
* ```
|
|
* 10
|
|
* '10%'
|
|
* '10, 10'
|
|
* '10%, 10'
|
|
* '10 + 10%'
|
|
* '10 - 5vh + 3%'
|
|
* '-10px + 5vh, 5px - 6%'
|
|
* ```
|
|
* > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap
|
|
* > with their reference element, unfortunately, you will have to disable the `flip` modifier.
|
|
* > More on this [reading this issue](https://github.com/FezVrasta/popper.js/issues/373)
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
offset: {
|
|
/** @prop {number} order=200 - Index used to define the order of execution */
|
|
order: 200,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: offset,
|
|
/** @prop {Number|String} offset=0
|
|
* The offset value as described in the modifier description
|
|
*/
|
|
offset: 0
|
|
},
|
|
|
|
/**
|
|
* Modifier used to prevent the popper from being positioned outside the boundary.
|
|
*
|
|
* An scenario exists where the reference itself is not within the boundaries.<br />
|
|
* We can say it has "escaped the boundaries" — or just "escaped".<br />
|
|
* In this case we need to decide whether the popper should either:
|
|
*
|
|
* - detach from the reference and remain "trapped" in the boundaries, or
|
|
* - if it should ignore the boundary and "escape with its reference"
|
|
*
|
|
* When `escapeWithReference` is set to`true` and reference is completely
|
|
* outside its boundaries, the popper will overflow (or completely leave)
|
|
* the boundaries in order to remain attached to the edge of the reference.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
preventOverflow: {
|
|
/** @prop {number} order=300 - Index used to define the order of execution */
|
|
order: 300,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: preventOverflow,
|
|
/**
|
|
* @prop {Array} [priority=['left','right','top','bottom']]
|
|
* Popper will try to prevent overflow following these priorities by default,
|
|
* then, it could overflow on the left and on top of the `boundariesElement`
|
|
*/
|
|
priority: ['left', 'right', 'top', 'bottom'],
|
|
/**
|
|
* @prop {number} padding=5
|
|
* Amount of pixel used to define a minimum distance between the boundaries
|
|
* and the popper this makes sure the popper has always a little padding
|
|
* between the edges of its container
|
|
*/
|
|
padding: 5,
|
|
/**
|
|
* @prop {String|HTMLElement} boundariesElement='scrollParent'
|
|
* Boundaries used by the modifier, can be `scrollParent`, `window`,
|
|
* `viewport` or any DOM element.
|
|
*/
|
|
boundariesElement: 'scrollParent'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to make sure the reference and its popper stay near eachothers
|
|
* without leaving any gap between the two. Expecially useful when the arrow is
|
|
* enabled and you want to assure it to point to its reference element.
|
|
* It cares only about the first axis, you can still have poppers with margin
|
|
* between the popper and its reference element.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
keepTogether: {
|
|
/** @prop {number} order=400 - Index used to define the order of execution */
|
|
order: 400,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: keepTogether
|
|
},
|
|
|
|
/**
|
|
* This modifier is used to move the `arrowElement` of the popper to make
|
|
* sure it is positioned between the reference element and its popper element.
|
|
* It will read the outer size of the `arrowElement` node to detect how many
|
|
* pixels of conjuction are needed.
|
|
*
|
|
* It has no effect if no `arrowElement` is provided.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
arrow: {
|
|
/** @prop {number} order=500 - Index used to define the order of execution */
|
|
order: 500,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: arrow,
|
|
/** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */
|
|
element: '[x-arrow]'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to flip the popper's placement when it starts to overlap its
|
|
* reference element.
|
|
*
|
|
* Requires the `preventOverflow` modifier before it in order to work.
|
|
*
|
|
* **NOTE:** this modifier will interrupt the current update cycle and will
|
|
* restart it if it detects the need to flip the placement.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
flip: {
|
|
/** @prop {number} order=600 - Index used to define the order of execution */
|
|
order: 600,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: flip,
|
|
/**
|
|
* @prop {String|Array} behavior='flip'
|
|
* The behavior used to change the popper's placement. It can be one of
|
|
* `flip`, `clockwise`, `counterclockwise` or an array with a list of valid
|
|
* placements (with optional variations).
|
|
*/
|
|
behavior: 'flip',
|
|
/**
|
|
* @prop {number} padding=5
|
|
* The popper will flip if it hits the edges of the `boundariesElement`
|
|
*/
|
|
padding: 5,
|
|
/**
|
|
* @prop {String|HTMLElement} boundariesElement='viewport'
|
|
* The element which will define the boundaries of the popper position,
|
|
* the popper will never be placed outside of the defined boundaries
|
|
* (except if keepTogether is enabled)
|
|
*/
|
|
boundariesElement: 'viewport'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to make the popper flow toward the inner of the reference element.
|
|
* By default, when this modifier is disabled, the popper will be placed outside
|
|
* the reference element.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
inner: {
|
|
/** @prop {number} order=700 - Index used to define the order of execution */
|
|
order: 700,
|
|
/** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */
|
|
enabled: false,
|
|
/** @prop {ModifierFn} */
|
|
fn: inner
|
|
},
|
|
|
|
/**
|
|
* Modifier used to hide the popper when its reference element is outside of the
|
|
* popper boundaries. It will set a `x-out-of-boundaries` attribute which can
|
|
* be used to hide with a CSS selector the popper when its reference is
|
|
* out of boundaries.
|
|
*
|
|
* Requires the `preventOverflow` modifier before it in order to work.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
hide: {
|
|
/** @prop {number} order=800 - Index used to define the order of execution */
|
|
order: 800,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: hide
|
|
},
|
|
|
|
/**
|
|
* Computes the style that will be applied to the popper element to gets
|
|
* properly positioned.
|
|
*
|
|
* Note that this modifier will not touch the DOM, it just prepares the styles
|
|
* so that `applyStyle` modifier can apply it. This separation is useful
|
|
* in case you need to replace `applyStyle` with a custom implementation.
|
|
*
|
|
* This modifier has `850` as `order` value to maintain backward compatibility
|
|
* with previous versions of Popper.js. Expect the modifiers ordering method
|
|
* to change in future major versions of the library.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
computeStyle: {
|
|
/** @prop {number} order=850 - Index used to define the order of execution */
|
|
order: 850,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: computeStyle,
|
|
/**
|
|
* @prop {Boolean} gpuAcceleration=true
|
|
* If true, it uses the CSS 3d transformation to position the popper.
|
|
* Otherwise, it will use the `top` and `left` properties.
|
|
*/
|
|
gpuAcceleration: true,
|
|
/**
|
|
* @prop {string} [x='bottom']
|
|
* Where to anchor the X axis (`bottom` or `top`). AKA X offset origin.
|
|
* Change this if your popper should grow in a direction different from `bottom`
|
|
*/
|
|
x: 'bottom',
|
|
/**
|
|
* @prop {string} [x='left']
|
|
* Where to anchor the Y axis (`left` or `right`). AKA Y offset origin.
|
|
* Change this if your popper should grow in a direction different from `right`
|
|
*/
|
|
y: 'right'
|
|
},
|
|
|
|
/**
|
|
* Applies the computed styles to the popper element.
|
|
*
|
|
* All the DOM manipulations are limited to this modifier. This is useful in case
|
|
* you want to integrate Popper.js inside a framework or view library and you
|
|
* want to delegate all the DOM manipulations to it.
|
|
*
|
|
* Note that if you disable this modifier, you must make sure the popper element
|
|
* has its position set to `absolute` before Popper.js can do its work!
|
|
*
|
|
* Just disable this modifier and define you own to achieve the desired effect.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
applyStyle: {
|
|
/** @prop {number} order=900 - Index used to define the order of execution */
|
|
order: 900,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: applyStyle,
|
|
/** @prop {Function} */
|
|
onLoad: applyStyleOnLoad,
|
|
/**
|
|
* @deprecated since version 1.10.0, the property moved to `computeStyle` modifier
|
|
* @prop {Boolean} gpuAcceleration=true
|
|
* If true, it uses the CSS 3d transformation to position the popper.
|
|
* Otherwise, it will use the `top` and `left` properties.
|
|
*/
|
|
gpuAcceleration: undefined
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The `dataObject` is an object containing all the informations used by Popper.js
|
|
* this object get passed to modifiers and to the `onCreate` and `onUpdate` callbacks.
|
|
* @name dataObject
|
|
* @property {Object} data.instance The Popper.js instance
|
|
* @property {String} data.placement Placement applied to popper
|
|
* @property {String} data.originalPlacement Placement originally defined on init
|
|
* @property {Boolean} data.flipped True if popper has been flipped by flip modifier
|
|
* @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper.
|
|
* @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier
|
|
* @property {Object} data.styles Any CSS property defined here will be applied to the popper, it expects the JavaScript nomenclature (eg. `marginBottom`)
|
|
* @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow, it expects the JavaScript nomenclature (eg. `marginBottom`)
|
|
* @property {Object} data.boundaries Offsets of the popper boundaries
|
|
* @property {Object} data.offsets The measurements of popper, reference and arrow elements.
|
|
* @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values
|
|
* @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values
|
|
* @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0
|
|
*/
|
|
|
|
/**
|
|
* Default options provided to Popper.js constructor.<br />
|
|
* These can be overriden using the `options` argument of Popper.js.<br />
|
|
* To override an option, simply pass as 3rd argument an object with the same
|
|
* structure of this object, example:
|
|
* ```
|
|
* new Popper(ref, pop, {
|
|
* modifiers: {
|
|
* preventOverflow: { enabled: false }
|
|
* }
|
|
* })
|
|
* ```
|
|
* @type {Object}
|
|
* @static
|
|
* @memberof Popper
|
|
*/
|
|
var Defaults = {
|
|
/**
|
|
* Popper's placement
|
|
* @prop {Popper.placements} placement='bottom'
|
|
*/
|
|
placement: 'bottom',
|
|
|
|
/**
|
|
* Set this to true if you want popper to position it self in 'fixed' mode
|
|
* @prop {Boolean} positionFixed=false
|
|
*/
|
|
positionFixed: false,
|
|
|
|
/**
|
|
* Whether events (resize, scroll) are initially enabled
|
|
* @prop {Boolean} eventsEnabled=true
|
|
*/
|
|
eventsEnabled: true,
|
|
|
|
/**
|
|
* Set to true if you want to automatically remove the popper when
|
|
* you call the `destroy` method.
|
|
* @prop {Boolean} removeOnDestroy=false
|
|
*/
|
|
removeOnDestroy: false,
|
|
|
|
/**
|
|
* Callback called when the popper is created.<br />
|
|
* By default, is set to no-op.<br />
|
|
* Access Popper.js instance with `data.instance`.
|
|
* @prop {onCreate}
|
|
*/
|
|
onCreate: function onCreate() {},
|
|
|
|
/**
|
|
* Callback called when the popper is updated, this callback is not called
|
|
* on the initialization/creation of the popper, but only on subsequent
|
|
* updates.<br />
|
|
* By default, is set to no-op.<br />
|
|
* Access Popper.js instance with `data.instance`.
|
|
* @prop {onUpdate}
|
|
*/
|
|
onUpdate: function onUpdate() {},
|
|
|
|
/**
|
|
* List of modifiers used to modify the offsets before they are applied to the popper.
|
|
* They provide most of the functionalities of Popper.js
|
|
* @prop {modifiers}
|
|
*/
|
|
modifiers: modifiers
|
|
};
|
|
|
|
/**
|
|
* @callback onCreate
|
|
* @param {dataObject} data
|
|
*/
|
|
|
|
/**
|
|
* @callback onUpdate
|
|
* @param {dataObject} data
|
|
*/
|
|
|
|
// Utils
|
|
// Methods
|
|
var Popper = function () {
|
|
/**
|
|
* Create a new Popper.js instance
|
|
* @class Popper
|
|
* @param {HTMLElement|referenceObject} reference - The reference element used to position the popper
|
|
* @param {HTMLElement} popper - The HTML element used as popper.
|
|
* @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults)
|
|
* @return {Object} instance - The generated Popper.js instance
|
|
*/
|
|
function Popper(reference, popper) {
|
|
var _this = this;
|
|
|
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
classCallCheck(this, Popper);
|
|
|
|
this.scheduleUpdate = function () {
|
|
return requestAnimationFrame(_this.update);
|
|
};
|
|
|
|
// make update() debounced, so that it only runs at most once-per-tick
|
|
this.update = debounce(this.update.bind(this));
|
|
|
|
// with {} we create a new object with the options inside it
|
|
this.options = _extends({}, Popper.Defaults, options);
|
|
|
|
// init state
|
|
this.state = {
|
|
isDestroyed: false,
|
|
isCreated: false,
|
|
scrollParents: []
|
|
};
|
|
|
|
// get reference and popper elements (allow jQuery wrappers)
|
|
this.reference = reference && reference.jquery ? reference[0] : reference;
|
|
this.popper = popper && popper.jquery ? popper[0] : popper;
|
|
|
|
// Deep merge modifiers options
|
|
this.options.modifiers = {};
|
|
Object.keys(_extends({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function (name) {
|
|
_this.options.modifiers[name] = _extends({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {});
|
|
});
|
|
|
|
// Refactoring modifiers' list (Object => Array)
|
|
this.modifiers = Object.keys(this.options.modifiers).map(function (name) {
|
|
return _extends({
|
|
name: name
|
|
}, _this.options.modifiers[name]);
|
|
})
|
|
// sort the modifiers by order
|
|
.sort(function (a, b) {
|
|
return a.order - b.order;
|
|
});
|
|
|
|
// modifiers have the ability to execute arbitrary code when Popper.js get inited
|
|
// such code is executed in the same order of its modifier
|
|
// they could add new properties to their options configuration
|
|
// BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`!
|
|
this.modifiers.forEach(function (modifierOptions) {
|
|
if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) {
|
|
modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state);
|
|
}
|
|
});
|
|
|
|
// fire the first update to position the popper in the right place
|
|
this.update();
|
|
|
|
var eventsEnabled = this.options.eventsEnabled;
|
|
if (eventsEnabled) {
|
|
// setup event listeners, they will take care of update the position in specific situations
|
|
this.enableEventListeners();
|
|
}
|
|
|
|
this.state.eventsEnabled = eventsEnabled;
|
|
}
|
|
|
|
// We can't use class properties because they don't get listed in the
|
|
// class prototype and break stuff like Sinon stubs
|
|
|
|
|
|
createClass(Popper, [{
|
|
key: 'update',
|
|
value: function update$$1() {
|
|
return update.call(this);
|
|
}
|
|
}, {
|
|
key: 'destroy',
|
|
value: function destroy$$1() {
|
|
return destroy.call(this);
|
|
}
|
|
}, {
|
|
key: 'enableEventListeners',
|
|
value: function enableEventListeners$$1() {
|
|
return enableEventListeners.call(this);
|
|
}
|
|
}, {
|
|
key: 'disableEventListeners',
|
|
value: function disableEventListeners$$1() {
|
|
return disableEventListeners.call(this);
|
|
}
|
|
|
|
/**
|
|
* Schedule an update, it will run on the next UI update available
|
|
* @method scheduleUpdate
|
|
* @memberof Popper
|
|
*/
|
|
|
|
|
|
/**
|
|
* Collection of utilities useful when writing custom modifiers.
|
|
* Starting from version 1.7, this method is available only if you
|
|
* include `popper-utils.js` before `popper.js`.
|
|
*
|
|
* **DEPRECATION**: This way to access PopperUtils is deprecated
|
|
* and will be removed in v2! Use the PopperUtils module directly instead.
|
|
* Due to the high instability of the methods contained in Utils, we can't
|
|
* guarantee them to follow semver. Use them at your own risk!
|
|
* @static
|
|
* @private
|
|
* @type {Object}
|
|
* @deprecated since version 1.8
|
|
* @member Utils
|
|
* @memberof Popper
|
|
*/
|
|
|
|
}]);
|
|
return Popper;
|
|
}();
|
|
|
|
/**
|
|
* The `referenceObject` is an object that provides an interface compatible with Popper.js
|
|
* and lets you use it as replacement of a real DOM node.<br />
|
|
* You can use this method to position a popper relatively to a set of coordinates
|
|
* in case you don't have a DOM node to use as reference.
|
|
*
|
|
* ```
|
|
* new Popper(referenceObject, popperNode);
|
|
* ```
|
|
*
|
|
* NB: This feature isn't supported in Internet Explorer 10
|
|
* @name referenceObject
|
|
* @property {Function} data.getBoundingClientRect
|
|
* A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method.
|
|
* @property {number} data.clientWidth
|
|
* An ES6 getter that will return the width of the virtual reference element.
|
|
* @property {number} data.clientHeight
|
|
* An ES6 getter that will return the height of the virtual reference element.
|
|
*/
|
|
|
|
|
|
Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils;
|
|
Popper.placements = placements;
|
|
Popper.Defaults = Defaults;
|
|
|
|
return Popper;
|
|
|
|
})));
|
|
//# sourceMappingURL=popper.js.map
|
|
|
|
/**!
|
|
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
|
* @version 1.14.1
|
|
* @license
|
|
* Copyright (c) 2016 Federico Zivolo and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
(factory((global.PopperUtils = {})));
|
|
}(this, (function (exports) { 'use strict';
|
|
|
|
/**
|
|
* Get CSS computed property of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Eement} element
|
|
* @argument {String} property
|
|
*/
|
|
function getStyleComputedProperty(element, property) {
|
|
if (element.nodeType !== 1) {
|
|
return [];
|
|
}
|
|
// NOTE: 1 DOM access here
|
|
var css = getComputedStyle(element, null);
|
|
return property ? css[property] : css;
|
|
}
|
|
|
|
/**
|
|
* Returns the parentNode or the host of the element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} parent
|
|
*/
|
|
function getParentNode(element) {
|
|
if (element.nodeName === 'HTML') {
|
|
return element;
|
|
}
|
|
return element.parentNode || element.host;
|
|
}
|
|
|
|
/**
|
|
* Returns the scrolling parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} scroll parent
|
|
*/
|
|
function getScrollParent(element) {
|
|
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
|
|
if (!element) {
|
|
return document.body;
|
|
}
|
|
|
|
switch (element.nodeName) {
|
|
case 'HTML':
|
|
case 'BODY':
|
|
return element.ownerDocument.body;
|
|
case '#document':
|
|
return element.body;
|
|
}
|
|
|
|
// Firefox want us to check `-x` and `-y` variations as well
|
|
|
|
var _getStyleComputedProp = getStyleComputedProperty(element),
|
|
overflow = _getStyleComputedProp.overflow,
|
|
overflowX = _getStyleComputedProp.overflowX,
|
|
overflowY = _getStyleComputedProp.overflowY;
|
|
|
|
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
|
|
return element;
|
|
}
|
|
|
|
return getScrollParent(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Tells if you are running Internet Explorer
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {number} version to check
|
|
* @returns {Boolean} isIE
|
|
*/
|
|
var cache = {};
|
|
|
|
var isIE = function () {
|
|
var version = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all';
|
|
|
|
version = version.toString();
|
|
if (cache.hasOwnProperty(version)) {
|
|
return cache[version];
|
|
}
|
|
switch (version) {
|
|
case '11':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1;
|
|
break;
|
|
case '10':
|
|
cache[version] = navigator.appVersion.indexOf('MSIE 10') !== -1;
|
|
break;
|
|
case 'all':
|
|
cache[version] = navigator.userAgent.indexOf('Trident') !== -1 || navigator.userAgent.indexOf('MSIE') !== -1;
|
|
break;
|
|
}
|
|
|
|
//Set IE
|
|
cache.all = cache.all || Object.keys(cache).some(function (key) {
|
|
return cache[key];
|
|
});
|
|
return cache[version];
|
|
};
|
|
|
|
/**
|
|
* Returns the offset parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} offset parent
|
|
*/
|
|
function getOffsetParent(element) {
|
|
if (!element) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
var noOffsetParent = isIE(10) ? document.body : null;
|
|
|
|
// NOTE: 1 DOM access here
|
|
var offsetParent = element.offsetParent;
|
|
// Skip hidden elements which don't have an offsetParent
|
|
while (offsetParent === noOffsetParent && element.nextElementSibling) {
|
|
offsetParent = (element = element.nextElementSibling).offsetParent;
|
|
}
|
|
|
|
var nodeName = offsetParent && offsetParent.nodeName;
|
|
|
|
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return element ? element.ownerDocument.documentElement : document.documentElement;
|
|
}
|
|
|
|
// .offsetParent will return the closest TD or TABLE in case
|
|
// no offsetParent is present, I hate this job...
|
|
if (['TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
|
|
return getOffsetParent(offsetParent);
|
|
}
|
|
|
|
return offsetParent;
|
|
}
|
|
|
|
function isOffsetContainer(element) {
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY') {
|
|
return false;
|
|
}
|
|
return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
|
|
}
|
|
|
|
/**
|
|
* Finds the root node (document, shadowDOM root) of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} node
|
|
* @returns {Element} root node
|
|
*/
|
|
function getRoot(node) {
|
|
if (node.parentNode !== null) {
|
|
return getRoot(node.parentNode);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Finds the offset parent common to the two provided nodes
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element1
|
|
* @argument {Element} element2
|
|
* @returns {Element} common offset parent
|
|
*/
|
|
function findCommonOffsetParent(element1, element2) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
// Here we make sure to give as "start" the element that comes first in the DOM
|
|
var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
var start = order ? element1 : element2;
|
|
var end = order ? element2 : element1;
|
|
|
|
// Get common ancestor container
|
|
var range = document.createRange();
|
|
range.setStart(start, 0);
|
|
range.setEnd(end, 0);
|
|
var commonAncestorContainer = range.commonAncestorContainer;
|
|
|
|
// Both nodes are inside #document
|
|
|
|
if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
|
|
if (isOffsetContainer(commonAncestorContainer)) {
|
|
return commonAncestorContainer;
|
|
}
|
|
|
|
return getOffsetParent(commonAncestorContainer);
|
|
}
|
|
|
|
// one of the nodes is inside shadowDOM, find which one
|
|
var element1root = getRoot(element1);
|
|
if (element1root.host) {
|
|
return findCommonOffsetParent(element1root.host, element2);
|
|
} else {
|
|
return findCommonOffsetParent(element1, getRoot(element2).host);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the scroll value of the given element in the given side (top and left)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {String} side `top` or `left`
|
|
* @returns {number} amount of scrolled pixels
|
|
*/
|
|
function getScroll(element) {
|
|
var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
|
|
|
|
var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
var html = element.ownerDocument.documentElement;
|
|
var scrollingElement = element.ownerDocument.scrollingElement || html;
|
|
return scrollingElement[upperSide];
|
|
}
|
|
|
|
return element[upperSide];
|
|
}
|
|
|
|
/*
|
|
* Sum or subtract the element scroll values (left and top) from a given rect object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} rect - Rect object you want to change
|
|
* @param {HTMLElement} element - The element from the function reads the scroll values
|
|
* @param {Boolean} subtract - set to true if you want to subtract the scroll values
|
|
* @return {Object} rect - The modifier rect object
|
|
*/
|
|
function includeScroll(rect, element) {
|
|
var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
var modifier = subtract ? -1 : 1;
|
|
rect.top += scrollTop * modifier;
|
|
rect.bottom += scrollTop * modifier;
|
|
rect.left += scrollLeft * modifier;
|
|
rect.right += scrollLeft * modifier;
|
|
return rect;
|
|
}
|
|
|
|
/*
|
|
* Helper to detect borders of a given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {CSSStyleDeclaration} styles
|
|
* Result of `getStyleComputedProperty` on the given element
|
|
* @param {String} axis - `x` or `y`
|
|
* @return {number} borders - The borders size of the given axis
|
|
*/
|
|
|
|
function getBordersSize(styles, axis) {
|
|
var sideA = axis === 'x' ? 'Left' : 'Top';
|
|
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
|
|
|
|
return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10);
|
|
}
|
|
|
|
function getSize(axis, body, html, computedStyle) {
|
|
return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0);
|
|
}
|
|
|
|
function getWindowSizes() {
|
|
var body = document.body;
|
|
var html = document.documentElement;
|
|
var computedStyle = isIE(10) && getComputedStyle(html);
|
|
|
|
return {
|
|
height: getSize('Height', body, html, computedStyle),
|
|
width: getSize('Width', body, html, computedStyle)
|
|
};
|
|
}
|
|
|
|
var _extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Given element offsets, generate an output similar to getBoundingClientRect
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} offsets
|
|
* @returns {Object} ClientRect like output
|
|
*/
|
|
function getClientRect(offsets) {
|
|
return _extends({}, offsets, {
|
|
right: offsets.left + offsets.width,
|
|
bottom: offsets.top + offsets.height
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get bounding client rect of given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} element
|
|
* @return {Object} client rect
|
|
*/
|
|
function getBoundingClientRect(element) {
|
|
var rect = {};
|
|
|
|
// IE10 10 FIX: Please, don't ask, the element isn't
|
|
// considered in DOM in some circumstances...
|
|
// This isn't reproducible in IE10 compatibility mode of IE11
|
|
try {
|
|
if (isIE(10)) {
|
|
rect = element.getBoundingClientRect();
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
rect.top += scrollTop;
|
|
rect.left += scrollLeft;
|
|
rect.bottom += scrollTop;
|
|
rect.right += scrollLeft;
|
|
} else {
|
|
rect = element.getBoundingClientRect();
|
|
}
|
|
} catch (e) {}
|
|
|
|
var result = {
|
|
left: rect.left,
|
|
top: rect.top,
|
|
width: rect.right - rect.left,
|
|
height: rect.bottom - rect.top
|
|
};
|
|
|
|
// subtract scrollbar size from sizes
|
|
var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {};
|
|
var width = sizes.width || element.clientWidth || result.right - result.left;
|
|
var height = sizes.height || element.clientHeight || result.bottom - result.top;
|
|
|
|
var horizScrollbar = element.offsetWidth - width;
|
|
var vertScrollbar = element.offsetHeight - height;
|
|
|
|
// if an hypothetical scrollbar is detected, we must be sure it's not a `border`
|
|
// we make this check conditional for performance reasons
|
|
if (horizScrollbar || vertScrollbar) {
|
|
var styles = getStyleComputedProperty(element);
|
|
horizScrollbar -= getBordersSize(styles, 'x');
|
|
vertScrollbar -= getBordersSize(styles, 'y');
|
|
|
|
result.width -= horizScrollbar;
|
|
result.height -= vertScrollbar;
|
|
}
|
|
|
|
return getClientRect(result);
|
|
}
|
|
|
|
function getOffsetRectRelativeToArbitraryNode(children, parent) {
|
|
var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var isIE10 = isIE(10);
|
|
var isHTML = parent.nodeName === 'HTML';
|
|
var childrenRect = getBoundingClientRect(children);
|
|
var parentRect = getBoundingClientRect(parent);
|
|
var scrollParent = getScrollParent(children);
|
|
|
|
var styles = getStyleComputedProperty(parent);
|
|
var borderTopWidth = parseFloat(styles.borderTopWidth, 10);
|
|
var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
|
|
|
|
// In cases where the parent is fixed, we must ignore negative scroll in offset calc
|
|
if (fixedPosition && parent.nodeName === 'HTML') {
|
|
parentRect.top = Math.max(parentRect.top, 0);
|
|
parentRect.left = Math.max(parentRect.left, 0);
|
|
}
|
|
var offsets = getClientRect({
|
|
top: childrenRect.top - parentRect.top - borderTopWidth,
|
|
left: childrenRect.left - parentRect.left - borderLeftWidth,
|
|
width: childrenRect.width,
|
|
height: childrenRect.height
|
|
});
|
|
offsets.marginTop = 0;
|
|
offsets.marginLeft = 0;
|
|
|
|
// Subtract margins of documentElement in case it's being used as parent
|
|
// we do this only on HTML because it's the only element that behaves
|
|
// differently when margins are applied to it. The margins are included in
|
|
// the box of the documentElement, in the other cases not.
|
|
if (!isIE10 && isHTML) {
|
|
var marginTop = parseFloat(styles.marginTop, 10);
|
|
var marginLeft = parseFloat(styles.marginLeft, 10);
|
|
|
|
offsets.top -= borderTopWidth - marginTop;
|
|
offsets.bottom -= borderTopWidth - marginTop;
|
|
offsets.left -= borderLeftWidth - marginLeft;
|
|
offsets.right -= borderLeftWidth - marginLeft;
|
|
|
|
// Attach marginTop and marginLeft because in some circumstances we may need them
|
|
offsets.marginTop = marginTop;
|
|
offsets.marginLeft = marginLeft;
|
|
}
|
|
|
|
if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
|
|
offsets = includeScroll(offsets, parent);
|
|
}
|
|
|
|
return offsets;
|
|
}
|
|
|
|
function getViewportOffsetRectRelativeToArtbitraryNode(element) {
|
|
var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var html = element.ownerDocument.documentElement;
|
|
var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
|
|
var width = Math.max(html.clientWidth, window.innerWidth || 0);
|
|
var height = Math.max(html.clientHeight, window.innerHeight || 0);
|
|
|
|
var scrollTop = !excludeScroll ? getScroll(html) : 0;
|
|
var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0;
|
|
|
|
var offset = {
|
|
top: scrollTop - relativeOffset.top + relativeOffset.marginTop,
|
|
left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
|
|
width: width,
|
|
height: height
|
|
};
|
|
|
|
return getClientRect(offset);
|
|
}
|
|
|
|
/**
|
|
* Check if the given element is fixed or is inside a fixed parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {Element} customContainer
|
|
* @returns {Boolean} answer to "isFixed?"
|
|
*/
|
|
function isFixed(element) {
|
|
var nodeName = element.nodeName;
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return false;
|
|
}
|
|
if (getStyleComputedProperty(element, 'position') === 'fixed') {
|
|
return true;
|
|
}
|
|
return isFixed(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Finds the first parent of an element that has a transformed property defined
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} first transformed parent or documentElement
|
|
*/
|
|
|
|
function getFixedPositionOffsetParent(element) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element || !element.parentElement || isIE()) {
|
|
return document.documentElement;
|
|
}
|
|
var el = element.parentElement;
|
|
while (el && getStyleComputedProperty(el, 'transform') === 'none') {
|
|
el = el.parentElement;
|
|
}
|
|
return el || document.documentElement;
|
|
}
|
|
|
|
/**
|
|
* Computed the boundaries limits and return them
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} popper
|
|
* @param {HTMLElement} reference
|
|
* @param {number} padding
|
|
* @param {HTMLElement} boundariesElement - Element used to define the boundaries
|
|
* @param {Boolean} fixedPosition - Is in fixed position mode
|
|
* @returns {Object} Coordinates of the boundaries
|
|
*/
|
|
function getBoundaries(popper, reference, padding, boundariesElement) {
|
|
var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
|
|
// NOTE: 1 DOM access here
|
|
|
|
var boundaries = { top: 0, left: 0 };
|
|
var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
|
|
// Handle viewport case
|
|
if (boundariesElement === 'viewport') {
|
|
boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition);
|
|
} else {
|
|
// Handle other cases based on DOM element used as boundaries
|
|
var boundariesNode = void 0;
|
|
if (boundariesElement === 'scrollParent') {
|
|
boundariesNode = getScrollParent(getParentNode(reference));
|
|
if (boundariesNode.nodeName === 'BODY') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
}
|
|
} else if (boundariesElement === 'window') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
} else {
|
|
boundariesNode = boundariesElement;
|
|
}
|
|
|
|
var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition);
|
|
|
|
// In case of HTML, we need a different computation
|
|
if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
|
|
var _getWindowSizes = getWindowSizes(),
|
|
height = _getWindowSizes.height,
|
|
width = _getWindowSizes.width;
|
|
|
|
boundaries.top += offsets.top - offsets.marginTop;
|
|
boundaries.bottom = height + offsets.top;
|
|
boundaries.left += offsets.left - offsets.marginLeft;
|
|
boundaries.right = width + offsets.left;
|
|
} else {
|
|
// for all the other DOM elements, this one is good
|
|
boundaries = offsets;
|
|
}
|
|
}
|
|
|
|
// Add paddings
|
|
boundaries.left += padding;
|
|
boundaries.top += padding;
|
|
boundaries.right -= padding;
|
|
boundaries.bottom -= padding;
|
|
|
|
return boundaries;
|
|
}
|
|
|
|
function getArea(_ref) {
|
|
var width = _ref.width,
|
|
height = _ref.height;
|
|
|
|
return width * height;
|
|
}
|
|
|
|
/**
|
|
* Utility used to transform the `auto` placement to the placement with more
|
|
* available space.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) {
|
|
var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
|
|
|
|
if (placement.indexOf('auto') === -1) {
|
|
return placement;
|
|
}
|
|
|
|
var boundaries = getBoundaries(popper, reference, padding, boundariesElement);
|
|
|
|
var rects = {
|
|
top: {
|
|
width: boundaries.width,
|
|
height: refRect.top - boundaries.top
|
|
},
|
|
right: {
|
|
width: boundaries.right - refRect.right,
|
|
height: boundaries.height
|
|
},
|
|
bottom: {
|
|
width: boundaries.width,
|
|
height: boundaries.bottom - refRect.bottom
|
|
},
|
|
left: {
|
|
width: refRect.left - boundaries.left,
|
|
height: boundaries.height
|
|
}
|
|
};
|
|
|
|
var sortedAreas = Object.keys(rects).map(function (key) {
|
|
return _extends({
|
|
key: key
|
|
}, rects[key], {
|
|
area: getArea(rects[key])
|
|
});
|
|
}).sort(function (a, b) {
|
|
return b.area - a.area;
|
|
});
|
|
|
|
var filteredAreas = sortedAreas.filter(function (_ref2) {
|
|
var width = _ref2.width,
|
|
height = _ref2.height;
|
|
return width >= popper.clientWidth && height >= popper.clientHeight;
|
|
});
|
|
|
|
var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
|
|
|
|
var variation = placement.split('-')[1];
|
|
|
|
return computedPlacement + (variation ? '-' + variation : '');
|
|
}
|
|
|
|
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
|
|
var timeoutDuration = 0;
|
|
for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
|
|
if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
|
|
timeoutDuration = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
function microtaskDebounce(fn) {
|
|
var called = false;
|
|
return function () {
|
|
if (called) {
|
|
return;
|
|
}
|
|
called = true;
|
|
window.Promise.resolve().then(function () {
|
|
called = false;
|
|
fn();
|
|
});
|
|
};
|
|
}
|
|
|
|
function taskDebounce(fn) {
|
|
var scheduled = false;
|
|
return function () {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
setTimeout(function () {
|
|
scheduled = false;
|
|
fn();
|
|
}, timeoutDuration);
|
|
}
|
|
};
|
|
}
|
|
|
|
var supportsMicroTasks = isBrowser && window.Promise;
|
|
|
|
/**
|
|
* Create a debounced version of a method, that's asynchronously deferred
|
|
* but called in the minimum time possible.
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Function} fn
|
|
* @returns {Function}
|
|
*/
|
|
var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
|
|
|
|
/**
|
|
* Mimics the `find` method of Array
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function find(arr, check) {
|
|
// use native find if supported
|
|
if (Array.prototype.find) {
|
|
return arr.find(check);
|
|
}
|
|
|
|
// use `filter` to obtain the same behavior of `find`
|
|
return arr.filter(check)[0];
|
|
}
|
|
|
|
/**
|
|
* Return the index of the matching object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function findIndex(arr, prop, value) {
|
|
// use native findIndex if supported
|
|
if (Array.prototype.findIndex) {
|
|
return arr.findIndex(function (cur) {
|
|
return cur[prop] === value;
|
|
});
|
|
}
|
|
|
|
// use `find` + `indexOf` if `findIndex` isn't supported
|
|
var match = find(arr, function (obj) {
|
|
return obj[prop] === value;
|
|
});
|
|
return arr.indexOf(match);
|
|
}
|
|
|
|
/**
|
|
* Get the position of the given element, relative to its offset parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Element} element
|
|
* @return {Object} position - Coordinates of the element and its `scrollTop`
|
|
*/
|
|
function getOffsetRect(element) {
|
|
var elementRect = void 0;
|
|
if (element.nodeName === 'HTML') {
|
|
var _getWindowSizes = getWindowSizes(),
|
|
width = _getWindowSizes.width,
|
|
height = _getWindowSizes.height;
|
|
|
|
elementRect = {
|
|
width: width,
|
|
height: height,
|
|
left: 0,
|
|
top: 0
|
|
};
|
|
} else {
|
|
elementRect = {
|
|
width: element.offsetWidth,
|
|
height: element.offsetHeight,
|
|
left: element.offsetLeft,
|
|
top: element.offsetTop
|
|
};
|
|
}
|
|
|
|
// position
|
|
return getClientRect(elementRect);
|
|
}
|
|
|
|
/**
|
|
* Get the outer sizes of the given element (offset size + margins)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Object} object containing width and height properties
|
|
*/
|
|
function getOuterSizes(element) {
|
|
var styles = getComputedStyle(element);
|
|
var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
|
|
var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
|
|
var result = {
|
|
width: element.offsetWidth + y,
|
|
height: element.offsetHeight + x
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement
|
|
* @returns {String} flipped placement
|
|
*/
|
|
function getOppositePlacement(placement) {
|
|
var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
|
|
return placement.replace(/left|right|bottom|top/g, function (matched) {
|
|
return hash[matched];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} position - CSS position the Popper will get applied
|
|
* @param {HTMLElement} popper - the popper element
|
|
* @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
|
|
* @param {String} placement - one of the valid placement options
|
|
* @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getPopperOffsets(popper, referenceOffsets, placement) {
|
|
placement = placement.split('-')[0];
|
|
|
|
// Get popper node sizes
|
|
var popperRect = getOuterSizes(popper);
|
|
|
|
// Add position, width and height to our offsets object
|
|
var popperOffsets = {
|
|
width: popperRect.width,
|
|
height: popperRect.height
|
|
};
|
|
|
|
// depending by the popper placement we have to compute its offsets slightly differently
|
|
var isHoriz = ['right', 'left'].indexOf(placement) !== -1;
|
|
var mainSide = isHoriz ? 'top' : 'left';
|
|
var secondarySide = isHoriz ? 'left' : 'top';
|
|
var measurement = isHoriz ? 'height' : 'width';
|
|
var secondaryMeasurement = !isHoriz ? 'height' : 'width';
|
|
|
|
popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
|
|
if (placement === secondarySide) {
|
|
popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
|
|
} else {
|
|
popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
|
|
}
|
|
|
|
return popperOffsets;
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the reference element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} state
|
|
* @param {Element} popper - the popper element
|
|
* @param {Element} reference - the reference element (the popper will be relative to this)
|
|
* @param {Element} fixedPosition - is in fixed position mode
|
|
* @returns {Object} An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getReferenceOffsets(state, popper, reference) {
|
|
var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
|
|
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
|
|
}
|
|
|
|
/**
|
|
* Get the prefixed supported property name
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} property (camelCase)
|
|
* @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
|
|
*/
|
|
function getSupportedPropertyName(property) {
|
|
var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O'];
|
|
var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
|
|
|
|
for (var i = 0; i < prefixes.length; i++) {
|
|
var prefix = prefixes[i];
|
|
var toCheck = prefix ? '' + prefix + upperProp : property;
|
|
if (typeof document.body.style[toCheck] !== 'undefined') {
|
|
return toCheck;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if the given variable is a function
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Any} functionToCheck - variable to check
|
|
* @returns {Boolean} answer to: is a function?
|
|
*/
|
|
function isFunction(functionToCheck) {
|
|
var getType = {};
|
|
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier is enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierEnabled(modifiers, modifierName) {
|
|
return modifiers.some(function (_ref) {
|
|
var name = _ref.name,
|
|
enabled = _ref.enabled;
|
|
return enabled && name === modifierName;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier depends from another one.<br />
|
|
* It checks if the needed modifier is listed and enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Array} modifiers - list of modifiers
|
|
* @param {String} requestingName - name of requesting modifier
|
|
* @param {String} requestedName - name of requested modifier
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierRequired(modifiers, requestingName, requestedName) {
|
|
var requesting = find(modifiers, function (_ref) {
|
|
var name = _ref.name;
|
|
return name === requestingName;
|
|
});
|
|
|
|
var isRequired = !!requesting && modifiers.some(function (modifier) {
|
|
return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
|
|
});
|
|
|
|
if (!isRequired) {
|
|
var _requesting = '`' + requestingName + '`';
|
|
var requested = '`' + requestedName + '`';
|
|
console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!');
|
|
}
|
|
return isRequired;
|
|
}
|
|
|
|
/**
|
|
* Tells if a given input is a number
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {*} input to check
|
|
* @return {Boolean}
|
|
*/
|
|
function isNumeric(n) {
|
|
return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
|
|
}
|
|
|
|
/**
|
|
* Get the window associated with the element
|
|
* @argument {Element} element
|
|
* @returns {Window}
|
|
*/
|
|
function getWindow(element) {
|
|
var ownerDocument = element.ownerDocument;
|
|
return ownerDocument ? ownerDocument.defaultView : window;
|
|
}
|
|
|
|
/**
|
|
* Remove event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function removeEventListeners(reference, state) {
|
|
// Remove resize event listener on window
|
|
getWindow(reference).removeEventListener('resize', state.updateBound);
|
|
|
|
// Remove scroll event listener on scroll parents
|
|
state.scrollParents.forEach(function (target) {
|
|
target.removeEventListener('scroll', state.updateBound);
|
|
});
|
|
|
|
// Reset state
|
|
state.updateBound = null;
|
|
state.scrollParents = [];
|
|
state.scrollElement = null;
|
|
state.eventsEnabled = false;
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Loop trough the list of modifiers and run them in order,
|
|
* each of them will then edit the data object.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {dataObject} data
|
|
* @param {Array} modifiers
|
|
* @param {String} ends - Optional modifier name used as stopper
|
|
* @returns {dataObject}
|
|
*/
|
|
function runModifiers(modifiers, data, ends) {
|
|
var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
|
|
|
|
modifiersToRun.forEach(function (modifier) {
|
|
if (modifier['function']) {
|
|
// eslint-disable-line dot-notation
|
|
console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
|
|
}
|
|
var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
|
|
if (modifier.enabled && isFunction(fn)) {
|
|
// Add properties to offsets to make them a complete clientRect object
|
|
// we do this before each modifier to make sure the previous one doesn't
|
|
// mess with these values
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
data.offsets.reference = getClientRect(data.offsets.reference);
|
|
|
|
data = fn(data, modifier);
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Set the attributes to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the attributes to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setAttributes(element, attributes) {
|
|
Object.keys(attributes).forEach(function (prop) {
|
|
var value = attributes[prop];
|
|
if (value !== false) {
|
|
element.setAttribute(prop, attributes[prop]);
|
|
} else {
|
|
element.removeAttribute(prop);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the style to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the style to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setStyles(element, styles) {
|
|
Object.keys(styles).forEach(function (prop) {
|
|
var unit = '';
|
|
// add unit if the value is numeric and is one of the following
|
|
if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
|
|
unit = 'px';
|
|
}
|
|
element.style[prop] = styles[prop] + unit;
|
|
});
|
|
}
|
|
|
|
function attachToScrollParents(scrollParent, event, callback, scrollParents) {
|
|
var isBody = scrollParent.nodeName === 'BODY';
|
|
var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
|
|
target.addEventListener(event, callback, { passive: true });
|
|
|
|
if (!isBody) {
|
|
attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
|
|
}
|
|
scrollParents.push(target);
|
|
}
|
|
|
|
/**
|
|
* Setup needed event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function setupEventListeners(reference, options, state, updateBound) {
|
|
// Resize event listener on window
|
|
state.updateBound = updateBound;
|
|
getWindow(reference).addEventListener('resize', state.updateBound, { passive: true });
|
|
|
|
// Scroll event listener on scroll parents
|
|
var scrollElement = getScrollParent(reference);
|
|
attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
|
|
state.scrollElement = scrollElement;
|
|
state.eventsEnabled = true;
|
|
|
|
return state;
|
|
}
|
|
|
|
// This is here just for backward compatibility with versions lower than v1.10.3
|
|
// you should import the utilities using named exports, if you want them all use:
|
|
// ```
|
|
// import * as PopperUtils from 'popper-utils';
|
|
// ```
|
|
// The default export will be removed in the next major version.
|
|
var index = {
|
|
computeAutoPlacement: computeAutoPlacement,
|
|
debounce: debounce,
|
|
findIndex: findIndex,
|
|
getBordersSize: getBordersSize,
|
|
getBoundaries: getBoundaries,
|
|
getBoundingClientRect: getBoundingClientRect,
|
|
getClientRect: getClientRect,
|
|
getOffsetParent: getOffsetParent,
|
|
getOffsetRect: getOffsetRect,
|
|
getOffsetRectRelativeToArbitraryNode: getOffsetRectRelativeToArbitraryNode,
|
|
getOuterSizes: getOuterSizes,
|
|
getParentNode: getParentNode,
|
|
getPopperOffsets: getPopperOffsets,
|
|
getReferenceOffsets: getReferenceOffsets,
|
|
getScroll: getScroll,
|
|
getScrollParent: getScrollParent,
|
|
getStyleComputedProperty: getStyleComputedProperty,
|
|
getSupportedPropertyName: getSupportedPropertyName,
|
|
getWindowSizes: getWindowSizes,
|
|
isFixed: isFixed,
|
|
isFunction: isFunction,
|
|
isModifierEnabled: isModifierEnabled,
|
|
isModifierRequired: isModifierRequired,
|
|
isNumeric: isNumeric,
|
|
removeEventListeners: removeEventListeners,
|
|
runModifiers: runModifiers,
|
|
setAttributes: setAttributes,
|
|
setStyles: setStyles,
|
|
setupEventListeners: setupEventListeners
|
|
};
|
|
|
|
exports.computeAutoPlacement = computeAutoPlacement;
|
|
exports.debounce = debounce;
|
|
exports.findIndex = findIndex;
|
|
exports.getBordersSize = getBordersSize;
|
|
exports.getBoundaries = getBoundaries;
|
|
exports.getBoundingClientRect = getBoundingClientRect;
|
|
exports.getClientRect = getClientRect;
|
|
exports.getOffsetParent = getOffsetParent;
|
|
exports.getOffsetRect = getOffsetRect;
|
|
exports.getOffsetRectRelativeToArbitraryNode = getOffsetRectRelativeToArbitraryNode;
|
|
exports.getOuterSizes = getOuterSizes;
|
|
exports.getParentNode = getParentNode;
|
|
exports.getPopperOffsets = getPopperOffsets;
|
|
exports.getReferenceOffsets = getReferenceOffsets;
|
|
exports.getScroll = getScroll;
|
|
exports.getScrollParent = getScrollParent;
|
|
exports.getStyleComputedProperty = getStyleComputedProperty;
|
|
exports.getSupportedPropertyName = getSupportedPropertyName;
|
|
exports.getWindowSizes = getWindowSizes;
|
|
exports.isFixed = isFixed;
|
|
exports.isFunction = isFunction;
|
|
exports.isModifierEnabled = isModifierEnabled;
|
|
exports.isModifierRequired = isModifierRequired;
|
|
exports.isNumeric = isNumeric;
|
|
exports.removeEventListeners = removeEventListeners;
|
|
exports.runModifiers = runModifiers;
|
|
exports.setAttributes = setAttributes;
|
|
exports.setStyles = setStyles;
|
|
exports.setupEventListeners = setupEventListeners;
|
|
exports['default'] = index;
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
})));
|
|
//# sourceMappingURL=popper-utils.js.map
|
|
|
|
/*!
|
|
* Select2 4.0.5
|
|
* https://select2.github.io
|
|
*
|
|
* Released under the MIT license
|
|
* https://github.com/select2/select2/blob/master/LICENSE.md
|
|
*/
|
|
(function (factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define(['jquery'], factory);
|
|
} else if (typeof module === 'object' && module.exports) {
|
|
// Node/CommonJS
|
|
module.exports = function (root, jQuery) {
|
|
if (jQuery === undefined) {
|
|
// require('jQuery') returns a factory that requires window to
|
|
// build a jQuery instance, we normalize how we use modules
|
|
// that require this pattern but the window provided is a noop
|
|
// if it's defined (how jquery works)
|
|
if (typeof window !== 'undefined') {
|
|
jQuery = require('jquery');
|
|
}
|
|
else {
|
|
jQuery = require('jquery')(root);
|
|
}
|
|
}
|
|
factory(jQuery);
|
|
return jQuery;
|
|
};
|
|
} else {
|
|
// Browser globals
|
|
factory(jQuery);
|
|
}
|
|
} (function (jQuery) {
|
|
// This is needed so we can catch the AMD loader configuration and use it
|
|
// The inner file should be wrapped (by `banner.start.js`) in a function that
|
|
// returns the AMD loader references.
|
|
var S2 =(function () {
|
|
// Restore the Select2 AMD loader so it can be used
|
|
// Needed mostly in the language files, where the loader is not inserted
|
|
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
|
|
var S2 = jQuery.fn.select2.amd;
|
|
}
|
|
var S2;(function () { if (!S2 || !S2.requirejs) {
|
|
if (!S2) { S2 = {}; } else { require = S2; }
|
|
/**
|
|
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
|
|
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
|
|
*/
|
|
//Going sloppy to avoid 'use strict' string cost, but strict practices should
|
|
//be followed.
|
|
/*global setTimeout: false */
|
|
|
|
var requirejs, require, define;
|
|
(function (undef) {
|
|
var main, req, makeMap, handlers,
|
|
defined = {},
|
|
waiting = {},
|
|
config = {},
|
|
defining = {},
|
|
hasOwn = Object.prototype.hasOwnProperty,
|
|
aps = [].slice,
|
|
jsSuffixRegExp = /\.js$/;
|
|
|
|
function hasProp(obj, prop) {
|
|
return hasOwn.call(obj, prop);
|
|
}
|
|
|
|
/**
|
|
* Given a relative module name, like ./something, normalize it to
|
|
* a real name that can be mapped to a path.
|
|
* @param {String} name the relative name
|
|
* @param {String} baseName a real name that the name arg is relative
|
|
* to.
|
|
* @returns {String} normalized name
|
|
*/
|
|
function normalize(name, baseName) {
|
|
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
|
|
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
|
|
baseParts = baseName && baseName.split("/"),
|
|
map = config.map,
|
|
starMap = (map && map['*']) || {};
|
|
|
|
//Adjust any relative paths.
|
|
if (name) {
|
|
name = name.split('/');
|
|
lastIndex = name.length - 1;
|
|
|
|
// If wanting node ID compatibility, strip .js from end
|
|
// of IDs. Have to do this here, and not in nameToUrl
|
|
// because node allows either .js or non .js to map
|
|
// to same file.
|
|
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
|
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
|
}
|
|
|
|
// Starts with a '.' so need the baseName
|
|
if (name[0].charAt(0) === '.' && baseParts) {
|
|
//Convert baseName to array, and lop off the last part,
|
|
//so that . matches that 'directory' and not name of the baseName's
|
|
//module. For instance, baseName of 'one/two/three', maps to
|
|
//'one/two/three.js', but we want the directory, 'one/two' for
|
|
//this normalization.
|
|
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
|
|
name = normalizedBaseParts.concat(name);
|
|
}
|
|
|
|
//start trimDots
|
|
for (i = 0; i < name.length; i++) {
|
|
part = name[i];
|
|
if (part === '.') {
|
|
name.splice(i, 1);
|
|
i -= 1;
|
|
} else if (part === '..') {
|
|
// If at the start, or previous value is still ..,
|
|
// keep them so that when converted to a path it may
|
|
// still work when converted to a path, even though
|
|
// as an ID it is less than ideal. In larger point
|
|
// releases, may be better to just kick out an error.
|
|
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
|
|
continue;
|
|
} else if (i > 0) {
|
|
name.splice(i - 1, 2);
|
|
i -= 2;
|
|
}
|
|
}
|
|
}
|
|
//end trimDots
|
|
|
|
name = name.join('/');
|
|
}
|
|
|
|
//Apply map config if available.
|
|
if ((baseParts || starMap) && map) {
|
|
nameParts = name.split('/');
|
|
|
|
for (i = nameParts.length; i > 0; i -= 1) {
|
|
nameSegment = nameParts.slice(0, i).join("/");
|
|
|
|
if (baseParts) {
|
|
//Find the longest baseName segment match in the config.
|
|
//So, do joins on the biggest to smallest lengths of baseParts.
|
|
for (j = baseParts.length; j > 0; j -= 1) {
|
|
mapValue = map[baseParts.slice(0, j).join('/')];
|
|
|
|
//baseName segment has config, find if it has one for
|
|
//this name.
|
|
if (mapValue) {
|
|
mapValue = mapValue[nameSegment];
|
|
if (mapValue) {
|
|
//Match, update name to the new value.
|
|
foundMap = mapValue;
|
|
foundI = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundMap) {
|
|
break;
|
|
}
|
|
|
|
//Check for a star map match, but just hold on to it,
|
|
//if there is a shorter segment match later in a matching
|
|
//config, then favor over this star map.
|
|
if (!foundStarMap && starMap && starMap[nameSegment]) {
|
|
foundStarMap = starMap[nameSegment];
|
|
starI = i;
|
|
}
|
|
}
|
|
|
|
if (!foundMap && foundStarMap) {
|
|
foundMap = foundStarMap;
|
|
foundI = starI;
|
|
}
|
|
|
|
if (foundMap) {
|
|
nameParts.splice(0, foundI, foundMap);
|
|
name = nameParts.join('/');
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
function makeRequire(relName, forceSync) {
|
|
return function () {
|
|
//A version of a require function that passes a moduleName
|
|
//value for items that may need to
|
|
//look up paths relative to the moduleName
|
|
var args = aps.call(arguments, 0);
|
|
|
|
//If first arg is not require('string'), and there is only
|
|
//one arg, it is the array form without a callback. Insert
|
|
//a null so that the following concat is correct.
|
|
if (typeof args[0] !== 'string' && args.length === 1) {
|
|
args.push(null);
|
|
}
|
|
return req.apply(undef, args.concat([relName, forceSync]));
|
|
};
|
|
}
|
|
|
|
function makeNormalize(relName) {
|
|
return function (name) {
|
|
return normalize(name, relName);
|
|
};
|
|
}
|
|
|
|
function makeLoad(depName) {
|
|
return function (value) {
|
|
defined[depName] = value;
|
|
};
|
|
}
|
|
|
|
function callDep(name) {
|
|
if (hasProp(waiting, name)) {
|
|
var args = waiting[name];
|
|
delete waiting[name];
|
|
defining[name] = true;
|
|
main.apply(undef, args);
|
|
}
|
|
|
|
if (!hasProp(defined, name) && !hasProp(defining, name)) {
|
|
throw new Error('No ' + name);
|
|
}
|
|
return defined[name];
|
|
}
|
|
|
|
//Turns a plugin!resource to [plugin, resource]
|
|
//with the plugin being undefined if the name
|
|
//did not have a plugin prefix.
|
|
function splitPrefix(name) {
|
|
var prefix,
|
|
index = name ? name.indexOf('!') : -1;
|
|
if (index > -1) {
|
|
prefix = name.substring(0, index);
|
|
name = name.substring(index + 1, name.length);
|
|
}
|
|
return [prefix, name];
|
|
}
|
|
|
|
//Creates a parts array for a relName where first part is plugin ID,
|
|
//second part is resource ID. Assumes relName has already been normalized.
|
|
function makeRelParts(relName) {
|
|
return relName ? splitPrefix(relName) : [];
|
|
}
|
|
|
|
/**
|
|
* Makes a name map, normalizing the name, and using a plugin
|
|
* for normalization if necessary. Grabs a ref to plugin
|
|
* too, as an optimization.
|
|
*/
|
|
makeMap = function (name, relParts) {
|
|
var plugin,
|
|
parts = splitPrefix(name),
|
|
prefix = parts[0],
|
|
relResourceName = relParts[1];
|
|
|
|
name = parts[1];
|
|
|
|
if (prefix) {
|
|
prefix = normalize(prefix, relResourceName);
|
|
plugin = callDep(prefix);
|
|
}
|
|
|
|
//Normalize according
|
|
if (prefix) {
|
|
if (plugin && plugin.normalize) {
|
|
name = plugin.normalize(name, makeNormalize(relResourceName));
|
|
} else {
|
|
name = normalize(name, relResourceName);
|
|
}
|
|
} else {
|
|
name = normalize(name, relResourceName);
|
|
parts = splitPrefix(name);
|
|
prefix = parts[0];
|
|
name = parts[1];
|
|
if (prefix) {
|
|
plugin = callDep(prefix);
|
|
}
|
|
}
|
|
|
|
//Using ridiculous property names for space reasons
|
|
return {
|
|
f: prefix ? prefix + '!' + name : name, //fullName
|
|
n: name,
|
|
pr: prefix,
|
|
p: plugin
|
|
};
|
|
};
|
|
|
|
function makeConfig(name) {
|
|
return function () {
|
|
return (config && config.config && config.config[name]) || {};
|
|
};
|
|
}
|
|
|
|
handlers = {
|
|
require: function (name) {
|
|
return makeRequire(name);
|
|
},
|
|
exports: function (name) {
|
|
var e = defined[name];
|
|
if (typeof e !== 'undefined') {
|
|
return e;
|
|
} else {
|
|
return (defined[name] = {});
|
|
}
|
|
},
|
|
module: function (name) {
|
|
return {
|
|
id: name,
|
|
uri: '',
|
|
exports: defined[name],
|
|
config: makeConfig(name)
|
|
};
|
|
}
|
|
};
|
|
|
|
main = function (name, deps, callback, relName) {
|
|
var cjsModule, depName, ret, map, i, relParts,
|
|
args = [],
|
|
callbackType = typeof callback,
|
|
usingExports;
|
|
|
|
//Use name if no relName
|
|
relName = relName || name;
|
|
relParts = makeRelParts(relName);
|
|
|
|
//Call the callback to define the module, if necessary.
|
|
if (callbackType === 'undefined' || callbackType === 'function') {
|
|
//Pull out the defined dependencies and pass the ordered
|
|
//values to the callback.
|
|
//Default to [require, exports, module] if no deps
|
|
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
|
|
for (i = 0; i < deps.length; i += 1) {
|
|
map = makeMap(deps[i], relParts);
|
|
depName = map.f;
|
|
|
|
//Fast path CommonJS standard dependencies.
|
|
if (depName === "require") {
|
|
args[i] = handlers.require(name);
|
|
} else if (depName === "exports") {
|
|
//CommonJS module spec 1.1
|
|
args[i] = handlers.exports(name);
|
|
usingExports = true;
|
|
} else if (depName === "module") {
|
|
//CommonJS module spec 1.1
|
|
cjsModule = args[i] = handlers.module(name);
|
|
} else if (hasProp(defined, depName) ||
|
|
hasProp(waiting, depName) ||
|
|
hasProp(defining, depName)) {
|
|
args[i] = callDep(depName);
|
|
} else if (map.p) {
|
|
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
|
|
args[i] = defined[depName];
|
|
} else {
|
|
throw new Error(name + ' missing ' + depName);
|
|
}
|
|
}
|
|
|
|
ret = callback ? callback.apply(defined[name], args) : undefined;
|
|
|
|
if (name) {
|
|
//If setting exports via "module" is in play,
|
|
//favor that over return value and exports. After that,
|
|
//favor a non-undefined return value over exports use.
|
|
if (cjsModule && cjsModule.exports !== undef &&
|
|
cjsModule.exports !== defined[name]) {
|
|
defined[name] = cjsModule.exports;
|
|
} else if (ret !== undef || !usingExports) {
|
|
//Use the return value from the function.
|
|
defined[name] = ret;
|
|
}
|
|
}
|
|
} else if (name) {
|
|
//May just be an object definition for the module. Only
|
|
//worry about defining if have a module name.
|
|
defined[name] = callback;
|
|
}
|
|
};
|
|
|
|
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
|
|
if (typeof deps === "string") {
|
|
if (handlers[deps]) {
|
|
//callback in this case is really relName
|
|
return handlers[deps](callback);
|
|
}
|
|
//Just return the module wanted. In this scenario, the
|
|
//deps arg is the module name, and second arg (if passed)
|
|
//is just the relName.
|
|
//Normalize module name, if it contains . or ..
|
|
return callDep(makeMap(deps, makeRelParts(callback)).f);
|
|
} else if (!deps.splice) {
|
|
//deps is a config object, not an array.
|
|
config = deps;
|
|
if (config.deps) {
|
|
req(config.deps, config.callback);
|
|
}
|
|
if (!callback) {
|
|
return;
|
|
}
|
|
|
|
if (callback.splice) {
|
|
//callback is an array, which means it is a dependency list.
|
|
//Adjust args if there are dependencies
|
|
deps = callback;
|
|
callback = relName;
|
|
relName = null;
|
|
} else {
|
|
deps = undef;
|
|
}
|
|
}
|
|
|
|
//Support require(['a'])
|
|
callback = callback || function () {};
|
|
|
|
//If relName is a function, it is an errback handler,
|
|
//so remove it.
|
|
if (typeof relName === 'function') {
|
|
relName = forceSync;
|
|
forceSync = alt;
|
|
}
|
|
|
|
//Simulate async callback;
|
|
if (forceSync) {
|
|
main(undef, deps, callback, relName);
|
|
} else {
|
|
//Using a non-zero value because of concern for what old browsers
|
|
//do, and latest browsers "upgrade" to 4 if lower value is used:
|
|
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
|
|
//If want a value immediately, use require('id') instead -- something
|
|
//that works in almond on the global level, but not guaranteed and
|
|
//unlikely to work in other AMD implementations.
|
|
setTimeout(function () {
|
|
main(undef, deps, callback, relName);
|
|
}, 4);
|
|
}
|
|
|
|
return req;
|
|
};
|
|
|
|
/**
|
|
* Just drops the config on the floor, but returns req in case
|
|
* the config return value is used.
|
|
*/
|
|
req.config = function (cfg) {
|
|
return req(cfg);
|
|
};
|
|
|
|
/**
|
|
* Expose module registry for debugging and tooling
|
|
*/
|
|
requirejs._defined = defined;
|
|
|
|
define = function (name, deps, callback) {
|
|
if (typeof name !== 'string') {
|
|
throw new Error('See almond README: incorrect module build, no module name');
|
|
}
|
|
|
|
//This module may not have dependencies
|
|
if (!deps.splice) {
|
|
//deps is not an array, so probably means
|
|
//an object literal or factory function for
|
|
//the value. Adjust args.
|
|
callback = deps;
|
|
deps = [];
|
|
}
|
|
|
|
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
|
|
waiting[name] = [name, deps, callback];
|
|
}
|
|
};
|
|
|
|
define.amd = {
|
|
jQuery: true
|
|
};
|
|
}());
|
|
|
|
S2.requirejs = requirejs;S2.require = require;S2.define = define;
|
|
}
|
|
}());
|
|
S2.define("almond", function(){});
|
|
|
|
/* global jQuery:false, $:false */
|
|
S2.define('jquery',[],function () {
|
|
var _$ = jQuery || $;
|
|
|
|
if (_$ == null && console && console.error) {
|
|
console.error(
|
|
'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
|
|
'found. Make sure that you are including jQuery before Select2 on your ' +
|
|
'web page.'
|
|
);
|
|
}
|
|
|
|
return _$;
|
|
});
|
|
|
|
S2.define('select2/utils',[
|
|
'jquery'
|
|
], function ($) {
|
|
var Utils = {};
|
|
|
|
Utils.Extend = function (ChildClass, SuperClass) {
|
|
var __hasProp = {}.hasOwnProperty;
|
|
|
|
function BaseConstructor () {
|
|
this.constructor = ChildClass;
|
|
}
|
|
|
|
for (var key in SuperClass) {
|
|
if (__hasProp.call(SuperClass, key)) {
|
|
ChildClass[key] = SuperClass[key];
|
|
}
|
|
}
|
|
|
|
BaseConstructor.prototype = SuperClass.prototype;
|
|
ChildClass.prototype = new BaseConstructor();
|
|
ChildClass.__super__ = SuperClass.prototype;
|
|
|
|
return ChildClass;
|
|
};
|
|
|
|
function getMethods (theClass) {
|
|
var proto = theClass.prototype;
|
|
|
|
var methods = [];
|
|
|
|
for (var methodName in proto) {
|
|
var m = proto[methodName];
|
|
|
|
if (typeof m !== 'function') {
|
|
continue;
|
|
}
|
|
|
|
if (methodName === 'constructor') {
|
|
continue;
|
|
}
|
|
|
|
methods.push(methodName);
|
|
}
|
|
|
|
return methods;
|
|
}
|
|
|
|
Utils.Decorate = function (SuperClass, DecoratorClass) {
|
|
var decoratedMethods = getMethods(DecoratorClass);
|
|
var superMethods = getMethods(SuperClass);
|
|
|
|
function DecoratedClass () {
|
|
var unshift = Array.prototype.unshift;
|
|
|
|
var argCount = DecoratorClass.prototype.constructor.length;
|
|
|
|
var calledConstructor = SuperClass.prototype.constructor;
|
|
|
|
if (argCount > 0) {
|
|
unshift.call(arguments, SuperClass.prototype.constructor);
|
|
|
|
calledConstructor = DecoratorClass.prototype.constructor;
|
|
}
|
|
|
|
calledConstructor.apply(this, arguments);
|
|
}
|
|
|
|
DecoratorClass.displayName = SuperClass.displayName;
|
|
|
|
function ctr () {
|
|
this.constructor = DecoratedClass;
|
|
}
|
|
|
|
DecoratedClass.prototype = new ctr();
|
|
|
|
for (var m = 0; m < superMethods.length; m++) {
|
|
var superMethod = superMethods[m];
|
|
|
|
DecoratedClass.prototype[superMethod] =
|
|
SuperClass.prototype[superMethod];
|
|
}
|
|
|
|
var calledMethod = function (methodName) {
|
|
// Stub out the original method if it's not decorating an actual method
|
|
var originalMethod = function () {};
|
|
|
|
if (methodName in DecoratedClass.prototype) {
|
|
originalMethod = DecoratedClass.prototype[methodName];
|
|
}
|
|
|
|
var decoratedMethod = DecoratorClass.prototype[methodName];
|
|
|
|
return function () {
|
|
var unshift = Array.prototype.unshift;
|
|
|
|
unshift.call(arguments, originalMethod);
|
|
|
|
return decoratedMethod.apply(this, arguments);
|
|
};
|
|
};
|
|
|
|
for (var d = 0; d < decoratedMethods.length; d++) {
|
|
var decoratedMethod = decoratedMethods[d];
|
|
|
|
DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
|
|
}
|
|
|
|
return DecoratedClass;
|
|
};
|
|
|
|
var Observable = function () {
|
|
this.listeners = {};
|
|
};
|
|
|
|
Observable.prototype.on = function (event, callback) {
|
|
this.listeners = this.listeners || {};
|
|
|
|
if (event in this.listeners) {
|
|
this.listeners[event].push(callback);
|
|
} else {
|
|
this.listeners[event] = [callback];
|
|
}
|
|
};
|
|
|
|
Observable.prototype.trigger = function (event) {
|
|
var slice = Array.prototype.slice;
|
|
var params = slice.call(arguments, 1);
|
|
|
|
this.listeners = this.listeners || {};
|
|
|
|
// Params should always come in as an array
|
|
if (params == null) {
|
|
params = [];
|
|
}
|
|
|
|
// If there are no arguments to the event, use a temporary object
|
|
if (params.length === 0) {
|
|
params.push({});
|
|
}
|
|
|
|
// Set the `_type` of the first object to the event
|
|
params[0]._type = event;
|
|
|
|
if (event in this.listeners) {
|
|
this.invoke(this.listeners[event], slice.call(arguments, 1));
|
|
}
|
|
|
|
if ('*' in this.listeners) {
|
|
this.invoke(this.listeners['*'], arguments);
|
|
}
|
|
};
|
|
|
|
Observable.prototype.invoke = function (listeners, params) {
|
|
for (var i = 0, len = listeners.length; i < len; i++) {
|
|
listeners[i].apply(this, params);
|
|
}
|
|
};
|
|
|
|
Utils.Observable = Observable;
|
|
|
|
Utils.generateChars = function (length) {
|
|
var chars = '';
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var randomChar = Math.floor(Math.random() * 36);
|
|
chars += randomChar.toString(36);
|
|
}
|
|
|
|
return chars;
|
|
};
|
|
|
|
Utils.bind = function (func, context) {
|
|
return function () {
|
|
func.apply(context, arguments);
|
|
};
|
|
};
|
|
|
|
Utils._convertData = function (data) {
|
|
for (var originalKey in data) {
|
|
var keys = originalKey.split('-');
|
|
|
|
var dataLevel = data;
|
|
|
|
if (keys.length === 1) {
|
|
continue;
|
|
}
|
|
|
|
for (var k = 0; k < keys.length; k++) {
|
|
var key = keys[k];
|
|
|
|
// Lowercase the first letter
|
|
// By default, dash-separated becomes camelCase
|
|
key = key.substring(0, 1).toLowerCase() + key.substring(1);
|
|
|
|
if (!(key in dataLevel)) {
|
|
dataLevel[key] = {};
|
|
}
|
|
|
|
if (k == keys.length - 1) {
|
|
dataLevel[key] = data[originalKey];
|
|
}
|
|
|
|
dataLevel = dataLevel[key];
|
|
}
|
|
|
|
delete data[originalKey];
|
|
}
|
|
|
|
return data;
|
|
};
|
|
|
|
Utils.hasScroll = function (index, el) {
|
|
// Adapted from the function created by @ShadowScripter
|
|
// and adapted by @BillBarry on the Stack Exchange Code Review website.
|
|
// The original code can be found at
|
|
// http://codereview.stackexchange.com/q/13338
|
|
// and was designed to be used with the Sizzle selector engine.
|
|
|
|
var $el = $(el);
|
|
var overflowX = el.style.overflowX;
|
|
var overflowY = el.style.overflowY;
|
|
|
|
//Check both x and y declarations
|
|
if (overflowX === overflowY &&
|
|
(overflowY === 'hidden' || overflowY === 'visible')) {
|
|
return false;
|
|
}
|
|
|
|
if (overflowX === 'scroll' || overflowY === 'scroll') {
|
|
return true;
|
|
}
|
|
|
|
return ($el.innerHeight() < el.scrollHeight ||
|
|
$el.innerWidth() < el.scrollWidth);
|
|
};
|
|
|
|
Utils.escapeMarkup = function (markup) {
|
|
var replaceMap = {
|
|
'\\': '\',
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
'\'': ''',
|
|
'/': '/'
|
|
};
|
|
|
|
// Do not try to escape the markup if it's not a string
|
|
if (typeof markup !== 'string') {
|
|
return markup;
|
|
}
|
|
|
|
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
|
|
return replaceMap[match];
|
|
});
|
|
};
|
|
|
|
// Append an array of jQuery nodes to a given element.
|
|
Utils.appendMany = function ($element, $nodes) {
|
|
// jQuery 1.7.x does not support $.fn.append() with an array
|
|
// Fall back to a jQuery object collection using $.fn.add()
|
|
if ($.fn.jquery.substr(0, 3) === '1.7') {
|
|
var $jqNodes = $();
|
|
|
|
$.map($nodes, function (node) {
|
|
$jqNodes = $jqNodes.add(node);
|
|
});
|
|
|
|
$nodes = $jqNodes;
|
|
}
|
|
|
|
$element.append($nodes);
|
|
};
|
|
|
|
return Utils;
|
|
});
|
|
|
|
S2.define('select2/results',[
|
|
'jquery',
|
|
'./utils'
|
|
], function ($, Utils) {
|
|
function Results ($element, options, dataAdapter) {
|
|
this.$element = $element;
|
|
this.data = dataAdapter;
|
|
this.options = options;
|
|
|
|
Results.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(Results, Utils.Observable);
|
|
|
|
Results.prototype.render = function () {
|
|
var $results = $(
|
|
'<ul class="select2-results__options" role="tree"></ul>'
|
|
);
|
|
|
|
if (this.options.get('multiple')) {
|
|
$results.attr('aria-multiselectable', 'true');
|
|
}
|
|
|
|
this.$results = $results;
|
|
|
|
return $results;
|
|
};
|
|
|
|
Results.prototype.clear = function () {
|
|
this.$results.empty();
|
|
};
|
|
|
|
Results.prototype.displayMessage = function (params) {
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
this.clear();
|
|
this.hideLoading();
|
|
|
|
var $message = $(
|
|
'<li role="treeitem" aria-live="assertive"' +
|
|
' class="select2-results__option"></li>'
|
|
);
|
|
|
|
var message = this.options.get('translations').get(params.message);
|
|
|
|
$message.append(
|
|
escapeMarkup(
|
|
message(params.args)
|
|
)
|
|
);
|
|
|
|
$message[0].className += ' select2-results__message';
|
|
|
|
this.$results.append($message);
|
|
};
|
|
|
|
Results.prototype.hideMessages = function () {
|
|
this.$results.find('.select2-results__message').remove();
|
|
};
|
|
|
|
Results.prototype.append = function (data) {
|
|
this.hideLoading();
|
|
|
|
var $options = [];
|
|
|
|
if (data.results == null || data.results.length === 0) {
|
|
if (this.$results.children().length === 0) {
|
|
this.trigger('results:message', {
|
|
message: 'noResults'
|
|
});
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
data.results = this.sort(data.results);
|
|
|
|
for (var d = 0; d < data.results.length; d++) {
|
|
var item = data.results[d];
|
|
|
|
var $option = this.option(item);
|
|
|
|
$options.push($option);
|
|
}
|
|
|
|
this.$results.append($options);
|
|
};
|
|
|
|
Results.prototype.position = function ($results, $dropdown) {
|
|
var $resultsContainer = $dropdown.find('.select2-results');
|
|
$resultsContainer.append($results);
|
|
};
|
|
|
|
Results.prototype.sort = function (data) {
|
|
var sorter = this.options.get('sorter');
|
|
|
|
return sorter(data);
|
|
};
|
|
|
|
Results.prototype.highlightFirstItem = function () {
|
|
var $options = this.$results
|
|
.find('.select2-results__option[aria-selected]');
|
|
|
|
var $selected = $options.filter('[aria-selected=true]');
|
|
|
|
// Check if there are any selected options
|
|
if ($selected.length > 0) {
|
|
// If there are selected options, highlight the first
|
|
$selected.first().trigger('mouseenter');
|
|
} else {
|
|
// If there are no selected options, highlight the first option
|
|
// in the dropdown
|
|
$options.first().trigger('mouseenter');
|
|
}
|
|
|
|
this.ensureHighlightVisible();
|
|
};
|
|
|
|
Results.prototype.setClasses = function () {
|
|
var self = this;
|
|
|
|
this.data.current(function (selected) {
|
|
var selectedIds = $.map(selected, function (s) {
|
|
return s.id.toString();
|
|
});
|
|
|
|
var $options = self.$results
|
|
.find('.select2-results__option[aria-selected]');
|
|
|
|
$options.each(function () {
|
|
var $option = $(this);
|
|
|
|
var item = $.data(this, 'data');
|
|
|
|
// id needs to be converted to a string when comparing
|
|
var id = '' + item.id;
|
|
|
|
if ((item.element != null && item.element.selected) ||
|
|
(item.element == null && $.inArray(id, selectedIds) > -1)) {
|
|
$option.attr('aria-selected', 'true');
|
|
} else {
|
|
$option.attr('aria-selected', 'false');
|
|
}
|
|
});
|
|
|
|
});
|
|
};
|
|
|
|
Results.prototype.showLoading = function (params) {
|
|
this.hideLoading();
|
|
|
|
var loadingMore = this.options.get('translations').get('searching');
|
|
|
|
var loading = {
|
|
disabled: true,
|
|
loading: true,
|
|
text: loadingMore(params)
|
|
};
|
|
var $loading = this.option(loading);
|
|
$loading.className += ' loading-results';
|
|
|
|
this.$results.prepend($loading);
|
|
};
|
|
|
|
Results.prototype.hideLoading = function () {
|
|
this.$results.find('.loading-results').remove();
|
|
};
|
|
|
|
Results.prototype.option = function (data) {
|
|
var option = document.createElement('li');
|
|
option.className = 'select2-results__option';
|
|
|
|
var attrs = {
|
|
'role': 'treeitem',
|
|
'aria-selected': 'false'
|
|
};
|
|
|
|
if (data.disabled) {
|
|
delete attrs['aria-selected'];
|
|
attrs['aria-disabled'] = 'true';
|
|
}
|
|
|
|
if (data.id == null) {
|
|
delete attrs['aria-selected'];
|
|
}
|
|
|
|
if (data._resultId != null) {
|
|
option.id = data._resultId;
|
|
}
|
|
|
|
if (data.title) {
|
|
option.title = data.title;
|
|
}
|
|
|
|
if (data.children) {
|
|
attrs.role = 'group';
|
|
attrs['aria-label'] = data.text;
|
|
delete attrs['aria-selected'];
|
|
}
|
|
|
|
for (var attr in attrs) {
|
|
var val = attrs[attr];
|
|
|
|
option.setAttribute(attr, val);
|
|
}
|
|
|
|
if (data.children) {
|
|
var $option = $(option);
|
|
|
|
var label = document.createElement('strong');
|
|
label.className = 'select2-results__group';
|
|
|
|
var $label = $(label);
|
|
this.template(data, label);
|
|
|
|
var $children = [];
|
|
|
|
for (var c = 0; c < data.children.length; c++) {
|
|
var child = data.children[c];
|
|
|
|
var $child = this.option(child);
|
|
|
|
$children.push($child);
|
|
}
|
|
|
|
var $childrenContainer = $('<ul></ul>', {
|
|
'class': 'select2-results__options select2-results__options--nested'
|
|
});
|
|
|
|
$childrenContainer.append($children);
|
|
|
|
$option.append(label);
|
|
$option.append($childrenContainer);
|
|
} else {
|
|
this.template(data, option);
|
|
}
|
|
|
|
$.data(option, 'data', data);
|
|
|
|
return option;
|
|
};
|
|
|
|
Results.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
var id = container.id + '-results';
|
|
|
|
this.$results.attr('id', id);
|
|
|
|
container.on('results:all', function (params) {
|
|
self.clear();
|
|
self.append(params.data);
|
|
|
|
if (container.isOpen()) {
|
|
self.setClasses();
|
|
self.highlightFirstItem();
|
|
}
|
|
});
|
|
|
|
container.on('results:append', function (params) {
|
|
self.append(params.data);
|
|
|
|
if (container.isOpen()) {
|
|
self.setClasses();
|
|
}
|
|
});
|
|
|
|
container.on('query', function (params) {
|
|
self.hideMessages();
|
|
self.showLoading(params);
|
|
});
|
|
|
|
container.on('select', function () {
|
|
if (!container.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
self.setClasses();
|
|
self.highlightFirstItem();
|
|
});
|
|
|
|
container.on('unselect', function () {
|
|
if (!container.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
self.setClasses();
|
|
self.highlightFirstItem();
|
|
});
|
|
|
|
container.on('open', function () {
|
|
// When the dropdown is open, aria-expended="true"
|
|
self.$results.attr('aria-expanded', 'true');
|
|
self.$results.attr('aria-hidden', 'false');
|
|
|
|
self.setClasses();
|
|
self.ensureHighlightVisible();
|
|
});
|
|
|
|
container.on('close', function () {
|
|
// When the dropdown is closed, aria-expended="false"
|
|
self.$results.attr('aria-expanded', 'false');
|
|
self.$results.attr('aria-hidden', 'true');
|
|
self.$results.removeAttr('aria-activedescendant');
|
|
});
|
|
|
|
container.on('results:toggle', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
if ($highlighted.length === 0) {
|
|
return;
|
|
}
|
|
|
|
$highlighted.trigger('mouseup');
|
|
});
|
|
|
|
container.on('results:select', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
if ($highlighted.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var data = $highlighted.data('data');
|
|
|
|
if ($highlighted.attr('aria-selected') == 'true') {
|
|
self.trigger('close', {});
|
|
} else {
|
|
self.trigger('select', {
|
|
data: data
|
|
});
|
|
}
|
|
});
|
|
|
|
container.on('results:previous', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
var $options = self.$results.find('[aria-selected]');
|
|
|
|
var currentIndex = $options.index($highlighted);
|
|
|
|
// If we are already at te top, don't move further
|
|
if (currentIndex === 0) {
|
|
return;
|
|
}
|
|
|
|
var nextIndex = currentIndex - 1;
|
|
|
|
// If none are highlighted, highlight the first
|
|
if ($highlighted.length === 0) {
|
|
nextIndex = 0;
|
|
}
|
|
|
|
var $next = $options.eq(nextIndex);
|
|
|
|
$next.trigger('mouseenter');
|
|
|
|
var currentOffset = self.$results.offset().top;
|
|
var nextTop = $next.offset().top;
|
|
var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
|
|
|
|
if (nextIndex === 0) {
|
|
self.$results.scrollTop(0);
|
|
} else if (nextTop - currentOffset < 0) {
|
|
self.$results.scrollTop(nextOffset);
|
|
}
|
|
});
|
|
|
|
container.on('results:next', function () {
|
|
var $highlighted = self.getHighlightedResults();
|
|
|
|
var $options = self.$results.find('[aria-selected]');
|
|
|
|
var currentIndex = $options.index($highlighted);
|
|
|
|
var nextIndex = currentIndex + 1;
|
|
|
|
// If we are at the last option, stay there
|
|
if (nextIndex >= $options.length) {
|
|
return;
|
|
}
|
|
|
|
var $next = $options.eq(nextIndex);
|
|
|
|
$next.trigger('mouseenter');
|
|
|
|
var currentOffset = self.$results.offset().top +
|
|
self.$results.outerHeight(false);
|
|
var nextBottom = $next.offset().top + $next.outerHeight(false);
|
|
var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
|
|
|
|
if (nextIndex === 0) {
|
|
self.$results.scrollTop(0);
|
|
} else if (nextBottom > currentOffset) {
|
|
self.$results.scrollTop(nextOffset);
|
|
}
|
|
});
|
|
|
|
container.on('results:focus', function (params) {
|
|
params.element.addClass('select2-results__option--highlighted');
|
|
});
|
|
|
|
container.on('results:message', function (params) {
|
|
self.displayMessage(params);
|
|
});
|
|
|
|
if ($.fn.mousewheel) {
|
|
this.$results.on('mousewheel', function (e) {
|
|
var top = self.$results.scrollTop();
|
|
|
|
var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
|
|
|
|
var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
|
|
var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
|
|
|
|
if (isAtTop) {
|
|
self.$results.scrollTop(0);
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
} else if (isAtBottom) {
|
|
self.$results.scrollTop(
|
|
self.$results.get(0).scrollHeight - self.$results.height()
|
|
);
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
});
|
|
}
|
|
|
|
this.$results.on('mouseup', '.select2-results__option[aria-selected]',
|
|
function (evt) {
|
|
var $this = $(this);
|
|
|
|
var data = $this.data('data');
|
|
|
|
if ($this.attr('aria-selected') === 'true') {
|
|
if (self.options.get('multiple')) {
|
|
self.trigger('unselect', {
|
|
originalEvent: evt,
|
|
data: data
|
|
});
|
|
} else {
|
|
self.trigger('close', {});
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
self.trigger('select', {
|
|
originalEvent: evt,
|
|
data: data
|
|
});
|
|
});
|
|
|
|
this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
|
|
function (evt) {
|
|
var data = $(this).data('data');
|
|
|
|
self.getHighlightedResults()
|
|
.removeClass('select2-results__option--highlighted');
|
|
|
|
self.trigger('results:focus', {
|
|
data: data,
|
|
element: $(this)
|
|
});
|
|
});
|
|
};
|
|
|
|
Results.prototype.getHighlightedResults = function () {
|
|
var $highlighted = this.$results
|
|
.find('.select2-results__option--highlighted');
|
|
|
|
return $highlighted;
|
|
};
|
|
|
|
Results.prototype.destroy = function () {
|
|
this.$results.remove();
|
|
};
|
|
|
|
Results.prototype.ensureHighlightVisible = function () {
|
|
var $highlighted = this.getHighlightedResults();
|
|
|
|
if ($highlighted.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var $options = this.$results.find('[aria-selected]');
|
|
|
|
var currentIndex = $options.index($highlighted);
|
|
|
|
var currentOffset = this.$results.offset().top;
|
|
var nextTop = $highlighted.offset().top;
|
|
var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
|
|
|
|
var offsetDelta = nextTop - currentOffset;
|
|
nextOffset -= $highlighted.outerHeight(false) * 2;
|
|
|
|
if (currentIndex <= 2) {
|
|
this.$results.scrollTop(0);
|
|
} else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
|
|
this.$results.scrollTop(nextOffset);
|
|
}
|
|
};
|
|
|
|
Results.prototype.template = function (result, container) {
|
|
var template = this.options.get('templateResult');
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
var content = template(result, container);
|
|
|
|
if (content == null) {
|
|
container.style.display = 'none';
|
|
} else if (typeof content === 'string') {
|
|
container.innerHTML = escapeMarkup(content);
|
|
} else {
|
|
$(container).append(content);
|
|
}
|
|
};
|
|
|
|
return Results;
|
|
});
|
|
|
|
S2.define('select2/keys',[
|
|
|
|
], function () {
|
|
var KEYS = {
|
|
BACKSPACE: 8,
|
|
TAB: 9,
|
|
ENTER: 13,
|
|
SHIFT: 16,
|
|
CTRL: 17,
|
|
ALT: 18,
|
|
ESC: 27,
|
|
SPACE: 32,
|
|
PAGE_UP: 33,
|
|
PAGE_DOWN: 34,
|
|
END: 35,
|
|
HOME: 36,
|
|
LEFT: 37,
|
|
UP: 38,
|
|
RIGHT: 39,
|
|
DOWN: 40,
|
|
DELETE: 46
|
|
};
|
|
|
|
return KEYS;
|
|
});
|
|
|
|
S2.define('select2/selection/base',[
|
|
'jquery',
|
|
'../utils',
|
|
'../keys'
|
|
], function ($, Utils, KEYS) {
|
|
function BaseSelection ($element, options) {
|
|
this.$element = $element;
|
|
this.options = options;
|
|
|
|
BaseSelection.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(BaseSelection, Utils.Observable);
|
|
|
|
BaseSelection.prototype.render = function () {
|
|
var $selection = $(
|
|
'<span class="select2-selection" role="combobox" ' +
|
|
' aria-haspopup="true" aria-expanded="false">' +
|
|
'</span>'
|
|
);
|
|
|
|
this._tabindex = 0;
|
|
|
|
if (this.$element.data('old-tabindex') != null) {
|
|
this._tabindex = this.$element.data('old-tabindex');
|
|
} else if (this.$element.attr('tabindex') != null) {
|
|
this._tabindex = this.$element.attr('tabindex');
|
|
}
|
|
|
|
$selection.attr('title', this.$element.attr('title'));
|
|
$selection.attr('tabindex', this._tabindex);
|
|
|
|
this.$selection = $selection;
|
|
|
|
return $selection;
|
|
};
|
|
|
|
BaseSelection.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
var id = container.id + '-container';
|
|
var resultsId = container.id + '-results';
|
|
|
|
this.container = container;
|
|
|
|
this.$selection.on('focus', function (evt) {
|
|
self.trigger('focus', evt);
|
|
});
|
|
|
|
this.$selection.on('blur', function (evt) {
|
|
self._handleBlur(evt);
|
|
});
|
|
|
|
this.$selection.on('keydown', function (evt) {
|
|
self.trigger('keypress', evt);
|
|
|
|
if (evt.which === KEYS.SPACE) {
|
|
evt.preventDefault();
|
|
}
|
|
});
|
|
|
|
container.on('results:focus', function (params) {
|
|
self.$selection.attr('aria-activedescendant', params.data._resultId);
|
|
});
|
|
|
|
container.on('selection:update', function (params) {
|
|
self.update(params.data);
|
|
});
|
|
|
|
container.on('open', function () {
|
|
// When the dropdown is open, aria-expanded="true"
|
|
self.$selection.attr('aria-expanded', 'true');
|
|
self.$selection.attr('aria-owns', resultsId);
|
|
|
|
self._attachCloseHandler(container);
|
|
});
|
|
|
|
container.on('close', function () {
|
|
// When the dropdown is closed, aria-expanded="false"
|
|
self.$selection.attr('aria-expanded', 'false');
|
|
self.$selection.removeAttr('aria-activedescendant');
|
|
self.$selection.removeAttr('aria-owns');
|
|
|
|
self.$selection.focus();
|
|
|
|
self._detachCloseHandler(container);
|
|
});
|
|
|
|
container.on('enable', function () {
|
|
self.$selection.attr('tabindex', self._tabindex);
|
|
});
|
|
|
|
container.on('disable', function () {
|
|
self.$selection.attr('tabindex', '-1');
|
|
});
|
|
};
|
|
|
|
BaseSelection.prototype._handleBlur = function (evt) {
|
|
var self = this;
|
|
|
|
// This needs to be delayed as the active element is the body when the tab
|
|
// key is pressed, possibly along with others.
|
|
window.setTimeout(function () {
|
|
// Don't trigger `blur` if the focus is still in the selection
|
|
if (
|
|
(document.activeElement == self.$selection[0]) ||
|
|
($.contains(self.$selection[0], document.activeElement))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
self.trigger('blur', evt);
|
|
}, 1);
|
|
};
|
|
|
|
BaseSelection.prototype._attachCloseHandler = function (container) {
|
|
var self = this;
|
|
|
|
$(document.body).on('mousedown.select2.' + container.id, function (e) {
|
|
var $target = $(e.target);
|
|
|
|
var $select = $target.closest('.select2');
|
|
|
|
var $all = $('.select2.select2-container--open');
|
|
|
|
$all.each(function () {
|
|
var $this = $(this);
|
|
|
|
if (this == $select[0]) {
|
|
return;
|
|
}
|
|
|
|
var $element = $this.data('element');
|
|
|
|
$element.select2('close');
|
|
});
|
|
});
|
|
};
|
|
|
|
BaseSelection.prototype._detachCloseHandler = function (container) {
|
|
$(document.body).off('mousedown.select2.' + container.id);
|
|
};
|
|
|
|
BaseSelection.prototype.position = function ($selection, $container) {
|
|
var $selectionContainer = $container.find('.selection');
|
|
$selectionContainer.append($selection);
|
|
};
|
|
|
|
BaseSelection.prototype.destroy = function () {
|
|
this._detachCloseHandler(this.container);
|
|
};
|
|
|
|
BaseSelection.prototype.update = function (data) {
|
|
throw new Error('The `update` method must be defined in child classes.');
|
|
};
|
|
|
|
return BaseSelection;
|
|
});
|
|
|
|
S2.define('select2/selection/single',[
|
|
'jquery',
|
|
'./base',
|
|
'../utils',
|
|
'../keys'
|
|
], function ($, BaseSelection, Utils, KEYS) {
|
|
function SingleSelection () {
|
|
SingleSelection.__super__.constructor.apply(this, arguments);
|
|
}
|
|
|
|
Utils.Extend(SingleSelection, BaseSelection);
|
|
|
|
SingleSelection.prototype.render = function () {
|
|
var $selection = SingleSelection.__super__.render.call(this);
|
|
|
|
$selection.addClass('select2-selection--single');
|
|
|
|
$selection.html(
|
|
'<span class="select2-selection__rendered"></span>' +
|
|
'<span class="select2-selection__arrow" role="presentation">' +
|
|
'<b role="presentation"></b>' +
|
|
'</span>'
|
|
);
|
|
|
|
return $selection;
|
|
};
|
|
|
|
SingleSelection.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
SingleSelection.__super__.bind.apply(this, arguments);
|
|
|
|
var id = container.id + '-container';
|
|
|
|
this.$selection.find('.select2-selection__rendered').attr('id', id);
|
|
this.$selection.attr('aria-labelledby', id);
|
|
|
|
this.$selection.on('mousedown', function (evt) {
|
|
// Only respond to left clicks
|
|
if (evt.which !== 1) {
|
|
return;
|
|
}
|
|
|
|
self.trigger('toggle', {
|
|
originalEvent: evt
|
|
});
|
|
});
|
|
|
|
this.$selection.on('focus', function (evt) {
|
|
// User focuses on the container
|
|
});
|
|
|
|
this.$selection.on('blur', function (evt) {
|
|
// User exits the container
|
|
});
|
|
|
|
container.on('focus', function (evt) {
|
|
if (!container.isOpen()) {
|
|
self.$selection.focus();
|
|
}
|
|
});
|
|
|
|
container.on('selection:update', function (params) {
|
|
self.update(params.data);
|
|
});
|
|
};
|
|
|
|
SingleSelection.prototype.clear = function () {
|
|
this.$selection.find('.select2-selection__rendered').empty();
|
|
};
|
|
|
|
SingleSelection.prototype.display = function (data, container) {
|
|
var template = this.options.get('templateSelection');
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
return escapeMarkup(template(data, container));
|
|
};
|
|
|
|
SingleSelection.prototype.selectionContainer = function () {
|
|
return $('<span></span>');
|
|
};
|
|
|
|
SingleSelection.prototype.update = function (data) {
|
|
if (data.length === 0) {
|
|
this.clear();
|
|
return;
|
|
}
|
|
|
|
var selection = data[0];
|
|
|
|
var $rendered = this.$selection.find('.select2-selection__rendered');
|
|
var formatted = this.display(selection, $rendered);
|
|
|
|
$rendered.empty().append(formatted);
|
|
$rendered.prop('title', selection.title || selection.text);
|
|
};
|
|
|
|
return SingleSelection;
|
|
});
|
|
|
|
S2.define('select2/selection/multiple',[
|
|
'jquery',
|
|
'./base',
|
|
'../utils'
|
|
], function ($, BaseSelection, Utils) {
|
|
function MultipleSelection ($element, options) {
|
|
MultipleSelection.__super__.constructor.apply(this, arguments);
|
|
}
|
|
|
|
Utils.Extend(MultipleSelection, BaseSelection);
|
|
|
|
MultipleSelection.prototype.render = function () {
|
|
var $selection = MultipleSelection.__super__.render.call(this);
|
|
|
|
$selection.addClass('select2-selection--multiple');
|
|
|
|
$selection.html(
|
|
'<ul class="select2-selection__rendered"></ul>'
|
|
);
|
|
|
|
return $selection;
|
|
};
|
|
|
|
MultipleSelection.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
MultipleSelection.__super__.bind.apply(this, arguments);
|
|
|
|
this.$selection.on('click', function (evt) {
|
|
self.trigger('toggle', {
|
|
originalEvent: evt
|
|
});
|
|
});
|
|
|
|
this.$selection.on(
|
|
'click',
|
|
'.select2-selection__choice__remove',
|
|
function (evt) {
|
|
// Ignore the event if it is disabled
|
|
if (self.options.get('disabled')) {
|
|
return;
|
|
}
|
|
|
|
var $remove = $(this);
|
|
var $selection = $remove.parent();
|
|
|
|
var data = $selection.data('data');
|
|
|
|
self.trigger('unselect', {
|
|
originalEvent: evt,
|
|
data: data
|
|
});
|
|
}
|
|
);
|
|
};
|
|
|
|
MultipleSelection.prototype.clear = function () {
|
|
this.$selection.find('.select2-selection__rendered').empty();
|
|
};
|
|
|
|
MultipleSelection.prototype.display = function (data, container) {
|
|
var template = this.options.get('templateSelection');
|
|
var escapeMarkup = this.options.get('escapeMarkup');
|
|
|
|
return escapeMarkup(template(data, container));
|
|
};
|
|
|
|
MultipleSelection.prototype.selectionContainer = function () {
|
|
var $container = $(
|
|
'<li class="select2-selection__choice">' +
|
|
'<span class="select2-selection__choice__remove" role="presentation">' +
|
|
'×' +
|
|
'</span>' +
|
|
'</li>'
|
|
);
|
|
|
|
return $container;
|
|
};
|
|
|
|
MultipleSelection.prototype.update = function (data) {
|
|
this.clear();
|
|
|
|
if (data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var $selections = [];
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var selection = data[d];
|
|
|
|
var $selection = this.selectionContainer();
|
|
var formatted = this.display(selection, $selection);
|
|
|
|
$selection.append(formatted);
|
|
$selection.prop('title', selection.title || selection.text);
|
|
|
|
$selection.data('data', selection);
|
|
|
|
$selections.push($selection);
|
|
}
|
|
|
|
var $rendered = this.$selection.find('.select2-selection__rendered');
|
|
|
|
Utils.appendMany($rendered, $selections);
|
|
};
|
|
|
|
return MultipleSelection;
|
|
});
|
|
|
|
S2.define('select2/selection/placeholder',[
|
|
'../utils'
|
|
], function (Utils) {
|
|
function Placeholder (decorated, $element, options) {
|
|
this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
|
|
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
|
|
if (typeof placeholder === 'string') {
|
|
placeholder = {
|
|
id: '',
|
|
text: placeholder
|
|
};
|
|
}
|
|
|
|
return placeholder;
|
|
};
|
|
|
|
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
|
|
var $placeholder = this.selectionContainer();
|
|
|
|
$placeholder.html(this.display(placeholder));
|
|
$placeholder.addClass('select2-selection__placeholder')
|
|
.removeClass('select2-selection__choice');
|
|
|
|
return $placeholder;
|
|
};
|
|
|
|
Placeholder.prototype.update = function (decorated, data) {
|
|
var singlePlaceholder = (
|
|
data.length == 1 && data[0].id != this.placeholder.id
|
|
);
|
|
var multipleSelections = data.length > 1;
|
|
|
|
if (multipleSelections || singlePlaceholder) {
|
|
return decorated.call(this, data);
|
|
}
|
|
|
|
this.clear();
|
|
|
|
var $placeholder = this.createPlaceholder(this.placeholder);
|
|
|
|
this.$selection.find('.select2-selection__rendered').append($placeholder);
|
|
};
|
|
|
|
return Placeholder;
|
|
});
|
|
|
|
S2.define('select2/selection/allowClear',[
|
|
'jquery',
|
|
'../keys'
|
|
], function ($, KEYS) {
|
|
function AllowClear () { }
|
|
|
|
AllowClear.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
if (this.placeholder == null) {
|
|
if (this.options.get('debug') && window.console && console.error) {
|
|
console.error(
|
|
'Select2: The `allowClear` option should be used in combination ' +
|
|
'with the `placeholder` option.'
|
|
);
|
|
}
|
|
}
|
|
|
|
this.$selection.on('mousedown', '.select2-selection__clear',
|
|
function (evt) {
|
|
self._handleClear(evt);
|
|
});
|
|
|
|
container.on('keypress', function (evt) {
|
|
self._handleKeyboardClear(evt, container);
|
|
});
|
|
};
|
|
|
|
AllowClear.prototype._handleClear = function (_, evt) {
|
|
// Ignore the event if it is disabled
|
|
if (this.options.get('disabled')) {
|
|
return;
|
|
}
|
|
|
|
var $clear = this.$selection.find('.select2-selection__clear');
|
|
|
|
// Ignore the event if nothing has been selected
|
|
if ($clear.length === 0) {
|
|
return;
|
|
}
|
|
|
|
evt.stopPropagation();
|
|
|
|
var data = $clear.data('data');
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var unselectData = {
|
|
data: data[d]
|
|
};
|
|
|
|
// Trigger the `unselect` event, so people can prevent it from being
|
|
// cleared.
|
|
this.trigger('unselect', unselectData);
|
|
|
|
// If the event was prevented, don't clear it out.
|
|
if (unselectData.prevented) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.$element.val(this.placeholder.id).trigger('change');
|
|
|
|
this.trigger('toggle', {});
|
|
};
|
|
|
|
AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
|
|
if (container.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
|
|
this._handleClear(evt);
|
|
}
|
|
};
|
|
|
|
AllowClear.prototype.update = function (decorated, data) {
|
|
decorated.call(this, data);
|
|
|
|
if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
|
|
data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var $remove = $(
|
|
'<span class="select2-selection__clear">' +
|
|
'×' +
|
|
'</span>'
|
|
);
|
|
$remove.data('data', data);
|
|
|
|
this.$selection.find('.select2-selection__rendered').prepend($remove);
|
|
};
|
|
|
|
return AllowClear;
|
|
});
|
|
|
|
S2.define('select2/selection/search',[
|
|
'jquery',
|
|
'../utils',
|
|
'../keys'
|
|
], function ($, Utils, KEYS) {
|
|
function Search (decorated, $element, options) {
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
Search.prototype.render = function (decorated) {
|
|
var $search = $(
|
|
'<li class="select2-search select2-search--inline">' +
|
|
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
|
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
|
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
|
|
'</li>'
|
|
);
|
|
|
|
this.$searchContainer = $search;
|
|
this.$search = $search.find('input');
|
|
|
|
var $rendered = decorated.call(this);
|
|
|
|
this._transferTabIndex();
|
|
|
|
return $rendered;
|
|
};
|
|
|
|
Search.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('open', function () {
|
|
self.$search.trigger('focus');
|
|
});
|
|
|
|
container.on('close', function () {
|
|
self.$search.val('');
|
|
self.$search.removeAttr('aria-activedescendant');
|
|
self.$search.trigger('focus');
|
|
});
|
|
|
|
container.on('enable', function () {
|
|
self.$search.prop('disabled', false);
|
|
|
|
self._transferTabIndex();
|
|
});
|
|
|
|
container.on('disable', function () {
|
|
self.$search.prop('disabled', true);
|
|
});
|
|
|
|
container.on('focus', function (evt) {
|
|
self.$search.trigger('focus');
|
|
});
|
|
|
|
container.on('results:focus', function (params) {
|
|
self.$search.attr('aria-activedescendant', params.id);
|
|
});
|
|
|
|
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
|
|
self.trigger('focus', evt);
|
|
});
|
|
|
|
this.$selection.on('focusout', '.select2-search--inline', function (evt) {
|
|
self._handleBlur(evt);
|
|
});
|
|
|
|
this.$selection.on('keydown', '.select2-search--inline', function (evt) {
|
|
evt.stopPropagation();
|
|
|
|
self.trigger('keypress', evt);
|
|
|
|
self._keyUpPrevented = evt.isDefaultPrevented();
|
|
|
|
var key = evt.which;
|
|
|
|
if (key === KEYS.BACKSPACE && self.$search.val() === '') {
|
|
var $previousChoice = self.$searchContainer
|
|
.prev('.select2-selection__choice');
|
|
|
|
if ($previousChoice.length > 0) {
|
|
var item = $previousChoice.data('data');
|
|
|
|
self.searchRemoveChoice(item);
|
|
|
|
evt.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Try to detect the IE version should the `documentMode` property that
|
|
// is stored on the document. This is only implemented in IE and is
|
|
// slightly cleaner than doing a user agent check.
|
|
// This property is not available in Edge, but Edge also doesn't have
|
|
// this bug.
|
|
var msie = document.documentMode;
|
|
var disableInputEvents = msie && msie <= 11;
|
|
|
|
// Workaround for browsers which do not support the `input` event
|
|
// This will prevent double-triggering of events for browsers which support
|
|
// both the `keyup` and `input` events.
|
|
this.$selection.on(
|
|
'input.searchcheck',
|
|
'.select2-search--inline',
|
|
function (evt) {
|
|
// IE will trigger the `input` event when a placeholder is used on a
|
|
// search box. To get around this issue, we are forced to ignore all
|
|
// `input` events in IE and keep using `keyup`.
|
|
if (disableInputEvents) {
|
|
self.$selection.off('input.search input.searchcheck');
|
|
return;
|
|
}
|
|
|
|
// Unbind the duplicated `keyup` event
|
|
self.$selection.off('keyup.search');
|
|
}
|
|
);
|
|
|
|
this.$selection.on(
|
|
'keyup.search input.search',
|
|
'.select2-search--inline',
|
|
function (evt) {
|
|
// IE will trigger the `input` event when a placeholder is used on a
|
|
// search box. To get around this issue, we are forced to ignore all
|
|
// `input` events in IE and keep using `keyup`.
|
|
if (disableInputEvents && evt.type === 'input') {
|
|
self.$selection.off('input.search input.searchcheck');
|
|
return;
|
|
}
|
|
|
|
var key = evt.which;
|
|
|
|
// We can freely ignore events from modifier keys
|
|
if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
|
|
return;
|
|
}
|
|
|
|
// Tabbing will be handled during the `keydown` phase
|
|
if (key == KEYS.TAB) {
|
|
return;
|
|
}
|
|
|
|
self.handleSearch(evt);
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* This method will transfer the tabindex attribute from the rendered
|
|
* selection to the search box. This allows for the search box to be used as
|
|
* the primary focus instead of the selection container.
|
|
*
|
|
* @private
|
|
*/
|
|
Search.prototype._transferTabIndex = function (decorated) {
|
|
this.$search.attr('tabindex', this.$selection.attr('tabindex'));
|
|
this.$selection.attr('tabindex', '-1');
|
|
};
|
|
|
|
Search.prototype.createPlaceholder = function (decorated, placeholder) {
|
|
this.$search.attr('placeholder', placeholder.text);
|
|
};
|
|
|
|
Search.prototype.update = function (decorated, data) {
|
|
var searchHadFocus = this.$search[0] == document.activeElement;
|
|
|
|
this.$search.attr('placeholder', '');
|
|
|
|
decorated.call(this, data);
|
|
|
|
this.$selection.find('.select2-selection__rendered')
|
|
.append(this.$searchContainer);
|
|
|
|
this.resizeSearch();
|
|
if (searchHadFocus) {
|
|
this.$search.focus();
|
|
}
|
|
};
|
|
|
|
Search.prototype.handleSearch = function () {
|
|
this.resizeSearch();
|
|
|
|
if (!this._keyUpPrevented) {
|
|
var input = this.$search.val();
|
|
|
|
this.trigger('query', {
|
|
term: input
|
|
});
|
|
}
|
|
|
|
this._keyUpPrevented = false;
|
|
};
|
|
|
|
Search.prototype.searchRemoveChoice = function (decorated, item) {
|
|
this.trigger('unselect', {
|
|
data: item
|
|
});
|
|
|
|
this.$search.val(item.text);
|
|
this.handleSearch();
|
|
};
|
|
|
|
Search.prototype.resizeSearch = function () {
|
|
this.$search.css('width', '25px');
|
|
|
|
var width = '';
|
|
|
|
if (this.$search.attr('placeholder') !== '') {
|
|
width = this.$selection.find('.select2-selection__rendered').innerWidth();
|
|
} else {
|
|
var minimumWidth = this.$search.val().length + 1;
|
|
|
|
width = (minimumWidth * 0.75) + 'em';
|
|
}
|
|
|
|
this.$search.css('width', width);
|
|
};
|
|
|
|
return Search;
|
|
});
|
|
|
|
S2.define('select2/selection/eventRelay',[
|
|
'jquery'
|
|
], function ($) {
|
|
function EventRelay () { }
|
|
|
|
EventRelay.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
var relayEvents = [
|
|
'open', 'opening',
|
|
'close', 'closing',
|
|
'select', 'selecting',
|
|
'unselect', 'unselecting'
|
|
];
|
|
|
|
var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('*', function (name, params) {
|
|
// Ignore events that should not be relayed
|
|
if ($.inArray(name, relayEvents) === -1) {
|
|
return;
|
|
}
|
|
|
|
// The parameters should always be an object
|
|
params = params || {};
|
|
|
|
// Generate the jQuery event for the Select2 event
|
|
var evt = $.Event('select2:' + name, {
|
|
params: params
|
|
});
|
|
|
|
self.$element.trigger(evt);
|
|
|
|
// Only handle preventable events if it was one
|
|
if ($.inArray(name, preventableEvents) === -1) {
|
|
return;
|
|
}
|
|
|
|
params.prevented = evt.isDefaultPrevented();
|
|
});
|
|
};
|
|
|
|
return EventRelay;
|
|
});
|
|
|
|
S2.define('select2/translation',[
|
|
'jquery',
|
|
'require'
|
|
], function ($, require) {
|
|
function Translation (dict) {
|
|
this.dict = dict || {};
|
|
}
|
|
|
|
Translation.prototype.all = function () {
|
|
return this.dict;
|
|
};
|
|
|
|
Translation.prototype.get = function (key) {
|
|
return this.dict[key];
|
|
};
|
|
|
|
Translation.prototype.extend = function (translation) {
|
|
this.dict = $.extend({}, translation.all(), this.dict);
|
|
};
|
|
|
|
// Static functions
|
|
|
|
Translation._cache = {};
|
|
|
|
Translation.loadPath = function (path) {
|
|
if (!(path in Translation._cache)) {
|
|
var translations = require(path);
|
|
|
|
Translation._cache[path] = translations;
|
|
}
|
|
|
|
return new Translation(Translation._cache[path]);
|
|
};
|
|
|
|
return Translation;
|
|
});
|
|
|
|
S2.define('select2/diacritics',[
|
|
|
|
], function () {
|
|
var diacritics = {
|
|
'\u24B6': 'A',
|
|
'\uFF21': 'A',
|
|
'\u00C0': 'A',
|
|
'\u00C1': 'A',
|
|
'\u00C2': 'A',
|
|
'\u1EA6': 'A',
|
|
'\u1EA4': 'A',
|
|
'\u1EAA': 'A',
|
|
'\u1EA8': 'A',
|
|
'\u00C3': 'A',
|
|
'\u0100': 'A',
|
|
'\u0102': 'A',
|
|
'\u1EB0': 'A',
|
|
'\u1EAE': 'A',
|
|
'\u1EB4': 'A',
|
|
'\u1EB2': 'A',
|
|
'\u0226': 'A',
|
|
'\u01E0': 'A',
|
|
'\u00C4': 'A',
|
|
'\u01DE': 'A',
|
|
'\u1EA2': 'A',
|
|
'\u00C5': 'A',
|
|
'\u01FA': 'A',
|
|
'\u01CD': 'A',
|
|
'\u0200': 'A',
|
|
'\u0202': 'A',
|
|
'\u1EA0': 'A',
|
|
'\u1EAC': 'A',
|
|
'\u1EB6': 'A',
|
|
'\u1E00': 'A',
|
|
'\u0104': 'A',
|
|
'\u023A': 'A',
|
|
'\u2C6F': 'A',
|
|
'\uA732': 'AA',
|
|
'\u00C6': 'AE',
|
|
'\u01FC': 'AE',
|
|
'\u01E2': 'AE',
|
|
'\uA734': 'AO',
|
|
'\uA736': 'AU',
|
|
'\uA738': 'AV',
|
|
'\uA73A': 'AV',
|
|
'\uA73C': 'AY',
|
|
'\u24B7': 'B',
|
|
'\uFF22': 'B',
|
|
'\u1E02': 'B',
|
|
'\u1E04': 'B',
|
|
'\u1E06': 'B',
|
|
'\u0243': 'B',
|
|
'\u0182': 'B',
|
|
'\u0181': 'B',
|
|
'\u24B8': 'C',
|
|
'\uFF23': 'C',
|
|
'\u0106': 'C',
|
|
'\u0108': 'C',
|
|
'\u010A': 'C',
|
|
'\u010C': 'C',
|
|
'\u00C7': 'C',
|
|
'\u1E08': 'C',
|
|
'\u0187': 'C',
|
|
'\u023B': 'C',
|
|
'\uA73E': 'C',
|
|
'\u24B9': 'D',
|
|
'\uFF24': 'D',
|
|
'\u1E0A': 'D',
|
|
'\u010E': 'D',
|
|
'\u1E0C': 'D',
|
|
'\u1E10': 'D',
|
|
'\u1E12': 'D',
|
|
'\u1E0E': 'D',
|
|
'\u0110': 'D',
|
|
'\u018B': 'D',
|
|
'\u018A': 'D',
|
|
'\u0189': 'D',
|
|
'\uA779': 'D',
|
|
'\u01F1': 'DZ',
|
|
'\u01C4': 'DZ',
|
|
'\u01F2': 'Dz',
|
|
'\u01C5': 'Dz',
|
|
'\u24BA': 'E',
|
|
'\uFF25': 'E',
|
|
'\u00C8': 'E',
|
|
'\u00C9': 'E',
|
|
'\u00CA': 'E',
|
|
'\u1EC0': 'E',
|
|
'\u1EBE': 'E',
|
|
'\u1EC4': 'E',
|
|
'\u1EC2': 'E',
|
|
'\u1EBC': 'E',
|
|
'\u0112': 'E',
|
|
'\u1E14': 'E',
|
|
'\u1E16': 'E',
|
|
'\u0114': 'E',
|
|
'\u0116': 'E',
|
|
'\u00CB': 'E',
|
|
'\u1EBA': 'E',
|
|
'\u011A': 'E',
|
|
'\u0204': 'E',
|
|
'\u0206': 'E',
|
|
'\u1EB8': 'E',
|
|
'\u1EC6': 'E',
|
|
'\u0228': 'E',
|
|
'\u1E1C': 'E',
|
|
'\u0118': 'E',
|
|
'\u1E18': 'E',
|
|
'\u1E1A': 'E',
|
|
'\u0190': 'E',
|
|
'\u018E': 'E',
|
|
'\u24BB': 'F',
|
|
'\uFF26': 'F',
|
|
'\u1E1E': 'F',
|
|
'\u0191': 'F',
|
|
'\uA77B': 'F',
|
|
'\u24BC': 'G',
|
|
'\uFF27': 'G',
|
|
'\u01F4': 'G',
|
|
'\u011C': 'G',
|
|
'\u1E20': 'G',
|
|
'\u011E': 'G',
|
|
'\u0120': 'G',
|
|
'\u01E6': 'G',
|
|
'\u0122': 'G',
|
|
'\u01E4': 'G',
|
|
'\u0193': 'G',
|
|
'\uA7A0': 'G',
|
|
'\uA77D': 'G',
|
|
'\uA77E': 'G',
|
|
'\u24BD': 'H',
|
|
'\uFF28': 'H',
|
|
'\u0124': 'H',
|
|
'\u1E22': 'H',
|
|
'\u1E26': 'H',
|
|
'\u021E': 'H',
|
|
'\u1E24': 'H',
|
|
'\u1E28': 'H',
|
|
'\u1E2A': 'H',
|
|
'\u0126': 'H',
|
|
'\u2C67': 'H',
|
|
'\u2C75': 'H',
|
|
'\uA78D': 'H',
|
|
'\u24BE': 'I',
|
|
'\uFF29': 'I',
|
|
'\u00CC': 'I',
|
|
'\u00CD': 'I',
|
|
'\u00CE': 'I',
|
|
'\u0128': 'I',
|
|
'\u012A': 'I',
|
|
'\u012C': 'I',
|
|
'\u0130': 'I',
|
|
'\u00CF': 'I',
|
|
'\u1E2E': 'I',
|
|
'\u1EC8': 'I',
|
|
'\u01CF': 'I',
|
|
'\u0208': 'I',
|
|
'\u020A': 'I',
|
|
'\u1ECA': 'I',
|
|
'\u012E': 'I',
|
|
'\u1E2C': 'I',
|
|
'\u0197': 'I',
|
|
'\u24BF': 'J',
|
|
'\uFF2A': 'J',
|
|
'\u0134': 'J',
|
|
'\u0248': 'J',
|
|
'\u24C0': 'K',
|
|
'\uFF2B': 'K',
|
|
'\u1E30': 'K',
|
|
'\u01E8': 'K',
|
|
'\u1E32': 'K',
|
|
'\u0136': 'K',
|
|
'\u1E34': 'K',
|
|
'\u0198': 'K',
|
|
'\u2C69': 'K',
|
|
'\uA740': 'K',
|
|
'\uA742': 'K',
|
|
'\uA744': 'K',
|
|
'\uA7A2': 'K',
|
|
'\u24C1': 'L',
|
|
'\uFF2C': 'L',
|
|
'\u013F': 'L',
|
|
'\u0139': 'L',
|
|
'\u013D': 'L',
|
|
'\u1E36': 'L',
|
|
'\u1E38': 'L',
|
|
'\u013B': 'L',
|
|
'\u1E3C': 'L',
|
|
'\u1E3A': 'L',
|
|
'\u0141': 'L',
|
|
'\u023D': 'L',
|
|
'\u2C62': 'L',
|
|
'\u2C60': 'L',
|
|
'\uA748': 'L',
|
|
'\uA746': 'L',
|
|
'\uA780': 'L',
|
|
'\u01C7': 'LJ',
|
|
'\u01C8': 'Lj',
|
|
'\u24C2': 'M',
|
|
'\uFF2D': 'M',
|
|
'\u1E3E': 'M',
|
|
'\u1E40': 'M',
|
|
'\u1E42': 'M',
|
|
'\u2C6E': 'M',
|
|
'\u019C': 'M',
|
|
'\u24C3': 'N',
|
|
'\uFF2E': 'N',
|
|
'\u01F8': 'N',
|
|
'\u0143': 'N',
|
|
'\u00D1': 'N',
|
|
'\u1E44': 'N',
|
|
'\u0147': 'N',
|
|
'\u1E46': 'N',
|
|
'\u0145': 'N',
|
|
'\u1E4A': 'N',
|
|
'\u1E48': 'N',
|
|
'\u0220': 'N',
|
|
'\u019D': 'N',
|
|
'\uA790': 'N',
|
|
'\uA7A4': 'N',
|
|
'\u01CA': 'NJ',
|
|
'\u01CB': 'Nj',
|
|
'\u24C4': 'O',
|
|
'\uFF2F': 'O',
|
|
'\u00D2': 'O',
|
|
'\u00D3': 'O',
|
|
'\u00D4': 'O',
|
|
'\u1ED2': 'O',
|
|
'\u1ED0': 'O',
|
|
'\u1ED6': 'O',
|
|
'\u1ED4': 'O',
|
|
'\u00D5': 'O',
|
|
'\u1E4C': 'O',
|
|
'\u022C': 'O',
|
|
'\u1E4E': 'O',
|
|
'\u014C': 'O',
|
|
'\u1E50': 'O',
|
|
'\u1E52': 'O',
|
|
'\u014E': 'O',
|
|
'\u022E': 'O',
|
|
'\u0230': 'O',
|
|
'\u00D6': 'O',
|
|
'\u022A': 'O',
|
|
'\u1ECE': 'O',
|
|
'\u0150': 'O',
|
|
'\u01D1': 'O',
|
|
'\u020C': 'O',
|
|
'\u020E': 'O',
|
|
'\u01A0': 'O',
|
|
'\u1EDC': 'O',
|
|
'\u1EDA': 'O',
|
|
'\u1EE0': 'O',
|
|
'\u1EDE': 'O',
|
|
'\u1EE2': 'O',
|
|
'\u1ECC': 'O',
|
|
'\u1ED8': 'O',
|
|
'\u01EA': 'O',
|
|
'\u01EC': 'O',
|
|
'\u00D8': 'O',
|
|
'\u01FE': 'O',
|
|
'\u0186': 'O',
|
|
'\u019F': 'O',
|
|
'\uA74A': 'O',
|
|
'\uA74C': 'O',
|
|
'\u01A2': 'OI',
|
|
'\uA74E': 'OO',
|
|
'\u0222': 'OU',
|
|
'\u24C5': 'P',
|
|
'\uFF30': 'P',
|
|
'\u1E54': 'P',
|
|
'\u1E56': 'P',
|
|
'\u01A4': 'P',
|
|
'\u2C63': 'P',
|
|
'\uA750': 'P',
|
|
'\uA752': 'P',
|
|
'\uA754': 'P',
|
|
'\u24C6': 'Q',
|
|
'\uFF31': 'Q',
|
|
'\uA756': 'Q',
|
|
'\uA758': 'Q',
|
|
'\u024A': 'Q',
|
|
'\u24C7': 'R',
|
|
'\uFF32': 'R',
|
|
'\u0154': 'R',
|
|
'\u1E58': 'R',
|
|
'\u0158': 'R',
|
|
'\u0210': 'R',
|
|
'\u0212': 'R',
|
|
'\u1E5A': 'R',
|
|
'\u1E5C': 'R',
|
|
'\u0156': 'R',
|
|
'\u1E5E': 'R',
|
|
'\u024C': 'R',
|
|
'\u2C64': 'R',
|
|
'\uA75A': 'R',
|
|
'\uA7A6': 'R',
|
|
'\uA782': 'R',
|
|
'\u24C8': 'S',
|
|
'\uFF33': 'S',
|
|
'\u1E9E': 'S',
|
|
'\u015A': 'S',
|
|
'\u1E64': 'S',
|
|
'\u015C': 'S',
|
|
'\u1E60': 'S',
|
|
'\u0160': 'S',
|
|
'\u1E66': 'S',
|
|
'\u1E62': 'S',
|
|
'\u1E68': 'S',
|
|
'\u0218': 'S',
|
|
'\u015E': 'S',
|
|
'\u2C7E': 'S',
|
|
'\uA7A8': 'S',
|
|
'\uA784': 'S',
|
|
'\u24C9': 'T',
|
|
'\uFF34': 'T',
|
|
'\u1E6A': 'T',
|
|
'\u0164': 'T',
|
|
'\u1E6C': 'T',
|
|
'\u021A': 'T',
|
|
'\u0162': 'T',
|
|
'\u1E70': 'T',
|
|
'\u1E6E': 'T',
|
|
'\u0166': 'T',
|
|
'\u01AC': 'T',
|
|
'\u01AE': 'T',
|
|
'\u023E': 'T',
|
|
'\uA786': 'T',
|
|
'\uA728': 'TZ',
|
|
'\u24CA': 'U',
|
|
'\uFF35': 'U',
|
|
'\u00D9': 'U',
|
|
'\u00DA': 'U',
|
|
'\u00DB': 'U',
|
|
'\u0168': 'U',
|
|
'\u1E78': 'U',
|
|
'\u016A': 'U',
|
|
'\u1E7A': 'U',
|
|
'\u016C': 'U',
|
|
'\u00DC': 'U',
|
|
'\u01DB': 'U',
|
|
'\u01D7': 'U',
|
|
'\u01D5': 'U',
|
|
'\u01D9': 'U',
|
|
'\u1EE6': 'U',
|
|
'\u016E': 'U',
|
|
'\u0170': 'U',
|
|
'\u01D3': 'U',
|
|
'\u0214': 'U',
|
|
'\u0216': 'U',
|
|
'\u01AF': 'U',
|
|
'\u1EEA': 'U',
|
|
'\u1EE8': 'U',
|
|
'\u1EEE': 'U',
|
|
'\u1EEC': 'U',
|
|
'\u1EF0': 'U',
|
|
'\u1EE4': 'U',
|
|
'\u1E72': 'U',
|
|
'\u0172': 'U',
|
|
'\u1E76': 'U',
|
|
'\u1E74': 'U',
|
|
'\u0244': 'U',
|
|
'\u24CB': 'V',
|
|
'\uFF36': 'V',
|
|
'\u1E7C': 'V',
|
|
'\u1E7E': 'V',
|
|
'\u01B2': 'V',
|
|
'\uA75E': 'V',
|
|
'\u0245': 'V',
|
|
'\uA760': 'VY',
|
|
'\u24CC': 'W',
|
|
'\uFF37': 'W',
|
|
'\u1E80': 'W',
|
|
'\u1E82': 'W',
|
|
'\u0174': 'W',
|
|
'\u1E86': 'W',
|
|
'\u1E84': 'W',
|
|
'\u1E88': 'W',
|
|
'\u2C72': 'W',
|
|
'\u24CD': 'X',
|
|
'\uFF38': 'X',
|
|
'\u1E8A': 'X',
|
|
'\u1E8C': 'X',
|
|
'\u24CE': 'Y',
|
|
'\uFF39': 'Y',
|
|
'\u1EF2': 'Y',
|
|
'\u00DD': 'Y',
|
|
'\u0176': 'Y',
|
|
'\u1EF8': 'Y',
|
|
'\u0232': 'Y',
|
|
'\u1E8E': 'Y',
|
|
'\u0178': 'Y',
|
|
'\u1EF6': 'Y',
|
|
'\u1EF4': 'Y',
|
|
'\u01B3': 'Y',
|
|
'\u024E': 'Y',
|
|
'\u1EFE': 'Y',
|
|
'\u24CF': 'Z',
|
|
'\uFF3A': 'Z',
|
|
'\u0179': 'Z',
|
|
'\u1E90': 'Z',
|
|
'\u017B': 'Z',
|
|
'\u017D': 'Z',
|
|
'\u1E92': 'Z',
|
|
'\u1E94': 'Z',
|
|
'\u01B5': 'Z',
|
|
'\u0224': 'Z',
|
|
'\u2C7F': 'Z',
|
|
'\u2C6B': 'Z',
|
|
'\uA762': 'Z',
|
|
'\u24D0': 'a',
|
|
'\uFF41': 'a',
|
|
'\u1E9A': 'a',
|
|
'\u00E0': 'a',
|
|
'\u00E1': 'a',
|
|
'\u00E2': 'a',
|
|
'\u1EA7': 'a',
|
|
'\u1EA5': 'a',
|
|
'\u1EAB': 'a',
|
|
'\u1EA9': 'a',
|
|
'\u00E3': 'a',
|
|
'\u0101': 'a',
|
|
'\u0103': 'a',
|
|
'\u1EB1': 'a',
|
|
'\u1EAF': 'a',
|
|
'\u1EB5': 'a',
|
|
'\u1EB3': 'a',
|
|
'\u0227': 'a',
|
|
'\u01E1': 'a',
|
|
'\u00E4': 'a',
|
|
'\u01DF': 'a',
|
|
'\u1EA3': 'a',
|
|
'\u00E5': 'a',
|
|
'\u01FB': 'a',
|
|
'\u01CE': 'a',
|
|
'\u0201': 'a',
|
|
'\u0203': 'a',
|
|
'\u1EA1': 'a',
|
|
'\u1EAD': 'a',
|
|
'\u1EB7': 'a',
|
|
'\u1E01': 'a',
|
|
'\u0105': 'a',
|
|
'\u2C65': 'a',
|
|
'\u0250': 'a',
|
|
'\uA733': 'aa',
|
|
'\u00E6': 'ae',
|
|
'\u01FD': 'ae',
|
|
'\u01E3': 'ae',
|
|
'\uA735': 'ao',
|
|
'\uA737': 'au',
|
|
'\uA739': 'av',
|
|
'\uA73B': 'av',
|
|
'\uA73D': 'ay',
|
|
'\u24D1': 'b',
|
|
'\uFF42': 'b',
|
|
'\u1E03': 'b',
|
|
'\u1E05': 'b',
|
|
'\u1E07': 'b',
|
|
'\u0180': 'b',
|
|
'\u0183': 'b',
|
|
'\u0253': 'b',
|
|
'\u24D2': 'c',
|
|
'\uFF43': 'c',
|
|
'\u0107': 'c',
|
|
'\u0109': 'c',
|
|
'\u010B': 'c',
|
|
'\u010D': 'c',
|
|
'\u00E7': 'c',
|
|
'\u1E09': 'c',
|
|
'\u0188': 'c',
|
|
'\u023C': 'c',
|
|
'\uA73F': 'c',
|
|
'\u2184': 'c',
|
|
'\u24D3': 'd',
|
|
'\uFF44': 'd',
|
|
'\u1E0B': 'd',
|
|
'\u010F': 'd',
|
|
'\u1E0D': 'd',
|
|
'\u1E11': 'd',
|
|
'\u1E13': 'd',
|
|
'\u1E0F': 'd',
|
|
'\u0111': 'd',
|
|
'\u018C': 'd',
|
|
'\u0256': 'd',
|
|
'\u0257': 'd',
|
|
'\uA77A': 'd',
|
|
'\u01F3': 'dz',
|
|
'\u01C6': 'dz',
|
|
'\u24D4': 'e',
|
|
'\uFF45': 'e',
|
|
'\u00E8': 'e',
|
|
'\u00E9': 'e',
|
|
'\u00EA': 'e',
|
|
'\u1EC1': 'e',
|
|
'\u1EBF': 'e',
|
|
'\u1EC5': 'e',
|
|
'\u1EC3': 'e',
|
|
'\u1EBD': 'e',
|
|
'\u0113': 'e',
|
|
'\u1E15': 'e',
|
|
'\u1E17': 'e',
|
|
'\u0115': 'e',
|
|
'\u0117': 'e',
|
|
'\u00EB': 'e',
|
|
'\u1EBB': 'e',
|
|
'\u011B': 'e',
|
|
'\u0205': 'e',
|
|
'\u0207': 'e',
|
|
'\u1EB9': 'e',
|
|
'\u1EC7': 'e',
|
|
'\u0229': 'e',
|
|
'\u1E1D': 'e',
|
|
'\u0119': 'e',
|
|
'\u1E19': 'e',
|
|
'\u1E1B': 'e',
|
|
'\u0247': 'e',
|
|
'\u025B': 'e',
|
|
'\u01DD': 'e',
|
|
'\u24D5': 'f',
|
|
'\uFF46': 'f',
|
|
'\u1E1F': 'f',
|
|
'\u0192': 'f',
|
|
'\uA77C': 'f',
|
|
'\u24D6': 'g',
|
|
'\uFF47': 'g',
|
|
'\u01F5': 'g',
|
|
'\u011D': 'g',
|
|
'\u1E21': 'g',
|
|
'\u011F': 'g',
|
|
'\u0121': 'g',
|
|
'\u01E7': 'g',
|
|
'\u0123': 'g',
|
|
'\u01E5': 'g',
|
|
'\u0260': 'g',
|
|
'\uA7A1': 'g',
|
|
'\u1D79': 'g',
|
|
'\uA77F': 'g',
|
|
'\u24D7': 'h',
|
|
'\uFF48': 'h',
|
|
'\u0125': 'h',
|
|
'\u1E23': 'h',
|
|
'\u1E27': 'h',
|
|
'\u021F': 'h',
|
|
'\u1E25': 'h',
|
|
'\u1E29': 'h',
|
|
'\u1E2B': 'h',
|
|
'\u1E96': 'h',
|
|
'\u0127': 'h',
|
|
'\u2C68': 'h',
|
|
'\u2C76': 'h',
|
|
'\u0265': 'h',
|
|
'\u0195': 'hv',
|
|
'\u24D8': 'i',
|
|
'\uFF49': 'i',
|
|
'\u00EC': 'i',
|
|
'\u00ED': 'i',
|
|
'\u00EE': 'i',
|
|
'\u0129': 'i',
|
|
'\u012B': 'i',
|
|
'\u012D': 'i',
|
|
'\u00EF': 'i',
|
|
'\u1E2F': 'i',
|
|
'\u1EC9': 'i',
|
|
'\u01D0': 'i',
|
|
'\u0209': 'i',
|
|
'\u020B': 'i',
|
|
'\u1ECB': 'i',
|
|
'\u012F': 'i',
|
|
'\u1E2D': 'i',
|
|
'\u0268': 'i',
|
|
'\u0131': 'i',
|
|
'\u24D9': 'j',
|
|
'\uFF4A': 'j',
|
|
'\u0135': 'j',
|
|
'\u01F0': 'j',
|
|
'\u0249': 'j',
|
|
'\u24DA': 'k',
|
|
'\uFF4B': 'k',
|
|
'\u1E31': 'k',
|
|
'\u01E9': 'k',
|
|
'\u1E33': 'k',
|
|
'\u0137': 'k',
|
|
'\u1E35': 'k',
|
|
'\u0199': 'k',
|
|
'\u2C6A': 'k',
|
|
'\uA741': 'k',
|
|
'\uA743': 'k',
|
|
'\uA745': 'k',
|
|
'\uA7A3': 'k',
|
|
'\u24DB': 'l',
|
|
'\uFF4C': 'l',
|
|
'\u0140': 'l',
|
|
'\u013A': 'l',
|
|
'\u013E': 'l',
|
|
'\u1E37': 'l',
|
|
'\u1E39': 'l',
|
|
'\u013C': 'l',
|
|
'\u1E3D': 'l',
|
|
'\u1E3B': 'l',
|
|
'\u017F': 'l',
|
|
'\u0142': 'l',
|
|
'\u019A': 'l',
|
|
'\u026B': 'l',
|
|
'\u2C61': 'l',
|
|
'\uA749': 'l',
|
|
'\uA781': 'l',
|
|
'\uA747': 'l',
|
|
'\u01C9': 'lj',
|
|
'\u24DC': 'm',
|
|
'\uFF4D': 'm',
|
|
'\u1E3F': 'm',
|
|
'\u1E41': 'm',
|
|
'\u1E43': 'm',
|
|
'\u0271': 'm',
|
|
'\u026F': 'm',
|
|
'\u24DD': 'n',
|
|
'\uFF4E': 'n',
|
|
'\u01F9': 'n',
|
|
'\u0144': 'n',
|
|
'\u00F1': 'n',
|
|
'\u1E45': 'n',
|
|
'\u0148': 'n',
|
|
'\u1E47': 'n',
|
|
'\u0146': 'n',
|
|
'\u1E4B': 'n',
|
|
'\u1E49': 'n',
|
|
'\u019E': 'n',
|
|
'\u0272': 'n',
|
|
'\u0149': 'n',
|
|
'\uA791': 'n',
|
|
'\uA7A5': 'n',
|
|
'\u01CC': 'nj',
|
|
'\u24DE': 'o',
|
|
'\uFF4F': 'o',
|
|
'\u00F2': 'o',
|
|
'\u00F3': 'o',
|
|
'\u00F4': 'o',
|
|
'\u1ED3': 'o',
|
|
'\u1ED1': 'o',
|
|
'\u1ED7': 'o',
|
|
'\u1ED5': 'o',
|
|
'\u00F5': 'o',
|
|
'\u1E4D': 'o',
|
|
'\u022D': 'o',
|
|
'\u1E4F': 'o',
|
|
'\u014D': 'o',
|
|
'\u1E51': 'o',
|
|
'\u1E53': 'o',
|
|
'\u014F': 'o',
|
|
'\u022F': 'o',
|
|
'\u0231': 'o',
|
|
'\u00F6': 'o',
|
|
'\u022B': 'o',
|
|
'\u1ECF': 'o',
|
|
'\u0151': 'o',
|
|
'\u01D2': 'o',
|
|
'\u020D': 'o',
|
|
'\u020F': 'o',
|
|
'\u01A1': 'o',
|
|
'\u1EDD': 'o',
|
|
'\u1EDB': 'o',
|
|
'\u1EE1': 'o',
|
|
'\u1EDF': 'o',
|
|
'\u1EE3': 'o',
|
|
'\u1ECD': 'o',
|
|
'\u1ED9': 'o',
|
|
'\u01EB': 'o',
|
|
'\u01ED': 'o',
|
|
'\u00F8': 'o',
|
|
'\u01FF': 'o',
|
|
'\u0254': 'o',
|
|
'\uA74B': 'o',
|
|
'\uA74D': 'o',
|
|
'\u0275': 'o',
|
|
'\u01A3': 'oi',
|
|
'\u0223': 'ou',
|
|
'\uA74F': 'oo',
|
|
'\u24DF': 'p',
|
|
'\uFF50': 'p',
|
|
'\u1E55': 'p',
|
|
'\u1E57': 'p',
|
|
'\u01A5': 'p',
|
|
'\u1D7D': 'p',
|
|
'\uA751': 'p',
|
|
'\uA753': 'p',
|
|
'\uA755': 'p',
|
|
'\u24E0': 'q',
|
|
'\uFF51': 'q',
|
|
'\u024B': 'q',
|
|
'\uA757': 'q',
|
|
'\uA759': 'q',
|
|
'\u24E1': 'r',
|
|
'\uFF52': 'r',
|
|
'\u0155': 'r',
|
|
'\u1E59': 'r',
|
|
'\u0159': 'r',
|
|
'\u0211': 'r',
|
|
'\u0213': 'r',
|
|
'\u1E5B': 'r',
|
|
'\u1E5D': 'r',
|
|
'\u0157': 'r',
|
|
'\u1E5F': 'r',
|
|
'\u024D': 'r',
|
|
'\u027D': 'r',
|
|
'\uA75B': 'r',
|
|
'\uA7A7': 'r',
|
|
'\uA783': 'r',
|
|
'\u24E2': 's',
|
|
'\uFF53': 's',
|
|
'\u00DF': 's',
|
|
'\u015B': 's',
|
|
'\u1E65': 's',
|
|
'\u015D': 's',
|
|
'\u1E61': 's',
|
|
'\u0161': 's',
|
|
'\u1E67': 's',
|
|
'\u1E63': 's',
|
|
'\u1E69': 's',
|
|
'\u0219': 's',
|
|
'\u015F': 's',
|
|
'\u023F': 's',
|
|
'\uA7A9': 's',
|
|
'\uA785': 's',
|
|
'\u1E9B': 's',
|
|
'\u24E3': 't',
|
|
'\uFF54': 't',
|
|
'\u1E6B': 't',
|
|
'\u1E97': 't',
|
|
'\u0165': 't',
|
|
'\u1E6D': 't',
|
|
'\u021B': 't',
|
|
'\u0163': 't',
|
|
'\u1E71': 't',
|
|
'\u1E6F': 't',
|
|
'\u0167': 't',
|
|
'\u01AD': 't',
|
|
'\u0288': 't',
|
|
'\u2C66': 't',
|
|
'\uA787': 't',
|
|
'\uA729': 'tz',
|
|
'\u24E4': 'u',
|
|
'\uFF55': 'u',
|
|
'\u00F9': 'u',
|
|
'\u00FA': 'u',
|
|
'\u00FB': 'u',
|
|
'\u0169': 'u',
|
|
'\u1E79': 'u',
|
|
'\u016B': 'u',
|
|
'\u1E7B': 'u',
|
|
'\u016D': 'u',
|
|
'\u00FC': 'u',
|
|
'\u01DC': 'u',
|
|
'\u01D8': 'u',
|
|
'\u01D6': 'u',
|
|
'\u01DA': 'u',
|
|
'\u1EE7': 'u',
|
|
'\u016F': 'u',
|
|
'\u0171': 'u',
|
|
'\u01D4': 'u',
|
|
'\u0215': 'u',
|
|
'\u0217': 'u',
|
|
'\u01B0': 'u',
|
|
'\u1EEB': 'u',
|
|
'\u1EE9': 'u',
|
|
'\u1EEF': 'u',
|
|
'\u1EED': 'u',
|
|
'\u1EF1': 'u',
|
|
'\u1EE5': 'u',
|
|
'\u1E73': 'u',
|
|
'\u0173': 'u',
|
|
'\u1E77': 'u',
|
|
'\u1E75': 'u',
|
|
'\u0289': 'u',
|
|
'\u24E5': 'v',
|
|
'\uFF56': 'v',
|
|
'\u1E7D': 'v',
|
|
'\u1E7F': 'v',
|
|
'\u028B': 'v',
|
|
'\uA75F': 'v',
|
|
'\u028C': 'v',
|
|
'\uA761': 'vy',
|
|
'\u24E6': 'w',
|
|
'\uFF57': 'w',
|
|
'\u1E81': 'w',
|
|
'\u1E83': 'w',
|
|
'\u0175': 'w',
|
|
'\u1E87': 'w',
|
|
'\u1E85': 'w',
|
|
'\u1E98': 'w',
|
|
'\u1E89': 'w',
|
|
'\u2C73': 'w',
|
|
'\u24E7': 'x',
|
|
'\uFF58': 'x',
|
|
'\u1E8B': 'x',
|
|
'\u1E8D': 'x',
|
|
'\u24E8': 'y',
|
|
'\uFF59': 'y',
|
|
'\u1EF3': 'y',
|
|
'\u00FD': 'y',
|
|
'\u0177': 'y',
|
|
'\u1EF9': 'y',
|
|
'\u0233': 'y',
|
|
'\u1E8F': 'y',
|
|
'\u00FF': 'y',
|
|
'\u1EF7': 'y',
|
|
'\u1E99': 'y',
|
|
'\u1EF5': 'y',
|
|
'\u01B4': 'y',
|
|
'\u024F': 'y',
|
|
'\u1EFF': 'y',
|
|
'\u24E9': 'z',
|
|
'\uFF5A': 'z',
|
|
'\u017A': 'z',
|
|
'\u1E91': 'z',
|
|
'\u017C': 'z',
|
|
'\u017E': 'z',
|
|
'\u1E93': 'z',
|
|
'\u1E95': 'z',
|
|
'\u01B6': 'z',
|
|
'\u0225': 'z',
|
|
'\u0240': 'z',
|
|
'\u2C6C': 'z',
|
|
'\uA763': 'z',
|
|
'\u0386': '\u0391',
|
|
'\u0388': '\u0395',
|
|
'\u0389': '\u0397',
|
|
'\u038A': '\u0399',
|
|
'\u03AA': '\u0399',
|
|
'\u038C': '\u039F',
|
|
'\u038E': '\u03A5',
|
|
'\u03AB': '\u03A5',
|
|
'\u038F': '\u03A9',
|
|
'\u03AC': '\u03B1',
|
|
'\u03AD': '\u03B5',
|
|
'\u03AE': '\u03B7',
|
|
'\u03AF': '\u03B9',
|
|
'\u03CA': '\u03B9',
|
|
'\u0390': '\u03B9',
|
|
'\u03CC': '\u03BF',
|
|
'\u03CD': '\u03C5',
|
|
'\u03CB': '\u03C5',
|
|
'\u03B0': '\u03C5',
|
|
'\u03C9': '\u03C9',
|
|
'\u03C2': '\u03C3'
|
|
};
|
|
|
|
return diacritics;
|
|
});
|
|
|
|
S2.define('select2/data/base',[
|
|
'../utils'
|
|
], function (Utils) {
|
|
function BaseAdapter ($element, options) {
|
|
BaseAdapter.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(BaseAdapter, Utils.Observable);
|
|
|
|
BaseAdapter.prototype.current = function (callback) {
|
|
throw new Error('The `current` method must be defined in child classes.');
|
|
};
|
|
|
|
BaseAdapter.prototype.query = function (params, callback) {
|
|
throw new Error('The `query` method must be defined in child classes.');
|
|
};
|
|
|
|
BaseAdapter.prototype.bind = function (container, $container) {
|
|
// Can be implemented in subclasses
|
|
};
|
|
|
|
BaseAdapter.prototype.destroy = function () {
|
|
// Can be implemented in subclasses
|
|
};
|
|
|
|
BaseAdapter.prototype.generateResultId = function (container, data) {
|
|
var id = container.id + '-result-';
|
|
|
|
id += Utils.generateChars(4);
|
|
|
|
if (data.id != null) {
|
|
id += '-' + data.id.toString();
|
|
} else {
|
|
id += '-' + Utils.generateChars(4);
|
|
}
|
|
return id;
|
|
};
|
|
|
|
return BaseAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/select',[
|
|
'./base',
|
|
'../utils',
|
|
'jquery'
|
|
], function (BaseAdapter, Utils, $) {
|
|
function SelectAdapter ($element, options) {
|
|
this.$element = $element;
|
|
this.options = options;
|
|
|
|
SelectAdapter.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(SelectAdapter, BaseAdapter);
|
|
|
|
SelectAdapter.prototype.current = function (callback) {
|
|
var data = [];
|
|
var self = this;
|
|
|
|
this.$element.find(':selected').each(function () {
|
|
var $option = $(this);
|
|
|
|
var option = self.item($option);
|
|
|
|
data.push(option);
|
|
});
|
|
|
|
callback(data);
|
|
};
|
|
|
|
SelectAdapter.prototype.select = function (data) {
|
|
var self = this;
|
|
|
|
data.selected = true;
|
|
|
|
// If data.element is a DOM node, use it instead
|
|
if ($(data.element).is('option')) {
|
|
data.element.selected = true;
|
|
|
|
this.$element.trigger('change');
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.$element.prop('multiple')) {
|
|
this.current(function (currentData) {
|
|
var val = [];
|
|
|
|
data = [data];
|
|
data.push.apply(data, currentData);
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var id = data[d].id;
|
|
|
|
if ($.inArray(id, val) === -1) {
|
|
val.push(id);
|
|
}
|
|
}
|
|
|
|
self.$element.val(val);
|
|
self.$element.trigger('change');
|
|
});
|
|
} else {
|
|
var val = data.id;
|
|
|
|
this.$element.val(val);
|
|
this.$element.trigger('change');
|
|
}
|
|
};
|
|
|
|
SelectAdapter.prototype.unselect = function (data) {
|
|
var self = this;
|
|
|
|
if (!this.$element.prop('multiple')) {
|
|
return;
|
|
}
|
|
|
|
data.selected = false;
|
|
|
|
if ($(data.element).is('option')) {
|
|
data.element.selected = false;
|
|
|
|
this.$element.trigger('change');
|
|
|
|
return;
|
|
}
|
|
|
|
this.current(function (currentData) {
|
|
var val = [];
|
|
|
|
for (var d = 0; d < currentData.length; d++) {
|
|
var id = currentData[d].id;
|
|
|
|
if (id !== data.id && $.inArray(id, val) === -1) {
|
|
val.push(id);
|
|
}
|
|
}
|
|
|
|
self.$element.val(val);
|
|
|
|
self.$element.trigger('change');
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.bind = function (container, $container) {
|
|
var self = this;
|
|
|
|
this.container = container;
|
|
|
|
container.on('select', function (params) {
|
|
self.select(params.data);
|
|
});
|
|
|
|
container.on('unselect', function (params) {
|
|
self.unselect(params.data);
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.destroy = function () {
|
|
// Remove anything added to child elements
|
|
this.$element.find('*').each(function () {
|
|
// Remove any custom data set by Select2
|
|
$.removeData(this, 'data');
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.query = function (params, callback) {
|
|
var data = [];
|
|
var self = this;
|
|
|
|
var $options = this.$element.children();
|
|
|
|
$options.each(function () {
|
|
var $option = $(this);
|
|
|
|
if (!$option.is('option') && !$option.is('optgroup')) {
|
|
return;
|
|
}
|
|
|
|
var option = self.item($option);
|
|
|
|
var matches = self.matches(params, option);
|
|
|
|
if (matches !== null) {
|
|
data.push(matches);
|
|
}
|
|
});
|
|
|
|
callback({
|
|
results: data
|
|
});
|
|
};
|
|
|
|
SelectAdapter.prototype.addOptions = function ($options) {
|
|
Utils.appendMany(this.$element, $options);
|
|
};
|
|
|
|
SelectAdapter.prototype.option = function (data) {
|
|
var option;
|
|
|
|
if (data.children) {
|
|
option = document.createElement('optgroup');
|
|
option.label = data.text;
|
|
} else {
|
|
option = document.createElement('option');
|
|
|
|
if (option.textContent !== undefined) {
|
|
option.textContent = data.text;
|
|
} else {
|
|
option.innerText = data.text;
|
|
}
|
|
}
|
|
|
|
if (data.id !== undefined) {
|
|
option.value = data.id;
|
|
}
|
|
|
|
if (data.disabled) {
|
|
option.disabled = true;
|
|
}
|
|
|
|
if (data.selected) {
|
|
option.selected = true;
|
|
}
|
|
|
|
if (data.title) {
|
|
option.title = data.title;
|
|
}
|
|
|
|
var $option = $(option);
|
|
|
|
var normalizedData = this._normalizeItem(data);
|
|
normalizedData.element = option;
|
|
|
|
// Override the option's data with the combined data
|
|
$.data(option, 'data', normalizedData);
|
|
|
|
return $option;
|
|
};
|
|
|
|
SelectAdapter.prototype.item = function ($option) {
|
|
var data = {};
|
|
|
|
data = $.data($option[0], 'data');
|
|
|
|
if (data != null) {
|
|
return data;
|
|
}
|
|
|
|
if ($option.is('option')) {
|
|
data = {
|
|
id: $option.val(),
|
|
text: $option.text(),
|
|
disabled: $option.prop('disabled'),
|
|
selected: $option.prop('selected'),
|
|
title: $option.prop('title')
|
|
};
|
|
} else if ($option.is('optgroup')) {
|
|
data = {
|
|
text: $option.prop('label'),
|
|
children: [],
|
|
title: $option.prop('title')
|
|
};
|
|
|
|
var $children = $option.children('option');
|
|
var children = [];
|
|
|
|
for (var c = 0; c < $children.length; c++) {
|
|
var $child = $($children[c]);
|
|
|
|
var child = this.item($child);
|
|
|
|
children.push(child);
|
|
}
|
|
|
|
data.children = children;
|
|
}
|
|
|
|
data = this._normalizeItem(data);
|
|
data.element = $option[0];
|
|
|
|
$.data($option[0], 'data', data);
|
|
|
|
return data;
|
|
};
|
|
|
|
SelectAdapter.prototype._normalizeItem = function (item) {
|
|
if (!$.isPlainObject(item)) {
|
|
item = {
|
|
id: item,
|
|
text: item
|
|
};
|
|
}
|
|
|
|
item = $.extend({}, {
|
|
text: ''
|
|
}, item);
|
|
|
|
var defaults = {
|
|
selected: false,
|
|
disabled: false
|
|
};
|
|
|
|
if (item.id != null) {
|
|
item.id = item.id.toString();
|
|
}
|
|
|
|
if (item.text != null) {
|
|
item.text = item.text.toString();
|
|
}
|
|
|
|
if (item._resultId == null && item.id && this.container != null) {
|
|
item._resultId = this.generateResultId(this.container, item);
|
|
}
|
|
|
|
return $.extend({}, defaults, item);
|
|
};
|
|
|
|
SelectAdapter.prototype.matches = function (params, data) {
|
|
var matcher = this.options.get('matcher');
|
|
|
|
return matcher(params, data);
|
|
};
|
|
|
|
return SelectAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/array',[
|
|
'./select',
|
|
'../utils',
|
|
'jquery'
|
|
], function (SelectAdapter, Utils, $) {
|
|
function ArrayAdapter ($element, options) {
|
|
var data = options.get('data') || [];
|
|
|
|
ArrayAdapter.__super__.constructor.call(this, $element, options);
|
|
|
|
this.addOptions(this.convertToOptions(data));
|
|
}
|
|
|
|
Utils.Extend(ArrayAdapter, SelectAdapter);
|
|
|
|
ArrayAdapter.prototype.select = function (data) {
|
|
var $option = this.$element.find('option').filter(function (i, elm) {
|
|
return elm.value == data.id.toString();
|
|
});
|
|
|
|
if ($option.length === 0) {
|
|
$option = this.option(data);
|
|
|
|
this.addOptions($option);
|
|
}
|
|
|
|
ArrayAdapter.__super__.select.call(this, data);
|
|
};
|
|
|
|
ArrayAdapter.prototype.convertToOptions = function (data) {
|
|
var self = this;
|
|
|
|
var $existing = this.$element.find('option');
|
|
var existingIds = $existing.map(function () {
|
|
return self.item($(this)).id;
|
|
}).get();
|
|
|
|
var $options = [];
|
|
|
|
// Filter out all items except for the one passed in the argument
|
|
function onlyItem (item) {
|
|
return function () {
|
|
return $(this).val() == item.id;
|
|
};
|
|
}
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var item = this._normalizeItem(data[d]);
|
|
|
|
// Skip items which were pre-loaded, only merge the data
|
|
if ($.inArray(item.id, existingIds) >= 0) {
|
|
var $existingOption = $existing.filter(onlyItem(item));
|
|
|
|
var existingData = this.item($existingOption);
|
|
var newData = $.extend(true, {}, item, existingData);
|
|
|
|
var $newOption = this.option(newData);
|
|
|
|
$existingOption.replaceWith($newOption);
|
|
|
|
continue;
|
|
}
|
|
|
|
var $option = this.option(item);
|
|
|
|
if (item.children) {
|
|
var $children = this.convertToOptions(item.children);
|
|
|
|
Utils.appendMany($option, $children);
|
|
}
|
|
|
|
$options.push($option);
|
|
}
|
|
|
|
return $options;
|
|
};
|
|
|
|
return ArrayAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/ajax',[
|
|
'./array',
|
|
'../utils',
|
|
'jquery'
|
|
], function (ArrayAdapter, Utils, $) {
|
|
function AjaxAdapter ($element, options) {
|
|
this.ajaxOptions = this._applyDefaults(options.get('ajax'));
|
|
|
|
if (this.ajaxOptions.processResults != null) {
|
|
this.processResults = this.ajaxOptions.processResults;
|
|
}
|
|
|
|
AjaxAdapter.__super__.constructor.call(this, $element, options);
|
|
}
|
|
|
|
Utils.Extend(AjaxAdapter, ArrayAdapter);
|
|
|
|
AjaxAdapter.prototype._applyDefaults = function (options) {
|
|
var defaults = {
|
|
data: function (params) {
|
|
return $.extend({}, params, {
|
|
q: params.term
|
|
});
|
|
},
|
|
transport: function (params, success, failure) {
|
|
var $request = $.ajax(params);
|
|
|
|
$request.then(success);
|
|
$request.fail(failure);
|
|
|
|
return $request;
|
|
}
|
|
};
|
|
|
|
return $.extend({}, defaults, options, true);
|
|
};
|
|
|
|
AjaxAdapter.prototype.processResults = function (results) {
|
|
return results;
|
|
};
|
|
|
|
AjaxAdapter.prototype.query = function (params, callback) {
|
|
var matches = [];
|
|
var self = this;
|
|
|
|
if (this._request != null) {
|
|
// JSONP requests cannot always be aborted
|
|
if ($.isFunction(this._request.abort)) {
|
|
this._request.abort();
|
|
}
|
|
|
|
this._request = null;
|
|
}
|
|
|
|
var options = $.extend({
|
|
type: 'GET'
|
|
}, this.ajaxOptions);
|
|
|
|
if (typeof options.url === 'function') {
|
|
options.url = options.url.call(this.$element, params);
|
|
}
|
|
|
|
if (typeof options.data === 'function') {
|
|
options.data = options.data.call(this.$element, params);
|
|
}
|
|
|
|
function request () {
|
|
var $request = options.transport(options, function (data) {
|
|
var results = self.processResults(data, params);
|
|
|
|
if (self.options.get('debug') && window.console && console.error) {
|
|
// Check to make sure that the response included a `results` key.
|
|
if (!results || !results.results || !$.isArray(results.results)) {
|
|
console.error(
|
|
'Select2: The AJAX results did not return an array in the ' +
|
|
'`results` key of the response.'
|
|
);
|
|
}
|
|
}
|
|
|
|
callback(results);
|
|
}, function () {
|
|
// Attempt to detect if a request was aborted
|
|
// Only works if the transport exposes a status property
|
|
if ($request.status && $request.status === '0') {
|
|
return;
|
|
}
|
|
|
|
self.trigger('results:message', {
|
|
message: 'errorLoading'
|
|
});
|
|
});
|
|
|
|
self._request = $request;
|
|
}
|
|
|
|
if (this.ajaxOptions.delay && params.term != null) {
|
|
if (this._queryTimeout) {
|
|
window.clearTimeout(this._queryTimeout);
|
|
}
|
|
|
|
this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
|
|
} else {
|
|
request();
|
|
}
|
|
};
|
|
|
|
return AjaxAdapter;
|
|
});
|
|
|
|
S2.define('select2/data/tags',[
|
|
'jquery'
|
|
], function ($) {
|
|
function Tags (decorated, $element, options) {
|
|
var tags = options.get('tags');
|
|
|
|
var createTag = options.get('createTag');
|
|
|
|
if (createTag !== undefined) {
|
|
this.createTag = createTag;
|
|
}
|
|
|
|
var insertTag = options.get('insertTag');
|
|
|
|
if (insertTag !== undefined) {
|
|
this.insertTag = insertTag;
|
|
}
|
|
|
|
decorated.call(this, $element, options);
|
|
|
|
if ($.isArray(tags)) {
|
|
for (var t = 0; t < tags.length; t++) {
|
|
var tag = tags[t];
|
|
var item = this._normalizeItem(tag);
|
|
|
|
var $option = this.option(item);
|
|
|
|
this.$element.append($option);
|
|
}
|
|
}
|
|
}
|
|
|
|
Tags.prototype.query = function (decorated, params, callback) {
|
|
var self = this;
|
|
|
|
this._removeOldTags();
|
|
|
|
if (params.term == null || params.page != null) {
|
|
decorated.call(this, params, callback);
|
|
return;
|
|
}
|
|
|
|
function wrapper (obj, child) {
|
|
var data = obj.results;
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
var option = data[i];
|
|
|
|
var checkChildren = (
|
|
option.children != null &&
|
|
!wrapper({
|
|
results: option.children
|
|
}, true)
|
|
);
|
|
|
|
var optionText = (option.text || '').toUpperCase();
|
|
var paramsTerm = (params.term || '').toUpperCase();
|
|
|
|
var checkText = optionText === paramsTerm;
|
|
|
|
if (checkText || checkChildren) {
|
|
if (child) {
|
|
return false;
|
|
}
|
|
|
|
obj.data = data;
|
|
callback(obj);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (child) {
|
|
return true;
|
|
}
|
|
|
|
var tag = self.createTag(params);
|
|
|
|
if (tag != null) {
|
|
var $option = self.option(tag);
|
|
$option.attr('data-select2-tag', true);
|
|
|
|
self.addOptions([$option]);
|
|
|
|
self.insertTag(data, tag);
|
|
}
|
|
|
|
obj.results = data;
|
|
|
|
callback(obj);
|
|
}
|
|
|
|
decorated.call(this, params, wrapper);
|
|
};
|
|
|
|
Tags.prototype.createTag = function (decorated, params) {
|
|
var term = $.trim(params.term);
|
|
|
|
if (term === '') {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: term,
|
|
text: term
|
|
};
|
|
};
|
|
|
|
Tags.prototype.insertTag = function (_, data, tag) {
|
|
data.unshift(tag);
|
|
};
|
|
|
|
Tags.prototype._removeOldTags = function (_) {
|
|
var tag = this._lastTag;
|
|
|
|
var $options = this.$element.find('option[data-select2-tag]');
|
|
|
|
$options.each(function () {
|
|
if (this.selected) {
|
|
return;
|
|
}
|
|
|
|
$(this).remove();
|
|
});
|
|
};
|
|
|
|
return Tags;
|
|
});
|
|
|
|
S2.define('select2/data/tokenizer',[
|
|
'jquery'
|
|
], function ($) {
|
|
function Tokenizer (decorated, $element, options) {
|
|
var tokenizer = options.get('tokenizer');
|
|
|
|
if (tokenizer !== undefined) {
|
|
this.tokenizer = tokenizer;
|
|
}
|
|
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
Tokenizer.prototype.bind = function (decorated, container, $container) {
|
|
decorated.call(this, container, $container);
|
|
|
|
this.$search = container.dropdown.$search || container.selection.$search ||
|
|
$container.find('.select2-search__field');
|
|
};
|
|
|
|
Tokenizer.prototype.query = function (decorated, params, callback) {
|
|
var self = this;
|
|
|
|
function createAndSelect (data) {
|
|
// Normalize the data object so we can use it for checks
|
|
var item = self._normalizeItem(data);
|
|
|
|
// Check if the data object already exists as a tag
|
|
// Select it if it doesn't
|
|
var $existingOptions = self.$element.find('option').filter(function () {
|
|
return $(this).val() === item.id;
|
|
});
|
|
|
|
// If an existing option wasn't found for it, create the option
|
|
if (!$existingOptions.length) {
|
|
var $option = self.option(item);
|
|
$option.attr('data-select2-tag', true);
|
|
|
|
self._removeOldTags();
|
|
self.addOptions([$option]);
|
|
}
|
|
|
|
// Select the item, now that we know there is an option for it
|
|
select(item);
|
|
}
|
|
|
|
function select (data) {
|
|
self.trigger('select', {
|
|
data: data
|
|
});
|
|
}
|
|
|
|
params.term = params.term || '';
|
|
|
|
var tokenData = this.tokenizer(params, this.options, createAndSelect);
|
|
|
|
if (tokenData.term !== params.term) {
|
|
// Replace the search term if we have the search box
|
|
if (this.$search.length) {
|
|
this.$search.val(tokenData.term);
|
|
this.$search.focus();
|
|
}
|
|
|
|
params.term = tokenData.term;
|
|
}
|
|
|
|
decorated.call(this, params, callback);
|
|
};
|
|
|
|
Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
|
|
var separators = options.get('tokenSeparators') || [];
|
|
var term = params.term;
|
|
var i = 0;
|
|
|
|
var createTag = this.createTag || function (params) {
|
|
return {
|
|
id: params.term,
|
|
text: params.term
|
|
};
|
|
};
|
|
|
|
while (i < term.length) {
|
|
var termChar = term[i];
|
|
|
|
if ($.inArray(termChar, separators) === -1) {
|
|
i++;
|
|
|
|
continue;
|
|
}
|
|
|
|
var part = term.substr(0, i);
|
|
var partParams = $.extend({}, params, {
|
|
term: part
|
|
});
|
|
|
|
var data = createTag(partParams);
|
|
|
|
if (data == null) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
callback(data);
|
|
|
|
// Reset the term to not include the tokenized portion
|
|
term = term.substr(i + 1) || '';
|
|
i = 0;
|
|
}
|
|
|
|
return {
|
|
term: term
|
|
};
|
|
};
|
|
|
|
return Tokenizer;
|
|
});
|
|
|
|
S2.define('select2/data/minimumInputLength',[
|
|
|
|
], function () {
|
|
function MinimumInputLength (decorated, $e, options) {
|
|
this.minimumInputLength = options.get('minimumInputLength');
|
|
|
|
decorated.call(this, $e, options);
|
|
}
|
|
|
|
MinimumInputLength.prototype.query = function (decorated, params, callback) {
|
|
params.term = params.term || '';
|
|
|
|
if (params.term.length < this.minimumInputLength) {
|
|
this.trigger('results:message', {
|
|
message: 'inputTooShort',
|
|
args: {
|
|
minimum: this.minimumInputLength,
|
|
input: params.term,
|
|
params: params
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
decorated.call(this, params, callback);
|
|
};
|
|
|
|
return MinimumInputLength;
|
|
});
|
|
|
|
S2.define('select2/data/maximumInputLength',[
|
|
|
|
], function () {
|
|
function MaximumInputLength (decorated, $e, options) {
|
|
this.maximumInputLength = options.get('maximumInputLength');
|
|
|
|
decorated.call(this, $e, options);
|
|
}
|
|
|
|
MaximumInputLength.prototype.query = function (decorated, params, callback) {
|
|
params.term = params.term || '';
|
|
|
|
if (this.maximumInputLength > 0 &&
|
|
params.term.length > this.maximumInputLength) {
|
|
this.trigger('results:message', {
|
|
message: 'inputTooLong',
|
|
args: {
|
|
maximum: this.maximumInputLength,
|
|
input: params.term,
|
|
params: params
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
decorated.call(this, params, callback);
|
|
};
|
|
|
|
return MaximumInputLength;
|
|
});
|
|
|
|
S2.define('select2/data/maximumSelectionLength',[
|
|
|
|
], function (){
|
|
function MaximumSelectionLength (decorated, $e, options) {
|
|
this.maximumSelectionLength = options.get('maximumSelectionLength');
|
|
|
|
decorated.call(this, $e, options);
|
|
}
|
|
|
|
MaximumSelectionLength.prototype.query =
|
|
function (decorated, params, callback) {
|
|
var self = this;
|
|
|
|
this.current(function (currentData) {
|
|
var count = currentData != null ? currentData.length : 0;
|
|
if (self.maximumSelectionLength > 0 &&
|
|
count >= self.maximumSelectionLength) {
|
|
self.trigger('results:message', {
|
|
message: 'maximumSelected',
|
|
args: {
|
|
maximum: self.maximumSelectionLength
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
decorated.call(self, params, callback);
|
|
});
|
|
};
|
|
|
|
return MaximumSelectionLength;
|
|
});
|
|
|
|
S2.define('select2/dropdown',[
|
|
'jquery',
|
|
'./utils'
|
|
], function ($, Utils) {
|
|
function Dropdown ($element, options) {
|
|
this.$element = $element;
|
|
this.options = options;
|
|
|
|
Dropdown.__super__.constructor.call(this);
|
|
}
|
|
|
|
Utils.Extend(Dropdown, Utils.Observable);
|
|
|
|
Dropdown.prototype.render = function () {
|
|
var $dropdown = $(
|
|
'<span class="select2-dropdown">' +
|
|
'<span class="select2-results"></span>' +
|
|
'</span>'
|
|
);
|
|
|
|
$dropdown.attr('dir', this.options.get('dir'));
|
|
|
|
this.$dropdown = $dropdown;
|
|
|
|
return $dropdown;
|
|
};
|
|
|
|
Dropdown.prototype.bind = function () {
|
|
// Should be implemented in subclasses
|
|
};
|
|
|
|
Dropdown.prototype.position = function ($dropdown, $container) {
|
|
// Should be implmented in subclasses
|
|
};
|
|
|
|
Dropdown.prototype.destroy = function () {
|
|
// Remove the dropdown from the DOM
|
|
this.$dropdown.remove();
|
|
};
|
|
|
|
return Dropdown;
|
|
});
|
|
|
|
S2.define('select2/dropdown/search',[
|
|
'jquery',
|
|
'../utils'
|
|
], function ($, Utils) {
|
|
function Search () { }
|
|
|
|
Search.prototype.render = function (decorated) {
|
|
var $rendered = decorated.call(this);
|
|
|
|
var $search = $(
|
|
'<span class="select2-search select2-search--dropdown">' +
|
|
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
|
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
|
' spellcheck="false" role="textbox" />' +
|
|
'</span>'
|
|
);
|
|
|
|
this.$searchContainer = $search;
|
|
this.$search = $search.find('input');
|
|
|
|
$rendered.prepend($search);
|
|
|
|
return $rendered;
|
|
};
|
|
|
|
Search.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
this.$search.on('keydown', function (evt) {
|
|
self.trigger('keypress', evt);
|
|
|
|
self._keyUpPrevented = evt.isDefaultPrevented();
|
|
});
|
|
|
|
// Workaround for browsers which do not support the `input` event
|
|
// This will prevent double-triggering of events for browsers which support
|
|
// both the `keyup` and `input` events.
|
|
this.$search.on('input', function (evt) {
|
|
// Unbind the duplicated `keyup` event
|
|
$(this).off('keyup');
|
|
});
|
|
|
|
this.$search.on('keyup input', function (evt) {
|
|
self.handleSearch(evt);
|
|
});
|
|
|
|
container.on('open', function () {
|
|
self.$search.attr('tabindex', 0);
|
|
|
|
self.$search.focus();
|
|
|
|
window.setTimeout(function () {
|
|
self.$search.focus();
|
|
}, 0);
|
|
});
|
|
|
|
container.on('close', function () {
|
|
self.$search.attr('tabindex', -1);
|
|
|
|
self.$search.val('');
|
|
});
|
|
|
|
container.on('focus', function () {
|
|
if (!container.isOpen()) {
|
|
self.$search.focus();
|
|
}
|
|
});
|
|
|
|
container.on('results:all', function (params) {
|
|
if (params.query.term == null || params.query.term === '') {
|
|
var showSearch = self.showSearch(params);
|
|
|
|
if (showSearch) {
|
|
self.$searchContainer.removeClass('select2-search--hide');
|
|
} else {
|
|
self.$searchContainer.addClass('select2-search--hide');
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Search.prototype.handleSearch = function (evt) {
|
|
if (!this._keyUpPrevented) {
|
|
var input = this.$search.val();
|
|
|
|
this.trigger('query', {
|
|
term: input
|
|
});
|
|
}
|
|
|
|
this._keyUpPrevented = false;
|
|
};
|
|
|
|
Search.prototype.showSearch = function (_, params) {
|
|
return true;
|
|
};
|
|
|
|
return Search;
|
|
});
|
|
|
|
S2.define('select2/dropdown/hidePlaceholder',[
|
|
|
|
], function () {
|
|
function HidePlaceholder (decorated, $element, options, dataAdapter) {
|
|
this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
|
|
|
|
decorated.call(this, $element, options, dataAdapter);
|
|
}
|
|
|
|
HidePlaceholder.prototype.append = function (decorated, data) {
|
|
data.results = this.removePlaceholder(data.results);
|
|
|
|
decorated.call(this, data);
|
|
};
|
|
|
|
HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
|
|
if (typeof placeholder === 'string') {
|
|
placeholder = {
|
|
id: '',
|
|
text: placeholder
|
|
};
|
|
}
|
|
|
|
return placeholder;
|
|
};
|
|
|
|
HidePlaceholder.prototype.removePlaceholder = function (_, data) {
|
|
var modifiedData = data.slice(0);
|
|
|
|
for (var d = data.length - 1; d >= 0; d--) {
|
|
var item = data[d];
|
|
|
|
if (this.placeholder.id === item.id) {
|
|
modifiedData.splice(d, 1);
|
|
}
|
|
}
|
|
|
|
return modifiedData;
|
|
};
|
|
|
|
return HidePlaceholder;
|
|
});
|
|
|
|
S2.define('select2/dropdown/infiniteScroll',[
|
|
'jquery'
|
|
], function ($) {
|
|
function InfiniteScroll (decorated, $element, options, dataAdapter) {
|
|
this.lastParams = {};
|
|
|
|
decorated.call(this, $element, options, dataAdapter);
|
|
|
|
this.$loadingMore = this.createLoadingMore();
|
|
this.loading = false;
|
|
}
|
|
|
|
InfiniteScroll.prototype.append = function (decorated, data) {
|
|
this.$loadingMore.remove();
|
|
this.loading = false;
|
|
|
|
decorated.call(this, data);
|
|
|
|
if (this.showLoadingMore(data)) {
|
|
this.$results.append(this.$loadingMore);
|
|
}
|
|
};
|
|
|
|
InfiniteScroll.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('query', function (params) {
|
|
self.lastParams = params;
|
|
self.loading = true;
|
|
});
|
|
|
|
container.on('query:append', function (params) {
|
|
self.lastParams = params;
|
|
self.loading = true;
|
|
});
|
|
|
|
this.$results.on('scroll', function () {
|
|
var isLoadMoreVisible = $.contains(
|
|
document.documentElement,
|
|
self.$loadingMore[0]
|
|
);
|
|
|
|
if (self.loading || !isLoadMoreVisible) {
|
|
return;
|
|
}
|
|
|
|
var currentOffset = self.$results.offset().top +
|
|
self.$results.outerHeight(false);
|
|
var loadingMoreOffset = self.$loadingMore.offset().top +
|
|
self.$loadingMore.outerHeight(false);
|
|
|
|
if (currentOffset + 50 >= loadingMoreOffset) {
|
|
self.loadMore();
|
|
}
|
|
});
|
|
};
|
|
|
|
InfiniteScroll.prototype.loadMore = function () {
|
|
this.loading = true;
|
|
|
|
var params = $.extend({}, {page: 1}, this.lastParams);
|
|
|
|
params.page++;
|
|
|
|
this.trigger('query:append', params);
|
|
};
|
|
|
|
InfiniteScroll.prototype.showLoadingMore = function (_, data) {
|
|
return data.pagination && data.pagination.more;
|
|
};
|
|
|
|
InfiniteScroll.prototype.createLoadingMore = function () {
|
|
var $option = $(
|
|
'<li ' +
|
|
'class="select2-results__option select2-results__option--load-more"' +
|
|
'role="treeitem" aria-disabled="true"></li>'
|
|
);
|
|
|
|
var message = this.options.get('translations').get('loadingMore');
|
|
|
|
$option.html(message(this.lastParams));
|
|
|
|
return $option;
|
|
};
|
|
|
|
return InfiniteScroll;
|
|
});
|
|
|
|
S2.define('select2/dropdown/attachBody',[
|
|
'jquery',
|
|
'../utils'
|
|
], function ($, Utils) {
|
|
function AttachBody (decorated, $element, options) {
|
|
this.$dropdownParent = options.get('dropdownParent') || $(document.body);
|
|
|
|
decorated.call(this, $element, options);
|
|
}
|
|
|
|
AttachBody.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
var setupResultsEvents = false;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('open', function () {
|
|
self._showDropdown();
|
|
self._attachPositioningHandler(container);
|
|
|
|
if (!setupResultsEvents) {
|
|
setupResultsEvents = true;
|
|
|
|
container.on('results:all', function () {
|
|
self._positionDropdown();
|
|
self._resizeDropdown();
|
|
});
|
|
|
|
container.on('results:append', function () {
|
|
self._positionDropdown();
|
|
self._resizeDropdown();
|
|
});
|
|
}
|
|
});
|
|
|
|
container.on('close', function () {
|
|
self._hideDropdown();
|
|
self._detachPositioningHandler(container);
|
|
});
|
|
|
|
this.$dropdownContainer.on('mousedown', function (evt) {
|
|
evt.stopPropagation();
|
|
});
|
|
};
|
|
|
|
AttachBody.prototype.destroy = function (decorated) {
|
|
decorated.call(this);
|
|
|
|
this.$dropdownContainer.remove();
|
|
};
|
|
|
|
AttachBody.prototype.position = function (decorated, $dropdown, $container) {
|
|
// Clone all of the container classes
|
|
$dropdown.attr('class', $container.attr('class'));
|
|
|
|
$dropdown.removeClass('select2');
|
|
$dropdown.addClass('select2-container--open');
|
|
|
|
$dropdown.css({
|
|
position: 'absolute',
|
|
top: -999999
|
|
});
|
|
|
|
this.$container = $container;
|
|
};
|
|
|
|
AttachBody.prototype.render = function (decorated) {
|
|
var $container = $('<span></span>');
|
|
|
|
var $dropdown = decorated.call(this);
|
|
$container.append($dropdown);
|
|
|
|
this.$dropdownContainer = $container;
|
|
|
|
return $container;
|
|
};
|
|
|
|
AttachBody.prototype._hideDropdown = function (decorated) {
|
|
this.$dropdownContainer.detach();
|
|
};
|
|
|
|
AttachBody.prototype._attachPositioningHandler =
|
|
function (decorated, container) {
|
|
var self = this;
|
|
|
|
var scrollEvent = 'scroll.select2.' + container.id;
|
|
var resizeEvent = 'resize.select2.' + container.id;
|
|
var orientationEvent = 'orientationchange.select2.' + container.id;
|
|
|
|
var $watchers = this.$container.parents().filter(Utils.hasScroll);
|
|
$watchers.each(function () {
|
|
$(this).data('select2-scroll-position', {
|
|
x: $(this).scrollLeft(),
|
|
y: $(this).scrollTop()
|
|
});
|
|
});
|
|
|
|
$watchers.on(scrollEvent, function (ev) {
|
|
var position = $(this).data('select2-scroll-position');
|
|
$(this).scrollTop(position.y);
|
|
});
|
|
|
|
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
|
|
function (e) {
|
|
self._positionDropdown();
|
|
self._resizeDropdown();
|
|
});
|
|
};
|
|
|
|
AttachBody.prototype._detachPositioningHandler =
|
|
function (decorated, container) {
|
|
var scrollEvent = 'scroll.select2.' + container.id;
|
|
var resizeEvent = 'resize.select2.' + container.id;
|
|
var orientationEvent = 'orientationchange.select2.' + container.id;
|
|
|
|
var $watchers = this.$container.parents().filter(Utils.hasScroll);
|
|
$watchers.off(scrollEvent);
|
|
|
|
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
|
|
};
|
|
|
|
AttachBody.prototype._positionDropdown = function () {
|
|
var $window = $(window);
|
|
|
|
var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
|
|
var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
|
|
|
|
var newDirection = null;
|
|
|
|
var offset = this.$container.offset();
|
|
|
|
offset.bottom = offset.top + this.$container.outerHeight(false);
|
|
|
|
var container = {
|
|
height: this.$container.outerHeight(false)
|
|
};
|
|
|
|
container.top = offset.top;
|
|
container.bottom = offset.top + container.height;
|
|
|
|
var dropdown = {
|
|
height: this.$dropdown.outerHeight(false)
|
|
};
|
|
|
|
var viewport = {
|
|
top: $window.scrollTop(),
|
|
bottom: $window.scrollTop() + $window.height()
|
|
};
|
|
|
|
var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
|
|
var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
|
|
|
|
var css = {
|
|
left: offset.left,
|
|
top: container.bottom
|
|
};
|
|
|
|
// Determine what the parent element is to use for calciulating the offset
|
|
var $offsetParent = this.$dropdownParent;
|
|
|
|
// For statically positoned elements, we need to get the element
|
|
// that is determining the offset
|
|
if ($offsetParent.css('position') === 'static') {
|
|
$offsetParent = $offsetParent.offsetParent();
|
|
}
|
|
|
|
var parentOffset = $offsetParent.offset();
|
|
|
|
css.top -= parentOffset.top;
|
|
css.left -= parentOffset.left;
|
|
|
|
if (!isCurrentlyAbove && !isCurrentlyBelow) {
|
|
newDirection = 'below';
|
|
}
|
|
|
|
if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
|
|
newDirection = 'above';
|
|
} else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
|
|
newDirection = 'below';
|
|
}
|
|
|
|
if (newDirection == 'above' ||
|
|
(isCurrentlyAbove && newDirection !== 'below')) {
|
|
css.top = container.top - parentOffset.top - dropdown.height;
|
|
}
|
|
|
|
if (newDirection != null) {
|
|
this.$dropdown
|
|
.removeClass('select2-dropdown--below select2-dropdown--above')
|
|
.addClass('select2-dropdown--' + newDirection);
|
|
this.$container
|
|
.removeClass('select2-container--below select2-container--above')
|
|
.addClass('select2-container--' + newDirection);
|
|
}
|
|
|
|
this.$dropdownContainer.css(css);
|
|
};
|
|
|
|
AttachBody.prototype._resizeDropdown = function () {
|
|
var css = {
|
|
width: this.$container.outerWidth(false) + 'px'
|
|
};
|
|
|
|
if (this.options.get('dropdownAutoWidth')) {
|
|
css.minWidth = css.width;
|
|
css.position = 'relative';
|
|
css.width = 'auto';
|
|
}
|
|
|
|
this.$dropdown.css(css);
|
|
};
|
|
|
|
AttachBody.prototype._showDropdown = function (decorated) {
|
|
this.$dropdownContainer.appendTo(this.$dropdownParent);
|
|
|
|
this._positionDropdown();
|
|
this._resizeDropdown();
|
|
};
|
|
|
|
return AttachBody;
|
|
});
|
|
|
|
S2.define('select2/dropdown/minimumResultsForSearch',[
|
|
|
|
], function () {
|
|
function countResults (data) {
|
|
var count = 0;
|
|
|
|
for (var d = 0; d < data.length; d++) {
|
|
var item = data[d];
|
|
|
|
if (item.children) {
|
|
count += countResults(item.children);
|
|
} else {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
|
|
this.minimumResultsForSearch = options.get('minimumResultsForSearch');
|
|
|
|
if (this.minimumResultsForSearch < 0) {
|
|
this.minimumResultsForSearch = Infinity;
|
|
}
|
|
|
|
decorated.call(this, $element, options, dataAdapter);
|
|
}
|
|
|
|
MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
|
|
if (countResults(params.data.results) < this.minimumResultsForSearch) {
|
|
return false;
|
|
}
|
|
|
|
return decorated.call(this, params);
|
|
};
|
|
|
|
return MinimumResultsForSearch;
|
|
});
|
|
|
|
S2.define('select2/dropdown/selectOnClose',[
|
|
|
|
], function () {
|
|
function SelectOnClose () { }
|
|
|
|
SelectOnClose.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('close', function (params) {
|
|
self._handleSelectOnClose(params);
|
|
});
|
|
};
|
|
|
|
SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
|
|
if (params && params.originalSelect2Event != null) {
|
|
var event = params.originalSelect2Event;
|
|
|
|
// Don't select an item if the close event was triggered from a select or
|
|
// unselect event
|
|
if (event._type === 'select' || event._type === 'unselect') {
|
|
return;
|
|
}
|
|
}
|
|
|
|
var $highlightedResults = this.getHighlightedResults();
|
|
|
|
// Only select highlighted results
|
|
if ($highlightedResults.length < 1) {
|
|
return;
|
|
}
|
|
|
|
var data = $highlightedResults.data('data');
|
|
|
|
// Don't re-select already selected resulte
|
|
if (
|
|
(data.element != null && data.element.selected) ||
|
|
(data.element == null && data.selected)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('select', {
|
|
data: data
|
|
});
|
|
};
|
|
|
|
return SelectOnClose;
|
|
});
|
|
|
|
S2.define('select2/dropdown/closeOnSelect',[
|
|
|
|
], function () {
|
|
function CloseOnSelect () { }
|
|
|
|
CloseOnSelect.prototype.bind = function (decorated, container, $container) {
|
|
var self = this;
|
|
|
|
decorated.call(this, container, $container);
|
|
|
|
container.on('select', function (evt) {
|
|
self._selectTriggered(evt);
|
|
});
|
|
|
|
container.on('unselect', function (evt) {
|
|
self._selectTriggered(evt);
|
|
});
|
|
};
|
|
|
|
CloseOnSelect.prototype._selectTriggered = function (_, evt) {
|
|
var originalEvent = evt.originalEvent;
|
|
|
|
// Don't close if the control key is being held
|
|
if (originalEvent && originalEvent.ctrlKey) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('close', {
|
|
originalEvent: originalEvent,
|
|
originalSelect2Event: evt
|
|
});
|
|
};
|
|
|
|
return CloseOnSelect;
|
|
});
|
|
|
|
S2.define('select2/i18n/en',[],function () {
|
|
// English
|
|
return {
|
|
errorLoading: function () {
|
|
return 'The results could not be loaded.';
|
|
},
|
|
inputTooLong: function (args) {
|
|
var overChars = args.input.length - args.maximum;
|
|
|
|
var message = 'Please delete ' + overChars + ' character';
|
|
|
|
if (overChars != 1) {
|
|
message += 's';
|
|
}
|
|
|
|
return message;
|
|
},
|
|
inputTooShort: function (args) {
|
|
var remainingChars = args.minimum - args.input.length;
|
|
|
|
var message = 'Please enter ' + remainingChars + ' or more characters';
|
|
|
|
return message;
|
|
},
|
|
loadingMore: function () {
|
|
return 'Loading more results…';
|
|
},
|
|
maximumSelected: function (args) {
|
|
var message = 'You can only select ' + args.maximum + ' item';
|
|
|
|
if (args.maximum != 1) {
|
|
message += 's';
|
|
}
|
|
|
|
return message;
|
|
},
|
|
noResults: function () {
|
|
return 'No results found';
|
|
},
|
|
searching: function () {
|
|
return 'Searching…';
|
|
}
|
|
};
|
|
});
|
|
|
|
S2.define('select2/defaults',[
|
|
'jquery',
|
|
'require',
|
|
|
|
'./results',
|
|
|
|
'./selection/single',
|
|
'./selection/multiple',
|
|
'./selection/placeholder',
|
|
'./selection/allowClear',
|
|
'./selection/search',
|
|
'./selection/eventRelay',
|
|
|
|
'./utils',
|
|
'./translation',
|
|
'./diacritics',
|
|
|
|
'./data/select',
|
|
'./data/array',
|
|
'./data/ajax',
|
|
'./data/tags',
|
|
'./data/tokenizer',
|
|
'./data/minimumInputLength',
|
|
'./data/maximumInputLength',
|
|
'./data/maximumSelectionLength',
|
|
|
|
'./dropdown',
|
|
'./dropdown/search',
|
|
'./dropdown/hidePlaceholder',
|
|
'./dropdown/infiniteScroll',
|
|
'./dropdown/attachBody',
|
|
'./dropdown/minimumResultsForSearch',
|
|
'./dropdown/selectOnClose',
|
|
'./dropdown/closeOnSelect',
|
|
|
|
'./i18n/en'
|
|
], function ($, require,
|
|
|
|
ResultsList,
|
|
|
|
SingleSelection, MultipleSelection, Placeholder, AllowClear,
|
|
SelectionSearch, EventRelay,
|
|
|
|
Utils, Translation, DIACRITICS,
|
|
|
|
SelectData, ArrayData, AjaxData, Tags, Tokenizer,
|
|
MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
|
|
|
|
Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
|
|
AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
|
|
|
|
EnglishTranslation) {
|
|
function Defaults () {
|
|
this.reset();
|
|
}
|
|
|
|
Defaults.prototype.apply = function (options) {
|
|
options = $.extend(true, {}, this.defaults, options);
|
|
|
|
if (options.dataAdapter == null) {
|
|
if (options.ajax != null) {
|
|
options.dataAdapter = AjaxData;
|
|
} else if (options.data != null) {
|
|
options.dataAdapter = ArrayData;
|
|
} else {
|
|
options.dataAdapter = SelectData;
|
|
}
|
|
|
|
if (options.minimumInputLength > 0) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
MinimumInputLength
|
|
);
|
|
}
|
|
|
|
if (options.maximumInputLength > 0) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
MaximumInputLength
|
|
);
|
|
}
|
|
|
|
if (options.maximumSelectionLength > 0) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
MaximumSelectionLength
|
|
);
|
|
}
|
|
|
|
if (options.tags) {
|
|
options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
|
|
}
|
|
|
|
if (options.tokenSeparators != null || options.tokenizer != null) {
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
Tokenizer
|
|
);
|
|
}
|
|
|
|
if (options.query != null) {
|
|
var Query = require(options.amdBase + 'compat/query');
|
|
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
Query
|
|
);
|
|
}
|
|
|
|
if (options.initSelection != null) {
|
|
var InitSelection = require(options.amdBase + 'compat/initSelection');
|
|
|
|
options.dataAdapter = Utils.Decorate(
|
|
options.dataAdapter,
|
|
InitSelection
|
|
);
|
|
}
|
|
}
|
|
|
|
if (options.resultsAdapter == null) {
|
|
options.resultsAdapter = ResultsList;
|
|
|
|
if (options.ajax != null) {
|
|
options.resultsAdapter = Utils.Decorate(
|
|
options.resultsAdapter,
|
|
InfiniteScroll
|
|
);
|
|
}
|
|
|
|
if (options.placeholder != null) {
|
|
options.resultsAdapter = Utils.Decorate(
|
|
options.resultsAdapter,
|
|
HidePlaceholder
|
|
);
|
|
}
|
|
|
|
if (options.selectOnClose) {
|
|
options.resultsAdapter = Utils.Decorate(
|
|
options.resultsAdapter,
|
|
SelectOnClose
|
|
);
|
|
}
|
|
}
|
|
|
|
if (options.dropdownAdapter == null) {
|
|
if (options.multiple) {
|
|
options.dropdownAdapter = Dropdown;
|
|
} else {
|
|
var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
|
|
|
|
options.dropdownAdapter = SearchableDropdown;
|
|
}
|
|
|
|
if (options.minimumResultsForSearch !== 0) {
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
MinimumResultsForSearch
|
|
);
|
|
}
|
|
|
|
if (options.closeOnSelect) {
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
CloseOnSelect
|
|
);
|
|
}
|
|
|
|
if (
|
|
options.dropdownCssClass != null ||
|
|
options.dropdownCss != null ||
|
|
options.adaptDropdownCssClass != null
|
|
) {
|
|
var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
|
|
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
DropdownCSS
|
|
);
|
|
}
|
|
|
|
options.dropdownAdapter = Utils.Decorate(
|
|
options.dropdownAdapter,
|
|
AttachBody
|
|
);
|
|
}
|
|
|
|
if (options.selectionAdapter == null) {
|
|
if (options.multiple) {
|
|
options.selectionAdapter = MultipleSelection;
|
|
} else {
|
|
options.selectionAdapter = SingleSelection;
|
|
}
|
|
|
|
// Add the placeholder mixin if a placeholder was specified
|
|
if (options.placeholder != null) {
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
Placeholder
|
|
);
|
|
}
|
|
|
|
if (options.allowClear) {
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
AllowClear
|
|
);
|
|
}
|
|
|
|
if (options.multiple) {
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
SelectionSearch
|
|
);
|
|
}
|
|
|
|
if (
|
|
options.containerCssClass != null ||
|
|
options.containerCss != null ||
|
|
options.adaptContainerCssClass != null
|
|
) {
|
|
var ContainerCSS = require(options.amdBase + 'compat/containerCss');
|
|
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
ContainerCSS
|
|
);
|
|
}
|
|
|
|
options.selectionAdapter = Utils.Decorate(
|
|
options.selectionAdapter,
|
|
EventRelay
|
|
);
|
|
}
|
|
|
|
if (typeof options.language === 'string') {
|
|
// Check if the language is specified with a region
|
|
if (options.language.indexOf('-') > 0) {
|
|
// Extract the region information if it is included
|
|
var languageParts = options.language.split('-');
|
|
var baseLanguage = languageParts[0];
|
|
|
|
options.language = [options.language, baseLanguage];
|
|
} else {
|
|
options.language = [options.language];
|
|
}
|
|
}
|
|
|
|
if ($.isArray(options.language)) {
|
|
var languages = new Translation();
|
|
options.language.push('en');
|
|
|
|
var languageNames = options.language;
|
|
|
|
for (var l = 0; l < languageNames.length; l++) {
|
|
var name = languageNames[l];
|
|
var language = {};
|
|
|
|
try {
|
|
// Try to load it with the original name
|
|
language = Translation.loadPath(name);
|
|
} catch (e) {
|
|
try {
|
|
// If we couldn't load it, check if it wasn't the full path
|
|
name = this.defaults.amdLanguageBase + name;
|
|
language = Translation.loadPath(name);
|
|
} catch (ex) {
|
|
// The translation could not be loaded at all. Sometimes this is
|
|
// because of a configuration problem, other times this can be
|
|
// because of how Select2 helps load all possible translation files.
|
|
if (options.debug && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The language file for "' + name + '" could not be ' +
|
|
'automatically loaded. A fallback will be used instead.'
|
|
);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
languages.extend(language);
|
|
}
|
|
|
|
options.translations = languages;
|
|
} else {
|
|
var baseTranslation = Translation.loadPath(
|
|
this.defaults.amdLanguageBase + 'en'
|
|
);
|
|
var customTranslation = new Translation(options.language);
|
|
|
|
customTranslation.extend(baseTranslation);
|
|
|
|
options.translations = customTranslation;
|
|
}
|
|
|
|
return options;
|
|
};
|
|
|
|
Defaults.prototype.reset = function () {
|
|
function stripDiacritics (text) {
|
|
// Used 'uni range + named function' from http://jsperf.com/diacritics/18
|
|
function match(a) {
|
|
return DIACRITICS[a] || a;
|
|
}
|
|
|
|
return text.replace(/[^\u0000-\u007E]/g, match);
|
|
}
|
|
|
|
function matcher (params, data) {
|
|
// Always return the object if there is nothing to compare
|
|
if ($.trim(params.term) === '') {
|
|
return data;
|
|
}
|
|
|
|
// Do a recursive check for options with children
|
|
if (data.children && data.children.length > 0) {
|
|
// Clone the data object if there are children
|
|
// This is required as we modify the object to remove any non-matches
|
|
var match = $.extend(true, {}, data);
|
|
|
|
// Check each child of the option
|
|
for (var c = data.children.length - 1; c >= 0; c--) {
|
|
var child = data.children[c];
|
|
|
|
var matches = matcher(params, child);
|
|
|
|
// If there wasn't a match, remove the object in the array
|
|
if (matches == null) {
|
|
match.children.splice(c, 1);
|
|
}
|
|
}
|
|
|
|
// If any children matched, return the new object
|
|
if (match.children.length > 0) {
|
|
return match;
|
|
}
|
|
|
|
// If there were no matching children, check just the plain object
|
|
return matcher(params, match);
|
|
}
|
|
|
|
var original = stripDiacritics(data.text).toUpperCase();
|
|
var term = stripDiacritics(params.term).toUpperCase();
|
|
|
|
// Check if the text contains the term
|
|
if (original.indexOf(term) > -1) {
|
|
return data;
|
|
}
|
|
|
|
// If it doesn't contain the term, don't return anything
|
|
return null;
|
|
}
|
|
|
|
this.defaults = {
|
|
amdBase: './',
|
|
amdLanguageBase: './i18n/',
|
|
closeOnSelect: true,
|
|
debug: false,
|
|
dropdownAutoWidth: false,
|
|
escapeMarkup: Utils.escapeMarkup,
|
|
language: EnglishTranslation,
|
|
matcher: matcher,
|
|
minimumInputLength: 0,
|
|
maximumInputLength: 0,
|
|
maximumSelectionLength: 0,
|
|
minimumResultsForSearch: 0,
|
|
selectOnClose: false,
|
|
sorter: function (data) {
|
|
return data;
|
|
},
|
|
templateResult: function (result) {
|
|
return result.text;
|
|
},
|
|
templateSelection: function (selection) {
|
|
return selection.text;
|
|
},
|
|
theme: 'default',
|
|
width: 'resolve'
|
|
};
|
|
};
|
|
|
|
Defaults.prototype.set = function (key, value) {
|
|
var camelKey = $.camelCase(key);
|
|
|
|
var data = {};
|
|
data[camelKey] = value;
|
|
|
|
var convertedData = Utils._convertData(data);
|
|
|
|
$.extend(this.defaults, convertedData);
|
|
};
|
|
|
|
var defaults = new Defaults();
|
|
|
|
return defaults;
|
|
});
|
|
|
|
S2.define('select2/options',[
|
|
'require',
|
|
'jquery',
|
|
'./defaults',
|
|
'./utils'
|
|
], function (require, $, Defaults, Utils) {
|
|
function Options (options, $element) {
|
|
this.options = options;
|
|
|
|
if ($element != null) {
|
|
this.fromElement($element);
|
|
}
|
|
|
|
this.options = Defaults.apply(this.options);
|
|
|
|
if ($element && $element.is('input')) {
|
|
var InputCompat = require(this.get('amdBase') + 'compat/inputData');
|
|
|
|
this.options.dataAdapter = Utils.Decorate(
|
|
this.options.dataAdapter,
|
|
InputCompat
|
|
);
|
|
}
|
|
}
|
|
|
|
Options.prototype.fromElement = function ($e) {
|
|
var excludedData = ['select2'];
|
|
|
|
if (this.options.multiple == null) {
|
|
this.options.multiple = $e.prop('multiple');
|
|
}
|
|
|
|
if (this.options.disabled == null) {
|
|
this.options.disabled = $e.prop('disabled');
|
|
}
|
|
|
|
if (this.options.language == null) {
|
|
if ($e.prop('lang')) {
|
|
this.options.language = $e.prop('lang').toLowerCase();
|
|
} else if ($e.closest('[lang]').prop('lang')) {
|
|
this.options.language = $e.closest('[lang]').prop('lang');
|
|
}
|
|
}
|
|
|
|
if (this.options.dir == null) {
|
|
if ($e.prop('dir')) {
|
|
this.options.dir = $e.prop('dir');
|
|
} else if ($e.closest('[dir]').prop('dir')) {
|
|
this.options.dir = $e.closest('[dir]').prop('dir');
|
|
} else {
|
|
this.options.dir = 'ltr';
|
|
}
|
|
}
|
|
|
|
$e.prop('disabled', this.options.disabled);
|
|
$e.prop('multiple', this.options.multiple);
|
|
|
|
if ($e.data('select2Tags')) {
|
|
if (this.options.debug && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `data-select2-tags` attribute has been changed to ' +
|
|
'use the `data-data` and `data-tags="true"` attributes and will be ' +
|
|
'removed in future versions of Select2.'
|
|
);
|
|
}
|
|
|
|
$e.data('data', $e.data('select2Tags'));
|
|
$e.data('tags', true);
|
|
}
|
|
|
|
if ($e.data('ajaxUrl')) {
|
|
if (this.options.debug && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `data-ajax-url` attribute has been changed to ' +
|
|
'`data-ajax--url` and support for the old attribute will be removed' +
|
|
' in future versions of Select2.'
|
|
);
|
|
}
|
|
|
|
$e.attr('ajax--url', $e.data('ajaxUrl'));
|
|
$e.data('ajax--url', $e.data('ajaxUrl'));
|
|
}
|
|
|
|
var dataset = {};
|
|
|
|
// Prefer the element's `dataset` attribute if it exists
|
|
// jQuery 1.x does not correctly handle data attributes with multiple dashes
|
|
if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
|
|
dataset = $.extend(true, {}, $e[0].dataset, $e.data());
|
|
} else {
|
|
dataset = $e.data();
|
|
}
|
|
|
|
var data = $.extend(true, {}, dataset);
|
|
|
|
data = Utils._convertData(data);
|
|
|
|
for (var key in data) {
|
|
if ($.inArray(key, excludedData) > -1) {
|
|
continue;
|
|
}
|
|
|
|
if ($.isPlainObject(this.options[key])) {
|
|
$.extend(this.options[key], data[key]);
|
|
} else {
|
|
this.options[key] = data[key];
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Options.prototype.get = function (key) {
|
|
return this.options[key];
|
|
};
|
|
|
|
Options.prototype.set = function (key, val) {
|
|
this.options[key] = val;
|
|
};
|
|
|
|
return Options;
|
|
});
|
|
|
|
S2.define('select2/core',[
|
|
'jquery',
|
|
'./options',
|
|
'./utils',
|
|
'./keys'
|
|
], function ($, Options, Utils, KEYS) {
|
|
var Select2 = function ($element, options) {
|
|
if ($element.data('select2') != null) {
|
|
$element.data('select2').destroy();
|
|
}
|
|
|
|
this.$element = $element;
|
|
|
|
this.id = this._generateId($element);
|
|
|
|
options = options || {};
|
|
|
|
this.options = new Options(options, $element);
|
|
|
|
Select2.__super__.constructor.call(this);
|
|
|
|
// Set up the tabindex
|
|
|
|
var tabindex = $element.attr('tabindex') || 0;
|
|
$element.data('old-tabindex', tabindex);
|
|
$element.attr('tabindex', '-1');
|
|
|
|
// Set up containers and adapters
|
|
|
|
var DataAdapter = this.options.get('dataAdapter');
|
|
this.dataAdapter = new DataAdapter($element, this.options);
|
|
|
|
var $container = this.render();
|
|
|
|
this._placeContainer($container);
|
|
|
|
var SelectionAdapter = this.options.get('selectionAdapter');
|
|
this.selection = new SelectionAdapter($element, this.options);
|
|
this.$selection = this.selection.render();
|
|
|
|
this.selection.position(this.$selection, $container);
|
|
|
|
var DropdownAdapter = this.options.get('dropdownAdapter');
|
|
this.dropdown = new DropdownAdapter($element, this.options);
|
|
this.$dropdown = this.dropdown.render();
|
|
|
|
this.dropdown.position(this.$dropdown, $container);
|
|
|
|
var ResultsAdapter = this.options.get('resultsAdapter');
|
|
this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
|
|
this.$results = this.results.render();
|
|
|
|
this.results.position(this.$results, this.$dropdown);
|
|
|
|
// Bind events
|
|
|
|
var self = this;
|
|
|
|
// Bind the container to all of the adapters
|
|
this._bindAdapters();
|
|
|
|
// Register any DOM event handlers
|
|
this._registerDomEvents();
|
|
|
|
// Register any internal event handlers
|
|
this._registerDataEvents();
|
|
this._registerSelectionEvents();
|
|
this._registerDropdownEvents();
|
|
this._registerResultsEvents();
|
|
this._registerEvents();
|
|
|
|
// Set the initial state
|
|
this.dataAdapter.current(function (initialData) {
|
|
self.trigger('selection:update', {
|
|
data: initialData
|
|
});
|
|
});
|
|
|
|
// Hide the original select
|
|
$element.addClass('select2-hidden-accessible');
|
|
$element.attr('aria-hidden', 'true');
|
|
|
|
// Synchronize any monitored attributes
|
|
this._syncAttributes();
|
|
|
|
$element.data('select2', this);
|
|
};
|
|
|
|
Utils.Extend(Select2, Utils.Observable);
|
|
|
|
Select2.prototype._generateId = function ($element) {
|
|
var id = '';
|
|
|
|
if ($element.attr('id') != null) {
|
|
id = $element.attr('id');
|
|
} else if ($element.attr('name') != null) {
|
|
id = $element.attr('name') + '-' + Utils.generateChars(2);
|
|
} else {
|
|
id = Utils.generateChars(4);
|
|
}
|
|
|
|
id = id.replace(/(:|\.|\[|\]|,)/g, '');
|
|
id = 'select2-' + id;
|
|
|
|
return id;
|
|
};
|
|
|
|
Select2.prototype._placeContainer = function ($container) {
|
|
$container.insertAfter(this.$element);
|
|
|
|
var width = this._resolveWidth(this.$element, this.options.get('width'));
|
|
|
|
if (width != null) {
|
|
$container.css('width', width);
|
|
}
|
|
};
|
|
|
|
Select2.prototype._resolveWidth = function ($element, method) {
|
|
var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
|
|
|
|
if (method == 'resolve') {
|
|
var styleWidth = this._resolveWidth($element, 'style');
|
|
|
|
if (styleWidth != null) {
|
|
return styleWidth;
|
|
}
|
|
|
|
return this._resolveWidth($element, 'element');
|
|
}
|
|
|
|
if (method == 'element') {
|
|
var elementWidth = $element.outerWidth(false);
|
|
|
|
if (elementWidth <= 0) {
|
|
return 'auto';
|
|
}
|
|
|
|
return elementWidth + 'px';
|
|
}
|
|
|
|
if (method == 'style') {
|
|
var style = $element.attr('style');
|
|
|
|
if (typeof(style) !== 'string') {
|
|
return null;
|
|
}
|
|
|
|
var attrs = style.split(';');
|
|
|
|
for (var i = 0, l = attrs.length; i < l; i = i + 1) {
|
|
var attr = attrs[i].replace(/\s/g, '');
|
|
var matches = attr.match(WIDTH);
|
|
|
|
if (matches !== null && matches.length >= 1) {
|
|
return matches[1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return method;
|
|
};
|
|
|
|
Select2.prototype._bindAdapters = function () {
|
|
this.dataAdapter.bind(this, this.$container);
|
|
this.selection.bind(this, this.$container);
|
|
|
|
this.dropdown.bind(this, this.$container);
|
|
this.results.bind(this, this.$container);
|
|
};
|
|
|
|
Select2.prototype._registerDomEvents = function () {
|
|
var self = this;
|
|
|
|
this.$element.on('change.select2', function () {
|
|
self.dataAdapter.current(function (data) {
|
|
self.trigger('selection:update', {
|
|
data: data
|
|
});
|
|
});
|
|
});
|
|
|
|
this.$element.on('focus.select2', function (evt) {
|
|
self.trigger('focus', evt);
|
|
});
|
|
|
|
this._syncA = Utils.bind(this._syncAttributes, this);
|
|
this._syncS = Utils.bind(this._syncSubtree, this);
|
|
|
|
if (this.$element[0].attachEvent) {
|
|
this.$element[0].attachEvent('onpropertychange', this._syncA);
|
|
}
|
|
|
|
var observer = window.MutationObserver ||
|
|
window.WebKitMutationObserver ||
|
|
window.MozMutationObserver
|
|
;
|
|
|
|
if (observer != null) {
|
|
this._observer = new observer(function (mutations) {
|
|
$.each(mutations, self._syncA);
|
|
$.each(mutations, self._syncS);
|
|
});
|
|
this._observer.observe(this.$element[0], {
|
|
attributes: true,
|
|
childList: true,
|
|
subtree: false
|
|
});
|
|
} else if (this.$element[0].addEventListener) {
|
|
this.$element[0].addEventListener(
|
|
'DOMAttrModified',
|
|
self._syncA,
|
|
false
|
|
);
|
|
this.$element[0].addEventListener(
|
|
'DOMNodeInserted',
|
|
self._syncS,
|
|
false
|
|
);
|
|
this.$element[0].addEventListener(
|
|
'DOMNodeRemoved',
|
|
self._syncS,
|
|
false
|
|
);
|
|
}
|
|
};
|
|
|
|
Select2.prototype._registerDataEvents = function () {
|
|
var self = this;
|
|
|
|
this.dataAdapter.on('*', function (name, params) {
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerSelectionEvents = function () {
|
|
var self = this;
|
|
var nonRelayEvents = ['toggle', 'focus'];
|
|
|
|
this.selection.on('toggle', function () {
|
|
self.toggleDropdown();
|
|
});
|
|
|
|
this.selection.on('focus', function (params) {
|
|
self.focus(params);
|
|
});
|
|
|
|
this.selection.on('*', function (name, params) {
|
|
if ($.inArray(name, nonRelayEvents) !== -1) {
|
|
return;
|
|
}
|
|
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerDropdownEvents = function () {
|
|
var self = this;
|
|
|
|
this.dropdown.on('*', function (name, params) {
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerResultsEvents = function () {
|
|
var self = this;
|
|
|
|
this.results.on('*', function (name, params) {
|
|
self.trigger(name, params);
|
|
});
|
|
};
|
|
|
|
Select2.prototype._registerEvents = function () {
|
|
var self = this;
|
|
|
|
this.on('open', function () {
|
|
self.$container.addClass('select2-container--open');
|
|
});
|
|
|
|
this.on('close', function () {
|
|
self.$container.removeClass('select2-container--open');
|
|
});
|
|
|
|
this.on('enable', function () {
|
|
self.$container.removeClass('select2-container--disabled');
|
|
});
|
|
|
|
this.on('disable', function () {
|
|
self.$container.addClass('select2-container--disabled');
|
|
});
|
|
|
|
this.on('blur', function () {
|
|
self.$container.removeClass('select2-container--focus');
|
|
});
|
|
|
|
this.on('query', function (params) {
|
|
if (!self.isOpen()) {
|
|
self.trigger('open', {});
|
|
}
|
|
|
|
this.dataAdapter.query(params, function (data) {
|
|
self.trigger('results:all', {
|
|
data: data,
|
|
query: params
|
|
});
|
|
});
|
|
});
|
|
|
|
this.on('query:append', function (params) {
|
|
this.dataAdapter.query(params, function (data) {
|
|
self.trigger('results:append', {
|
|
data: data,
|
|
query: params
|
|
});
|
|
});
|
|
});
|
|
|
|
this.on('keypress', function (evt) {
|
|
var key = evt.which;
|
|
|
|
if (self.isOpen()) {
|
|
if (key === KEYS.ESC || key === KEYS.TAB ||
|
|
(key === KEYS.UP && evt.altKey)) {
|
|
self.close();
|
|
|
|
evt.preventDefault();
|
|
} else if (key === KEYS.ENTER) {
|
|
self.trigger('results:select', {});
|
|
|
|
evt.preventDefault();
|
|
} else if ((key === KEYS.SPACE && evt.ctrlKey)) {
|
|
self.trigger('results:toggle', {});
|
|
|
|
evt.preventDefault();
|
|
} else if (key === KEYS.UP) {
|
|
self.trigger('results:previous', {});
|
|
|
|
evt.preventDefault();
|
|
} else if (key === KEYS.DOWN) {
|
|
self.trigger('results:next', {});
|
|
|
|
evt.preventDefault();
|
|
}
|
|
} else {
|
|
if (key === KEYS.ENTER || key === KEYS.SPACE ||
|
|
(key === KEYS.DOWN && evt.altKey)) {
|
|
self.open();
|
|
|
|
evt.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Select2.prototype._syncAttributes = function () {
|
|
this.options.set('disabled', this.$element.prop('disabled'));
|
|
|
|
if (this.options.get('disabled')) {
|
|
if (this.isOpen()) {
|
|
this.close();
|
|
}
|
|
|
|
this.trigger('disable', {});
|
|
} else {
|
|
this.trigger('enable', {});
|
|
}
|
|
};
|
|
|
|
Select2.prototype._syncSubtree = function (evt, mutations) {
|
|
var changed = false;
|
|
var self = this;
|
|
|
|
// Ignore any mutation events raised for elements that aren't options or
|
|
// optgroups. This handles the case when the select element is destroyed
|
|
if (
|
|
evt && evt.target && (
|
|
evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (!mutations) {
|
|
// If mutation events aren't supported, then we can only assume that the
|
|
// change affected the selections
|
|
changed = true;
|
|
} else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
|
|
for (var n = 0; n < mutations.addedNodes.length; n++) {
|
|
var node = mutations.addedNodes[n];
|
|
|
|
if (node.selected) {
|
|
changed = true;
|
|
}
|
|
}
|
|
} else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
|
|
changed = true;
|
|
}
|
|
|
|
// Only re-pull the data if we think there is a change
|
|
if (changed) {
|
|
this.dataAdapter.current(function (currentData) {
|
|
self.trigger('selection:update', {
|
|
data: currentData
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Override the trigger method to automatically trigger pre-events when
|
|
* there are events that can be prevented.
|
|
*/
|
|
Select2.prototype.trigger = function (name, args) {
|
|
var actualTrigger = Select2.__super__.trigger;
|
|
var preTriggerMap = {
|
|
'open': 'opening',
|
|
'close': 'closing',
|
|
'select': 'selecting',
|
|
'unselect': 'unselecting'
|
|
};
|
|
|
|
if (args === undefined) {
|
|
args = {};
|
|
}
|
|
|
|
if (name in preTriggerMap) {
|
|
var preTriggerName = preTriggerMap[name];
|
|
var preTriggerArgs = {
|
|
prevented: false,
|
|
name: name,
|
|
args: args
|
|
};
|
|
|
|
actualTrigger.call(this, preTriggerName, preTriggerArgs);
|
|
|
|
if (preTriggerArgs.prevented) {
|
|
args.prevented = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
actualTrigger.call(this, name, args);
|
|
};
|
|
|
|
Select2.prototype.toggleDropdown = function () {
|
|
if (this.options.get('disabled')) {
|
|
return;
|
|
}
|
|
|
|
if (this.isOpen()) {
|
|
this.close();
|
|
} else {
|
|
this.open();
|
|
}
|
|
};
|
|
|
|
Select2.prototype.open = function () {
|
|
if (this.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('query', {});
|
|
};
|
|
|
|
Select2.prototype.close = function () {
|
|
if (!this.isOpen()) {
|
|
return;
|
|
}
|
|
|
|
this.trigger('close', {});
|
|
};
|
|
|
|
Select2.prototype.isOpen = function () {
|
|
return this.$container.hasClass('select2-container--open');
|
|
};
|
|
|
|
Select2.prototype.hasFocus = function () {
|
|
return this.$container.hasClass('select2-container--focus');
|
|
};
|
|
|
|
Select2.prototype.focus = function (data) {
|
|
// No need to re-trigger focus events if we are already focused
|
|
if (this.hasFocus()) {
|
|
return;
|
|
}
|
|
|
|
this.$container.addClass('select2-container--focus');
|
|
this.trigger('focus', {});
|
|
};
|
|
|
|
Select2.prototype.enable = function (args) {
|
|
if (this.options.get('debug') && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `select2("enable")` method has been deprecated and will' +
|
|
' be removed in later Select2 versions. Use $element.prop("disabled")' +
|
|
' instead.'
|
|
);
|
|
}
|
|
|
|
if (args == null || args.length === 0) {
|
|
args = [true];
|
|
}
|
|
|
|
var disabled = !args[0];
|
|
|
|
this.$element.prop('disabled', disabled);
|
|
};
|
|
|
|
Select2.prototype.data = function () {
|
|
if (this.options.get('debug') &&
|
|
arguments.length > 0 && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: Data can no longer be set using `select2("data")`. You ' +
|
|
'should consider setting the value instead using `$element.val()`.'
|
|
);
|
|
}
|
|
|
|
var data = [];
|
|
|
|
this.dataAdapter.current(function (currentData) {
|
|
data = currentData;
|
|
});
|
|
|
|
return data;
|
|
};
|
|
|
|
Select2.prototype.val = function (args) {
|
|
if (this.options.get('debug') && window.console && console.warn) {
|
|
console.warn(
|
|
'Select2: The `select2("val")` method has been deprecated and will be' +
|
|
' removed in later Select2 versions. Use $element.val() instead.'
|
|
);
|
|
}
|
|
|
|
if (args == null || args.length === 0) {
|
|
return this.$element.val();
|
|
}
|
|
|
|
var newVal = args[0];
|
|
|
|
if ($.isArray(newVal)) {
|
|
newVal = $.map(newVal, function (obj) {
|
|
return obj.toString();
|
|
});
|
|
}
|
|
|
|
this.$element.val(newVal).trigger('change');
|
|
};
|
|
|
|
Select2.prototype.destroy = function () {
|
|
this.$container.remove();
|
|
|
|
if (this.$element[0].detachEvent) {
|
|
this.$element[0].detachEvent('onpropertychange', this._syncA);
|
|
}
|
|
|
|
if (this._observer != null) {
|
|
this._observer.disconnect();
|
|
this._observer = null;
|
|
} else if (this.$element[0].removeEventListener) {
|
|
this.$element[0]
|
|
.removeEventListener('DOMAttrModified', this._syncA, false);
|
|
this.$element[0]
|
|
.removeEventListener('DOMNodeInserted', this._syncS, false);
|
|
this.$element[0]
|
|
.removeEventListener('DOMNodeRemoved', this._syncS, false);
|
|
}
|
|
|
|
this._syncA = null;
|
|
this._syncS = null;
|
|
|
|
this.$element.off('.select2');
|
|
this.$element.attr('tabindex', this.$element.data('old-tabindex'));
|
|
|
|
this.$element.removeClass('select2-hidden-accessible');
|
|
this.$element.attr('aria-hidden', 'false');
|
|
this.$element.removeData('select2');
|
|
|
|
this.dataAdapter.destroy();
|
|
this.selection.destroy();
|
|
this.dropdown.destroy();
|
|
this.results.destroy();
|
|
|
|
this.dataAdapter = null;
|
|
this.selection = null;
|
|
this.dropdown = null;
|
|
this.results = null;
|
|
};
|
|
|
|
Select2.prototype.render = function () {
|
|
var $container = $(
|
|
'<span class="select2 select2-container">' +
|
|
'<span class="selection"></span>' +
|
|
'<span class="dropdown-wrapper" aria-hidden="true"></span>' +
|
|
'</span>'
|
|
);
|
|
|
|
$container.attr('dir', this.options.get('dir'));
|
|
|
|
this.$container = $container;
|
|
|
|
this.$container.addClass('select2-container--' + this.options.get('theme'));
|
|
|
|
$container.data('element', this.$element);
|
|
|
|
return $container;
|
|
};
|
|
|
|
return Select2;
|
|
});
|
|
|
|
S2.define('jquery-mousewheel',[
|
|
'jquery'
|
|
], function ($) {
|
|
// Used to shim jQuery.mousewheel for non-full builds.
|
|
return $;
|
|
});
|
|
|
|
S2.define('jquery.select2',[
|
|
'jquery',
|
|
'jquery-mousewheel',
|
|
|
|
'./select2/core',
|
|
'./select2/defaults'
|
|
], function ($, _, Select2, Defaults) {
|
|
if ($.fn.select2 == null) {
|
|
// All methods that should return the element
|
|
var thisMethods = ['open', 'close', 'destroy'];
|
|
|
|
$.fn.select2 = function (options) {
|
|
options = options || {};
|
|
|
|
if (typeof options === 'object') {
|
|
this.each(function () {
|
|
var instanceOptions = $.extend(true, {}, options);
|
|
|
|
var instance = new Select2($(this), instanceOptions);
|
|
});
|
|
|
|
return this;
|
|
} else if (typeof options === 'string') {
|
|
var ret;
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
|
|
this.each(function () {
|
|
var instance = $(this).data('select2');
|
|
|
|
if (instance == null && window.console && console.error) {
|
|
console.error(
|
|
'The select2(\'' + options + '\') method was called on an ' +
|
|
'element that is not using Select2.'
|
|
);
|
|
}
|
|
|
|
ret = instance[options].apply(instance, args);
|
|
});
|
|
|
|
// Check if we should be returning `this`
|
|
if ($.inArray(options, thisMethods) > -1) {
|
|
return this;
|
|
}
|
|
|
|
return ret;
|
|
} else {
|
|
throw new Error('Invalid arguments for Select2: ' + options);
|
|
}
|
|
};
|
|
}
|
|
|
|
if ($.fn.select2.defaults == null) {
|
|
$.fn.select2.defaults = Defaults;
|
|
}
|
|
|
|
return Select2;
|
|
});
|
|
|
|
// Return the AMD loader configuration so it can be used outside of this file
|
|
return {
|
|
define: S2.define,
|
|
require: S2.require
|
|
};
|
|
}());
|
|
|
|
// Autoload the jQuery bindings
|
|
// We know that all of the modules exist above this, so we're safe
|
|
var select2 = S2.require('jquery.select2');
|
|
|
|
// Hold the AMD module references on the jQuery function that was just loaded
|
|
// This allows Select2 to use the internal loader outside of this file, such
|
|
// as in the language files.
|
|
jQuery.fn.select2.amd = S2;
|
|
|
|
// Return the Select2 instance for anyone who is importing it.
|
|
return select2;
|
|
}));
|
|
|
|
/*!
|
|
* iCheck v1.0.2, http://git.io/arlzeA
|
|
* ===================================
|
|
* Powerful jQuery and Zepto plugin for checkboxes and radio buttons customization
|
|
*
|
|
* (c) 2013 Damir Sultanov, http://fronteed.com
|
|
* MIT Licensed
|
|
*/
|
|
|
|
(function($) {
|
|
|
|
// Cached vars
|
|
var _iCheck = 'iCheck',
|
|
_iCheckHelper = _iCheck + '-helper',
|
|
_checkbox = 'checkbox',
|
|
_radio = 'radio',
|
|
_checked = 'checked',
|
|
_unchecked = 'un' + _checked,
|
|
_disabled = 'disabled',
|
|
_determinate = 'determinate',
|
|
_indeterminate = 'in' + _determinate,
|
|
_update = 'update',
|
|
_type = 'type',
|
|
_click = 'click',
|
|
_touch = 'touchbegin.i touchend.i',
|
|
_add = 'addClass',
|
|
_remove = 'removeClass',
|
|
_callback = 'trigger',
|
|
_label = 'label',
|
|
_cursor = 'cursor',
|
|
_mobile = /ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);
|
|
|
|
// Plugin init
|
|
$.fn[_iCheck] = function(options, fire) {
|
|
|
|
// Walker
|
|
var handle = 'input[type="' + _checkbox + '"], input[type="' + _radio + '"]',
|
|
stack = $(),
|
|
walker = function(object) {
|
|
object.each(function() {
|
|
var self = $(this);
|
|
|
|
if (self.is(handle)) {
|
|
stack = stack.add(self);
|
|
} else {
|
|
stack = stack.add(self.find(handle));
|
|
}
|
|
});
|
|
};
|
|
|
|
// Check if we should operate with some method
|
|
if (/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(options)) {
|
|
|
|
// Normalize method's name
|
|
options = options.toLowerCase();
|
|
|
|
// Find checkboxes and radio buttons
|
|
walker(this);
|
|
|
|
return stack.each(function() {
|
|
var self = $(this);
|
|
|
|
if (options == 'destroy') {
|
|
tidy(self, 'ifDestroyed');
|
|
} else {
|
|
operate(self, true, options);
|
|
}
|
|
|
|
// Fire method's callback
|
|
if ($.isFunction(fire)) {
|
|
fire();
|
|
}
|
|
});
|
|
|
|
// Customization
|
|
} else if (typeof options == 'object' || !options) {
|
|
|
|
// Check if any options were passed
|
|
var settings = $.extend({
|
|
checkedClass: _checked,
|
|
disabledClass: _disabled,
|
|
indeterminateClass: _indeterminate,
|
|
labelHover: true
|
|
}, options),
|
|
|
|
selector = settings.handle,
|
|
hoverClass = settings.hoverClass || 'hover',
|
|
focusClass = settings.focusClass || 'focus',
|
|
activeClass = settings.activeClass || 'active',
|
|
labelHover = !!settings.labelHover,
|
|
labelHoverClass = settings.labelHoverClass || 'hover',
|
|
|
|
// Setup clickable area
|
|
area = ('' + settings.increaseArea).replace('%', '') | 0;
|
|
|
|
// Selector limit
|
|
if (selector == _checkbox || selector == _radio) {
|
|
handle = 'input[type="' + selector + '"]';
|
|
}
|
|
|
|
// Clickable area limit
|
|
if (area < -50) {
|
|
area = -50;
|
|
}
|
|
|
|
// Walk around the selector
|
|
walker(this);
|
|
|
|
return stack.each(function() {
|
|
var self = $(this);
|
|
|
|
// If already customized
|
|
tidy(self);
|
|
|
|
var node = this,
|
|
id = node.id,
|
|
|
|
// Layer styles
|
|
offset = -area + '%',
|
|
size = 100 + (area * 2) + '%',
|
|
layer = {
|
|
position: 'absolute',
|
|
top: offset,
|
|
left: offset,
|
|
display: 'block',
|
|
width: size,
|
|
height: size,
|
|
margin: 0,
|
|
padding: 0,
|
|
background: '#fff',
|
|
border: 0,
|
|
opacity: 0
|
|
},
|
|
|
|
// Choose how to hide input
|
|
hide = _mobile ? {
|
|
position: 'absolute',
|
|
visibility: 'hidden'
|
|
} : area ? layer : {
|
|
position: 'absolute',
|
|
opacity: 0
|
|
},
|
|
|
|
// Get proper class
|
|
className = node[_type] == _checkbox ? settings.checkboxClass || 'i' + _checkbox : settings.radioClass || 'i' + _radio,
|
|
|
|
// Find assigned labels
|
|
label = $(_label + '[for="' + id + '"]').add(self.closest(_label)),
|
|
|
|
// Check ARIA option
|
|
aria = !!settings.aria,
|
|
|
|
// Set ARIA placeholder
|
|
ariaID = _iCheck + '-' + Math.random().toString(36).substr(2,6),
|
|
|
|
// Parent & helper
|
|
parent = '<div class="' + className + '" ' + (aria ? 'role="' + node[_type] + '" ' : ''),
|
|
helper;
|
|
|
|
// Set ARIA "labelledby"
|
|
if (aria) {
|
|
label.each(function() {
|
|
parent += 'aria-labelledby="';
|
|
|
|
if (this.id) {
|
|
parent += this.id;
|
|
} else {
|
|
this.id = ariaID;
|
|
parent += ariaID;
|
|
}
|
|
|
|
parent += '"';
|
|
});
|
|
}
|
|
|
|
// Wrap input
|
|
parent = self.wrap(parent + '/>')[_callback]('ifCreated').parent().append(settings.insert);
|
|
|
|
// Layer addition
|
|
helper = $('<ins class="' + _iCheckHelper + '"/>').css(layer).appendTo(parent);
|
|
|
|
// Finalize customization
|
|
self.data(_iCheck, {o: settings, s: self.attr('style')}).css(hide);
|
|
!!settings.inheritClass && parent[_add](node.className || '');
|
|
!!settings.inheritID && id && parent.attr('id', _iCheck + '-' + id);
|
|
parent.css('position') == 'static' && parent.css('position', 'relative');
|
|
operate(self, true, _update);
|
|
|
|
// Label events
|
|
if (label.length) {
|
|
label.on(_click + '.i mouseover.i mouseout.i ' + _touch, function(event) {
|
|
var type = event[_type],
|
|
item = $(this);
|
|
|
|
// Do nothing if input is disabled
|
|
if (!node[_disabled]) {
|
|
|
|
// Click
|
|
if (type == _click) {
|
|
if ($(event.target).is('a')) {
|
|
return;
|
|
}
|
|
operate(self, false, true);
|
|
|
|
// Hover state
|
|
} else if (labelHover) {
|
|
|
|
// mouseout|touchend
|
|
if (/ut|nd/.test(type)) {
|
|
parent[_remove](hoverClass);
|
|
item[_remove](labelHoverClass);
|
|
} else {
|
|
parent[_add](hoverClass);
|
|
item[_add](labelHoverClass);
|
|
}
|
|
}
|
|
|
|
if (_mobile) {
|
|
event.stopPropagation();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Input events
|
|
self.on(_click + '.i focus.i blur.i keyup.i keydown.i keypress.i', function(event) {
|
|
var type = event[_type],
|
|
key = event.keyCode;
|
|
|
|
// Click
|
|
if (type == _click) {
|
|
return false;
|
|
|
|
// Keydown
|
|
} else if (type == 'keydown' && key == 32) {
|
|
if (!(node[_type] == _radio && node[_checked])) {
|
|
if (node[_checked]) {
|
|
off(self, _checked);
|
|
} else {
|
|
on(self, _checked);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
// Keyup
|
|
} else if (type == 'keyup' && node[_type] == _radio) {
|
|
!node[_checked] && on(self, _checked);
|
|
|
|
// Focus/blur
|
|
} else if (/us|ur/.test(type)) {
|
|
parent[type == 'blur' ? _remove : _add](focusClass);
|
|
}
|
|
});
|
|
|
|
// Helper events
|
|
helper.on(_click + ' mousedown mouseup mouseover mouseout ' + _touch, function(event) {
|
|
var type = event[_type],
|
|
|
|
// mousedown|mouseup
|
|
toggle = /wn|up/.test(type) ? activeClass : hoverClass;
|
|
|
|
// Do nothing if input is disabled
|
|
if (!node[_disabled]) {
|
|
|
|
// Click
|
|
if (type == _click) {
|
|
operate(self, false, true);
|
|
|
|
// Active and hover states
|
|
} else {
|
|
|
|
// State is on
|
|
if (/wn|er|in/.test(type)) {
|
|
|
|
// mousedown|mouseover|touchbegin
|
|
parent[_add](toggle);
|
|
|
|
// State is off
|
|
} else {
|
|
parent[_remove](toggle + ' ' + activeClass);
|
|
}
|
|
|
|
// Label hover
|
|
if (label.length && labelHover && toggle == hoverClass) {
|
|
|
|
// mouseout|touchend
|
|
label[/ut|nd/.test(type) ? _remove : _add](labelHoverClass);
|
|
}
|
|
}
|
|
|
|
if (_mobile) {
|
|
event.stopPropagation();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
return this;
|
|
}
|
|
};
|
|
|
|
// Do something with inputs
|
|
function operate(input, direct, method) {
|
|
var node = input[0],
|
|
state = /er/.test(method) ? _indeterminate : /bl/.test(method) ? _disabled : _checked,
|
|
active = method == _update ? {
|
|
checked: node[_checked],
|
|
disabled: node[_disabled],
|
|
indeterminate: input.attr(_indeterminate) == 'true' || input.attr(_determinate) == 'false'
|
|
} : node[state];
|
|
|
|
// Check, disable or indeterminate
|
|
if (/^(ch|di|in)/.test(method) && !active) {
|
|
on(input, state);
|
|
|
|
// Uncheck, enable or determinate
|
|
} else if (/^(un|en|de)/.test(method) && active) {
|
|
off(input, state);
|
|
|
|
// Update
|
|
} else if (method == _update) {
|
|
|
|
// Handle states
|
|
for (var each in active) {
|
|
if (active[each]) {
|
|
on(input, each, true);
|
|
} else {
|
|
off(input, each, true);
|
|
}
|
|
}
|
|
|
|
} else if (!direct || method == 'toggle') {
|
|
|
|
// Helper or label was clicked
|
|
if (!direct) {
|
|
input[_callback]('ifClicked');
|
|
}
|
|
|
|
// Toggle checked state
|
|
if (active) {
|
|
if (node[_type] !== _radio) {
|
|
off(input, state);
|
|
}
|
|
} else {
|
|
on(input, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add checked, disabled or indeterminate state
|
|
function on(input, state, keep) {
|
|
var node = input[0],
|
|
parent = input.parent(),
|
|
checked = state == _checked,
|
|
indeterminate = state == _indeterminate,
|
|
disabled = state == _disabled,
|
|
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
|
|
regular = option(input, callback + capitalize(node[_type])),
|
|
specific = option(input, state + capitalize(node[_type]));
|
|
|
|
// Prevent unnecessary actions
|
|
if (node[state] !== true) {
|
|
|
|
// Toggle assigned radio buttons
|
|
if (!keep && state == _checked && node[_type] == _radio && node.name) {
|
|
var form = input.closest('form'),
|
|
inputs = 'input[name="' + node.name + '"]';
|
|
|
|
inputs = form.length ? form.find(inputs) : $(inputs);
|
|
|
|
inputs.each(function() {
|
|
if (this !== node && $(this).data(_iCheck)) {
|
|
off($(this), state);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Indeterminate state
|
|
if (indeterminate) {
|
|
|
|
// Add indeterminate state
|
|
node[state] = true;
|
|
|
|
// Remove checked state
|
|
if (node[_checked]) {
|
|
off(input, _checked, 'force');
|
|
}
|
|
|
|
// Checked or disabled state
|
|
} else {
|
|
|
|
// Add checked or disabled state
|
|
if (!keep) {
|
|
node[state] = true;
|
|
}
|
|
|
|
// Remove indeterminate state
|
|
if (checked && node[_indeterminate]) {
|
|
off(input, _indeterminate, false);
|
|
}
|
|
}
|
|
|
|
// Trigger callbacks
|
|
callbacks(input, checked, state, keep);
|
|
}
|
|
|
|
// Add proper cursor
|
|
if (node[_disabled] && !!option(input, _cursor, true)) {
|
|
parent.find('.' + _iCheckHelper).css(_cursor, 'default');
|
|
}
|
|
|
|
// Add state class
|
|
parent[_add](specific || option(input, state) || '');
|
|
|
|
// Set ARIA attribute
|
|
if (!!parent.attr('role') && !indeterminate) {
|
|
parent.attr('aria-' + (disabled ? _disabled : _checked), 'true');
|
|
}
|
|
|
|
// Remove regular state class
|
|
parent[_remove](regular || option(input, callback) || '');
|
|
}
|
|
|
|
// Remove checked, disabled or indeterminate state
|
|
function off(input, state, keep) {
|
|
var node = input[0],
|
|
parent = input.parent(),
|
|
checked = state == _checked,
|
|
indeterminate = state == _indeterminate,
|
|
disabled = state == _disabled,
|
|
callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
|
|
regular = option(input, callback + capitalize(node[_type])),
|
|
specific = option(input, state + capitalize(node[_type]));
|
|
|
|
// Prevent unnecessary actions
|
|
if (node[state] !== false) {
|
|
|
|
// Toggle state
|
|
if (indeterminate || !keep || keep == 'force') {
|
|
node[state] = false;
|
|
}
|
|
|
|
// Trigger callbacks
|
|
callbacks(input, checked, callback, keep);
|
|
}
|
|
|
|
// Add proper cursor
|
|
if (!node[_disabled] && !!option(input, _cursor, true)) {
|
|
parent.find('.' + _iCheckHelper).css(_cursor, 'pointer');
|
|
}
|
|
|
|
// Remove state class
|
|
parent[_remove](specific || option(input, state) || '');
|
|
|
|
// Set ARIA attribute
|
|
if (!!parent.attr('role') && !indeterminate) {
|
|
parent.attr('aria-' + (disabled ? _disabled : _checked), 'false');
|
|
}
|
|
|
|
// Add regular state class
|
|
parent[_add](regular || option(input, callback) || '');
|
|
}
|
|
|
|
// Remove all traces
|
|
function tidy(input, callback) {
|
|
if (input.data(_iCheck)) {
|
|
|
|
// Remove everything except input
|
|
input.parent().html(input.attr('style', input.data(_iCheck).s || ''));
|
|
|
|
// Callback
|
|
if (callback) {
|
|
input[_callback](callback);
|
|
}
|
|
|
|
// Unbind events
|
|
input.off('.i').unwrap();
|
|
$(_label + '[for="' + input[0].id + '"]').add(input.closest(_label)).off('.i');
|
|
}
|
|
}
|
|
|
|
// Get some option
|
|
function option(input, state, regular) {
|
|
if (input.data(_iCheck)) {
|
|
return input.data(_iCheck).o[state + (regular ? '' : 'Class')];
|
|
}
|
|
}
|
|
|
|
// Capitalize some string
|
|
function capitalize(string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
}
|
|
|
|
// Executable handlers
|
|
function callbacks(input, checked, callback, keep) {
|
|
if (!keep) {
|
|
if (checked) {
|
|
input[_callback]('ifToggled');
|
|
}
|
|
|
|
input[_callback]('ifChanged')[_callback]('if' + capitalize(callback));
|
|
}
|
|
}
|
|
})(window.jQuery || window.Zepto);
|
|
|
|
/*!
|
|
* Pikaday
|
|
*
|
|
* Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday
|
|
*/
|
|
|
|
(function (root, factory)
|
|
{
|
|
'use strict';
|
|
|
|
var moment;
|
|
if (typeof exports === 'object') {
|
|
// CommonJS module
|
|
// Load moment.js as an optional dependency
|
|
try { moment = require('moment'); } catch (e) {}
|
|
module.exports = factory(moment);
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define(function (req)
|
|
{
|
|
// Load moment.js as an optional dependency
|
|
var id = 'moment';
|
|
try { moment = req(id); } catch (e) {}
|
|
return factory(moment);
|
|
});
|
|
} else {
|
|
root.Pikaday = factory(root.moment);
|
|
}
|
|
}(this, function (moment)
|
|
{
|
|
'use strict';
|
|
|
|
/**
|
|
* feature detection and helper functions
|
|
*/
|
|
var hasMoment = typeof moment === 'function',
|
|
|
|
hasEventListeners = !!window.addEventListener,
|
|
|
|
document = window.document,
|
|
|
|
sto = window.setTimeout,
|
|
|
|
addEvent = function(el, e, callback, capture)
|
|
{
|
|
if (hasEventListeners) {
|
|
el.addEventListener(e, callback, !!capture);
|
|
} else {
|
|
el.attachEvent('on' + e, callback);
|
|
}
|
|
},
|
|
|
|
removeEvent = function(el, e, callback, capture)
|
|
{
|
|
if (hasEventListeners) {
|
|
el.removeEventListener(e, callback, !!capture);
|
|
} else {
|
|
el.detachEvent('on' + e, callback);
|
|
}
|
|
},
|
|
|
|
trim = function(str)
|
|
{
|
|
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
|
|
},
|
|
|
|
hasClass = function(el, cn)
|
|
{
|
|
return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
|
|
},
|
|
|
|
addClass = function(el, cn)
|
|
{
|
|
if (!hasClass(el, cn)) {
|
|
el.className = (el.className === '') ? cn : el.className + ' ' + cn;
|
|
}
|
|
},
|
|
|
|
removeClass = function(el, cn)
|
|
{
|
|
el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
|
|
},
|
|
|
|
isArray = function(obj)
|
|
{
|
|
return (/Array/).test(Object.prototype.toString.call(obj));
|
|
},
|
|
|
|
isDate = function(obj)
|
|
{
|
|
return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
|
|
},
|
|
|
|
isWeekend = function(date)
|
|
{
|
|
var day = date.getDay();
|
|
return day === 0 || day === 6;
|
|
},
|
|
|
|
isLeapYear = function(year)
|
|
{
|
|
// solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
|
|
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
},
|
|
|
|
getDaysInMonth = function(year, month)
|
|
{
|
|
return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
},
|
|
|
|
setToStartOfDay = function(date)
|
|
{
|
|
if (isDate(date)) date.setHours(0,0,0,0);
|
|
},
|
|
|
|
compareDates = function(a,b)
|
|
{
|
|
// weak date comparison (use setToStartOfDay(date) to ensure correct result)
|
|
return a.getTime() === b.getTime();
|
|
},
|
|
|
|
extend = function(to, from, overwrite)
|
|
{
|
|
var prop, hasProp;
|
|
for (prop in from) {
|
|
hasProp = to[prop] !== undefined;
|
|
if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
|
|
if (isDate(from[prop])) {
|
|
if (overwrite) {
|
|
to[prop] = new Date(from[prop].getTime());
|
|
}
|
|
}
|
|
else if (isArray(from[prop])) {
|
|
if (overwrite) {
|
|
to[prop] = from[prop].slice(0);
|
|
}
|
|
} else {
|
|
to[prop] = extend({}, from[prop], overwrite);
|
|
}
|
|
} else if (overwrite || !hasProp) {
|
|
to[prop] = from[prop];
|
|
}
|
|
}
|
|
return to;
|
|
},
|
|
|
|
fireEvent = function(el, eventName, data)
|
|
{
|
|
var ev;
|
|
|
|
if (document.createEvent) {
|
|
ev = document.createEvent('HTMLEvents');
|
|
ev.initEvent(eventName, true, false);
|
|
ev = extend(ev, data);
|
|
el.dispatchEvent(ev);
|
|
} else if (document.createEventObject) {
|
|
ev = document.createEventObject();
|
|
ev = extend(ev, data);
|
|
el.fireEvent('on' + eventName, ev);
|
|
}
|
|
},
|
|
|
|
adjustCalendar = function(calendar) {
|
|
if (calendar.month < 0) {
|
|
calendar.year -= Math.ceil(Math.abs(calendar.month)/12);
|
|
calendar.month += 12;
|
|
}
|
|
if (calendar.month > 11) {
|
|
calendar.year += Math.floor(Math.abs(calendar.month)/12);
|
|
calendar.month -= 12;
|
|
}
|
|
return calendar;
|
|
},
|
|
|
|
/**
|
|
* defaults and localisation
|
|
*/
|
|
defaults = {
|
|
|
|
// bind the picker to a form field
|
|
field: null,
|
|
|
|
// automatically show/hide the picker on `field` focus (default `true` if `field` is set)
|
|
bound: undefined,
|
|
|
|
// position of the datepicker, relative to the field (default to bottom & left)
|
|
// ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
|
|
position: 'bottom left',
|
|
|
|
// automatically fit in the viewport even if it means repositioning from the position option
|
|
reposition: true,
|
|
|
|
// the default output format for `.toString()` and `field` value
|
|
format: 'YYYY-MM-DD',
|
|
|
|
// the toString function which gets passed a current date object and format
|
|
// and returns a string
|
|
toString: null,
|
|
|
|
// used to create date object from current input string
|
|
parse: null,
|
|
|
|
// the initial date to view when first opened
|
|
defaultDate: null,
|
|
|
|
// make the `defaultDate` the initial selected value
|
|
setDefaultDate: false,
|
|
|
|
// first day of week (0: Sunday, 1: Monday etc)
|
|
firstDay: 0,
|
|
|
|
// the default flag for moment's strict date parsing
|
|
formatStrict: false,
|
|
|
|
// the minimum/earliest date that can be selected
|
|
minDate: null,
|
|
// the maximum/latest date that can be selected
|
|
maxDate: null,
|
|
|
|
// number of years either side, or array of upper/lower range
|
|
yearRange: 10,
|
|
|
|
// show week numbers at head of row
|
|
showWeekNumber: false,
|
|
|
|
// Week picker mode
|
|
pickWholeWeek: false,
|
|
|
|
// used internally (don't config outside)
|
|
minYear: 0,
|
|
maxYear: 9999,
|
|
minMonth: undefined,
|
|
maxMonth: undefined,
|
|
|
|
startRange: null,
|
|
endRange: null,
|
|
|
|
isRTL: false,
|
|
|
|
// Additional text to append to the year in the calendar title
|
|
yearSuffix: '',
|
|
|
|
// Render the month after year in the calendar title
|
|
showMonthAfterYear: false,
|
|
|
|
// Render days of the calendar grid that fall in the next or previous month
|
|
showDaysInNextAndPreviousMonths: false,
|
|
|
|
// Allows user to select days that fall in the next or previous month
|
|
enableSelectionDaysInNextAndPreviousMonths: false,
|
|
|
|
// how many months are visible
|
|
numberOfMonths: 1,
|
|
|
|
// when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
|
|
// only used for the first display or when a selected date is not visible
|
|
mainCalendar: 'left',
|
|
|
|
// Specify a DOM element to render the calendar in
|
|
container: undefined,
|
|
|
|
// Blur field when date is selected
|
|
blurFieldOnSelect : true,
|
|
|
|
// internationalization
|
|
i18n: {
|
|
previousMonth : 'Previous Month',
|
|
nextMonth : 'Next Month',
|
|
months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
|
|
weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
|
|
weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
|
|
},
|
|
|
|
// Theme Classname
|
|
theme: null,
|
|
|
|
// events array
|
|
events: [],
|
|
|
|
// callback function
|
|
onSelect: null,
|
|
onOpen: null,
|
|
onClose: null,
|
|
onDraw: null,
|
|
|
|
// Enable keyboard input
|
|
keyboardInput: true
|
|
},
|
|
|
|
|
|
/**
|
|
* templating functions to abstract HTML rendering
|
|
*/
|
|
renderDayName = function(opts, day, abbr)
|
|
{
|
|
day += opts.firstDay;
|
|
while (day >= 7) {
|
|
day -= 7;
|
|
}
|
|
return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
|
|
},
|
|
|
|
renderDay = function(opts)
|
|
{
|
|
var arr = [];
|
|
var ariaSelected = 'false';
|
|
if (opts.isEmpty) {
|
|
if (opts.showDaysInNextAndPreviousMonths) {
|
|
arr.push('is-outside-current-month');
|
|
|
|
if(!opts.enableSelectionDaysInNextAndPreviousMonths) {
|
|
arr.push('is-selection-disabled');
|
|
}
|
|
|
|
} else {
|
|
return '<td class="is-empty"></td>';
|
|
}
|
|
}
|
|
if (opts.isDisabled) {
|
|
arr.push('is-disabled');
|
|
}
|
|
if (opts.isToday) {
|
|
arr.push('is-today');
|
|
}
|
|
if (opts.isSelected) {
|
|
arr.push('is-selected');
|
|
ariaSelected = 'true';
|
|
}
|
|
if (opts.hasEvent) {
|
|
arr.push('has-event');
|
|
}
|
|
if (opts.isInRange) {
|
|
arr.push('is-inrange');
|
|
}
|
|
if (opts.isStartRange) {
|
|
arr.push('is-startrange');
|
|
}
|
|
if (opts.isEndRange) {
|
|
arr.push('is-endrange');
|
|
}
|
|
return '<td data-day="' + opts.day + '" class="' + arr.join(' ') + '" aria-selected="' + ariaSelected + '">' +
|
|
'<button class="pika-button pika-day" type="button" ' +
|
|
'data-pika-year="' + opts.year + '" data-pika-month="' + opts.month + '" data-pika-day="' + opts.day + '">' +
|
|
opts.day +
|
|
'</button>' +
|
|
'</td>';
|
|
},
|
|
|
|
renderWeek = function (d, m, y) {
|
|
// Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified.
|
|
var onejan = new Date(y, 0, 1),
|
|
weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7);
|
|
return '<td class="pika-week">' + weekNum + '</td>';
|
|
},
|
|
|
|
renderRow = function(days, isRTL, pickWholeWeek, isRowSelected)
|
|
{
|
|
return '<tr class="pika-row' + (pickWholeWeek ? ' pick-whole-week' : '') + (isRowSelected ? ' is-selected' : '') + '">' + (isRTL ? days.reverse() : days).join('') + '</tr>';
|
|
},
|
|
|
|
renderBody = function(rows)
|
|
{
|
|
return '<tbody>' + rows.join('') + '</tbody>';
|
|
},
|
|
|
|
renderHead = function(opts)
|
|
{
|
|
var i, arr = [];
|
|
if (opts.showWeekNumber) {
|
|
arr.push('<th></th>');
|
|
}
|
|
for (i = 0; i < 7; i++) {
|
|
arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>');
|
|
}
|
|
return '<thead><tr>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</tr></thead>';
|
|
},
|
|
|
|
renderTitle = function(instance, c, year, month, refYear, randId)
|
|
{
|
|
var i, j, arr,
|
|
opts = instance._o,
|
|
isMinYear = year === opts.minYear,
|
|
isMaxYear = year === opts.maxYear,
|
|
html = '<div id="' + randId + '" class="pika-title" role="heading" aria-live="assertive">',
|
|
monthHtml,
|
|
yearHtml,
|
|
prev = true,
|
|
next = true;
|
|
|
|
for (arr = [], i = 0; i < 12; i++) {
|
|
arr.push('<option value="' + (year === refYear ? i - c : 12 + i - c) + '"' +
|
|
(i === month ? ' selected="selected"': '') +
|
|
((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled="disabled"' : '') + '>' +
|
|
opts.i18n.months[i] + '</option>');
|
|
}
|
|
|
|
monthHtml = '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month" tabindex="-1">' + arr.join('') + '</select></div>';
|
|
|
|
if (isArray(opts.yearRange)) {
|
|
i = opts.yearRange[0];
|
|
j = opts.yearRange[1] + 1;
|
|
} else {
|
|
i = year - opts.yearRange;
|
|
j = 1 + year + opts.yearRange;
|
|
}
|
|
|
|
for (arr = []; i < j && i <= opts.maxYear; i++) {
|
|
if (i >= opts.minYear) {
|
|
arr.push('<option value="' + i + '"' + (i === year ? ' selected="selected"': '') + '>' + (i) + '</option>');
|
|
}
|
|
}
|
|
yearHtml = '<div class="pika-label">' + year + opts.yearSuffix + '<select class="pika-select pika-select-year" tabindex="-1">' + arr.join('') + '</select></div>';
|
|
|
|
if (opts.showMonthAfterYear) {
|
|
html += yearHtml + monthHtml;
|
|
} else {
|
|
html += monthHtml + yearHtml;
|
|
}
|
|
|
|
if (isMinYear && (month === 0 || opts.minMonth >= month)) {
|
|
prev = false;
|
|
}
|
|
|
|
if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
|
|
next = false;
|
|
}
|
|
|
|
if (c === 0) {
|
|
html += '<button class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">' + opts.i18n.previousMonth + '</button>';
|
|
}
|
|
if (c === (instance._o.numberOfMonths - 1) ) {
|
|
html += '<button class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">' + opts.i18n.nextMonth + '</button>';
|
|
}
|
|
|
|
return html += '</div>';
|
|
},
|
|
|
|
renderTable = function(opts, data, randId)
|
|
{
|
|
return '<table cellpadding="0" cellspacing="0" class="pika-table" role="grid" aria-labelledby="' + randId + '">' + renderHead(opts) + renderBody(data) + '</table>';
|
|
},
|
|
|
|
|
|
/**
|
|
* Pikaday constructor
|
|
*/
|
|
Pikaday = function(options)
|
|
{
|
|
var self = this,
|
|
opts = self.config(options);
|
|
|
|
self._onMouseDown = function(e)
|
|
{
|
|
if (!self._v) {
|
|
return;
|
|
}
|
|
e = e || window.event;
|
|
var target = e.target || e.srcElement;
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
if (!hasClass(target, 'is-disabled')) {
|
|
if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty') && !hasClass(target.parentNode, 'is-disabled')) {
|
|
self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
|
|
if (opts.bound) {
|
|
sto(function() {
|
|
self.hide();
|
|
if (opts.blurFieldOnSelect && opts.field) {
|
|
opts.field.blur();
|
|
}
|
|
}, 100);
|
|
}
|
|
}
|
|
else if (hasClass(target, 'pika-prev')) {
|
|
self.prevMonth();
|
|
}
|
|
else if (hasClass(target, 'pika-next')) {
|
|
self.nextMonth();
|
|
}
|
|
}
|
|
if (!hasClass(target, 'pika-select')) {
|
|
// if this is touch event prevent mouse events emulation
|
|
if (e.preventDefault) {
|
|
e.preventDefault();
|
|
} else {
|
|
e.returnValue = false;
|
|
return false;
|
|
}
|
|
} else {
|
|
self._c = true;
|
|
}
|
|
};
|
|
|
|
self._onChange = function(e)
|
|
{
|
|
e = e || window.event;
|
|
var target = e.target || e.srcElement;
|
|
if (!target) {
|
|
return;
|
|
}
|
|
if (hasClass(target, 'pika-select-month')) {
|
|
self.gotoMonth(target.value);
|
|
}
|
|
else if (hasClass(target, 'pika-select-year')) {
|
|
self.gotoYear(target.value);
|
|
}
|
|
};
|
|
|
|
self._onKeyChange = function(e)
|
|
{
|
|
e = e || window.event;
|
|
|
|
if (self.isVisible()) {
|
|
|
|
switch(e.keyCode){
|
|
case 13:
|
|
case 27:
|
|
if (opts.field) {
|
|
opts.field.blur();
|
|
}
|
|
break;
|
|
case 37:
|
|
e.preventDefault();
|
|
self.adjustDate('subtract', 1);
|
|
break;
|
|
case 38:
|
|
self.adjustDate('subtract', 7);
|
|
break;
|
|
case 39:
|
|
self.adjustDate('add', 1);
|
|
break;
|
|
case 40:
|
|
self.adjustDate('add', 7);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
self._onInputChange = function(e)
|
|
{
|
|
var date;
|
|
|
|
if (e.firedBy === self) {
|
|
return;
|
|
}
|
|
if (opts.parse) {
|
|
date = opts.parse(opts.field.value, opts.format);
|
|
} else if (hasMoment) {
|
|
date = moment(opts.field.value, opts.format, opts.formatStrict);
|
|
date = (date && date.isValid()) ? date.toDate() : null;
|
|
}
|
|
else {
|
|
date = new Date(Date.parse(opts.field.value));
|
|
}
|
|
if (isDate(date)) {
|
|
self.setDate(date);
|
|
}
|
|
if (!self._v) {
|
|
self.show();
|
|
}
|
|
};
|
|
|
|
self._onInputFocus = function()
|
|
{
|
|
self.show();
|
|
};
|
|
|
|
self._onInputClick = function()
|
|
{
|
|
self.show();
|
|
};
|
|
|
|
self._onInputBlur = function()
|
|
{
|
|
// IE allows pika div to gain focus; catch blur the input field
|
|
var pEl = document.activeElement;
|
|
do {
|
|
if (hasClass(pEl, 'pika-single')) {
|
|
return;
|
|
}
|
|
}
|
|
while ((pEl = pEl.parentNode));
|
|
|
|
if (!self._c) {
|
|
self._b = sto(function() {
|
|
self.hide();
|
|
}, 50);
|
|
}
|
|
self._c = false;
|
|
};
|
|
|
|
self._onClick = function(e)
|
|
{
|
|
e = e || window.event;
|
|
var target = e.target || e.srcElement,
|
|
pEl = target;
|
|
if (!target) {
|
|
return;
|
|
}
|
|
if (!hasEventListeners && hasClass(target, 'pika-select')) {
|
|
if (!target.onchange) {
|
|
target.setAttribute('onchange', 'return;');
|
|
addEvent(target, 'change', self._onChange);
|
|
}
|
|
}
|
|
do {
|
|
if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
|
|
return;
|
|
}
|
|
}
|
|
while ((pEl = pEl.parentNode));
|
|
if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
|
|
self.hide();
|
|
}
|
|
};
|
|
|
|
self.el = document.createElement('div');
|
|
self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
|
|
|
|
addEvent(self.el, 'mousedown', self._onMouseDown, true);
|
|
addEvent(self.el, 'touchend', self._onMouseDown, true);
|
|
addEvent(self.el, 'change', self._onChange);
|
|
|
|
if (opts.keyboardInput) {
|
|
addEvent(document, 'keydown', self._onKeyChange);
|
|
}
|
|
|
|
if (opts.field) {
|
|
if (opts.container) {
|
|
opts.container.appendChild(self.el);
|
|
} else if (opts.bound) {
|
|
document.body.appendChild(self.el);
|
|
} else {
|
|
opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
|
|
}
|
|
addEvent(opts.field, 'change', self._onInputChange);
|
|
|
|
if (!opts.defaultDate) {
|
|
if (hasMoment && opts.field.value) {
|
|
opts.defaultDate = moment(opts.field.value, opts.format).toDate();
|
|
} else {
|
|
opts.defaultDate = new Date(Date.parse(opts.field.value));
|
|
}
|
|
opts.setDefaultDate = true;
|
|
}
|
|
}
|
|
|
|
var defDate = opts.defaultDate;
|
|
|
|
if (isDate(defDate)) {
|
|
if (opts.setDefaultDate) {
|
|
self.setDate(defDate, true);
|
|
} else {
|
|
self.gotoDate(defDate);
|
|
}
|
|
} else {
|
|
self.gotoDate(new Date());
|
|
}
|
|
|
|
if (opts.bound) {
|
|
this.hide();
|
|
self.el.className += ' is-bound';
|
|
addEvent(opts.trigger, 'click', self._onInputClick);
|
|
addEvent(opts.trigger, 'focus', self._onInputFocus);
|
|
addEvent(opts.trigger, 'blur', self._onInputBlur);
|
|
} else {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* public Pikaday API
|
|
*/
|
|
Pikaday.prototype = {
|
|
|
|
|
|
/**
|
|
* configure functionality
|
|
*/
|
|
config: function(options)
|
|
{
|
|
if (!this._o) {
|
|
this._o = extend({}, defaults, true);
|
|
}
|
|
|
|
var opts = extend(this._o, options, true);
|
|
|
|
opts.isRTL = !!opts.isRTL;
|
|
|
|
opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
|
|
|
|
opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
|
|
|
|
opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
|
|
|
|
opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
|
|
|
|
opts.disableWeekends = !!opts.disableWeekends;
|
|
|
|
opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
|
|
|
|
var nom = parseInt(opts.numberOfMonths, 10) || 1;
|
|
opts.numberOfMonths = nom > 4 ? 4 : nom;
|
|
|
|
if (!isDate(opts.minDate)) {
|
|
opts.minDate = false;
|
|
}
|
|
if (!isDate(opts.maxDate)) {
|
|
opts.maxDate = false;
|
|
}
|
|
if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
|
|
opts.maxDate = opts.minDate = false;
|
|
}
|
|
if (opts.minDate) {
|
|
this.setMinDate(opts.minDate);
|
|
}
|
|
if (opts.maxDate) {
|
|
this.setMaxDate(opts.maxDate);
|
|
}
|
|
|
|
if (isArray(opts.yearRange)) {
|
|
var fallback = new Date().getFullYear() - 10;
|
|
opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
|
|
opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
|
|
} else {
|
|
opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
|
|
if (opts.yearRange > 100) {
|
|
opts.yearRange = 100;
|
|
}
|
|
}
|
|
|
|
return opts;
|
|
},
|
|
|
|
/**
|
|
* return a formatted string of the current selection (using Moment.js if available)
|
|
*/
|
|
toString: function(format)
|
|
{
|
|
format = format || this._o.format;
|
|
if (!isDate(this._d)) {
|
|
return '';
|
|
}
|
|
if (this._o.toString) {
|
|
return this._o.toString(this._d, format);
|
|
}
|
|
if (hasMoment) {
|
|
return moment(this._d).format(format);
|
|
}
|
|
return this._d.toDateString();
|
|
},
|
|
|
|
/**
|
|
* return a Moment.js object of the current selection (if available)
|
|
*/
|
|
getMoment: function()
|
|
{
|
|
return hasMoment ? moment(this._d) : null;
|
|
},
|
|
|
|
/**
|
|
* set the current selection from a Moment.js object (if available)
|
|
*/
|
|
setMoment: function(date, preventOnSelect)
|
|
{
|
|
if (hasMoment && moment.isMoment(date)) {
|
|
this.setDate(date.toDate(), preventOnSelect);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* return a Date object of the current selection
|
|
*/
|
|
getDate: function()
|
|
{
|
|
return isDate(this._d) ? new Date(this._d.getTime()) : null;
|
|
},
|
|
|
|
/**
|
|
* set the current selection
|
|
*/
|
|
setDate: function(date, preventOnSelect)
|
|
{
|
|
if (!date) {
|
|
this._d = null;
|
|
|
|
if (this._o.field) {
|
|
this._o.field.value = '';
|
|
fireEvent(this._o.field, 'change', { firedBy: this });
|
|
}
|
|
|
|
return this.draw();
|
|
}
|
|
if (typeof date === 'string') {
|
|
date = new Date(Date.parse(date));
|
|
}
|
|
if (!isDate(date)) {
|
|
return;
|
|
}
|
|
|
|
var min = this._o.minDate,
|
|
max = this._o.maxDate;
|
|
|
|
if (isDate(min) && date < min) {
|
|
date = min;
|
|
} else if (isDate(max) && date > max) {
|
|
date = max;
|
|
}
|
|
|
|
this._d = new Date(date.getTime());
|
|
setToStartOfDay(this._d);
|
|
this.gotoDate(this._d);
|
|
|
|
if (this._o.field) {
|
|
this._o.field.value = this.toString();
|
|
fireEvent(this._o.field, 'change', { firedBy: this });
|
|
}
|
|
if (!preventOnSelect && typeof this._o.onSelect === 'function') {
|
|
this._o.onSelect.call(this, this.getDate());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* change view to a specific date
|
|
*/
|
|
gotoDate: function(date)
|
|
{
|
|
var newCalendar = true;
|
|
|
|
if (!isDate(date)) {
|
|
return;
|
|
}
|
|
|
|
if (this.calendars) {
|
|
var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
|
|
lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
|
|
visibleDate = date.getTime();
|
|
// get the end of the month
|
|
lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
|
|
lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
|
|
newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
|
|
}
|
|
|
|
if (newCalendar) {
|
|
this.calendars = [{
|
|
month: date.getMonth(),
|
|
year: date.getFullYear()
|
|
}];
|
|
if (this._o.mainCalendar === 'right') {
|
|
this.calendars[0].month += 1 - this._o.numberOfMonths;
|
|
}
|
|
}
|
|
|
|
this.adjustCalendars();
|
|
},
|
|
|
|
adjustDate: function(sign, days) {
|
|
|
|
var day = this.getDate() || new Date();
|
|
var difference = parseInt(days)*24*60*60*1000;
|
|
|
|
var newDay;
|
|
|
|
if (sign === 'add') {
|
|
newDay = new Date(day.valueOf() + difference);
|
|
} else if (sign === 'subtract') {
|
|
newDay = new Date(day.valueOf() - difference);
|
|
}
|
|
|
|
this.setDate(newDay);
|
|
},
|
|
|
|
adjustCalendars: function() {
|
|
this.calendars[0] = adjustCalendar(this.calendars[0]);
|
|
for (var c = 1; c < this._o.numberOfMonths; c++) {
|
|
this.calendars[c] = adjustCalendar({
|
|
month: this.calendars[0].month + c,
|
|
year: this.calendars[0].year
|
|
});
|
|
}
|
|
this.draw();
|
|
},
|
|
|
|
gotoToday: function()
|
|
{
|
|
this.gotoDate(new Date());
|
|
},
|
|
|
|
/**
|
|
* change view to a specific month (zero-index, e.g. 0: January)
|
|
*/
|
|
gotoMonth: function(month)
|
|
{
|
|
if (!isNaN(month)) {
|
|
this.calendars[0].month = parseInt(month, 10);
|
|
this.adjustCalendars();
|
|
}
|
|
},
|
|
|
|
nextMonth: function()
|
|
{
|
|
this.calendars[0].month++;
|
|
this.adjustCalendars();
|
|
},
|
|
|
|
prevMonth: function()
|
|
{
|
|
this.calendars[0].month--;
|
|
this.adjustCalendars();
|
|
},
|
|
|
|
/**
|
|
* change view to a specific full year (e.g. "2012")
|
|
*/
|
|
gotoYear: function(year)
|
|
{
|
|
if (!isNaN(year)) {
|
|
this.calendars[0].year = parseInt(year, 10);
|
|
this.adjustCalendars();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* change the minDate
|
|
*/
|
|
setMinDate: function(value)
|
|
{
|
|
if(value instanceof Date) {
|
|
setToStartOfDay(value);
|
|
this._o.minDate = value;
|
|
this._o.minYear = value.getFullYear();
|
|
this._o.minMonth = value.getMonth();
|
|
} else {
|
|
this._o.minDate = defaults.minDate;
|
|
this._o.minYear = defaults.minYear;
|
|
this._o.minMonth = defaults.minMonth;
|
|
this._o.startRange = defaults.startRange;
|
|
}
|
|
|
|
this.draw();
|
|
},
|
|
|
|
/**
|
|
* change the maxDate
|
|
*/
|
|
setMaxDate: function(value)
|
|
{
|
|
if(value instanceof Date) {
|
|
setToStartOfDay(value);
|
|
this._o.maxDate = value;
|
|
this._o.maxYear = value.getFullYear();
|
|
this._o.maxMonth = value.getMonth();
|
|
} else {
|
|
this._o.maxDate = defaults.maxDate;
|
|
this._o.maxYear = defaults.maxYear;
|
|
this._o.maxMonth = defaults.maxMonth;
|
|
this._o.endRange = defaults.endRange;
|
|
}
|
|
|
|
this.draw();
|
|
},
|
|
|
|
setStartRange: function(value)
|
|
{
|
|
this._o.startRange = value;
|
|
},
|
|
|
|
setEndRange: function(value)
|
|
{
|
|
this._o.endRange = value;
|
|
},
|
|
|
|
/**
|
|
* refresh the HTML
|
|
*/
|
|
draw: function(force)
|
|
{
|
|
if (!this._v && !force) {
|
|
return;
|
|
}
|
|
var opts = this._o,
|
|
minYear = opts.minYear,
|
|
maxYear = opts.maxYear,
|
|
minMonth = opts.minMonth,
|
|
maxMonth = opts.maxMonth,
|
|
html = '',
|
|
randId;
|
|
|
|
if (this._y <= minYear) {
|
|
this._y = minYear;
|
|
if (!isNaN(minMonth) && this._m < minMonth) {
|
|
this._m = minMonth;
|
|
}
|
|
}
|
|
if (this._y >= maxYear) {
|
|
this._y = maxYear;
|
|
if (!isNaN(maxMonth) && this._m > maxMonth) {
|
|
this._m = maxMonth;
|
|
}
|
|
}
|
|
|
|
randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2);
|
|
|
|
for (var c = 0; c < opts.numberOfMonths; c++) {
|
|
html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '</div>';
|
|
}
|
|
|
|
this.el.innerHTML = html;
|
|
|
|
if (opts.bound) {
|
|
if(opts.field.type !== 'hidden') {
|
|
sto(function() {
|
|
opts.trigger.focus();
|
|
}, 1);
|
|
}
|
|
}
|
|
|
|
if (typeof this._o.onDraw === 'function') {
|
|
this._o.onDraw(this);
|
|
}
|
|
|
|
if (opts.bound) {
|
|
// let the screen reader user know to use arrow keys
|
|
opts.field.setAttribute('aria-label', 'Use the arrow keys to pick a date');
|
|
}
|
|
},
|
|
|
|
adjustPosition: function()
|
|
{
|
|
var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect;
|
|
|
|
if (this._o.container) return;
|
|
|
|
this.el.style.position = 'absolute';
|
|
|
|
field = this._o.trigger;
|
|
pEl = field;
|
|
width = this.el.offsetWidth;
|
|
height = this.el.offsetHeight;
|
|
viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
|
|
|
|
if (typeof field.getBoundingClientRect === 'function') {
|
|
clientRect = field.getBoundingClientRect();
|
|
left = clientRect.left + window.pageXOffset;
|
|
top = clientRect.bottom + window.pageYOffset;
|
|
} else {
|
|
left = pEl.offsetLeft;
|
|
top = pEl.offsetTop + pEl.offsetHeight;
|
|
while((pEl = pEl.offsetParent)) {
|
|
left += pEl.offsetLeft;
|
|
top += pEl.offsetTop;
|
|
}
|
|
}
|
|
|
|
// default position is bottom & left
|
|
if ((this._o.reposition && left + width > viewportWidth) ||
|
|
(
|
|
this._o.position.indexOf('right') > -1 &&
|
|
left - width + field.offsetWidth > 0
|
|
)
|
|
) {
|
|
left = left - width + field.offsetWidth;
|
|
}
|
|
if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
|
|
(
|
|
this._o.position.indexOf('top') > -1 &&
|
|
top - height - field.offsetHeight > 0
|
|
)
|
|
) {
|
|
top = top - height - field.offsetHeight;
|
|
}
|
|
|
|
this.el.style.left = left + 'px';
|
|
this.el.style.top = top + 'px';
|
|
},
|
|
|
|
/**
|
|
* render HTML for a particular month
|
|
*/
|
|
render: function(year, month, randId)
|
|
{
|
|
var opts = this._o,
|
|
now = new Date(),
|
|
days = getDaysInMonth(year, month),
|
|
before = new Date(year, month, 1).getDay(),
|
|
data = [],
|
|
row = [];
|
|
setToStartOfDay(now);
|
|
if (opts.firstDay > 0) {
|
|
before -= opts.firstDay;
|
|
if (before < 0) {
|
|
before += 7;
|
|
}
|
|
}
|
|
var previousMonth = month === 0 ? 11 : month - 1,
|
|
nextMonth = month === 11 ? 0 : month + 1,
|
|
yearOfPreviousMonth = month === 0 ? year - 1 : year,
|
|
yearOfNextMonth = month === 11 ? year + 1 : year,
|
|
daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth);
|
|
var cells = days + before,
|
|
after = cells;
|
|
while(after > 7) {
|
|
after -= 7;
|
|
}
|
|
cells += 7 - after;
|
|
var isWeekSelected = false;
|
|
for (var i = 0, r = 0; i < cells; i++)
|
|
{
|
|
var day = new Date(year, month, 1 + (i - before)),
|
|
isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
|
|
isToday = compareDates(day, now),
|
|
hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false,
|
|
isEmpty = i < before || i >= (days + before),
|
|
dayNumber = 1 + (i - before),
|
|
monthNumber = month,
|
|
yearNumber = year,
|
|
isStartRange = opts.startRange && compareDates(opts.startRange, day),
|
|
isEndRange = opts.endRange && compareDates(opts.endRange, day),
|
|
isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
|
|
isDisabled = (opts.minDate && day < opts.minDate) ||
|
|
(opts.maxDate && day > opts.maxDate) ||
|
|
(opts.disableWeekends && isWeekend(day)) ||
|
|
(opts.disableDayFn && opts.disableDayFn(day));
|
|
|
|
if (isEmpty) {
|
|
if (i < before) {
|
|
dayNumber = daysInPreviousMonth + dayNumber;
|
|
monthNumber = previousMonth;
|
|
yearNumber = yearOfPreviousMonth;
|
|
} else {
|
|
dayNumber = dayNumber - days;
|
|
monthNumber = nextMonth;
|
|
yearNumber = yearOfNextMonth;
|
|
}
|
|
}
|
|
|
|
var dayConfig = {
|
|
day: dayNumber,
|
|
month: monthNumber,
|
|
year: yearNumber,
|
|
hasEvent: hasEvent,
|
|
isSelected: isSelected,
|
|
isToday: isToday,
|
|
isDisabled: isDisabled,
|
|
isEmpty: isEmpty,
|
|
isStartRange: isStartRange,
|
|
isEndRange: isEndRange,
|
|
isInRange: isInRange,
|
|
showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths,
|
|
enableSelectionDaysInNextAndPreviousMonths: opts.enableSelectionDaysInNextAndPreviousMonths
|
|
};
|
|
|
|
if (opts.pickWholeWeek && isSelected) {
|
|
isWeekSelected = true;
|
|
}
|
|
|
|
row.push(renderDay(dayConfig));
|
|
|
|
if (++r === 7) {
|
|
if (opts.showWeekNumber) {
|
|
row.unshift(renderWeek(i - before, month, year));
|
|
}
|
|
data.push(renderRow(row, opts.isRTL, opts.pickWholeWeek, isWeekSelected));
|
|
row = [];
|
|
r = 0;
|
|
isWeekSelected = false;
|
|
}
|
|
}
|
|
return renderTable(opts, data, randId);
|
|
},
|
|
|
|
isVisible: function()
|
|
{
|
|
return this._v;
|
|
},
|
|
|
|
show: function()
|
|
{
|
|
if (!this.isVisible()) {
|
|
this._v = true;
|
|
this.draw();
|
|
removeClass(this.el, 'is-hidden');
|
|
if (this._o.bound) {
|
|
addEvent(document, 'click', this._onClick);
|
|
this.adjustPosition();
|
|
}
|
|
if (typeof this._o.onOpen === 'function') {
|
|
this._o.onOpen.call(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
hide: function()
|
|
{
|
|
var v = this._v;
|
|
if (v !== false) {
|
|
if (this._o.bound) {
|
|
removeEvent(document, 'click', this._onClick);
|
|
}
|
|
this.el.style.position = 'static'; // reset
|
|
this.el.style.left = 'auto';
|
|
this.el.style.top = 'auto';
|
|
addClass(this.el, 'is-hidden');
|
|
this._v = false;
|
|
if (v !== undefined && typeof this._o.onClose === 'function') {
|
|
this._o.onClose.call(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* GAME OVER
|
|
*/
|
|
destroy: function()
|
|
{
|
|
var opts = this._o;
|
|
|
|
this.hide();
|
|
removeEvent(this.el, 'mousedown', this._onMouseDown, true);
|
|
removeEvent(this.el, 'touchend', this._onMouseDown, true);
|
|
removeEvent(this.el, 'change', this._onChange);
|
|
if (opts.keyboardInput) {
|
|
removeEvent(document, 'keydown', this._onKeyChange);
|
|
}
|
|
if (opts.field) {
|
|
removeEvent(opts.field, 'change', this._onInputChange);
|
|
if (opts.bound) {
|
|
removeEvent(opts.trigger, 'click', this._onInputClick);
|
|
removeEvent(opts.trigger, 'focus', this._onInputFocus);
|
|
removeEvent(opts.trigger, 'blur', this._onInputBlur);
|
|
}
|
|
}
|
|
if (this.el.parentNode) {
|
|
this.el.parentNode.removeChild(this.el);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
return Pikaday;
|
|
}));
|
|
|
|
/*! X-editable - v1.5.1
|
|
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
|
|
* http://github.com/vitalets/x-editable
|
|
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
|
|
/**
|
|
Form with single input element, two buttons and two states: normal/loading.
|
|
Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
|
|
Editableform is linked with one of input types, e.g. 'text', 'select' etc.
|
|
|
|
@class editableform
|
|
@uses text
|
|
@uses textarea
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var EditableForm = function (div, options) {
|
|
this.options = $.extend({}, $.fn.editableform.defaults, options);
|
|
this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
|
|
if(!this.options.scope) {
|
|
this.options.scope = this;
|
|
}
|
|
//nothing shown after init
|
|
};
|
|
|
|
EditableForm.prototype = {
|
|
constructor: EditableForm,
|
|
initInput: function() { //called once
|
|
//take input from options (as it is created in editable-element)
|
|
this.input = this.options.input;
|
|
|
|
//set initial value
|
|
//todo: may be add check: typeof str === 'string' ?
|
|
this.value = this.input.str2value(this.options.value);
|
|
|
|
//prerender: get input.$input
|
|
this.input.prerender();
|
|
},
|
|
initTemplate: function() {
|
|
this.$form = $($.fn.editableform.template);
|
|
},
|
|
initButtons: function() {
|
|
var $btn = this.$form.find('.editable-buttons');
|
|
$btn.append($.fn.editableform.buttons);
|
|
if(this.options.showbuttons === 'bottom') {
|
|
$btn.addClass('editable-buttons-bottom');
|
|
}
|
|
},
|
|
/**
|
|
Renders editableform
|
|
|
|
@method render
|
|
**/
|
|
render: function() {
|
|
//init loader
|
|
this.$loading = $($.fn.editableform.loading);
|
|
this.$div.empty().append(this.$loading);
|
|
|
|
//init form template and buttons
|
|
this.initTemplate();
|
|
if(this.options.showbuttons) {
|
|
this.initButtons();
|
|
} else {
|
|
this.$form.find('.editable-buttons').remove();
|
|
}
|
|
|
|
//show loading state
|
|
this.showLoading();
|
|
|
|
//flag showing is form now saving value to server.
|
|
//It is needed to wait when closing form.
|
|
this.isSaving = false;
|
|
|
|
/**
|
|
Fired when rendering starts
|
|
@event rendering
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('rendering');
|
|
|
|
//init input
|
|
this.initInput();
|
|
|
|
//append input to form
|
|
this.$form.find('div.editable-input').append(this.input.$tpl);
|
|
|
|
//append form to container
|
|
this.$div.append(this.$form);
|
|
|
|
//render input
|
|
$.when(this.input.render())
|
|
.then($.proxy(function () {
|
|
//setup input to submit automatically when no buttons shown
|
|
if(!this.options.showbuttons) {
|
|
this.input.autosubmit();
|
|
}
|
|
|
|
//attach 'cancel' handler
|
|
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
|
|
|
|
if(this.input.error) {
|
|
this.error(this.input.error);
|
|
this.$form.find('.editable-submit').attr('disabled', true);
|
|
this.input.$input.attr('disabled', true);
|
|
//prevent form from submitting
|
|
this.$form.submit(function(e){ e.preventDefault(); });
|
|
} else {
|
|
this.error(false);
|
|
this.input.$input.removeAttr('disabled');
|
|
this.$form.find('.editable-submit').removeAttr('disabled');
|
|
var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
|
|
this.input.value2input(value);
|
|
//attach submit handler
|
|
this.$form.submit($.proxy(this.submit, this));
|
|
}
|
|
|
|
/**
|
|
Fired when form is rendered
|
|
@event rendered
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('rendered');
|
|
|
|
this.showForm();
|
|
|
|
//call postrender method to perform actions required visibility of form
|
|
if(this.input.postrender) {
|
|
this.input.postrender();
|
|
}
|
|
}, this));
|
|
},
|
|
cancel: function() {
|
|
/**
|
|
Fired when form was cancelled by user
|
|
@event cancel
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('cancel');
|
|
},
|
|
showLoading: function() {
|
|
var w, h;
|
|
if(this.$form) {
|
|
//set loading size equal to form
|
|
w = this.$form.outerWidth();
|
|
h = this.$form.outerHeight();
|
|
if(w) {
|
|
this.$loading.width(w);
|
|
}
|
|
if(h) {
|
|
this.$loading.height(h);
|
|
}
|
|
this.$form.hide();
|
|
} else {
|
|
//stretch loading to fill container width
|
|
w = this.$loading.parent().width();
|
|
if(w) {
|
|
this.$loading.width(w);
|
|
}
|
|
}
|
|
this.$loading.show();
|
|
},
|
|
|
|
showForm: function(activate) {
|
|
this.$loading.hide();
|
|
this.$form.show();
|
|
if(activate !== false) {
|
|
this.input.activate();
|
|
}
|
|
/**
|
|
Fired when form is shown
|
|
@event show
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('show');
|
|
},
|
|
|
|
error: function(msg) {
|
|
var $group = this.$form.find('.control-group'),
|
|
$block = this.$form.find('.editable-error-block'),
|
|
lines;
|
|
|
|
if(msg === false) {
|
|
$group.removeClass($.fn.editableform.errorGroupClass);
|
|
$block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
|
|
} else {
|
|
//convert newline to <br> for more pretty error display
|
|
if(msg) {
|
|
lines = (''+msg).split('\n');
|
|
for (var i = 0; i < lines.length; i++) {
|
|
lines[i] = $('<div>').text(lines[i]).html();
|
|
}
|
|
msg = lines.join('<br>');
|
|
}
|
|
$group.addClass($.fn.editableform.errorGroupClass);
|
|
$block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
|
|
}
|
|
},
|
|
|
|
submit: function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
//get new value from input
|
|
var newValue = this.input.input2value();
|
|
|
|
//validation: if validate returns string or truthy value - means error
|
|
//if returns object like {newValue: '...'} => submitted value is reassigned to it
|
|
var error = this.validate(newValue);
|
|
if ($.type(error) === 'object' && error.newValue !== undefined) {
|
|
newValue = error.newValue;
|
|
this.input.value2input(newValue);
|
|
if(typeof error.msg === 'string') {
|
|
this.error(error.msg);
|
|
this.showForm();
|
|
return;
|
|
}
|
|
} else if (error) {
|
|
this.error(error);
|
|
this.showForm();
|
|
return;
|
|
}
|
|
|
|
//if value not changed --> trigger 'nochange' event and return
|
|
/*jslint eqeq: true*/
|
|
if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
|
|
/*jslint eqeq: false*/
|
|
/**
|
|
Fired when value not changed but form is submitted. Requires savenochange = false.
|
|
@event nochange
|
|
@param {Object} event event object
|
|
**/
|
|
this.$div.triggerHandler('nochange');
|
|
return;
|
|
}
|
|
|
|
//convert value for submitting to server
|
|
var submitValue = this.input.value2submit(newValue);
|
|
|
|
this.isSaving = true;
|
|
|
|
//sending data to server
|
|
$.when(this.save(submitValue))
|
|
.done($.proxy(function(response) {
|
|
this.isSaving = false;
|
|
|
|
//run success callback
|
|
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
|
|
|
|
//if success callback returns false --> keep form open and do not activate input
|
|
if(res === false) {
|
|
this.error(false);
|
|
this.showForm(false);
|
|
return;
|
|
}
|
|
|
|
//if success callback returns string --> keep form open, show error and activate input
|
|
if(typeof res === 'string') {
|
|
this.error(res);
|
|
this.showForm();
|
|
return;
|
|
}
|
|
|
|
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted
|
|
//it is usefull if you want to chnage value in url-function
|
|
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
|
|
newValue = res.newValue;
|
|
}
|
|
|
|
//clear error message
|
|
this.error(false);
|
|
this.value = newValue;
|
|
/**
|
|
Fired when form is submitted
|
|
@event save
|
|
@param {Object} event event object
|
|
@param {Object} params additional params
|
|
@param {mixed} params.newValue raw new value
|
|
@param {mixed} params.submitValue submitted value as string
|
|
@param {Object} params.response ajax response
|
|
|
|
@example
|
|
$('#form-div').on('save'), function(e, params){
|
|
if(params.newValue === 'username') {...}
|
|
});
|
|
**/
|
|
this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
|
|
}, this))
|
|
.fail($.proxy(function(xhr) {
|
|
this.isSaving = false;
|
|
|
|
var msg;
|
|
if(typeof this.options.error === 'function') {
|
|
msg = this.options.error.call(this.options.scope, xhr, newValue);
|
|
} else {
|
|
msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
|
|
}
|
|
|
|
this.error(msg);
|
|
this.showForm();
|
|
}, this));
|
|
},
|
|
|
|
save: function(submitValue) {
|
|
//try parse composite pk defined as json string in data-pk
|
|
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
|
|
|
|
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
|
|
/*
|
|
send on server in following cases:
|
|
1. url is function
|
|
2. url is string AND (pk defined OR send option = always)
|
|
*/
|
|
send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
|
|
params;
|
|
|
|
if (send) { //send to server
|
|
this.showLoading();
|
|
|
|
//standard params
|
|
params = {
|
|
name: this.options.name || '',
|
|
value: submitValue,
|
|
pk: pk
|
|
};
|
|
|
|
//additional params
|
|
if(typeof this.options.params === 'function') {
|
|
params = this.options.params.call(this.options.scope, params);
|
|
} else {
|
|
//try parse json in single quotes (from data-params attribute)
|
|
this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);
|
|
$.extend(params, this.options.params);
|
|
}
|
|
|
|
if(typeof this.options.url === 'function') { //user's function
|
|
return this.options.url.call(this.options.scope, params);
|
|
} else {
|
|
//send ajax to server and return deferred object
|
|
return $.ajax($.extend({
|
|
url : this.options.url,
|
|
data : params,
|
|
type : 'POST'
|
|
}, this.options.ajaxOptions));
|
|
}
|
|
}
|
|
},
|
|
|
|
validate: function (value) {
|
|
if (value === undefined) {
|
|
value = this.value;
|
|
}
|
|
if (typeof this.options.validate === 'function') {
|
|
return this.options.validate.call(this.options.scope, value);
|
|
}
|
|
},
|
|
|
|
option: function(key, value) {
|
|
if(key in this.options) {
|
|
this.options[key] = value;
|
|
}
|
|
|
|
if(key === 'value') {
|
|
this.setValue(value);
|
|
}
|
|
|
|
//do not pass option to input as it is passed in editable-element
|
|
},
|
|
|
|
setValue: function(value, convertStr) {
|
|
if(convertStr) {
|
|
this.value = this.input.str2value(value);
|
|
} else {
|
|
this.value = value;
|
|
}
|
|
|
|
//if form is visible, update input
|
|
if(this.$form && this.$form.is(':visible')) {
|
|
this.input.value2input(this.value);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Initialize editableform. Applied to jQuery object.
|
|
|
|
@method $().editableform(options)
|
|
@params {Object} options
|
|
@example
|
|
var $form = $('<div>').editableform({
|
|
type: 'text',
|
|
name: 'username',
|
|
url: '/post',
|
|
value: 'vitaliy'
|
|
});
|
|
|
|
//to display form you should call 'render' method
|
|
$form.editableform('render');
|
|
*/
|
|
$.fn.editableform = function (option) {
|
|
var args = arguments;
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data('editableform'),
|
|
options = typeof option === 'object' && option;
|
|
if (!data) {
|
|
$this.data('editableform', (data = new EditableForm(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') { //call method
|
|
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
|
}
|
|
});
|
|
};
|
|
|
|
//keep link to constructor to allow inheritance
|
|
$.fn.editableform.Constructor = EditableForm;
|
|
|
|
//defaults
|
|
$.fn.editableform.defaults = {
|
|
/* see also defaults for input */
|
|
|
|
/**
|
|
Type of input. Can be <code>text|textarea|select|date|checklist</code>
|
|
|
|
@property type
|
|
@type string
|
|
@default 'text'
|
|
**/
|
|
type: 'text',
|
|
/**
|
|
Url for submit, e.g. <code>'/post'</code>
|
|
If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
|
|
|
|
@property url
|
|
@type string|function
|
|
@default null
|
|
@example
|
|
url: function(params) {
|
|
var d = new $.Deferred;
|
|
if(params.value === 'abc') {
|
|
return d.reject('error message'); //returning error via deferred object
|
|
} else {
|
|
//async saving data in js model
|
|
someModel.asyncSaveMethod({
|
|
...,
|
|
success: function(){
|
|
d.resolve();
|
|
}
|
|
});
|
|
return d.promise();
|
|
}
|
|
}
|
|
**/
|
|
url:null,
|
|
/**
|
|
Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).
|
|
If defined as <code>function</code> - returned object **overwrites** original ajax data.
|
|
@example
|
|
params: function(params) {
|
|
//originally params contain pk, name and value
|
|
params.a = 1;
|
|
return params;
|
|
}
|
|
|
|
@property params
|
|
@type object|function
|
|
@default null
|
|
**/
|
|
params:null,
|
|
/**
|
|
Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
|
|
|
|
@property name
|
|
@type string
|
|
@default null
|
|
**/
|
|
name: null,
|
|
/**
|
|
Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
|
|
Can be calculated dynamically via function.
|
|
|
|
@property pk
|
|
@type string|object|function
|
|
@default null
|
|
**/
|
|
pk: null,
|
|
/**
|
|
Initial value. If not defined - will be taken from element's content.
|
|
For __select__ type should be defined (as it is ID of shown text).
|
|
|
|
@property value
|
|
@type string|object
|
|
@default null
|
|
**/
|
|
value: null,
|
|
/**
|
|
Value that will be displayed in input if original field value is empty (`null|undefined|''`).
|
|
|
|
@property defaultValue
|
|
@type string|object
|
|
@default null
|
|
@since 1.4.6
|
|
**/
|
|
defaultValue: null,
|
|
/**
|
|
Strategy for sending data on server. Can be `auto|always|never`.
|
|
When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
|
|
|
|
@property send
|
|
@type string
|
|
@default 'auto'
|
|
**/
|
|
send: 'auto',
|
|
/**
|
|
Function for client-side validation. If returns string - means validation not passed and string showed as error.
|
|
Since 1.5.1 you can modify submitted value by returning object from `validate`:
|
|
`{newValue: '...'}` or `{newValue: '...', msg: '...'}`
|
|
|
|
@property validate
|
|
@type function
|
|
@default null
|
|
@example
|
|
validate: function(value) {
|
|
if($.trim(value) == '') {
|
|
return 'This field is required';
|
|
}
|
|
}
|
|
**/
|
|
validate: null,
|
|
/**
|
|
Success callback. Called when value successfully sent on server and **response status = 200**.
|
|
Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
|
|
or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
|
|
If it returns **string** - means error occured and string is shown as error message.
|
|
If it returns **object like** <code>{newValue: <something>}</code> - it overwrites value, submitted by user.
|
|
Otherwise newValue simply rendered into element.
|
|
|
|
@property success
|
|
@type function
|
|
@default null
|
|
@example
|
|
success: function(response, newValue) {
|
|
if(!response.success) return response.msg;
|
|
}
|
|
**/
|
|
success: null,
|
|
/**
|
|
Error callback. Called when request failed (response status != 200).
|
|
Usefull when you want to parse error response and display a custom message.
|
|
Must return **string** - the message to be displayed in the error block.
|
|
|
|
@property error
|
|
@type function
|
|
@default null
|
|
@since 1.4.4
|
|
@example
|
|
error: function(response, newValue) {
|
|
if(response.status === 500) {
|
|
return 'Service unavailable. Please try later.';
|
|
} else {
|
|
return response.responseText;
|
|
}
|
|
}
|
|
**/
|
|
error: null,
|
|
/**
|
|
Additional options for submit ajax request.
|
|
List of values: http://api.jquery.com/jQuery.ajax
|
|
|
|
@property ajaxOptions
|
|
@type object
|
|
@default null
|
|
@since 1.1.1
|
|
@example
|
|
ajaxOptions: {
|
|
type: 'put',
|
|
dataType: 'json'
|
|
}
|
|
**/
|
|
ajaxOptions: null,
|
|
/**
|
|
Where to show buttons: left(true)|bottom|false
|
|
Form without buttons is auto-submitted.
|
|
|
|
@property showbuttons
|
|
@type boolean|string
|
|
@default true
|
|
@since 1.1.1
|
|
**/
|
|
showbuttons: true,
|
|
/**
|
|
Scope for callback methods (success, validate).
|
|
If <code>null</code> means editableform instance itself.
|
|
|
|
@property scope
|
|
@type DOMElement|object
|
|
@default null
|
|
@since 1.2.0
|
|
@private
|
|
**/
|
|
scope: null,
|
|
/**
|
|
Whether to save or cancel value when it was not changed but form was submitted
|
|
|
|
@property savenochange
|
|
@type boolean
|
|
@default false
|
|
@since 1.2.0
|
|
**/
|
|
savenochange: false
|
|
};
|
|
|
|
/*
|
|
Note: following params could redefined in engine: bootstrap or jqueryui:
|
|
Classes 'control-group' and 'editable-error-block' must always present!
|
|
*/
|
|
$.fn.editableform.template = '<form class="form-inline editableform">'+
|
|
'<div class="control-group">' +
|
|
'<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
|
|
'<div class="editable-error-block"></div>' +
|
|
'</div>' +
|
|
'</form>';
|
|
|
|
//loading div
|
|
$.fn.editableform.loading = '<div class="editableform-loading"></div>';
|
|
|
|
//buttons
|
|
$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
|
|
'<button type="button" class="editable-cancel">cancel</button>';
|
|
|
|
//error class attached to control-group
|
|
$.fn.editableform.errorGroupClass = null;
|
|
|
|
//error class attached to editable-error-block
|
|
$.fn.editableform.errorBlockClass = 'editable-error';
|
|
|
|
//engine
|
|
$.fn.editableform.engine = 'jquery';
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
* EditableForm utilites
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//utils
|
|
$.fn.editableutils = {
|
|
/**
|
|
* classic JS inheritance function
|
|
*/
|
|
inherit: function (Child, Parent) {
|
|
var F = function() { };
|
|
F.prototype = Parent.prototype;
|
|
Child.prototype = new F();
|
|
Child.prototype.constructor = Child;
|
|
Child.superclass = Parent.prototype;
|
|
},
|
|
|
|
/**
|
|
* set caret position in input
|
|
* see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
|
|
*/
|
|
setCursorPosition: function(elem, pos) {
|
|
if (elem.setSelectionRange) {
|
|
elem.setSelectionRange(pos, pos);
|
|
} else if (elem.createTextRange) {
|
|
var range = elem.createTextRange();
|
|
range.collapse(true);
|
|
range.moveEnd('character', pos);
|
|
range.moveStart('character', pos);
|
|
range.select();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
|
|
* That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
|
|
* safe = true --> means no exception will be thrown
|
|
* for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
|
|
*/
|
|
tryParseJson: function(s, safe) {
|
|
if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
|
|
if (safe) {
|
|
try {
|
|
/*jslint evil: true*/
|
|
s = (new Function('return ' + s))();
|
|
/*jslint evil: false*/
|
|
} catch (e) {} finally {
|
|
return s;
|
|
}
|
|
} else {
|
|
/*jslint evil: true*/
|
|
s = (new Function('return ' + s))();
|
|
/*jslint evil: false*/
|
|
}
|
|
}
|
|
return s;
|
|
},
|
|
|
|
/**
|
|
* slice object by specified keys
|
|
*/
|
|
sliceObj: function(obj, keys, caseSensitive /* default: false */) {
|
|
var key, keyLower, newObj = {};
|
|
|
|
if (!$.isArray(keys) || !keys.length) {
|
|
return newObj;
|
|
}
|
|
|
|
for (var i = 0; i < keys.length; i++) {
|
|
key = keys[i];
|
|
if (obj.hasOwnProperty(key)) {
|
|
newObj[key] = obj[key];
|
|
}
|
|
|
|
if(caseSensitive === true) {
|
|
continue;
|
|
}
|
|
|
|
//when getting data-* attributes via $.data() it's converted to lowercase.
|
|
//details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
|
|
//workaround is code below.
|
|
keyLower = key.toLowerCase();
|
|
if (obj.hasOwnProperty(keyLower)) {
|
|
newObj[key] = obj[keyLower];
|
|
}
|
|
}
|
|
|
|
return newObj;
|
|
},
|
|
|
|
/*
|
|
exclude complex objects from $.data() before pass to config
|
|
*/
|
|
getConfigData: function($element) {
|
|
var data = {};
|
|
$.each($element.data(), function(k, v) {
|
|
if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
|
|
data[k] = v;
|
|
}
|
|
});
|
|
return data;
|
|
},
|
|
|
|
/*
|
|
returns keys of object
|
|
*/
|
|
objectKeys: function(o) {
|
|
if (Object.keys) {
|
|
return Object.keys(o);
|
|
} else {
|
|
if (o !== Object(o)) {
|
|
throw new TypeError('Object.keys called on a non-object');
|
|
}
|
|
var k=[], p;
|
|
for (p in o) {
|
|
if (Object.prototype.hasOwnProperty.call(o,p)) {
|
|
k.push(p);
|
|
}
|
|
}
|
|
return k;
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
method to escape html.
|
|
**/
|
|
escape: function(str) {
|
|
return $('<div>').text(str).html();
|
|
},
|
|
|
|
/*
|
|
returns array items from sourceData having value property equal or inArray of 'value'
|
|
*/
|
|
itemsByValue: function(value, sourceData, valueProp) {
|
|
if(!sourceData || value === null) {
|
|
return [];
|
|
}
|
|
|
|
if (typeof(valueProp) !== "function") {
|
|
var idKey = valueProp || 'value';
|
|
valueProp = function (e) { return e[idKey]; };
|
|
}
|
|
|
|
var isValArray = $.isArray(value),
|
|
result = [],
|
|
that = this;
|
|
|
|
$.each(sourceData, function(i, o) {
|
|
if(o.children) {
|
|
result = result.concat(that.itemsByValue(value, o.children, valueProp));
|
|
} else {
|
|
/*jslint eqeq: true*/
|
|
if(isValArray) {
|
|
if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {
|
|
result.push(o);
|
|
}
|
|
} else {
|
|
var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;
|
|
if(value == itemValue) {
|
|
result.push(o);
|
|
}
|
|
}
|
|
/*jslint eqeq: false*/
|
|
}
|
|
});
|
|
|
|
return result;
|
|
},
|
|
|
|
/*
|
|
Returns input by options: type, mode.
|
|
*/
|
|
createInput: function(options) {
|
|
var TypeConstructor, typeOptions, input,
|
|
type = options.type;
|
|
|
|
//`date` is some kind of virtual type that is transformed to one of exact types
|
|
//depending on mode and core lib
|
|
if(type === 'date') {
|
|
//inline
|
|
if(options.mode === 'inline') {
|
|
if($.fn.editabletypes.datefield) {
|
|
type = 'datefield';
|
|
} else if($.fn.editabletypes.dateuifield) {
|
|
type = 'dateuifield';
|
|
}
|
|
//popup
|
|
} else {
|
|
if($.fn.editabletypes.date) {
|
|
type = 'date';
|
|
} else if($.fn.editabletypes.dateui) {
|
|
type = 'dateui';
|
|
}
|
|
}
|
|
|
|
//if type still `date` and not exist in types, replace with `combodate` that is base input
|
|
if(type === 'date' && !$.fn.editabletypes.date) {
|
|
type = 'combodate';
|
|
}
|
|
}
|
|
|
|
//`datetime` should be datetimefield in 'inline' mode
|
|
if(type === 'datetime' && options.mode === 'inline') {
|
|
type = 'datetimefield';
|
|
}
|
|
|
|
//change wysihtml5 to textarea for jquery UI and plain versions
|
|
if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
|
|
type = 'textarea';
|
|
}
|
|
|
|
//create input of specified type. Input will be used for converting value, not in form
|
|
if(typeof $.fn.editabletypes[type] === 'function') {
|
|
TypeConstructor = $.fn.editabletypes[type];
|
|
typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
|
|
input = new TypeConstructor(typeOptions);
|
|
return input;
|
|
} else {
|
|
$.error('Unknown type: '+ type);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
//see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
|
|
supportsTransitions: function () {
|
|
var b = document.body || document.documentElement,
|
|
s = b.style,
|
|
p = 'transition',
|
|
v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
|
|
|
|
if(typeof s[p] === 'string') {
|
|
return true;
|
|
}
|
|
|
|
// Tests for vendor specific prop
|
|
p = p.charAt(0).toUpperCase() + p.substr(1);
|
|
for(var i=0; i<v.length; i++) {
|
|
if(typeof s[v[i] + p] === 'string') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
};
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
|
|
This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
|
|
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
|
|
Applied as jQuery method.
|
|
|
|
@class editableContainer
|
|
@uses editableform
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Popup = function (element, options) {
|
|
this.init(element, options);
|
|
};
|
|
|
|
var Inline = function (element, options) {
|
|
this.init(element, options);
|
|
};
|
|
|
|
//methods
|
|
Popup.prototype = {
|
|
containerName: null, //method to call container on element
|
|
containerDataName: null, //object name in element's .data()
|
|
innerCss: null, //tbd in child class
|
|
containerClass: 'editable-container editable-popup', //css class applied to container element
|
|
defaults: {}, //container itself defaults
|
|
|
|
init: function(element, options) {
|
|
this.$element = $(element);
|
|
//since 1.4.1 container do not use data-* directly as they already merged into options.
|
|
this.options = $.extend({}, $.fn.editableContainer.defaults, options);
|
|
this.splitOptions();
|
|
|
|
//set scope of form callbacks to element
|
|
this.formOptions.scope = this.$element[0];
|
|
|
|
this.initContainer();
|
|
|
|
//flag to hide container, when saving value will finish
|
|
this.delayedHide = false;
|
|
|
|
//bind 'destroyed' listener to destroy container when element is removed from dom
|
|
this.$element.on('destroyed', $.proxy(function(){
|
|
this.destroy();
|
|
}, this));
|
|
|
|
//attach document handler to close containers on click / escape
|
|
if(!$(document).data('editable-handlers-attached')) {
|
|
//close all on escape
|
|
$(document).on('keyup.editable', function (e) {
|
|
if (e.which === 27) {
|
|
$('.editable-open').editableContainer('hide');
|
|
//todo: return focus on element
|
|
}
|
|
});
|
|
|
|
//close containers when click outside
|
|
//(mousedown could be better than click, it closes everything also on drag drop)
|
|
$(document).on('click.editable', function(e) {
|
|
var $target = $(e.target), i,
|
|
exclude_classes = ['.editable-container',
|
|
'.ui-datepicker-header',
|
|
'.datepicker', //in inline mode datepicker is rendered into body
|
|
'.modal-backdrop',
|
|
'.bootstrap-wysihtml5-insert-image-modal',
|
|
'.bootstrap-wysihtml5-insert-link-modal'
|
|
];
|
|
|
|
//check if element is detached. It occurs when clicking in bootstrap datepicker
|
|
if (!$.contains(document.documentElement, e.target)) {
|
|
return;
|
|
}
|
|
|
|
//for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
|
|
//we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
|
|
//Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
|
|
if($target.is(document)) {
|
|
return;
|
|
}
|
|
|
|
//if click inside one of exclude classes --> no nothing
|
|
for(i=0; i<exclude_classes.length; i++) {
|
|
if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
//close all open containers (except one - target)
|
|
Popup.prototype.closeOthers(e.target);
|
|
});
|
|
|
|
$(document).data('editable-handlers-attached', true);
|
|
}
|
|
},
|
|
|
|
//split options on containerOptions and formOptions
|
|
splitOptions: function() {
|
|
this.containerOptions = {};
|
|
this.formOptions = {};
|
|
|
|
if(!$.fn[this.containerName]) {
|
|
throw new Error(this.containerName + ' not found. Have you included corresponding js file?');
|
|
}
|
|
|
|
//keys defined in container defaults go to container, others go to form
|
|
for(var k in this.options) {
|
|
if(k in this.defaults) {
|
|
this.containerOptions[k] = this.options[k];
|
|
} else {
|
|
this.formOptions[k] = this.options[k];
|
|
}
|
|
}
|
|
},
|
|
|
|
/*
|
|
Returns jquery object of container
|
|
@method tip()
|
|
*/
|
|
tip: function() {
|
|
return this.container() ? this.container().$tip : null;
|
|
},
|
|
|
|
/* returns container object */
|
|
container: function() {
|
|
var container;
|
|
//first, try get it by `containerDataName`
|
|
if(this.containerDataName) {
|
|
if(container = this.$element.data(this.containerDataName)) {
|
|
return container;
|
|
}
|
|
}
|
|
//second, try `containerName`
|
|
container = this.$element.data(this.containerName);
|
|
return container;
|
|
},
|
|
|
|
/* call native method of underlying container, e.g. this.$element.popover('method') */
|
|
call: function() {
|
|
this.$element[this.containerName].apply(this.$element, arguments);
|
|
},
|
|
|
|
initContainer: function(){
|
|
this.call(this.containerOptions);
|
|
},
|
|
|
|
renderForm: function() {
|
|
this.$form
|
|
.editableform(this.formOptions)
|
|
.on({
|
|
save: $.proxy(this.save, this), //click on submit button (value changed)
|
|
nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
|
|
cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
|
|
show: $.proxy(function() {
|
|
if(this.delayedHide) {
|
|
this.hide(this.delayedHide.reason);
|
|
this.delayedHide = false;
|
|
} else {
|
|
this.setPosition();
|
|
}
|
|
}, this), //re-position container every time form is shown (occurs each time after loading state)
|
|
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
|
|
resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
|
|
rendered: $.proxy(function(){
|
|
/**
|
|
Fired when container is shown and form is rendered (for select will wait for loading dropdown options).
|
|
**Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.
|
|
The workaround is to check `arguments.length` that is always `2` for x-editable.
|
|
|
|
@event shown
|
|
@param {Object} event event object
|
|
@example
|
|
$('#username').on('shown', function(e, editable) {
|
|
editable.input.$input.val('overwriting value of input..');
|
|
});
|
|
**/
|
|
/*
|
|
TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.
|
|
*/
|
|
this.$element.triggerHandler('shown', $(this.options.scope).data('editable'));
|
|
}, this)
|
|
})
|
|
.editableform('render');
|
|
},
|
|
|
|
/**
|
|
Shows container with form
|
|
@method show()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
/* Note: poshytip owerwrites this method totally! */
|
|
show: function (closeAll) {
|
|
this.$element.addClass('editable-open');
|
|
if(closeAll !== false) {
|
|
//close all open containers (except this)
|
|
this.closeOthers(this.$element[0]);
|
|
}
|
|
|
|
//show container itself
|
|
this.innerShow();
|
|
this.tip().addClass(this.containerClass);
|
|
|
|
/*
|
|
Currently, form is re-rendered on every show.
|
|
The main reason is that we dont know, what will container do with content when closed:
|
|
remove(), detach() or just hide() - it depends on container.
|
|
|
|
Detaching form itself before hide and re-insert before show is good solution,
|
|
but visually it looks ugly --> container changes size before hide.
|
|
*/
|
|
|
|
//if form already exist - delete previous data
|
|
if(this.$form) {
|
|
//todo: destroy prev data!
|
|
//this.$form.destroy();
|
|
}
|
|
|
|
this.$form = $('<div>');
|
|
|
|
//insert form into container body
|
|
if(this.tip().is(this.innerCss)) {
|
|
//for inline container
|
|
this.tip().append(this.$form);
|
|
} else {
|
|
this.tip().find(this.innerCss).append(this.$form);
|
|
}
|
|
|
|
//render form
|
|
this.renderForm();
|
|
},
|
|
|
|
/**
|
|
Hides container with form
|
|
@method hide()
|
|
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
|
**/
|
|
hide: function(reason) {
|
|
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
|
|
return;
|
|
}
|
|
|
|
//if form is saving value, schedule hide
|
|
if(this.$form.data('editableform').isSaving) {
|
|
this.delayedHide = {reason: reason};
|
|
return;
|
|
} else {
|
|
this.delayedHide = false;
|
|
}
|
|
|
|
this.$element.removeClass('editable-open');
|
|
this.innerHide();
|
|
|
|
/**
|
|
Fired when container was hidden. It occurs on both save or cancel.
|
|
**Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
|
|
The workaround is to check `arguments.length` that is always `2` for x-editable.
|
|
|
|
@event hidden
|
|
@param {object} event event object
|
|
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
|
|
@example
|
|
$('#username').on('hidden', function(e, reason) {
|
|
if(reason === 'save' || reason === 'cancel') {
|
|
//auto-open next editable
|
|
$(this).closest('tr').next().find('.editable').editable('show');
|
|
}
|
|
});
|
|
**/
|
|
this.$element.triggerHandler('hidden', reason || 'manual');
|
|
},
|
|
|
|
/* internal show method. To be overwritten in child classes */
|
|
innerShow: function () {
|
|
|
|
},
|
|
|
|
/* internal hide method. To be overwritten in child classes */
|
|
innerHide: function () {
|
|
|
|
},
|
|
|
|
/**
|
|
Toggles container visibility (show / hide)
|
|
@method toggle()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
toggle: function(closeAll) {
|
|
if(this.container() && this.tip() && this.tip().is(':visible')) {
|
|
this.hide();
|
|
} else {
|
|
this.show(closeAll);
|
|
}
|
|
},
|
|
|
|
/*
|
|
Updates the position of container when content changed.
|
|
@method setPosition()
|
|
*/
|
|
setPosition: function() {
|
|
//tbd in child class
|
|
},
|
|
|
|
save: function(e, params) {
|
|
/**
|
|
Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
|
|
|
|
@event save
|
|
@param {Object} event event object
|
|
@param {Object} params additional params
|
|
@param {mixed} params.newValue submitted value
|
|
@param {Object} params.response ajax response
|
|
@example
|
|
$('#username').on('save', function(e, params) {
|
|
//assuming server response: '{success: true}'
|
|
var pk = $(this).data('editableContainer').options.pk;
|
|
if(params.response && params.response.success) {
|
|
alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
|
|
} else {
|
|
alert('error!');
|
|
}
|
|
});
|
|
**/
|
|
this.$element.triggerHandler('save', params);
|
|
|
|
//hide must be after trigger, as saving value may require methods of plugin, applied to input
|
|
this.hide('save');
|
|
},
|
|
|
|
/**
|
|
Sets new option
|
|
|
|
@method option(key, value)
|
|
@param {string} key
|
|
@param {mixed} value
|
|
**/
|
|
option: function(key, value) {
|
|
this.options[key] = value;
|
|
if(key in this.containerOptions) {
|
|
this.containerOptions[key] = value;
|
|
this.setContainerOption(key, value);
|
|
} else {
|
|
this.formOptions[key] = value;
|
|
if(this.$form) {
|
|
this.$form.editableform('option', key, value);
|
|
}
|
|
}
|
|
},
|
|
|
|
setContainerOption: function(key, value) {
|
|
this.call('option', key, value);
|
|
},
|
|
|
|
/**
|
|
Destroys the container instance
|
|
@method destroy()
|
|
**/
|
|
destroy: function() {
|
|
this.hide();
|
|
this.innerDestroy();
|
|
this.$element.off('destroyed');
|
|
this.$element.removeData('editableContainer');
|
|
},
|
|
|
|
/* to be overwritten in child classes */
|
|
innerDestroy: function() {
|
|
|
|
},
|
|
|
|
/*
|
|
Closes other containers except one related to passed element.
|
|
Other containers can be cancelled or submitted (depends on onblur option)
|
|
*/
|
|
closeOthers: function(element) {
|
|
$('.editable-open').each(function(i, el){
|
|
//do nothing with passed element and it's children
|
|
if(el === element || $(el).find(element).length) {
|
|
return;
|
|
}
|
|
|
|
//otherwise cancel or submit all open containers
|
|
var $el = $(el),
|
|
ec = $el.data('editableContainer');
|
|
|
|
if(!ec) {
|
|
return;
|
|
}
|
|
|
|
if(ec.options.onblur === 'cancel') {
|
|
$el.data('editableContainer').hide('onblur');
|
|
} else if(ec.options.onblur === 'submit') {
|
|
$el.data('editableContainer').tip().find('form').submit();
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
/**
|
|
Activates input of visible container (e.g. set focus)
|
|
@method activate()
|
|
**/
|
|
activate: function() {
|
|
if(this.tip && this.tip().is(':visible') && this.$form) {
|
|
this.$form.data('editableform').input.activate();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
jQuery method to initialize editableContainer.
|
|
|
|
@method $().editableContainer(options)
|
|
@params {Object} options
|
|
@example
|
|
$('#edit').editableContainer({
|
|
type: 'text',
|
|
url: '/post',
|
|
pk: 1,
|
|
value: 'hello'
|
|
});
|
|
**/
|
|
$.fn.editableContainer = function (option) {
|
|
var args = arguments;
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
dataKey = 'editableContainer',
|
|
data = $this.data(dataKey),
|
|
options = typeof option === 'object' && option,
|
|
Constructor = (options.mode === 'inline') ? Inline : Popup;
|
|
|
|
if (!data) {
|
|
$this.data(dataKey, (data = new Constructor(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') { //call method
|
|
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
|
}
|
|
});
|
|
};
|
|
|
|
//store constructors
|
|
$.fn.editableContainer.Popup = Popup;
|
|
$.fn.editableContainer.Inline = Inline;
|
|
|
|
//defaults
|
|
$.fn.editableContainer.defaults = {
|
|
/**
|
|
Initial value of form input
|
|
|
|
@property value
|
|
@type mixed
|
|
@default null
|
|
@private
|
|
**/
|
|
value: null,
|
|
/**
|
|
Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
|
|
|
|
@property placement
|
|
@type string
|
|
@default 'top'
|
|
**/
|
|
placement: 'top',
|
|
/**
|
|
Whether to hide container on save/cancel.
|
|
|
|
@property autohide
|
|
@type boolean
|
|
@default true
|
|
@private
|
|
**/
|
|
autohide: true,
|
|
/**
|
|
Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
|
|
Setting <code>ignore</code> allows to have several containers open.
|
|
|
|
@property onblur
|
|
@type string
|
|
@default 'cancel'
|
|
@since 1.1.1
|
|
**/
|
|
onblur: 'cancel',
|
|
|
|
/**
|
|
Animation speed (inline mode only)
|
|
@property anim
|
|
@type string
|
|
@default false
|
|
**/
|
|
anim: false,
|
|
|
|
/**
|
|
Mode of editable, can be `popup` or `inline`
|
|
|
|
@property mode
|
|
@type string
|
|
@default 'popup'
|
|
@since 1.4.0
|
|
**/
|
|
mode: 'popup'
|
|
};
|
|
|
|
/*
|
|
* workaround to have 'destroyed' event to destroy popover when element is destroyed
|
|
* see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
|
|
*/
|
|
jQuery.event.special.destroyed = {
|
|
remove: function(o) {
|
|
if (o.handler) {
|
|
o.handler();
|
|
}
|
|
}
|
|
};
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
* Editable Inline
|
|
* ---------------------
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//copy prototype from EditableContainer
|
|
//extend methods
|
|
$.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
|
|
containerName: 'editableform',
|
|
innerCss: '.editable-inline',
|
|
containerClass: 'editable-container editable-inline', //css class applied to container element
|
|
|
|
initContainer: function(){
|
|
//container is <span> element
|
|
this.$tip = $('<span></span>');
|
|
|
|
//convert anim to miliseconds (int)
|
|
if(!this.options.anim) {
|
|
this.options.anim = 0;
|
|
}
|
|
},
|
|
|
|
splitOptions: function() {
|
|
//all options are passed to form
|
|
this.containerOptions = {};
|
|
this.formOptions = this.options;
|
|
},
|
|
|
|
tip: function() {
|
|
return this.$tip;
|
|
},
|
|
|
|
innerShow: function () {
|
|
this.$element.hide();
|
|
this.tip().insertAfter(this.$element).show();
|
|
},
|
|
|
|
innerHide: function () {
|
|
this.$tip.hide(this.options.anim, $.proxy(function() {
|
|
this.$element.show();
|
|
this.innerDestroy();
|
|
}, this));
|
|
},
|
|
|
|
innerDestroy: function() {
|
|
if(this.tip()) {
|
|
this.tip().empty().remove();
|
|
}
|
|
}
|
|
});
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Makes editable any HTML element on the page. Applied as jQuery method.
|
|
|
|
@class editable
|
|
@uses editableContainer
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Editable = function (element, options) {
|
|
this.$element = $(element);
|
|
//data-* has more priority over js options: because dynamically created elements may change data-*
|
|
this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
|
|
if(this.options.selector) {
|
|
this.initLive();
|
|
} else {
|
|
this.init();
|
|
}
|
|
|
|
//check for transition support
|
|
if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
|
|
this.options.highlight = false;
|
|
}
|
|
};
|
|
|
|
Editable.prototype = {
|
|
constructor: Editable,
|
|
init: function () {
|
|
var isValueByText = false,
|
|
doAutotext, finalize;
|
|
|
|
//name
|
|
this.options.name = this.options.name || this.$element.attr('id');
|
|
|
|
//create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
|
|
//also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
|
|
this.options.scope = this.$element[0];
|
|
this.input = $.fn.editableutils.createInput(this.options);
|
|
if(!this.input) {
|
|
return;
|
|
}
|
|
|
|
//set value from settings or by element's text
|
|
if (this.options.value === undefined || this.options.value === null) {
|
|
this.value = this.input.html2value($.trim(this.$element.html()));
|
|
isValueByText = true;
|
|
} else {
|
|
/*
|
|
value can be string when received from 'data-value' attribute
|
|
for complext objects value can be set as json string in data-value attribute,
|
|
e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
|
|
*/
|
|
this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
|
|
if(typeof this.options.value === 'string') {
|
|
this.value = this.input.str2value(this.options.value);
|
|
} else {
|
|
this.value = this.options.value;
|
|
}
|
|
}
|
|
|
|
//add 'editable' class to every editable element
|
|
this.$element.addClass('editable');
|
|
|
|
//specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
|
|
if(this.input.type === 'textarea') {
|
|
this.$element.addClass('editable-pre-wrapped');
|
|
}
|
|
|
|
//attach handler activating editable. In disabled mode it just prevent default action (useful for links)
|
|
if(this.options.toggle !== 'manual') {
|
|
this.$element.addClass('editable-click');
|
|
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
|
|
//prevent following link if editable enabled
|
|
if(!this.options.disabled) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
//stop propagation not required because in document click handler it checks event target
|
|
//e.stopPropagation();
|
|
|
|
if(this.options.toggle === 'mouseenter') {
|
|
//for hover only show container
|
|
this.show();
|
|
} else {
|
|
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
|
|
var closeAll = (this.options.toggle !== 'click');
|
|
this.toggle(closeAll);
|
|
}
|
|
}, this));
|
|
} else {
|
|
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
|
|
}
|
|
|
|
//if display is function it's far more convinient to have autotext = always to render correctly on init
|
|
//see https://github.com/vitalets/x-editable-yii/issues/34
|
|
if(typeof this.options.display === 'function') {
|
|
this.options.autotext = 'always';
|
|
}
|
|
|
|
//check conditions for autotext:
|
|
switch(this.options.autotext) {
|
|
case 'always':
|
|
doAutotext = true;
|
|
break;
|
|
case 'auto':
|
|
//if element text is empty and value is defined and value not generated by text --> run autotext
|
|
doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
|
|
break;
|
|
default:
|
|
doAutotext = false;
|
|
}
|
|
|
|
//depending on autotext run render() or just finilize init
|
|
$.when(doAutotext ? this.render() : true).then($.proxy(function() {
|
|
if(this.options.disabled) {
|
|
this.disable();
|
|
} else {
|
|
this.enable();
|
|
}
|
|
/**
|
|
Fired when element was initialized by `$().editable()` method.
|
|
Please note that you should setup `init` handler **before** applying `editable`.
|
|
|
|
@event init
|
|
@param {Object} event event object
|
|
@param {Object} editable editable instance (as here it cannot accessed via data('editable'))
|
|
@since 1.2.0
|
|
@example
|
|
$('#username').on('init', function(e, editable) {
|
|
alert('initialized ' + editable.options.name);
|
|
});
|
|
$('#username').editable();
|
|
**/
|
|
this.$element.triggerHandler('init', this);
|
|
}, this));
|
|
},
|
|
|
|
/*
|
|
Initializes parent element for live editables
|
|
*/
|
|
initLive: function() {
|
|
//store selector
|
|
var selector = this.options.selector;
|
|
//modify options for child elements
|
|
this.options.selector = false;
|
|
this.options.autotext = 'never';
|
|
//listen toggle events
|
|
this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
|
|
var $target = $(e.target);
|
|
if(!$target.data('editable')) {
|
|
//if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
|
|
//see https://github.com/vitalets/x-editable/issues/137
|
|
if($target.hasClass(this.options.emptyclass)) {
|
|
$target.empty();
|
|
}
|
|
$target.editable(this.options).trigger(e);
|
|
}
|
|
}, this));
|
|
},
|
|
|
|
/*
|
|
Renders value into element's text.
|
|
Can call custom display method from options.
|
|
Can return deferred object.
|
|
@method render()
|
|
@param {mixed} response server response (if exist) to pass into display function
|
|
*/
|
|
render: function(response) {
|
|
//do not display anything
|
|
if(this.options.display === false) {
|
|
return;
|
|
}
|
|
|
|
//if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
|
|
if(this.input.value2htmlFinal) {
|
|
return this.input.value2html(this.value, this.$element[0], this.options.display, response);
|
|
//if display method defined --> use it
|
|
} else if(typeof this.options.display === 'function') {
|
|
return this.options.display.call(this.$element[0], this.value, response);
|
|
//else use input's original value2html() method
|
|
} else {
|
|
return this.input.value2html(this.value, this.$element[0]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Enables editable
|
|
@method enable()
|
|
**/
|
|
enable: function() {
|
|
this.options.disabled = false;
|
|
this.$element.removeClass('editable-disabled');
|
|
this.handleEmpty(this.isEmpty);
|
|
if(this.options.toggle !== 'manual') {
|
|
if(this.$element.attr('tabindex') === '-1') {
|
|
this.$element.removeAttr('tabindex');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Disables editable
|
|
@method disable()
|
|
**/
|
|
disable: function() {
|
|
this.options.disabled = true;
|
|
this.hide();
|
|
this.$element.addClass('editable-disabled');
|
|
this.handleEmpty(this.isEmpty);
|
|
//do not stop focus on this element
|
|
this.$element.attr('tabindex', -1);
|
|
},
|
|
|
|
/**
|
|
Toggles enabled / disabled state of editable element
|
|
@method toggleDisabled()
|
|
**/
|
|
toggleDisabled: function() {
|
|
if(this.options.disabled) {
|
|
this.enable();
|
|
} else {
|
|
this.disable();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Sets new option
|
|
|
|
@method option(key, value)
|
|
@param {string|object} key option name or object with several options
|
|
@param {mixed} value option new value
|
|
@example
|
|
$('.editable').editable('option', 'pk', 2);
|
|
**/
|
|
option: function(key, value) {
|
|
//set option(s) by object
|
|
if(key && typeof key === 'object') {
|
|
$.each(key, $.proxy(function(k, v){
|
|
this.option($.trim(k), v);
|
|
}, this));
|
|
return;
|
|
}
|
|
|
|
//set option by string
|
|
this.options[key] = value;
|
|
|
|
//disabled
|
|
if(key === 'disabled') {
|
|
return value ? this.disable() : this.enable();
|
|
}
|
|
|
|
//value
|
|
if(key === 'value') {
|
|
this.setValue(value);
|
|
}
|
|
|
|
//transfer new option to container!
|
|
if(this.container) {
|
|
this.container.option(key, value);
|
|
}
|
|
|
|
//pass option to input directly (as it points to the same in form)
|
|
if(this.input.option) {
|
|
this.input.option(key, value);
|
|
}
|
|
|
|
},
|
|
|
|
/*
|
|
* set emptytext if element is empty
|
|
*/
|
|
handleEmpty: function (isEmpty) {
|
|
//do not handle empty if we do not display anything
|
|
if(this.options.display === false) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
isEmpty may be set directly as param of method.
|
|
It is required when we enable/disable field and can't rely on content
|
|
as node content is text: "Empty" that is not empty %)
|
|
*/
|
|
if(isEmpty !== undefined) {
|
|
this.isEmpty = isEmpty;
|
|
} else {
|
|
//detect empty
|
|
//for some inputs we need more smart check
|
|
//e.g. wysihtml5 may have <br>, <p></p>, <img>
|
|
if(typeof(this.input.isEmpty) === 'function') {
|
|
this.isEmpty = this.input.isEmpty(this.$element);
|
|
} else {
|
|
this.isEmpty = $.trim(this.$element.html()) === '';
|
|
}
|
|
}
|
|
|
|
//emptytext shown only for enabled
|
|
if(!this.options.disabled) {
|
|
if (this.isEmpty) {
|
|
this.$element.html(this.options.emptytext);
|
|
if(this.options.emptyclass) {
|
|
this.$element.addClass(this.options.emptyclass);
|
|
}
|
|
} else if(this.options.emptyclass) {
|
|
this.$element.removeClass(this.options.emptyclass);
|
|
}
|
|
} else {
|
|
//below required if element disable property was changed
|
|
if(this.isEmpty) {
|
|
this.$element.empty();
|
|
if(this.options.emptyclass) {
|
|
this.$element.removeClass(this.options.emptyclass);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Shows container with form
|
|
@method show()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
show: function (closeAll) {
|
|
if(this.options.disabled) {
|
|
return;
|
|
}
|
|
|
|
//init editableContainer: popover, tooltip, inline, etc..
|
|
if(!this.container) {
|
|
var containerOptions = $.extend({}, this.options, {
|
|
value: this.value,
|
|
input: this.input //pass input to form (as it is already created)
|
|
});
|
|
this.$element.editableContainer(containerOptions);
|
|
//listen `save` event
|
|
this.$element.on("save.internal", $.proxy(this.save, this));
|
|
this.container = this.$element.data('editableContainer');
|
|
} else if(this.container.tip().is(':visible')) {
|
|
return;
|
|
}
|
|
|
|
//show container
|
|
this.container.show(closeAll);
|
|
},
|
|
|
|
/**
|
|
Hides container with form
|
|
@method hide()
|
|
**/
|
|
hide: function () {
|
|
if(this.container) {
|
|
this.container.hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Toggles container visibility (show / hide)
|
|
@method toggle()
|
|
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
|
**/
|
|
toggle: function(closeAll) {
|
|
if(this.container && this.container.tip().is(':visible')) {
|
|
this.hide();
|
|
} else {
|
|
this.show(closeAll);
|
|
}
|
|
},
|
|
|
|
/*
|
|
* called when form was submitted
|
|
*/
|
|
save: function(e, params) {
|
|
//mark element with unsaved class if needed
|
|
if(this.options.unsavedclass) {
|
|
/*
|
|
Add unsaved css to element if:
|
|
- url is not user's function
|
|
- value was not sent to server
|
|
- params.response === undefined, that means data was not sent
|
|
- value changed
|
|
*/
|
|
var sent = false;
|
|
sent = sent || typeof this.options.url === 'function';
|
|
sent = sent || this.options.display === false;
|
|
sent = sent || params.response !== undefined;
|
|
sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue));
|
|
|
|
if(sent) {
|
|
this.$element.removeClass(this.options.unsavedclass);
|
|
} else {
|
|
this.$element.addClass(this.options.unsavedclass);
|
|
}
|
|
}
|
|
|
|
//highlight when saving
|
|
if(this.options.highlight) {
|
|
var $e = this.$element,
|
|
bgColor = $e.css('background-color');
|
|
|
|
$e.css('background-color', this.options.highlight);
|
|
setTimeout(function(){
|
|
if(bgColor === 'transparent') {
|
|
bgColor = '';
|
|
}
|
|
$e.css('background-color', bgColor);
|
|
$e.addClass('editable-bg-transition');
|
|
setTimeout(function(){
|
|
$e.removeClass('editable-bg-transition');
|
|
}, 1700);
|
|
}, 10);
|
|
}
|
|
|
|
//set new value
|
|
this.setValue(params.newValue, false, params.response);
|
|
|
|
/**
|
|
Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
|
|
|
|
@event save
|
|
@param {Object} event event object
|
|
@param {Object} params additional params
|
|
@param {mixed} params.newValue submitted value
|
|
@param {Object} params.response ajax response
|
|
@example
|
|
$('#username').on('save', function(e, params) {
|
|
alert('Saved value: ' + params.newValue);
|
|
});
|
|
**/
|
|
//event itself is triggered by editableContainer. Description here is only for documentation
|
|
},
|
|
|
|
validate: function () {
|
|
if (typeof this.options.validate === 'function') {
|
|
return this.options.validate.call(this, this.value);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Sets new value of editable
|
|
@method setValue(value, convertStr)
|
|
@param {mixed} value new value
|
|
@param {boolean} convertStr whether to convert value from string to internal format
|
|
**/
|
|
setValue: function(value, convertStr, response) {
|
|
if(convertStr) {
|
|
this.value = this.input.str2value(value);
|
|
} else {
|
|
this.value = value;
|
|
}
|
|
if(this.container) {
|
|
this.container.option('value', this.value);
|
|
}
|
|
$.when(this.render(response))
|
|
.then($.proxy(function() {
|
|
this.handleEmpty();
|
|
}, this));
|
|
},
|
|
|
|
/**
|
|
Activates input of visible container (e.g. set focus)
|
|
@method activate()
|
|
**/
|
|
activate: function() {
|
|
if(this.container) {
|
|
this.container.activate();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Removes editable feature from element
|
|
@method destroy()
|
|
**/
|
|
destroy: function() {
|
|
this.disable();
|
|
|
|
if(this.container) {
|
|
this.container.destroy();
|
|
}
|
|
|
|
this.input.destroy();
|
|
|
|
if(this.options.toggle !== 'manual') {
|
|
this.$element.removeClass('editable-click');
|
|
this.$element.off(this.options.toggle + '.editable');
|
|
}
|
|
|
|
this.$element.off("save.internal");
|
|
|
|
this.$element.removeClass('editable editable-open editable-disabled');
|
|
this.$element.removeData('editable');
|
|
}
|
|
};
|
|
|
|
/* EDITABLE PLUGIN DEFINITION
|
|
* ======================= */
|
|
|
|
/**
|
|
jQuery method to initialize editable element.
|
|
|
|
@method $().editable(options)
|
|
@params {Object} options
|
|
@example
|
|
$('#username').editable({
|
|
type: 'text',
|
|
url: '/post',
|
|
pk: 1
|
|
});
|
|
**/
|
|
$.fn.editable = function (option) {
|
|
//special API methods returning non-jquery object
|
|
var result = {}, args = arguments, datakey = 'editable';
|
|
switch (option) {
|
|
/**
|
|
Runs client-side validation for all matched editables
|
|
|
|
@method validate()
|
|
@returns {Object} validation errors map
|
|
@example
|
|
$('#username, #fullname').editable('validate');
|
|
// possible result:
|
|
{
|
|
username: "username is required",
|
|
fullname: "fullname should be minimum 3 letters length"
|
|
}
|
|
**/
|
|
case 'validate':
|
|
this.each(function () {
|
|
var $this = $(this), data = $this.data(datakey), error;
|
|
if (data && (error = data.validate())) {
|
|
result[data.options.name] = error;
|
|
}
|
|
});
|
|
return result;
|
|
|
|
/**
|
|
Returns current values of editable elements.
|
|
Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
|
|
If value of some editable is `null` or `undefined` it is excluded from result object.
|
|
When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
|
|
|
|
@method getValue()
|
|
@param {bool} isSingle whether to return just value of single element
|
|
@returns {Object} object of element names and values
|
|
@example
|
|
$('#username, #fullname').editable('getValue');
|
|
//result:
|
|
{
|
|
username: "superuser",
|
|
fullname: "John"
|
|
}
|
|
//isSingle = true
|
|
$('#username').editable('getValue', true);
|
|
//result "superuser"
|
|
**/
|
|
case 'getValue':
|
|
if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
|
|
result = this.eq(0).data(datakey).value;
|
|
} else {
|
|
this.each(function () {
|
|
var $this = $(this), data = $this.data(datakey);
|
|
if (data && data.value !== undefined && data.value !== null) {
|
|
result[data.options.name] = data.input.value2submit(data.value);
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
|
|
/**
|
|
This method collects values from several editable elements and submit them all to server.
|
|
Internally it runs client-side validation for all fields and submits only in case of success.
|
|
See <a href="#newrecord">creating new records</a> for details.
|
|
Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
|
|
`url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`.
|
|
|
|
@method submit(options)
|
|
@param {object} options
|
|
@param {object} options.url url to submit data
|
|
@param {object} options.data additional data to submit
|
|
@param {object} options.ajaxOptions additional ajax options
|
|
@param {function} options.error(obj) error handler
|
|
@param {function} options.success(obj,config) success handler
|
|
@returns {Object} jQuery object
|
|
**/
|
|
case 'submit': //collects value, validate and submit to server for creating new record
|
|
var config = arguments[1] || {},
|
|
$elems = this,
|
|
errors = this.editable('validate');
|
|
|
|
// validation ok
|
|
if($.isEmptyObject(errors)) {
|
|
var ajaxOptions = {};
|
|
|
|
// for single element use url, success etc from options
|
|
if($elems.length === 1) {
|
|
var editable = $elems.data('editable');
|
|
//standard params
|
|
var params = {
|
|
name: editable.options.name || '',
|
|
value: editable.input.value2submit(editable.value),
|
|
pk: (typeof editable.options.pk === 'function') ?
|
|
editable.options.pk.call(editable.options.scope) :
|
|
editable.options.pk
|
|
};
|
|
|
|
//additional params
|
|
if(typeof editable.options.params === 'function') {
|
|
params = editable.options.params.call(editable.options.scope, params);
|
|
} else {
|
|
//try parse json in single quotes (from data-params attribute)
|
|
editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);
|
|
$.extend(params, editable.options.params);
|
|
}
|
|
|
|
ajaxOptions = {
|
|
url: editable.options.url,
|
|
data: params,
|
|
type: 'POST'
|
|
};
|
|
|
|
// use success / error from options
|
|
config.success = config.success || editable.options.success;
|
|
config.error = config.error || editable.options.error;
|
|
|
|
// multiple elements
|
|
} else {
|
|
var values = this.editable('getValue');
|
|
|
|
ajaxOptions = {
|
|
url: config.url,
|
|
data: values,
|
|
type: 'POST'
|
|
};
|
|
}
|
|
|
|
// ajax success callabck (response 200 OK)
|
|
ajaxOptions.success = typeof config.success === 'function' ? function(response) {
|
|
config.success.call($elems, response, config);
|
|
} : $.noop;
|
|
|
|
// ajax error callabck
|
|
ajaxOptions.error = typeof config.error === 'function' ? function() {
|
|
config.error.apply($elems, arguments);
|
|
} : $.noop;
|
|
|
|
// extend ajaxOptions
|
|
if(config.ajaxOptions) {
|
|
$.extend(ajaxOptions, config.ajaxOptions);
|
|
}
|
|
|
|
// extra data
|
|
if(config.data) {
|
|
$.extend(ajaxOptions.data, config.data);
|
|
}
|
|
|
|
// perform ajax request
|
|
$.ajax(ajaxOptions);
|
|
} else { //client-side validation error
|
|
if(typeof config.error === 'function') {
|
|
config.error.call($elems, errors);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
//return jquery object
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data(datakey),
|
|
options = typeof option === 'object' && option;
|
|
|
|
//for delegated targets do not store `editable` object for element
|
|
//it's allows several different selectors.
|
|
//see: https://github.com/vitalets/x-editable/issues/312
|
|
if(options && options.selector) {
|
|
data = new Editable(this, options);
|
|
return;
|
|
}
|
|
|
|
if (!data) {
|
|
$this.data(datakey, (data = new Editable(this, options)));
|
|
}
|
|
|
|
if (typeof option === 'string') { //call method
|
|
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
$.fn.editable.defaults = {
|
|
/**
|
|
Type of input. Can be <code>text|textarea|select|date|checklist</code> and more
|
|
|
|
@property type
|
|
@type string
|
|
@default 'text'
|
|
**/
|
|
type: 'text',
|
|
/**
|
|
Sets disabled state of editable
|
|
|
|
@property disabled
|
|
@type boolean
|
|
@default false
|
|
**/
|
|
disabled: false,
|
|
/**
|
|
How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.
|
|
When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.
|
|
**Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element,
|
|
you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
|
|
|
|
@example
|
|
$('#edit-button').click(function(e) {
|
|
e.stopPropagation();
|
|
$('#username').editable('toggle');
|
|
});
|
|
|
|
@property toggle
|
|
@type string
|
|
@default 'click'
|
|
**/
|
|
toggle: 'click',
|
|
/**
|
|
Text shown when element is empty.
|
|
|
|
@property emptytext
|
|
@type string
|
|
@default 'Empty'
|
|
**/
|
|
emptytext: 'Empty',
|
|
/**
|
|
Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
|
|
For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.
|
|
<code>auto</code> - text will be automatically set only if element is empty.
|
|
<code>always|never</code> - always(never) try to set element's text.
|
|
|
|
@property autotext
|
|
@type string
|
|
@default 'auto'
|
|
**/
|
|
autotext: 'auto',
|
|
/**
|
|
Initial value of input. If not set, taken from element's text.
|
|
Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).
|
|
For example, to display currency sign:
|
|
@example
|
|
<a id="price" data-type="text" data-value="100"></a>
|
|
<script>
|
|
$('#price').editable({
|
|
...
|
|
display: function(value) {
|
|
$(this).text(value + '$');
|
|
}
|
|
})
|
|
</script>
|
|
|
|
@property value
|
|
@type mixed
|
|
@default element's text
|
|
**/
|
|
value: null,
|
|
/**
|
|
Callback to perform custom displaying of value in element's text.
|
|
If `null`, default input's display used.
|
|
If `false`, no displaying methods will be called, element's text will never change.
|
|
Runs under element's scope.
|
|
_**Parameters:**_
|
|
|
|
* `value` current value to be displayed
|
|
* `response` server response (if display called after ajax submit), since 1.4.0
|
|
|
|
For _inputs with source_ (select, checklist) parameters are different:
|
|
|
|
* `value` current value to be displayed
|
|
* `sourceData` array of items for current input (e.g. dropdown items)
|
|
* `response` server response (if display called after ajax submit), since 1.4.0
|
|
|
|
To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
|
|
|
|
@property display
|
|
@type function|boolean
|
|
@default null
|
|
@since 1.2.0
|
|
@example
|
|
display: function(value, sourceData) {
|
|
//display checklist as comma-separated values
|
|
var html = [],
|
|
checked = $.fn.editableutils.itemsByValue(value, sourceData);
|
|
|
|
if(checked.length) {
|
|
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
|
|
$(this).html(html.join(', '));
|
|
} else {
|
|
$(this).empty();
|
|
}
|
|
}
|
|
**/
|
|
display: null,
|
|
/**
|
|
Css class applied when editable text is empty.
|
|
|
|
@property emptyclass
|
|
@type string
|
|
@since 1.4.1
|
|
@default editable-empty
|
|
**/
|
|
emptyclass: 'editable-empty',
|
|
/**
|
|
Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
|
|
You may set it to `null` if you work with editables locally and submit them together.
|
|
|
|
@property unsavedclass
|
|
@type string
|
|
@since 1.4.1
|
|
@default editable-unsaved
|
|
**/
|
|
unsavedclass: 'editable-unsaved',
|
|
/**
|
|
If selector is provided, editable will be delegated to the specified targets.
|
|
Usefull for dynamically generated DOM elements.
|
|
**Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options,
|
|
as they actually become editable only after first click.
|
|
You should manually set class `editable-click` to these elements.
|
|
Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
|
|
|
|
@property selector
|
|
@type string
|
|
@since 1.4.1
|
|
@default null
|
|
@example
|
|
<div id="user">
|
|
<!-- empty -->
|
|
<a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
|
|
<!-- non-empty -->
|
|
<a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
|
|
</div>
|
|
|
|
<script>
|
|
$('#user').editable({
|
|
selector: 'a',
|
|
url: '/post',
|
|
pk: 1
|
|
});
|
|
</script>
|
|
**/
|
|
selector: null,
|
|
/**
|
|
Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
|
|
|
|
@property highlight
|
|
@type string|boolean
|
|
@since 1.4.5
|
|
@default #FFFF80
|
|
**/
|
|
highlight: '#FFFF80'
|
|
};
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
AbstractInput - base class for all editable inputs.
|
|
It defines interface to be implemented by any input type.
|
|
To create your own input you can inherit from this class.
|
|
|
|
@class abstractinput
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//types
|
|
$.fn.editabletypes = {};
|
|
|
|
var AbstractInput = function () { };
|
|
|
|
AbstractInput.prototype = {
|
|
/**
|
|
Initializes input
|
|
|
|
@method init()
|
|
**/
|
|
init: function(type, options, defaults) {
|
|
this.type = type;
|
|
this.options = $.extend({}, defaults, options);
|
|
},
|
|
|
|
/*
|
|
this method called before render to init $tpl that is inserted in DOM
|
|
*/
|
|
prerender: function() {
|
|
this.$tpl = $(this.options.tpl); //whole tpl as jquery object
|
|
this.$input = this.$tpl; //control itself, can be changed in render method
|
|
this.$clear = null; //clear button
|
|
this.error = null; //error message, if input cannot be rendered
|
|
},
|
|
|
|
/**
|
|
Renders input from tpl. Can return jQuery deferred object.
|
|
Can be overwritten in child objects
|
|
|
|
@method render()
|
|
**/
|
|
render: function() {
|
|
|
|
},
|
|
|
|
/**
|
|
Sets element's html by value.
|
|
|
|
@method value2html(value, element)
|
|
@param {mixed} value
|
|
@param {DOMElement} element
|
|
**/
|
|
value2html: function(value, element) {
|
|
$(element)[this.options.escape ? 'text' : 'html']($.trim(value));
|
|
},
|
|
|
|
/**
|
|
Converts element's html to value
|
|
|
|
@method html2value(html)
|
|
@param {string} html
|
|
@returns {mixed}
|
|
**/
|
|
html2value: function(html) {
|
|
return $('<div>').html(html).text();
|
|
},
|
|
|
|
/**
|
|
Converts value to string (for internal compare). For submitting to server used value2submit().
|
|
|
|
@method value2str(value)
|
|
@param {mixed} value
|
|
@returns {string}
|
|
**/
|
|
value2str: function(value) {
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
Converts string received from server into value. Usually from `data-value` attribute.
|
|
|
|
@method str2value(str)
|
|
@param {string} str
|
|
@returns {mixed}
|
|
**/
|
|
str2value: function(str) {
|
|
return str;
|
|
},
|
|
|
|
/**
|
|
Converts value for submitting to server. Result can be string or object.
|
|
|
|
@method value2submit(value)
|
|
@param {mixed} value
|
|
@returns {mixed}
|
|
**/
|
|
value2submit: function(value) {
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
Sets value of input.
|
|
|
|
@method value2input(value)
|
|
@param {mixed} value
|
|
**/
|
|
value2input: function(value) {
|
|
this.$input.val(value);
|
|
},
|
|
|
|
/**
|
|
Returns value of input. Value can be object (e.g. datepicker)
|
|
|
|
@method input2value()
|
|
**/
|
|
input2value: function() {
|
|
return this.$input.val();
|
|
},
|
|
|
|
/**
|
|
Activates input. For text it sets focus.
|
|
|
|
@method activate()
|
|
**/
|
|
activate: function() {
|
|
if(this.$input.is(':visible')) {
|
|
this.$input.focus();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Creates input.
|
|
|
|
@method clear()
|
|
**/
|
|
clear: function() {
|
|
this.$input.val(null);
|
|
},
|
|
|
|
/**
|
|
method to escape html.
|
|
**/
|
|
escape: function(str) {
|
|
return $('<div>').text(str).html();
|
|
},
|
|
|
|
/**
|
|
attach handler to automatically submit form when value changed (useful when buttons not shown)
|
|
**/
|
|
autosubmit: function() {
|
|
|
|
},
|
|
|
|
/**
|
|
Additional actions when destroying element
|
|
**/
|
|
destroy: function() {
|
|
},
|
|
|
|
// -------- helper functions --------
|
|
setClass: function() {
|
|
if(this.options.inputclass) {
|
|
this.$input.addClass(this.options.inputclass);
|
|
}
|
|
},
|
|
|
|
setAttr: function(attr) {
|
|
if (this.options[attr] !== undefined && this.options[attr] !== null) {
|
|
this.$input.attr(attr, this.options[attr]);
|
|
}
|
|
},
|
|
|
|
option: function(key, value) {
|
|
this.options[key] = value;
|
|
}
|
|
|
|
};
|
|
|
|
AbstractInput.defaults = {
|
|
/**
|
|
HTML template of input. Normally you should not change it.
|
|
|
|
@property tpl
|
|
@type string
|
|
@default ''
|
|
**/
|
|
tpl: '',
|
|
/**
|
|
CSS class automatically applied to input
|
|
|
|
@property inputclass
|
|
@type string
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
|
|
/**
|
|
If `true` - html will be escaped in content of element via $.text() method.
|
|
If `false` - html will not be escaped, $.html() used.
|
|
When you use own `display` function, this option obviosly has no effect.
|
|
|
|
@property escape
|
|
@type boolean
|
|
@since 1.5.0
|
|
@default true
|
|
**/
|
|
escape: true,
|
|
|
|
//scope for external methods (e.g. source defined as function)
|
|
//for internal use only
|
|
scope: null,
|
|
|
|
//need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)
|
|
showbuttons: true
|
|
};
|
|
|
|
$.extend($.fn.editabletypes, {abstractinput: AbstractInput});
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
List - abstract class for inputs that have source option loaded from js array or via ajax
|
|
|
|
@class list
|
|
@extends abstractinput
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var List = function (options) {
|
|
|
|
};
|
|
|
|
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(List.prototype, {
|
|
render: function () {
|
|
var deferred = $.Deferred();
|
|
|
|
this.error = null;
|
|
this.onSourceReady(function () {
|
|
this.renderList();
|
|
deferred.resolve();
|
|
}, function () {
|
|
this.error = this.options.sourceError;
|
|
deferred.resolve();
|
|
});
|
|
|
|
return deferred.promise();
|
|
},
|
|
|
|
html2value: function (html) {
|
|
return null; //can't set value by text
|
|
},
|
|
|
|
value2html: function (value, element, display, response) {
|
|
var deferred = $.Deferred(),
|
|
success = function () {
|
|
if(typeof display === 'function') {
|
|
//custom display method
|
|
display.call(element, value, this.sourceData, response);
|
|
} else {
|
|
this.value2htmlFinal(value, element);
|
|
}
|
|
deferred.resolve();
|
|
};
|
|
|
|
//for null value just call success without loading source
|
|
if(value === null) {
|
|
success.call(this);
|
|
} else {
|
|
this.onSourceReady(success, function () { deferred.resolve(); });
|
|
}
|
|
|
|
return deferred.promise();
|
|
},
|
|
|
|
// ------------- additional functions ------------
|
|
|
|
onSourceReady: function (success, error) {
|
|
//run source if it function
|
|
var source;
|
|
if ($.isFunction(this.options.source)) {
|
|
source = this.options.source.call(this.options.scope);
|
|
this.sourceData = null;
|
|
//note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
|
|
} else {
|
|
source = this.options.source;
|
|
}
|
|
|
|
//if allready loaded just call success
|
|
if(this.options.sourceCache && $.isArray(this.sourceData)) {
|
|
success.call(this);
|
|
return;
|
|
}
|
|
|
|
//try parse json in single quotes (for double quotes jquery does automatically)
|
|
try {
|
|
source = $.fn.editableutils.tryParseJson(source, false);
|
|
} catch (e) {
|
|
error.call(this);
|
|
return;
|
|
}
|
|
|
|
//loading from url
|
|
if (typeof source === 'string') {
|
|
//try to get sourceData from cache
|
|
if(this.options.sourceCache) {
|
|
var cacheID = source,
|
|
cache;
|
|
|
|
if (!$(document).data(cacheID)) {
|
|
$(document).data(cacheID, {});
|
|
}
|
|
cache = $(document).data(cacheID);
|
|
|
|
//check for cached data
|
|
if (cache.loading === false && cache.sourceData) { //take source from cache
|
|
this.sourceData = cache.sourceData;
|
|
this.doPrepend();
|
|
success.call(this);
|
|
return;
|
|
} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
|
|
cache.callbacks.push($.proxy(function () {
|
|
this.sourceData = cache.sourceData;
|
|
this.doPrepend();
|
|
success.call(this);
|
|
}, this));
|
|
|
|
//also collecting error callbacks
|
|
cache.err_callbacks.push($.proxy(error, this));
|
|
return;
|
|
} else { //no cache yet, activate it
|
|
cache.loading = true;
|
|
cache.callbacks = [];
|
|
cache.err_callbacks = [];
|
|
}
|
|
}
|
|
|
|
//ajaxOptions for source. Can be overwritten bt options.sourceOptions
|
|
var ajaxOptions = $.extend({
|
|
url: source,
|
|
type: 'get',
|
|
cache: false,
|
|
dataType: 'json',
|
|
success: $.proxy(function (data) {
|
|
if(cache) {
|
|
cache.loading = false;
|
|
}
|
|
this.sourceData = this.makeArray(data);
|
|
if($.isArray(this.sourceData)) {
|
|
if(cache) {
|
|
//store result in cache
|
|
cache.sourceData = this.sourceData;
|
|
//run success callbacks for other fields waiting for this source
|
|
$.each(cache.callbacks, function () { this.call(); });
|
|
}
|
|
this.doPrepend();
|
|
success.call(this);
|
|
} else {
|
|
error.call(this);
|
|
if(cache) {
|
|
//run error callbacks for other fields waiting for this source
|
|
$.each(cache.err_callbacks, function () { this.call(); });
|
|
}
|
|
}
|
|
}, this),
|
|
error: $.proxy(function () {
|
|
error.call(this);
|
|
if(cache) {
|
|
cache.loading = false;
|
|
//run error callbacks for other fields
|
|
$.each(cache.err_callbacks, function () { this.call(); });
|
|
}
|
|
}, this)
|
|
}, this.options.sourceOptions);
|
|
|
|
//loading sourceData from server
|
|
$.ajax(ajaxOptions);
|
|
|
|
} else { //options as json/array
|
|
this.sourceData = this.makeArray(source);
|
|
|
|
if($.isArray(this.sourceData)) {
|
|
this.doPrepend();
|
|
success.call(this);
|
|
} else {
|
|
error.call(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
doPrepend: function () {
|
|
if(this.options.prepend === null || this.options.prepend === undefined) {
|
|
return;
|
|
}
|
|
|
|
if(!$.isArray(this.prependData)) {
|
|
//run prepend if it is function (once)
|
|
if ($.isFunction(this.options.prepend)) {
|
|
this.options.prepend = this.options.prepend.call(this.options.scope);
|
|
}
|
|
|
|
//try parse json in single quotes
|
|
this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
|
|
|
|
//convert prepend from string to object
|
|
if (typeof this.options.prepend === 'string') {
|
|
this.options.prepend = {'': this.options.prepend};
|
|
}
|
|
|
|
this.prependData = this.makeArray(this.options.prepend);
|
|
}
|
|
|
|
if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
|
|
this.sourceData = this.prependData.concat(this.sourceData);
|
|
}
|
|
},
|
|
|
|
/*
|
|
renders input list
|
|
*/
|
|
renderList: function() {
|
|
// this method should be overwritten in child class
|
|
},
|
|
|
|
/*
|
|
set element's html by value
|
|
*/
|
|
value2htmlFinal: function(value, element) {
|
|
// this method should be overwritten in child class
|
|
},
|
|
|
|
/**
|
|
* convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
|
|
*/
|
|
makeArray: function(data) {
|
|
var count, obj, result = [], item, iterateItem;
|
|
if(!data || typeof data === 'string') {
|
|
return null;
|
|
}
|
|
|
|
if($.isArray(data)) { //array
|
|
/*
|
|
function to iterate inside item of array if item is object.
|
|
Caclulates count of keys in item and store in obj.
|
|
*/
|
|
iterateItem = function (k, v) {
|
|
obj = {value: k, text: v};
|
|
if(count++ >= 2) {
|
|
return false;// exit from `each` if item has more than one key.
|
|
}
|
|
};
|
|
|
|
for(var i = 0; i < data.length; i++) {
|
|
item = data[i];
|
|
if(typeof item === 'object') {
|
|
count = 0; //count of keys inside item
|
|
$.each(item, iterateItem);
|
|
//case: [{val1: 'text1'}, {val2: 'text2} ...]
|
|
if(count === 1) {
|
|
result.push(obj);
|
|
//case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
|
|
} else if(count > 1) {
|
|
//removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
|
|
if(item.children) {
|
|
item.children = this.makeArray(item.children);
|
|
}
|
|
result.push(item);
|
|
}
|
|
} else {
|
|
//case: ['text1', 'text2' ...]
|
|
result.push({value: item, text: item});
|
|
}
|
|
}
|
|
} else { //case: {val1: 'text1', val2: 'text2, ...}
|
|
$.each(data, function (k, v) {
|
|
result.push({value: k, text: v});
|
|
});
|
|
}
|
|
return result;
|
|
},
|
|
|
|
option: function(key, value) {
|
|
this.options[key] = value;
|
|
if(key === 'source') {
|
|
this.sourceData = null;
|
|
}
|
|
if(key === 'prepend') {
|
|
this.prependData = null;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
Source data for list.
|
|
If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`
|
|
For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
|
|
|
|
If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.
|
|
|
|
If **function**, it should return data in format above (since 1.4.0).
|
|
|
|
Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).
|
|
`[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]`
|
|
|
|
|
|
@property source
|
|
@type string | array | object | function
|
|
@default null
|
|
**/
|
|
source: null,
|
|
/**
|
|
Data automatically prepended to the beginning of dropdown list.
|
|
|
|
@property prepend
|
|
@type string | array | object | function
|
|
@default false
|
|
**/
|
|
prepend: false,
|
|
/**
|
|
Error message when list cannot be loaded (e.g. ajax error)
|
|
|
|
@property sourceError
|
|
@type string
|
|
@default Error when loading list
|
|
**/
|
|
sourceError: 'Error when loading list',
|
|
/**
|
|
if <code>true</code> and source is **string url** - results will be cached for fields with the same source.
|
|
Usefull for editable column in grid to prevent extra requests.
|
|
|
|
@property sourceCache
|
|
@type boolean
|
|
@default true
|
|
@since 1.2.0
|
|
**/
|
|
sourceCache: true,
|
|
/**
|
|
Additional ajax options to be used in $.ajax() when loading list from server.
|
|
Useful to send extra parameters (`data` key) or change request method (`type` key).
|
|
|
|
@property sourceOptions
|
|
@type object|function
|
|
@default null
|
|
@since 1.5.0
|
|
**/
|
|
sourceOptions: null
|
|
});
|
|
|
|
$.fn.editabletypes.list = List;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Text input
|
|
|
|
@class text
|
|
@extends abstractinput
|
|
@final
|
|
@example
|
|
<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
|
|
<script>
|
|
$(function(){
|
|
$('#username').editable({
|
|
url: '/post',
|
|
title: 'Enter username'
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Text = function (options) {
|
|
this.init('text', options, Text.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Text.prototype, {
|
|
render: function() {
|
|
this.renderClear();
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
},
|
|
|
|
activate: function() {
|
|
if(this.$input.is(':visible')) {
|
|
this.$input.focus();
|
|
$.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
|
|
if(this.toggleClear) {
|
|
this.toggleClear();
|
|
}
|
|
}
|
|
},
|
|
|
|
//render clear button
|
|
renderClear: function() {
|
|
if (this.options.clear) {
|
|
this.$clear = $('<span class="editable-clear-x"></span>');
|
|
this.$input.after(this.$clear)
|
|
.css('padding-right', 24)
|
|
.keyup($.proxy(function(e) {
|
|
//arrows, enter, tab, etc
|
|
if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout(this.t);
|
|
var that = this;
|
|
this.t = setTimeout(function() {
|
|
that.toggleClear(e);
|
|
}, 100);
|
|
|
|
}, this))
|
|
.parent().css('position', 'relative');
|
|
|
|
this.$clear.click($.proxy(this.clear, this));
|
|
}
|
|
},
|
|
|
|
postrender: function() {
|
|
/*
|
|
//now `clear` is positioned via css
|
|
if(this.$clear) {
|
|
//can position clear button only here, when form is shown and height can be calculated
|
|
// var h = this.$input.outerHeight(true) || 20,
|
|
var h = this.$clear.parent().height(),
|
|
delta = (h - this.$clear.height()) / 2;
|
|
|
|
//this.$clear.css({bottom: delta, right: delta});
|
|
}
|
|
*/
|
|
},
|
|
|
|
//show / hide clear button
|
|
toggleClear: function(e) {
|
|
if(!this.$clear) {
|
|
return;
|
|
}
|
|
|
|
var len = this.$input.val().length,
|
|
visible = this.$clear.is(':visible');
|
|
|
|
if(len && !visible) {
|
|
this.$clear.show();
|
|
}
|
|
|
|
if(!len && visible) {
|
|
this.$clear.hide();
|
|
}
|
|
},
|
|
|
|
clear: function() {
|
|
this.$clear.hide();
|
|
this.$input.val('').focus();
|
|
}
|
|
});
|
|
|
|
Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="text">
|
|
**/
|
|
tpl: '<input type="text">',
|
|
/**
|
|
Placeholder attribute of input. Shown when input is empty.
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
|
|
/**
|
|
Whether to show `clear` button
|
|
|
|
@property clear
|
|
@type boolean
|
|
@default true
|
|
**/
|
|
clear: true
|
|
});
|
|
|
|
$.fn.editabletypes.text = Text;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Textarea input
|
|
|
|
@class textarea
|
|
@extends abstractinput
|
|
@final
|
|
@example
|
|
<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
|
|
<script>
|
|
$(function(){
|
|
$('#comments').editable({
|
|
url: '/post',
|
|
title: 'Enter comments',
|
|
rows: 10
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Textarea = function (options) {
|
|
this.init('textarea', options, Textarea.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Textarea.prototype, {
|
|
render: function () {
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
this.setAttr('rows');
|
|
|
|
//ctrl + enter
|
|
this.$input.keydown(function (e) {
|
|
if (e.ctrlKey && e.which === 13) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
//using `white-space: pre-wrap` solves \n <--> BR conversion very elegant!
|
|
/*
|
|
value2html: function(value, element) {
|
|
var html = '', lines;
|
|
if(value) {
|
|
lines = value.split("\n");
|
|
for (var i = 0; i < lines.length; i++) {
|
|
lines[i] = $('<div>').text(lines[i]).html();
|
|
}
|
|
html = lines.join('<br>');
|
|
}
|
|
$(element).html(html);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
if(!html) {
|
|
return '';
|
|
}
|
|
|
|
var regex = new RegExp(String.fromCharCode(10), 'g');
|
|
var lines = html.split(/<br\s*\/?>/i);
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var text = $('<div>').html(lines[i]).text();
|
|
|
|
// Remove newline characters (\n) to avoid them being converted by value2html() method
|
|
// thus adding extra <br> tags
|
|
text = text.replace(regex, '');
|
|
|
|
lines[i] = text;
|
|
}
|
|
return lines.join("\n");
|
|
},
|
|
*/
|
|
activate: function() {
|
|
$.fn.editabletypes.text.prototype.activate.call(this);
|
|
}
|
|
});
|
|
|
|
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <textarea></textarea>
|
|
**/
|
|
tpl:'<textarea></textarea>',
|
|
/**
|
|
@property inputclass
|
|
@default input-large
|
|
**/
|
|
inputclass: 'input-large',
|
|
/**
|
|
Placeholder attribute of input. Shown when input is empty.
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
/**
|
|
Number of rows in textarea
|
|
|
|
@property rows
|
|
@type integer
|
|
@default 7
|
|
**/
|
|
rows: 7
|
|
});
|
|
|
|
$.fn.editabletypes.textarea = Textarea;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Select (dropdown)
|
|
|
|
@class select
|
|
@extends list
|
|
@final
|
|
@example
|
|
<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a>
|
|
<script>
|
|
$(function(){
|
|
$('#status').editable({
|
|
value: 2,
|
|
source: [
|
|
{value: 1, text: 'Active'},
|
|
{value: 2, text: 'Blocked'},
|
|
{value: 3, text: 'Deleted'}
|
|
]
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Select = function (options) {
|
|
this.init('select', options, Select.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
|
|
|
|
$.extend(Select.prototype, {
|
|
renderList: function() {
|
|
this.$input.empty();
|
|
|
|
var fillItems = function($el, data) {
|
|
var attr;
|
|
if($.isArray(data)) {
|
|
for(var i=0; i<data.length; i++) {
|
|
attr = {};
|
|
if(data[i].children) {
|
|
attr.label = data[i].text;
|
|
$el.append(fillItems($('<optgroup>', attr), data[i].children));
|
|
} else {
|
|
attr.value = data[i].value;
|
|
if(data[i].disabled) {
|
|
attr.disabled = true;
|
|
}
|
|
$el.append($('<option>', attr).text(data[i].text));
|
|
}
|
|
}
|
|
}
|
|
return $el;
|
|
};
|
|
|
|
fillItems(this.$input, this.sourceData);
|
|
|
|
this.setClass();
|
|
|
|
//enter submit
|
|
this.$input.on('keydown.editable', function (e) {
|
|
if (e.which === 13) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
value2htmlFinal: function(value, element) {
|
|
var text = '',
|
|
items = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
|
|
|
if(items.length) {
|
|
text = items[0].text;
|
|
}
|
|
|
|
//$(element).text(text);
|
|
$.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element);
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.off('keydown.editable').on('change.editable', function(){
|
|
$(this).closest('form').submit();
|
|
});
|
|
}
|
|
});
|
|
|
|
Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <select></select>
|
|
**/
|
|
tpl:'<select></select>'
|
|
});
|
|
|
|
$.fn.editabletypes.select = Select;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
List of checkboxes.
|
|
Internally value stored as javascript array of values.
|
|
|
|
@class checklist
|
|
@extends list
|
|
@final
|
|
@example
|
|
<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a>
|
|
<script>
|
|
$(function(){
|
|
$('#options').editable({
|
|
value: [2, 3],
|
|
source: [
|
|
{value: 1, text: 'option1'},
|
|
{value: 2, text: 'option2'},
|
|
{value: 3, text: 'option3'}
|
|
]
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Checklist = function (options) {
|
|
this.init('checklist', options, Checklist.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
|
|
|
|
$.extend(Checklist.prototype, {
|
|
renderList: function() {
|
|
var $label, $div;
|
|
|
|
this.$tpl.empty();
|
|
|
|
if(!$.isArray(this.sourceData)) {
|
|
return;
|
|
}
|
|
|
|
for(var i=0; i<this.sourceData.length; i++) {
|
|
$label = $('<label>').append($('<input>', {
|
|
type: 'checkbox',
|
|
value: this.sourceData[i].value
|
|
}))
|
|
.append($('<span>').text(' '+this.sourceData[i].text));
|
|
|
|
$('<div>').append($label).appendTo(this.$tpl);
|
|
}
|
|
|
|
this.$input = this.$tpl.find('input[type="checkbox"]');
|
|
this.setClass();
|
|
},
|
|
|
|
value2str: function(value) {
|
|
return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
|
|
},
|
|
|
|
//parse separated string
|
|
str2value: function(str) {
|
|
var reg, value = null;
|
|
if(typeof str === 'string' && str.length) {
|
|
reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
|
|
value = str.split(reg);
|
|
} else if($.isArray(str)) {
|
|
value = str;
|
|
} else {
|
|
value = [str];
|
|
}
|
|
return value;
|
|
},
|
|
|
|
//set checked on required checkboxes
|
|
value2input: function(value) {
|
|
this.$input.prop('checked', false);
|
|
if($.isArray(value) && value.length) {
|
|
this.$input.each(function(i, el) {
|
|
var $el = $(el);
|
|
// cannot use $.inArray as it performs strict comparison
|
|
$.each(value, function(j, val){
|
|
/*jslint eqeq: true*/
|
|
if($el.val() == val) {
|
|
/*jslint eqeq: false*/
|
|
$el.prop('checked', true);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
var checked = [];
|
|
this.$input.filter(':checked').each(function(i, el) {
|
|
checked.push($(el).val());
|
|
});
|
|
return checked;
|
|
},
|
|
|
|
//collect text of checked boxes
|
|
value2htmlFinal: function(value, element) {
|
|
var html = [],
|
|
checked = $.fn.editableutils.itemsByValue(value, this.sourceData),
|
|
escape = this.options.escape;
|
|
|
|
if(checked.length) {
|
|
$.each(checked, function(i, v) {
|
|
var text = escape ? $.fn.editableutils.escape(v.text) : v.text;
|
|
html.push(text);
|
|
});
|
|
$(element).html(html.join('<br>'));
|
|
} else {
|
|
$(element).empty();
|
|
}
|
|
},
|
|
|
|
activate: function() {
|
|
this.$input.first().focus();
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('keydown', function(e){
|
|
if (e.which === 13) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <div></div>
|
|
**/
|
|
tpl:'<div class="editable-checklist"></div>',
|
|
|
|
/**
|
|
@property inputclass
|
|
@type string
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
|
|
/**
|
|
Separator of values when reading from `data-value` attribute
|
|
|
|
@property separator
|
|
@type string
|
|
@default ','
|
|
**/
|
|
separator: ','
|
|
});
|
|
|
|
$.fn.editabletypes.checklist = Checklist;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
HTML5 input types.
|
|
Following types are supported:
|
|
|
|
* password
|
|
* email
|
|
* url
|
|
* tel
|
|
* number
|
|
* range
|
|
* time
|
|
|
|
Learn more about html5 inputs:
|
|
http://www.w3.org/wiki/HTML5_form_additions
|
|
To check browser compatibility please see:
|
|
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
|
|
|
|
@class html5types
|
|
@extends text
|
|
@final
|
|
@since 1.3.0
|
|
@example
|
|
<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a>
|
|
<script>
|
|
$(function(){
|
|
$('#email').editable({
|
|
url: '/post',
|
|
title: 'Enter email'
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
|
|
/**
|
|
@property tpl
|
|
@default depends on type
|
|
**/
|
|
|
|
/*
|
|
Password
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Password = function (options) {
|
|
this.init('password', options, Password.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
|
|
$.extend(Password.prototype, {
|
|
//do not display password, show '[hidden]' instead
|
|
value2html: function(value, element) {
|
|
if(value) {
|
|
$(element).text('[hidden]');
|
|
} else {
|
|
$(element).empty();
|
|
}
|
|
},
|
|
//as password not displayed, should not set value by html
|
|
html2value: function(html) {
|
|
return null;
|
|
}
|
|
});
|
|
Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="password">'
|
|
});
|
|
$.fn.editabletypes.password = Password;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Email
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Email = function (options) {
|
|
this.init('email', options, Email.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
|
|
Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="email">'
|
|
});
|
|
$.fn.editabletypes.email = Email;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Url
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Url = function (options) {
|
|
this.init('url', options, Url.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
|
|
Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="url">'
|
|
});
|
|
$.fn.editabletypes.url = Url;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Tel
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Tel = function (options) {
|
|
this.init('tel', options, Tel.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
|
|
Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="tel">'
|
|
});
|
|
$.fn.editabletypes.tel = Tel;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Number
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var NumberInput = function (options) {
|
|
this.init('number', options, NumberInput.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
|
|
$.extend(NumberInput.prototype, {
|
|
render: function () {
|
|
NumberInput.superclass.render.call(this);
|
|
this.setAttr('min');
|
|
this.setAttr('max');
|
|
this.setAttr('step');
|
|
},
|
|
postrender: function() {
|
|
if(this.$clear) {
|
|
//increase right ffset for up/down arrows
|
|
this.$clear.css({right: 24});
|
|
/*
|
|
//can position clear button only here, when form is shown and height can be calculated
|
|
var h = this.$input.outerHeight(true) || 20,
|
|
delta = (h - this.$clear.height()) / 2;
|
|
|
|
//add 12px to offset right for up/down arrows
|
|
this.$clear.css({top: delta, right: delta + 16});
|
|
*/
|
|
}
|
|
}
|
|
});
|
|
NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
|
tpl: '<input type="number">',
|
|
inputclass: 'input-mini',
|
|
min: null,
|
|
max: null,
|
|
step: null
|
|
});
|
|
$.fn.editabletypes.number = NumberInput;
|
|
}(window.jQuery));
|
|
|
|
|
|
/*
|
|
Range (inherit from number)
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Range = function (options) {
|
|
this.init('range', options, Range.defaults);
|
|
};
|
|
$.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
|
|
$.extend(Range.prototype, {
|
|
render: function () {
|
|
this.$input = this.$tpl.filter('input');
|
|
|
|
this.setClass();
|
|
this.setAttr('min');
|
|
this.setAttr('max');
|
|
this.setAttr('step');
|
|
|
|
this.$input.on('input', function(){
|
|
$(this).siblings('output').text($(this).val());
|
|
});
|
|
},
|
|
activate: function() {
|
|
this.$input.focus();
|
|
}
|
|
});
|
|
Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
|
|
tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
|
|
inputclass: 'input-medium'
|
|
});
|
|
$.fn.editabletypes.range = Range;
|
|
}(window.jQuery));
|
|
|
|
/*
|
|
Time
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Time = function (options) {
|
|
this.init('time', options, Time.defaults);
|
|
};
|
|
//inherit from abstract, as inheritance from text gives selection error.
|
|
$.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);
|
|
$.extend(Time.prototype, {
|
|
render: function() {
|
|
this.setClass();
|
|
}
|
|
});
|
|
Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
tpl: '<input type="time">'
|
|
});
|
|
$.fn.editabletypes.time = Time;
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
|
|
Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
|
|
|
|
You should manually download and include select2 distributive:
|
|
|
|
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
|
|
<script src="select2/select2.js"></script>
|
|
|
|
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
|
|
|
|
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
|
|
|
|
**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
|
|
You need initially put both `data-value` and element's text youself:
|
|
|
|
<a href="#" data-type="select2" data-value="1">Text1</a>
|
|
|
|
|
|
@class select2
|
|
@extends abstractinput
|
|
@since 1.4.1
|
|
@final
|
|
@example
|
|
<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
|
|
<script>
|
|
$(function(){
|
|
//local source
|
|
$('#country').editable({
|
|
source: [
|
|
{id: 'gb', text: 'Great Britain'},
|
|
{id: 'us', text: 'United States'},
|
|
{id: 'ru', text: 'Russia'}
|
|
],
|
|
select2: {
|
|
multiple: true
|
|
}
|
|
});
|
|
//remote source (simple)
|
|
$('#country').editable({
|
|
source: '/getCountries',
|
|
select2: {
|
|
placeholder: 'Select Country',
|
|
minimumInputLength: 1
|
|
}
|
|
});
|
|
//remote source (advanced)
|
|
$('#country').editable({
|
|
select2: {
|
|
placeholder: 'Select Country',
|
|
allowClear: true,
|
|
minimumInputLength: 3,
|
|
id: function (item) {
|
|
return item.CountryId;
|
|
},
|
|
ajax: {
|
|
url: '/getCountries',
|
|
dataType: 'json',
|
|
data: function (term, page) {
|
|
return { query: term };
|
|
},
|
|
results: function (data, page) {
|
|
return { results: data };
|
|
}
|
|
},
|
|
formatResult: function (item) {
|
|
return item.CountryName;
|
|
},
|
|
formatSelection: function (item) {
|
|
return item.CountryName;
|
|
},
|
|
initSelection: function (element, callback) {
|
|
return $.get('/getCountryById', { query: element.val() }, function (data) {
|
|
callback(data);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Constructor = function (options) {
|
|
this.init('select2', options, Constructor.defaults);
|
|
|
|
options.select2 = options.select2 || {};
|
|
|
|
this.sourceData = null;
|
|
|
|
//placeholder
|
|
if(options.placeholder) {
|
|
options.select2.placeholder = options.placeholder;
|
|
}
|
|
|
|
//if not `tags` mode, use source
|
|
if(!options.select2.tags && options.source) {
|
|
var source = options.source;
|
|
//if source is function, call it (once!)
|
|
if ($.isFunction(options.source)) {
|
|
source = options.source.call(options.scope);
|
|
}
|
|
|
|
if (typeof source === 'string') {
|
|
options.select2.ajax = options.select2.ajax || {};
|
|
//some default ajax params
|
|
if(!options.select2.ajax.data) {
|
|
options.select2.ajax.data = function(term) {return { query:term };};
|
|
}
|
|
if(!options.select2.ajax.results) {
|
|
options.select2.ajax.results = function(data) { return {results:data };};
|
|
}
|
|
options.select2.ajax.url = source;
|
|
} else {
|
|
//check format and convert x-editable format to select2 format (if needed)
|
|
this.sourceData = this.convertSource(source);
|
|
options.select2.data = this.sourceData;
|
|
}
|
|
}
|
|
|
|
//overriding objects in config (as by default jQuery extend() is not recursive)
|
|
this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
|
|
|
|
//detect whether it is multi-valued
|
|
this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
|
|
this.isRemote = ('ajax' in this.options.select2);
|
|
|
|
//store function returning ID of item
|
|
//should be here as used inautotext for local source
|
|
this.idFunc = this.options.select2.id;
|
|
if (typeof(this.idFunc) !== "function") {
|
|
var idKey = this.idFunc || 'id';
|
|
this.idFunc = function (e) { return e[idKey]; };
|
|
}
|
|
|
|
//store function that renders text in select2
|
|
this.formatSelection = this.options.select2.formatSelection;
|
|
if (typeof(this.formatSelection) !== "function") {
|
|
this.formatSelection = function (e) { return e.text; };
|
|
}
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Constructor.prototype, {
|
|
render: function() {
|
|
this.setClass();
|
|
|
|
//can not apply select2 here as it calls initSelection
|
|
//over input that does not have correct value yet.
|
|
//apply select2 only in value2input
|
|
//this.$input.select2(this.options.select2);
|
|
|
|
//when data is loaded via ajax, we need to know when it's done to populate listData
|
|
if(this.isRemote) {
|
|
//listen to loaded event to populate data
|
|
this.$input.on('select2-loaded', $.proxy(function(e) {
|
|
this.sourceData = e.items.results;
|
|
}, this));
|
|
}
|
|
|
|
//trigger resize of editableform to re-position container in multi-valued mode
|
|
if(this.isMultiple) {
|
|
this.$input.on('change', function() {
|
|
$(this).closest('form').parent().triggerHandler('resize');
|
|
});
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = '', data,
|
|
that = this;
|
|
|
|
if(this.options.select2.tags) { //in tags mode just assign value
|
|
data = value;
|
|
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
|
|
} else if(this.sourceData) {
|
|
data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
|
|
} else {
|
|
//can not get list of possible values
|
|
//(e.g. autotext for select2 with ajax source)
|
|
}
|
|
|
|
//data may be array (when multiple values allowed)
|
|
if($.isArray(data)) {
|
|
//collect selected data and show with separator
|
|
text = [];
|
|
$.each(data, function(k, v){
|
|
text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
|
|
});
|
|
} else if(data) {
|
|
text = that.formatSelection(data);
|
|
}
|
|
|
|
text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
|
|
|
|
//$(element).text(text);
|
|
Constructor.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
|
|
},
|
|
|
|
value2input: function(value) {
|
|
// if value array => join it anyway
|
|
if($.isArray(value)) {
|
|
value = value.join(this.getSeparator());
|
|
}
|
|
|
|
//for remote source just set value, text is updated by initSelection
|
|
if(!this.$input.data('select2')) {
|
|
this.$input.val(value);
|
|
this.$input.select2(this.options.select2);
|
|
} else {
|
|
//second argument needed to separate initial change from user's click (for autosubmit)
|
|
this.$input.val(value).trigger('change', true);
|
|
|
|
//Uncaught Error: cannot call val() if initSelection() is not defined
|
|
//this.$input.select2('val', value);
|
|
}
|
|
|
|
// if defined remote source AND no multiple mode AND no user's initSelection provided -->
|
|
// we should somehow get text for provided id.
|
|
// The solution is to use element's text as text for that id (exclude empty)
|
|
if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
|
|
// customId and customText are methods to extract `id` and `text` from data object
|
|
// we can use this workaround only if user did not define these methods
|
|
// otherwise we cant construct data object
|
|
var customId = this.options.select2.id,
|
|
customText = this.options.select2.formatSelection;
|
|
|
|
if(!customId && !customText) {
|
|
var $el = $(this.options.scope);
|
|
if (!$el.data('editable').isEmpty) {
|
|
var data = {id: value, text: $el.text()};
|
|
this.$input.select2('data', data);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.select2('val');
|
|
},
|
|
|
|
str2value: function(str, separator) {
|
|
if(typeof str !== 'string' || !this.isMultiple) {
|
|
return str;
|
|
}
|
|
|
|
separator = separator || this.getSeparator();
|
|
|
|
var val, i, l;
|
|
|
|
if (str === null || str.length < 1) {
|
|
return null;
|
|
}
|
|
val = str.split(separator);
|
|
for (i = 0, l = val.length; i < l; i = i + 1) {
|
|
val[i] = $.trim(val[i]);
|
|
}
|
|
|
|
return val;
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('change', function(e, isInitial){
|
|
if(!isInitial) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
getSeparator: function() {
|
|
return this.options.select2.separator || $.fn.select2.defaults.separator;
|
|
},
|
|
|
|
/*
|
|
Converts source from x-editable format: {value: 1, text: "1"} to
|
|
select2 format: {id: 1, text: "1"}
|
|
*/
|
|
convertSource: function(source) {
|
|
if($.isArray(source) && source.length && source[0].value !== undefined) {
|
|
for(var i = 0; i<source.length; i++) {
|
|
if(source[i].value !== undefined) {
|
|
source[i].id = source[i].value;
|
|
delete source[i].value;
|
|
}
|
|
}
|
|
}
|
|
return source;
|
|
},
|
|
|
|
destroy: function() {
|
|
if(this.$input.data('select2')) {
|
|
this.$input.select2('destroy');
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="hidden">
|
|
**/
|
|
tpl:'<input type="hidden">',
|
|
/**
|
|
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
|
|
|
|
@property select2
|
|
@type object
|
|
@default null
|
|
**/
|
|
select2: null,
|
|
/**
|
|
Placeholder attribute of select
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
/**
|
|
Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
|
|
Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
|
|
E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
|
|
|
|
@property source
|
|
@type array|string|function
|
|
@default null
|
|
**/
|
|
source: null,
|
|
/**
|
|
Separator used to display tags.
|
|
|
|
@property viewseparator
|
|
@type string
|
|
@default ', '
|
|
**/
|
|
viewseparator: ', '
|
|
});
|
|
|
|
$.fn.editabletypes.select2 = Constructor;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
* Combodate - 1.0.5
|
|
* Dropdown date and time picker.
|
|
* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
|
|
* Uses momentjs as datetime library http://momentjs.com.
|
|
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
|
|
*
|
|
* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
|
|
* In combodate:
|
|
* 12:00 pm --> 12:00 (24-h format, midday)
|
|
* 12:00 am --> 00:00 (24-h format, midnight, start of day)
|
|
*
|
|
* Differs from momentjs parse rules:
|
|
* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
|
|
* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
|
|
*
|
|
*
|
|
* Author: Vitaliy Potapov
|
|
* Project page: http://github.com/vitalets/combodate
|
|
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
|
|
**/
|
|
(function ($) {
|
|
|
|
var Combodate = function (element, options) {
|
|
this.$element = $(element);
|
|
if(!this.$element.is('input')) {
|
|
$.error('Combodate should be applied to INPUT element');
|
|
return;
|
|
}
|
|
this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
|
|
this.init();
|
|
};
|
|
|
|
Combodate.prototype = {
|
|
constructor: Combodate,
|
|
init: function () {
|
|
this.map = {
|
|
//key regexp moment.method
|
|
day: ['D', 'date'],
|
|
month: ['M', 'month'],
|
|
year: ['Y', 'year'],
|
|
hour: ['[Hh]', 'hours'],
|
|
minute: ['m', 'minutes'],
|
|
second: ['s', 'seconds'],
|
|
ampm: ['[Aa]', '']
|
|
};
|
|
|
|
this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
|
|
|
|
this.initCombos();
|
|
|
|
//update original input on change
|
|
this.$widget.on('change', 'select', $.proxy(function(e) {
|
|
this.$element.val(this.getValue()).change();
|
|
// update days count if month or year changes
|
|
if (this.options.smartDays) {
|
|
if ($(e.target).is('.month') || $(e.target).is('.year')) {
|
|
this.fillCombo('day');
|
|
}
|
|
}
|
|
}, this));
|
|
|
|
this.$widget.find('select').css('width', 'auto');
|
|
|
|
// hide original input and insert widget
|
|
this.$element.hide().after(this.$widget);
|
|
|
|
// set initial value
|
|
this.setValue(this.$element.val() || this.options.value);
|
|
},
|
|
|
|
/*
|
|
Replace tokens in template with <select> elements
|
|
*/
|
|
getTemplate: function() {
|
|
var tpl = this.options.template;
|
|
|
|
//first pass
|
|
$.each(this.map, function(k, v) {
|
|
v = v[0];
|
|
var r = new RegExp(v+'+'),
|
|
token = v.length > 1 ? v.substring(1, 2) : v;
|
|
|
|
tpl = tpl.replace(r, '{'+token+'}');
|
|
});
|
|
|
|
//replace spaces with
|
|
tpl = tpl.replace(/ /g, ' ');
|
|
|
|
//second pass
|
|
$.each(this.map, function(k, v) {
|
|
v = v[0];
|
|
var token = v.length > 1 ? v.substring(1, 2) : v;
|
|
|
|
tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
|
|
});
|
|
|
|
return tpl;
|
|
},
|
|
|
|
/*
|
|
Initialize combos that presents in template
|
|
*/
|
|
initCombos: function() {
|
|
for (var k in this.map) {
|
|
var $c = this.$widget.find('.'+k);
|
|
// set properties like this.$day, this.$month etc.
|
|
this['$'+k] = $c.length ? $c : null;
|
|
// fill with items
|
|
this.fillCombo(k);
|
|
}
|
|
},
|
|
|
|
/*
|
|
Fill combo with items
|
|
*/
|
|
fillCombo: function(k) {
|
|
var $combo = this['$'+k];
|
|
if (!$combo) {
|
|
return;
|
|
}
|
|
|
|
// define method name to fill items, e.g `fillDays`
|
|
var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1);
|
|
var items = this[f]();
|
|
var value = $combo.val();
|
|
|
|
$combo.empty();
|
|
for(var i=0; i<items.length; i++) {
|
|
$combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
|
|
}
|
|
|
|
$combo.val(value);
|
|
},
|
|
|
|
/*
|
|
Initialize items of combos. Handles `firstItem` option
|
|
*/
|
|
fillCommon: function(key) {
|
|
var values = [],
|
|
relTime;
|
|
|
|
if(this.options.firstItem === 'name') {
|
|
//need both to support moment ver < 2 and >= 2
|
|
relTime = moment.relativeTime || moment.langData()._relativeTime;
|
|
var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
|
|
//take last entry (see momentjs lang files structure)
|
|
header = header.split(' ').reverse()[0];
|
|
values.push(['', header]);
|
|
} else if(this.options.firstItem === 'empty') {
|
|
values.push(['', '']);
|
|
}
|
|
return values;
|
|
},
|
|
|
|
|
|
/*
|
|
fill day
|
|
*/
|
|
fillDay: function() {
|
|
var items = this.fillCommon('d'), name, i,
|
|
twoDigit = this.options.template.indexOf('DD') !== -1,
|
|
daysCount = 31;
|
|
|
|
// detect days count (depends on month and year)
|
|
// originally https://github.com/vitalets/combodate/pull/7
|
|
if (this.options.smartDays && this.$month && this.$year) {
|
|
var month = parseInt(this.$month.val(), 10);
|
|
var year = parseInt(this.$year.val(), 10);
|
|
|
|
if (!isNaN(month) && !isNaN(year)) {
|
|
daysCount = moment([year, month]).daysInMonth();
|
|
}
|
|
}
|
|
|
|
for (i = 1; i <= daysCount; i++) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill month
|
|
*/
|
|
fillMonth: function() {
|
|
var items = this.fillCommon('M'), name, i,
|
|
longNames = this.options.template.indexOf('MMMM') !== -1,
|
|
shortNames = this.options.template.indexOf('MMM') !== -1,
|
|
twoDigit = this.options.template.indexOf('MM') !== -1;
|
|
|
|
for(i=0; i<=11; i++) {
|
|
if(longNames) {
|
|
//see https://github.com/timrwood/momentjs.com/pull/36
|
|
name = moment().date(1).month(i).format('MMMM');
|
|
} else if(shortNames) {
|
|
name = moment().date(1).month(i).format('MMM');
|
|
} else if(twoDigit) {
|
|
name = this.leadZero(i+1);
|
|
} else {
|
|
name = i+1;
|
|
}
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill year
|
|
*/
|
|
fillYear: function() {
|
|
var items = [], name, i,
|
|
longNames = this.options.template.indexOf('YYYY') !== -1;
|
|
|
|
for(i=this.options.maxYear; i>=this.options.minYear; i--) {
|
|
name = longNames ? i : (i+'').substring(2);
|
|
items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
|
|
}
|
|
|
|
items = this.fillCommon('y').concat(items);
|
|
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill hour
|
|
*/
|
|
fillHour: function() {
|
|
var items = this.fillCommon('h'), name, i,
|
|
h12 = this.options.template.indexOf('h') !== -1,
|
|
h24 = this.options.template.indexOf('H') !== -1,
|
|
twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
|
|
min = h12 ? 1 : 0,
|
|
max = h12 ? 12 : 23;
|
|
|
|
for(i=min; i<=max; i++) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill minute
|
|
*/
|
|
fillMinute: function() {
|
|
var items = this.fillCommon('m'), name, i,
|
|
twoDigit = this.options.template.indexOf('mm') !== -1;
|
|
|
|
for(i=0; i<=59; i+= this.options.minuteStep) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill second
|
|
*/
|
|
fillSecond: function() {
|
|
var items = this.fillCommon('s'), name, i,
|
|
twoDigit = this.options.template.indexOf('ss') !== -1;
|
|
|
|
for(i=0; i<=59; i+= this.options.secondStep) {
|
|
name = twoDigit ? this.leadZero(i) : i;
|
|
items.push([i, name]);
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
fill ampm
|
|
*/
|
|
fillAmpm: function() {
|
|
var ampmL = this.options.template.indexOf('a') !== -1,
|
|
ampmU = this.options.template.indexOf('A') !== -1,
|
|
items = [
|
|
['am', ampmL ? 'am' : 'AM'],
|
|
['pm', ampmL ? 'pm' : 'PM']
|
|
];
|
|
return items;
|
|
},
|
|
|
|
/*
|
|
Returns current date value from combos.
|
|
If format not specified - `options.format` used.
|
|
If format = `null` - Moment object returned.
|
|
*/
|
|
getValue: function(format) {
|
|
var dt, values = {},
|
|
that = this,
|
|
notSelected = false;
|
|
|
|
//getting selected values
|
|
$.each(this.map, function(k, v) {
|
|
if(k === 'ampm') {
|
|
return;
|
|
}
|
|
var def = k === 'day' ? 1 : 0;
|
|
|
|
values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;
|
|
|
|
if(isNaN(values[k])) {
|
|
notSelected = true;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
//if at least one visible combo not selected - return empty string
|
|
if(notSelected) {
|
|
return '';
|
|
}
|
|
|
|
//convert hours 12h --> 24h
|
|
if(this.$ampm) {
|
|
//12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
|
|
if(values.hour === 12) {
|
|
values.hour = this.$ampm.val() === 'am' ? 0 : 12;
|
|
} else {
|
|
values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
|
|
}
|
|
}
|
|
|
|
dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
|
|
|
|
//highlight invalid date
|
|
this.highlight(dt);
|
|
|
|
format = format === undefined ? this.options.format : format;
|
|
if(format === null) {
|
|
return dt.isValid() ? dt : null;
|
|
} else {
|
|
return dt.isValid() ? dt.format(format) : '';
|
|
}
|
|
},
|
|
|
|
setValue: function(value) {
|
|
if(!value) {
|
|
return;
|
|
}
|
|
|
|
var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
|
|
that = this,
|
|
values = {};
|
|
|
|
//function to find nearest value in select options
|
|
function getNearest($select, value) {
|
|
var delta = {};
|
|
$select.children('option').each(function(i, opt){
|
|
var optValue = $(opt).attr('value'),
|
|
distance;
|
|
|
|
if(optValue === '') return;
|
|
distance = Math.abs(optValue - value);
|
|
if(typeof delta.distance === 'undefined' || distance < delta.distance) {
|
|
delta = {value: optValue, distance: distance};
|
|
}
|
|
});
|
|
return delta.value;
|
|
}
|
|
|
|
if(dt.isValid()) {
|
|
//read values from date object
|
|
$.each(this.map, function(k, v) {
|
|
if(k === 'ampm') {
|
|
return;
|
|
}
|
|
values[k] = dt[v[1]]();
|
|
});
|
|
|
|
if(this.$ampm) {
|
|
//12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
|
|
if(values.hour >= 12) {
|
|
values.ampm = 'pm';
|
|
if(values.hour > 12) {
|
|
values.hour -= 12;
|
|
}
|
|
} else {
|
|
values.ampm = 'am';
|
|
if(values.hour === 0) {
|
|
values.hour = 12;
|
|
}
|
|
}
|
|
}
|
|
|
|
$.each(values, function(k, v) {
|
|
//call val() for each existing combo, e.g. this.$hour.val()
|
|
if(that['$'+k]) {
|
|
|
|
if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
|
|
v = getNearest(that['$'+k], v);
|
|
}
|
|
|
|
if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
|
|
v = getNearest(that['$'+k], v);
|
|
}
|
|
|
|
that['$'+k].val(v);
|
|
}
|
|
});
|
|
|
|
// update days count
|
|
if (this.options.smartDays) {
|
|
this.fillCombo('day');
|
|
}
|
|
|
|
this.$element.val(dt.format(this.options.format)).change();
|
|
}
|
|
},
|
|
|
|
/*
|
|
highlight combos if date is invalid
|
|
*/
|
|
highlight: function(dt) {
|
|
if(!dt.isValid()) {
|
|
if(this.options.errorClass) {
|
|
this.$widget.addClass(this.options.errorClass);
|
|
} else {
|
|
//store original border color
|
|
if(!this.borderColor) {
|
|
this.borderColor = this.$widget.find('select').css('border-color');
|
|
}
|
|
this.$widget.find('select').css('border-color', 'red');
|
|
}
|
|
} else {
|
|
if(this.options.errorClass) {
|
|
this.$widget.removeClass(this.options.errorClass);
|
|
} else {
|
|
this.$widget.find('select').css('border-color', this.borderColor);
|
|
}
|
|
}
|
|
},
|
|
|
|
leadZero: function(v) {
|
|
return v <= 9 ? '0' + v : v;
|
|
},
|
|
|
|
destroy: function() {
|
|
this.$widget.remove();
|
|
this.$element.removeData('combodate').show();
|
|
}
|
|
|
|
//todo: clear method
|
|
};
|
|
|
|
$.fn.combodate = function ( option ) {
|
|
var d, args = Array.apply(null, arguments);
|
|
args.shift();
|
|
|
|
//getValue returns date as string / object (not jQuery object)
|
|
if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
|
|
return d.getValue.apply(d, args);
|
|
}
|
|
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data('combodate'),
|
|
options = typeof option == 'object' && option;
|
|
if (!data) {
|
|
$this.data('combodate', (data = new Combodate(this, options)));
|
|
}
|
|
if (typeof option == 'string' && typeof data[option] == 'function') {
|
|
data[option].apply(data, args);
|
|
}
|
|
});
|
|
};
|
|
|
|
$.fn.combodate.defaults = {
|
|
//in this format value stored in original input
|
|
format: 'DD-MM-YYYY HH:mm',
|
|
//in this format items in dropdowns are displayed
|
|
template: 'D / MMM / YYYY H : mm',
|
|
//initial value, can be `new Date()`
|
|
value: null,
|
|
minYear: 1970,
|
|
maxYear: 2015,
|
|
yearDescending: true,
|
|
minuteStep: 5,
|
|
secondStep: 1,
|
|
firstItem: 'empty', //'name', 'empty', 'none'
|
|
errorClass: null,
|
|
roundTime: true, // whether to round minutes and seconds if step > 1
|
|
smartDays: false // whether days in combo depend on selected month: 31, 30, 28
|
|
};
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Combodate input - dropdown date and time picker.
|
|
Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
|
|
|
|
<script src="js/moment.min.js"></script>
|
|
|
|
Allows to input:
|
|
|
|
* only date
|
|
* only time
|
|
* both date and time
|
|
|
|
Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.
|
|
Internally value stored as `momentjs` object.
|
|
|
|
@class combodate
|
|
@extends abstractinput
|
|
@final
|
|
@since 1.4.0
|
|
@example
|
|
<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a>
|
|
<script>
|
|
$(function(){
|
|
$('#dob').editable({
|
|
format: 'YYYY-MM-DD',
|
|
viewformat: 'DD.MM.YYYY',
|
|
template: 'D / MMMM / YYYY',
|
|
combodate: {
|
|
minYear: 2000,
|
|
maxYear: 2015,
|
|
minuteStep: 1
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
|
|
/*global moment*/
|
|
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Constructor = function (options) {
|
|
this.init('combodate', options, Constructor.defaults);
|
|
|
|
//by default viewformat equals to format
|
|
if(!this.options.viewformat) {
|
|
this.options.viewformat = this.options.format;
|
|
}
|
|
|
|
//try parse combodate config defined as json string in data-combodate
|
|
options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
|
|
|
|
//overriding combodate config (as by default jQuery extend() is not recursive)
|
|
this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
|
|
format: this.options.format,
|
|
template: this.options.template
|
|
});
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Constructor.prototype, {
|
|
render: function () {
|
|
this.$input.combodate(this.options.combodate);
|
|
|
|
if($.fn.editableform.engine === 'bs3') {
|
|
this.$input.siblings().find('select').addClass('form-control');
|
|
}
|
|
|
|
if(this.options.inputclass) {
|
|
this.$input.siblings().find('select').addClass(this.options.inputclass);
|
|
}
|
|
//"clear" link
|
|
/*
|
|
if(this.options.clear) {
|
|
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.clear();
|
|
}, this));
|
|
|
|
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
|
}
|
|
*/
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = value ? value.format(this.options.viewformat) : '';
|
|
//$(element).text(text);
|
|
Constructor.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return html ? moment(html, this.options.viewformat) : null;
|
|
},
|
|
|
|
value2str: function(value) {
|
|
return value ? value.format(this.options.format) : '';
|
|
},
|
|
|
|
str2value: function(str) {
|
|
return str ? moment(str, this.options.format) : null;
|
|
},
|
|
|
|
value2submit: function(value) {
|
|
return this.value2str(value);
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.combodate('setValue', value);
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.combodate('getValue', null);
|
|
},
|
|
|
|
activate: function() {
|
|
this.$input.siblings('.combodate').find('select').eq(0).focus();
|
|
},
|
|
|
|
/*
|
|
clear: function() {
|
|
this.$input.data('datepicker').date = null;
|
|
this.$input.find('.active').removeClass('active');
|
|
},
|
|
*/
|
|
|
|
autosubmit: function() {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="text">
|
|
**/
|
|
tpl:'<input type="text">',
|
|
/**
|
|
@property inputclass
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
/**
|
|
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
|
See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)
|
|
|
|
@property format
|
|
@type string
|
|
@default YYYY-MM-DD
|
|
**/
|
|
format:'YYYY-MM-DD',
|
|
/**
|
|
Format used for displaying date. Also applied when converting date from element's text on init.
|
|
If not specified equals to `format`.
|
|
|
|
@property viewformat
|
|
@type string
|
|
@default null
|
|
**/
|
|
viewformat: null,
|
|
/**
|
|
Template used for displaying dropdowns.
|
|
|
|
@property template
|
|
@type string
|
|
@default D / MMM / YYYY
|
|
**/
|
|
template: 'D / MMM / YYYY',
|
|
/**
|
|
Configuration of combodate.
|
|
Full list of options: http://vitalets.github.com/combodate/#docs
|
|
|
|
@property combodate
|
|
@type object
|
|
@default null
|
|
**/
|
|
combodate: null
|
|
|
|
/*
|
|
(not implemented yet)
|
|
Text shown as clear date button.
|
|
If <code>false</code> clear button will not be rendered.
|
|
|
|
@property clear
|
|
@type boolean|string
|
|
@default 'x clear'
|
|
*/
|
|
//clear: '× clear'
|
|
});
|
|
|
|
$.fn.editabletypes.combodate = Constructor;
|
|
|
|
}(window.jQuery));
|
|
|
|
/*
|
|
Editableform based on Twitter Bootstrap 3
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//store parent methods
|
|
var pInitInput = $.fn.editableform.Constructor.prototype.initInput;
|
|
|
|
$.extend($.fn.editableform.Constructor.prototype, {
|
|
initTemplate: function() {
|
|
this.$form = $($.fn.editableform.template);
|
|
this.$form.find('.control-group').addClass('form-group');
|
|
this.$form.find('.editable-error-block').addClass('help-block');
|
|
},
|
|
initInput: function() {
|
|
pInitInput.apply(this);
|
|
|
|
//for bs3 set default class `input-sm` to standard inputs
|
|
var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false;
|
|
var defaultClass = 'input-sm';
|
|
|
|
//bs3 add `form-control` class to standard inputs
|
|
var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs'.split(',');
|
|
if(~$.inArray(this.input.type, stdtypes)) {
|
|
this.input.$input.addClass('form-control');
|
|
if(emptyInputClass) {
|
|
this.input.options.inputclass = defaultClass;
|
|
this.input.$input.addClass(defaultClass);
|
|
}
|
|
}
|
|
|
|
//apply bs3 size class also to buttons (to fit size of control)
|
|
var $btn = this.$form.find('.editable-buttons');
|
|
var classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' ');
|
|
for(var i=0; i<classes.length; i++) {
|
|
// `btn-sm` is default now
|
|
/*
|
|
if(classes[i].toLowerCase() === 'input-sm') {
|
|
$btn.find('button').addClass('btn-sm');
|
|
}
|
|
*/
|
|
if(classes[i].toLowerCase() === 'input-lg') {
|
|
$btn.find('button').removeClass('btn-sm').addClass('btn-lg');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
//buttons
|
|
$.fn.editableform.buttons =
|
|
'<button type="submit" class="btn btn-primary btn-sm editable-submit">'+
|
|
'<i class="glyphicon glyphicon-ok"></i>'+
|
|
'</button>'+
|
|
'<button type="button" class="btn btn-default btn-sm editable-cancel">'+
|
|
'<i class="glyphicon glyphicon-remove"></i>'+
|
|
'</button>';
|
|
|
|
//error classes
|
|
$.fn.editableform.errorGroupClass = 'has-error';
|
|
$.fn.editableform.errorBlockClass = null;
|
|
//engine
|
|
$.fn.editableform.engine = 'bs3';
|
|
}(window.jQuery));
|
|
/**
|
|
* Editable Popover3 (for Bootstrap 3)
|
|
* ---------------------
|
|
* requires bootstrap-popover.js
|
|
*/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//extend methods
|
|
$.extend($.fn.editableContainer.Popup.prototype, {
|
|
containerName: 'popover',
|
|
containerDataName: 'bs.popover',
|
|
innerCss: '.popover-content',
|
|
defaults: $.fn.popover.Constructor.DEFAULTS,
|
|
|
|
initContainer: function(){
|
|
$.extend(this.containerOptions, {
|
|
trigger: 'manual',
|
|
selector: false,
|
|
content: ' ',
|
|
template: this.defaults.template
|
|
});
|
|
|
|
//as template property is used in inputs, hide it from popover
|
|
var t;
|
|
if(this.$element.data('template')) {
|
|
t = this.$element.data('template');
|
|
this.$element.removeData('template');
|
|
}
|
|
|
|
this.call(this.containerOptions);
|
|
|
|
if(t) {
|
|
//restore data('template')
|
|
this.$element.data('template', t);
|
|
}
|
|
},
|
|
|
|
/* show */
|
|
innerShow: function () {
|
|
this.call('show');
|
|
},
|
|
|
|
/* hide */
|
|
innerHide: function () {
|
|
this.call('hide');
|
|
},
|
|
|
|
/* destroy */
|
|
innerDestroy: function() {
|
|
this.call('destroy');
|
|
},
|
|
|
|
setContainerOption: function(key, value) {
|
|
this.container().options[key] = value;
|
|
},
|
|
|
|
/**
|
|
* move popover to new position. This function mainly copied from bootstrap-popover.
|
|
*/
|
|
/*jshint laxcomma: true, eqeqeq: false*/
|
|
setPosition: function () {
|
|
|
|
(function() {
|
|
/*
|
|
var $tip = this.tip()
|
|
, inside
|
|
, pos
|
|
, actualWidth
|
|
, actualHeight
|
|
, placement
|
|
, tp
|
|
, tpt
|
|
, tpb
|
|
, tpl
|
|
, tpr;
|
|
|
|
placement = typeof this.options.placement === 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement;
|
|
|
|
inside = /in/.test(placement);
|
|
|
|
$tip
|
|
// .detach()
|
|
//vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
|
|
.removeClass('top right bottom left')
|
|
.css({ top: 0, left: 0, display: 'block' });
|
|
// .insertAfter(this.$element);
|
|
|
|
pos = this.getPosition(inside);
|
|
|
|
actualWidth = $tip[0].offsetWidth;
|
|
actualHeight = $tip[0].offsetHeight;
|
|
|
|
placement = inside ? placement.split(' ')[1] : placement;
|
|
|
|
tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
|
|
tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
|
|
tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
|
|
tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
|
|
|
|
switch (placement) {
|
|
case 'bottom':
|
|
if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) {
|
|
if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'top';
|
|
} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
|
|
placement = 'right';
|
|
} else if (tpl.left > $(window).scrollLeft()) {
|
|
placement = 'left';
|
|
} else {
|
|
placement = 'right';
|
|
}
|
|
}
|
|
break;
|
|
case 'top':
|
|
if (tpt.top < $(window).scrollTop()) {
|
|
if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) {
|
|
placement = 'bottom';
|
|
} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
|
|
placement = 'right';
|
|
} else if (tpl.left > $(window).scrollLeft()) {
|
|
placement = 'left';
|
|
} else {
|
|
placement = 'right';
|
|
}
|
|
}
|
|
break;
|
|
case 'left':
|
|
if (tpl.left < $(window).scrollLeft()) {
|
|
if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
|
|
placement = 'right';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'top';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'bottom';
|
|
} else {
|
|
placement = 'right';
|
|
}
|
|
}
|
|
break;
|
|
case 'right':
|
|
if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) {
|
|
if (tpl.left > $(window).scrollLeft()) {
|
|
placement = 'left';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'top';
|
|
} else if (tpt.top > $(window).scrollTop()) {
|
|
placement = 'bottom';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (placement) {
|
|
case 'bottom':
|
|
tp = tpb;
|
|
break;
|
|
case 'top':
|
|
tp = tpt;
|
|
break;
|
|
case 'left':
|
|
tp = tpl;
|
|
break;
|
|
case 'right':
|
|
tp = tpr;
|
|
break;
|
|
}
|
|
|
|
$tip
|
|
.offset(tp)
|
|
.addClass(placement)
|
|
.addClass('in');
|
|
*/
|
|
|
|
|
|
var $tip = this.tip();
|
|
|
|
var placement = typeof this.options.placement == 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement;
|
|
|
|
var autoToken = /\s?auto?\s?/i;
|
|
var autoPlace = autoToken.test(placement);
|
|
if (autoPlace) {
|
|
placement = placement.replace(autoToken, '') || 'top';
|
|
}
|
|
|
|
|
|
var pos = this.getPosition();
|
|
var actualWidth = $tip[0].offsetWidth;
|
|
var actualHeight = $tip[0].offsetHeight;
|
|
|
|
if (autoPlace) {
|
|
var $parent = this.$element.parent();
|
|
|
|
var orgPlacement = placement;
|
|
var docScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
|
var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth();
|
|
var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight();
|
|
var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left;
|
|
|
|
placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' :
|
|
placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' :
|
|
placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' :
|
|
placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' :
|
|
placement;
|
|
|
|
$tip
|
|
.removeClass(orgPlacement)
|
|
.addClass(placement);
|
|
}
|
|
|
|
|
|
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);
|
|
|
|
this.applyPlacement(calculatedOffset, placement);
|
|
|
|
|
|
}).call(this.container());
|
|
/*jshint laxcomma: false, eqeqeq: true*/
|
|
}
|
|
});
|
|
|
|
}(window.jQuery));
|
|
|
|
/* =========================================================
|
|
* bootstrap-datepicker.js
|
|
* http://www.eyecon.ro/bootstrap-datepicker
|
|
* =========================================================
|
|
* Copyright 2012 Stefan Petre
|
|
* Improvements by Andrew Rowls
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
* ========================================================= */
|
|
|
|
(function( $ ) {
|
|
|
|
function UTCDate(){
|
|
return new Date(Date.UTC.apply(Date, arguments));
|
|
}
|
|
function UTCToday(){
|
|
var today = new Date();
|
|
return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
|
|
}
|
|
|
|
// Picker object
|
|
|
|
var Datepicker = function(element, options) {
|
|
var that = this;
|
|
|
|
this._process_options(options);
|
|
|
|
this.element = $(element);
|
|
this.isInline = false;
|
|
this.isInput = this.element.is('input');
|
|
this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
|
|
this.hasInput = this.component && this.element.find('input').length;
|
|
if(this.component && this.component.length === 0)
|
|
this.component = false;
|
|
|
|
this.picker = $(DPGlobal.template);
|
|
this._buildEvents();
|
|
this._attachEvents();
|
|
|
|
if(this.isInline) {
|
|
this.picker.addClass('datepicker-inline').appendTo(this.element);
|
|
} else {
|
|
this.picker.addClass('datepicker-dropdown dropdown-menu');
|
|
}
|
|
|
|
if (this.o.rtl){
|
|
this.picker.addClass('datepicker-rtl');
|
|
this.picker.find('.prev i, .next i')
|
|
.toggleClass('icon-arrow-left icon-arrow-right');
|
|
}
|
|
|
|
|
|
this.viewMode = this.o.startView;
|
|
|
|
if (this.o.calendarWeeks)
|
|
this.picker.find('tfoot th.today')
|
|
.attr('colspan', function(i, val){
|
|
return parseInt(val) + 1;
|
|
});
|
|
|
|
this._allow_update = false;
|
|
|
|
this.setStartDate(this.o.startDate);
|
|
this.setEndDate(this.o.endDate);
|
|
this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
|
|
|
|
this.fillDow();
|
|
this.fillMonths();
|
|
|
|
this._allow_update = true;
|
|
|
|
this.update();
|
|
this.showMode();
|
|
|
|
if(this.isInline) {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
Datepicker.prototype = {
|
|
constructor: Datepicker,
|
|
|
|
_process_options: function(opts){
|
|
// Store raw options for reference
|
|
this._o = $.extend({}, this._o, opts);
|
|
// Processed options
|
|
var o = this.o = $.extend({}, this._o);
|
|
|
|
// Check if "de-DE" style date is available, if not language should
|
|
// fallback to 2 letter code eg "de"
|
|
var lang = o.language;
|
|
if (!dates[lang]) {
|
|
lang = lang.split('-')[0];
|
|
if (!dates[lang])
|
|
lang = defaults.language;
|
|
}
|
|
o.language = lang;
|
|
|
|
switch(o.startView){
|
|
case 2:
|
|
case 'decade':
|
|
o.startView = 2;
|
|
break;
|
|
case 1:
|
|
case 'year':
|
|
o.startView = 1;
|
|
break;
|
|
default:
|
|
o.startView = 0;
|
|
}
|
|
|
|
switch (o.minViewMode) {
|
|
case 1:
|
|
case 'months':
|
|
o.minViewMode = 1;
|
|
break;
|
|
case 2:
|
|
case 'years':
|
|
o.minViewMode = 2;
|
|
break;
|
|
default:
|
|
o.minViewMode = 0;
|
|
}
|
|
|
|
o.startView = Math.max(o.startView, o.minViewMode);
|
|
|
|
o.weekStart %= 7;
|
|
o.weekEnd = ((o.weekStart + 6) % 7);
|
|
|
|
var format = DPGlobal.parseFormat(o.format)
|
|
if (o.startDate !== -Infinity) {
|
|
o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
|
|
}
|
|
if (o.endDate !== Infinity) {
|
|
o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
|
|
}
|
|
|
|
o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
|
|
if (!$.isArray(o.daysOfWeekDisabled))
|
|
o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
|
|
o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
|
|
return parseInt(d, 10);
|
|
});
|
|
},
|
|
_events: [],
|
|
_secondaryEvents: [],
|
|
_applyEvents: function(evs){
|
|
for (var i=0, el, ev; i<evs.length; i++){
|
|
el = evs[i][0];
|
|
ev = evs[i][1];
|
|
el.on(ev);
|
|
}
|
|
},
|
|
_unapplyEvents: function(evs){
|
|
for (var i=0, el, ev; i<evs.length; i++){
|
|
el = evs[i][0];
|
|
ev = evs[i][1];
|
|
el.off(ev);
|
|
}
|
|
},
|
|
_buildEvents: function(){
|
|
if (this.isInput) { // single input
|
|
this._events = [
|
|
[this.element, {
|
|
focus: $.proxy(this.show, this),
|
|
keyup: $.proxy(this.update, this),
|
|
keydown: $.proxy(this.keydown, this)
|
|
}]
|
|
];
|
|
}
|
|
else if (this.component && this.hasInput){ // component: input + button
|
|
this._events = [
|
|
// For components that are not readonly, allow keyboard nav
|
|
[this.element.find('input'), {
|
|
focus: $.proxy(this.show, this),
|
|
keyup: $.proxy(this.update, this),
|
|
keydown: $.proxy(this.keydown, this)
|
|
}],
|
|
[this.component, {
|
|
click: $.proxy(this.show, this)
|
|
}]
|
|
];
|
|
}
|
|
else if (this.element.is('div')) { // inline datepicker
|
|
this.isInline = true;
|
|
}
|
|
else {
|
|
this._events = [
|
|
[this.element, {
|
|
click: $.proxy(this.show, this)
|
|
}]
|
|
];
|
|
}
|
|
|
|
this._secondaryEvents = [
|
|
[this.picker, {
|
|
click: $.proxy(this.click, this)
|
|
}],
|
|
[$(window), {
|
|
resize: $.proxy(this.place, this)
|
|
}],
|
|
[$(document), {
|
|
mousedown: $.proxy(function (e) {
|
|
// Clicked outside the datepicker, hide it
|
|
if (!(
|
|
this.element.is(e.target) ||
|
|
this.element.find(e.target).size() ||
|
|
this.picker.is(e.target) ||
|
|
this.picker.find(e.target).size()
|
|
)) {
|
|
this.hide();
|
|
}
|
|
}, this)
|
|
}]
|
|
];
|
|
},
|
|
_attachEvents: function(){
|
|
this._detachEvents();
|
|
this._applyEvents(this._events);
|
|
},
|
|
_detachEvents: function(){
|
|
this._unapplyEvents(this._events);
|
|
},
|
|
_attachSecondaryEvents: function(){
|
|
this._detachSecondaryEvents();
|
|
this._applyEvents(this._secondaryEvents);
|
|
},
|
|
_detachSecondaryEvents: function(){
|
|
this._unapplyEvents(this._secondaryEvents);
|
|
},
|
|
_trigger: function(event, altdate){
|
|
var date = altdate || this.date,
|
|
local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000));
|
|
|
|
this.element.trigger({
|
|
type: event,
|
|
date: local_date,
|
|
format: $.proxy(function(altformat){
|
|
var format = altformat || this.o.format;
|
|
return DPGlobal.formatDate(date, format, this.o.language);
|
|
}, this)
|
|
});
|
|
},
|
|
|
|
show: function(e) {
|
|
if (!this.isInline)
|
|
this.picker.appendTo('body');
|
|
this.picker.show();
|
|
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
|
|
this.place();
|
|
this._attachSecondaryEvents();
|
|
if (e) {
|
|
e.preventDefault();
|
|
}
|
|
this._trigger('show');
|
|
},
|
|
|
|
hide: function(e){
|
|
if(this.isInline) return;
|
|
if (!this.picker.is(':visible')) return;
|
|
this.picker.hide().detach();
|
|
this._detachSecondaryEvents();
|
|
this.viewMode = this.o.startView;
|
|
this.showMode();
|
|
|
|
if (
|
|
this.o.forceParse &&
|
|
(
|
|
this.isInput && this.element.val() ||
|
|
this.hasInput && this.element.find('input').val()
|
|
)
|
|
)
|
|
this.setValue();
|
|
this._trigger('hide');
|
|
},
|
|
|
|
remove: function() {
|
|
this.hide();
|
|
this._detachEvents();
|
|
this._detachSecondaryEvents();
|
|
this.picker.remove();
|
|
delete this.element.data().datepicker;
|
|
if (!this.isInput) {
|
|
delete this.element.data().date;
|
|
}
|
|
},
|
|
|
|
getDate: function() {
|
|
var d = this.getUTCDate();
|
|
return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
|
|
},
|
|
|
|
getUTCDate: function() {
|
|
return this.date;
|
|
},
|
|
|
|
setDate: function(d) {
|
|
this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
|
|
},
|
|
|
|
setUTCDate: function(d) {
|
|
this.date = d;
|
|
this.setValue();
|
|
},
|
|
|
|
setValue: function() {
|
|
var formatted = this.getFormattedDate();
|
|
if (!this.isInput) {
|
|
if (this.component){
|
|
this.element.find('input').val(formatted);
|
|
}
|
|
} else {
|
|
this.element.val(formatted);
|
|
}
|
|
},
|
|
|
|
getFormattedDate: function(format) {
|
|
if (format === undefined)
|
|
format = this.o.format;
|
|
return DPGlobal.formatDate(this.date, format, this.o.language);
|
|
},
|
|
|
|
setStartDate: function(startDate){
|
|
this._process_options({startDate: startDate});
|
|
this.update();
|
|
this.updateNavArrows();
|
|
},
|
|
|
|
setEndDate: function(endDate){
|
|
this._process_options({endDate: endDate});
|
|
this.update();
|
|
this.updateNavArrows();
|
|
},
|
|
|
|
setDaysOfWeekDisabled: function(daysOfWeekDisabled){
|
|
this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
|
|
this.update();
|
|
this.updateNavArrows();
|
|
},
|
|
|
|
place: function(){
|
|
if(this.isInline) return;
|
|
var zIndex = parseInt(this.element.parents().filter(function() {
|
|
return $(this).css('z-index') != 'auto';
|
|
}).first().css('z-index'))+10;
|
|
var offset = this.component ? this.component.parent().offset() : this.element.offset();
|
|
var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
|
|
this.picker.css({
|
|
top: offset.top + height,
|
|
left: offset.left,
|
|
zIndex: zIndex
|
|
});
|
|
},
|
|
|
|
_allow_update: true,
|
|
update: function(){
|
|
if (!this._allow_update) return;
|
|
|
|
var date, fromArgs = false;
|
|
if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
|
|
date = arguments[0];
|
|
fromArgs = true;
|
|
} else {
|
|
date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
|
|
delete this.element.data().date;
|
|
}
|
|
|
|
this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
|
|
|
|
if(fromArgs) this.setValue();
|
|
|
|
if (this.date < this.o.startDate) {
|
|
this.viewDate = new Date(this.o.startDate);
|
|
} else if (this.date > this.o.endDate) {
|
|
this.viewDate = new Date(this.o.endDate);
|
|
} else {
|
|
this.viewDate = new Date(this.date);
|
|
}
|
|
this.fill();
|
|
},
|
|
|
|
fillDow: function(){
|
|
var dowCnt = this.o.weekStart,
|
|
html = '<tr>';
|
|
if(this.o.calendarWeeks){
|
|
var cell = '<th class="cw"> </th>';
|
|
html += cell;
|
|
this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
|
|
}
|
|
while (dowCnt < this.o.weekStart + 7) {
|
|
html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
|
|
}
|
|
html += '</tr>';
|
|
this.picker.find('.datepicker-days thead').append(html);
|
|
},
|
|
|
|
fillMonths: function(){
|
|
var html = '',
|
|
i = 0;
|
|
while (i < 12) {
|
|
html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
|
|
}
|
|
this.picker.find('.datepicker-months td').html(html);
|
|
},
|
|
|
|
setRange: function(range){
|
|
if (!range || !range.length)
|
|
delete this.range;
|
|
else
|
|
this.range = $.map(range, function(d){ return d.valueOf(); });
|
|
this.fill();
|
|
},
|
|
|
|
getClassNames: function(date){
|
|
var cls = [],
|
|
year = this.viewDate.getUTCFullYear(),
|
|
month = this.viewDate.getUTCMonth(),
|
|
currentDate = this.date.valueOf(),
|
|
today = new Date();
|
|
if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
|
|
cls.push('old');
|
|
} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
|
|
cls.push('new');
|
|
}
|
|
// Compare internal UTC date with local today, not UTC today
|
|
if (this.o.todayHighlight &&
|
|
date.getUTCFullYear() == today.getFullYear() &&
|
|
date.getUTCMonth() == today.getMonth() &&
|
|
date.getUTCDate() == today.getDate()) {
|
|
cls.push('today');
|
|
}
|
|
if (currentDate && date.valueOf() == currentDate) {
|
|
cls.push('active');
|
|
}
|
|
if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
|
|
$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
|
|
cls.push('disabled');
|
|
}
|
|
if (this.range){
|
|
if (date > this.range[0] && date < this.range[this.range.length-1]){
|
|
cls.push('range');
|
|
}
|
|
if ($.inArray(date.valueOf(), this.range) != -1){
|
|
cls.push('selected');
|
|
}
|
|
}
|
|
return cls;
|
|
},
|
|
|
|
fill: function() {
|
|
var d = new Date(this.viewDate),
|
|
year = d.getUTCFullYear(),
|
|
month = d.getUTCMonth(),
|
|
startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
|
|
startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
|
|
endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
|
|
endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
|
|
currentDate = this.date && this.date.valueOf(),
|
|
tooltip;
|
|
this.picker.find('.datepicker-days thead th.datepicker-switch')
|
|
.text(dates[this.o.language].months[month]+' '+year);
|
|
this.picker.find('tfoot th.today')
|
|
.text(dates[this.o.language].today)
|
|
.toggle(this.o.todayBtn !== false);
|
|
this.picker.find('tfoot th.clear')
|
|
.text(dates[this.o.language].clear)
|
|
.toggle(this.o.clearBtn !== false);
|
|
this.updateNavArrows();
|
|
this.fillMonths();
|
|
var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
|
|
day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
|
|
prevMonth.setUTCDate(day);
|
|
prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
|
|
var nextMonth = new Date(prevMonth);
|
|
nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
|
|
nextMonth = nextMonth.valueOf();
|
|
var html = [];
|
|
var clsName;
|
|
while(prevMonth.valueOf() < nextMonth) {
|
|
if (prevMonth.getUTCDay() == this.o.weekStart) {
|
|
html.push('<tr>');
|
|
if(this.o.calendarWeeks){
|
|
// ISO 8601: First week contains first thursday.
|
|
// ISO also states week starts on Monday, but we can be more abstract here.
|
|
var
|
|
// Start of current week: based on weekstart/current date
|
|
ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
|
|
// Thursday of this week
|
|
th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
|
|
// First Thursday of year, year from thursday
|
|
yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
|
|
// Calendar week: ms between thursdays, div ms per day, div 7 days
|
|
calWeek = (th - yth) / 864e5 / 7 + 1;
|
|
html.push('<td class="cw">'+ calWeek +'</td>');
|
|
|
|
}
|
|
}
|
|
clsName = this.getClassNames(prevMonth);
|
|
clsName.push('day');
|
|
|
|
var before = this.o.beforeShowDay(prevMonth);
|
|
if (before === undefined)
|
|
before = {};
|
|
else if (typeof(before) === 'boolean')
|
|
before = {enabled: before};
|
|
else if (typeof(before) === 'string')
|
|
before = {classes: before};
|
|
if (before.enabled === false)
|
|
clsName.push('disabled');
|
|
if (before.classes)
|
|
clsName = clsName.concat(before.classes.split(/\s+/));
|
|
if (before.tooltip)
|
|
tooltip = before.tooltip;
|
|
|
|
clsName = $.unique(clsName);
|
|
html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
|
|
if (prevMonth.getUTCDay() == this.o.weekEnd) {
|
|
html.push('</tr>');
|
|
}
|
|
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
|
|
}
|
|
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
|
|
var currentYear = this.date && this.date.getUTCFullYear();
|
|
|
|
var months = this.picker.find('.datepicker-months')
|
|
.find('th:eq(1)')
|
|
.text(year)
|
|
.end()
|
|
.find('span').removeClass('active');
|
|
if (currentYear && currentYear == year) {
|
|
months.eq(this.date.getUTCMonth()).addClass('active');
|
|
}
|
|
if (year < startYear || year > endYear) {
|
|
months.addClass('disabled');
|
|
}
|
|
if (year == startYear) {
|
|
months.slice(0, startMonth).addClass('disabled');
|
|
}
|
|
if (year == endYear) {
|
|
months.slice(endMonth+1).addClass('disabled');
|
|
}
|
|
|
|
html = '';
|
|
year = parseInt(year/10, 10) * 10;
|
|
var yearCont = this.picker.find('.datepicker-years')
|
|
.find('th:eq(1)')
|
|
.text(year + '-' + (year + 9))
|
|
.end()
|
|
.find('td');
|
|
year -= 1;
|
|
for (var i = -1; i < 11; i++) {
|
|
html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
|
|
year += 1;
|
|
}
|
|
yearCont.html(html);
|
|
},
|
|
|
|
updateNavArrows: function() {
|
|
if (!this._allow_update) return;
|
|
|
|
var d = new Date(this.viewDate),
|
|
year = d.getUTCFullYear(),
|
|
month = d.getUTCMonth();
|
|
switch (this.viewMode) {
|
|
case 0:
|
|
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
|
|
this.picker.find('.prev').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.prev').css({visibility: 'visible'});
|
|
}
|
|
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
|
|
this.picker.find('.next').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.next').css({visibility: 'visible'});
|
|
}
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
|
|
this.picker.find('.prev').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.prev').css({visibility: 'visible'});
|
|
}
|
|
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
|
|
this.picker.find('.next').css({visibility: 'hidden'});
|
|
} else {
|
|
this.picker.find('.next').css({visibility: 'visible'});
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
click: function(e) {
|
|
e.preventDefault();
|
|
var target = $(e.target).closest('span, td, th');
|
|
if (target.length == 1) {
|
|
switch(target[0].nodeName.toLowerCase()) {
|
|
case 'th':
|
|
switch(target[0].className) {
|
|
case 'datepicker-switch':
|
|
this.showMode(1);
|
|
break;
|
|
case 'prev':
|
|
case 'next':
|
|
var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
|
|
switch(this.viewMode){
|
|
case 0:
|
|
this.viewDate = this.moveMonth(this.viewDate, dir);
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
this.viewDate = this.moveYear(this.viewDate, dir);
|
|
break;
|
|
}
|
|
this.fill();
|
|
break;
|
|
case 'today':
|
|
var date = new Date();
|
|
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
|
|
|
this.showMode(-2);
|
|
var which = this.o.todayBtn == 'linked' ? null : 'view';
|
|
this._setDate(date, which);
|
|
break;
|
|
case 'clear':
|
|
var element;
|
|
if (this.isInput)
|
|
element = this.element;
|
|
else if (this.component)
|
|
element = this.element.find('input');
|
|
if (element)
|
|
element.val("").change();
|
|
this._trigger('changeDate');
|
|
this.update();
|
|
if (this.o.autoclose)
|
|
this.hide();
|
|
break;
|
|
}
|
|
break;
|
|
case 'span':
|
|
if (!target.is('.disabled')) {
|
|
this.viewDate.setUTCDate(1);
|
|
if (target.is('.month')) {
|
|
var day = 1;
|
|
var month = target.parent().find('span').index(target);
|
|
var year = this.viewDate.getUTCFullYear();
|
|
this.viewDate.setUTCMonth(month);
|
|
this._trigger('changeMonth', this.viewDate);
|
|
if (this.o.minViewMode === 1) {
|
|
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
|
}
|
|
} else {
|
|
var year = parseInt(target.text(), 10)||0;
|
|
var day = 1;
|
|
var month = 0;
|
|
this.viewDate.setUTCFullYear(year);
|
|
this._trigger('changeYear', this.viewDate);
|
|
if (this.o.minViewMode === 2) {
|
|
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
|
}
|
|
}
|
|
this.showMode(-1);
|
|
this.fill();
|
|
}
|
|
break;
|
|
case 'td':
|
|
if (target.is('.day') && !target.is('.disabled')){
|
|
var day = parseInt(target.text(), 10)||1;
|
|
var year = this.viewDate.getUTCFullYear(),
|
|
month = this.viewDate.getUTCMonth();
|
|
if (target.is('.old')) {
|
|
if (month === 0) {
|
|
month = 11;
|
|
year -= 1;
|
|
} else {
|
|
month -= 1;
|
|
}
|
|
} else if (target.is('.new')) {
|
|
if (month == 11) {
|
|
month = 0;
|
|
year += 1;
|
|
} else {
|
|
month += 1;
|
|
}
|
|
}
|
|
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
_setDate: function(date, which){
|
|
if (!which || which == 'date')
|
|
this.date = new Date(date);
|
|
if (!which || which == 'view')
|
|
this.viewDate = new Date(date);
|
|
this.fill();
|
|
this.setValue();
|
|
this._trigger('changeDate');
|
|
var element;
|
|
if (this.isInput) {
|
|
element = this.element;
|
|
} else if (this.component){
|
|
element = this.element.find('input');
|
|
}
|
|
if (element) {
|
|
element.change();
|
|
if (this.o.autoclose && (!which || which == 'date')) {
|
|
this.hide();
|
|
}
|
|
}
|
|
},
|
|
|
|
moveMonth: function(date, dir){
|
|
if (!dir) return date;
|
|
var new_date = new Date(date.valueOf()),
|
|
day = new_date.getUTCDate(),
|
|
month = new_date.getUTCMonth(),
|
|
mag = Math.abs(dir),
|
|
new_month, test;
|
|
dir = dir > 0 ? 1 : -1;
|
|
if (mag == 1){
|
|
test = dir == -1
|
|
// If going back one month, make sure month is not current month
|
|
// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
|
|
? function(){ return new_date.getUTCMonth() == month; }
|
|
// If going forward one month, make sure month is as expected
|
|
// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
|
|
: function(){ return new_date.getUTCMonth() != new_month; };
|
|
new_month = month + dir;
|
|
new_date.setUTCMonth(new_month);
|
|
// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
|
|
if (new_month < 0 || new_month > 11)
|
|
new_month = (new_month + 12) % 12;
|
|
} else {
|
|
// For magnitudes >1, move one month at a time...
|
|
for (var i=0; i<mag; i++)
|
|
// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
|
|
new_date = this.moveMonth(new_date, dir);
|
|
// ...then reset the day, keeping it in the new month
|
|
new_month = new_date.getUTCMonth();
|
|
new_date.setUTCDate(day);
|
|
test = function(){ return new_month != new_date.getUTCMonth(); };
|
|
}
|
|
// Common date-resetting loop -- if date is beyond end of month, make it
|
|
// end of month
|
|
while (test()){
|
|
new_date.setUTCDate(--day);
|
|
new_date.setUTCMonth(new_month);
|
|
}
|
|
return new_date;
|
|
},
|
|
|
|
moveYear: function(date, dir){
|
|
return this.moveMonth(date, dir*12);
|
|
},
|
|
|
|
dateWithinRange: function(date){
|
|
return date >= this.o.startDate && date <= this.o.endDate;
|
|
},
|
|
|
|
keydown: function(e){
|
|
if (this.picker.is(':not(:visible)')){
|
|
if (e.keyCode == 27) // allow escape to hide and re-show picker
|
|
this.show();
|
|
return;
|
|
}
|
|
var dateChanged = false,
|
|
dir, day, month,
|
|
newDate, newViewDate;
|
|
switch(e.keyCode){
|
|
case 27: // escape
|
|
this.hide();
|
|
e.preventDefault();
|
|
break;
|
|
case 37: // left
|
|
case 39: // right
|
|
if (!this.o.keyboardNavigation) break;
|
|
dir = e.keyCode == 37 ? -1 : 1;
|
|
if (e.ctrlKey){
|
|
newDate = this.moveYear(this.date, dir);
|
|
newViewDate = this.moveYear(this.viewDate, dir);
|
|
} else if (e.shiftKey){
|
|
newDate = this.moveMonth(this.date, dir);
|
|
newViewDate = this.moveMonth(this.viewDate, dir);
|
|
} else {
|
|
newDate = new Date(this.date);
|
|
newDate.setUTCDate(this.date.getUTCDate() + dir);
|
|
newViewDate = new Date(this.viewDate);
|
|
newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
|
|
}
|
|
if (this.dateWithinRange(newDate)){
|
|
this.date = newDate;
|
|
this.viewDate = newViewDate;
|
|
this.setValue();
|
|
this.update();
|
|
e.preventDefault();
|
|
dateChanged = true;
|
|
}
|
|
break;
|
|
case 38: // up
|
|
case 40: // down
|
|
if (!this.o.keyboardNavigation) break;
|
|
dir = e.keyCode == 38 ? -1 : 1;
|
|
if (e.ctrlKey){
|
|
newDate = this.moveYear(this.date, dir);
|
|
newViewDate = this.moveYear(this.viewDate, dir);
|
|
} else if (e.shiftKey){
|
|
newDate = this.moveMonth(this.date, dir);
|
|
newViewDate = this.moveMonth(this.viewDate, dir);
|
|
} else {
|
|
newDate = new Date(this.date);
|
|
newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
|
|
newViewDate = new Date(this.viewDate);
|
|
newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
|
|
}
|
|
if (this.dateWithinRange(newDate)){
|
|
this.date = newDate;
|
|
this.viewDate = newViewDate;
|
|
this.setValue();
|
|
this.update();
|
|
e.preventDefault();
|
|
dateChanged = true;
|
|
}
|
|
break;
|
|
case 13: // enter
|
|
this.hide();
|
|
e.preventDefault();
|
|
break;
|
|
case 9: // tab
|
|
this.hide();
|
|
break;
|
|
}
|
|
if (dateChanged){
|
|
this._trigger('changeDate');
|
|
var element;
|
|
if (this.isInput) {
|
|
element = this.element;
|
|
} else if (this.component){
|
|
element = this.element.find('input');
|
|
}
|
|
if (element) {
|
|
element.change();
|
|
}
|
|
}
|
|
},
|
|
|
|
showMode: function(dir) {
|
|
if (dir) {
|
|
this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
|
|
}
|
|
/*
|
|
vitalets: fixing bug of very special conditions:
|
|
jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
|
|
Method show() does not set display css correctly and datepicker is not shown.
|
|
Changed to .css('display', 'block') solve the problem.
|
|
See https://github.com/vitalets/x-editable/issues/37
|
|
|
|
In jquery 1.7.2+ everything works fine.
|
|
*/
|
|
//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
|
|
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
|
|
this.updateNavArrows();
|
|
}
|
|
};
|
|
|
|
var DateRangePicker = function(element, options){
|
|
this.element = $(element);
|
|
this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
|
|
delete options.inputs;
|
|
|
|
$(this.inputs)
|
|
.datepicker(options)
|
|
.bind('changeDate', $.proxy(this.dateUpdated, this));
|
|
|
|
this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
|
|
this.updateDates();
|
|
};
|
|
DateRangePicker.prototype = {
|
|
updateDates: function(){
|
|
this.dates = $.map(this.pickers, function(i){ return i.date; });
|
|
this.updateRanges();
|
|
},
|
|
updateRanges: function(){
|
|
var range = $.map(this.dates, function(d){ return d.valueOf(); });
|
|
$.each(this.pickers, function(i, p){
|
|
p.setRange(range);
|
|
});
|
|
},
|
|
dateUpdated: function(e){
|
|
var dp = $(e.target).data('datepicker'),
|
|
new_date = dp.getUTCDate(),
|
|
i = $.inArray(e.target, this.inputs),
|
|
l = this.inputs.length;
|
|
if (i == -1) return;
|
|
|
|
if (new_date < this.dates[i]){
|
|
// Date being moved earlier/left
|
|
while (i>=0 && new_date < this.dates[i]){
|
|
this.pickers[i--].setUTCDate(new_date);
|
|
}
|
|
}
|
|
else if (new_date > this.dates[i]){
|
|
// Date being moved later/right
|
|
while (i<l && new_date > this.dates[i]){
|
|
this.pickers[i++].setUTCDate(new_date);
|
|
}
|
|
}
|
|
this.updateDates();
|
|
},
|
|
remove: function(){
|
|
$.map(this.pickers, function(p){ p.remove(); });
|
|
delete this.element.data().datepicker;
|
|
}
|
|
};
|
|
|
|
function opts_from_el(el, prefix){
|
|
// Derive options from element data-attrs
|
|
var data = $(el).data(),
|
|
out = {}, inkey,
|
|
replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
|
|
prefix = new RegExp('^' + prefix.toLowerCase());
|
|
for (var key in data)
|
|
if (prefix.test(key)){
|
|
inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
|
|
out[inkey] = data[key];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function opts_from_locale(lang){
|
|
// Derive options from locale plugins
|
|
var out = {};
|
|
// Check if "de-DE" style date is available, if not language should
|
|
// fallback to 2 letter code eg "de"
|
|
if (!dates[lang]) {
|
|
lang = lang.split('-')[0]
|
|
if (!dates[lang])
|
|
return;
|
|
}
|
|
var d = dates[lang];
|
|
$.each(locale_opts, function(i,k){
|
|
if (k in d)
|
|
out[k] = d[k];
|
|
});
|
|
return out;
|
|
}
|
|
|
|
var old = $.fn.datepicker;
|
|
var datepicker = $.fn.datepicker = function ( option ) {
|
|
var args = Array.apply(null, arguments);
|
|
args.shift();
|
|
var internal_return,
|
|
this_return;
|
|
this.each(function () {
|
|
var $this = $(this),
|
|
data = $this.data('datepicker'),
|
|
options = typeof option == 'object' && option;
|
|
if (!data) {
|
|
var elopts = opts_from_el(this, 'date'),
|
|
// Preliminary otions
|
|
xopts = $.extend({}, defaults, elopts, options),
|
|
locopts = opts_from_locale(xopts.language),
|
|
// Options priority: js args, data-attrs, locales, defaults
|
|
opts = $.extend({}, defaults, locopts, elopts, options);
|
|
if ($this.is('.input-daterange') || opts.inputs){
|
|
var ropts = {
|
|
inputs: opts.inputs || $this.find('input').toArray()
|
|
};
|
|
$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
|
|
}
|
|
else{
|
|
$this.data('datepicker', (data = new Datepicker(this, opts)));
|
|
}
|
|
}
|
|
if (typeof option == 'string' && typeof data[option] == 'function') {
|
|
internal_return = data[option].apply(data, args);
|
|
if (internal_return !== undefined)
|
|
return false;
|
|
}
|
|
});
|
|
if (internal_return !== undefined)
|
|
return internal_return;
|
|
else
|
|
return this;
|
|
};
|
|
|
|
var defaults = $.fn.datepicker.defaults = {
|
|
autoclose: false,
|
|
beforeShowDay: $.noop,
|
|
calendarWeeks: false,
|
|
clearBtn: false,
|
|
daysOfWeekDisabled: [],
|
|
endDate: Infinity,
|
|
forceParse: true,
|
|
format: 'mm/dd/yyyy',
|
|
keyboardNavigation: true,
|
|
language: 'en',
|
|
minViewMode: 0,
|
|
rtl: false,
|
|
startDate: -Infinity,
|
|
startView: 0,
|
|
todayBtn: false,
|
|
todayHighlight: false,
|
|
weekStart: 0
|
|
};
|
|
var locale_opts = $.fn.datepicker.locale_opts = [
|
|
'format',
|
|
'rtl',
|
|
'weekStart'
|
|
];
|
|
$.fn.datepicker.Constructor = Datepicker;
|
|
var dates = $.fn.datepicker.dates = {
|
|
en: {
|
|
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
|
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
|
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
|
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
|
today: "Today",
|
|
clear: "Clear"
|
|
}
|
|
};
|
|
|
|
var DPGlobal = {
|
|
modes: [
|
|
{
|
|
clsName: 'days',
|
|
navFnc: 'Month',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'months',
|
|
navFnc: 'FullYear',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'years',
|
|
navFnc: 'FullYear',
|
|
navStep: 10
|
|
}],
|
|
isLeapYear: function (year) {
|
|
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
|
|
},
|
|
getDaysInMonth: function (year, month) {
|
|
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
},
|
|
validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
|
|
nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
|
|
parseFormat: function(format){
|
|
// IE treats \0 as a string end in inputs (truncating the value),
|
|
// so it's a bad format delimiter, anyway
|
|
var separators = format.replace(this.validParts, '\0').split('\0'),
|
|
parts = format.match(this.validParts);
|
|
if (!separators || !separators.length || !parts || parts.length === 0){
|
|
throw new Error("Invalid date format.");
|
|
}
|
|
return {separators: separators, parts: parts};
|
|
},
|
|
parseDate: function(date, format, language) {
|
|
if (date instanceof Date) return date;
|
|
if (typeof format === 'string')
|
|
format = DPGlobal.parseFormat(format);
|
|
if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
|
|
var part_re = /([\-+]\d+)([dmwy])/,
|
|
parts = date.match(/([\-+]\d+)([dmwy])/g),
|
|
part, dir;
|
|
date = new Date();
|
|
for (var i=0; i<parts.length; i++) {
|
|
part = part_re.exec(parts[i]);
|
|
dir = parseInt(part[1]);
|
|
switch(part[2]){
|
|
case 'd':
|
|
date.setUTCDate(date.getUTCDate() + dir);
|
|
break;
|
|
case 'm':
|
|
date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
|
|
break;
|
|
case 'w':
|
|
date.setUTCDate(date.getUTCDate() + dir * 7);
|
|
break;
|
|
case 'y':
|
|
date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
|
|
break;
|
|
}
|
|
}
|
|
return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
|
|
}
|
|
var parts = date && date.match(this.nonpunctuation) || [],
|
|
date = new Date(),
|
|
parsed = {},
|
|
setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
|
|
setters_map = {
|
|
yyyy: function(d,v){ return d.setUTCFullYear(v); },
|
|
yy: function(d,v){ return d.setUTCFullYear(2000+v); },
|
|
m: function(d,v){
|
|
v -= 1;
|
|
while (v<0) v += 12;
|
|
v %= 12;
|
|
d.setUTCMonth(v);
|
|
while (d.getUTCMonth() != v)
|
|
d.setUTCDate(d.getUTCDate()-1);
|
|
return d;
|
|
},
|
|
d: function(d,v){ return d.setUTCDate(v); }
|
|
},
|
|
val, filtered, part;
|
|
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
|
|
setters_map['dd'] = setters_map['d'];
|
|
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
|
var fparts = format.parts.slice();
|
|
// Remove noop parts
|
|
if (parts.length != fparts.length) {
|
|
fparts = $(fparts).filter(function(i,p){
|
|
return $.inArray(p, setters_order) !== -1;
|
|
}).toArray();
|
|
}
|
|
// Process remainder
|
|
if (parts.length == fparts.length) {
|
|
for (var i=0, cnt = fparts.length; i < cnt; i++) {
|
|
val = parseInt(parts[i], 10);
|
|
part = fparts[i];
|
|
if (isNaN(val)) {
|
|
switch(part) {
|
|
case 'MM':
|
|
filtered = $(dates[language].months).filter(function(){
|
|
var m = this.slice(0, parts[i].length),
|
|
p = parts[i].slice(0, m.length);
|
|
return m == p;
|
|
});
|
|
val = $.inArray(filtered[0], dates[language].months) + 1;
|
|
break;
|
|
case 'M':
|
|
filtered = $(dates[language].monthsShort).filter(function(){
|
|
var m = this.slice(0, parts[i].length),
|
|
p = parts[i].slice(0, m.length);
|
|
return m == p;
|
|
});
|
|
val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
|
|
break;
|
|
}
|
|
}
|
|
parsed[part] = val;
|
|
}
|
|
for (var i=0, s; i<setters_order.length; i++){
|
|
s = setters_order[i];
|
|
if (s in parsed && !isNaN(parsed[s]))
|
|
setters_map[s](date, parsed[s]);
|
|
}
|
|
}
|
|
return date;
|
|
},
|
|
formatDate: function(date, format, language){
|
|
if (typeof format === 'string')
|
|
format = DPGlobal.parseFormat(format);
|
|
var val = {
|
|
d: date.getUTCDate(),
|
|
D: dates[language].daysShort[date.getUTCDay()],
|
|
DD: dates[language].days[date.getUTCDay()],
|
|
m: date.getUTCMonth() + 1,
|
|
M: dates[language].monthsShort[date.getUTCMonth()],
|
|
MM: dates[language].months[date.getUTCMonth()],
|
|
yy: date.getUTCFullYear().toString().substring(2),
|
|
yyyy: date.getUTCFullYear()
|
|
};
|
|
val.dd = (val.d < 10 ? '0' : '') + val.d;
|
|
val.mm = (val.m < 10 ? '0' : '') + val.m;
|
|
var date = [],
|
|
seps = $.extend([], format.separators);
|
|
for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
|
|
if (seps.length)
|
|
date.push(seps.shift());
|
|
date.push(val[format.parts[i]]);
|
|
}
|
|
return date.join('');
|
|
},
|
|
headTemplate: '<thead>'+
|
|
'<tr>'+
|
|
'<th class="prev"><i class="icon-arrow-left"/></th>'+
|
|
'<th colspan="5" class="datepicker-switch"></th>'+
|
|
'<th class="next"><i class="icon-arrow-right"/></th>'+
|
|
'</tr>'+
|
|
'</thead>',
|
|
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
|
|
footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
|
|
};
|
|
DPGlobal.template = '<div class="datepicker">'+
|
|
'<div class="datepicker-days">'+
|
|
'<table class=" table-condensed">'+
|
|
DPGlobal.headTemplate+
|
|
'<tbody></tbody>'+
|
|
DPGlobal.footTemplate+
|
|
'</table>'+
|
|
'</div>'+
|
|
'<div class="datepicker-months">'+
|
|
'<table class="table-condensed">'+
|
|
DPGlobal.headTemplate+
|
|
DPGlobal.contTemplate+
|
|
DPGlobal.footTemplate+
|
|
'</table>'+
|
|
'</div>'+
|
|
'<div class="datepicker-years">'+
|
|
'<table class="table-condensed">'+
|
|
DPGlobal.headTemplate+
|
|
DPGlobal.contTemplate+
|
|
DPGlobal.footTemplate+
|
|
'</table>'+
|
|
'</div>'+
|
|
'</div>';
|
|
|
|
$.fn.datepicker.DPGlobal = DPGlobal;
|
|
|
|
|
|
/* DATEPICKER NO CONFLICT
|
|
* =================== */
|
|
|
|
$.fn.datepicker.noConflict = function(){
|
|
$.fn.datepicker = old;
|
|
return this;
|
|
};
|
|
|
|
|
|
/* DATEPICKER DATA-API
|
|
* ================== */
|
|
|
|
$(document).on(
|
|
'focus.datepicker.data-api click.datepicker.data-api',
|
|
'[data-provide="datepicker"]',
|
|
function(e){
|
|
var $this = $(this);
|
|
if ($this.data('datepicker')) return;
|
|
e.preventDefault();
|
|
// component click requires us to explicitly show it
|
|
datepicker.call($this, 'show');
|
|
}
|
|
);
|
|
$(function(){
|
|
//$('[data-provide="datepicker-inline"]').datepicker();
|
|
//vit: changed to support noConflict()
|
|
datepicker.call($('[data-provide="datepicker-inline"]'));
|
|
});
|
|
|
|
}( window.jQuery ));
|
|
|
|
/**
|
|
Bootstrap-datepicker.
|
|
Description and examples: https://github.com/eternicode/bootstrap-datepicker.
|
|
For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
|
|
and set `language` option.
|
|
Since 1.4.0 date has different appearance in **popup** and **inline** modes.
|
|
|
|
@class date
|
|
@extends abstractinput
|
|
@final
|
|
@example
|
|
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a>
|
|
<script>
|
|
$(function(){
|
|
$('#dob').editable({
|
|
format: 'yyyy-mm-dd',
|
|
viewformat: 'dd/mm/yyyy',
|
|
datepicker: {
|
|
weekStart: 1
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
//store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
|
|
$.fn.bdatepicker = $.fn.datepicker.noConflict();
|
|
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
|
|
$.fn.datepicker = $.fn.bdatepicker;
|
|
}
|
|
|
|
var Date = function (options) {
|
|
this.init('date', options, Date.defaults);
|
|
this.initPicker(options, Date.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Date.prototype, {
|
|
initPicker: function(options, defaults) {
|
|
//'format' is set directly from settings or data-* attributes
|
|
|
|
//by default viewformat equals to format
|
|
if(!this.options.viewformat) {
|
|
this.options.viewformat = this.options.format;
|
|
}
|
|
|
|
//try parse datepicker config defined as json string in data-datepicker
|
|
options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true);
|
|
|
|
//overriding datepicker config (as by default jQuery extend() is not recursive)
|
|
//since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
|
|
this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
|
|
format: this.options.viewformat
|
|
});
|
|
|
|
//language
|
|
this.options.datepicker.language = this.options.datepicker.language || 'en';
|
|
|
|
//store DPglobal
|
|
this.dpg = $.fn.bdatepicker.DPGlobal;
|
|
|
|
//store parsed formats
|
|
this.parsedFormat = this.dpg.parseFormat(this.options.format);
|
|
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
|
|
},
|
|
|
|
render: function () {
|
|
this.$input.bdatepicker(this.options.datepicker);
|
|
|
|
//"clear" link
|
|
if(this.options.clear) {
|
|
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.clear();
|
|
}, this));
|
|
|
|
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
|
|
Date.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return this.parseDate(html, this.parsedViewFormat);
|
|
},
|
|
|
|
value2str: function(value) {
|
|
return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
|
|
},
|
|
|
|
str2value: function(str) {
|
|
return this.parseDate(str, this.parsedFormat);
|
|
},
|
|
|
|
value2submit: function(value) {
|
|
return this.value2str(value);
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.bdatepicker('update', value);
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.data('datepicker').date;
|
|
},
|
|
|
|
activate: function() {
|
|
},
|
|
|
|
clear: function() {
|
|
this.$input.data('datepicker').date = null;
|
|
this.$input.find('.active').removeClass('active');
|
|
if(!this.options.showbuttons) {
|
|
this.$input.closest('form').submit();
|
|
}
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('mouseup', '.day', function(e){
|
|
if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
|
|
return;
|
|
}
|
|
var $form = $(this).closest('form');
|
|
setTimeout(function() {
|
|
$form.submit();
|
|
}, 200);
|
|
});
|
|
//changedate is not suitable as it triggered when showing datepicker. see #149
|
|
/*
|
|
this.$input.on('changeDate', function(e){
|
|
var $form = $(this).closest('form');
|
|
setTimeout(function() {
|
|
$form.submit();
|
|
}, 200);
|
|
});
|
|
*/
|
|
},
|
|
|
|
/*
|
|
For incorrect date bootstrap-datepicker returns current date that is not suitable
|
|
for datefield.
|
|
This function returns null for incorrect date.
|
|
*/
|
|
parseDate: function(str, format) {
|
|
var date = null, formattedBack;
|
|
if(str) {
|
|
date = this.dpg.parseDate(str, format, this.options.datepicker.language);
|
|
if(typeof str === 'string') {
|
|
formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language);
|
|
if(str !== formattedBack) {
|
|
date = null;
|
|
}
|
|
}
|
|
}
|
|
return date;
|
|
}
|
|
|
|
});
|
|
|
|
Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <div></div>
|
|
**/
|
|
tpl:'<div class="editable-date well"></div>',
|
|
/**
|
|
@property inputclass
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
/**
|
|
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
|
Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
|
|
|
|
@property format
|
|
@type string
|
|
@default yyyy-mm-dd
|
|
**/
|
|
format:'yyyy-mm-dd',
|
|
/**
|
|
Format used for displaying date. Also applied when converting date from element's text on init.
|
|
If not specified equals to <code>format</code>
|
|
|
|
@property viewformat
|
|
@type string
|
|
@default null
|
|
**/
|
|
viewformat: null,
|
|
/**
|
|
Configuration of datepicker.
|
|
Full list of options: http://bootstrap-datepicker.readthedocs.org/en/latest/options.html
|
|
|
|
@property datepicker
|
|
@type object
|
|
@default {
|
|
weekStart: 0,
|
|
startView: 0,
|
|
minViewMode: 0,
|
|
autoclose: false
|
|
}
|
|
**/
|
|
datepicker:{
|
|
weekStart: 0,
|
|
startView: 0,
|
|
minViewMode: 0,
|
|
autoclose: false
|
|
},
|
|
/**
|
|
Text shown as clear date button.
|
|
If <code>false</code> clear button will not be rendered.
|
|
|
|
@property clear
|
|
@type boolean|string
|
|
@default 'x clear'
|
|
**/
|
|
clear: '× clear'
|
|
});
|
|
|
|
$.fn.editabletypes.date = Date;
|
|
|
|
}(window.jQuery));
|
|
|
|
/**
|
|
Bootstrap datefield input - modification for inline mode.
|
|
Shows normal <input type="text"> and binds popup datepicker.
|
|
Automatically shown in inline mode.
|
|
|
|
@class datefield
|
|
@extends date
|
|
|
|
@since 1.4.0
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var DateField = function (options) {
|
|
this.init('datefield', options, DateField.defaults);
|
|
this.initPicker(options, DateField.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
|
|
|
|
$.extend(DateField.prototype, {
|
|
render: function () {
|
|
this.$input = this.$tpl.find('input');
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
|
|
//bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
|
|
this.$tpl.bdatepicker(this.options.datepicker);
|
|
|
|
//need to disable original event handlers
|
|
this.$input.off('focus keydown');
|
|
|
|
//update value of datepicker
|
|
this.$input.keyup($.proxy(function(){
|
|
this.$tpl.removeData('date');
|
|
this.$tpl.bdatepicker('update');
|
|
}, this));
|
|
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
|
|
this.$tpl.bdatepicker('update');
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.html2value(this.$input.val());
|
|
},
|
|
|
|
activate: function() {
|
|
$.fn.editabletypes.text.prototype.activate.call(this);
|
|
},
|
|
|
|
autosubmit: function() {
|
|
//reset autosubmit to empty
|
|
}
|
|
});
|
|
|
|
DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
|
|
/**
|
|
@property tpl
|
|
**/
|
|
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
|
|
/**
|
|
@property inputclass
|
|
@default 'input-small'
|
|
**/
|
|
inputclass: 'input-small',
|
|
|
|
/* datepicker config */
|
|
datepicker: {
|
|
weekStart: 0,
|
|
startView: 0,
|
|
minViewMode: 0,
|
|
autoclose: true
|
|
}
|
|
});
|
|
|
|
$.fn.editabletypes.datefield = DateField;
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Bootstrap-datetimepicker.
|
|
Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker).
|
|
Before usage you should manually include dependent js and css:
|
|
|
|
<link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link>
|
|
<script src="js/bootstrap-datetimepicker.js"></script>
|
|
|
|
For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales
|
|
and set `language` option.
|
|
|
|
@class datetime
|
|
@extends abstractinput
|
|
@final
|
|
@since 1.4.4
|
|
@example
|
|
<a href="#" id="last_seen" data-type="datetime" data-pk="1" data-url="/post" title="Select date & time">15/03/2013 12:45</a>
|
|
<script>
|
|
$(function(){
|
|
$('#last_seen').editable({
|
|
format: 'yyyy-mm-dd hh:ii',
|
|
viewformat: 'dd/mm/yyyy hh:ii',
|
|
datetimepicker: {
|
|
weekStart: 1
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var DateTime = function (options) {
|
|
this.init('datetime', options, DateTime.defaults);
|
|
this.initPicker(options, DateTime.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(DateTime.prototype, {
|
|
initPicker: function(options, defaults) {
|
|
//'format' is set directly from settings or data-* attributes
|
|
|
|
//by default viewformat equals to format
|
|
if(!this.options.viewformat) {
|
|
this.options.viewformat = this.options.format;
|
|
}
|
|
|
|
//try parse datetimepicker config defined as json string in data-datetimepicker
|
|
options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true);
|
|
|
|
//overriding datetimepicker config (as by default jQuery extend() is not recursive)
|
|
//since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only
|
|
this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, {
|
|
format: this.options.viewformat
|
|
});
|
|
|
|
//language
|
|
this.options.datetimepicker.language = this.options.datetimepicker.language || 'en';
|
|
|
|
//store DPglobal
|
|
this.dpg = $.fn.datetimepicker.DPGlobal;
|
|
|
|
//store parsed formats
|
|
this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType);
|
|
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType);
|
|
},
|
|
|
|
render: function () {
|
|
this.$input.datetimepicker(this.options.datetimepicker);
|
|
|
|
//adjust container position when viewMode changes
|
|
//see https://github.com/smalot/bootstrap-datetimepicker/pull/80
|
|
this.$input.on('changeMode', function(e) {
|
|
var f = $(this).closest('form').parent();
|
|
//timeout here, otherwise container changes position before form has new size
|
|
setTimeout(function(){
|
|
f.triggerHandler('resize');
|
|
}, 0);
|
|
});
|
|
|
|
//"clear" link
|
|
if(this.options.clear) {
|
|
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.clear();
|
|
}, this));
|
|
|
|
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
//formatDate works with UTCDate!
|
|
var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : '';
|
|
if(element) {
|
|
DateTime.superclass.value2html.call(this, text, element);
|
|
} else {
|
|
return text;
|
|
}
|
|
},
|
|
|
|
html2value: function(html) {
|
|
//parseDate return utc date!
|
|
var value = this.parseDate(html, this.parsedViewFormat);
|
|
return value ? this.fromUTC(value) : null;
|
|
},
|
|
|
|
value2str: function(value) {
|
|
//formatDate works with UTCDate!
|
|
return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : '';
|
|
},
|
|
|
|
str2value: function(str) {
|
|
//parseDate return utc date!
|
|
var value = this.parseDate(str, this.parsedFormat);
|
|
return value ? this.fromUTC(value) : null;
|
|
},
|
|
|
|
value2submit: function(value) {
|
|
return this.value2str(value);
|
|
},
|
|
|
|
value2input: function(value) {
|
|
if(value) {
|
|
this.$input.data('datetimepicker').setDate(value);
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
//date may be cleared, in that case getDate() triggers error
|
|
var dt = this.$input.data('datetimepicker');
|
|
return dt.date ? dt.getDate() : null;
|
|
},
|
|
|
|
activate: function() {
|
|
},
|
|
|
|
clear: function() {
|
|
this.$input.data('datetimepicker').date = null;
|
|
this.$input.find('.active').removeClass('active');
|
|
if(!this.options.showbuttons) {
|
|
this.$input.closest('form').submit();
|
|
}
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('mouseup', '.minute', function(e){
|
|
var $form = $(this).closest('form');
|
|
setTimeout(function() {
|
|
$form.submit();
|
|
}, 200);
|
|
});
|
|
},
|
|
|
|
//convert date from local to utc
|
|
toUTC: function(value) {
|
|
return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value;
|
|
},
|
|
|
|
//convert date from utc to local
|
|
fromUTC: function(value) {
|
|
return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value;
|
|
},
|
|
|
|
/*
|
|
For incorrect date bootstrap-datetimepicker returns current date that is not suitable
|
|
for datetimefield.
|
|
This function returns null for incorrect date.
|
|
*/
|
|
parseDate: function(str, format) {
|
|
var date = null, formattedBack;
|
|
if(str) {
|
|
date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType);
|
|
if(typeof str === 'string') {
|
|
formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType);
|
|
if(str !== formattedBack) {
|
|
date = null;
|
|
}
|
|
}
|
|
}
|
|
return date;
|
|
}
|
|
|
|
});
|
|
|
|
DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <div></div>
|
|
**/
|
|
tpl:'<div class="editable-date well"></div>',
|
|
/**
|
|
@property inputclass
|
|
@default null
|
|
**/
|
|
inputclass: null,
|
|
/**
|
|
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
|
Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code>
|
|
|
|
@property format
|
|
@type string
|
|
@default yyyy-mm-dd hh:ii
|
|
**/
|
|
format:'yyyy-mm-dd hh:ii',
|
|
formatType:'standard',
|
|
/**
|
|
Format used for displaying date. Also applied when converting date from element's text on init.
|
|
If not specified equals to <code>format</code>
|
|
|
|
@property viewformat
|
|
@type string
|
|
@default null
|
|
**/
|
|
viewformat: null,
|
|
/**
|
|
Configuration of datetimepicker.
|
|
Full list of options: https://github.com/smalot/bootstrap-datetimepicker
|
|
|
|
@property datetimepicker
|
|
@type object
|
|
@default { }
|
|
**/
|
|
datetimepicker:{
|
|
todayHighlight: false,
|
|
autoclose: false
|
|
},
|
|
/**
|
|
Text shown as clear date button.
|
|
If <code>false</code> clear button will not be rendered.
|
|
|
|
@property clear
|
|
@type boolean|string
|
|
@default 'x clear'
|
|
**/
|
|
clear: '× clear'
|
|
});
|
|
|
|
$.fn.editabletypes.datetime = DateTime;
|
|
|
|
}(window.jQuery));
|
|
/**
|
|
Bootstrap datetimefield input - datetime input for inline mode.
|
|
Shows normal <input type="text"> and binds popup datetimepicker.
|
|
Automatically shown in inline mode.
|
|
|
|
@class datetimefield
|
|
@extends datetime
|
|
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var DateTimeField = function (options) {
|
|
this.init('datetimefield', options, DateTimeField.defaults);
|
|
this.initPicker(options, DateTimeField.defaults);
|
|
};
|
|
|
|
$.fn.editableutils.inherit(DateTimeField, $.fn.editabletypes.datetime);
|
|
|
|
$.extend(DateTimeField.prototype, {
|
|
render: function () {
|
|
this.$input = this.$tpl.find('input');
|
|
this.setClass();
|
|
this.setAttr('placeholder');
|
|
|
|
this.$tpl.datetimepicker(this.options.datetimepicker);
|
|
|
|
//need to disable original event handlers
|
|
this.$input.off('focus keydown');
|
|
|
|
//update value of datepicker
|
|
this.$input.keyup($.proxy(function(){
|
|
this.$tpl.removeData('date');
|
|
this.$tpl.datetimepicker('update');
|
|
}, this));
|
|
|
|
},
|
|
|
|
value2input: function(value) {
|
|
this.$input.val(this.value2html(value));
|
|
this.$tpl.datetimepicker('update');
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.html2value(this.$input.val());
|
|
},
|
|
|
|
activate: function() {
|
|
$.fn.editabletypes.text.prototype.activate.call(this);
|
|
},
|
|
|
|
autosubmit: function() {
|
|
//reset autosubmit to empty
|
|
}
|
|
});
|
|
|
|
DateTimeField.defaults = $.extend({}, $.fn.editabletypes.datetime.defaults, {
|
|
/**
|
|
@property tpl
|
|
**/
|
|
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
|
|
/**
|
|
@property inputclass
|
|
@default 'input-medium'
|
|
**/
|
|
inputclass: 'input-medium',
|
|
|
|
/* datetimepicker config */
|
|
datetimepicker:{
|
|
todayHighlight: false,
|
|
autoclose: true
|
|
}
|
|
});
|
|
|
|
$.fn.editabletypes.datetimefield = DateTimeField;
|
|
|
|
}(window.jQuery));
|
|
/*! version : 4.17.47
|
|
=========================================================
|
|
bootstrap-datetimejs
|
|
https://github.com/Eonasdan/bootstrap-datetimepicker
|
|
Copyright (c) 2015 Jonathan Peterson
|
|
=========================================================
|
|
*/
|
|
/*
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2015 Jonathan Peterson
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
/*global define:false */
|
|
/*global exports:false */
|
|
/*global require:false */
|
|
/*global jQuery:false */
|
|
/*global moment:false */
|
|
(function (factory) {
|
|
'use strict';
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD is used - Register as an anonymous module.
|
|
define(['jquery', 'moment'], factory);
|
|
} else if (typeof exports === 'object') {
|
|
module.exports = factory(require('jquery'), require('moment'));
|
|
} else {
|
|
// Neither AMD nor CommonJS used. Use global variables.
|
|
if (typeof jQuery === 'undefined') {
|
|
throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
|
|
}
|
|
if (typeof moment === 'undefined') {
|
|
throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
|
|
}
|
|
factory(jQuery, moment);
|
|
}
|
|
}(function ($, moment) {
|
|
'use strict';
|
|
if (!moment) {
|
|
throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
|
|
}
|
|
|
|
var dateTimePicker = function (element, options) {
|
|
var picker = {},
|
|
date,
|
|
viewDate,
|
|
unset = true,
|
|
input,
|
|
component = false,
|
|
widget = false,
|
|
use24Hours,
|
|
minViewModeNumber = 0,
|
|
actualFormat,
|
|
parseFormats,
|
|
currentViewMode,
|
|
datePickerModes = [
|
|
{
|
|
clsName: 'days',
|
|
navFnc: 'M',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'months',
|
|
navFnc: 'y',
|
|
navStep: 1
|
|
},
|
|
{
|
|
clsName: 'years',
|
|
navFnc: 'y',
|
|
navStep: 10
|
|
},
|
|
{
|
|
clsName: 'decades',
|
|
navFnc: 'y',
|
|
navStep: 100
|
|
}
|
|
],
|
|
viewModes = ['days', 'months', 'years', 'decades'],
|
|
verticalModes = ['top', 'bottom', 'auto'],
|
|
horizontalModes = ['left', 'right', 'auto'],
|
|
toolbarPlacements = ['default', 'top', 'bottom'],
|
|
keyMap = {
|
|
'up': 38,
|
|
38: 'up',
|
|
'down': 40,
|
|
40: 'down',
|
|
'left': 37,
|
|
37: 'left',
|
|
'right': 39,
|
|
39: 'right',
|
|
'tab': 9,
|
|
9: 'tab',
|
|
'escape': 27,
|
|
27: 'escape',
|
|
'enter': 13,
|
|
13: 'enter',
|
|
'pageUp': 33,
|
|
33: 'pageUp',
|
|
'pageDown': 34,
|
|
34: 'pageDown',
|
|
'shift': 16,
|
|
16: 'shift',
|
|
'control': 17,
|
|
17: 'control',
|
|
'space': 32,
|
|
32: 'space',
|
|
't': 84,
|
|
84: 't',
|
|
'delete': 46,
|
|
46: 'delete'
|
|
},
|
|
keyState = {},
|
|
|
|
/********************************************************************************
|
|
*
|
|
* Private functions
|
|
*
|
|
********************************************************************************/
|
|
|
|
hasTimeZone = function () {
|
|
return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '';
|
|
},
|
|
|
|
getMoment = function (d) {
|
|
var returnMoment;
|
|
|
|
if (d === undefined || d === null) {
|
|
returnMoment = moment(); //TODO should this use format? and locale?
|
|
} else if (moment.isDate(d) || moment.isMoment(d)) {
|
|
// If the date that is passed in is already a Date() or moment() object,
|
|
// pass it directly to moment.
|
|
returnMoment = moment(d);
|
|
} else if (hasTimeZone()) { // There is a string to parse and a default time zone
|
|
// parse with the tz function which takes a default time zone if it is not in the format string
|
|
returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone);
|
|
} else {
|
|
returnMoment = moment(d, parseFormats, options.useStrict);
|
|
}
|
|
|
|
if (hasTimeZone()) {
|
|
returnMoment.tz(options.timeZone);
|
|
}
|
|
|
|
return returnMoment;
|
|
},
|
|
|
|
isEnabled = function (granularity) {
|
|
if (typeof granularity !== 'string' || granularity.length > 1) {
|
|
throw new TypeError('isEnabled expects a single character string parameter');
|
|
}
|
|
switch (granularity) {
|
|
case 'y':
|
|
return actualFormat.indexOf('Y') !== -1;
|
|
case 'M':
|
|
return actualFormat.indexOf('M') !== -1;
|
|
case 'd':
|
|
return actualFormat.toLowerCase().indexOf('d') !== -1;
|
|
case 'h':
|
|
case 'H':
|
|
return actualFormat.toLowerCase().indexOf('h') !== -1;
|
|
case 'm':
|
|
return actualFormat.indexOf('m') !== -1;
|
|
case 's':
|
|
return actualFormat.indexOf('s') !== -1;
|
|
default:
|
|
return false;
|
|
}
|
|
},
|
|
|
|
hasTime = function () {
|
|
return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
|
|
},
|
|
|
|
hasDate = function () {
|
|
return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
|
|
},
|
|
|
|
getDatePickerTemplate = function () {
|
|
var headTemplate = $('<thead>')
|
|
.append($('<tr>')
|
|
.append($('<th>').addClass('prev').attr('data-action', 'previous')
|
|
.append($('<span>').addClass(options.icons.previous))
|
|
)
|
|
.append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
|
|
.append($('<th>').addClass('next').attr('data-action', 'next')
|
|
.append($('<span>').addClass(options.icons.next))
|
|
)
|
|
),
|
|
contTemplate = $('<tbody>')
|
|
.append($('<tr>')
|
|
.append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
|
|
);
|
|
|
|
return [
|
|
$('<div>').addClass('datepicker-days')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate)
|
|
.append($('<tbody>'))
|
|
),
|
|
$('<div>').addClass('datepicker-months')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate.clone())
|
|
.append(contTemplate.clone())
|
|
),
|
|
$('<div>').addClass('datepicker-years')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate.clone())
|
|
.append(contTemplate.clone())
|
|
),
|
|
$('<div>').addClass('datepicker-decades')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append(headTemplate.clone())
|
|
.append(contTemplate.clone())
|
|
)
|
|
];
|
|
},
|
|
|
|
getTimePickerMainTemplate = function () {
|
|
var topRow = $('<tr>'),
|
|
middleRow = $('<tr>'),
|
|
bottomRow = $('<tr>');
|
|
|
|
if (isEnabled('h')) {
|
|
topRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour }).addClass('btn').attr('data-action', 'incrementHours').append($('<span>').addClass(options.icons.up))));
|
|
middleRow.append($('<td>')
|
|
.append($('<span>').addClass('timepicker-hour').attr({ 'data-time-component': 'hours', 'title': options.tooltips.pickHour }).attr('data-action', 'showHours')));
|
|
bottomRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour }).addClass('btn').attr('data-action', 'decrementHours').append($('<span>').addClass(options.icons.down))));
|
|
}
|
|
if (isEnabled('m')) {
|
|
if (isEnabled('h')) {
|
|
topRow.append($('<td>').addClass('separator'));
|
|
middleRow.append($('<td>').addClass('separator').html(':'));
|
|
bottomRow.append($('<td>').addClass('separator'));
|
|
}
|
|
topRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute }).addClass('btn').attr('data-action', 'incrementMinutes')
|
|
.append($('<span>').addClass(options.icons.up))));
|
|
middleRow.append($('<td>')
|
|
.append($('<span>').addClass('timepicker-minute').attr({ 'data-time-component': 'minutes', 'title': options.tooltips.pickMinute }).attr('data-action', 'showMinutes')));
|
|
bottomRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute }).addClass('btn').attr('data-action', 'decrementMinutes')
|
|
.append($('<span>').addClass(options.icons.down))));
|
|
}
|
|
if (isEnabled('s')) {
|
|
if (isEnabled('m')) {
|
|
topRow.append($('<td>').addClass('separator'));
|
|
middleRow.append($('<td>').addClass('separator').html(':'));
|
|
bottomRow.append($('<td>').addClass('separator'));
|
|
}
|
|
topRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond }).addClass('btn').attr('data-action', 'incrementSeconds')
|
|
.append($('<span>').addClass(options.icons.up))));
|
|
middleRow.append($('<td>')
|
|
.append($('<span>').addClass('timepicker-second').attr({ 'data-time-component': 'seconds', 'title': options.tooltips.pickSecond }).attr('data-action', 'showSeconds')));
|
|
bottomRow.append($('<td>')
|
|
.append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond }).addClass('btn').attr('data-action', 'decrementSeconds')
|
|
.append($('<span>').addClass(options.icons.down))));
|
|
}
|
|
|
|
if (!use24Hours) {
|
|
topRow.append($('<td>').addClass('separator'));
|
|
middleRow.append($('<td>')
|
|
.append($('<button>').addClass('btn btn-primary').attr({ 'data-action': 'togglePeriod', tabindex: '-1', 'title': options.tooltips.togglePeriod })));
|
|
bottomRow.append($('<td>').addClass('separator'));
|
|
}
|
|
|
|
return $('<div>').addClass('timepicker-picker')
|
|
.append($('<table>').addClass('table-condensed')
|
|
.append([topRow, middleRow, bottomRow]));
|
|
},
|
|
|
|
getTimePickerTemplate = function () {
|
|
var hoursView = $('<div>').addClass('timepicker-hours')
|
|
.append($('<table>').addClass('table-condensed')),
|
|
minutesView = $('<div>').addClass('timepicker-minutes')
|
|
.append($('<table>').addClass('table-condensed')),
|
|
secondsView = $('<div>').addClass('timepicker-seconds')
|
|
.append($('<table>').addClass('table-condensed')),
|
|
ret = [getTimePickerMainTemplate()];
|
|
|
|
if (isEnabled('h')) {
|
|
ret.push(hoursView);
|
|
}
|
|
if (isEnabled('m')) {
|
|
ret.push(minutesView);
|
|
}
|
|
if (isEnabled('s')) {
|
|
ret.push(secondsView);
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
getToolbar = function () {
|
|
var row = [];
|
|
if (options.showTodayButton) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'today', 'title': options.tooltips.today }).append($('<span>').addClass(options.icons.today))));
|
|
}
|
|
if (!options.sideBySide && hasDate() && hasTime()) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'togglePicker', 'title': options.tooltips.selectTime }).append($('<span>').addClass(options.icons.time))));
|
|
}
|
|
if (options.showClear) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'clear', 'title': options.tooltips.clear }).append($('<span>').addClass(options.icons.clear))));
|
|
}
|
|
if (options.showClose) {
|
|
row.push($('<td>').append($('<a>').attr({ 'data-action': 'close', 'title': options.tooltips.close }).append($('<span>').addClass(options.icons.close))));
|
|
}
|
|
return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
|
|
},
|
|
|
|
getTemplate = function () {
|
|
var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
|
|
dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
|
|
timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
|
|
content = $('<ul>').addClass('list-unstyled'),
|
|
toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());
|
|
|
|
if (options.inline) {
|
|
template.removeClass('dropdown-menu');
|
|
}
|
|
|
|
if (use24Hours) {
|
|
template.addClass('usetwentyfour');
|
|
}
|
|
|
|
if (isEnabled('s') && !use24Hours) {
|
|
template.addClass('wider');
|
|
}
|
|
|
|
if (options.sideBySide && hasDate() && hasTime()) {
|
|
template.addClass('timepicker-sbs');
|
|
if (options.toolbarPlacement === 'top') {
|
|
template.append(toolbar);
|
|
}
|
|
template.append(
|
|
$('<div>').addClass('row')
|
|
.append(dateView.addClass('col-md-6'))
|
|
.append(timeView.addClass('col-md-6'))
|
|
);
|
|
if (options.toolbarPlacement === 'bottom') {
|
|
template.append(toolbar);
|
|
}
|
|
return template;
|
|
}
|
|
|
|
if (options.toolbarPlacement === 'top') {
|
|
content.append(toolbar);
|
|
}
|
|
if (hasDate()) {
|
|
content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
|
|
}
|
|
if (options.toolbarPlacement === 'default') {
|
|
content.append(toolbar);
|
|
}
|
|
if (hasTime()) {
|
|
content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
|
|
}
|
|
if (options.toolbarPlacement === 'bottom') {
|
|
content.append(toolbar);
|
|
}
|
|
return template.append(content);
|
|
},
|
|
|
|
dataToOptions = function () {
|
|
var eData,
|
|
dataOptions = {};
|
|
|
|
if (element.is('input') || options.inline) {
|
|
eData = element.data();
|
|
} else {
|
|
eData = element.find('input').data();
|
|
}
|
|
|
|
if (eData.dateOptions && eData.dateOptions instanceof Object) {
|
|
dataOptions = $.extend(true, dataOptions, eData.dateOptions);
|
|
}
|
|
|
|
$.each(options, function (key) {
|
|
var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
|
|
if (eData[attributeName] !== undefined) {
|
|
dataOptions[key] = eData[attributeName];
|
|
}
|
|
});
|
|
return dataOptions;
|
|
},
|
|
|
|
place = function () {
|
|
var position = (component || element).position(),
|
|
offset = (component || element).offset(),
|
|
vertical = options.widgetPositioning.vertical,
|
|
horizontal = options.widgetPositioning.horizontal,
|
|
parent;
|
|
|
|
if (options.widgetParent) {
|
|
parent = options.widgetParent.append(widget);
|
|
} else if (element.is('input')) {
|
|
parent = element.after(widget).parent();
|
|
} else if (options.inline) {
|
|
parent = element.append(widget);
|
|
return;
|
|
} else {
|
|
parent = element;
|
|
element.children().first().after(widget);
|
|
}
|
|
|
|
// Top and bottom logic
|
|
if (vertical === 'auto') {
|
|
if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
|
|
widget.height() + element.outerHeight() < offset.top) {
|
|
vertical = 'top';
|
|
} else {
|
|
vertical = 'bottom';
|
|
}
|
|
}
|
|
|
|
// Left and right logic
|
|
if (horizontal === 'auto') {
|
|
if (parent.width() < offset.left + widget.outerWidth() / 2 &&
|
|
offset.left + widget.outerWidth() > $(window).width()) {
|
|
horizontal = 'right';
|
|
} else {
|
|
horizontal = 'left';
|
|
}
|
|
}
|
|
|
|
if (vertical === 'top') {
|
|
widget.addClass('top').removeClass('bottom');
|
|
} else {
|
|
widget.addClass('bottom').removeClass('top');
|
|
}
|
|
|
|
if (horizontal === 'right') {
|
|
widget.addClass('pull-right');
|
|
} else {
|
|
widget.removeClass('pull-right');
|
|
}
|
|
|
|
// find the first parent element that has a non-static css positioning
|
|
if (parent.css('position') === 'static') {
|
|
parent = parent.parents().filter(function () {
|
|
return $(this).css('position') !== 'static';
|
|
}).first();
|
|
}
|
|
|
|
if (parent.length === 0) {
|
|
throw new Error('datetimepicker component should be placed within a non-static positioned container');
|
|
}
|
|
|
|
widget.css({
|
|
top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
|
|
bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto',
|
|
left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
|
|
right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
|
|
});
|
|
},
|
|
|
|
notifyEvent = function (e) {
|
|
if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
|
|
return;
|
|
}
|
|
element.trigger(e);
|
|
},
|
|
|
|
viewUpdate = function (e) {
|
|
if (e === 'y') {
|
|
e = 'YYYY';
|
|
}
|
|
notifyEvent({
|
|
type: 'dp.update',
|
|
change: e,
|
|
viewDate: viewDate.clone()
|
|
});
|
|
},
|
|
|
|
showMode = function (dir) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
if (dir) {
|
|
currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir));
|
|
}
|
|
widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
|
|
},
|
|
|
|
fillDow = function () {
|
|
var row = $('<tr>'),
|
|
currentDate = viewDate.clone().startOf('w').startOf('d');
|
|
|
|
if (options.calendarWeeks === true) {
|
|
row.append($('<th>').addClass('cw').text('#'));
|
|
}
|
|
|
|
while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
|
|
row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
|
|
currentDate.add(1, 'd');
|
|
}
|
|
widget.find('.datepicker-days thead').append(row);
|
|
},
|
|
|
|
isInDisabledDates = function (testDate) {
|
|
return options.disabledDates[testDate.format('YYYY-MM-DD')] === true;
|
|
},
|
|
|
|
isInEnabledDates = function (testDate) {
|
|
return options.enabledDates[testDate.format('YYYY-MM-DD')] === true;
|
|
},
|
|
|
|
isInDisabledHours = function (testDate) {
|
|
return options.disabledHours[testDate.format('H')] === true;
|
|
},
|
|
|
|
isInEnabledHours = function (testDate) {
|
|
return options.enabledHours[testDate.format('H')] === true;
|
|
},
|
|
|
|
isValid = function (targetMoment, granularity) {
|
|
if (!targetMoment.isValid()) {
|
|
return false;
|
|
}
|
|
if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
|
|
return false;
|
|
}
|
|
if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
|
|
return false;
|
|
}
|
|
if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
|
|
return false;
|
|
}
|
|
if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) {
|
|
return false;
|
|
}
|
|
if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {
|
|
var found = false;
|
|
$.each(options.disabledTimeIntervals, function () {
|
|
if (targetMoment.isBetween(this[0], this[1])) {
|
|
found = true;
|
|
return false;
|
|
}
|
|
});
|
|
if (found) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
fillMonths = function () {
|
|
var spans = [],
|
|
monthsShort = viewDate.clone().startOf('y').startOf('d');
|
|
while (monthsShort.isSame(viewDate, 'y')) {
|
|
spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
|
|
monthsShort.add(1, 'M');
|
|
}
|
|
widget.find('.datepicker-months td').empty().append(spans);
|
|
},
|
|
|
|
updateMonths = function () {
|
|
var monthsView = widget.find('.datepicker-months'),
|
|
monthsViewHeader = monthsView.find('th'),
|
|
months = monthsView.find('tbody').find('span');
|
|
|
|
monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear);
|
|
monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear);
|
|
monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear);
|
|
|
|
monthsView.find('.disabled').removeClass('disabled');
|
|
|
|
if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
|
|
monthsViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
|
|
monthsViewHeader.eq(1).text(viewDate.year());
|
|
|
|
if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
|
|
monthsViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
months.removeClass('active');
|
|
if (date.isSame(viewDate, 'y') && !unset) {
|
|
months.eq(date.month()).addClass('active');
|
|
}
|
|
|
|
months.each(function (index) {
|
|
if (!isValid(viewDate.clone().month(index), 'M')) {
|
|
$(this).addClass('disabled');
|
|
}
|
|
});
|
|
},
|
|
|
|
updateYears = function () {
|
|
var yearsView = widget.find('.datepicker-years'),
|
|
yearsViewHeader = yearsView.find('th'),
|
|
startYear = viewDate.clone().subtract(5, 'y'),
|
|
endYear = viewDate.clone().add(6, 'y'),
|
|
html = '';
|
|
|
|
yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade);
|
|
yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade);
|
|
yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade);
|
|
|
|
yearsView.find('.disabled').removeClass('disabled');
|
|
|
|
if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
|
|
yearsViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
|
|
yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());
|
|
|
|
if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
|
|
yearsViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
while (!startYear.isAfter(endYear, 'y')) {
|
|
html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
|
|
startYear.add(1, 'y');
|
|
}
|
|
|
|
yearsView.find('td').html(html);
|
|
},
|
|
|
|
updateDecades = function () {
|
|
var decadesView = widget.find('.datepicker-decades'),
|
|
decadesViewHeader = decadesView.find('th'),
|
|
startDecade = moment({ y: viewDate.year() - (viewDate.year() % 100) - 1 }),
|
|
endDecade = startDecade.clone().add(100, 'y'),
|
|
startedAt = startDecade.clone(),
|
|
minDateDecade = false,
|
|
maxDateDecade = false,
|
|
endDecadeYear,
|
|
html = '';
|
|
|
|
decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury);
|
|
decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury);
|
|
|
|
decadesView.find('.disabled').removeClass('disabled');
|
|
|
|
if (startDecade.isSame(moment({ y: 1900 })) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
|
|
decadesViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
|
|
decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());
|
|
|
|
if (startDecade.isSame(moment({ y: 2000 })) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
|
|
decadesViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
while (!startDecade.isAfter(endDecade, 'y')) {
|
|
endDecadeYear = startDecade.year() + 12;
|
|
minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear;
|
|
maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear;
|
|
html += '<span data-action="selectDecade" class="decade' + (date.isAfter(startDecade) && date.year() <= endDecadeYear ? ' active' : '') +
|
|
(!isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
|
|
startDecade.add(12, 'y');
|
|
}
|
|
html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
|
|
|
|
decadesView.find('td').html(html);
|
|
decadesViewHeader.eq(1).text((startedAt.year() + 1) + '-' + (startDecade.year()));
|
|
},
|
|
|
|
fillDate = function () {
|
|
var daysView = widget.find('.datepicker-days'),
|
|
daysViewHeader = daysView.find('th'),
|
|
currentDate,
|
|
html = [],
|
|
row,
|
|
clsNames = [],
|
|
i;
|
|
|
|
if (!hasDate()) {
|
|
return;
|
|
}
|
|
|
|
daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth);
|
|
daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth);
|
|
daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth);
|
|
|
|
daysView.find('.disabled').removeClass('disabled');
|
|
daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
|
|
|
|
if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
|
|
daysViewHeader.eq(0).addClass('disabled');
|
|
}
|
|
if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
|
|
daysViewHeader.eq(2).addClass('disabled');
|
|
}
|
|
|
|
currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d');
|
|
|
|
for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks)
|
|
if (currentDate.weekday() === 0) {
|
|
row = $('<tr>');
|
|
if (options.calendarWeeks) {
|
|
row.append('<td class="cw">' + currentDate.week() + '</td>');
|
|
}
|
|
html.push(row);
|
|
}
|
|
clsNames = ['day'];
|
|
if (currentDate.isBefore(viewDate, 'M')) {
|
|
clsNames.push('old');
|
|
}
|
|
if (currentDate.isAfter(viewDate, 'M')) {
|
|
clsNames.push('new');
|
|
}
|
|
if (currentDate.isSame(date, 'd') && !unset) {
|
|
clsNames.push('active');
|
|
}
|
|
if (!isValid(currentDate, 'd')) {
|
|
clsNames.push('disabled');
|
|
}
|
|
if (currentDate.isSame(getMoment(), 'd')) {
|
|
clsNames.push('today');
|
|
}
|
|
if (currentDate.day() === 0 || currentDate.day() === 6) {
|
|
clsNames.push('weekend');
|
|
}
|
|
notifyEvent({
|
|
type: 'dp.classify',
|
|
date: currentDate,
|
|
classNames: clsNames
|
|
});
|
|
row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="' + clsNames.join(' ') + '">' + currentDate.date() + '</td>');
|
|
currentDate.add(1, 'd');
|
|
}
|
|
|
|
daysView.find('tbody').empty().append(html);
|
|
|
|
updateMonths();
|
|
|
|
updateYears();
|
|
|
|
updateDecades();
|
|
},
|
|
|
|
fillHours = function () {
|
|
var table = widget.find('.timepicker-hours table'),
|
|
currentHour = viewDate.clone().startOf('d'),
|
|
html = [],
|
|
row = $('<tr>');
|
|
|
|
if (viewDate.hour() > 11 && !use24Hours) {
|
|
currentHour.hour(12);
|
|
}
|
|
while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
|
|
if (currentHour.hour() % 4 === 0) {
|
|
row = $('<tr>');
|
|
html.push(row);
|
|
}
|
|
row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
|
|
currentHour.add(1, 'h');
|
|
}
|
|
table.empty().append(html);
|
|
},
|
|
|
|
fillMinutes = function () {
|
|
var table = widget.find('.timepicker-minutes table'),
|
|
currentMinute = viewDate.clone().startOf('h'),
|
|
html = [],
|
|
row = $('<tr>'),
|
|
step = options.stepping === 1 ? 5 : options.stepping;
|
|
|
|
while (viewDate.isSame(currentMinute, 'h')) {
|
|
if (currentMinute.minute() % (step * 4) === 0) {
|
|
row = $('<tr>');
|
|
html.push(row);
|
|
}
|
|
row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
|
|
currentMinute.add(step, 'm');
|
|
}
|
|
table.empty().append(html);
|
|
},
|
|
|
|
fillSeconds = function () {
|
|
var table = widget.find('.timepicker-seconds table'),
|
|
currentSecond = viewDate.clone().startOf('m'),
|
|
html = [],
|
|
row = $('<tr>');
|
|
|
|
while (viewDate.isSame(currentSecond, 'm')) {
|
|
if (currentSecond.second() % 20 === 0) {
|
|
row = $('<tr>');
|
|
html.push(row);
|
|
}
|
|
row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
|
|
currentSecond.add(5, 's');
|
|
}
|
|
|
|
table.empty().append(html);
|
|
},
|
|
|
|
fillTime = function () {
|
|
var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]');
|
|
|
|
if (!use24Hours) {
|
|
toggle = widget.find('.timepicker [data-action=togglePeriod]');
|
|
newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h');
|
|
|
|
toggle.text(date.format('A'));
|
|
|
|
if (isValid(newDate, 'h')) {
|
|
toggle.removeClass('disabled');
|
|
} else {
|
|
toggle.addClass('disabled');
|
|
}
|
|
}
|
|
timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
|
|
timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
|
|
timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));
|
|
|
|
fillHours();
|
|
fillMinutes();
|
|
fillSeconds();
|
|
},
|
|
|
|
update = function () {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
fillDate();
|
|
fillTime();
|
|
},
|
|
|
|
setValue = function (targetMoment) {
|
|
var oldDate = unset ? null : date;
|
|
|
|
// case of calling setValue(null or false)
|
|
if (!targetMoment) {
|
|
unset = true;
|
|
input.val('');
|
|
element.data('date', '');
|
|
notifyEvent({
|
|
type: 'dp.change',
|
|
date: false,
|
|
oldDate: oldDate
|
|
});
|
|
update();
|
|
return;
|
|
}
|
|
|
|
targetMoment = targetMoment.clone().locale(options.locale);
|
|
|
|
if (hasTimeZone()) {
|
|
targetMoment.tz(options.timeZone);
|
|
}
|
|
|
|
if (options.stepping !== 1) {
|
|
targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping)).seconds(0);
|
|
|
|
while (options.minDate && targetMoment.isBefore(options.minDate)) {
|
|
targetMoment.add(options.stepping, 'minutes');
|
|
}
|
|
}
|
|
|
|
if (isValid(targetMoment)) {
|
|
date = targetMoment;
|
|
viewDate = date.clone();
|
|
input.val(date.format(actualFormat));
|
|
element.data('date', date.format(actualFormat));
|
|
unset = false;
|
|
update();
|
|
notifyEvent({
|
|
type: 'dp.change',
|
|
date: date.clone(),
|
|
oldDate: oldDate
|
|
});
|
|
} else {
|
|
if (!options.keepInvalid) {
|
|
input.val(unset ? '' : date.format(actualFormat));
|
|
} else {
|
|
notifyEvent({
|
|
type: 'dp.change',
|
|
date: targetMoment,
|
|
oldDate: oldDate
|
|
});
|
|
}
|
|
notifyEvent({
|
|
type: 'dp.error',
|
|
date: targetMoment,
|
|
oldDate: oldDate
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Hides the widget. Possibly will emit dp.hide
|
|
*/
|
|
hide = function () {
|
|
var transitioning = false;
|
|
if (!widget) {
|
|
return picker;
|
|
}
|
|
// Ignore event if in the middle of a picker transition
|
|
widget.find('.collapse').each(function () {
|
|
var collapseData = $(this).data('collapse');
|
|
if (collapseData && collapseData.transitioning) {
|
|
transitioning = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (transitioning) {
|
|
return picker;
|
|
}
|
|
if (component && component.hasClass('btn')) {
|
|
component.toggleClass('active');
|
|
}
|
|
widget.hide();
|
|
|
|
$(window).off('resize', place);
|
|
widget.off('click', '[data-action]');
|
|
widget.off('mousedown', false);
|
|
|
|
widget.remove();
|
|
widget = false;
|
|
|
|
notifyEvent({
|
|
type: 'dp.hide',
|
|
date: date.clone()
|
|
});
|
|
|
|
input.blur();
|
|
|
|
viewDate = date.clone();
|
|
|
|
return picker;
|
|
},
|
|
|
|
clear = function () {
|
|
setValue(null);
|
|
},
|
|
|
|
parseInputDate = function (inputDate) {
|
|
if (options.parseInputDate === undefined) {
|
|
if (!moment.isMoment(inputDate) || inputDate instanceof Date) {
|
|
inputDate = getMoment(inputDate);
|
|
}
|
|
} else {
|
|
inputDate = options.parseInputDate(inputDate);
|
|
}
|
|
//inputDate.locale(options.locale);
|
|
return inputDate;
|
|
},
|
|
|
|
/********************************************************************************
|
|
*
|
|
* Widget UI interaction functions
|
|
*
|
|
********************************************************************************/
|
|
actions = {
|
|
next: function () {
|
|
var navFnc = datePickerModes[currentViewMode].navFnc;
|
|
viewDate.add(datePickerModes[currentViewMode].navStep, navFnc);
|
|
fillDate();
|
|
viewUpdate(navFnc);
|
|
},
|
|
|
|
previous: function () {
|
|
var navFnc = datePickerModes[currentViewMode].navFnc;
|
|
viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc);
|
|
fillDate();
|
|
viewUpdate(navFnc);
|
|
},
|
|
|
|
pickerSwitch: function () {
|
|
showMode(1);
|
|
},
|
|
|
|
selectMonth: function (e) {
|
|
var month = $(e.target).closest('tbody').find('span').index($(e.target));
|
|
viewDate.month(month);
|
|
if (currentViewMode === minViewModeNumber) {
|
|
setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
|
|
if (!options.inline) {
|
|
hide();
|
|
}
|
|
} else {
|
|
showMode(-1);
|
|
fillDate();
|
|
}
|
|
viewUpdate('M');
|
|
},
|
|
|
|
selectYear: function (e) {
|
|
var year = parseInt($(e.target).text(), 10) || 0;
|
|
viewDate.year(year);
|
|
if (currentViewMode === minViewModeNumber) {
|
|
setValue(date.clone().year(viewDate.year()));
|
|
if (!options.inline) {
|
|
hide();
|
|
}
|
|
} else {
|
|
showMode(-1);
|
|
fillDate();
|
|
}
|
|
viewUpdate('YYYY');
|
|
},
|
|
|
|
selectDecade: function (e) {
|
|
var year = parseInt($(e.target).data('selection'), 10) || 0;
|
|
viewDate.year(year);
|
|
if (currentViewMode === minViewModeNumber) {
|
|
setValue(date.clone().year(viewDate.year()));
|
|
if (!options.inline) {
|
|
hide();
|
|
}
|
|
} else {
|
|
showMode(-1);
|
|
fillDate();
|
|
}
|
|
viewUpdate('YYYY');
|
|
},
|
|
|
|
selectDay: function (e) {
|
|
var day = viewDate.clone();
|
|
if ($(e.target).is('.old')) {
|
|
day.subtract(1, 'M');
|
|
}
|
|
if ($(e.target).is('.new')) {
|
|
day.add(1, 'M');
|
|
}
|
|
setValue(day.date(parseInt($(e.target).text(), 10)));
|
|
if (!hasTime() && !options.keepOpen && !options.inline) {
|
|
hide();
|
|
}
|
|
},
|
|
|
|
incrementHours: function () {
|
|
var newDate = date.clone().add(1, 'h');
|
|
if (isValid(newDate, 'h')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
incrementMinutes: function () {
|
|
var newDate = date.clone().add(options.stepping, 'm');
|
|
if (isValid(newDate, 'm')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
incrementSeconds: function () {
|
|
var newDate = date.clone().add(1, 's');
|
|
if (isValid(newDate, 's')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
decrementHours: function () {
|
|
var newDate = date.clone().subtract(1, 'h');
|
|
if (isValid(newDate, 'h')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
decrementMinutes: function () {
|
|
var newDate = date.clone().subtract(options.stepping, 'm');
|
|
if (isValid(newDate, 'm')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
decrementSeconds: function () {
|
|
var newDate = date.clone().subtract(1, 's');
|
|
if (isValid(newDate, 's')) {
|
|
setValue(newDate);
|
|
}
|
|
},
|
|
|
|
togglePeriod: function () {
|
|
setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
|
|
},
|
|
|
|
togglePicker: function (e) {
|
|
var $this = $(e.target),
|
|
$parent = $this.closest('ul'),
|
|
expanded = $parent.find('.in'),
|
|
closed = $parent.find('.collapse:not(.in)'),
|
|
collapseData;
|
|
|
|
if (expanded && expanded.length) {
|
|
collapseData = expanded.data('collapse');
|
|
if (collapseData && collapseData.transitioning) {
|
|
return;
|
|
}
|
|
if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it
|
|
expanded.collapse('hide');
|
|
closed.collapse('show');
|
|
} else { // otherwise just toggle in class on the two views
|
|
expanded.removeClass('in');
|
|
closed.addClass('in');
|
|
}
|
|
if ($this.is('span')) {
|
|
$this.toggleClass(options.icons.time + ' ' + options.icons.date);
|
|
} else {
|
|
$this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
|
|
}
|
|
|
|
// NOTE: uncomment if toggled state will be restored in show()
|
|
//if (component) {
|
|
// component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
|
|
//}
|
|
}
|
|
},
|
|
|
|
showPicker: function () {
|
|
widget.find('.timepicker > div:not(.timepicker-picker)').hide();
|
|
widget.find('.timepicker .timepicker-picker').show();
|
|
},
|
|
|
|
showHours: function () {
|
|
widget.find('.timepicker .timepicker-picker').hide();
|
|
widget.find('.timepicker .timepicker-hours').show();
|
|
},
|
|
|
|
showMinutes: function () {
|
|
widget.find('.timepicker .timepicker-picker').hide();
|
|
widget.find('.timepicker .timepicker-minutes').show();
|
|
},
|
|
|
|
showSeconds: function () {
|
|
widget.find('.timepicker .timepicker-picker').hide();
|
|
widget.find('.timepicker .timepicker-seconds').show();
|
|
},
|
|
|
|
selectHour: function (e) {
|
|
var hour = parseInt($(e.target).text(), 10);
|
|
|
|
if (!use24Hours) {
|
|
if (date.hours() >= 12) {
|
|
if (hour !== 12) {
|
|
hour += 12;
|
|
}
|
|
} else {
|
|
if (hour === 12) {
|
|
hour = 0;
|
|
}
|
|
}
|
|
}
|
|
setValue(date.clone().hours(hour));
|
|
actions.showPicker.call(picker);
|
|
},
|
|
|
|
selectMinute: function (e) {
|
|
setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
|
|
actions.showPicker.call(picker);
|
|
},
|
|
|
|
selectSecond: function (e) {
|
|
setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
|
|
actions.showPicker.call(picker);
|
|
},
|
|
|
|
clear: clear,
|
|
|
|
today: function () {
|
|
var todaysDate = getMoment();
|
|
if (isValid(todaysDate, 'd')) {
|
|
setValue(todaysDate);
|
|
}
|
|
},
|
|
|
|
close: hide
|
|
},
|
|
|
|
doAction = function (e) {
|
|
if ($(e.currentTarget).is('.disabled')) {
|
|
return false;
|
|
}
|
|
actions[$(e.currentTarget).data('action')].apply(picker, arguments);
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Shows the widget. Possibly will emit dp.show and dp.change
|
|
*/
|
|
show = function () {
|
|
var currentMoment,
|
|
useCurrentGranularity = {
|
|
'year': function (m) {
|
|
return m.month(0).date(1).hours(0).seconds(0).minutes(0);
|
|
},
|
|
'month': function (m) {
|
|
return m.date(1).hours(0).seconds(0).minutes(0);
|
|
},
|
|
'day': function (m) {
|
|
return m.hours(0).seconds(0).minutes(0);
|
|
},
|
|
'hour': function (m) {
|
|
return m.seconds(0).minutes(0);
|
|
},
|
|
'minute': function (m) {
|
|
return m.seconds(0);
|
|
}
|
|
};
|
|
|
|
if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) {
|
|
return picker;
|
|
}
|
|
if (input.val() !== undefined && input.val().trim().length !== 0) {
|
|
setValue(parseInputDate(input.val().trim()));
|
|
} else if (unset && options.useCurrent && (options.inline || (input.is('input') && input.val().trim().length === 0))) {
|
|
currentMoment = getMoment();
|
|
if (typeof options.useCurrent === 'string') {
|
|
currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
|
|
}
|
|
setValue(currentMoment);
|
|
}
|
|
widget = getTemplate();
|
|
|
|
fillDow();
|
|
fillMonths();
|
|
|
|
widget.find('.timepicker-hours').hide();
|
|
widget.find('.timepicker-minutes').hide();
|
|
widget.find('.timepicker-seconds').hide();
|
|
|
|
update();
|
|
showMode();
|
|
|
|
$(window).on('resize', place);
|
|
widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
|
|
widget.on('mousedown', false);
|
|
|
|
if (component && component.hasClass('btn')) {
|
|
component.toggleClass('active');
|
|
}
|
|
place();
|
|
widget.show();
|
|
if (options.focusOnShow && !input.is(':focus')) {
|
|
input.focus();
|
|
}
|
|
|
|
notifyEvent({
|
|
type: 'dp.show'
|
|
});
|
|
return picker;
|
|
},
|
|
|
|
/**
|
|
* Shows or hides the widget
|
|
*/
|
|
toggle = function () {
|
|
return (widget ? hide() : show());
|
|
},
|
|
|
|
keydown = function (e) {
|
|
var handler = null,
|
|
index,
|
|
index2,
|
|
pressedKeys = [],
|
|
pressedModifiers = {},
|
|
currentKey = e.which,
|
|
keyBindKeys,
|
|
allModifiersPressed,
|
|
pressed = 'p';
|
|
|
|
keyState[currentKey] = pressed;
|
|
|
|
for (index in keyState) {
|
|
if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
|
|
pressedKeys.push(index);
|
|
if (parseInt(index, 10) !== currentKey) {
|
|
pressedModifiers[index] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (index in options.keyBinds) {
|
|
if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
|
|
keyBindKeys = index.split(' ');
|
|
if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
|
|
allModifiersPressed = true;
|
|
for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
|
|
if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
|
|
allModifiersPressed = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allModifiersPressed) {
|
|
handler = options.keyBinds[index];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (handler) {
|
|
handler.call(picker, widget);
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
|
|
keyup = function (e) {
|
|
keyState[e.which] = 'r';
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
},
|
|
|
|
change = function (e) {
|
|
var val = $(e.target).val().trim(),
|
|
parsedDate = val ? parseInputDate(val) : null;
|
|
setValue(parsedDate);
|
|
e.stopImmediatePropagation();
|
|
return false;
|
|
},
|
|
|
|
attachDatePickerElementEvents = function () {
|
|
input.on({
|
|
'change': change,
|
|
'blur': options.debug ? '' : hide,
|
|
'keydown': keydown,
|
|
'keyup': keyup,
|
|
'focus': options.allowInputToggle ? show : ''
|
|
});
|
|
|
|
if (element.is('input')) {
|
|
input.on({
|
|
'focus': show
|
|
});
|
|
} else if (component) {
|
|
component.on('click', toggle);
|
|
component.on('mousedown', false);
|
|
}
|
|
},
|
|
|
|
detachDatePickerElementEvents = function () {
|
|
input.off({
|
|
'change': change,
|
|
'blur': blur,
|
|
'keydown': keydown,
|
|
'keyup': keyup,
|
|
'focus': options.allowInputToggle ? hide : ''
|
|
});
|
|
|
|
if (element.is('input')) {
|
|
input.off({
|
|
'focus': show
|
|
});
|
|
} else if (component) {
|
|
component.off('click', toggle);
|
|
component.off('mousedown', false);
|
|
}
|
|
},
|
|
|
|
indexGivenDates = function (givenDatesArray) {
|
|
// Store given enabledDates and disabledDates as keys.
|
|
// This way we can check their existence in O(1) time instead of looping through whole array.
|
|
// (for example: options.enabledDates['2014-02-27'] === true)
|
|
var givenDatesIndexed = {};
|
|
$.each(givenDatesArray, function () {
|
|
var dDate = parseInputDate(this);
|
|
if (dDate.isValid()) {
|
|
givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
|
|
}
|
|
});
|
|
return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
|
|
},
|
|
|
|
indexGivenHours = function (givenHoursArray) {
|
|
// Store given enabledHours and disabledHours as keys.
|
|
// This way we can check their existence in O(1) time instead of looping through whole array.
|
|
// (for example: options.enabledHours['2014-02-27'] === true)
|
|
var givenHoursIndexed = {};
|
|
$.each(givenHoursArray, function () {
|
|
givenHoursIndexed[this] = true;
|
|
});
|
|
return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false;
|
|
},
|
|
|
|
initFormatting = function () {
|
|
var format = options.format || 'L LT';
|
|
|
|
actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {
|
|
var newinput = date.localeData().longDateFormat(formatInput) || formatInput;
|
|
return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740
|
|
return date.localeData().longDateFormat(formatInput2) || formatInput2;
|
|
});
|
|
});
|
|
|
|
|
|
parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
|
|
if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
|
|
parseFormats.push(actualFormat);
|
|
}
|
|
|
|
use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1);
|
|
|
|
if (isEnabled('y')) {
|
|
minViewModeNumber = 2;
|
|
}
|
|
if (isEnabled('M')) {
|
|
minViewModeNumber = 1;
|
|
}
|
|
if (isEnabled('d')) {
|
|
minViewModeNumber = 0;
|
|
}
|
|
|
|
currentViewMode = Math.max(minViewModeNumber, currentViewMode);
|
|
|
|
if (!unset) {
|
|
setValue(date);
|
|
}
|
|
};
|
|
|
|
/********************************************************************************
|
|
*
|
|
* Public API functions
|
|
* =====================
|
|
*
|
|
* Important: Do not expose direct references to private objects or the options
|
|
* object to the outer world. Always return a clone when returning values or make
|
|
* a clone when setting a private variable.
|
|
*
|
|
********************************************************************************/
|
|
picker.destroy = function () {
|
|
///<summary>Destroys the widget and removes all attached event listeners</summary>
|
|
hide();
|
|
detachDatePickerElementEvents();
|
|
element.removeData('DateTimePicker');
|
|
element.removeData('date');
|
|
};
|
|
|
|
picker.toggle = toggle;
|
|
|
|
picker.show = show;
|
|
|
|
picker.hide = hide;
|
|
|
|
picker.disable = function () {
|
|
///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it.
|
|
///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary>
|
|
hide();
|
|
if (component && component.hasClass('btn')) {
|
|
component.addClass('disabled');
|
|
}
|
|
input.prop('disabled', true);
|
|
return picker;
|
|
};
|
|
|
|
picker.enable = function () {
|
|
///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary>
|
|
if (component && component.hasClass('btn')) {
|
|
component.removeClass('disabled');
|
|
}
|
|
input.prop('disabled', false);
|
|
return picker;
|
|
};
|
|
|
|
picker.ignoreReadonly = function (ignoreReadonly) {
|
|
if (arguments.length === 0) {
|
|
return options.ignoreReadonly;
|
|
}
|
|
if (typeof ignoreReadonly !== 'boolean') {
|
|
throw new TypeError('ignoreReadonly () expects a boolean parameter');
|
|
}
|
|
options.ignoreReadonly = ignoreReadonly;
|
|
return picker;
|
|
};
|
|
|
|
picker.options = function (newOptions) {
|
|
if (arguments.length === 0) {
|
|
return $.extend(true, {}, options);
|
|
}
|
|
|
|
if (!(newOptions instanceof Object)) {
|
|
throw new TypeError('options() options parameter should be an object');
|
|
}
|
|
$.extend(true, options, newOptions);
|
|
$.each(options, function (key, value) {
|
|
if (picker[key] !== undefined) {
|
|
picker[key](value);
|
|
} else {
|
|
throw new TypeError('option ' + key + ' is not recognized!');
|
|
}
|
|
});
|
|
return picker;
|
|
};
|
|
|
|
picker.date = function (newDate) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.date">
|
|
///<summary>Returns the component's model current date, a moment object or null if not set.</summary>
|
|
///<returns type="Moment">date.clone()</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
|
|
///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
if (unset) {
|
|
return null;
|
|
}
|
|
return date.clone();
|
|
}
|
|
|
|
if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
|
|
throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
|
|
}
|
|
|
|
setValue(newDate === null ? null : parseInputDate(newDate));
|
|
return picker;
|
|
};
|
|
|
|
picker.format = function (newFormat) {
|
|
///<summary>test su</summary>
|
|
///<param name="newFormat">info about para</param>
|
|
///<returns type="string|boolean">returns foo</returns>
|
|
if (arguments.length === 0) {
|
|
return options.format;
|
|
}
|
|
|
|
if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
|
|
throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat);
|
|
}
|
|
|
|
options.format = newFormat;
|
|
if (actualFormat) {
|
|
initFormatting(); // reinit formatting
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.timeZone = function (newZone) {
|
|
if (arguments.length === 0) {
|
|
return options.timeZone;
|
|
}
|
|
|
|
if (typeof newZone !== 'string') {
|
|
throw new TypeError('newZone() expects a string parameter');
|
|
}
|
|
|
|
options.timeZone = newZone;
|
|
|
|
return picker;
|
|
};
|
|
|
|
picker.dayViewHeaderFormat = function (newFormat) {
|
|
if (arguments.length === 0) {
|
|
return options.dayViewHeaderFormat;
|
|
}
|
|
|
|
if (typeof newFormat !== 'string') {
|
|
throw new TypeError('dayViewHeaderFormat() expects a string parameter');
|
|
}
|
|
|
|
options.dayViewHeaderFormat = newFormat;
|
|
return picker;
|
|
};
|
|
|
|
picker.extraFormats = function (formats) {
|
|
if (arguments.length === 0) {
|
|
return options.extraFormats;
|
|
}
|
|
|
|
if (formats !== false && !(formats instanceof Array)) {
|
|
throw new TypeError('extraFormats() expects an array or false parameter');
|
|
}
|
|
|
|
options.extraFormats = formats;
|
|
if (parseFormats) {
|
|
initFormatting(); // reinit formatting
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.disabledDates = function (dates) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.disabledDates">
|
|
///<summary>Returns an array with the currently set disabled dates on the component.</summary>
|
|
///<returns type="array">options.disabledDates</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
|
|
///options.enabledDates if such exist.</summary>
|
|
///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
|
|
}
|
|
|
|
if (!dates) {
|
|
options.disabledDates = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(dates instanceof Array)) {
|
|
throw new TypeError('disabledDates() expects an array parameter');
|
|
}
|
|
options.disabledDates = indexGivenDates(dates);
|
|
options.enabledDates = false;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.enabledDates = function (dates) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.enabledDates">
|
|
///<summary>Returns an array with the currently set enabled dates on the component.</summary>
|
|
///<returns type="array">options.enabledDates</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary>
|
|
///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
|
|
}
|
|
|
|
if (!dates) {
|
|
options.enabledDates = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(dates instanceof Array)) {
|
|
throw new TypeError('enabledDates() expects an array parameter');
|
|
}
|
|
options.enabledDates = indexGivenDates(dates);
|
|
options.disabledDates = false;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
|
|
if (arguments.length === 0) {
|
|
return options.daysOfWeekDisabled.splice(0);
|
|
}
|
|
|
|
if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) {
|
|
options.daysOfWeekDisabled = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
|
|
if (!(daysOfWeekDisabled instanceof Array)) {
|
|
throw new TypeError('daysOfWeekDisabled() expects an array parameter');
|
|
}
|
|
options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
|
|
currentValue = parseInt(currentValue, 10);
|
|
if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
|
|
return previousValue;
|
|
}
|
|
if (previousValue.indexOf(currentValue) === -1) {
|
|
previousValue.push(currentValue);
|
|
}
|
|
return previousValue;
|
|
}, []).sort();
|
|
if (options.useCurrent && !options.keepInvalid) {
|
|
var tries = 0;
|
|
while (!isValid(date, 'd')) {
|
|
date.add(1, 'd');
|
|
if (tries === 31) {
|
|
throw 'Tried 31 times to find a valid date';
|
|
}
|
|
tries++;
|
|
}
|
|
setValue(date);
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.maxDate = function (maxDate) {
|
|
if (arguments.length === 0) {
|
|
return options.maxDate ? options.maxDate.clone() : options.maxDate;
|
|
}
|
|
|
|
if ((typeof maxDate === 'boolean') && maxDate === false) {
|
|
options.maxDate = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
|
|
if (typeof maxDate === 'string') {
|
|
if (maxDate === 'now' || maxDate === 'moment') {
|
|
maxDate = getMoment();
|
|
}
|
|
}
|
|
|
|
var parsedDate = parseInputDate(maxDate);
|
|
|
|
if (!parsedDate.isValid()) {
|
|
throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate);
|
|
}
|
|
if (options.minDate && parsedDate.isBefore(options.minDate)) {
|
|
throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
|
|
}
|
|
options.maxDate = parsedDate;
|
|
if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) {
|
|
setValue(options.maxDate);
|
|
}
|
|
if (viewDate.isAfter(parsedDate)) {
|
|
viewDate = parsedDate.clone().subtract(options.stepping, 'm');
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.minDate = function (minDate) {
|
|
if (arguments.length === 0) {
|
|
return options.minDate ? options.minDate.clone() : options.minDate;
|
|
}
|
|
|
|
if ((typeof minDate === 'boolean') && minDate === false) {
|
|
options.minDate = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
|
|
if (typeof minDate === 'string') {
|
|
if (minDate === 'now' || minDate === 'moment') {
|
|
minDate = getMoment();
|
|
}
|
|
}
|
|
|
|
var parsedDate = parseInputDate(minDate);
|
|
|
|
if (!parsedDate.isValid()) {
|
|
throw new TypeError('minDate() Could not parse date parameter: ' + minDate);
|
|
}
|
|
if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
|
|
throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
|
|
}
|
|
options.minDate = parsedDate;
|
|
if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) {
|
|
setValue(options.minDate);
|
|
}
|
|
if (viewDate.isBefore(parsedDate)) {
|
|
viewDate = parsedDate.clone().add(options.stepping, 'm');
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.defaultDate = function (defaultDate) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.defaultDate">
|
|
///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary>
|
|
///<returns type="Moment">date.clone()</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary>
|
|
///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
|
|
}
|
|
if (!defaultDate) {
|
|
options.defaultDate = false;
|
|
return picker;
|
|
}
|
|
|
|
if (typeof defaultDate === 'string') {
|
|
if (defaultDate === 'now' || defaultDate === 'moment') {
|
|
defaultDate = getMoment();
|
|
} else {
|
|
defaultDate = getMoment(defaultDate);
|
|
}
|
|
}
|
|
|
|
var parsedDate = parseInputDate(defaultDate);
|
|
if (!parsedDate.isValid()) {
|
|
throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
|
|
}
|
|
if (!isValid(parsedDate)) {
|
|
throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
|
|
}
|
|
|
|
options.defaultDate = parsedDate;
|
|
|
|
if ((options.defaultDate && options.inline) || input.val().trim() === '') {
|
|
setValue(options.defaultDate);
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.locale = function (locale) {
|
|
if (arguments.length === 0) {
|
|
return options.locale;
|
|
}
|
|
|
|
if (!moment.localeData(locale)) {
|
|
throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
|
|
}
|
|
|
|
options.locale = locale;
|
|
date.locale(options.locale);
|
|
viewDate.locale(options.locale);
|
|
|
|
if (actualFormat) {
|
|
initFormatting(); // reinit formatting
|
|
}
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.stepping = function (stepping) {
|
|
if (arguments.length === 0) {
|
|
return options.stepping;
|
|
}
|
|
|
|
stepping = parseInt(stepping, 10);
|
|
if (isNaN(stepping) || stepping < 1) {
|
|
stepping = 1;
|
|
}
|
|
options.stepping = stepping;
|
|
return picker;
|
|
};
|
|
|
|
picker.useCurrent = function (useCurrent) {
|
|
var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
|
|
if (arguments.length === 0) {
|
|
return options.useCurrent;
|
|
}
|
|
|
|
if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
|
|
throw new TypeError('useCurrent() expects a boolean or string parameter');
|
|
}
|
|
if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
|
|
throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
|
|
}
|
|
options.useCurrent = useCurrent;
|
|
return picker;
|
|
};
|
|
|
|
picker.collapse = function (collapse) {
|
|
if (arguments.length === 0) {
|
|
return options.collapse;
|
|
}
|
|
|
|
if (typeof collapse !== 'boolean') {
|
|
throw new TypeError('collapse() expects a boolean parameter');
|
|
}
|
|
if (options.collapse === collapse) {
|
|
return picker;
|
|
}
|
|
options.collapse = collapse;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.icons = function (icons) {
|
|
if (arguments.length === 0) {
|
|
return $.extend({}, options.icons);
|
|
}
|
|
|
|
if (!(icons instanceof Object)) {
|
|
throw new TypeError('icons() expects parameter to be an Object');
|
|
}
|
|
$.extend(options.icons, icons);
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.tooltips = function (tooltips) {
|
|
if (arguments.length === 0) {
|
|
return $.extend({}, options.tooltips);
|
|
}
|
|
|
|
if (!(tooltips instanceof Object)) {
|
|
throw new TypeError('tooltips() expects parameter to be an Object');
|
|
}
|
|
$.extend(options.tooltips, tooltips);
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.useStrict = function (useStrict) {
|
|
if (arguments.length === 0) {
|
|
return options.useStrict;
|
|
}
|
|
|
|
if (typeof useStrict !== 'boolean') {
|
|
throw new TypeError('useStrict() expects a boolean parameter');
|
|
}
|
|
options.useStrict = useStrict;
|
|
return picker;
|
|
};
|
|
|
|
picker.sideBySide = function (sideBySide) {
|
|
if (arguments.length === 0) {
|
|
return options.sideBySide;
|
|
}
|
|
|
|
if (typeof sideBySide !== 'boolean') {
|
|
throw new TypeError('sideBySide() expects a boolean parameter');
|
|
}
|
|
options.sideBySide = sideBySide;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.viewMode = function (viewMode) {
|
|
if (arguments.length === 0) {
|
|
return options.viewMode;
|
|
}
|
|
|
|
if (typeof viewMode !== 'string') {
|
|
throw new TypeError('viewMode() expects a string parameter');
|
|
}
|
|
|
|
if (viewModes.indexOf(viewMode) === -1) {
|
|
throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
|
|
}
|
|
|
|
options.viewMode = viewMode;
|
|
currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber);
|
|
|
|
showMode();
|
|
return picker;
|
|
};
|
|
|
|
picker.toolbarPlacement = function (toolbarPlacement) {
|
|
if (arguments.length === 0) {
|
|
return options.toolbarPlacement;
|
|
}
|
|
|
|
if (typeof toolbarPlacement !== 'string') {
|
|
throw new TypeError('toolbarPlacement() expects a string parameter');
|
|
}
|
|
if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
|
|
throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
|
|
}
|
|
options.toolbarPlacement = toolbarPlacement;
|
|
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.widgetPositioning = function (widgetPositioning) {
|
|
if (arguments.length === 0) {
|
|
return $.extend({}, options.widgetPositioning);
|
|
}
|
|
|
|
if (({}).toString.call(widgetPositioning) !== '[object Object]') {
|
|
throw new TypeError('widgetPositioning() expects an object variable');
|
|
}
|
|
if (widgetPositioning.horizontal) {
|
|
if (typeof widgetPositioning.horizontal !== 'string') {
|
|
throw new TypeError('widgetPositioning() horizontal variable must be a string');
|
|
}
|
|
widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
|
|
if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
|
|
throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
|
|
}
|
|
options.widgetPositioning.horizontal = widgetPositioning.horizontal;
|
|
}
|
|
if (widgetPositioning.vertical) {
|
|
if (typeof widgetPositioning.vertical !== 'string') {
|
|
throw new TypeError('widgetPositioning() vertical variable must be a string');
|
|
}
|
|
widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
|
|
if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
|
|
throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
|
|
}
|
|
options.widgetPositioning.vertical = widgetPositioning.vertical;
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.calendarWeeks = function (calendarWeeks) {
|
|
if (arguments.length === 0) {
|
|
return options.calendarWeeks;
|
|
}
|
|
|
|
if (typeof calendarWeeks !== 'boolean') {
|
|
throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
|
|
}
|
|
|
|
options.calendarWeeks = calendarWeeks;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.showTodayButton = function (showTodayButton) {
|
|
if (arguments.length === 0) {
|
|
return options.showTodayButton;
|
|
}
|
|
|
|
if (typeof showTodayButton !== 'boolean') {
|
|
throw new TypeError('showTodayButton() expects a boolean parameter');
|
|
}
|
|
|
|
options.showTodayButton = showTodayButton;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.showClear = function (showClear) {
|
|
if (arguments.length === 0) {
|
|
return options.showClear;
|
|
}
|
|
|
|
if (typeof showClear !== 'boolean') {
|
|
throw new TypeError('showClear() expects a boolean parameter');
|
|
}
|
|
|
|
options.showClear = showClear;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.widgetParent = function (widgetParent) {
|
|
if (arguments.length === 0) {
|
|
return options.widgetParent;
|
|
}
|
|
|
|
if (typeof widgetParent === 'string') {
|
|
widgetParent = $(widgetParent);
|
|
}
|
|
|
|
if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) {
|
|
throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
|
|
}
|
|
|
|
options.widgetParent = widgetParent;
|
|
if (widget) {
|
|
hide();
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
picker.keepOpen = function (keepOpen) {
|
|
if (arguments.length === 0) {
|
|
return options.keepOpen;
|
|
}
|
|
|
|
if (typeof keepOpen !== 'boolean') {
|
|
throw new TypeError('keepOpen() expects a boolean parameter');
|
|
}
|
|
|
|
options.keepOpen = keepOpen;
|
|
return picker;
|
|
};
|
|
|
|
picker.focusOnShow = function (focusOnShow) {
|
|
if (arguments.length === 0) {
|
|
return options.focusOnShow;
|
|
}
|
|
|
|
if (typeof focusOnShow !== 'boolean') {
|
|
throw new TypeError('focusOnShow() expects a boolean parameter');
|
|
}
|
|
|
|
options.focusOnShow = focusOnShow;
|
|
return picker;
|
|
};
|
|
|
|
picker.inline = function (inline) {
|
|
if (arguments.length === 0) {
|
|
return options.inline;
|
|
}
|
|
|
|
if (typeof inline !== 'boolean') {
|
|
throw new TypeError('inline() expects a boolean parameter');
|
|
}
|
|
|
|
options.inline = inline;
|
|
return picker;
|
|
};
|
|
|
|
picker.clear = function () {
|
|
clear();
|
|
return picker;
|
|
};
|
|
|
|
picker.keyBinds = function (keyBinds) {
|
|
if (arguments.length === 0) {
|
|
return options.keyBinds;
|
|
}
|
|
|
|
options.keyBinds = keyBinds;
|
|
return picker;
|
|
};
|
|
|
|
picker.getMoment = function (d) {
|
|
return getMoment(d);
|
|
};
|
|
|
|
picker.debug = function (debug) {
|
|
if (typeof debug !== 'boolean') {
|
|
throw new TypeError('debug() expects a boolean parameter');
|
|
}
|
|
|
|
options.debug = debug;
|
|
return picker;
|
|
};
|
|
|
|
picker.allowInputToggle = function (allowInputToggle) {
|
|
if (arguments.length === 0) {
|
|
return options.allowInputToggle;
|
|
}
|
|
|
|
if (typeof allowInputToggle !== 'boolean') {
|
|
throw new TypeError('allowInputToggle() expects a boolean parameter');
|
|
}
|
|
|
|
options.allowInputToggle = allowInputToggle;
|
|
return picker;
|
|
};
|
|
|
|
picker.showClose = function (showClose) {
|
|
if (arguments.length === 0) {
|
|
return options.showClose;
|
|
}
|
|
|
|
if (typeof showClose !== 'boolean') {
|
|
throw new TypeError('showClose() expects a boolean parameter');
|
|
}
|
|
|
|
options.showClose = showClose;
|
|
return picker;
|
|
};
|
|
|
|
picker.keepInvalid = function (keepInvalid) {
|
|
if (arguments.length === 0) {
|
|
return options.keepInvalid;
|
|
}
|
|
|
|
if (typeof keepInvalid !== 'boolean') {
|
|
throw new TypeError('keepInvalid() expects a boolean parameter');
|
|
}
|
|
options.keepInvalid = keepInvalid;
|
|
return picker;
|
|
};
|
|
|
|
picker.datepickerInput = function (datepickerInput) {
|
|
if (arguments.length === 0) {
|
|
return options.datepickerInput;
|
|
}
|
|
|
|
if (typeof datepickerInput !== 'string') {
|
|
throw new TypeError('datepickerInput() expects a string parameter');
|
|
}
|
|
|
|
options.datepickerInput = datepickerInput;
|
|
return picker;
|
|
};
|
|
|
|
picker.parseInputDate = function (parseInputDate) {
|
|
if (arguments.length === 0) {
|
|
return options.parseInputDate;
|
|
}
|
|
|
|
if (typeof parseInputDate !== 'function') {
|
|
throw new TypeError('parseInputDate() sholud be as function');
|
|
}
|
|
|
|
options.parseInputDate = parseInputDate;
|
|
|
|
return picker;
|
|
};
|
|
|
|
picker.disabledTimeIntervals = function (disabledTimeIntervals) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals">
|
|
///<summary>Returns an array with the currently set disabled dates on the component.</summary>
|
|
///<returns type="array">options.disabledTimeIntervals</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
|
|
///options.enabledDates if such exist.</summary>
|
|
///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals);
|
|
}
|
|
|
|
if (!disabledTimeIntervals) {
|
|
options.disabledTimeIntervals = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(disabledTimeIntervals instanceof Array)) {
|
|
throw new TypeError('disabledTimeIntervals() expects an array parameter');
|
|
}
|
|
options.disabledTimeIntervals = disabledTimeIntervals;
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.disabledHours = function (hours) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.disabledHours">
|
|
///<summary>Returns an array with the currently set disabled hours on the component.</summary>
|
|
///<returns type="array">options.disabledHours</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
|
|
///options.enabledHours if such exist.</summary>
|
|
///<param name="hours" locid="$.fn.datetimepicker.disabledHours_p:hours">Takes an [ int ] of values and disallows the user to select only from those hours.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours);
|
|
}
|
|
|
|
if (!hours) {
|
|
options.disabledHours = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(hours instanceof Array)) {
|
|
throw new TypeError('disabledHours() expects an array parameter');
|
|
}
|
|
options.disabledHours = indexGivenHours(hours);
|
|
options.enabledHours = false;
|
|
if (options.useCurrent && !options.keepInvalid) {
|
|
var tries = 0;
|
|
while (!isValid(date, 'h')) {
|
|
date.add(1, 'h');
|
|
if (tries === 24) {
|
|
throw 'Tried 24 times to find a valid date';
|
|
}
|
|
tries++;
|
|
}
|
|
setValue(date);
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
|
|
picker.enabledHours = function (hours) {
|
|
///<signature helpKeyword="$.fn.datetimepicker.enabledHours">
|
|
///<summary>Returns an array with the currently set enabled hours on the component.</summary>
|
|
///<returns type="array">options.enabledHours</returns>
|
|
///</signature>
|
|
///<signature>
|
|
///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist.</summary>
|
|
///<param name="hours" locid="$.fn.datetimepicker.enabledHours_p:hours">Takes an [ int ] of values and allows the user to select only from those hours.</param>
|
|
///</signature>
|
|
if (arguments.length === 0) {
|
|
return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours);
|
|
}
|
|
|
|
if (!hours) {
|
|
options.enabledHours = false;
|
|
update();
|
|
return picker;
|
|
}
|
|
if (!(hours instanceof Array)) {
|
|
throw new TypeError('enabledHours() expects an array parameter');
|
|
}
|
|
options.enabledHours = indexGivenHours(hours);
|
|
options.disabledHours = false;
|
|
if (options.useCurrent && !options.keepInvalid) {
|
|
var tries = 0;
|
|
while (!isValid(date, 'h')) {
|
|
date.add(1, 'h');
|
|
if (tries === 24) {
|
|
throw 'Tried 24 times to find a valid date';
|
|
}
|
|
tries++;
|
|
}
|
|
setValue(date);
|
|
}
|
|
update();
|
|
return picker;
|
|
};
|
|
/**
|
|
* Returns the component's model current viewDate, a moment object or null if not set. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.
|
|
* @param {Takes string, viewDate, moment, null parameter.} newDate
|
|
* @returns {viewDate.clone()}
|
|
*/
|
|
picker.viewDate = function (newDate) {
|
|
if (arguments.length === 0) {
|
|
return viewDate.clone();
|
|
}
|
|
|
|
if (!newDate) {
|
|
viewDate = date.clone();
|
|
return picker;
|
|
}
|
|
|
|
if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
|
|
throw new TypeError('viewDate() parameter must be one of [string, moment or Date]');
|
|
}
|
|
|
|
viewDate = parseInputDate(newDate);
|
|
viewUpdate();
|
|
return picker;
|
|
};
|
|
|
|
// initializing element and component attributes
|
|
if (element.is('input')) {
|
|
input = element;
|
|
} else {
|
|
input = element.find(options.datepickerInput);
|
|
if (input.length === 0) {
|
|
input = element.find('input');
|
|
} else if (!input.is('input')) {
|
|
throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
|
|
}
|
|
}
|
|
|
|
if (element.hasClass('input-group')) {
|
|
// in case there is more then one 'input-group-addon' Issue #48
|
|
if (element.find('.datepickerbutton').length === 0) {
|
|
component = element.find('.input-group-addon');
|
|
} else {
|
|
component = element.find('.datepickerbutton');
|
|
}
|
|
}
|
|
|
|
if (!options.inline && !input.is('input')) {
|
|
throw new Error('Could not initialize DateTimePicker without an input element');
|
|
}
|
|
|
|
// Set defaults for date here now instead of in var declaration
|
|
date = getMoment();
|
|
viewDate = date.clone();
|
|
|
|
$.extend(true, options, dataToOptions());
|
|
|
|
picker.options(options);
|
|
|
|
initFormatting();
|
|
|
|
attachDatePickerElementEvents();
|
|
|
|
if (input.prop('disabled')) {
|
|
picker.disable();
|
|
}
|
|
if (input.is('input') && input.val().trim().length !== 0) {
|
|
setValue(parseInputDate(input.val().trim()));
|
|
}
|
|
else if (options.defaultDate && input.attr('placeholder') === undefined) {
|
|
setValue(options.defaultDate);
|
|
}
|
|
if (options.inline) {
|
|
show();
|
|
}
|
|
return picker;
|
|
};
|
|
|
|
/********************************************************************************
|
|
*
|
|
* jQuery plugin constructor and defaults object
|
|
*
|
|
********************************************************************************/
|
|
|
|
/**
|
|
* See (http://jquery.com/).
|
|
* @name jQuery
|
|
* @class
|
|
* See the jQuery Library (http://jquery.com/) for full details. This just
|
|
* documents the function and classes that are added to jQuery by this plug-in.
|
|
*/
|
|
/**
|
|
* See (http://jquery.com/)
|
|
* @name fn
|
|
* @class
|
|
* See the jQuery Library (http://jquery.com/) for full details. This just
|
|
* documents the function and classes that are added to jQuery by this plug-in.
|
|
* @memberOf jQuery
|
|
*/
|
|
/**
|
|
* Show comments
|
|
* @class datetimepicker
|
|
* @memberOf jQuery.fn
|
|
*/
|
|
$.fn.datetimepicker = function (options) {
|
|
options = options || {};
|
|
|
|
var args = Array.prototype.slice.call(arguments, 1),
|
|
isInstance = true,
|
|
thisMethods = ['destroy', 'hide', 'show', 'toggle'],
|
|
returnValue;
|
|
|
|
if (typeof options === 'object') {
|
|
return this.each(function () {
|
|
var $this = $(this),
|
|
_options;
|
|
if (!$this.data('DateTimePicker')) {
|
|
// create a private copy of the defaults object
|
|
_options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
|
|
$this.data('DateTimePicker', dateTimePicker($this, _options));
|
|
}
|
|
});
|
|
} else if (typeof options === 'string') {
|
|
this.each(function () {
|
|
var $this = $(this),
|
|
instance = $this.data('DateTimePicker');
|
|
if (!instance) {
|
|
throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker');
|
|
}
|
|
|
|
returnValue = instance[options].apply(instance, args);
|
|
isInstance = returnValue === instance;
|
|
});
|
|
|
|
if (isInstance || $.inArray(options, thisMethods) > -1) {
|
|
return this;
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
throw new TypeError('Invalid arguments for DateTimePicker: ' + options);
|
|
};
|
|
|
|
$.fn.datetimepicker.defaults = {
|
|
timeZone: '',
|
|
format: false,
|
|
dayViewHeaderFormat: 'MMMM YYYY',
|
|
extraFormats: false,
|
|
stepping: 1,
|
|
minDate: false,
|
|
maxDate: false,
|
|
useCurrent: true,
|
|
collapse: true,
|
|
locale: moment.locale(),
|
|
defaultDate: false,
|
|
disabledDates: false,
|
|
enabledDates: false,
|
|
icons: {
|
|
time: 'glyphicon glyphicon-time',
|
|
date: 'glyphicon glyphicon-calendar',
|
|
up: 'glyphicon glyphicon-chevron-up',
|
|
down: 'glyphicon glyphicon-chevron-down',
|
|
previous: 'glyphicon glyphicon-chevron-left',
|
|
next: 'glyphicon glyphicon-chevron-right',
|
|
today: 'glyphicon glyphicon-screenshot',
|
|
clear: 'glyphicon glyphicon-trash',
|
|
close: 'glyphicon glyphicon-remove'
|
|
},
|
|
tooltips: {
|
|
today: 'Go to today',
|
|
clear: 'Clear selection',
|
|
close: 'Close the picker',
|
|
selectMonth: 'Select Month',
|
|
prevMonth: 'Previous Month',
|
|
nextMonth: 'Next Month',
|
|
selectYear: 'Select Year',
|
|
prevYear: 'Previous Year',
|
|
nextYear: 'Next Year',
|
|
selectDecade: 'Select Decade',
|
|
prevDecade: 'Previous Decade',
|
|
nextDecade: 'Next Decade',
|
|
prevCentury: 'Previous Century',
|
|
nextCentury: 'Next Century',
|
|
pickHour: 'Pick Hour',
|
|
incrementHour: 'Increment Hour',
|
|
decrementHour: 'Decrement Hour',
|
|
pickMinute: 'Pick Minute',
|
|
incrementMinute: 'Increment Minute',
|
|
decrementMinute: 'Decrement Minute',
|
|
pickSecond: 'Pick Second',
|
|
incrementSecond: 'Increment Second',
|
|
decrementSecond: 'Decrement Second',
|
|
togglePeriod: 'Toggle Period',
|
|
selectTime: 'Select Time'
|
|
},
|
|
useStrict: false,
|
|
sideBySide: false,
|
|
daysOfWeekDisabled: false,
|
|
calendarWeeks: false,
|
|
viewMode: 'days',
|
|
toolbarPlacement: 'default',
|
|
showTodayButton: false,
|
|
showClear: false,
|
|
showClose: false,
|
|
widgetPositioning: {
|
|
horizontal: 'auto',
|
|
vertical: 'auto'
|
|
},
|
|
widgetParent: null,
|
|
ignoreReadonly: false,
|
|
keepOpen: false,
|
|
focusOnShow: true,
|
|
inline: false,
|
|
keepInvalid: false,
|
|
datepickerInput: '.datepickerinput',
|
|
keyBinds: {
|
|
up: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(7, 'd'));
|
|
} else {
|
|
this.date(d.clone().add(this.stepping(), 'm'));
|
|
}
|
|
},
|
|
down: function (widget) {
|
|
if (!widget) {
|
|
this.show();
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(7, 'd'));
|
|
} else {
|
|
this.date(d.clone().subtract(this.stepping(), 'm'));
|
|
}
|
|
},
|
|
'control up': function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(1, 'y'));
|
|
} else {
|
|
this.date(d.clone().add(1, 'h'));
|
|
}
|
|
},
|
|
'control down': function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(1, 'y'));
|
|
} else {
|
|
this.date(d.clone().subtract(1, 'h'));
|
|
}
|
|
},
|
|
left: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(1, 'd'));
|
|
}
|
|
},
|
|
right: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(1, 'd'));
|
|
}
|
|
},
|
|
pageUp: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().subtract(1, 'M'));
|
|
}
|
|
},
|
|
pageDown: function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
var d = this.date() || this.getMoment();
|
|
if (widget.find('.datepicker').is(':visible')) {
|
|
this.date(d.clone().add(1, 'M'));
|
|
}
|
|
},
|
|
enter: function () {
|
|
this.hide();
|
|
},
|
|
escape: function () {
|
|
this.hide();
|
|
},
|
|
//tab: function (widget) { //this break the flow of the form. disabling for now
|
|
// var toggle = widget.find('.picker-switch a[data-action="togglePicker"]');
|
|
// if(toggle.length > 0) toggle.click();
|
|
//},
|
|
'control space': function (widget) {
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
if (widget.find('.timepicker').is(':visible')) {
|
|
widget.find('.btn[data-action="togglePeriod"]').click();
|
|
}
|
|
},
|
|
t: function () {
|
|
this.date(this.getMoment());
|
|
},
|
|
'delete': function () {
|
|
this.clear();
|
|
}
|
|
},
|
|
debug: false,
|
|
allowInputToggle: false,
|
|
disabledTimeIntervals: false,
|
|
disabledHours: false,
|
|
enabledHours: false,
|
|
viewDate: false
|
|
};
|
|
|
|
return $.fn.datetimepicker;
|
|
}));
|
|
|
|
/*!
|
|
* Copyright 2012, Chris Wanstrath
|
|
* Released under the MIT License
|
|
* https://github.com/defunkt/jquery-pjax
|
|
*/
|
|
|
|
(function($){
|
|
|
|
// When called on a container with a selector, fetches the href with
|
|
// ajax into the container or with the data-pjax attribute on the link
|
|
// itself.
|
|
//
|
|
// Tries to make sure the back button and ctrl+click work the way
|
|
// you'd expect.
|
|
//
|
|
// Exported as $.fn.pjax
|
|
//
|
|
// Accepts a jQuery ajax options object that may include these
|
|
// pjax specific options:
|
|
//
|
|
//
|
|
// container - String selector for the element where to place the response body.
|
|
// push - Whether to pushState the URL. Defaults to true (of course).
|
|
// replace - Want to use replaceState instead? That's cool.
|
|
//
|
|
// For convenience the second parameter can be either the container or
|
|
// the options object.
|
|
//
|
|
// Returns the jQuery object
|
|
function fnPjax(selector, container, options) {
|
|
options = optionsFor(container, options)
|
|
return this.on('click.pjax', selector, function(event) {
|
|
var opts = options
|
|
if (!opts.container) {
|
|
opts = $.extend({}, options)
|
|
opts.container = $(this).attr('data-pjax')
|
|
}
|
|
handleClick(event, opts)
|
|
})
|
|
}
|
|
|
|
// Public: pjax on click handler
|
|
//
|
|
// Exported as $.pjax.click.
|
|
//
|
|
// event - "click" jQuery.Event
|
|
// options - pjax options
|
|
//
|
|
// Examples
|
|
//
|
|
// $(document).on('click', 'a', $.pjax.click)
|
|
// // is the same as
|
|
// $(document).pjax('a')
|
|
//
|
|
// Returns nothing.
|
|
function handleClick(event, container, options) {
|
|
options = optionsFor(container, options)
|
|
|
|
var link = event.currentTarget
|
|
var $link = $(link)
|
|
|
|
if (link.tagName.toUpperCase() !== 'A')
|
|
throw "$.fn.pjax or $.pjax.click requires an anchor element"
|
|
|
|
// Middle click, cmd click, and ctrl click should open
|
|
// links in a new tab as normal.
|
|
if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
|
|
return
|
|
|
|
// Ignore cross origin links
|
|
if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
|
|
return
|
|
|
|
// Ignore case when a hash is being tacked on the current URL
|
|
if ( link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location) )
|
|
return
|
|
|
|
// Ignore event with default prevented
|
|
if (event.isDefaultPrevented())
|
|
return
|
|
|
|
var defaults = {
|
|
url: link.href,
|
|
container: $link.attr('data-pjax'),
|
|
target: link
|
|
}
|
|
|
|
var opts = $.extend({}, defaults, options)
|
|
var clickEvent = $.Event('pjax:click')
|
|
$link.trigger(clickEvent, [opts])
|
|
|
|
if (!clickEvent.isDefaultPrevented()) {
|
|
pjax(opts)
|
|
event.preventDefault()
|
|
$link.trigger('pjax:clicked', [opts])
|
|
}
|
|
}
|
|
|
|
// Public: pjax on form submit handler
|
|
//
|
|
// Exported as $.pjax.submit
|
|
//
|
|
// event - "click" jQuery.Event
|
|
// options - pjax options
|
|
//
|
|
// Examples
|
|
//
|
|
// $(document).on('submit', 'form', function(event) {
|
|
// $.pjax.submit(event, '[data-pjax-container]')
|
|
// })
|
|
//
|
|
// Returns nothing.
|
|
function handleSubmit(event, container, options) {
|
|
options = optionsFor(container, options)
|
|
|
|
var form = event.currentTarget
|
|
var $form = $(form)
|
|
|
|
if (form.tagName.toUpperCase() !== 'FORM')
|
|
throw "$.pjax.submit requires a form element"
|
|
|
|
var defaults = {
|
|
type: ($form.attr('method') || 'GET').toUpperCase(),
|
|
url: $form.attr('action'),
|
|
container: $form.attr('data-pjax'),
|
|
target: form
|
|
}
|
|
|
|
if (defaults.type !== 'GET' && window.FormData !== undefined) {
|
|
defaults.data = new FormData(form)
|
|
defaults.processData = false
|
|
defaults.contentType = false
|
|
} else {
|
|
// Can't handle file uploads, exit
|
|
if ($form.find(':file').length) {
|
|
return
|
|
}
|
|
|
|
// Fallback to manually serializing the fields
|
|
defaults.data = $form.serializeArray()
|
|
}
|
|
|
|
pjax($.extend({}, defaults, options))
|
|
|
|
event.preventDefault()
|
|
}
|
|
|
|
// Loads a URL with ajax, puts the response body inside a container,
|
|
// then pushState()'s the loaded URL.
|
|
//
|
|
// Works just like $.ajax in that it accepts a jQuery ajax
|
|
// settings object (with keys like url, type, data, etc).
|
|
//
|
|
// Accepts these extra keys:
|
|
//
|
|
// container - String selector for where to stick the response body.
|
|
// push - Whether to pushState the URL. Defaults to true (of course).
|
|
// replace - Want to use replaceState instead? That's cool.
|
|
//
|
|
// Use it just like $.ajax:
|
|
//
|
|
// var xhr = $.pjax({ url: this.href, container: '#main' })
|
|
// console.log( xhr.readyState )
|
|
//
|
|
// Returns whatever $.ajax returns.
|
|
function pjax(options) {
|
|
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
|
|
|
|
if ($.isFunction(options.url)) {
|
|
options.url = options.url()
|
|
}
|
|
|
|
var hash = parseURL(options.url).hash
|
|
|
|
var containerType = $.type(options.container)
|
|
if (containerType !== 'string') {
|
|
throw "expected string value for 'container' option; got " + containerType
|
|
}
|
|
var context = options.context = $(options.container)
|
|
if (!context.length) {
|
|
throw "the container selector '" + options.container + "' did not match anything"
|
|
}
|
|
|
|
// We want the browser to maintain two separate internal caches: one
|
|
// for pjax'd partial page loads and one for normal page loads.
|
|
// Without adding this secret parameter, some browsers will often
|
|
// confuse the two.
|
|
if (!options.data) options.data = {}
|
|
if ($.isArray(options.data)) {
|
|
options.data.push({name: '_pjax', value: options.container})
|
|
} else {
|
|
options.data._pjax = options.container
|
|
}
|
|
|
|
function fire(type, args, props) {
|
|
if (!props) props = {}
|
|
props.relatedTarget = options.target
|
|
var event = $.Event(type, props)
|
|
context.trigger(event, args)
|
|
return !event.isDefaultPrevented()
|
|
}
|
|
|
|
var timeoutTimer
|
|
|
|
options.beforeSend = function(xhr, settings) {
|
|
// No timeout for non-GET requests
|
|
// Its not safe to request the resource again with a fallback method.
|
|
if (settings.type !== 'GET') {
|
|
settings.timeout = 0
|
|
}
|
|
|
|
xhr.setRequestHeader('X-PJAX', 'true')
|
|
xhr.setRequestHeader('X-PJAX-Container', options.container)
|
|
|
|
if (!fire('pjax:beforeSend', [xhr, settings]))
|
|
return false
|
|
|
|
if (settings.timeout > 0) {
|
|
timeoutTimer = setTimeout(function() {
|
|
if (fire('pjax:timeout', [xhr, options]))
|
|
xhr.abort('timeout')
|
|
}, settings.timeout)
|
|
|
|
// Clear timeout setting so jquerys internal timeout isn't invoked
|
|
settings.timeout = 0
|
|
}
|
|
|
|
var url = parseURL(settings.url)
|
|
if (hash) url.hash = hash
|
|
options.requestUrl = stripInternalParams(url)
|
|
}
|
|
|
|
options.complete = function(xhr, textStatus) {
|
|
if (timeoutTimer)
|
|
clearTimeout(timeoutTimer)
|
|
|
|
fire('pjax:complete', [xhr, textStatus, options])
|
|
|
|
fire('pjax:end', [xhr, options])
|
|
}
|
|
|
|
options.error = function(xhr, textStatus, errorThrown) {
|
|
var container = extractContainer("", xhr, options)
|
|
|
|
var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
|
|
if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
|
|
locationReplace(container.url)
|
|
}
|
|
}
|
|
|
|
options.success = function(data, status, xhr) {
|
|
var previousState = pjax.state
|
|
|
|
// If $.pjax.defaults.version is a function, invoke it first.
|
|
// Otherwise it can be a static string.
|
|
var currentVersion = typeof $.pjax.defaults.version === 'function' ?
|
|
$.pjax.defaults.version() :
|
|
$.pjax.defaults.version
|
|
|
|
var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
|
|
|
|
var container = extractContainer(data, xhr, options)
|
|
|
|
var url = parseURL(container.url)
|
|
if (hash) {
|
|
url.hash = hash
|
|
container.url = url.href
|
|
}
|
|
|
|
// If there is a layout version mismatch, hard load the new url
|
|
if (currentVersion && latestVersion && currentVersion !== latestVersion) {
|
|
locationReplace(container.url)
|
|
return
|
|
}
|
|
|
|
// If the new response is missing a body, hard load the page
|
|
if (!container.contents) {
|
|
locationReplace(container.url)
|
|
return
|
|
}
|
|
|
|
pjax.state = {
|
|
id: options.id || uniqueId(),
|
|
url: container.url,
|
|
title: container.title,
|
|
container: options.container,
|
|
fragment: options.fragment,
|
|
timeout: options.timeout
|
|
}
|
|
|
|
if (options.push || options.replace) {
|
|
window.history.replaceState(pjax.state, container.title, container.url)
|
|
}
|
|
|
|
// Only blur the focus if the focused element is within the container.
|
|
var blurFocus = $.contains(context, document.activeElement)
|
|
|
|
// Clear out any focused controls before inserting new page contents.
|
|
if (blurFocus) {
|
|
try {
|
|
document.activeElement.blur()
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
if (container.title) document.title = container.title
|
|
|
|
fire('pjax:beforeReplace', [container.contents, options], {
|
|
state: pjax.state,
|
|
previousState: previousState
|
|
})
|
|
context.html(container.contents)
|
|
|
|
// FF bug: Won't autofocus fields that are inserted via JS.
|
|
// This behavior is incorrect. So if theres no current focus, autofocus
|
|
// the last field.
|
|
//
|
|
// http://www.w3.org/html/wg/drafts/html/master/forms.html
|
|
var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
|
|
if (autofocusEl && document.activeElement !== autofocusEl) {
|
|
autofocusEl.focus()
|
|
}
|
|
|
|
executeScriptTags(container.scripts)
|
|
|
|
var scrollTo = options.scrollTo
|
|
|
|
// Ensure browser scrolls to the element referenced by the URL anchor
|
|
if (hash) {
|
|
var name = decodeURIComponent(hash.slice(1))
|
|
var target = document.getElementById(name) || document.getElementsByName(name)[0]
|
|
if (target) scrollTo = $(target).offset().top
|
|
}
|
|
|
|
if (typeof scrollTo == 'number') $(window).scrollTop(scrollTo)
|
|
|
|
fire('pjax:success', [data, status, xhr, options])
|
|
}
|
|
|
|
|
|
// Initialize pjax.state for the initial page load. Assume we're
|
|
// using the container and options of the link we're loading for the
|
|
// back button to the initial page. This ensures good back button
|
|
// behavior.
|
|
if (!pjax.state) {
|
|
pjax.state = {
|
|
id: uniqueId(),
|
|
url: window.location.href,
|
|
title: document.title,
|
|
container: options.container,
|
|
fragment: options.fragment,
|
|
timeout: options.timeout
|
|
}
|
|
window.history.replaceState(pjax.state, document.title)
|
|
}
|
|
|
|
// Cancel the current request if we're already pjaxing
|
|
abortXHR(pjax.xhr)
|
|
|
|
pjax.options = options
|
|
var xhr = pjax.xhr = $.ajax(options)
|
|
|
|
if (xhr.readyState > 0) {
|
|
if (options.push && !options.replace) {
|
|
// Cache current container element before replacing it
|
|
cachePush(pjax.state.id, [options.container, cloneContents(context)])
|
|
|
|
window.history.pushState(null, "", options.requestUrl)
|
|
}
|
|
|
|
fire('pjax:start', [xhr, options])
|
|
fire('pjax:send', [xhr, options])
|
|
}
|
|
|
|
return pjax.xhr
|
|
}
|
|
|
|
// Public: Reload current page with pjax.
|
|
//
|
|
// Returns whatever $.pjax returns.
|
|
function pjaxReload(container, options) {
|
|
var defaults = {
|
|
url: window.location.href,
|
|
push: false,
|
|
replace: true,
|
|
scrollTo: false
|
|
}
|
|
|
|
return pjax($.extend(defaults, optionsFor(container, options)))
|
|
}
|
|
|
|
// Internal: Hard replace current state with url.
|
|
//
|
|
// Work for around WebKit
|
|
// https://bugs.webkit.org/show_bug.cgi?id=93506
|
|
//
|
|
// Returns nothing.
|
|
function locationReplace(url) {
|
|
window.history.replaceState(null, "", pjax.state.url)
|
|
window.location.replace(url)
|
|
}
|
|
|
|
|
|
var initialPop = true
|
|
var initialURL = window.location.href
|
|
var initialState = window.history.state
|
|
|
|
// Initialize $.pjax.state if possible
|
|
// Happens when reloading a page and coming forward from a different
|
|
// session history.
|
|
if (initialState && initialState.container) {
|
|
pjax.state = initialState
|
|
}
|
|
|
|
// Non-webkit browsers don't fire an initial popstate event
|
|
if ('state' in window.history) {
|
|
initialPop = false
|
|
}
|
|
|
|
// popstate handler takes care of the back and forward buttons
|
|
//
|
|
// You probably shouldn't use pjax on pages with other pushState
|
|
// stuff yet.
|
|
function onPjaxPopstate(event) {
|
|
|
|
// Hitting back or forward should override any pending PJAX request.
|
|
if (!initialPop) {
|
|
abortXHR(pjax.xhr)
|
|
}
|
|
|
|
var previousState = pjax.state
|
|
var state = event.state
|
|
var direction
|
|
|
|
if (state && state.container) {
|
|
// When coming forward from a separate history session, will get an
|
|
// initial pop with a state we are already at. Skip reloading the current
|
|
// page.
|
|
if (initialPop && initialURL == state.url) return
|
|
|
|
if (previousState) {
|
|
// If popping back to the same state, just skip.
|
|
// Could be clicking back from hashchange rather than a pushState.
|
|
if (previousState.id === state.id) return
|
|
|
|
// Since state IDs always increase, we can deduce the navigation direction
|
|
direction = previousState.id < state.id ? 'forward' : 'back'
|
|
}
|
|
|
|
var cache = cacheMapping[state.id] || []
|
|
var containerSelector = cache[0] || state.container
|
|
var container = $(containerSelector), contents = cache[1]
|
|
|
|
if (container.length) {
|
|
if (previousState) {
|
|
// Cache current container before replacement and inform the
|
|
// cache which direction the history shifted.
|
|
cachePop(direction, previousState.id, [containerSelector, cloneContents(container)])
|
|
}
|
|
|
|
var popstateEvent = $.Event('pjax:popstate', {
|
|
state: state,
|
|
direction: direction
|
|
})
|
|
container.trigger(popstateEvent)
|
|
|
|
var options = {
|
|
id: state.id,
|
|
url: state.url,
|
|
container: containerSelector,
|
|
push: false,
|
|
fragment: state.fragment,
|
|
timeout: state.timeout,
|
|
scrollTo: false
|
|
}
|
|
|
|
if (contents) {
|
|
container.trigger('pjax:start', [null, options])
|
|
|
|
pjax.state = state
|
|
if (state.title) document.title = state.title
|
|
var beforeReplaceEvent = $.Event('pjax:beforeReplace', {
|
|
state: state,
|
|
previousState: previousState
|
|
})
|
|
container.trigger(beforeReplaceEvent, [contents, options])
|
|
container.html(contents)
|
|
|
|
container.trigger('pjax:end', [null, options])
|
|
} else {
|
|
pjax(options)
|
|
}
|
|
|
|
// Force reflow/relayout before the browser tries to restore the
|
|
// scroll position.
|
|
container[0].offsetHeight // eslint-disable-line no-unused-expressions
|
|
} else {
|
|
locationReplace(location.href)
|
|
}
|
|
}
|
|
initialPop = false
|
|
}
|
|
|
|
// Fallback version of main pjax function for browsers that don't
|
|
// support pushState.
|
|
//
|
|
// Returns nothing since it retriggers a hard form submission.
|
|
function fallbackPjax(options) {
|
|
var url = $.isFunction(options.url) ? options.url() : options.url,
|
|
method = options.type ? options.type.toUpperCase() : 'GET'
|
|
|
|
var form = $('<form>', {
|
|
method: method === 'GET' ? 'GET' : 'POST',
|
|
action: url,
|
|
style: 'display:none'
|
|
})
|
|
|
|
if (method !== 'GET' && method !== 'POST') {
|
|
form.append($('<input>', {
|
|
type: 'hidden',
|
|
name: '_method',
|
|
value: method.toLowerCase()
|
|
}))
|
|
}
|
|
|
|
var data = options.data
|
|
if (typeof data === 'string') {
|
|
$.each(data.split('&'), function(index, value) {
|
|
var pair = value.split('=')
|
|
form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
|
|
})
|
|
} else if ($.isArray(data)) {
|
|
$.each(data, function(index, value) {
|
|
form.append($('<input>', {type: 'hidden', name: value.name, value: value.value}))
|
|
})
|
|
} else if (typeof data === 'object') {
|
|
var key
|
|
for (key in data)
|
|
form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
|
|
}
|
|
|
|
$(document.body).append(form)
|
|
form.submit()
|
|
}
|
|
|
|
// Internal: Abort an XmlHttpRequest if it hasn't been completed,
|
|
// also removing its event handlers.
|
|
function abortXHR(xhr) {
|
|
if ( xhr && xhr.readyState < 4) {
|
|
xhr.onreadystatechange = $.noop
|
|
xhr.abort()
|
|
}
|
|
}
|
|
|
|
// Internal: Generate unique id for state object.
|
|
//
|
|
// Use a timestamp instead of a counter since ids should still be
|
|
// unique across page loads.
|
|
//
|
|
// Returns Number.
|
|
function uniqueId() {
|
|
return (new Date).getTime()
|
|
}
|
|
|
|
function cloneContents(container) {
|
|
var cloned = container.clone()
|
|
// Unmark script tags as already being eval'd so they can get executed again
|
|
// when restored from cache. HAXX: Uses jQuery internal method.
|
|
cloned.find('script').each(function(){
|
|
if (!this.src) $._data(this, 'globalEval', false)
|
|
})
|
|
return cloned.contents()
|
|
}
|
|
|
|
// Internal: Strip internal query params from parsed URL.
|
|
//
|
|
// Returns sanitized url.href String.
|
|
function stripInternalParams(url) {
|
|
url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '')
|
|
return url.href.replace(/\?($|#)/, '$1')
|
|
}
|
|
|
|
// Internal: Parse URL components and returns a Locationish object.
|
|
//
|
|
// url - String URL
|
|
//
|
|
// Returns HTMLAnchorElement that acts like Location.
|
|
function parseURL(url) {
|
|
var a = document.createElement('a')
|
|
a.href = url
|
|
return a
|
|
}
|
|
|
|
// Internal: Return the `href` component of given URL object with the hash
|
|
// portion removed.
|
|
//
|
|
// location - Location or HTMLAnchorElement
|
|
//
|
|
// Returns String
|
|
function stripHash(location) {
|
|
return location.href.replace(/#.*/, '')
|
|
}
|
|
|
|
// Internal: Build options Object for arguments.
|
|
//
|
|
// For convenience the first parameter can be either the container or
|
|
// the options object.
|
|
//
|
|
// Examples
|
|
//
|
|
// optionsFor('#container')
|
|
// // => {container: '#container'}
|
|
//
|
|
// optionsFor('#container', {push: true})
|
|
// // => {container: '#container', push: true}
|
|
//
|
|
// optionsFor({container: '#container', push: true})
|
|
// // => {container: '#container', push: true}
|
|
//
|
|
// Returns options Object.
|
|
function optionsFor(container, options) {
|
|
if (container && options) {
|
|
options = $.extend({}, options)
|
|
options.container = container
|
|
return options
|
|
} else if ($.isPlainObject(container)) {
|
|
return container
|
|
} else {
|
|
return {container: container}
|
|
}
|
|
}
|
|
|
|
// Internal: Filter and find all elements matching the selector.
|
|
//
|
|
// Where $.fn.find only matches descendants, findAll will test all the
|
|
// top level elements in the jQuery object as well.
|
|
//
|
|
// elems - jQuery object of Elements
|
|
// selector - String selector to match
|
|
//
|
|
// Returns a jQuery object.
|
|
function findAll(elems, selector) {
|
|
return elems.filter(selector).add(elems.find(selector))
|
|
}
|
|
|
|
function parseHTML(html) {
|
|
return $.parseHTML(html, document, true)
|
|
}
|
|
|
|
// Internal: Extracts container and metadata from response.
|
|
//
|
|
// 1. Extracts X-PJAX-URL header if set
|
|
// 2. Extracts inline <title> tags
|
|
// 3. Builds response Element and extracts fragment if set
|
|
//
|
|
// data - String response data
|
|
// xhr - XHR response
|
|
// options - pjax options Object
|
|
//
|
|
// Returns an Object with url, title, and contents keys.
|
|
function extractContainer(data, xhr, options) {
|
|
var obj = {}, fullDocument = /<html/i.test(data)
|
|
|
|
// Prefer X-PJAX-URL header if it was set, otherwise fallback to
|
|
// using the original requested url.
|
|
var serverUrl = xhr.getResponseHeader('X-PJAX-URL')
|
|
obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl
|
|
|
|
var $head, $body
|
|
// Attempt to parse response html into elements
|
|
if (fullDocument) {
|
|
$body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
|
|
var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)
|
|
$head = head != null ? $(parseHTML(head[0])) : $body
|
|
} else {
|
|
$head = $body = $(parseHTML(data))
|
|
}
|
|
|
|
// If response data is empty, return fast
|
|
if ($body.length === 0)
|
|
return obj
|
|
|
|
// If there's a <title> tag in the header, use it as
|
|
// the page's title.
|
|
obj.title = findAll($head, 'title').last().text()
|
|
|
|
if (options.fragment) {
|
|
var $fragment = $body
|
|
// If they specified a fragment, look for it in the response
|
|
// and pull it out.
|
|
if (options.fragment !== 'body') {
|
|
$fragment = findAll($fragment, options.fragment).first()
|
|
}
|
|
|
|
if ($fragment.length) {
|
|
obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents()
|
|
|
|
// If there's no title, look for data-title and title attributes
|
|
// on the fragment
|
|
if (!obj.title)
|
|
obj.title = $fragment.attr('title') || $fragment.data('title')
|
|
}
|
|
|
|
} else if (!fullDocument) {
|
|
obj.contents = $body
|
|
}
|
|
|
|
// Clean up any <title> tags
|
|
if (obj.contents) {
|
|
// Remove any parent title elements
|
|
obj.contents = obj.contents.not(function() { return $(this).is('title') })
|
|
|
|
// Then scrub any titles from their descendants
|
|
obj.contents.find('title').remove()
|
|
|
|
// Gather all script[src] elements
|
|
obj.scripts = findAll(obj.contents, 'script[src]').remove()
|
|
obj.contents = obj.contents.not(obj.scripts)
|
|
}
|
|
|
|
// Trim any whitespace off the title
|
|
if (obj.title) obj.title = $.trim(obj.title)
|
|
|
|
return obj
|
|
}
|
|
|
|
// Load an execute scripts using standard script request.
|
|
//
|
|
// Avoids jQuery's traditional $.getScript which does a XHR request and
|
|
// globalEval.
|
|
//
|
|
// scripts - jQuery object of script Elements
|
|
//
|
|
// Returns nothing.
|
|
function executeScriptTags(scripts) {
|
|
if (!scripts) return
|
|
|
|
var existingScripts = $('script[src]')
|
|
|
|
scripts.each(function() {
|
|
var src = this.src
|
|
var matchedScripts = existingScripts.filter(function() {
|
|
return this.src === src
|
|
})
|
|
if (matchedScripts.length) return
|
|
|
|
var script = document.createElement('script')
|
|
var type = $(this).attr('type')
|
|
if (type) script.type = type
|
|
script.src = $(this).attr('src')
|
|
document.head.appendChild(script)
|
|
})
|
|
}
|
|
|
|
// Internal: History DOM caching class.
|
|
var cacheMapping = {}
|
|
var cacheForwardStack = []
|
|
var cacheBackStack = []
|
|
|
|
// Push previous state id and container contents into the history
|
|
// cache. Should be called in conjunction with `pushState` to save the
|
|
// previous container contents.
|
|
//
|
|
// id - State ID Number
|
|
// value - DOM Element to cache
|
|
//
|
|
// Returns nothing.
|
|
function cachePush(id, value) {
|
|
cacheMapping[id] = value
|
|
cacheBackStack.push(id)
|
|
|
|
// Remove all entries in forward history stack after pushing a new page.
|
|
trimCacheStack(cacheForwardStack, 0)
|
|
|
|
// Trim back history stack to max cache length.
|
|
trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength)
|
|
}
|
|
|
|
// Shifts cache from directional history cache. Should be
|
|
// called on `popstate` with the previous state id and container
|
|
// contents.
|
|
//
|
|
// direction - "forward" or "back" String
|
|
// id - State ID Number
|
|
// value - DOM Element to cache
|
|
//
|
|
// Returns nothing.
|
|
function cachePop(direction, id, value) {
|
|
var pushStack, popStack
|
|
cacheMapping[id] = value
|
|
|
|
if (direction === 'forward') {
|
|
pushStack = cacheBackStack
|
|
popStack = cacheForwardStack
|
|
} else {
|
|
pushStack = cacheForwardStack
|
|
popStack = cacheBackStack
|
|
}
|
|
|
|
pushStack.push(id)
|
|
id = popStack.pop()
|
|
if (id) delete cacheMapping[id]
|
|
|
|
// Trim whichever stack we just pushed to to max cache length.
|
|
trimCacheStack(pushStack, pjax.defaults.maxCacheLength)
|
|
}
|
|
|
|
// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no
|
|
// longer than the specified length, deleting cached DOM elements as necessary.
|
|
//
|
|
// stack - Array of state IDs
|
|
// length - Maximum length to trim to
|
|
//
|
|
// Returns nothing.
|
|
function trimCacheStack(stack, length) {
|
|
while (stack.length > length)
|
|
delete cacheMapping[stack.shift()]
|
|
}
|
|
|
|
// Public: Find version identifier for the initial page load.
|
|
//
|
|
// Returns String version or undefined.
|
|
function findVersion() {
|
|
return $('meta').filter(function() {
|
|
var name = $(this).attr('http-equiv')
|
|
return name && name.toUpperCase() === 'X-PJAX-VERSION'
|
|
}).attr('content')
|
|
}
|
|
|
|
// Install pjax functions on $.pjax to enable pushState behavior.
|
|
//
|
|
// Does nothing if already enabled.
|
|
//
|
|
// Examples
|
|
//
|
|
// $.pjax.enable()
|
|
//
|
|
// Returns nothing.
|
|
function enable() {
|
|
$.fn.pjax = fnPjax
|
|
$.pjax = pjax
|
|
$.pjax.enable = $.noop
|
|
$.pjax.disable = disable
|
|
$.pjax.click = handleClick
|
|
$.pjax.submit = handleSubmit
|
|
$.pjax.reload = pjaxReload
|
|
$.pjax.defaults = {
|
|
timeout: 650,
|
|
push: true,
|
|
replace: false,
|
|
type: 'GET',
|
|
dataType: 'html',
|
|
scrollTo: 0,
|
|
maxCacheLength: 20,
|
|
version: findVersion
|
|
}
|
|
$(window).on('popstate.pjax', onPjaxPopstate)
|
|
}
|
|
|
|
// Disable pushState behavior.
|
|
//
|
|
// This is the case when a browser doesn't support pushState. It is
|
|
// sometimes useful to disable pushState for debugging on a modern
|
|
// browser.
|
|
//
|
|
// Examples
|
|
//
|
|
// $.pjax.disable()
|
|
//
|
|
// Returns nothing.
|
|
function disable() {
|
|
$.fn.pjax = function() { return this }
|
|
$.pjax = fallbackPjax
|
|
$.pjax.enable = enable
|
|
$.pjax.disable = $.noop
|
|
$.pjax.click = $.noop
|
|
$.pjax.submit = $.noop
|
|
$.pjax.reload = function() { window.location.reload() }
|
|
|
|
$(window).off('popstate.pjax', onPjaxPopstate)
|
|
}
|
|
|
|
|
|
// Add the state property to jQuery's event object so we can use it in
|
|
// $(window).bind('popstate')
|
|
if ($.event.props && $.inArray('state', $.event.props) < 0) {
|
|
$.event.props.push('state')
|
|
} else if (!('state' in $.Event.prototype)) {
|
|
$.event.addProp('state')
|
|
}
|
|
|
|
// Is pjax supported by this browser?
|
|
$.support.pjax =
|
|
window.history && window.history.pushState && window.history.replaceState &&
|
|
// pushState isn't reliable on iOS until 5.
|
|
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
|
|
|
|
if ($.support.pjax) {
|
|
enable()
|
|
} else {
|
|
disable()
|
|
}
|
|
|
|
})(jQuery)
|