|
|
define(["jquery","api/SplunkVisualizationBase","api/SplunkVisualizationUtils","splunkjs/mvc/searchmanager","splunkjs/mvc","splunk.util","splunkjs/mvc/timerangeview","splunkjs/mvc/dropdownview","strftime"], function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_5__, __WEBPACK_EXTERNAL_MODULE_8__, __WEBPACK_EXTERNAL_MODULE_9__, __WEBPACK_EXTERNAL_MODULE_10__, __WEBPACK_EXTERNAL_MODULE_11__, __WEBPACK_EXTERNAL_MODULE_15__) { return /******/ (function(modules) { // webpackBootstrap
|
|
|
/******/ // The module cache
|
|
|
/******/ var installedModules = {};
|
|
|
|
|
|
/******/ // The require function
|
|
|
/******/ function __webpack_require__(moduleId) {
|
|
|
|
|
|
/******/ // Check if module is in cache
|
|
|
/******/ if(installedModules[moduleId])
|
|
|
/******/ return installedModules[moduleId].exports;
|
|
|
|
|
|
/******/ // Create a new module (and put it into the cache)
|
|
|
/******/ var module = installedModules[moduleId] = {
|
|
|
/******/ exports: {},
|
|
|
/******/ id: moduleId,
|
|
|
/******/ loaded: false
|
|
|
/******/ };
|
|
|
|
|
|
/******/ // Execute the module function
|
|
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
|
|
|
|
/******/ // Flag the module as loaded
|
|
|
/******/ module.loaded = true;
|
|
|
|
|
|
/******/ // Return the exports of the module
|
|
|
/******/ return module.exports;
|
|
|
/******/ }
|
|
|
|
|
|
|
|
|
/******/ // expose the modules object (__webpack_modules__)
|
|
|
/******/ __webpack_require__.m = modules;
|
|
|
|
|
|
/******/ // expose the module cache
|
|
|
/******/ __webpack_require__.c = installedModules;
|
|
|
|
|
|
/******/ // __webpack_public_path__
|
|
|
/******/ __webpack_require__.p = "";
|
|
|
|
|
|
/******/ // Load entry module and return exports
|
|
|
/******/ return __webpack_require__(0);
|
|
|
/******/ })
|
|
|
/************************************************************************/
|
|
|
/******/ ([
|
|
|
/* 0 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
|
|
|
__webpack_require__(1),
|
|
|
__webpack_require__(2),
|
|
|
__webpack_require__(3),
|
|
|
__webpack_require__(4),
|
|
|
__webpack_require__(5),
|
|
|
__webpack_require__(6),
|
|
|
__webpack_require__(8),
|
|
|
__webpack_require__(16),
|
|
|
], __WEBPACK_AMD_DEFINE_RESULT__ = function(
|
|
|
$,
|
|
|
_,
|
|
|
SplunkVisualizationBase,
|
|
|
vizUtils,
|
|
|
SearchManager,
|
|
|
EntityContextPanel,
|
|
|
mvc,
|
|
|
EntityContextPanelModel
|
|
|
) {
|
|
|
|
|
|
var baseView;
|
|
|
var renderDone;
|
|
|
|
|
|
return SplunkVisualizationBase.extend({
|
|
|
|
|
|
initialize: function() {
|
|
|
SplunkVisualizationBase.prototype.initialize.apply(this, arguments);
|
|
|
this.$el = $(this.el);
|
|
|
},
|
|
|
|
|
|
setupView: function () {
|
|
|
var config = this.getCurrentConfig();
|
|
|
var header_id = this._getEscapedProperty('header_id', config) || '';
|
|
|
|
|
|
baseView = new EntityContextPanel({
|
|
|
model: new EntityContextPanelModel({header_id: header_id}), el: this.$el
|
|
|
});
|
|
|
renderDone = baseView.render();
|
|
|
},
|
|
|
|
|
|
updateView: function (data) {
|
|
|
if (baseView && renderDone) {
|
|
|
renderDone.done(_.bind(function() {
|
|
|
baseView.model.setEntityInfo(this._flattenFields(data));
|
|
|
}, this));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
getInitialDataParams: function() {
|
|
|
return ({
|
|
|
outputMode: SplunkVisualizationBase.ROW_MAJOR_OUTPUT_MODE,
|
|
|
count: 200
|
|
|
});
|
|
|
},
|
|
|
|
|
|
_flattenFields: function (data) {
|
|
|
data.fields = _.map(data.fields, function (field) {
|
|
|
return field.name;
|
|
|
});
|
|
|
return data;
|
|
|
},
|
|
|
|
|
|
_getEscapedProperty: function(name, config) {
|
|
|
var propertyValue = config[this.getPropertyNamespaceInfo().propertyNamespace + name];
|
|
|
return vizUtils.escapeHtml(propertyValue);
|
|
|
}
|
|
|
|
|
|
});
|
|
|
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
/***/ }),
|
|
|
/* 1 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 2 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
/* WEBPACK VAR INJECTION */(function(global) {(function (global, factory) {
|
|
|
true ? module.exports = factory() :
|
|
|
typeof define === 'function' && define.amd ? define('underscore', factory) :
|
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () {
|
|
|
var current = global._;
|
|
|
var exports = global._ = factory();
|
|
|
exports.noConflict = function () { global._ = current; return exports; };
|
|
|
}()));
|
|
|
}(this, (function () {
|
|
|
// Underscore.js 1.13.7
|
|
|
// https://underscorejs.org
|
|
|
// (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
|
|
|
// Underscore may be freely distributed under the MIT license.
|
|
|
|
|
|
// Current version.
|
|
|
var VERSION = '1.13.7';
|
|
|
|
|
|
// Establish the root object, `window` (`self`) in the browser, `global`
|
|
|
// on the server, or `this` in some virtual machines. We use `self`
|
|
|
// instead of `window` for `WebWorker` support.
|
|
|
var root = (typeof self == 'object' && self.self === self && self) ||
|
|
|
(typeof global == 'object' && global.global === global && global) ||
|
|
|
Function('return this')() ||
|
|
|
{};
|
|
|
|
|
|
// Save bytes in the minified (but not gzipped) version:
|
|
|
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
|
|
|
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
|
|
|
|
|
|
// Create quick reference variables for speed access to core prototypes.
|
|
|
var push = ArrayProto.push,
|
|
|
slice = ArrayProto.slice,
|
|
|
toString = ObjProto.toString,
|
|
|
hasOwnProperty = ObjProto.hasOwnProperty;
|
|
|
|
|
|
// Modern feature detection.
|
|
|
var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined',
|
|
|
supportsDataView = typeof DataView !== 'undefined';
|
|
|
|
|
|
// All **ECMAScript 5+** native function implementations that we hope to use
|
|
|
// are declared here.
|
|
|
var nativeIsArray = Array.isArray,
|
|
|
nativeKeys = Object.keys,
|
|
|
nativeCreate = Object.create,
|
|
|
nativeIsView = supportsArrayBuffer && ArrayBuffer.isView;
|
|
|
|
|
|
// Create references to these builtin functions because we override them.
|
|
|
var _isNaN = isNaN,
|
|
|
_isFinite = isFinite;
|
|
|
|
|
|
// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
|
|
|
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
|
|
|
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
|
|
|
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
|
|
|
|
|
|
// The largest integer that can be represented exactly.
|
|
|
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
|
|
|
|
|
|
// Some functions take a variable number of arguments, or a few expected
|
|
|
// arguments at the beginning and then a variable number of values to operate
|
|
|
// on. This helper accumulates all remaining arguments past the function’s
|
|
|
// argument length (or an explicit `startIndex`), into an array that becomes
|
|
|
// the last argument. Similar to ES6’s "rest parameter".
|
|
|
function restArguments(func, startIndex) {
|
|
|
startIndex = startIndex == null ? func.length - 1 : +startIndex;
|
|
|
return function() {
|
|
|
var length = Math.max(arguments.length - startIndex, 0),
|
|
|
rest = Array(length),
|
|
|
index = 0;
|
|
|
for (; index < length; index++) {
|
|
|
rest[index] = arguments[index + startIndex];
|
|
|
}
|
|
|
switch (startIndex) {
|
|
|
case 0: return func.call(this, rest);
|
|
|
case 1: return func.call(this, arguments[0], rest);
|
|
|
case 2: return func.call(this, arguments[0], arguments[1], rest);
|
|
|
}
|
|
|
var args = Array(startIndex + 1);
|
|
|
for (index = 0; index < startIndex; index++) {
|
|
|
args[index] = arguments[index];
|
|
|
}
|
|
|
args[startIndex] = rest;
|
|
|
return func.apply(this, args);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Is a given variable an object?
|
|
|
function isObject(obj) {
|
|
|
var type = typeof obj;
|
|
|
return type === 'function' || (type === 'object' && !!obj);
|
|
|
}
|
|
|
|
|
|
// Is a given value equal to null?
|
|
|
function isNull(obj) {
|
|
|
return obj === null;
|
|
|
}
|
|
|
|
|
|
// Is a given variable undefined?
|
|
|
function isUndefined(obj) {
|
|
|
return obj === void 0;
|
|
|
}
|
|
|
|
|
|
// Is a given value a boolean?
|
|
|
function isBoolean(obj) {
|
|
|
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
|
|
|
}
|
|
|
|
|
|
// Is a given value a DOM element?
|
|
|
function isElement(obj) {
|
|
|
return !!(obj && obj.nodeType === 1);
|
|
|
}
|
|
|
|
|
|
// Internal function for creating a `toString`-based type tester.
|
|
|
function tagTester(name) {
|
|
|
var tag = '[object ' + name + ']';
|
|
|
return function(obj) {
|
|
|
return toString.call(obj) === tag;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
var isString = tagTester('String');
|
|
|
|
|
|
var isNumber = tagTester('Number');
|
|
|
|
|
|
var isDate = tagTester('Date');
|
|
|
|
|
|
var isRegExp = tagTester('RegExp');
|
|
|
|
|
|
var isError = tagTester('Error');
|
|
|
|
|
|
var isSymbol = tagTester('Symbol');
|
|
|
|
|
|
var isArrayBuffer = tagTester('ArrayBuffer');
|
|
|
|
|
|
var isFunction = tagTester('Function');
|
|
|
|
|
|
// Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old
|
|
|
// v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
|
|
|
var nodelist = root.document && root.document.childNodes;
|
|
|
if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
|
|
|
isFunction = function(obj) {
|
|
|
return typeof obj == 'function' || false;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
var isFunction$1 = isFunction;
|
|
|
|
|
|
var hasObjectTag = tagTester('Object');
|
|
|
|
|
|
// In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`.
|
|
|
// In IE 11, the most common among them, this problem also applies to
|
|
|
// `Map`, `WeakMap` and `Set`.
|
|
|
// Also, there are cases where an application can override the native
|
|
|
// `DataView` object, in cases like that we can't use the constructor
|
|
|
// safely and should just rely on alternate `DataView` checks
|
|
|
var hasDataViewBug = (
|
|
|
supportsDataView && (!/\[native code\]/.test(String(DataView)) || hasObjectTag(new DataView(new ArrayBuffer(8))))
|
|
|
),
|
|
|
isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map));
|
|
|
|
|
|
var isDataView = tagTester('DataView');
|
|
|
|
|
|
// In IE 10 - Edge 13, we need a different heuristic
|
|
|
// to determine whether an object is a `DataView`.
|
|
|
// Also, in cases where the native `DataView` is
|
|
|
// overridden we can't rely on the tag itself.
|
|
|
function alternateIsDataView(obj) {
|
|
|
return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer);
|
|
|
}
|
|
|
|
|
|
var isDataView$1 = (hasDataViewBug ? alternateIsDataView : isDataView);
|
|
|
|
|
|
// Is a given value an array?
|
|
|
// Delegates to ECMA5's native `Array.isArray`.
|
|
|
var isArray = nativeIsArray || tagTester('Array');
|
|
|
|
|
|
// Internal function to check whether `key` is an own property name of `obj`.
|
|
|
function has$1(obj, key) {
|
|
|
return obj != null && hasOwnProperty.call(obj, key);
|
|
|
}
|
|
|
|
|
|
var isArguments = tagTester('Arguments');
|
|
|
|
|
|
// Define a fallback version of the method in browsers (ahem, IE < 9), where
|
|
|
// there isn't any inspectable "Arguments" type.
|
|
|
(function() {
|
|
|
if (!isArguments(arguments)) {
|
|
|
isArguments = function(obj) {
|
|
|
return has$1(obj, 'callee');
|
|
|
};
|
|
|
}
|
|
|
}());
|
|
|
|
|
|
var isArguments$1 = isArguments;
|
|
|
|
|
|
// Is a given object a finite number?
|
|
|
function isFinite$1(obj) {
|
|
|
return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj));
|
|
|
}
|
|
|
|
|
|
// Is the given value `NaN`?
|
|
|
function isNaN$1(obj) {
|
|
|
return isNumber(obj) && _isNaN(obj);
|
|
|
}
|
|
|
|
|
|
// Predicate-generating function. Often useful outside of Underscore.
|
|
|
function constant(value) {
|
|
|
return function() {
|
|
|
return value;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Common internal logic for `isArrayLike` and `isBufferLike`.
|
|
|
function createSizePropertyCheck(getSizeProperty) {
|
|
|
return function(collection) {
|
|
|
var sizeProperty = getSizeProperty(collection);
|
|
|
return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Internal helper to generate a function to obtain property `key` from `obj`.
|
|
|
function shallowProperty(key) {
|
|
|
return function(obj) {
|
|
|
return obj == null ? void 0 : obj[key];
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Internal helper to obtain the `byteLength` property of an object.
|
|
|
var getByteLength = shallowProperty('byteLength');
|
|
|
|
|
|
// Internal helper to determine whether we should spend extensive checks against
|
|
|
// `ArrayBuffer` et al.
|
|
|
var isBufferLike = createSizePropertyCheck(getByteLength);
|
|
|
|
|
|
// Is a given value a typed array?
|
|
|
var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;
|
|
|
function isTypedArray(obj) {
|
|
|
// `ArrayBuffer.isView` is the most future-proof, so use it when available.
|
|
|
// Otherwise, fall back on the above regular expression.
|
|
|
return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) :
|
|
|
isBufferLike(obj) && typedArrayPattern.test(toString.call(obj));
|
|
|
}
|
|
|
|
|
|
var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false);
|
|
|
|
|
|
// Internal helper to obtain the `length` property of an object.
|
|
|
var getLength = shallowProperty('length');
|
|
|
|
|
|
// Internal helper to create a simple lookup structure.
|
|
|
// `collectNonEnumProps` used to depend on `_.contains`, but this led to
|
|
|
// circular imports. `emulatedSet` is a one-off solution that only works for
|
|
|
// arrays of strings.
|
|
|
function emulatedSet(keys) {
|
|
|
var hash = {};
|
|
|
for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true;
|
|
|
return {
|
|
|
contains: function(key) { return hash[key] === true; },
|
|
|
push: function(key) {
|
|
|
hash[key] = true;
|
|
|
return keys.push(key);
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't
|
|
|
// be iterated by `for key in ...` and thus missed. Extends `keys` in place if
|
|
|
// needed.
|
|
|
function collectNonEnumProps(obj, keys) {
|
|
|
keys = emulatedSet(keys);
|
|
|
var nonEnumIdx = nonEnumerableProps.length;
|
|
|
var constructor = obj.constructor;
|
|
|
var proto = (isFunction$1(constructor) && constructor.prototype) || ObjProto;
|
|
|
|
|
|
// Constructor is a special case.
|
|
|
var prop = 'constructor';
|
|
|
if (has$1(obj, prop) && !keys.contains(prop)) keys.push(prop);
|
|
|
|
|
|
while (nonEnumIdx--) {
|
|
|
prop = nonEnumerableProps[nonEnumIdx];
|
|
|
if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) {
|
|
|
keys.push(prop);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Retrieve the names of an object's own properties.
|
|
|
// Delegates to **ECMAScript 5**'s native `Object.keys`.
|
|
|
function keys(obj) {
|
|
|
if (!isObject(obj)) return [];
|
|
|
if (nativeKeys) return nativeKeys(obj);
|
|
|
var keys = [];
|
|
|
for (var key in obj) if (has$1(obj, key)) keys.push(key);
|
|
|
// Ahem, IE < 9.
|
|
|
if (hasEnumBug) collectNonEnumProps(obj, keys);
|
|
|
return keys;
|
|
|
}
|
|
|
|
|
|
// Is a given array, string, or object empty?
|
|
|
// An "empty" object has no enumerable own-properties.
|
|
|
function isEmpty(obj) {
|
|
|
if (obj == null) return true;
|
|
|
// Skip the more expensive `toString`-based type checks if `obj` has no
|
|
|
// `.length`.
|
|
|
var length = getLength(obj);
|
|
|
if (typeof length == 'number' && (
|
|
|
isArray(obj) || isString(obj) || isArguments$1(obj)
|
|
|
)) return length === 0;
|
|
|
return getLength(keys(obj)) === 0;
|
|
|
}
|
|
|
|
|
|
// Returns whether an object has a given set of `key:value` pairs.
|
|
|
function isMatch(object, attrs) {
|
|
|
var _keys = keys(attrs), length = _keys.length;
|
|
|
if (object == null) return !length;
|
|
|
var obj = Object(object);
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
var key = _keys[i];
|
|
|
if (attrs[key] !== obj[key] || !(key in obj)) return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// If Underscore is called as a function, it returns a wrapped object that can
|
|
|
// be used OO-style. This wrapper holds altered versions of all functions added
|
|
|
// through `_.mixin`. Wrapped objects may be chained.
|
|
|
function _$1(obj) {
|
|
|
if (obj instanceof _$1) return obj;
|
|
|
if (!(this instanceof _$1)) return new _$1(obj);
|
|
|
this._wrapped = obj;
|
|
|
}
|
|
|
|
|
|
_$1.VERSION = VERSION;
|
|
|
|
|
|
// Extracts the result from a wrapped and chained object.
|
|
|
_$1.prototype.value = function() {
|
|
|
return this._wrapped;
|
|
|
};
|
|
|
|
|
|
// Provide unwrapping proxies for some methods used in engine operations
|
|
|
// such as arithmetic and JSON stringification.
|
|
|
_$1.prototype.valueOf = _$1.prototype.toJSON = _$1.prototype.value;
|
|
|
|
|
|
_$1.prototype.toString = function() {
|
|
|
return String(this._wrapped);
|
|
|
};
|
|
|
|
|
|
// Internal function to wrap or shallow-copy an ArrayBuffer,
|
|
|
// typed array or DataView to a new view, reusing the buffer.
|
|
|
function toBufferView(bufferSource) {
|
|
|
return new Uint8Array(
|
|
|
bufferSource.buffer || bufferSource,
|
|
|
bufferSource.byteOffset || 0,
|
|
|
getByteLength(bufferSource)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// We use this string twice, so give it a name for minification.
|
|
|
var tagDataView = '[object DataView]';
|
|
|
|
|
|
// Internal recursive comparison function for `_.isEqual`.
|
|
|
function eq(a, b, aStack, bStack) {
|
|
|
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
|
|
// See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
|
|
if (a === b) return a !== 0 || 1 / a === 1 / b;
|
|
|
// `null` or `undefined` only equal to itself (strict comparison).
|
|
|
if (a == null || b == null) return false;
|
|
|
// `NaN`s are equivalent, but non-reflexive.
|
|
|
if (a !== a) return b !== b;
|
|
|
// Exhaust primitive checks
|
|
|
var type = typeof a;
|
|
|
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
|
|
|
return deepEq(a, b, aStack, bStack);
|
|
|
}
|
|
|
|
|
|
// Internal recursive comparison function for `_.isEqual`.
|
|
|
function deepEq(a, b, aStack, bStack) {
|
|
|
// Unwrap any wrapped objects.
|
|
|
if (a instanceof _$1) a = a._wrapped;
|
|
|
if (b instanceof _$1) b = b._wrapped;
|
|
|
// Compare `[[Class]]` names.
|
|
|
var className = toString.call(a);
|
|
|
if (className !== toString.call(b)) return false;
|
|
|
// Work around a bug in IE 10 - Edge 13.
|
|
|
if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
|
|
|
if (!isDataView$1(b)) return false;
|
|
|
className = tagDataView;
|
|
|
}
|
|
|
switch (className) {
|
|
|
// These types are compared by value.
|
|
|
case '[object RegExp]':
|
|
|
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
|
|
|
case '[object String]':
|
|
|
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
|
|
// equivalent to `new String("5")`.
|
|
|
return '' + a === '' + b;
|
|
|
case '[object Number]':
|
|
|
// `NaN`s are equivalent, but non-reflexive.
|
|
|
// Object(NaN) is equivalent to NaN.
|
|
|
if (+a !== +a) return +b !== +b;
|
|
|
// An `egal` comparison is performed for other numeric values.
|
|
|
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
|
|
|
case '[object Date]':
|
|
|
case '[object Boolean]':
|
|
|
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
|
|
// millisecond representations. Note that invalid dates with millisecond representations
|
|
|
// of `NaN` are not equivalent.
|
|
|
return +a === +b;
|
|
|
case '[object Symbol]':
|
|
|
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
|
|
|
case '[object ArrayBuffer]':
|
|
|
case tagDataView:
|
|
|
// Coerce to typed array so we can fall through.
|
|
|
return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
|
|
|
}
|
|
|
|
|
|
var areArrays = className === '[object Array]';
|
|
|
if (!areArrays && isTypedArray$1(a)) {
|
|
|
var byteLength = getByteLength(a);
|
|
|
if (byteLength !== getByteLength(b)) return false;
|
|
|
if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
|
|
|
areArrays = true;
|
|
|
}
|
|
|
if (!areArrays) {
|
|
|
if (typeof a != 'object' || typeof b != 'object') return false;
|
|
|
|
|
|
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
|
|
|
// from different frames are.
|
|
|
var aCtor = a.constructor, bCtor = b.constructor;
|
|
|
if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
|
|
|
isFunction$1(bCtor) && bCtor instanceof bCtor)
|
|
|
&& ('constructor' in a && 'constructor' in b)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
|
|
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
|
|
|
|
|
// Initializing stack of traversed objects.
|
|
|
// It's done here since we only need them for objects and arrays comparison.
|
|
|
aStack = aStack || [];
|
|
|
bStack = bStack || [];
|
|
|
var length = aStack.length;
|
|
|
while (length--) {
|
|
|
// Linear search. Performance is inversely proportional to the number of
|
|
|
// unique nested structures.
|
|
|
if (aStack[length] === a) return bStack[length] === b;
|
|
|
}
|
|
|
|
|
|
// Add the first object to the stack of traversed objects.
|
|
|
aStack.push(a);
|
|
|
bStack.push(b);
|
|
|
|
|
|
// Recursively compare objects and arrays.
|
|
|
if (areArrays) {
|
|
|
// Compare array lengths to determine if a deep comparison is necessary.
|
|
|
length = a.length;
|
|
|
if (length !== b.length) return false;
|
|
|
// Deep compare the contents, ignoring non-numeric properties.
|
|
|
while (length--) {
|
|
|
if (!eq(a[length], b[length], aStack, bStack)) return false;
|
|
|
}
|
|
|
} else {
|
|
|
// Deep compare objects.
|
|
|
var _keys = keys(a), key;
|
|
|
length = _keys.length;
|
|
|
// Ensure that both objects contain the same number of properties before comparing deep equality.
|
|
|
if (keys(b).length !== length) return false;
|
|
|
while (length--) {
|
|
|
// Deep compare each member
|
|
|
key = _keys[length];
|
|
|
if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
|
|
|
}
|
|
|
}
|
|
|
// Remove the first object from the stack of traversed objects.
|
|
|
aStack.pop();
|
|
|
bStack.pop();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// Perform a deep comparison to check if two objects are equal.
|
|
|
function isEqual(a, b) {
|
|
|
return eq(a, b);
|
|
|
}
|
|
|
|
|
|
// Retrieve all the enumerable property names of an object.
|
|
|
function allKeys(obj) {
|
|
|
if (!isObject(obj)) return [];
|
|
|
var keys = [];
|
|
|
for (var key in obj) keys.push(key);
|
|
|
// Ahem, IE < 9.
|
|
|
if (hasEnumBug) collectNonEnumProps(obj, keys);
|
|
|
return keys;
|
|
|
}
|
|
|
|
|
|
// Since the regular `Object.prototype.toString` type tests don't work for
|
|
|
// some types in IE 11, we use a fingerprinting heuristic instead, based
|
|
|
// on the methods. It's not great, but it's the best we got.
|
|
|
// The fingerprint method lists are defined below.
|
|
|
function ie11fingerprint(methods) {
|
|
|
var length = getLength(methods);
|
|
|
return function(obj) {
|
|
|
if (obj == null) return false;
|
|
|
// `Map`, `WeakMap` and `Set` have no enumerable keys.
|
|
|
var keys = allKeys(obj);
|
|
|
if (getLength(keys)) return false;
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
if (!isFunction$1(obj[methods[i]])) return false;
|
|
|
}
|
|
|
// If we are testing against `WeakMap`, we need to ensure that
|
|
|
// `obj` doesn't have a `forEach` method in order to distinguish
|
|
|
// it from a regular `Map`.
|
|
|
return methods !== weakMapMethods || !isFunction$1(obj[forEachName]);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// In the interest of compact minification, we write
|
|
|
// each string in the fingerprints only once.
|
|
|
var forEachName = 'forEach',
|
|
|
hasName = 'has',
|
|
|
commonInit = ['clear', 'delete'],
|
|
|
mapTail = ['get', hasName, 'set'];
|
|
|
|
|
|
// `Map`, `WeakMap` and `Set` each have slightly different
|
|
|
// combinations of the above sublists.
|
|
|
var mapMethods = commonInit.concat(forEachName, mapTail),
|
|
|
weakMapMethods = commonInit.concat(mapTail),
|
|
|
setMethods = ['add'].concat(commonInit, forEachName, hasName);
|
|
|
|
|
|
var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map');
|
|
|
|
|
|
var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap');
|
|
|
|
|
|
var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set');
|
|
|
|
|
|
var isWeakSet = tagTester('WeakSet');
|
|
|
|
|
|
// Retrieve the values of an object's properties.
|
|
|
function values(obj) {
|
|
|
var _keys = keys(obj);
|
|
|
var length = _keys.length;
|
|
|
var values = Array(length);
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
values[i] = obj[_keys[i]];
|
|
|
}
|
|
|
return values;
|
|
|
}
|
|
|
|
|
|
// Convert an object into a list of `[key, value]` pairs.
|
|
|
// The opposite of `_.object` with one argument.
|
|
|
function pairs(obj) {
|
|
|
var _keys = keys(obj);
|
|
|
var length = _keys.length;
|
|
|
var pairs = Array(length);
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
pairs[i] = [_keys[i], obj[_keys[i]]];
|
|
|
}
|
|
|
return pairs;
|
|
|
}
|
|
|
|
|
|
// Invert the keys and values of an object. The values must be serializable.
|
|
|
function invert(obj) {
|
|
|
var result = {};
|
|
|
var _keys = keys(obj);
|
|
|
for (var i = 0, length = _keys.length; i < length; i++) {
|
|
|
result[obj[_keys[i]]] = _keys[i];
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Return a sorted list of the function names available on the object.
|
|
|
function functions(obj) {
|
|
|
var names = [];
|
|
|
for (var key in obj) {
|
|
|
if (isFunction$1(obj[key])) names.push(key);
|
|
|
}
|
|
|
return names.sort();
|
|
|
}
|
|
|
|
|
|
// An internal function for creating assigner functions.
|
|
|
function createAssigner(keysFunc, defaults) {
|
|
|
return function(obj) {
|
|
|
var length = arguments.length;
|
|
|
if (defaults) obj = Object(obj);
|
|
|
if (length < 2 || obj == null) return obj;
|
|
|
for (var index = 1; index < length; index++) {
|
|
|
var source = arguments[index],
|
|
|
keys = keysFunc(source),
|
|
|
l = keys.length;
|
|
|
for (var i = 0; i < l; i++) {
|
|
|
var key = keys[i];
|
|
|
if (!defaults || obj[key] === void 0) obj[key] = source[key];
|
|
|
}
|
|
|
}
|
|
|
return obj;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Extend a given object with all the properties in passed-in object(s).
|
|
|
var extend = createAssigner(allKeys);
|
|
|
|
|
|
// Assigns a given object with all the own properties in the passed-in
|
|
|
// object(s).
|
|
|
// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
|
|
|
var extendOwn = createAssigner(keys);
|
|
|
|
|
|
// Fill in a given object with default properties.
|
|
|
var defaults = createAssigner(allKeys, true);
|
|
|
|
|
|
// Create a naked function reference for surrogate-prototype-swapping.
|
|
|
function ctor() {
|
|
|
return function(){};
|
|
|
}
|
|
|
|
|
|
// An internal function for creating a new object that inherits from another.
|
|
|
function baseCreate(prototype) {
|
|
|
if (!isObject(prototype)) return {};
|
|
|
if (nativeCreate) return nativeCreate(prototype);
|
|
|
var Ctor = ctor();
|
|
|
Ctor.prototype = prototype;
|
|
|
var result = new Ctor;
|
|
|
Ctor.prototype = null;
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Creates an object that inherits from the given prototype object.
|
|
|
// If additional properties are provided then they will be added to the
|
|
|
// created object.
|
|
|
function create(prototype, props) {
|
|
|
var result = baseCreate(prototype);
|
|
|
if (props) extendOwn(result, props);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Create a (shallow-cloned) duplicate of an object.
|
|
|
function clone(obj) {
|
|
|
if (!isObject(obj)) return obj;
|
|
|
return isArray(obj) ? obj.slice() : extend({}, obj);
|
|
|
}
|
|
|
|
|
|
// Invokes `interceptor` with the `obj` and then returns `obj`.
|
|
|
// The primary purpose of this method is to "tap into" a method chain, in
|
|
|
// order to perform operations on intermediate results within the chain.
|
|
|
function tap(obj, interceptor) {
|
|
|
interceptor(obj);
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
|
// Normalize a (deep) property `path` to array.
|
|
|
// Like `_.iteratee`, this function can be customized.
|
|
|
function toPath$1(path) {
|
|
|
return isArray(path) ? path : [path];
|
|
|
}
|
|
|
_$1.toPath = toPath$1;
|
|
|
|
|
|
// Internal wrapper for `_.toPath` to enable minification.
|
|
|
// Similar to `cb` for `_.iteratee`.
|
|
|
function toPath(path) {
|
|
|
return _$1.toPath(path);
|
|
|
}
|
|
|
|
|
|
// Internal function to obtain a nested property in `obj` along `path`.
|
|
|
function deepGet(obj, path) {
|
|
|
var length = path.length;
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
if (obj == null) return void 0;
|
|
|
obj = obj[path[i]];
|
|
|
}
|
|
|
return length ? obj : void 0;
|
|
|
}
|
|
|
|
|
|
// Get the value of the (deep) property on `path` from `object`.
|
|
|
// If any property in `path` does not exist or if the value is
|
|
|
// `undefined`, return `defaultValue` instead.
|
|
|
// The `path` is normalized through `_.toPath`.
|
|
|
function get(object, path, defaultValue) {
|
|
|
var value = deepGet(object, toPath(path));
|
|
|
return isUndefined(value) ? defaultValue : value;
|
|
|
}
|
|
|
|
|
|
// Shortcut function for checking if an object has a given property directly on
|
|
|
// itself (in other words, not on a prototype). Unlike the internal `has`
|
|
|
// function, this public version can also traverse nested properties.
|
|
|
function has(obj, path) {
|
|
|
path = toPath(path);
|
|
|
var length = path.length;
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
var key = path[i];
|
|
|
if (!has$1(obj, key)) return false;
|
|
|
obj = obj[key];
|
|
|
}
|
|
|
return !!length;
|
|
|
}
|
|
|
|
|
|
// Keep the identity function around for default iteratees.
|
|
|
function identity(value) {
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
// Returns a predicate for checking whether an object has a given set of
|
|
|
// `key:value` pairs.
|
|
|
function matcher(attrs) {
|
|
|
attrs = extendOwn({}, attrs);
|
|
|
return function(obj) {
|
|
|
return isMatch(obj, attrs);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Creates a function that, when passed an object, will traverse that object’s
|
|
|
// properties down the given `path`, specified as an array of keys or indices.
|
|
|
function property(path) {
|
|
|
path = toPath(path);
|
|
|
return function(obj) {
|
|
|
return deepGet(obj, path);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Internal function that returns an efficient (for current engines) version
|
|
|
// of the passed-in callback, to be repeatedly applied in other Underscore
|
|
|
// functions.
|
|
|
function optimizeCb(func, context, argCount) {
|
|
|
if (context === void 0) return func;
|
|
|
switch (argCount == null ? 3 : argCount) {
|
|
|
case 1: return function(value) {
|
|
|
return func.call(context, value);
|
|
|
};
|
|
|
// The 2-argument case is omitted because we’re not using it.
|
|
|
case 3: return function(value, index, collection) {
|
|
|
return func.call(context, value, index, collection);
|
|
|
};
|
|
|
case 4: return function(accumulator, value, index, collection) {
|
|
|
return func.call(context, accumulator, value, index, collection);
|
|
|
};
|
|
|
}
|
|
|
return function() {
|
|
|
return func.apply(context, arguments);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// An internal function to generate callbacks that can be applied to each
|
|
|
// element in a collection, returning the desired result — either `_.identity`,
|
|
|
// an arbitrary callback, a property matcher, or a property accessor.
|
|
|
function baseIteratee(value, context, argCount) {
|
|
|
if (value == null) return identity;
|
|
|
if (isFunction$1(value)) return optimizeCb(value, context, argCount);
|
|
|
if (isObject(value) && !isArray(value)) return matcher(value);
|
|
|
return property(value);
|
|
|
}
|
|
|
|
|
|
// External wrapper for our callback generator. Users may customize
|
|
|
// `_.iteratee` if they want additional predicate/iteratee shorthand styles.
|
|
|
// This abstraction hides the internal-only `argCount` argument.
|
|
|
function iteratee(value, context) {
|
|
|
return baseIteratee(value, context, Infinity);
|
|
|
}
|
|
|
_$1.iteratee = iteratee;
|
|
|
|
|
|
// The function we call internally to generate a callback. It invokes
|
|
|
// `_.iteratee` if overridden, otherwise `baseIteratee`.
|
|
|
function cb(value, context, argCount) {
|
|
|
if (_$1.iteratee !== iteratee) return _$1.iteratee(value, context);
|
|
|
return baseIteratee(value, context, argCount);
|
|
|
}
|
|
|
|
|
|
// Returns the results of applying the `iteratee` to each element of `obj`.
|
|
|
// In contrast to `_.map` it returns an object.
|
|
|
function mapObject(obj, iteratee, context) {
|
|
|
iteratee = cb(iteratee, context);
|
|
|
var _keys = keys(obj),
|
|
|
length = _keys.length,
|
|
|
results = {};
|
|
|
for (var index = 0; index < length; index++) {
|
|
|
var currentKey = _keys[index];
|
|
|
results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
|
|
|
}
|
|
|
return results;
|
|
|
}
|
|
|
|
|
|
// Predicate-generating function. Often useful outside of Underscore.
|
|
|
function noop(){}
|
|
|
|
|
|
// Generates a function for a given object that returns a given property.
|
|
|
function propertyOf(obj) {
|
|
|
if (obj == null) return noop;
|
|
|
return function(path) {
|
|
|
return get(obj, path);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Run a function **n** times.
|
|
|
function times(n, iteratee, context) {
|
|
|
var accum = Array(Math.max(0, n));
|
|
|
iteratee = optimizeCb(iteratee, context, 1);
|
|
|
for (var i = 0; i < n; i++) accum[i] = iteratee(i);
|
|
|
return accum;
|
|
|
}
|
|
|
|
|
|
// Return a random integer between `min` and `max` (inclusive).
|
|
|
function random(min, max) {
|
|
|
if (max == null) {
|
|
|
max = min;
|
|
|
min = 0;
|
|
|
}
|
|
|
return min + Math.floor(Math.random() * (max - min + 1));
|
|
|
}
|
|
|
|
|
|
// A (possibly faster) way to get the current timestamp as an integer.
|
|
|
var now = Date.now || function() {
|
|
|
return new Date().getTime();
|
|
|
};
|
|
|
|
|
|
// Internal helper to generate functions for escaping and unescaping strings
|
|
|
// to/from HTML interpolation.
|
|
|
function createEscaper(map) {
|
|
|
var escaper = function(match) {
|
|
|
return map[match];
|
|
|
};
|
|
|
// Regexes for identifying a key that needs to be escaped.
|
|
|
var source = '(?:' + keys(map).join('|') + ')';
|
|
|
var testRegexp = RegExp(source);
|
|
|
var replaceRegexp = RegExp(source, 'g');
|
|
|
return function(string) {
|
|
|
string = string == null ? '' : '' + string;
|
|
|
return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Internal list of HTML entities for escaping.
|
|
|
var escapeMap = {
|
|
|
'&': '&',
|
|
|
'<': '<',
|
|
|
'>': '>',
|
|
|
'"': '"',
|
|
|
"'": ''',
|
|
|
'`': '`'
|
|
|
};
|
|
|
|
|
|
// Function for escaping strings to HTML interpolation.
|
|
|
var _escape = createEscaper(escapeMap);
|
|
|
|
|
|
// Internal list of HTML entities for unescaping.
|
|
|
var unescapeMap = invert(escapeMap);
|
|
|
|
|
|
// Function for unescaping strings from HTML interpolation.
|
|
|
var _unescape = createEscaper(unescapeMap);
|
|
|
|
|
|
// By default, Underscore uses ERB-style template delimiters. Change the
|
|
|
// following template settings to use alternative delimiters.
|
|
|
var templateSettings = _$1.templateSettings = {
|
|
|
evaluate: /<%([\s\S]+?)%>/g,
|
|
|
interpolate: /<%=([\s\S]+?)%>/g,
|
|
|
escape: /<%-([\s\S]+?)%>/g
|
|
|
};
|
|
|
|
|
|
// When customizing `_.templateSettings`, if you don't want to define an
|
|
|
// interpolation, evaluation or escaping regex, we need one that is
|
|
|
// guaranteed not to match.
|
|
|
var noMatch = /(.)^/;
|
|
|
|
|
|
// Certain characters need to be escaped so that they can be put into a
|
|
|
// string literal.
|
|
|
var escapes = {
|
|
|
"'": "'",
|
|
|
'\\': '\\',
|
|
|
'\r': 'r',
|
|
|
'\n': 'n',
|
|
|
'\u2028': 'u2028',
|
|
|
'\u2029': 'u2029'
|
|
|
};
|
|
|
|
|
|
var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
|
|
|
|
|
|
function escapeChar(match) {
|
|
|
return '\\' + escapes[match];
|
|
|
}
|
|
|
|
|
|
// In order to prevent third-party code injection through
|
|
|
// `_.templateSettings.variable`, we test it against the following regular
|
|
|
// expression. It is intentionally a bit more liberal than just matching valid
|
|
|
// identifiers, but still prevents possible loopholes through defaults or
|
|
|
// destructuring assignment.
|
|
|
var bareIdentifier = /^\s*(\w|\$)+\s*$/;
|
|
|
|
|
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
|
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|
|
// and correctly escapes quotes within interpolated code.
|
|
|
// NB: `oldSettings` only exists for backwards compatibility.
|
|
|
function template(text, settings, oldSettings) {
|
|
|
if (!settings && oldSettings) settings = oldSettings;
|
|
|
settings = defaults({}, settings, _$1.templateSettings);
|
|
|
|
|
|
// Combine delimiters into one regular expression via alternation.
|
|
|
var matcher = RegExp([
|
|
|
(settings.escape || noMatch).source,
|
|
|
(settings.interpolate || noMatch).source,
|
|
|
(settings.evaluate || noMatch).source
|
|
|
].join('|') + '|$', 'g');
|
|
|
|
|
|
// Compile the template source, escaping string literals appropriately.
|
|
|
var index = 0;
|
|
|
var source = "__p+='";
|
|
|
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
|
|
|
source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
|
|
|
index = offset + match.length;
|
|
|
|
|
|
if (escape) {
|
|
|
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
|
|
|
} else if (interpolate) {
|
|
|
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
|
|
|
} else if (evaluate) {
|
|
|
source += "';\n" + evaluate + "\n__p+='";
|
|
|
}
|
|
|
|
|
|
// Adobe VMs need the match returned to produce the correct offset.
|
|
|
return match;
|
|
|
});
|
|
|
source += "';\n";
|
|
|
|
|
|
var argument = settings.variable;
|
|
|
if (argument) {
|
|
|
// Insure against third-party code injection. (CVE-2021-23358)
|
|
|
if (!bareIdentifier.test(argument)) throw new Error(
|
|
|
'variable is not a bare identifier: ' + argument
|
|
|
);
|
|
|
} else {
|
|
|
// If a variable is not specified, place data values in local scope.
|
|
|
source = 'with(obj||{}){\n' + source + '}\n';
|
|
|
argument = 'obj';
|
|
|
}
|
|
|
|
|
|
source = "var __t,__p='',__j=Array.prototype.join," +
|
|
|
"print=function(){__p+=__j.call(arguments,'');};\n" +
|
|
|
source + 'return __p;\n';
|
|
|
|
|
|
var render;
|
|
|
try {
|
|
|
render = new Function(argument, '_', source);
|
|
|
} catch (e) {
|
|
|
e.source = source;
|
|
|
throw e;
|
|
|
}
|
|
|
|
|
|
var template = function(data) {
|
|
|
return render.call(this, data, _$1);
|
|
|
};
|
|
|
|
|
|
// Provide the compiled source as a convenience for precompilation.
|
|
|
template.source = 'function(' + argument + '){\n' + source + '}';
|
|
|
|
|
|
return template;
|
|
|
}
|
|
|
|
|
|
// Traverses the children of `obj` along `path`. If a child is a function, it
|
|
|
// is invoked with its parent as context. Returns the value of the final
|
|
|
// child, or `fallback` if any child is undefined.
|
|
|
function result(obj, path, fallback) {
|
|
|
path = toPath(path);
|
|
|
var length = path.length;
|
|
|
if (!length) {
|
|
|
return isFunction$1(fallback) ? fallback.call(obj) : fallback;
|
|
|
}
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
var prop = obj == null ? void 0 : obj[path[i]];
|
|
|
if (prop === void 0) {
|
|
|
prop = fallback;
|
|
|
i = length; // Ensure we don't continue iterating.
|
|
|
}
|
|
|
obj = isFunction$1(prop) ? prop.call(obj) : prop;
|
|
|
}
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
|
// Generate a unique integer id (unique within the entire client session).
|
|
|
// Useful for temporary DOM ids.
|
|
|
var idCounter = 0;
|
|
|
function uniqueId(prefix) {
|
|
|
var id = ++idCounter + '';
|
|
|
return prefix ? prefix + id : id;
|
|
|
}
|
|
|
|
|
|
// Start chaining a wrapped Underscore object.
|
|
|
function chain(obj) {
|
|
|
var instance = _$1(obj);
|
|
|
instance._chain = true;
|
|
|
return instance;
|
|
|
}
|
|
|
|
|
|
// Internal function to execute `sourceFunc` bound to `context` with optional
|
|
|
// `args`. Determines whether to execute a function as a constructor or as a
|
|
|
// normal function.
|
|
|
function executeBound(sourceFunc, boundFunc, context, callingContext, args) {
|
|
|
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
|
|
|
var self = baseCreate(sourceFunc.prototype);
|
|
|
var result = sourceFunc.apply(self, args);
|
|
|
if (isObject(result)) return result;
|
|
|
return self;
|
|
|
}
|
|
|
|
|
|
// Partially apply a function by creating a version that has had some of its
|
|
|
// arguments pre-filled, without changing its dynamic `this` context. `_` acts
|
|
|
// as a placeholder by default, allowing any combination of arguments to be
|
|
|
// pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
|
|
|
var partial = restArguments(function(func, boundArgs) {
|
|
|
var placeholder = partial.placeholder;
|
|
|
var bound = function() {
|
|
|
var position = 0, length = boundArgs.length;
|
|
|
var args = Array(length);
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
|
|
|
}
|
|
|
while (position < arguments.length) args.push(arguments[position++]);
|
|
|
return executeBound(func, bound, this, this, args);
|
|
|
};
|
|
|
return bound;
|
|
|
});
|
|
|
|
|
|
partial.placeholder = _$1;
|
|
|
|
|
|
// Create a function bound to a given object (assigning `this`, and arguments,
|
|
|
// optionally).
|
|
|
var bind = restArguments(function(func, context, args) {
|
|
|
if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function');
|
|
|
var bound = restArguments(function(callArgs) {
|
|
|
return executeBound(func, bound, context, this, args.concat(callArgs));
|
|
|
});
|
|
|
return bound;
|
|
|
});
|
|
|
|
|
|
// Internal helper for collection methods to determine whether a collection
|
|
|
// should be iterated as an array or as an object.
|
|
|
// Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
|
|
|
// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
|
|
|
var isArrayLike = createSizePropertyCheck(getLength);
|
|
|
|
|
|
// Internal implementation of a recursive `flatten` function.
|
|
|
function flatten$1(input, depth, strict, output) {
|
|
|
output = output || [];
|
|
|
if (!depth && depth !== 0) {
|
|
|
depth = Infinity;
|
|
|
} else if (depth <= 0) {
|
|
|
return output.concat(input);
|
|
|
}
|
|
|
var idx = output.length;
|
|
|
for (var i = 0, length = getLength(input); i < length; i++) {
|
|
|
var value = input[i];
|
|
|
if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
|
|
|
// Flatten current level of array or arguments object.
|
|
|
if (depth > 1) {
|
|
|
flatten$1(value, depth - 1, strict, output);
|
|
|
idx = output.length;
|
|
|
} else {
|
|
|
var j = 0, len = value.length;
|
|
|
while (j < len) output[idx++] = value[j++];
|
|
|
}
|
|
|
} else if (!strict) {
|
|
|
output[idx++] = value;
|
|
|
}
|
|
|
}
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
// Bind a number of an object's methods to that object. Remaining arguments
|
|
|
// are the method names to be bound. Useful for ensuring that all callbacks
|
|
|
// defined on an object belong to it.
|
|
|
var bindAll = restArguments(function(obj, keys) {
|
|
|
keys = flatten$1(keys, false, false);
|
|
|
var index = keys.length;
|
|
|
if (index < 1) throw new Error('bindAll must be passed function names');
|
|
|
while (index--) {
|
|
|
var key = keys[index];
|
|
|
obj[key] = bind(obj[key], obj);
|
|
|
}
|
|
|
return obj;
|
|
|
});
|
|
|
|
|
|
// Memoize an expensive function by storing its results.
|
|
|
function memoize(func, hasher) {
|
|
|
var memoize = function(key) {
|
|
|
var cache = memoize.cache;
|
|
|
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
|
|
|
if (!has$1(cache, address)) cache[address] = func.apply(this, arguments);
|
|
|
return cache[address];
|
|
|
};
|
|
|
memoize.cache = {};
|
|
|
return memoize;
|
|
|
}
|
|
|
|
|
|
// Delays a function for the given number of milliseconds, and then calls
|
|
|
// it with the arguments supplied.
|
|
|
var delay = restArguments(function(func, wait, args) {
|
|
|
return setTimeout(function() {
|
|
|
return func.apply(null, args);
|
|
|
}, wait);
|
|
|
});
|
|
|
|
|
|
// Defers a function, scheduling it to run after the current call stack has
|
|
|
// cleared.
|
|
|
var defer = partial(delay, _$1, 1);
|
|
|
|
|
|
// Returns a function, that, when invoked, will only be triggered at most once
|
|
|
// during a given window of time. Normally, the throttled function will run
|
|
|
// as much as it can, without ever going more than once per `wait` duration;
|
|
|
// but if you'd like to disable the execution on the leading edge, pass
|
|
|
// `{leading: false}`. To disable execution on the trailing edge, ditto.
|
|
|
function throttle(func, wait, options) {
|
|
|
var timeout, context, args, result;
|
|
|
var previous = 0;
|
|
|
if (!options) options = {};
|
|
|
|
|
|
var later = function() {
|
|
|
previous = options.leading === false ? 0 : now();
|
|
|
timeout = null;
|
|
|
result = func.apply(context, args);
|
|
|
if (!timeout) context = args = null;
|
|
|
};
|
|
|
|
|
|
var throttled = function() {
|
|
|
var _now = now();
|
|
|
if (!previous && options.leading === false) previous = _now;
|
|
|
var remaining = wait - (_now - previous);
|
|
|
context = this;
|
|
|
args = arguments;
|
|
|
if (remaining <= 0 || remaining > wait) {
|
|
|
if (timeout) {
|
|
|
clearTimeout(timeout);
|
|
|
timeout = null;
|
|
|
}
|
|
|
previous = _now;
|
|
|
result = func.apply(context, args);
|
|
|
if (!timeout) context = args = null;
|
|
|
} else if (!timeout && options.trailing !== false) {
|
|
|
timeout = setTimeout(later, remaining);
|
|
|
}
|
|
|
return result;
|
|
|
};
|
|
|
|
|
|
throttled.cancel = function() {
|
|
|
clearTimeout(timeout);
|
|
|
previous = 0;
|
|
|
timeout = context = args = null;
|
|
|
};
|
|
|
|
|
|
return throttled;
|
|
|
}
|
|
|
|
|
|
// When a sequence of calls of the returned function ends, the argument
|
|
|
// function is triggered. The end of a sequence is defined by the `wait`
|
|
|
// parameter. If `immediate` is passed, the argument function will be
|
|
|
// triggered at the beginning of the sequence instead of at the end.
|
|
|
function debounce(func, wait, immediate) {
|
|
|
var timeout, previous, args, result, context;
|
|
|
|
|
|
var later = function() {
|
|
|
var passed = now() - previous;
|
|
|
if (wait > passed) {
|
|
|
timeout = setTimeout(later, wait - passed);
|
|
|
} else {
|
|
|
timeout = null;
|
|
|
if (!immediate) result = func.apply(context, args);
|
|
|
// This check is needed because `func` can recursively invoke `debounced`.
|
|
|
if (!timeout) args = context = null;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var debounced = restArguments(function(_args) {
|
|
|
context = this;
|
|
|
args = _args;
|
|
|
previous = now();
|
|
|
if (!timeout) {
|
|
|
timeout = setTimeout(later, wait);
|
|
|
if (immediate) result = func.apply(context, args);
|
|
|
}
|
|
|
return result;
|
|
|
});
|
|
|
|
|
|
debounced.cancel = function() {
|
|
|
clearTimeout(timeout);
|
|
|
timeout = args = context = null;
|
|
|
};
|
|
|
|
|
|
return debounced;
|
|
|
}
|
|
|
|
|
|
// Returns the first function passed as an argument to the second,
|
|
|
// allowing you to adjust arguments, run code before and after, and
|
|
|
// conditionally execute the original function.
|
|
|
function wrap(func, wrapper) {
|
|
|
return partial(wrapper, func);
|
|
|
}
|
|
|
|
|
|
// Returns a negated version of the passed-in predicate.
|
|
|
function negate(predicate) {
|
|
|
return function() {
|
|
|
return !predicate.apply(this, arguments);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Returns a function that is the composition of a list of functions, each
|
|
|
// consuming the return value of the function that follows.
|
|
|
function compose() {
|
|
|
var args = arguments;
|
|
|
var start = args.length - 1;
|
|
|
return function() {
|
|
|
var i = start;
|
|
|
var result = args[start].apply(this, arguments);
|
|
|
while (i--) result = args[i].call(this, result);
|
|
|
return result;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Returns a function that will only be executed on and after the Nth call.
|
|
|
function after(times, func) {
|
|
|
return function() {
|
|
|
if (--times < 1) {
|
|
|
return func.apply(this, arguments);
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Returns a function that will only be executed up to (but not including) the
|
|
|
// Nth call.
|
|
|
function before(times, func) {
|
|
|
var memo;
|
|
|
return function() {
|
|
|
if (--times > 0) {
|
|
|
memo = func.apply(this, arguments);
|
|
|
}
|
|
|
if (times <= 1) func = null;
|
|
|
return memo;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Returns a function that will be executed at most one time, no matter how
|
|
|
// often you call it. Useful for lazy initialization.
|
|
|
var once = partial(before, 2);
|
|
|
|
|
|
// Returns the first key on an object that passes a truth test.
|
|
|
function findKey(obj, predicate, context) {
|
|
|
predicate = cb(predicate, context);
|
|
|
var _keys = keys(obj), key;
|
|
|
for (var i = 0, length = _keys.length; i < length; i++) {
|
|
|
key = _keys[i];
|
|
|
if (predicate(obj[key], key, obj)) return key;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Internal function to generate `_.findIndex` and `_.findLastIndex`.
|
|
|
function createPredicateIndexFinder(dir) {
|
|
|
return function(array, predicate, context) {
|
|
|
predicate = cb(predicate, context);
|
|
|
var length = getLength(array);
|
|
|
var index = dir > 0 ? 0 : length - 1;
|
|
|
for (; index >= 0 && index < length; index += dir) {
|
|
|
if (predicate(array[index], index, array)) return index;
|
|
|
}
|
|
|
return -1;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Returns the first index on an array-like that passes a truth test.
|
|
|
var findIndex = createPredicateIndexFinder(1);
|
|
|
|
|
|
// Returns the last index on an array-like that passes a truth test.
|
|
|
var findLastIndex = createPredicateIndexFinder(-1);
|
|
|
|
|
|
// Use a comparator function to figure out the smallest index at which
|
|
|
// an object should be inserted so as to maintain order. Uses binary search.
|
|
|
function sortedIndex(array, obj, iteratee, context) {
|
|
|
iteratee = cb(iteratee, context, 1);
|
|
|
var value = iteratee(obj);
|
|
|
var low = 0, high = getLength(array);
|
|
|
while (low < high) {
|
|
|
var mid = Math.floor((low + high) / 2);
|
|
|
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
|
|
|
}
|
|
|
return low;
|
|
|
}
|
|
|
|
|
|
// Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions.
|
|
|
function createIndexFinder(dir, predicateFind, sortedIndex) {
|
|
|
return function(array, item, idx) {
|
|
|
var i = 0, length = getLength(array);
|
|
|
if (typeof idx == 'number') {
|
|
|
if (dir > 0) {
|
|
|
i = idx >= 0 ? idx : Math.max(idx + length, i);
|
|
|
} else {
|
|
|
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
|
|
|
}
|
|
|
} else if (sortedIndex && idx && length) {
|
|
|
idx = sortedIndex(array, item);
|
|
|
return array[idx] === item ? idx : -1;
|
|
|
}
|
|
|
if (item !== item) {
|
|
|
idx = predicateFind(slice.call(array, i, length), isNaN$1);
|
|
|
return idx >= 0 ? idx + i : -1;
|
|
|
}
|
|
|
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
|
|
|
if (array[idx] === item) return idx;
|
|
|
}
|
|
|
return -1;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Return the position of the first occurrence of an item in an array,
|
|
|
// or -1 if the item is not included in the array.
|
|
|
// If the array is large and already in sort order, pass `true`
|
|
|
// for **isSorted** to use binary search.
|
|
|
var indexOf = createIndexFinder(1, findIndex, sortedIndex);
|
|
|
|
|
|
// Return the position of the last occurrence of an item in an array,
|
|
|
// or -1 if the item is not included in the array.
|
|
|
var lastIndexOf = createIndexFinder(-1, findLastIndex);
|
|
|
|
|
|
// Return the first value which passes a truth test.
|
|
|
function find(obj, predicate, context) {
|
|
|
var keyFinder = isArrayLike(obj) ? findIndex : findKey;
|
|
|
var key = keyFinder(obj, predicate, context);
|
|
|
if (key !== void 0 && key !== -1) return obj[key];
|
|
|
}
|
|
|
|
|
|
// Convenience version of a common use case of `_.find`: getting the first
|
|
|
// object containing specific `key:value` pairs.
|
|
|
function findWhere(obj, attrs) {
|
|
|
return find(obj, matcher(attrs));
|
|
|
}
|
|
|
|
|
|
// The cornerstone for collection functions, an `each`
|
|
|
// implementation, aka `forEach`.
|
|
|
// Handles raw objects in addition to array-likes. Treats all
|
|
|
// sparse array-likes as if they were dense.
|
|
|
function each(obj, iteratee, context) {
|
|
|
iteratee = optimizeCb(iteratee, context);
|
|
|
var i, length;
|
|
|
if (isArrayLike(obj)) {
|
|
|
for (i = 0, length = obj.length; i < length; i++) {
|
|
|
iteratee(obj[i], i, obj);
|
|
|
}
|
|
|
} else {
|
|
|
var _keys = keys(obj);
|
|
|
for (i = 0, length = _keys.length; i < length; i++) {
|
|
|
iteratee(obj[_keys[i]], _keys[i], obj);
|
|
|
}
|
|
|
}
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
|
// Return the results of applying the iteratee to each element.
|
|
|
function map(obj, iteratee, context) {
|
|
|
iteratee = cb(iteratee, context);
|
|
|
var _keys = !isArrayLike(obj) && keys(obj),
|
|
|
length = (_keys || obj).length,
|
|
|
results = Array(length);
|
|
|
for (var index = 0; index < length; index++) {
|
|
|
var currentKey = _keys ? _keys[index] : index;
|
|
|
results[index] = iteratee(obj[currentKey], currentKey, obj);
|
|
|
}
|
|
|
return results;
|
|
|
}
|
|
|
|
|
|
// Internal helper to create a reducing function, iterating left or right.
|
|
|
function createReduce(dir) {
|
|
|
// Wrap code that reassigns argument variables in a separate function than
|
|
|
// the one that accesses `arguments.length` to avoid a perf hit. (#1991)
|
|
|
var reducer = function(obj, iteratee, memo, initial) {
|
|
|
var _keys = !isArrayLike(obj) && keys(obj),
|
|
|
length = (_keys || obj).length,
|
|
|
index = dir > 0 ? 0 : length - 1;
|
|
|
if (!initial) {
|
|
|
memo = obj[_keys ? _keys[index] : index];
|
|
|
index += dir;
|
|
|
}
|
|
|
for (; index >= 0 && index < length; index += dir) {
|
|
|
var currentKey = _keys ? _keys[index] : index;
|
|
|
memo = iteratee(memo, obj[currentKey], currentKey, obj);
|
|
|
}
|
|
|
return memo;
|
|
|
};
|
|
|
|
|
|
return function(obj, iteratee, memo, context) {
|
|
|
var initial = arguments.length >= 3;
|
|
|
return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
|
|
// or `foldl`.
|
|
|
var reduce = createReduce(1);
|
|
|
|
|
|
// The right-associative version of reduce, also known as `foldr`.
|
|
|
var reduceRight = createReduce(-1);
|
|
|
|
|
|
// Return all the elements that pass a truth test.
|
|
|
function filter(obj, predicate, context) {
|
|
|
var results = [];
|
|
|
predicate = cb(predicate, context);
|
|
|
each(obj, function(value, index, list) {
|
|
|
if (predicate(value, index, list)) results.push(value);
|
|
|
});
|
|
|
return results;
|
|
|
}
|
|
|
|
|
|
// Return all the elements for which a truth test fails.
|
|
|
function reject(obj, predicate, context) {
|
|
|
return filter(obj, negate(cb(predicate)), context);
|
|
|
}
|
|
|
|
|
|
// Determine whether all of the elements pass a truth test.
|
|
|
function every(obj, predicate, context) {
|
|
|
predicate = cb(predicate, context);
|
|
|
var _keys = !isArrayLike(obj) && keys(obj),
|
|
|
length = (_keys || obj).length;
|
|
|
for (var index = 0; index < length; index++) {
|
|
|
var currentKey = _keys ? _keys[index] : index;
|
|
|
if (!predicate(obj[currentKey], currentKey, obj)) return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// Determine if at least one element in the object passes a truth test.
|
|
|
function some(obj, predicate, context) {
|
|
|
predicate = cb(predicate, context);
|
|
|
var _keys = !isArrayLike(obj) && keys(obj),
|
|
|
length = (_keys || obj).length;
|
|
|
for (var index = 0; index < length; index++) {
|
|
|
var currentKey = _keys ? _keys[index] : index;
|
|
|
if (predicate(obj[currentKey], currentKey, obj)) return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// Determine if the array or object contains a given item (using `===`).
|
|
|
function contains(obj, item, fromIndex, guard) {
|
|
|
if (!isArrayLike(obj)) obj = values(obj);
|
|
|
if (typeof fromIndex != 'number' || guard) fromIndex = 0;
|
|
|
return indexOf(obj, item, fromIndex) >= 0;
|
|
|
}
|
|
|
|
|
|
// Invoke a method (with arguments) on every item in a collection.
|
|
|
var invoke = restArguments(function(obj, path, args) {
|
|
|
var contextPath, func;
|
|
|
if (isFunction$1(path)) {
|
|
|
func = path;
|
|
|
} else {
|
|
|
path = toPath(path);
|
|
|
contextPath = path.slice(0, -1);
|
|
|
path = path[path.length - 1];
|
|
|
}
|
|
|
return map(obj, function(context) {
|
|
|
var method = func;
|
|
|
if (!method) {
|
|
|
if (contextPath && contextPath.length) {
|
|
|
context = deepGet(context, contextPath);
|
|
|
}
|
|
|
if (context == null) return void 0;
|
|
|
method = context[path];
|
|
|
}
|
|
|
return method == null ? method : method.apply(context, args);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// Convenience version of a common use case of `_.map`: fetching a property.
|
|
|
function pluck(obj, key) {
|
|
|
return map(obj, property(key));
|
|
|
}
|
|
|
|
|
|
// Convenience version of a common use case of `_.filter`: selecting only
|
|
|
// objects containing specific `key:value` pairs.
|
|
|
function where(obj, attrs) {
|
|
|
return filter(obj, matcher(attrs));
|
|
|
}
|
|
|
|
|
|
// Return the maximum element (or element-based computation).
|
|
|
function max(obj, iteratee, context) {
|
|
|
var result = -Infinity, lastComputed = -Infinity,
|
|
|
value, computed;
|
|
|
if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null)) {
|
|
|
obj = isArrayLike(obj) ? obj : values(obj);
|
|
|
for (var i = 0, length = obj.length; i < length; i++) {
|
|
|
value = obj[i];
|
|
|
if (value != null && value > result) {
|
|
|
result = value;
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
iteratee = cb(iteratee, context);
|
|
|
each(obj, function(v, index, list) {
|
|
|
computed = iteratee(v, index, list);
|
|
|
if (computed > lastComputed || (computed === -Infinity && result === -Infinity)) {
|
|
|
result = v;
|
|
|
lastComputed = computed;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Return the minimum element (or element-based computation).
|
|
|
function min(obj, iteratee, context) {
|
|
|
var result = Infinity, lastComputed = Infinity,
|
|
|
value, computed;
|
|
|
if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null)) {
|
|
|
obj = isArrayLike(obj) ? obj : values(obj);
|
|
|
for (var i = 0, length = obj.length; i < length; i++) {
|
|
|
value = obj[i];
|
|
|
if (value != null && value < result) {
|
|
|
result = value;
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
iteratee = cb(iteratee, context);
|
|
|
each(obj, function(v, index, list) {
|
|
|
computed = iteratee(v, index, list);
|
|
|
if (computed < lastComputed || (computed === Infinity && result === Infinity)) {
|
|
|
result = v;
|
|
|
lastComputed = computed;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Safely create a real, live array from anything iterable.
|
|
|
var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
|
|
|
function toArray(obj) {
|
|
|
if (!obj) return [];
|
|
|
if (isArray(obj)) return slice.call(obj);
|
|
|
if (isString(obj)) {
|
|
|
// Keep surrogate pair characters together.
|
|
|
return obj.match(reStrSymbol);
|
|
|
}
|
|
|
if (isArrayLike(obj)) return map(obj, identity);
|
|
|
return values(obj);
|
|
|
}
|
|
|
|
|
|
// Sample **n** random values from a collection using the modern version of the
|
|
|
// [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
|
|
|
// If **n** is not specified, returns a single random element.
|
|
|
// The internal `guard` argument allows it to work with `_.map`.
|
|
|
function sample(obj, n, guard) {
|
|
|
if (n == null || guard) {
|
|
|
if (!isArrayLike(obj)) obj = values(obj);
|
|
|
return obj[random(obj.length - 1)];
|
|
|
}
|
|
|
var sample = toArray(obj);
|
|
|
var length = getLength(sample);
|
|
|
n = Math.max(Math.min(n, length), 0);
|
|
|
var last = length - 1;
|
|
|
for (var index = 0; index < n; index++) {
|
|
|
var rand = random(index, last);
|
|
|
var temp = sample[index];
|
|
|
sample[index] = sample[rand];
|
|
|
sample[rand] = temp;
|
|
|
}
|
|
|
return sample.slice(0, n);
|
|
|
}
|
|
|
|
|
|
// Shuffle a collection.
|
|
|
function shuffle(obj) {
|
|
|
return sample(obj, Infinity);
|
|
|
}
|
|
|
|
|
|
// Sort the object's values by a criterion produced by an iteratee.
|
|
|
function sortBy(obj, iteratee, context) {
|
|
|
var index = 0;
|
|
|
iteratee = cb(iteratee, context);
|
|
|
return pluck(map(obj, function(value, key, list) {
|
|
|
return {
|
|
|
value: value,
|
|
|
index: index++,
|
|
|
criteria: iteratee(value, key, list)
|
|
|
};
|
|
|
}).sort(function(left, right) {
|
|
|
var a = left.criteria;
|
|
|
var b = right.criteria;
|
|
|
if (a !== b) {
|
|
|
if (a > b || a === void 0) return 1;
|
|
|
if (a < b || b === void 0) return -1;
|
|
|
}
|
|
|
return left.index - right.index;
|
|
|
}), 'value');
|
|
|
}
|
|
|
|
|
|
// An internal function used for aggregate "group by" operations.
|
|
|
function group(behavior, partition) {
|
|
|
return function(obj, iteratee, context) {
|
|
|
var result = partition ? [[], []] : {};
|
|
|
iteratee = cb(iteratee, context);
|
|
|
each(obj, function(value, index) {
|
|
|
var key = iteratee(value, index, obj);
|
|
|
behavior(result, value, key);
|
|
|
});
|
|
|
return result;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Groups the object's values by a criterion. Pass either a string attribute
|
|
|
// to group by, or a function that returns the criterion.
|
|
|
var groupBy = group(function(result, value, key) {
|
|
|
if (has$1(result, key)) result[key].push(value); else result[key] = [value];
|
|
|
});
|
|
|
|
|
|
// Indexes the object's values by a criterion, similar to `_.groupBy`, but for
|
|
|
// when you know that your index values will be unique.
|
|
|
var indexBy = group(function(result, value, key) {
|
|
|
result[key] = value;
|
|
|
});
|
|
|
|
|
|
// Counts instances of an object that group by a certain criterion. Pass
|
|
|
// either a string attribute to count by, or a function that returns the
|
|
|
// criterion.
|
|
|
var countBy = group(function(result, value, key) {
|
|
|
if (has$1(result, key)) result[key]++; else result[key] = 1;
|
|
|
});
|
|
|
|
|
|
// Split a collection into two arrays: one whose elements all pass the given
|
|
|
// truth test, and one whose elements all do not pass the truth test.
|
|
|
var partition = group(function(result, value, pass) {
|
|
|
result[pass ? 0 : 1].push(value);
|
|
|
}, true);
|
|
|
|
|
|
// Return the number of elements in a collection.
|
|
|
function size(obj) {
|
|
|
if (obj == null) return 0;
|
|
|
return isArrayLike(obj) ? obj.length : keys(obj).length;
|
|
|
}
|
|
|
|
|
|
// Internal `_.pick` helper function to determine whether `key` is an enumerable
|
|
|
// property name of `obj`.
|
|
|
function keyInObj(value, key, obj) {
|
|
|
return key in obj;
|
|
|
}
|
|
|
|
|
|
// Return a copy of the object only containing the allowed properties.
|
|
|
var pick = restArguments(function(obj, keys) {
|
|
|
var result = {}, iteratee = keys[0];
|
|
|
if (obj == null) return result;
|
|
|
if (isFunction$1(iteratee)) {
|
|
|
if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
|
|
|
keys = allKeys(obj);
|
|
|
} else {
|
|
|
iteratee = keyInObj;
|
|
|
keys = flatten$1(keys, false, false);
|
|
|
obj = Object(obj);
|
|
|
}
|
|
|
for (var i = 0, length = keys.length; i < length; i++) {
|
|
|
var key = keys[i];
|
|
|
var value = obj[key];
|
|
|
if (iteratee(value, key, obj)) result[key] = value;
|
|
|
}
|
|
|
return result;
|
|
|
});
|
|
|
|
|
|
// Return a copy of the object without the disallowed properties.
|
|
|
var omit = restArguments(function(obj, keys) {
|
|
|
var iteratee = keys[0], context;
|
|
|
if (isFunction$1(iteratee)) {
|
|
|
iteratee = negate(iteratee);
|
|
|
if (keys.length > 1) context = keys[1];
|
|
|
} else {
|
|
|
keys = map(flatten$1(keys, false, false), String);
|
|
|
iteratee = function(value, key) {
|
|
|
return !contains(keys, key);
|
|
|
};
|
|
|
}
|
|
|
return pick(obj, iteratee, context);
|
|
|
});
|
|
|
|
|
|
// Returns everything but the last entry of the array. Especially useful on
|
|
|
// the arguments object. Passing **n** will return all the values in
|
|
|
// the array, excluding the last N.
|
|
|
function initial(array, n, guard) {
|
|
|
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
|
|
|
}
|
|
|
|
|
|
// Get the first element of an array. Passing **n** will return the first N
|
|
|
// values in the array. The **guard** check allows it to work with `_.map`.
|
|
|
function first(array, n, guard) {
|
|
|
if (array == null || array.length < 1) return n == null || guard ? void 0 : [];
|
|
|
if (n == null || guard) return array[0];
|
|
|
return initial(array, array.length - n);
|
|
|
}
|
|
|
|
|
|
// Returns everything but the first entry of the `array`. Especially useful on
|
|
|
// the `arguments` object. Passing an **n** will return the rest N values in the
|
|
|
// `array`.
|
|
|
function rest(array, n, guard) {
|
|
|
return slice.call(array, n == null || guard ? 1 : n);
|
|
|
}
|
|
|
|
|
|
// Get the last element of an array. Passing **n** will return the last N
|
|
|
// values in the array.
|
|
|
function last(array, n, guard) {
|
|
|
if (array == null || array.length < 1) return n == null || guard ? void 0 : [];
|
|
|
if (n == null || guard) return array[array.length - 1];
|
|
|
return rest(array, Math.max(0, array.length - n));
|
|
|
}
|
|
|
|
|
|
// Trim out all falsy values from an array.
|
|
|
function compact(array) {
|
|
|
return filter(array, Boolean);
|
|
|
}
|
|
|
|
|
|
// Flatten out an array, either recursively (by default), or up to `depth`.
|
|
|
// Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively.
|
|
|
function flatten(array, depth) {
|
|
|
return flatten$1(array, depth, false);
|
|
|
}
|
|
|
|
|
|
// Take the difference between one array and a number of other arrays.
|
|
|
// Only the elements present in just the first array will remain.
|
|
|
var difference = restArguments(function(array, rest) {
|
|
|
rest = flatten$1(rest, true, true);
|
|
|
return filter(array, function(value){
|
|
|
return !contains(rest, value);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// Return a version of the array that does not contain the specified value(s).
|
|
|
var without = restArguments(function(array, otherArrays) {
|
|
|
return difference(array, otherArrays);
|
|
|
});
|
|
|
|
|
|
// Produce a duplicate-free version of the array. If the array has already
|
|
|
// been sorted, you have the option of using a faster algorithm.
|
|
|
// The faster algorithm will not work with an iteratee if the iteratee
|
|
|
// is not a one-to-one function, so providing an iteratee will disable
|
|
|
// the faster algorithm.
|
|
|
function uniq(array, isSorted, iteratee, context) {
|
|
|
if (!isBoolean(isSorted)) {
|
|
|
context = iteratee;
|
|
|
iteratee = isSorted;
|
|
|
isSorted = false;
|
|
|
}
|
|
|
if (iteratee != null) iteratee = cb(iteratee, context);
|
|
|
var result = [];
|
|
|
var seen = [];
|
|
|
for (var i = 0, length = getLength(array); i < length; i++) {
|
|
|
var value = array[i],
|
|
|
computed = iteratee ? iteratee(value, i, array) : value;
|
|
|
if (isSorted && !iteratee) {
|
|
|
if (!i || seen !== computed) result.push(value);
|
|
|
seen = computed;
|
|
|
} else if (iteratee) {
|
|
|
if (!contains(seen, computed)) {
|
|
|
seen.push(computed);
|
|
|
result.push(value);
|
|
|
}
|
|
|
} else if (!contains(result, value)) {
|
|
|
result.push(value);
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Produce an array that contains the union: each distinct element from all of
|
|
|
// the passed-in arrays.
|
|
|
var union = restArguments(function(arrays) {
|
|
|
return uniq(flatten$1(arrays, true, true));
|
|
|
});
|
|
|
|
|
|
// Produce an array that contains every item shared between all the
|
|
|
// passed-in arrays.
|
|
|
function intersection(array) {
|
|
|
var result = [];
|
|
|
var argsLength = arguments.length;
|
|
|
for (var i = 0, length = getLength(array); i < length; i++) {
|
|
|
var item = array[i];
|
|
|
if (contains(result, item)) continue;
|
|
|
var j;
|
|
|
for (j = 1; j < argsLength; j++) {
|
|
|
if (!contains(arguments[j], item)) break;
|
|
|
}
|
|
|
if (j === argsLength) result.push(item);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Complement of zip. Unzip accepts an array of arrays and groups
|
|
|
// each array's elements on shared indices.
|
|
|
function unzip(array) {
|
|
|
var length = (array && max(array, getLength).length) || 0;
|
|
|
var result = Array(length);
|
|
|
|
|
|
for (var index = 0; index < length; index++) {
|
|
|
result[index] = pluck(array, index);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Zip together multiple lists into a single array -- elements that share
|
|
|
// an index go together.
|
|
|
var zip = restArguments(unzip);
|
|
|
|
|
|
// Converts lists into objects. Pass either a single array of `[key, value]`
|
|
|
// pairs, or two parallel arrays of the same length -- one of keys, and one of
|
|
|
// the corresponding values. Passing by pairs is the reverse of `_.pairs`.
|
|
|
function object(list, values) {
|
|
|
var result = {};
|
|
|
for (var i = 0, length = getLength(list); i < length; i++) {
|
|
|
if (values) {
|
|
|
result[list[i]] = values[i];
|
|
|
} else {
|
|
|
result[list[i][0]] = list[i][1];
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Generate an integer Array containing an arithmetic progression. A port of
|
|
|
// the native Python `range()` function. See
|
|
|
// [the Python documentation](https://docs.python.org/library/functions.html#range).
|
|
|
function range(start, stop, step) {
|
|
|
if (stop == null) {
|
|
|
stop = start || 0;
|
|
|
start = 0;
|
|
|
}
|
|
|
if (!step) {
|
|
|
step = stop < start ? -1 : 1;
|
|
|
}
|
|
|
|
|
|
var length = Math.max(Math.ceil((stop - start) / step), 0);
|
|
|
var range = Array(length);
|
|
|
|
|
|
for (var idx = 0; idx < length; idx++, start += step) {
|
|
|
range[idx] = start;
|
|
|
}
|
|
|
|
|
|
return range;
|
|
|
}
|
|
|
|
|
|
// Chunk a single array into multiple arrays, each containing `count` or fewer
|
|
|
// items.
|
|
|
function chunk(array, count) {
|
|
|
if (count == null || count < 1) return [];
|
|
|
var result = [];
|
|
|
var i = 0, length = array.length;
|
|
|
while (i < length) {
|
|
|
result.push(slice.call(array, i, i += count));
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// Helper function to continue chaining intermediate results.
|
|
|
function chainResult(instance, obj) {
|
|
|
return instance._chain ? _$1(obj).chain() : obj;
|
|
|
}
|
|
|
|
|
|
// Add your own custom functions to the Underscore object.
|
|
|
function mixin(obj) {
|
|
|
each(functions(obj), function(name) {
|
|
|
var func = _$1[name] = obj[name];
|
|
|
_$1.prototype[name] = function() {
|
|
|
var args = [this._wrapped];
|
|
|
push.apply(args, arguments);
|
|
|
return chainResult(this, func.apply(_$1, args));
|
|
|
};
|
|
|
});
|
|
|
return _$1;
|
|
|
}
|
|
|
|
|
|
// Add all mutator `Array` functions to the wrapper.
|
|
|
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
|
|
var method = ArrayProto[name];
|
|
|
_$1.prototype[name] = function() {
|
|
|
var obj = this._wrapped;
|
|
|
if (obj != null) {
|
|
|
method.apply(obj, arguments);
|
|
|
if ((name === 'shift' || name === 'splice') && obj.length === 0) {
|
|
|
delete obj[0];
|
|
|
}
|
|
|
}
|
|
|
return chainResult(this, obj);
|
|
|
};
|
|
|
});
|
|
|
|
|
|
// Add all accessor `Array` functions to the wrapper.
|
|
|
each(['concat', 'join', 'slice'], function(name) {
|
|
|
var method = ArrayProto[name];
|
|
|
_$1.prototype[name] = function() {
|
|
|
var obj = this._wrapped;
|
|
|
if (obj != null) obj = method.apply(obj, arguments);
|
|
|
return chainResult(this, obj);
|
|
|
};
|
|
|
});
|
|
|
|
|
|
// Named Exports
|
|
|
|
|
|
var allExports = {
|
|
|
__proto__: null,
|
|
|
VERSION: VERSION,
|
|
|
restArguments: restArguments,
|
|
|
isObject: isObject,
|
|
|
isNull: isNull,
|
|
|
isUndefined: isUndefined,
|
|
|
isBoolean: isBoolean,
|
|
|
isElement: isElement,
|
|
|
isString: isString,
|
|
|
isNumber: isNumber,
|
|
|
isDate: isDate,
|
|
|
isRegExp: isRegExp,
|
|
|
isError: isError,
|
|
|
isSymbol: isSymbol,
|
|
|
isArrayBuffer: isArrayBuffer,
|
|
|
isDataView: isDataView$1,
|
|
|
isArray: isArray,
|
|
|
isFunction: isFunction$1,
|
|
|
isArguments: isArguments$1,
|
|
|
isFinite: isFinite$1,
|
|
|
isNaN: isNaN$1,
|
|
|
isTypedArray: isTypedArray$1,
|
|
|
isEmpty: isEmpty,
|
|
|
isMatch: isMatch,
|
|
|
isEqual: isEqual,
|
|
|
isMap: isMap,
|
|
|
isWeakMap: isWeakMap,
|
|
|
isSet: isSet,
|
|
|
isWeakSet: isWeakSet,
|
|
|
keys: keys,
|
|
|
allKeys: allKeys,
|
|
|
values: values,
|
|
|
pairs: pairs,
|
|
|
invert: invert,
|
|
|
functions: functions,
|
|
|
methods: functions,
|
|
|
extend: extend,
|
|
|
extendOwn: extendOwn,
|
|
|
assign: extendOwn,
|
|
|
defaults: defaults,
|
|
|
create: create,
|
|
|
clone: clone,
|
|
|
tap: tap,
|
|
|
get: get,
|
|
|
has: has,
|
|
|
mapObject: mapObject,
|
|
|
identity: identity,
|
|
|
constant: constant,
|
|
|
noop: noop,
|
|
|
toPath: toPath$1,
|
|
|
property: property,
|
|
|
propertyOf: propertyOf,
|
|
|
matcher: matcher,
|
|
|
matches: matcher,
|
|
|
times: times,
|
|
|
random: random,
|
|
|
now: now,
|
|
|
escape: _escape,
|
|
|
unescape: _unescape,
|
|
|
templateSettings: templateSettings,
|
|
|
template: template,
|
|
|
result: result,
|
|
|
uniqueId: uniqueId,
|
|
|
chain: chain,
|
|
|
iteratee: iteratee,
|
|
|
partial: partial,
|
|
|
bind: bind,
|
|
|
bindAll: bindAll,
|
|
|
memoize: memoize,
|
|
|
delay: delay,
|
|
|
defer: defer,
|
|
|
throttle: throttle,
|
|
|
debounce: debounce,
|
|
|
wrap: wrap,
|
|
|
negate: negate,
|
|
|
compose: compose,
|
|
|
after: after,
|
|
|
before: before,
|
|
|
once: once,
|
|
|
findKey: findKey,
|
|
|
findIndex: findIndex,
|
|
|
findLastIndex: findLastIndex,
|
|
|
sortedIndex: sortedIndex,
|
|
|
indexOf: indexOf,
|
|
|
lastIndexOf: lastIndexOf,
|
|
|
find: find,
|
|
|
detect: find,
|
|
|
findWhere: findWhere,
|
|
|
each: each,
|
|
|
forEach: each,
|
|
|
map: map,
|
|
|
collect: map,
|
|
|
reduce: reduce,
|
|
|
foldl: reduce,
|
|
|
inject: reduce,
|
|
|
reduceRight: reduceRight,
|
|
|
foldr: reduceRight,
|
|
|
filter: filter,
|
|
|
select: filter,
|
|
|
reject: reject,
|
|
|
every: every,
|
|
|
all: every,
|
|
|
some: some,
|
|
|
any: some,
|
|
|
contains: contains,
|
|
|
includes: contains,
|
|
|
include: contains,
|
|
|
invoke: invoke,
|
|
|
pluck: pluck,
|
|
|
where: where,
|
|
|
max: max,
|
|
|
min: min,
|
|
|
shuffle: shuffle,
|
|
|
sample: sample,
|
|
|
sortBy: sortBy,
|
|
|
groupBy: groupBy,
|
|
|
indexBy: indexBy,
|
|
|
countBy: countBy,
|
|
|
partition: partition,
|
|
|
toArray: toArray,
|
|
|
size: size,
|
|
|
pick: pick,
|
|
|
omit: omit,
|
|
|
first: first,
|
|
|
head: first,
|
|
|
take: first,
|
|
|
initial: initial,
|
|
|
last: last,
|
|
|
rest: rest,
|
|
|
tail: rest,
|
|
|
drop: rest,
|
|
|
compact: compact,
|
|
|
flatten: flatten,
|
|
|
without: without,
|
|
|
uniq: uniq,
|
|
|
unique: uniq,
|
|
|
union: union,
|
|
|
intersection: intersection,
|
|
|
difference: difference,
|
|
|
unzip: unzip,
|
|
|
transpose: unzip,
|
|
|
zip: zip,
|
|
|
object: object,
|
|
|
range: range,
|
|
|
chunk: chunk,
|
|
|
mixin: mixin,
|
|
|
'default': _$1
|
|
|
};
|
|
|
|
|
|
// Default Export
|
|
|
|
|
|
// Add all of the Underscore functions to the wrapper object.
|
|
|
var _ = mixin(allExports);
|
|
|
// Legacy Node.js API.
|
|
|
_._ = _;
|
|
|
|
|
|
return _;
|
|
|
|
|
|
})));
|
|
|
//# sourceMappingURL=underscore-umd.js.map
|
|
|
|
|
|
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
|
|
|
|
|
|
/***/ }),
|
|
|
/* 3 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 4 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_4__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 5 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_5__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 6 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_RESULT__;/*global define */
|
|
|
!(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, module) {
|
|
|
var $ = __webpack_require__(1);
|
|
|
var _ = __webpack_require__(2);
|
|
|
var Backbone = __webpack_require__(7);
|
|
|
var mvc = __webpack_require__(8);
|
|
|
var splunk_util = __webpack_require__(9);
|
|
|
var TimeRangeInput = __webpack_require__(10);
|
|
|
var DropdownView = __webpack_require__(11);
|
|
|
var TokenUtils = __webpack_require__(12);
|
|
|
var MaintenanceModeBannerView = __webpack_require__(13);
|
|
|
var i18n = __webpack_require__(14);
|
|
|
|
|
|
_.mixin({
|
|
|
t: function(string) {
|
|
|
return i18n._(string)
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* Safely remove the view if existing and attached to the DOM
|
|
|
* @param {String} viewId - ID of the view to remove
|
|
|
*/
|
|
|
var safeRemoveView = function(viewId) {
|
|
|
var oldView = mvc.Components.getInstance(viewId);
|
|
|
if (oldView) {
|
|
|
oldView.remove();
|
|
|
mvc.Components.revokeInstance(viewId);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var insertTimePicker = function() {
|
|
|
var timePickerInsertionPriority = [
|
|
|
'div.dashboard-header div.edit-menu.splunkjs-mvc-simplexml-editdashboard-menuview', // Splunk 6.4 and prior
|
|
|
'div.dashboard-header span.dashboard-view-controls', // Splunk 6.5
|
|
|
'#context' //default
|
|
|
];
|
|
|
|
|
|
var insertionSelector = _.find(timePickerInsertionPriority, function(selector) {
|
|
|
return $(selector).length > 0;
|
|
|
});
|
|
|
|
|
|
$(insertionSelector)
|
|
|
.append('<div class="input-timerangepicker pull-right" style="padding-left: 8px;" id="entity-context-panel-time-picker"></div>');
|
|
|
|
|
|
safeRemoveView('entity-context-panel-time-picker');
|
|
|
|
|
|
var timePicker = new TimeRangeInput({
|
|
|
'id': 'entity-context-panel-time-picker',
|
|
|
'searchWhenChanged': true,
|
|
|
'earliest_time': '$earliest$',
|
|
|
'latest_time': '$latest$',
|
|
|
'el': $('#entity-context-panel-time-picker')
|
|
|
}, {tokens: true}).render();
|
|
|
|
|
|
timePicker.on('change', function (newValue) {
|
|
|
TokenUtils.submitTokens({
|
|
|
earliest: newValue.earliest_time,
|
|
|
latest: newValue.latest_time
|
|
|
});
|
|
|
});
|
|
|
};
|
|
|
|
|
|
var restoreTimePickerAsNeeded = function() {
|
|
|
/* In Splunk 6.5, the edit feature adds the banner object and cycles it during
|
|
|
* edit/view. Earlier versions are less destructive and leave the TimePicker in
|
|
|
* place, so restoring it isn't necessary.
|
|
|
*/
|
|
|
var banners = $('header[role="banner"]');
|
|
|
if (banners.length === 0) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/* Defer places the operation at the end of the stack of processes the JS engine
|
|
|
* will call, ensuring that this operation happens after the view restoration is
|
|
|
* complete.
|
|
|
*/
|
|
|
var headerObserver = new window.MutationObserver(function(mutations) {
|
|
|
_.defer(function() {
|
|
|
if ($('header .edit-cancel').length === 0) {
|
|
|
var oldTimepicker = mvc.Components.getInstance('entity-context-panel-time-picker');
|
|
|
if (oldTimepicker) {
|
|
|
oldTimepicker.remove();
|
|
|
mvc.Components.revokeInstance('entity-context-panel-time-picker');
|
|
|
}
|
|
|
insertTimePicker();
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
headerObserver.observe(banners.get(0), {
|
|
|
attributes: false, characterData: false, childList: true
|
|
|
});
|
|
|
};
|
|
|
|
|
|
|
|
|
var EntityContextPanel = Backbone.View.extend({
|
|
|
/* Describes the endpoint to get all entities and determine whether they are in maintenance mode */
|
|
|
MAINTENANCE_CALENDAR_ENDPOINT: splunk_util.make_url(
|
|
|
[
|
|
|
'splunkd',
|
|
|
'__raw',
|
|
|
'servicesNS',
|
|
|
'nobody',
|
|
|
'SA-ITOA',
|
|
|
'maintenance_services_interface',
|
|
|
'maintenance_calendar'
|
|
|
].join('/')),
|
|
|
events: {
|
|
|
'click #open-inventory-modal' : function(e) {
|
|
|
$('#inventory-fields-modal').modal('show');
|
|
|
}
|
|
|
},
|
|
|
/**
|
|
|
* Initialize the view
|
|
|
*
|
|
|
*
|
|
|
* @param {Object} options
|
|
|
* @public
|
|
|
*/
|
|
|
initialize: function (options) {
|
|
|
|
|
|
Backbone.View.prototype.initialize.apply(this, arguments);
|
|
|
|
|
|
this.minichart_id = this.model.get('header_id') + '_minicharts';
|
|
|
|
|
|
// Hook up all model listeners
|
|
|
this.listenTo(this.model, {
|
|
|
'change:entities': this._updateEntityList,
|
|
|
'change:current_entity_info': this._updateEntityInfoView,
|
|
|
'change:context_content_title': this._updateContextView
|
|
|
}, this);
|
|
|
|
|
|
/* Event handler registers only after search completes,
|
|
|
* this guarantees that this.entities gets populated no matter what
|
|
|
*/
|
|
|
this._updateEntityList();
|
|
|
|
|
|
if (_.isEmpty(this.model.get('current_entity'))) {
|
|
|
this.currentEntityTitle = _('Select an entity').t();
|
|
|
} else {
|
|
|
this.currentEntityTitle = this.model.get('current_entity').title;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Gets the current entity's key
|
|
|
*
|
|
|
**/
|
|
|
_getCurrentEntityKey: function() {
|
|
|
return (this.model.get('current_entity').id || this.model.get('current_entity')._key);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Gets the url for entity detail page based on the selected entity
|
|
|
*
|
|
|
**/
|
|
|
_getEntityDetailUrl: function() {
|
|
|
/* On initial drilldown load, current_entity gets picked up from payload with entity key
|
|
|
* stored in a field called "id". On entity change, the entity key is picked up from the
|
|
|
* list of entities that were returned from the search, and this field is now stored in
|
|
|
* "_key".
|
|
|
*/
|
|
|
return (splunk_util.make_url(['app', 'itsi', 'entity_detail'].join('/')) +
|
|
|
'?entity_key=' + this._getCurrentEntityKey());
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Render the timepicker, context views and the mini charts
|
|
|
*
|
|
|
* @public
|
|
|
*/
|
|
|
render: function () {
|
|
|
// Fetch from model for model specific context info
|
|
|
return this.model.fetch().done(_.bind(function () {
|
|
|
var context_title = '';
|
|
|
if (!this.model.get('is_from_entity_details')){
|
|
|
context_title = this.model.get('context_content_title');
|
|
|
}
|
|
|
this.$el.html(this._template({
|
|
|
context_title: context_title,
|
|
|
context_value: this.model.get('context_content_value'),
|
|
|
context_url: this.model.get('context_content_link'),
|
|
|
minichart_id: this.minichart_id
|
|
|
}));
|
|
|
|
|
|
/* HACK:
|
|
|
* As per the UI requirements, this appends the timepicker object to the
|
|
|
* top-right corner along with the edit dropdown and "More info" dropdown
|
|
|
* Since the classes and structure of the page has changed across
|
|
|
* releases, best option is to try each one until we find a suitable
|
|
|
* location. The last default option will always work.
|
|
|
*/
|
|
|
insertTimePicker();
|
|
|
|
|
|
/* SECONDARY HACK
|
|
|
* Restore the timePicker whenever the edit header indicates it is no
|
|
|
* longer in EDIT mode. This is safe as previous timePickers stop
|
|
|
* eventing, and the current one is just putting time ranges onto the
|
|
|
* token bus.
|
|
|
*/
|
|
|
restoreTimePickerAsNeeded();
|
|
|
|
|
|
safeRemoveView('entity-context-panel-entity-dropdown');
|
|
|
|
|
|
var entityPicker = new DropdownView({
|
|
|
'id': 'entity-context-panel-entity-dropdown',
|
|
|
'choices': this.entities,
|
|
|
'labelField': 'title',
|
|
|
'valueField': 'title',
|
|
|
'showClearButton': false,
|
|
|
'default': this.currentEntityTitle,
|
|
|
'el': $('#entity-context-panel-entity-picker')
|
|
|
}).render();
|
|
|
|
|
|
entityPicker.on('change', function (evt) {
|
|
|
if (evt !== undefined) {
|
|
|
this.currentEntityTitle = evt;
|
|
|
var entity = _.find(this.entities, function(item) {
|
|
|
return item.value === evt;
|
|
|
});
|
|
|
|
|
|
// Update the model
|
|
|
this.model.set('current_entity', _.find(this.model.get('entities'), function(item) {
|
|
|
return item.title === evt;
|
|
|
}));
|
|
|
|
|
|
var tokens = _.pick(entity.data, this.model.get('requested_entity_tokens'));
|
|
|
TokenUtils.submitTokens(tokens);
|
|
|
this._clearEntityInfoView();
|
|
|
}
|
|
|
}, this);
|
|
|
}, this));
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Updates the entity list for the dropdown view
|
|
|
*
|
|
|
* @private
|
|
|
*/
|
|
|
_updateEntityList: function() {
|
|
|
this.entities = _.map(this.model.get('entities'), function(item) {
|
|
|
var rowItem = {};
|
|
|
rowItem['label'] = item['title'];
|
|
|
rowItem['value'] = item['title'];
|
|
|
rowItem['data'] = item;
|
|
|
return rowItem;
|
|
|
});
|
|
|
|
|
|
var dropdown = mvc.Components.get('entity-context-panel-entity-dropdown');
|
|
|
if (dropdown) {
|
|
|
dropdown.settings.set('choices', this.entities);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Updates the context view when the base page model changes
|
|
|
*
|
|
|
* @private
|
|
|
*/
|
|
|
_updateContextView: function () {
|
|
|
$('#entity-context-panel-contextLink').attr('href', this.model.get('context_content_link'));
|
|
|
$('#entity-context-panel-contextLink').text(this.model.get('context_content_value'));
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Does a lookup of the entity given its key against the REST endpoint
|
|
|
* containing all the entities that have a maintenance window, and determines
|
|
|
* whether given the current time, the entity is still in a maintenance window
|
|
|
*
|
|
|
* @private
|
|
|
*
|
|
|
* @params {object} maintenance_calendars
|
|
|
**/
|
|
|
_isEntityInMaintenanceWindow: function(maintenance_calendars) {
|
|
|
var current_entity_key = this._getCurrentEntityKey();
|
|
|
// getTime() returns value in milliseconds, so needs to be adjusted to seconds
|
|
|
var current_time = new Date().getTime() / 1000;
|
|
|
return _.some(maintenance_calendars, function(calendar) {
|
|
|
return _.some(calendar['objects'], function(element) {
|
|
|
return element['object_type'] === 'entity' && element['_key'] === current_entity_key &&
|
|
|
current_time > calendar['start_time'] && current_time < calendar['end_time'];
|
|
|
});
|
|
|
});
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Clears all banners inside the maintenance-mode container. This will be called when
|
|
|
* a new banner gets rendered for maintenance mode, or if the entity was switched from
|
|
|
* one that was in maintenance mode to one that isn't.
|
|
|
**/
|
|
|
_clearMaintenanceWindowBanner: function() {
|
|
|
$(".maintenance-mode").empty();
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Renders the maintenance window banner if the selected entity in the dropdown
|
|
|
* is in maintenance mode
|
|
|
*
|
|
|
* @private
|
|
|
**/
|
|
|
_renderMaintenanceWindowBanner: function() {
|
|
|
// Clear any banners that were previously there, that were closed.
|
|
|
this._clearMaintenanceWindowBanner();
|
|
|
|
|
|
var maintenanceBanner = new MaintenanceModeBannerView({
|
|
|
el : $('.maintenance-mode'),
|
|
|
message: 'This Entity is currently in maintenance, data on this page may not be accurate'
|
|
|
});
|
|
|
maintenanceBanner.render();
|
|
|
maintenanceBanner.show();
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Updates the entity info view when the model's entity info get populated
|
|
|
*
|
|
|
* @private
|
|
|
*/
|
|
|
_updateEntityInfoView: function () {
|
|
|
// Clears all of the inventory fields that had been displayed here
|
|
|
$('.inventory-field').remove();
|
|
|
|
|
|
$('#entity-context-panel-entity-info').append(this._entityInfoTemplate({
|
|
|
entity_title: this.currentEntityTitle,
|
|
|
data: this.model.get('current_entity_info'),
|
|
|
entity_detail_url: this._getEntityDetailUrl()
|
|
|
})).trigger('entity-fields-change-event', this.model.get('current_entity_info'));
|
|
|
|
|
|
// Append the modal view to context panel with all fields described
|
|
|
$('#inventory-fields-modal').html(this._showAllInventoryFieldsModal({
|
|
|
data: this.model.get('current_entity_info'),
|
|
|
entity_detail_url: this._getEntityDetailUrl()
|
|
|
})).trigger('entity-fields-change-event', this.model.get('current_entity_info'));
|
|
|
|
|
|
// Adapted from @ksternberg's UI logic on Entity Detail page
|
|
|
_.defer(_.bind(function() {
|
|
|
$('#show-more-fields').remove();
|
|
|
var MAX_LOCATION = $('#' + this.minichart_id).position().left;
|
|
|
var extra = 0;
|
|
|
_.each($('.wrapped-item.inventory-field'), function(item) {
|
|
|
if($(item).position().left + $(item).width() > MAX_LOCATION) {
|
|
|
$(item).remove();
|
|
|
extra++;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
if (extra > 0) {
|
|
|
$('#entity-context-panel-entity-info').append(this._showMoreFieldsTemplate({
|
|
|
extra: extra
|
|
|
}));
|
|
|
|
|
|
// Make sure the "Show X more" modal link is in same column as final field shown
|
|
|
if ($('#show-more-fields').position().left !== $('.wrapped-item.inventory-field').last().position().left) {
|
|
|
$('.wrapped-item.inventory-field').last().remove();
|
|
|
$('#show-more-fields > #open-inventory-modal > .scanning').text('Show ' + (extra + 1) + ' more');
|
|
|
}
|
|
|
}
|
|
|
}, this));
|
|
|
|
|
|
/* Fetches the existing maintenance windows from the REST endpoint, and determines
|
|
|
* whether the selected entity is in one of those windows
|
|
|
*/
|
|
|
$.get(this.MAINTENANCE_CALENDAR_ENDPOINT)
|
|
|
.done(_.bind(function(maintenance_calendars) {
|
|
|
if (this._isEntityInMaintenanceWindow(maintenance_calendars)) {
|
|
|
this._renderMaintenanceWindowBanner();
|
|
|
}
|
|
|
else {
|
|
|
this._clearMaintenanceWindowBanner();
|
|
|
}
|
|
|
}, this))
|
|
|
.fail(function() {
|
|
|
console.error('Failed to retrieve maintenance calendars.');
|
|
|
});
|
|
|
},
|
|
|
|
|
|
_clearEntityInfoView: function() {
|
|
|
// This discernment is necessary because there's an 'if' in the
|
|
|
// template.
|
|
|
var maybeContext = $('#entity-context-panel-contextLink');
|
|
|
var entityPicker = $('#entity-context-panel-entity-picker');
|
|
|
var lastUnchanging = (maybeContext.length ? maybeContext : entityPicker)
|
|
|
.closest('label');
|
|
|
lastUnchanging.nextAll('label.wrapped-item').remove();
|
|
|
},
|
|
|
|
|
|
_template: _.template([
|
|
|
'<div class="panel-body html">',
|
|
|
'<div id="context">',
|
|
|
' <div id="entity-context-panel-entity-info" class="wrapped-layout">',
|
|
|
' <label class="wrapped-item">',
|
|
|
' <span>' + _('Entity').t() + '</span>',
|
|
|
' <div class="input-dropdown scanning" id="entity-context-panel-entity-picker" style="margin-top: -9px; height: 0px;"></div>',
|
|
|
' </label>',
|
|
|
' <% if (context_title) { %>',
|
|
|
' <label class="wrapped-item">',
|
|
|
' <span><%- context_title %></span>',
|
|
|
' <span>',
|
|
|
' <a id=entity-context-panel-contextLink href="<%- context_url%>"><%- context_value%></a>',
|
|
|
' </span>',
|
|
|
' </label>',
|
|
|
' <% } %>',
|
|
|
' </div>',
|
|
|
' <div class="modal fade" id="inventory-fields-modal" tabindex="-1" role="dialog"></div>',
|
|
|
' <div id="<%=minichart_id%>" class="minicharts"></div>',
|
|
|
'</div>',
|
|
|
'</div>',
|
|
|
'<div class="maintenance-mode"></div>'].join('\n')),
|
|
|
_showMoreFieldsTemplate: _.template([
|
|
|
'<label class="wrapped-item" id="show-more-fields">',
|
|
|
' <a id="open-inventory-modal" href="#">',
|
|
|
' <span class="scanning">' + _('Show ').t() + '<%= extra%>' + _(' more').t() + '</span>',
|
|
|
' </a>',
|
|
|
'</label>'
|
|
|
].join('\n')),
|
|
|
_entityInfoTemplate: _.template([
|
|
|
'<% if(data) { %>',
|
|
|
' <% _.each(data, function(value, key) { %>',
|
|
|
' <label class="wrapped-item inventory-field">',
|
|
|
' <span><%= key%></span>',
|
|
|
' <span><%= value ? value : "Not Found"%></span>',
|
|
|
' </label>',
|
|
|
' <% }); %>',
|
|
|
' <label class="wrapped-item inventory-field">',
|
|
|
' <a href="<%= entity_detail_url%>" target="_blank" rel="noopener noreferrer" class="icon-external icon-no-underline">',
|
|
|
' <span class="scanning">' + _('View Entity Health').t() + '</span>',
|
|
|
' </a>',
|
|
|
' </label>',
|
|
|
'<% } else { %>',
|
|
|
' <label>' + _('No data available.').t() + '</label>',
|
|
|
'<% } %>'].join('\n')),
|
|
|
_showAllInventoryFieldsModal: _.template([
|
|
|
'<div class="modal-dialog modal-sm">',
|
|
|
' <div class="modal-content">',
|
|
|
' <div class="modal-header">',
|
|
|
' <button type="button" class="close" data-dismiss="modal">×</button>',
|
|
|
' <h4 class="modal-title">' + _('All Inventory Fields').t() + '</h4>',
|
|
|
' </div>',
|
|
|
' <div class="modal-body scrollable">',
|
|
|
' <% _.each(data, function(value, field) { %>',
|
|
|
' <label><%= field%>: <%= value%></label>',
|
|
|
' <% }); %>',
|
|
|
' <label>',
|
|
|
' <a href="<%= entity_detail_url%>" target="_blank" rel="noopener noreferrer" class="icon-external icon-no-underline">',
|
|
|
' <span class="scanning">' + _('View Entity Health').t() + '</span>',
|
|
|
' </a>',
|
|
|
' </label>',
|
|
|
' </div>',
|
|
|
' <div class="modal-footer">',
|
|
|
' <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>',
|
|
|
' </div>',
|
|
|
' </div>',
|
|
|
'</div>'
|
|
|
].join('\n'))
|
|
|
});
|
|
|
|
|
|
return EntityContextPanel;
|
|
|
}.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
/* 7 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global) {// Backbone.js 1.6.0
|
|
|
|
|
|
// (c) 2010-2024 Jeremy Ashkenas and DocumentCloud
|
|
|
// Backbone may be freely distributed under the MIT license.
|
|
|
// For all details and documentation:
|
|
|
// http://backbonejs.org
|
|
|
|
|
|
(function(factory) {
|
|
|
|
|
|
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
|
|
// We use `self` instead of `window` for `WebWorker` support.
|
|
|
var root = typeof self == 'object' && self.self === self && self ||
|
|
|
typeof global == 'object' && global.global === global && global;
|
|
|
|
|
|
// Set up Backbone appropriately for the environment. Start with AMD.
|
|
|
if (true) {
|
|
|
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(2), __webpack_require__(1), exports], __WEBPACK_AMD_DEFINE_RESULT__ = function(_, $, exports) {
|
|
|
// Export global even in AMD case in case this script is loaded with
|
|
|
// others that may still expect a global Backbone.
|
|
|
root.Backbone = factory(root, exports, _, $);
|
|
|
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
|
|
|
} else if (typeof exports !== 'undefined') {
|
|
|
var _ = require('underscore'), $;
|
|
|
try { $ = require('jquery'); } catch (e) {}
|
|
|
factory(root, exports, _, $);
|
|
|
|
|
|
// Finally, as a browser global.
|
|
|
} else {
|
|
|
root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$);
|
|
|
}
|
|
|
|
|
|
})(function(root, Backbone, _, $) {
|
|
|
|
|
|
// Initial Setup
|
|
|
// -------------
|
|
|
|
|
|
// Save the previous value of the `Backbone` variable, so that it can be
|
|
|
// restored later on, if `noConflict` is used.
|
|
|
var previousBackbone = root.Backbone;
|
|
|
|
|
|
// Create a local reference to a common array method we'll want to use later.
|
|
|
var slice = Array.prototype.slice;
|
|
|
|
|
|
// Current version of the library. Keep in sync with `package.json`.
|
|
|
Backbone.VERSION = '1.6.0';
|
|
|
|
|
|
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
|
|
|
// the `$` variable.
|
|
|
Backbone.$ = $;
|
|
|
|
|
|
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
|
|
|
// to its previous owner. Returns a reference to this Backbone object.
|
|
|
Backbone.noConflict = function() {
|
|
|
root.Backbone = previousBackbone;
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
|
|
|
// will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
|
|
|
// set a `X-Http-Method-Override` header.
|
|
|
Backbone.emulateHTTP = false;
|
|
|
|
|
|
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
|
|
|
// `application/json` requests ... this will encode the body as
|
|
|
// `application/x-www-form-urlencoded` instead and will send the model in a
|
|
|
// form param named `model`.
|
|
|
Backbone.emulateJSON = false;
|
|
|
|
|
|
// Backbone.Events
|
|
|
// ---------------
|
|
|
|
|
|
// A module that can be mixed in to *any object* in order to provide it with
|
|
|
// a custom event channel. You may bind a callback to an event with `on` or
|
|
|
// remove with `off`; `trigger`-ing an event fires all callbacks in
|
|
|
// succession.
|
|
|
//
|
|
|
// var object = {};
|
|
|
// _.extend(object, Backbone.Events);
|
|
|
// object.on('expand', function(){ alert('expanded'); });
|
|
|
// object.trigger('expand');
|
|
|
//
|
|
|
var Events = Backbone.Events = {};
|
|
|
|
|
|
// Regular expression used to split event strings.
|
|
|
var eventSplitter = /\s+/;
|
|
|
|
|
|
// A private global variable to share between listeners and listenees.
|
|
|
var _listening;
|
|
|
|
|
|
// Iterates over the standard `event, callback` (as well as the fancy multiple
|
|
|
// space-separated events `"change blur", callback` and jQuery-style event
|
|
|
// maps `{event: callback}`).
|
|
|
var eventsApi = function(iteratee, events, name, callback, opts) {
|
|
|
var i = 0, names;
|
|
|
if (name && typeof name === 'object') {
|
|
|
// Handle event maps.
|
|
|
if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
|
|
|
for (names = _.keys(name); i < names.length ; i++) {
|
|
|
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
|
|
|
}
|
|
|
} else if (name && eventSplitter.test(name)) {
|
|
|
// Handle space-separated event names by delegating them individually.
|
|
|
for (names = name.split(eventSplitter); i < names.length; i++) {
|
|
|
events = iteratee(events, names[i], callback, opts);
|
|
|
}
|
|
|
} else {
|
|
|
// Finally, standard events.
|
|
|
events = iteratee(events, name, callback, opts);
|
|
|
}
|
|
|
return events;
|
|
|
};
|
|
|
|
|
|
// Bind an event to a `callback` function. Passing `"all"` will bind
|
|
|
// the callback to all events fired.
|
|
|
Events.on = function(name, callback, context) {
|
|
|
this._events = eventsApi(onApi, this._events || {}, name, callback, {
|
|
|
context: context,
|
|
|
ctx: this,
|
|
|
listening: _listening
|
|
|
});
|
|
|
|
|
|
if (_listening) {
|
|
|
var listeners = this._listeners || (this._listeners = {});
|
|
|
listeners[_listening.id] = _listening;
|
|
|
// Allow the listening to use a counter, instead of tracking
|
|
|
// callbacks for library interop
|
|
|
_listening.interop = false;
|
|
|
}
|
|
|
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
// Inversion-of-control versions of `on`. Tell *this* object to listen to
|
|
|
// an event in another object... keeping track of what it's listening to
|
|
|
// for easier unbinding later.
|
|
|
Events.listenTo = function(obj, name, callback) {
|
|
|
if (!obj) return this;
|
|
|
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
|
|
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
|
|
var listening = _listening = listeningTo[id];
|
|
|
|
|
|
// This object is not listening to any other events on `obj` yet.
|
|
|
// Setup the necessary references to track the listening callbacks.
|
|
|
if (!listening) {
|
|
|
this._listenId || (this._listenId = _.uniqueId('l'));
|
|
|
listening = _listening = listeningTo[id] = new Listening(this, obj);
|
|
|
}
|
|
|
|
|
|
// Bind callbacks on obj.
|
|
|
var error = tryCatchOn(obj, name, callback, this);
|
|
|
_listening = void 0;
|
|
|
|
|
|
if (error) throw error;
|
|
|
// If the target obj is not Backbone.Events, track events manually.
|
|
|
if (listening.interop) listening.on(name, callback);
|
|
|
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
// The reducing API that adds a callback to the `events` object.
|
|
|
var onApi = function(events, name, callback, options) {
|
|
|
if (callback) {
|
|
|
var handlers = events[name] || (events[name] = []);
|
|
|
var context = options.context, ctx = options.ctx, listening = options.listening;
|
|
|
if (listening) listening.count++;
|
|
|
|
|
|
handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
|
|
|
}
|
|
|
return events;
|
|
|
};
|
|
|
|
|
|
// An try-catch guarded #on function, to prevent poisoning the global
|
|
|
// `_listening` variable.
|
|
|
var tryCatchOn = function(obj, name, callback, context) {
|
|
|
try {
|
|
|
obj.on(name, callback, context);
|
|
|
} catch (e) {
|
|
|
return e;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// Remove one or many callbacks. If `context` is null, removes all
|
|
|
// callbacks with that function. If `callback` is null, removes all
|
|
|
// callbacks for the event. If `name` is null, removes all bound
|
|
|
// callbacks for all events.
|
|
|
Events.off = function(name, callback, context) {
|
|
|
if (!this._events) return this;
|
|
|
this._events = eventsApi(offApi, this._events, name, callback, {
|
|
|
context: context,
|
|
|
listeners: this._listeners
|
|
|
});
|
|
|
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
// Tell this object to stop listening to either specific events ... or
|
|
|
// to every object it's currently listening to.
|
|
|
Events.stopListening = function(obj, name, callback) {
|
|
|
var listeningTo = this._listeningTo;
|
|
|
if (!listeningTo) return this;
|
|
|
|
|
|
var ids = obj ? [obj._listenId] : _.keys(listeningTo);
|
|
|
for (var i = 0; i < ids.length; i++) {
|
|
|
var listening = listeningTo[ids[i]];
|
|
|
|
|
|
// If listening doesn't exist, this object is not currently
|
|
|
// listening to obj. Break out early.
|
|
|
if (!listening) break;
|
|
|
|
|
|
listening.obj.off(name, callback, this);
|
|
|
if (listening.interop) listening.off(name, callback);
|
|
|
}
|
|
|
if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
|
|
|
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
// The reducing API that removes a callback from the `events` object.
|
|
|
var offApi = function(events, name, callback, options) {
|
|
|
if (!events) return;
|
|
|
|
|
|
var context = options.context, listeners = options.listeners;
|
|
|
var i = 0, names;
|
|
|
|
|
|
// Delete all event listeners and "drop" events.
|
|
|
if (!name && !context && !callback) {
|
|
|
for (names = _.keys(listeners); i < names.length; i++) {
|
|
|
listeners[names[i]].cleanup();
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
names = name ? [name] : _.keys(events);
|
|
|
for (; i < names.length; i++) {
|
|
|
name = names[i];
|
|
|
var handlers = events[name];
|
|
|
|
|
|
// Bail out if there are no events stored.
|
|
|
if (!handlers) break;
|
|
|
|
|
|
// Find any remaining events.
|
|
|
var remaining = [];
|
|
|
for (var j = 0; j < handlers.length; j++) {
|
|
|
var handler = handlers[j];
|
|
|
if (
|
|
|
callback && callback !== handler.callback &&
|
|
|
callback !== handler.callback._callback ||
|
|
|
context && context !== handler.context
|
|
|
) {
|
|
|
remaining.push(handler);
|
|
|
} else {
|
|
|
var listening = handler.listening;
|
|
|
if (listening) listening.off(name, callback);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Replace events if there are any remaining. Otherwise, clean up.
|
|
|
if (remaining.length) {
|
|
|
events[name] = remaining;
|
|
|
} else {
|
|
|
delete events[name];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return events;
|
|
|
};
|
|
|
|
|
|
// Bind an event to only be triggered a single time. After the first time
|
|
|
// the callback is invoked, its listener will be removed. If multiple events
|
|
|
// are passed in using the space-separated syntax, the handler will fire
|
|
|
// once for each event, not once for a combination of all events.
|
|
|
Events.once = function(name, callback, context) {
|
|
|
// Map the event into a `{event: once}` object.
|
|
|
var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
|
|
|
if (typeof name === 'string' && context == null) callback = void 0;
|
|
|
return this.on(events, callback, context);
|
|
|
};
|
|
|
|
|
|
// Inversion-of-control versions of `once`.
|
|
|
Events.listenToOnce = function(obj, name, callback) {
|
|
|
// Map the event into a `{event: once}` object.
|
|
|
var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
|
|
|
return this.listenTo(obj, events);
|
|
|
};
|
|
|
|
|
|
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
|
|
|
// `offer` unbinds the `onceWrapper` after it has been called.
|
|
|
var onceMap = function(map, name, callback, offer) {
|
|
|
if (callback) {
|
|
|
var once = map[name] = _.once(function() {
|
|
|
offer(name, once);
|
|
|
callback.apply(this, arguments);
|
|
|
});
|
|
|
once._callback = callback;
|
|
|
}
|
|
|
return map;
|
|
|
};
|
|
|
|
|
|
// Trigger one or many events, firing all bound callbacks. Callbacks are
|
|
|
// passed the same arguments as `trigger` is, apart from the event name
|
|
|
// (unless you're listening on `"all"`, which will cause your callback to
|
|
|
// receive the true name of the event as the first argument).
|
|
|
Events.trigger = function(name) {
|
|
|
if (!this._events) return this;
|
|
|
|
|
|
var length = Math.max(0, arguments.length - 1);
|
|
|
var args = Array(length);
|
|
|
for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
|
|
|
|
|
|
eventsApi(triggerApi, this._events, name, void 0, args);
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
// Handles triggering the appropriate event callbacks.
|
|
|
var triggerApi = function(objEvents, name, callback, args) {
|
|
|
if (objEvents) {
|
|
|
var events = objEvents[name];
|
|
|
var allEvents = objEvents.all;
|
|
|
if (events && allEvents) allEvents = allEvents.slice();
|
|
|
if (events) triggerEvents(events, args);
|
|
|
if (allEvents) triggerEvents(allEvents, [name].concat(args));
|
|
|
}
|
|
|
return objEvents;
|
|
|
};
|
|
|
|
|
|
// A difficult-to-believe, but optimized internal dispatch function for
|
|
|
// triggering events. Tries to keep the usual cases speedy (most internal
|
|
|
// Backbone events have 3 arguments).
|
|
|
var triggerEvents = function(events, args) {
|
|
|
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
|
|
|
switch (args.length) {
|
|
|
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
|
|
|
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
|
|
|
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
|
|
|
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
|
|
|
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// A listening class that tracks and cleans up memory bindings
|
|
|
// when all callbacks have been offed.
|
|
|
var Listening = function(listener, obj) {
|
|
|
this.id = listener._listenId;
|
|
|
this.listener = listener;
|
|
|
this.obj = obj;
|
|
|
this.interop = true;
|
|
|
this.count = 0;
|
|
|
this._events = void 0;
|
|
|
};
|
|
|
|
|
|
Listening.prototype.on = Events.on;
|
|
|
|
|
|
// Offs a callback (or several).
|
|
|
// Uses an optimized counter if the listenee uses Backbone.Events.
|
|
|
// Otherwise, falls back to manual tracking to support events
|
|
|
// library interop.
|
|
|
Listening.prototype.off = function(name, callback) {
|
|
|
var cleanup;
|
|
|
if (this.interop) {
|
|
|
this._events = eventsApi(offApi, this._events, name, callback, {
|
|
|
context: void 0,
|
|
|
listeners: void 0
|
|
|
});
|
|
|
cleanup = !this._events;
|
|
|
} else {
|
|
|
this.count--;
|
|
|
cleanup = this.count === 0;
|
|
|
}
|
|
|
if (cleanup) this.cleanup();
|
|
|
};
|
|
|
|
|
|
// Cleans up memory bindings between the listener and the listenee.
|
|
|
Listening.prototype.cleanup = function() {
|
|
|
delete this.listener._listeningTo[this.obj._listenId];
|
|
|
if (!this.interop) delete this.obj._listeners[this.id];
|
|
|
};
|
|
|
|
|
|
// Aliases for backwards compatibility.
|
|
|
Events.bind = Events.on;
|
|
|
Events.unbind = Events.off;
|
|
|
|
|
|
// Allow the `Backbone` object to serve as a global event bus, for folks who
|
|
|
// want global "pubsub" in a convenient place.
|
|
|
_.extend(Backbone, Events);
|
|
|
|
|
|
// Backbone.Model
|
|
|
// --------------
|
|
|
|
|
|
// Backbone **Models** are the basic data object in the framework --
|
|
|
// frequently representing a row in a table in a database on your server.
|
|
|
// A discrete chunk of data and a bunch of useful, related methods for
|
|
|
// performing computations and transformations on that data.
|
|
|
|
|
|
// Create a new model with the specified attributes. A client id (`cid`)
|
|
|
// is automatically generated and assigned for you.
|
|
|
var Model = Backbone.Model = function(attributes, options) {
|
|
|
var attrs = attributes || {};
|
|
|
options || (options = {});
|
|
|
this.preinitialize.apply(this, arguments);
|
|
|
this.cid = _.uniqueId(this.cidPrefix);
|
|
|
this.attributes = {};
|
|
|
if (options.collection) this.collection = options.collection;
|
|
|
if (options.parse) attrs = this.parse(attrs, options) || {};
|
|
|
var defaults = _.result(this, 'defaults');
|
|
|
|
|
|
// Just _.defaults would work fine, but the additional _.extends
|
|
|
// is in there for historical reasons. See #3843.
|
|
|
attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
|
|
|
|
|
|
this.set(attrs, options);
|
|
|
this.changed = {};
|
|
|
this.initialize.apply(this, arguments);
|
|
|
};
|
|
|
|
|
|
// Attach all inheritable methods to the Model prototype.
|
|
|
_.extend(Model.prototype, Events, {
|
|
|
|
|
|
// A hash of attributes whose current and previous value differ.
|
|
|
changed: null,
|
|
|
|
|
|
// The value returned during the last failed validation.
|
|
|
validationError: null,
|
|
|
|
|
|
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
|
|
// CouchDB users may want to set this to `"_id"`.
|
|
|
idAttribute: 'id',
|
|
|
|
|
|
// The prefix is used to create the client id which is used to identify models locally.
|
|
|
// You may want to override this if you're experiencing name clashes with model ids.
|
|
|
cidPrefix: 'c',
|
|
|
|
|
|
// preinitialize is an empty function by default. You can override it with a function
|
|
|
// or object. preinitialize will run before any instantiation logic is run in the Model.
|
|
|
preinitialize: function(){},
|
|
|
|
|
|
// Initialize is an empty function by default. Override it with your own
|
|
|
// initialization logic.
|
|
|
initialize: function(){},
|
|
|
|
|
|
// Return a copy of the model's `attributes` object.
|
|
|
toJSON: function(options) {
|
|
|
return _.clone(this.attributes);
|
|
|
},
|
|
|
|
|
|
// Proxy `Backbone.sync` by default -- but override this if you need
|
|
|
// custom syncing semantics for *this* particular model.
|
|
|
sync: function() {
|
|
|
return Backbone.sync.apply(this, arguments);
|
|
|
},
|
|
|
|
|
|
// Get the value of an attribute.
|
|
|
get: function(attr) {
|
|
|
return this.attributes[attr];
|
|
|
},
|
|
|
|
|
|
// Get the HTML-escaped value of an attribute.
|
|
|
escape: function(attr) {
|
|
|
return _.escape(this.get(attr));
|
|
|
},
|
|
|
|
|
|
// Returns `true` if the attribute contains a value that is not null
|
|
|
// or undefined.
|
|
|
has: function(attr) {
|
|
|
return this.get(attr) != null;
|
|
|
},
|
|
|
|
|
|
// Special-cased proxy to underscore's `_.matches` method.
|
|
|
matches: function(attrs) {
|
|
|
return !!_.iteratee(attrs, this)(this.attributes);
|
|
|
},
|
|
|
|
|
|
// Set a hash of model attributes on the object, firing `"change"`. This is
|
|
|
// the core primitive operation of a model, updating the data and notifying
|
|
|
// anyone who needs to know about the change in state. The heart of the beast.
|
|
|
set: function(key, val, options) {
|
|
|
if (key == null) return this;
|
|
|
|
|
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
|
var attrs;
|
|
|
if (typeof key === 'object') {
|
|
|
attrs = key;
|
|
|
options = val;
|
|
|
} else {
|
|
|
(attrs = {})[key] = val;
|
|
|
}
|
|
|
|
|
|
options || (options = {});
|
|
|
|
|
|
// Run validation.
|
|
|
if (!this._validate(attrs, options)) return false;
|
|
|
|
|
|
// Extract attributes and options.
|
|
|
var unset = options.unset;
|
|
|
var silent = options.silent;
|
|
|
var changes = [];
|
|
|
var changing = this._changing;
|
|
|
this._changing = true;
|
|
|
|
|
|
if (!changing) {
|
|
|
this._previousAttributes = _.clone(this.attributes);
|
|
|
this.changed = {};
|
|
|
}
|
|
|
|
|
|
var current = this.attributes;
|
|
|
var changed = this.changed;
|
|
|
var prev = this._previousAttributes;
|
|
|
|
|
|
// For each `set` attribute, update or delete the current value.
|
|
|
for (var attr in attrs) {
|
|
|
val = attrs[attr];
|
|
|
if (!_.isEqual(current[attr], val)) changes.push(attr);
|
|
|
if (!_.isEqual(prev[attr], val)) {
|
|
|
changed[attr] = val;
|
|
|
} else {
|
|
|
delete changed[attr];
|
|
|
}
|
|
|
unset ? delete current[attr] : current[attr] = val;
|
|
|
}
|
|
|
|
|
|
// Update the `id`.
|
|
|
if (this.idAttribute in attrs) {
|
|
|
var prevId = this.id;
|
|
|
this.id = this.get(this.idAttribute);
|
|
|
this.trigger('changeId', this, prevId, options);
|
|
|
}
|
|
|
|
|
|
// Trigger all relevant attribute changes.
|
|
|
if (!silent) {
|
|
|
if (changes.length) this._pending = options;
|
|
|
for (var i = 0; i < changes.length; i++) {
|
|
|
this.trigger('change:' + changes[i], this, current[changes[i]], options);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// You might be wondering why there's a `while` loop here. Changes can
|
|
|
// be recursively nested within `"change"` events.
|
|
|
if (changing) return this;
|
|
|
if (!silent) {
|
|
|
while (this._pending) {
|
|
|
options = this._pending;
|
|
|
this._pending = false;
|
|
|
this.trigger('change', this, options);
|
|
|
}
|
|
|
}
|
|
|
this._pending = false;
|
|
|
this._changing = false;
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
|
|
|
// if the attribute doesn't exist.
|
|
|
unset: function(attr, options) {
|
|
|
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
|
|
|
},
|
|
|
|
|
|
// Clear all attributes on the model, firing `"change"`.
|
|
|
clear: function(options) {
|
|
|
var attrs = {};
|
|
|
for (var key in this.attributes) attrs[key] = void 0;
|
|
|
return this.set(attrs, _.extend({}, options, {unset: true}));
|
|
|
},
|
|
|
|
|
|
// Determine if the model has changed since the last `"change"` event.
|
|
|
// If you specify an attribute name, determine if that attribute has changed.
|
|
|
hasChanged: function(attr) {
|
|
|
if (attr == null) return !_.isEmpty(this.changed);
|
|
|
return _.has(this.changed, attr);
|
|
|
},
|
|
|
|
|
|
// Return an object containing all the attributes that have changed, or
|
|
|
// false if there are no changed attributes. Useful for determining what
|
|
|
// parts of a view need to be updated and/or what attributes need to be
|
|
|
// persisted to the server. Unset attributes will be set to undefined.
|
|
|
// You can also pass an attributes object to diff against the model,
|
|
|
// determining if there *would be* a change.
|
|
|
changedAttributes: function(diff) {
|
|
|
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
|
|
|
var old = this._changing ? this._previousAttributes : this.attributes;
|
|
|
var changed = {};
|
|
|
var hasChanged;
|
|
|
for (var attr in diff) {
|
|
|
var val = diff[attr];
|
|
|
if (_.isEqual(old[attr], val)) continue;
|
|
|
changed[attr] = val;
|
|
|
hasChanged = true;
|
|
|
}
|
|
|
return hasChanged ? changed : false;
|
|
|
},
|
|
|
|
|
|
// Get the previous value of an attribute, recorded at the time the last
|
|
|
// `"change"` event was fired.
|
|
|
previous: function(attr) {
|
|
|
if (attr == null || !this._previousAttributes) return null;
|
|
|
return this._previousAttributes[attr];
|
|
|
},
|
|
|
|
|
|
// Get all of the attributes of the model at the time of the previous
|
|
|
// `"change"` event.
|
|
|
previousAttributes: function() {
|
|
|
return _.clone(this._previousAttributes);
|
|
|
},
|
|
|
|
|
|
// Fetch the model from the server, merging the response with the model's
|
|
|
// local attributes. Any changed attributes will trigger a "change" event.
|
|
|
fetch: function(options) {
|
|
|
options = _.extend({parse: true}, options);
|
|
|
var model = this;
|
|
|
var success = options.success;
|
|
|
options.success = function(resp) {
|
|
|
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
|
|
|
if (!model.set(serverAttrs, options)) return false;
|
|
|
if (success) success.call(options.context, model, resp, options);
|
|
|
model.trigger('sync', model, resp, options);
|
|
|
};
|
|
|
wrapError(this, options);
|
|
|
return this.sync('read', this, options);
|
|
|
},
|
|
|
|
|
|
// Set a hash of model attributes, and sync the model to the server.
|
|
|
// If the server returns an attributes hash that differs, the model's
|
|
|
// state will be `set` again.
|
|
|
save: function(key, val, options) {
|
|
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
|
var attrs;
|
|
|
if (key == null || typeof key === 'object') {
|
|
|
attrs = key;
|
|
|
options = val;
|
|
|
} else {
|
|
|
(attrs = {})[key] = val;
|
|
|
}
|
|
|
|
|
|
options = _.extend({validate: true, parse: true}, options);
|
|
|
var wait = options.wait;
|
|
|
|
|
|
// If we're not waiting and attributes exist, save acts as
|
|
|
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
|
|
// the model will be valid when the attributes, if any, are set.
|
|
|
if (attrs && !wait) {
|
|
|
if (!this.set(attrs, options)) return false;
|
|
|
} else if (!this._validate(attrs, options)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// After a successful server-side save, the client is (optionally)
|
|
|
// updated with the server-side state.
|
|
|
var model = this;
|
|
|
var success = options.success;
|
|
|
var attributes = this.attributes;
|
|
|
options.success = function(resp) {
|
|
|
// Ensure attributes are restored during synchronous saves.
|
|
|
model.attributes = attributes;
|
|
|
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
|
|
|
if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
|
|
|
if (serverAttrs && !model.set(serverAttrs, options)) return false;
|
|
|
if (success) success.call(options.context, model, resp, options);
|
|
|
model.trigger('sync', model, resp, options);
|
|
|
};
|
|
|
wrapError(this, options);
|
|
|
|
|
|
// Set temporary attributes if `{wait: true}` to properly find new ids.
|
|
|
if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
|
|
|
|
|
|
var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
|
|
|
if (method === 'patch' && !options.attrs) options.attrs = attrs;
|
|
|
var xhr = this.sync(method, this, options);
|
|
|
|
|
|
// Restore attributes.
|
|
|
this.attributes = attributes;
|
|
|
|
|
|
return xhr;
|
|
|
},
|
|
|
|
|
|
// Destroy this model on the server if it was already persisted.
|
|
|
// Optimistically removes the model from its collection, if it has one.
|
|
|
// If `wait: true` is passed, waits for the server to respond before removal.
|
|
|
destroy: function(options) {
|
|
|
options = options ? _.clone(options) : {};
|
|
|
var model = this;
|
|
|
var success = options.success;
|
|
|
var wait = options.wait;
|
|
|
|
|
|
var destroy = function() {
|
|
|
model.stopListening();
|
|
|
model.trigger('destroy', model, model.collection, options);
|
|
|
};
|
|
|
|
|
|
options.success = function(resp) {
|
|
|
if (wait) destroy();
|
|
|
if (success) success.call(options.context, model, resp, options);
|
|
|
if (!model.isNew()) model.trigger('sync', model, resp, options);
|
|
|
};
|
|
|
|
|
|
var xhr = false;
|
|
|
if (this.isNew()) {
|
|
|
_.defer(options.success);
|
|
|
} else {
|
|
|
wrapError(this, options);
|
|
|
xhr = this.sync('delete', this, options);
|
|
|
}
|
|
|
if (!wait) destroy();
|
|
|
return xhr;
|
|
|
},
|
|
|
|
|
|
// Default URL for the model's representation on the server -- if you're
|
|
|
// using Backbone's restful methods, override this to change the endpoint
|
|
|
// that will be called.
|
|
|
url: function() {
|
|
|
var base =
|
|
|
_.result(this, 'urlRoot') ||
|
|
|
_.result(this.collection, 'url') ||
|
|
|
urlError();
|
|
|
if (this.isNew()) return base;
|
|
|
var id = this.get(this.idAttribute);
|
|
|
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
|
|
|
},
|
|
|
|
|
|
// **parse** converts a response into the hash of attributes to be `set` on
|
|
|
// the model. The default implementation is just to pass the response along.
|
|
|
parse: function(resp, options) {
|
|
|
return resp;
|
|
|
},
|
|
|
|
|
|
// Create a new model with identical attributes to this one.
|
|
|
clone: function() {
|
|
|
return new this.constructor(this.attributes);
|
|
|
},
|
|
|
|
|
|
// A model is new if it has never been saved to the server, and lacks an id.
|
|
|
isNew: function() {
|
|
|
return !this.has(this.idAttribute);
|
|
|
},
|
|
|
|
|
|
// Check if the model is currently in a valid state.
|
|
|
isValid: function(options) {
|
|
|
return this._validate({}, _.extend({}, options, {validate: true}));
|
|
|
},
|
|
|
|
|
|
// Run validation against the next complete set of model attributes,
|
|
|
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
|
|
|
_validate: function(attrs, options) {
|
|
|
if (!options.validate || !this.validate) return true;
|
|
|
attrs = _.extend({}, this.attributes, attrs);
|
|
|
var error = this.validationError = this.validate(attrs, options) || null;
|
|
|
if (!error) return true;
|
|
|
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// Backbone.Collection
|
|
|
// -------------------
|
|
|
|
|
|
// If models tend to represent a single row of data, a Backbone Collection is
|
|
|
// more analogous to a table full of data ... or a small slice or page of that
|
|
|
// table, or a collection of rows that belong together for a particular reason
|
|
|
// -- all of the messages in this particular folder, all of the documents
|
|
|
// belonging to this particular author, and so on. Collections maintain
|
|
|
// indexes of their models, both in order, and for lookup by `id`.
|
|
|
|
|
|
// Create a new **Collection**, perhaps to contain a specific type of `model`.
|
|
|
// If a `comparator` is specified, the Collection will maintain
|
|
|
// its models in sort order, as they're added and removed.
|
|
|
var Collection = Backbone.Collection = function(models, options) {
|
|
|
options || (options = {});
|
|
|
this.preinitialize.apply(this, arguments);
|
|
|
if (options.model) this.model = options.model;
|
|
|
if (options.comparator !== void 0) this.comparator = options.comparator;
|
|
|
this._reset();
|
|
|
this.initialize.apply(this, arguments);
|
|
|
if (models) this.reset(models, _.extend({silent: true}, options));
|
|
|
};
|
|
|
|
|
|
// Default options for `Collection#set`.
|
|
|
var setOptions = {add: true, remove: true, merge: true};
|
|
|
var addOptions = {add: true, remove: false};
|
|
|
|
|
|
// Splices `insert` into `array` at index `at`.
|
|
|
var splice = function(array, insert, at) {
|
|
|
at = Math.min(Math.max(at, 0), array.length);
|
|
|
var tail = Array(array.length - at);
|
|
|
var length = insert.length;
|
|
|
var i;
|
|
|
for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
|
|
|
for (i = 0; i < length; i++) array[i + at] = insert[i];
|
|
|
for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
|
|
|
};
|
|
|
|
|
|
// Define the Collection's inheritable methods.
|
|
|
_.extend(Collection.prototype, Events, {
|
|
|
|
|
|
// The default model for a collection is just a **Backbone.Model**.
|
|
|
// This should be overridden in most cases.
|
|
|
model: Model,
|
|
|
|
|
|
|
|
|
// preinitialize is an empty function by default. You can override it with a function
|
|
|
// or object. preinitialize will run before any instantiation logic is run in the Collection.
|
|
|
preinitialize: function(){},
|
|
|
|
|
|
// Initialize is an empty function by default. Override it with your own
|
|
|
// initialization logic.
|
|
|
initialize: function(){},
|
|
|
|
|
|
// The JSON representation of a Collection is an array of the
|
|
|
// models' attributes.
|
|
|
toJSON: function(options) {
|
|
|
return this.map(function(model) { return model.toJSON(options); });
|
|
|
},
|
|
|
|
|
|
// Proxy `Backbone.sync` by default.
|
|
|
sync: function() {
|
|
|
return Backbone.sync.apply(this, arguments);
|
|
|
},
|
|
|
|
|
|
// Add a model, or list of models to the set. `models` may be Backbone
|
|
|
// Models or raw JavaScript objects to be converted to Models, or any
|
|
|
// combination of the two.
|
|
|
add: function(models, options) {
|
|
|
return this.set(models, _.extend({merge: false}, options, addOptions));
|
|
|
},
|
|
|
|
|
|
// Remove a model, or a list of models from the set.
|
|
|
remove: function(models, options) {
|
|
|
options = _.extend({}, options);
|
|
|
var singular = !_.isArray(models);
|
|
|
models = singular ? [models] : models.slice();
|
|
|
var removed = this._removeModels(models, options);
|
|
|
if (!options.silent && removed.length) {
|
|
|
options.changes = {added: [], merged: [], removed: removed};
|
|
|
this.trigger('update', this, options);
|
|
|
}
|
|
|
return singular ? removed[0] : removed;
|
|
|
},
|
|
|
|
|
|
// Update a collection by `set`-ing a new list of models, adding new ones,
|
|
|
// removing models that are no longer present, and merging models that
|
|
|
// already exist in the collection, as necessary. Similar to **Model#set**,
|
|
|
// the core operation for updating the data contained by the collection.
|
|
|
set: function(models, options) {
|
|
|
if (models == null) return;
|
|
|
|
|
|
options = _.extend({}, setOptions, options);
|
|
|
if (options.parse && !this._isModel(models)) {
|
|
|
models = this.parse(models, options) || [];
|
|
|
}
|
|
|
|
|
|
var singular = !_.isArray(models);
|
|
|
models = singular ? [models] : models.slice();
|
|
|
|
|
|
var at = options.at;
|
|
|
if (at != null) at = +at;
|
|
|
if (at > this.length) at = this.length;
|
|
|
if (at < 0) at += this.length + 1;
|
|
|
|
|
|
var set = [];
|
|
|
var toAdd = [];
|
|
|
var toMerge = [];
|
|
|
var toRemove = [];
|
|
|
var modelMap = {};
|
|
|
|
|
|
var add = options.add;
|
|
|
var merge = options.merge;
|
|
|
var remove = options.remove;
|
|
|
|
|
|
var sort = false;
|
|
|
var sortable = this.comparator && at == null && options.sort !== false;
|
|
|
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
|
|
|
|
|
|
// Turn bare objects into model references, and prevent invalid models
|
|
|
// from being added.
|
|
|
var model, i;
|
|
|
for (i = 0; i < models.length; i++) {
|
|
|
model = models[i];
|
|
|
|
|
|
// If a duplicate is found, prevent it from being added and
|
|
|
// optionally merge it into the existing model.
|
|
|
var existing = this.get(model);
|
|
|
if (existing) {
|
|
|
if (merge && model !== existing) {
|
|
|
var attrs = this._isModel(model) ? model.attributes : model;
|
|
|
if (options.parse) attrs = existing.parse(attrs, options);
|
|
|
existing.set(attrs, options);
|
|
|
toMerge.push(existing);
|
|
|
if (sortable && !sort) sort = existing.hasChanged(sortAttr);
|
|
|
}
|
|
|
if (!modelMap[existing.cid]) {
|
|
|
modelMap[existing.cid] = true;
|
|
|
set.push(existing);
|
|
|
}
|
|
|
models[i] = existing;
|
|
|
|
|
|
// If this is a new, valid model, push it to the `toAdd` list.
|
|
|
} else if (add) {
|
|
|
model = models[i] = this._prepareModel(model, options);
|
|
|
if (model) {
|
|
|
toAdd.push(model);
|
|
|
this._addReference(model, options);
|
|
|
modelMap[model.cid] = true;
|
|
|
set.push(model);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Remove stale models.
|
|
|
if (remove) {
|
|
|
for (i = 0; i < this.length; i++) {
|
|
|
model = this.models[i];
|
|
|
if (!modelMap[model.cid]) toRemove.push(model);
|
|
|
}
|
|
|
if (toRemove.length) this._removeModels(toRemove, options);
|
|
|
}
|
|
|
|
|
|
// See if sorting is needed, update `length` and splice in new models.
|
|
|
var orderChanged = false;
|
|
|
var replace = !sortable && add && remove;
|
|
|
if (set.length && replace) {
|
|
|
orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
|
|
|
return m !== set[index];
|
|
|
});
|
|
|
this.models.length = 0;
|
|
|
splice(this.models, set, 0);
|
|
|
this.length = this.models.length;
|
|
|
} else if (toAdd.length) {
|
|
|
if (sortable) sort = true;
|
|
|
splice(this.models, toAdd, at == null ? this.length : at);
|
|
|
this.length = this.models.length;
|
|
|
}
|
|
|
|
|
|
// Silently sort the collection if appropriate.
|
|
|
if (sort) this.sort({silent: true});
|
|
|
|
|
|
// Unless silenced, it's time to fire all appropriate add/sort/update events.
|
|
|
if (!options.silent) {
|
|
|
for (i = 0; i < toAdd.length; i++) {
|
|
|
if (at != null) options.index = at + i;
|
|
|
model = toAdd[i];
|
|
|
model.trigger('add', model, this, options);
|
|
|
}
|
|
|
if (sort || orderChanged) this.trigger('sort', this, options);
|
|
|
if (toAdd.length || toRemove.length || toMerge.length) {
|
|
|
options.changes = {
|
|
|
added: toAdd,
|
|
|
removed: toRemove,
|
|
|
merged: toMerge
|
|
|
};
|
|
|
this.trigger('update', this, options);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Return the added (or merged) model (or models).
|
|
|
return singular ? models[0] : models;
|
|
|
},
|
|
|
|
|
|
// When you have more items than you want to add or remove individually,
|
|
|
// you can reset the entire set with a new list of models, without firing
|
|
|
// any granular `add` or `remove` events. Fires `reset` when finished.
|
|
|
// Useful for bulk operations and optimizations.
|
|
|
reset: function(models, options) {
|
|
|
options = options ? _.clone(options) : {};
|
|
|
for (var i = 0; i < this.models.length; i++) {
|
|
|
this._removeReference(this.models[i], options);
|
|
|
}
|
|
|
options.previousModels = this.models;
|
|
|
this._reset();
|
|
|
models = this.add(models, _.extend({silent: true}, options));
|
|
|
if (!options.silent) this.trigger('reset', this, options);
|
|
|
return models;
|
|
|
},
|
|
|
|
|
|
// Add a model to the end of the collection.
|
|
|
push: function(model, options) {
|
|
|
return this.add(model, _.extend({at: this.length}, options));
|
|
|
},
|
|
|
|
|
|
// Remove a model from the end of the collection.
|
|
|
pop: function(options) {
|
|
|
var model = this.at(this.length - 1);
|
|
|
return this.remove(model, options);
|
|
|
},
|
|
|
|
|
|
// Add a model to the beginning of the collection.
|
|
|
unshift: function(model, options) {
|
|
|
return this.add(model, _.extend({at: 0}, options));
|
|
|
},
|
|
|
|
|
|
// Remove a model from the beginning of the collection.
|
|
|
shift: function(options) {
|
|
|
var model = this.at(0);
|
|
|
return this.remove(model, options);
|
|
|
},
|
|
|
|
|
|
// Slice out a sub-array of models from the collection.
|
|
|
slice: function() {
|
|
|
return slice.apply(this.models, arguments);
|
|
|
},
|
|
|
|
|
|
// Get a model from the set by id, cid, model object with id or cid
|
|
|
// properties, or an attributes object that is transformed through modelId.
|
|
|
get: function(obj) {
|
|
|
if (obj == null) return void 0;
|
|
|
return this._byId[obj] ||
|
|
|
this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] ||
|
|
|
obj.cid && this._byId[obj.cid];
|
|
|
},
|
|
|
|
|
|
// Returns `true` if the model is in the collection.
|
|
|
has: function(obj) {
|
|
|
return this.get(obj) != null;
|
|
|
},
|
|
|
|
|
|
// Get the model at the given index.
|
|
|
at: function(index) {
|
|
|
if (index < 0) index += this.length;
|
|
|
return this.models[index];
|
|
|
},
|
|
|
|
|
|
// Return models with matching attributes. Useful for simple cases of
|
|
|
// `filter`.
|
|
|
where: function(attrs, first) {
|
|
|
return this[first ? 'find' : 'filter'](attrs);
|
|
|
},
|
|
|
|
|
|
// Return the first model with matching attributes. Useful for simple cases
|
|
|
// of `find`.
|
|
|
findWhere: function(attrs) {
|
|
|
return this.where(attrs, true);
|
|
|
},
|
|
|
|
|
|
// Force the collection to re-sort itself. You don't need to call this under
|
|
|
// normal circumstances, as the set will maintain sort order as each item
|
|
|
// is added.
|
|
|
sort: function(options) {
|
|
|
var comparator = this.comparator;
|
|
|
if (!comparator) throw new Error('Cannot sort a set without a comparator');
|
|
|
options || (options = {});
|
|
|
|
|
|
var length = comparator.length;
|
|
|
if (_.isFunction(comparator)) comparator = comparator.bind(this);
|
|
|
|
|
|
// Run sort based on type of `comparator`.
|
|
|
if (length === 1 || _.isString(comparator)) {
|
|
|
this.models = this.sortBy(comparator);
|
|
|
} else {
|
|
|
this.models.sort(comparator);
|
|
|
}
|
|
|
if (!options.silent) this.trigger('sort', this, options);
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Pluck an attribute from each model in the collection.
|
|
|
pluck: function(attr) {
|
|
|
return this.map(attr + '');
|
|
|
},
|
|
|
|
|
|
// Fetch the default set of models for this collection, resetting the
|
|
|
// collection when they arrive. If `reset: true` is passed, the response
|
|
|
// data will be passed through the `reset` method instead of `set`.
|
|
|
fetch: function(options) {
|
|
|
options = _.extend({parse: true}, options);
|
|
|
var success = options.success;
|
|
|
var collection = this;
|
|
|
options.success = function(resp) {
|
|
|
var method = options.reset ? 'reset' : 'set';
|
|
|
collection[method](resp, options);
|
|
|
if (success) success.call(options.context, collection, resp, options);
|
|
|
collection.trigger('sync', collection, resp, options);
|
|
|
};
|
|
|
wrapError(this, options);
|
|
|
return this.sync('read', this, options);
|
|
|
},
|
|
|
|
|
|
// Create a new instance of a model in this collection. Add the model to the
|
|
|
// collection immediately, unless `wait: true` is passed, in which case we
|
|
|
// wait for the server to agree.
|
|
|
create: function(model, options) {
|
|
|
options = options ? _.clone(options) : {};
|
|
|
var wait = options.wait;
|
|
|
model = this._prepareModel(model, options);
|
|
|
if (!model) return false;
|
|
|
if (!wait) this.add(model, options);
|
|
|
var collection = this;
|
|
|
var success = options.success;
|
|
|
options.success = function(m, resp, callbackOpts) {
|
|
|
if (wait) {
|
|
|
m.off('error', collection._forwardPristineError, collection);
|
|
|
collection.add(m, callbackOpts);
|
|
|
}
|
|
|
if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
|
|
|
};
|
|
|
// In case of wait:true, our collection is not listening to any
|
|
|
// of the model's events yet, so it will not forward the error
|
|
|
// event. In this special case, we need to listen for it
|
|
|
// separately and handle the event just once.
|
|
|
// (The reason we don't need to do this for the sync event is
|
|
|
// in the success handler above: we add the model first, which
|
|
|
// causes the collection to listen, and then invoke the callback
|
|
|
// that triggers the event.)
|
|
|
if (wait) {
|
|
|
model.once('error', this._forwardPristineError, this);
|
|
|
}
|
|
|
model.save(null, options);
|
|
|
return model;
|
|
|
},
|
|
|
|
|
|
// **parse** converts a response into a list of models to be added to the
|
|
|
// collection. The default implementation is just to pass it through.
|
|
|
parse: function(resp, options) {
|
|
|
return resp;
|
|
|
},
|
|
|
|
|
|
// Create a new collection with an identical list of models as this one.
|
|
|
clone: function() {
|
|
|
return new this.constructor(this.models, {
|
|
|
model: this.model,
|
|
|
comparator: this.comparator
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// Define how to uniquely identify models in the collection.
|
|
|
modelId: function(attrs, idAttribute) {
|
|
|
return attrs[idAttribute || this.model.prototype.idAttribute || 'id'];
|
|
|
},
|
|
|
|
|
|
// Get an iterator of all models in this collection.
|
|
|
values: function() {
|
|
|
return new CollectionIterator(this, ITERATOR_VALUES);
|
|
|
},
|
|
|
|
|
|
// Get an iterator of all model IDs in this collection.
|
|
|
keys: function() {
|
|
|
return new CollectionIterator(this, ITERATOR_KEYS);
|
|
|
},
|
|
|
|
|
|
// Get an iterator of all [ID, model] tuples in this collection.
|
|
|
entries: function() {
|
|
|
return new CollectionIterator(this, ITERATOR_KEYSVALUES);
|
|
|
},
|
|
|
|
|
|
// Private method to reset all internal state. Called when the collection
|
|
|
// is first initialized or reset.
|
|
|
_reset: function() {
|
|
|
this.length = 0;
|
|
|
this.models = [];
|
|
|
this._byId = {};
|
|
|
},
|
|
|
|
|
|
// Prepare a hash of attributes (or other model) to be added to this
|
|
|
// collection.
|
|
|
_prepareModel: function(attrs, options) {
|
|
|
if (this._isModel(attrs)) {
|
|
|
if (!attrs.collection) attrs.collection = this;
|
|
|
return attrs;
|
|
|
}
|
|
|
options = options ? _.clone(options) : {};
|
|
|
options.collection = this;
|
|
|
|
|
|
var model;
|
|
|
if (this.model.prototype) {
|
|
|
model = new this.model(attrs, options);
|
|
|
} else {
|
|
|
// ES class methods didn't have prototype
|
|
|
model = this.model(attrs, options);
|
|
|
}
|
|
|
|
|
|
if (!model.validationError) return model;
|
|
|
this.trigger('invalid', this, model.validationError, options);
|
|
|
return false;
|
|
|
},
|
|
|
|
|
|
// Internal method called by both remove and set.
|
|
|
_removeModels: function(models, options) {
|
|
|
var removed = [];
|
|
|
for (var i = 0; i < models.length; i++) {
|
|
|
var model = this.get(models[i]);
|
|
|
if (!model) continue;
|
|
|
|
|
|
var index = this.indexOf(model);
|
|
|
this.models.splice(index, 1);
|
|
|
this.length--;
|
|
|
|
|
|
// Remove references before triggering 'remove' event to prevent an
|
|
|
// infinite loop. #3693
|
|
|
delete this._byId[model.cid];
|
|
|
var id = this.modelId(model.attributes, model.idAttribute);
|
|
|
if (id != null) delete this._byId[id];
|
|
|
|
|
|
if (!options.silent) {
|
|
|
options.index = index;
|
|
|
model.trigger('remove', model, this, options);
|
|
|
}
|
|
|
|
|
|
removed.push(model);
|
|
|
this._removeReference(model, options);
|
|
|
}
|
|
|
if (models.length > 0 && !options.silent) delete options.index;
|
|
|
return removed;
|
|
|
},
|
|
|
|
|
|
// Method for checking whether an object should be considered a model for
|
|
|
// the purposes of adding to the collection.
|
|
|
_isModel: function(model) {
|
|
|
return model instanceof Model;
|
|
|
},
|
|
|
|
|
|
// Internal method to create a model's ties to a collection.
|
|
|
_addReference: function(model, options) {
|
|
|
this._byId[model.cid] = model;
|
|
|
var id = this.modelId(model.attributes, model.idAttribute);
|
|
|
if (id != null) this._byId[id] = model;
|
|
|
model.on('all', this._onModelEvent, this);
|
|
|
},
|
|
|
|
|
|
// Internal method to sever a model's ties to a collection.
|
|
|
_removeReference: function(model, options) {
|
|
|
delete this._byId[model.cid];
|
|
|
var id = this.modelId(model.attributes, model.idAttribute);
|
|
|
if (id != null) delete this._byId[id];
|
|
|
if (this === model.collection) delete model.collection;
|
|
|
model.off('all', this._onModelEvent, this);
|
|
|
},
|
|
|
|
|
|
// Internal method called every time a model in the set fires an event.
|
|
|
// Sets need to update their indexes when models change ids. All other
|
|
|
// events simply proxy through. "add" and "remove" events that originate
|
|
|
// in other collections are ignored.
|
|
|
_onModelEvent: function(event, model, collection, options) {
|
|
|
if (model) {
|
|
|
if ((event === 'add' || event === 'remove') && collection !== this) return;
|
|
|
if (event === 'destroy') this.remove(model, options);
|
|
|
if (event === 'changeId') {
|
|
|
var prevId = this.modelId(model.previousAttributes(), model.idAttribute);
|
|
|
var id = this.modelId(model.attributes, model.idAttribute);
|
|
|
if (prevId != null) delete this._byId[prevId];
|
|
|
if (id != null) this._byId[id] = model;
|
|
|
}
|
|
|
}
|
|
|
this.trigger.apply(this, arguments);
|
|
|
},
|
|
|
|
|
|
// Internal callback method used in `create`. It serves as a
|
|
|
// stand-in for the `_onModelEvent` method, which is not yet bound
|
|
|
// during the `wait` period of the `create` call. We still want to
|
|
|
// forward any `'error'` event at the end of the `wait` period,
|
|
|
// hence a customized callback.
|
|
|
_forwardPristineError: function(model, collection, options) {
|
|
|
// Prevent double forward if the model was already in the
|
|
|
// collection before the call to `create`.
|
|
|
if (this.has(model)) return;
|
|
|
this._onModelEvent('error', model, collection, options);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// Defining an @@iterator method implements JavaScript's Iterable protocol.
|
|
|
// In modern ES2015 browsers, this value is found at Symbol.iterator.
|
|
|
/* global Symbol */
|
|
|
var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
|
|
|
if ($$iterator) {
|
|
|
Collection.prototype[$$iterator] = Collection.prototype.values;
|
|
|
}
|
|
|
|
|
|
// CollectionIterator
|
|
|
// ------------------
|
|
|
|
|
|
// A CollectionIterator implements JavaScript's Iterator protocol, allowing the
|
|
|
// use of `for of` loops in modern browsers and interoperation between
|
|
|
// Backbone.Collection and other JavaScript functions and third-party libraries
|
|
|
// which can operate on Iterables.
|
|
|
var CollectionIterator = function(collection, kind) {
|
|
|
this._collection = collection;
|
|
|
this._kind = kind;
|
|
|
this._index = 0;
|
|
|
};
|
|
|
|
|
|
// This "enum" defines the three possible kinds of values which can be emitted
|
|
|
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
|
|
// methods on Collection, respectively.
|
|
|
var ITERATOR_VALUES = 1;
|
|
|
var ITERATOR_KEYS = 2;
|
|
|
var ITERATOR_KEYSVALUES = 3;
|
|
|
|
|
|
// All Iterators should themselves be Iterable.
|
|
|
if ($$iterator) {
|
|
|
CollectionIterator.prototype[$$iterator] = function() {
|
|
|
return this;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
CollectionIterator.prototype.next = function() {
|
|
|
if (this._collection) {
|
|
|
|
|
|
// Only continue iterating if the iterated collection is long enough.
|
|
|
if (this._index < this._collection.length) {
|
|
|
var model = this._collection.at(this._index);
|
|
|
this._index++;
|
|
|
|
|
|
// Construct a value depending on what kind of values should be iterated.
|
|
|
var value;
|
|
|
if (this._kind === ITERATOR_VALUES) {
|
|
|
value = model;
|
|
|
} else {
|
|
|
var id = this._collection.modelId(model.attributes, model.idAttribute);
|
|
|
if (this._kind === ITERATOR_KEYS) {
|
|
|
value = id;
|
|
|
} else { // ITERATOR_KEYSVALUES
|
|
|
value = [id, model];
|
|
|
}
|
|
|
}
|
|
|
return {value: value, done: false};
|
|
|
}
|
|
|
|
|
|
// Once exhausted, remove the reference to the collection so future
|
|
|
// calls to the next method always return done.
|
|
|
this._collection = void 0;
|
|
|
}
|
|
|
|
|
|
return {value: void 0, done: true};
|
|
|
};
|
|
|
|
|
|
// Backbone.View
|
|
|
// -------------
|
|
|
|
|
|
// Backbone Views are almost more convention than they are actual code. A View
|
|
|
// is simply a JavaScript object that represents a logical chunk of UI in the
|
|
|
// DOM. This might be a single item, an entire list, a sidebar or panel, or
|
|
|
// even the surrounding frame which wraps your whole app. Defining a chunk of
|
|
|
// UI as a **View** allows you to define your DOM events declaratively, without
|
|
|
// having to worry about render order ... and makes it easy for the view to
|
|
|
// react to specific changes in the state of your models.
|
|
|
|
|
|
// Creating a Backbone.View creates its initial element outside of the DOM,
|
|
|
// if an existing element is not provided...
|
|
|
var View = Backbone.View = function(options) {
|
|
|
this.cid = _.uniqueId('view');
|
|
|
this.preinitialize.apply(this, arguments);
|
|
|
_.extend(this, _.pick(options, viewOptions));
|
|
|
this._ensureElement();
|
|
|
this.initialize.apply(this, arguments);
|
|
|
};
|
|
|
|
|
|
// Cached regex to split keys for `delegate`.
|
|
|
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
|
|
|
|
|
|
// List of view options to be set as properties.
|
|
|
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
|
|
|
|
|
|
// Set up all inheritable **Backbone.View** properties and methods.
|
|
|
_.extend(View.prototype, Events, {
|
|
|
|
|
|
// The default `tagName` of a View's element is `"div"`.
|
|
|
tagName: 'div',
|
|
|
|
|
|
// jQuery delegate for element lookup, scoped to DOM elements within the
|
|
|
// current view. This should be preferred to global lookups where possible.
|
|
|
$: function(selector) {
|
|
|
return this.$el.find(selector);
|
|
|
},
|
|
|
|
|
|
// preinitialize is an empty function by default. You can override it with a function
|
|
|
// or object. preinitialize will run before any instantiation logic is run in the View
|
|
|
preinitialize: function(){},
|
|
|
|
|
|
// Initialize is an empty function by default. Override it with your own
|
|
|
// initialization logic.
|
|
|
initialize: function(){},
|
|
|
|
|
|
// **render** is the core function that your view should override, in order
|
|
|
// to populate its element (`this.el`), with the appropriate HTML. The
|
|
|
// convention is for **render** to always return `this`.
|
|
|
render: function() {
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Remove this view by taking the element out of the DOM, and removing any
|
|
|
// applicable Backbone.Events listeners.
|
|
|
remove: function() {
|
|
|
this._removeElement();
|
|
|
this.stopListening();
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Remove this view's element from the document and all event listeners
|
|
|
// attached to it. Exposed for subclasses using an alternative DOM
|
|
|
// manipulation API.
|
|
|
_removeElement: function() {
|
|
|
this.$el.remove();
|
|
|
},
|
|
|
|
|
|
// Change the view's element (`this.el` property) and re-delegate the
|
|
|
// view's events on the new element.
|
|
|
setElement: function(element) {
|
|
|
this.undelegateEvents();
|
|
|
this._setElement(element);
|
|
|
this.delegateEvents();
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Creates the `this.el` and `this.$el` references for this view using the
|
|
|
// given `el`. `el` can be a CSS selector or an HTML string, a jQuery
|
|
|
// context or an element. Subclasses can override this to utilize an
|
|
|
// alternative DOM manipulation API and are only required to set the
|
|
|
// `this.el` property.
|
|
|
_setElement: function(el) {
|
|
|
this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
|
|
|
this.el = this.$el[0];
|
|
|
},
|
|
|
|
|
|
// Set callbacks, where `this.events` is a hash of
|
|
|
//
|
|
|
// *{"event selector": "callback"}*
|
|
|
//
|
|
|
// {
|
|
|
// 'mousedown .title': 'edit',
|
|
|
// 'click .button': 'save',
|
|
|
// 'click .open': function(e) { ... }
|
|
|
// }
|
|
|
//
|
|
|
// pairs. Callbacks will be bound to the view, with `this` set properly.
|
|
|
// Uses event delegation for efficiency.
|
|
|
// Omitting the selector binds the event to `this.el`.
|
|
|
delegateEvents: function(events) {
|
|
|
events || (events = _.result(this, 'events'));
|
|
|
if (!events) return this;
|
|
|
this.undelegateEvents();
|
|
|
for (var key in events) {
|
|
|
var method = events[key];
|
|
|
if (!_.isFunction(method)) method = this[method];
|
|
|
if (!method) continue;
|
|
|
var match = key.match(delegateEventSplitter);
|
|
|
this.delegate(match[1], match[2], method.bind(this));
|
|
|
}
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Add a single event listener to the view's element (or a child element
|
|
|
// using `selector`). This only works for delegate-able events: not `focus`,
|
|
|
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
|
|
|
delegate: function(eventName, selector, listener) {
|
|
|
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Clears all callbacks previously bound to the view by `delegateEvents`.
|
|
|
// You usually don't need to use this, but may wish to if you have multiple
|
|
|
// Backbone views attached to the same DOM element.
|
|
|
undelegateEvents: function() {
|
|
|
if (this.$el) this.$el.off('.delegateEvents' + this.cid);
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// A finer-grained `undelegateEvents` for removing a single delegated event.
|
|
|
// `selector` and `listener` are both optional.
|
|
|
undelegate: function(eventName, selector, listener) {
|
|
|
this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Produces a DOM element to be assigned to your view. Exposed for
|
|
|
// subclasses using an alternative DOM manipulation API.
|
|
|
_createElement: function(tagName) {
|
|
|
return document.createElement(tagName);
|
|
|
},
|
|
|
|
|
|
// Ensure that the View has a DOM element to render into.
|
|
|
// If `this.el` is a string, pass it through `$()`, take the first
|
|
|
// matching element, and re-assign it to `el`. Otherwise, create
|
|
|
// an element from the `id`, `className` and `tagName` properties.
|
|
|
_ensureElement: function() {
|
|
|
if (!this.el) {
|
|
|
var attrs = _.extend({}, _.result(this, 'attributes'));
|
|
|
if (this.id) attrs.id = _.result(this, 'id');
|
|
|
if (this.className) attrs['class'] = _.result(this, 'className');
|
|
|
this.setElement(this._createElement(_.result(this, 'tagName')));
|
|
|
this._setAttributes(attrs);
|
|
|
} else {
|
|
|
this.setElement(_.result(this, 'el'));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Set attributes from a hash on this view's element. Exposed for
|
|
|
// subclasses using an alternative DOM manipulation API.
|
|
|
_setAttributes: function(attributes) {
|
|
|
this.$el.attr(attributes);
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// Proxy Backbone class methods to Underscore functions, wrapping the model's
|
|
|
// `attributes` object or collection's `models` array behind the scenes.
|
|
|
//
|
|
|
// collection.filter(function(model) { return model.get('age') > 10 });
|
|
|
// collection.each(this.addView);
|
|
|
//
|
|
|
// `Function#apply` can be slow so we use the method's arg count, if we know it.
|
|
|
var addMethod = function(base, length, method, attribute) {
|
|
|
switch (length) {
|
|
|
case 1: return function() {
|
|
|
return base[method](this[attribute]);
|
|
|
};
|
|
|
case 2: return function(value) {
|
|
|
return base[method](this[attribute], value);
|
|
|
};
|
|
|
case 3: return function(iteratee, context) {
|
|
|
return base[method](this[attribute], cb(iteratee, this), context);
|
|
|
};
|
|
|
case 4: return function(iteratee, defaultVal, context) {
|
|
|
return base[method](this[attribute], cb(iteratee, this), defaultVal, context);
|
|
|
};
|
|
|
default: return function() {
|
|
|
var args = slice.call(arguments);
|
|
|
args.unshift(this[attribute]);
|
|
|
return base[method].apply(base, args);
|
|
|
};
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var addUnderscoreMethods = function(Class, base, methods, attribute) {
|
|
|
_.each(methods, function(length, method) {
|
|
|
if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute);
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
|
|
|
var cb = function(iteratee, instance) {
|
|
|
if (_.isFunction(iteratee)) return iteratee;
|
|
|
if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
|
|
|
if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
|
|
|
return iteratee;
|
|
|
};
|
|
|
var modelMatcher = function(attrs) {
|
|
|
var matcher = _.matches(attrs);
|
|
|
return function(model) {
|
|
|
return matcher(model.attributes);
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// Underscore methods that we want to implement on the Collection.
|
|
|
// 90% of the core usefulness of Backbone Collections is actually implemented
|
|
|
// right here:
|
|
|
var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
|
|
|
foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
|
|
|
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
|
|
|
contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
|
|
|
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
|
|
|
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
|
|
|
isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
|
|
|
sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
|
|
|
|
|
|
|
|
|
// Underscore methods that we want to implement on the Model, mapped to the
|
|
|
// number of arguments they take.
|
|
|
var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
|
|
|
omit: 0, chain: 1, isEmpty: 1};
|
|
|
|
|
|
// Mix in each Underscore method as a proxy to `Collection#models`.
|
|
|
|
|
|
_.each([
|
|
|
[Collection, collectionMethods, 'models'],
|
|
|
[Model, modelMethods, 'attributes']
|
|
|
], function(config) {
|
|
|
var Base = config[0],
|
|
|
methods = config[1],
|
|
|
attribute = config[2];
|
|
|
|
|
|
Base.mixin = function(obj) {
|
|
|
var mappings = _.reduce(_.functions(obj), function(memo, name) {
|
|
|
memo[name] = 0;
|
|
|
return memo;
|
|
|
}, {});
|
|
|
addUnderscoreMethods(Base, obj, mappings, attribute);
|
|
|
};
|
|
|
|
|
|
addUnderscoreMethods(Base, _, methods, attribute);
|
|
|
});
|
|
|
|
|
|
// Backbone.sync
|
|
|
// -------------
|
|
|
|
|
|
// Override this function to change the manner in which Backbone persists
|
|
|
// models to the server. You will be passed the type of request, and the
|
|
|
// model in question. By default, makes a RESTful Ajax request
|
|
|
// to the model's `url()`. Some possible customizations could be:
|
|
|
//
|
|
|
// * Use `setTimeout` to batch rapid-fire updates into a single request.
|
|
|
// * Send up the models as XML instead of JSON.
|
|
|
// * Persist models via WebSockets instead of Ajax.
|
|
|
//
|
|
|
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
|
|
|
// as `POST`, with a `_method` parameter containing the true HTTP method,
|
|
|
// as well as all requests with the body as `application/x-www-form-urlencoded`
|
|
|
// instead of `application/json` with the model in a param named `model`.
|
|
|
// Useful when interfacing with server-side languages like **PHP** that make
|
|
|
// it difficult to read the body of `PUT` requests.
|
|
|
Backbone.sync = function(method, model, options) {
|
|
|
var type = methodMap[method];
|
|
|
|
|
|
// Default options, unless specified.
|
|
|
_.defaults(options || (options = {}), {
|
|
|
emulateHTTP: Backbone.emulateHTTP,
|
|
|
emulateJSON: Backbone.emulateJSON
|
|
|
});
|
|
|
|
|
|
// Default JSON-request options.
|
|
|
var params = {type: type, dataType: 'json'};
|
|
|
|
|
|
// Ensure that we have a URL.
|
|
|
if (!options.url) {
|
|
|
params.url = _.result(model, 'url') || urlError();
|
|
|
}
|
|
|
|
|
|
// Ensure that we have the appropriate request data.
|
|
|
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
|
|
params.contentType = 'application/json';
|
|
|
params.data = JSON.stringify(options.attrs || model.toJSON(options));
|
|
|
}
|
|
|
|
|
|
// For older servers, emulate JSON by encoding the request into an HTML-form.
|
|
|
if (options.emulateJSON) {
|
|
|
params.contentType = 'application/x-www-form-urlencoded';
|
|
|
params.data = params.data ? {model: params.data} : {};
|
|
|
}
|
|
|
|
|
|
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
|
|
|
// And an `X-HTTP-Method-Override` header.
|
|
|
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
|
|
|
params.type = 'POST';
|
|
|
if (options.emulateJSON) params.data._method = type;
|
|
|
var beforeSend = options.beforeSend;
|
|
|
options.beforeSend = function(xhr) {
|
|
|
xhr.setRequestHeader('X-HTTP-Method-Override', type);
|
|
|
if (beforeSend) return beforeSend.apply(this, arguments);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Don't process data on a non-GET request.
|
|
|
if (params.type !== 'GET' && !options.emulateJSON) {
|
|
|
params.processData = false;
|
|
|
}
|
|
|
|
|
|
// Pass along `textStatus` and `errorThrown` from jQuery.
|
|
|
var error = options.error;
|
|
|
options.error = function(xhr, textStatus, errorThrown) {
|
|
|
options.textStatus = textStatus;
|
|
|
options.errorThrown = errorThrown;
|
|
|
if (error) error.call(options.context, xhr, textStatus, errorThrown);
|
|
|
};
|
|
|
|
|
|
// Make the request, allowing the user to override any Ajax options.
|
|
|
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
|
|
|
model.trigger('request', model, xhr, options);
|
|
|
return xhr;
|
|
|
};
|
|
|
|
|
|
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
|
|
var methodMap = {
|
|
|
'create': 'POST',
|
|
|
'update': 'PUT',
|
|
|
'patch': 'PATCH',
|
|
|
'delete': 'DELETE',
|
|
|
'read': 'GET'
|
|
|
};
|
|
|
|
|
|
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
|
|
|
// Override this if you'd like to use a different library.
|
|
|
Backbone.ajax = function() {
|
|
|
return Backbone.$.ajax.apply(Backbone.$, arguments);
|
|
|
};
|
|
|
|
|
|
// Backbone.Router
|
|
|
// ---------------
|
|
|
|
|
|
// Routers map faux-URLs to actions, and fire events when routes are
|
|
|
// matched. Creating a new one sets its `routes` hash, if not set statically.
|
|
|
var Router = Backbone.Router = function(options) {
|
|
|
options || (options = {});
|
|
|
this.preinitialize.apply(this, arguments);
|
|
|
if (options.routes) this.routes = options.routes;
|
|
|
this._bindRoutes();
|
|
|
this.initialize.apply(this, arguments);
|
|
|
};
|
|
|
|
|
|
// Cached regular expressions for matching named param parts and splatted
|
|
|
// parts of route strings.
|
|
|
var optionalParam = /\((.*?)\)/g;
|
|
|
var namedParam = /(\(\?)?:\w+/g;
|
|
|
var splatParam = /\*\w+/g;
|
|
|
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
|
|
|
|
|
|
// Set up all inheritable **Backbone.Router** properties and methods.
|
|
|
_.extend(Router.prototype, Events, {
|
|
|
|
|
|
// preinitialize is an empty function by default. You can override it with a function
|
|
|
// or object. preinitialize will run before any instantiation logic is run in the Router.
|
|
|
preinitialize: function(){},
|
|
|
|
|
|
// Initialize is an empty function by default. Override it with your own
|
|
|
// initialization logic.
|
|
|
initialize: function(){},
|
|
|
|
|
|
// Manually bind a single named route to a callback. For example:
|
|
|
//
|
|
|
// this.route('search/:query/p:num', 'search', function(query, num) {
|
|
|
// ...
|
|
|
// });
|
|
|
//
|
|
|
route: function(route, name, callback) {
|
|
|
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
|
|
|
if (_.isFunction(name)) {
|
|
|
callback = name;
|
|
|
name = '';
|
|
|
}
|
|
|
if (!callback) callback = this[name];
|
|
|
var router = this;
|
|
|
Backbone.history.route(route, function(fragment) {
|
|
|
var args = router._extractParameters(route, fragment);
|
|
|
if (router.execute(callback, args, name) !== false) {
|
|
|
router.trigger.apply(router, ['route:' + name].concat(args));
|
|
|
router.trigger('route', name, args);
|
|
|
Backbone.history.trigger('route', router, name, args);
|
|
|
}
|
|
|
});
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Execute a route handler with the provided parameters. This is an
|
|
|
// excellent place to do pre-route setup or post-route cleanup.
|
|
|
execute: function(callback, args, name) {
|
|
|
if (callback) callback.apply(this, args);
|
|
|
},
|
|
|
|
|
|
// Simple proxy to `Backbone.history` to save a fragment into the history.
|
|
|
navigate: function(fragment, options) {
|
|
|
Backbone.history.navigate(fragment, options);
|
|
|
return this;
|
|
|
},
|
|
|
|
|
|
// Bind all defined routes to `Backbone.history`. We have to reverse the
|
|
|
// order of the routes here to support behavior where the most general
|
|
|
// routes can be defined at the bottom of the route map.
|
|
|
_bindRoutes: function() {
|
|
|
if (!this.routes) return;
|
|
|
this.routes = _.result(this, 'routes');
|
|
|
var route, routes = _.keys(this.routes);
|
|
|
while ((route = routes.pop()) != null) {
|
|
|
this.route(route, this.routes[route]);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Convert a route string into a regular expression, suitable for matching
|
|
|
// against the current location hash.
|
|
|
_routeToRegExp: function(route) {
|
|
|
route = route.replace(escapeRegExp, '\\$&')
|
|
|
.replace(optionalParam, '(?:$1)?')
|
|
|
.replace(namedParam, function(match, optional) {
|
|
|
return optional ? match : '([^/?]+)';
|
|
|
})
|
|
|
.replace(splatParam, '([^?]*?)');
|
|
|
return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
|
|
|
},
|
|
|
|
|
|
// Given a route, and a URL fragment that it matches, return the array of
|
|
|
// extracted decoded parameters. Empty or unmatched parameters will be
|
|
|
// treated as `null` to normalize cross-browser behavior.
|
|
|
_extractParameters: function(route, fragment) {
|
|
|
var params = route.exec(fragment).slice(1);
|
|
|
return _.map(params, function(param, i) {
|
|
|
// Don't decode the search params.
|
|
|
if (i === params.length - 1) return param || null;
|
|
|
return param ? decodeURIComponent(param) : null;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// Backbone.History
|
|
|
// ----------------
|
|
|
|
|
|
// Handles cross-browser history management, based on either
|
|
|
// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
|
|
|
// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
|
|
|
// and URL fragments. If the browser supports neither (old IE, natch),
|
|
|
// falls back to polling.
|
|
|
var History = Backbone.History = function() {
|
|
|
this.handlers = [];
|
|
|
this.checkUrl = this.checkUrl.bind(this);
|
|
|
|
|
|
// Ensure that `History` can be used outside of the browser.
|
|
|
if (typeof window !== 'undefined') {
|
|
|
this.location = window.location;
|
|
|
this.history = window.history;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// Cached regex for stripping a leading hash/slash and trailing space.
|
|
|
var routeStripper = /^[#\/]|\s+$/g;
|
|
|
|
|
|
// Cached regex for stripping leading and trailing slashes.
|
|
|
var rootStripper = /^\/+|\/+$/g;
|
|
|
|
|
|
// Cached regex for stripping urls of hash.
|
|
|
var pathStripper = /#.*$/;
|
|
|
|
|
|
// Has the history handling already been started?
|
|
|
History.started = false;
|
|
|
|
|
|
// Set up all inheritable **Backbone.History** properties and methods.
|
|
|
_.extend(History.prototype, Events, {
|
|
|
|
|
|
// The default interval to poll for hash changes, if necessary, is
|
|
|
// twenty times a second.
|
|
|
interval: 50,
|
|
|
|
|
|
// Are we at the app root?
|
|
|
atRoot: function() {
|
|
|
var path = this.location.pathname.replace(/[^\/]$/, '$&/');
|
|
|
return path === this.root && !this.getSearch();
|
|
|
},
|
|
|
|
|
|
// Does the pathname match the root?
|
|
|
matchRoot: function() {
|
|
|
var path = this.decodeFragment(this.location.pathname);
|
|
|
var rootPath = path.slice(0, this.root.length - 1) + '/';
|
|
|
return rootPath === this.root;
|
|
|
},
|
|
|
|
|
|
// Unicode characters in `location.pathname` are percent encoded so they're
|
|
|
// decoded for comparison. `%25` should not be decoded since it may be part
|
|
|
// of an encoded parameter.
|
|
|
decodeFragment: function(fragment) {
|
|
|
return decodeURI(fragment.replace(/%25/g, '%2525'));
|
|
|
},
|
|
|
|
|
|
// In IE6, the hash fragment and search params are incorrect if the
|
|
|
// fragment contains `?`.
|
|
|
getSearch: function() {
|
|
|
var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
|
|
|
return match ? match[0] : '';
|
|
|
},
|
|
|
|
|
|
// Gets the true hash value. Cannot use location.hash directly due to bug
|
|
|
// in Firefox where location.hash will always be decoded.
|
|
|
getHash: function(window) {
|
|
|
var match = (window || this).location.href.match(/#(.*)$/);
|
|
|
return match ? match[1] : '';
|
|
|
},
|
|
|
|
|
|
// Get the pathname and search params, without the root.
|
|
|
getPath: function() {
|
|
|
var path = this.decodeFragment(
|
|
|
this.location.pathname + this.getSearch()
|
|
|
).slice(this.root.length - 1);
|
|
|
return path.charAt(0) === '/' ? path.slice(1) : path;
|
|
|
},
|
|
|
|
|
|
// Get the cross-browser normalized URL fragment from the path or hash.
|
|
|
getFragment: function(fragment) {
|
|
|
if (fragment == null) {
|
|
|
if (this._usePushState || !this._wantsHashChange) {
|
|
|
fragment = this.getPath();
|
|
|
} else {
|
|
|
fragment = this.getHash();
|
|
|
}
|
|
|
}
|
|
|
return fragment.replace(routeStripper, '');
|
|
|
},
|
|
|
|
|
|
// Start the hash change handling, returning `true` if the current URL matches
|
|
|
// an existing route, and `false` otherwise.
|
|
|
start: function(options) {
|
|
|
if (History.started) throw new Error('Backbone.history has already been started');
|
|
|
History.started = true;
|
|
|
|
|
|
// Figure out the initial configuration. Do we need an iframe?
|
|
|
// Is pushState desired ... is it available?
|
|
|
this.options = _.extend({root: '/'}, this.options, options);
|
|
|
this.root = this.options.root;
|
|
|
this._trailingSlash = this.options.trailingSlash;
|
|
|
this._wantsHashChange = this.options.hashChange !== false;
|
|
|
this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
|
|
|
this._useHashChange = this._wantsHashChange && this._hasHashChange;
|
|
|
this._wantsPushState = !!this.options.pushState;
|
|
|
this._hasPushState = !!(this.history && this.history.pushState);
|
|
|
this._usePushState = this._wantsPushState && this._hasPushState;
|
|
|
this.fragment = this.getFragment();
|
|
|
|
|
|
// Normalize root to always include a leading and trailing slash.
|
|
|
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
|
|
|
|
|
|
// Transition from hashChange to pushState or vice versa if both are
|
|
|
// requested.
|
|
|
if (this._wantsHashChange && this._wantsPushState) {
|
|
|
|
|
|
// If we've started off with a route from a `pushState`-enabled
|
|
|
// browser, but we're currently in a browser that doesn't support it...
|
|
|
if (!this._hasPushState && !this.atRoot()) {
|
|
|
var rootPath = this.root.slice(0, -1) || '/';
|
|
|
this.location.replace(rootPath + '#' + this.getPath());
|
|
|
// Return immediately as browser will do redirect to new url
|
|
|
return true;
|
|
|
|
|
|
// Or if we've started out with a hash-based route, but we're currently
|
|
|
// in a browser where it could be `pushState`-based instead...
|
|
|
} else if (this._hasPushState && this.atRoot()) {
|
|
|
this.navigate(this.getHash(), {replace: true});
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Proxy an iframe to handle location events if the browser doesn't
|
|
|
// support the `hashchange` event, HTML5 history, or the user wants
|
|
|
// `hashChange` but not `pushState`.
|
|
|
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
|
|
|
this.iframe = document.createElement('iframe');
|
|
|
this.iframe.src = 'javascript:0';
|
|
|
this.iframe.style.display = 'none';
|
|
|
this.iframe.tabIndex = -1;
|
|
|
var body = document.body;
|
|
|
// Using `appendChild` will throw on IE < 9 if the document is not ready.
|
|
|
var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
|
|
|
iWindow.document.open();
|
|
|
iWindow.document.close();
|
|
|
iWindow.location.hash = '#' + this.fragment;
|
|
|
}
|
|
|
|
|
|
// Add a cross-platform `addEventListener` shim for older browsers.
|
|
|
var addEventListener = window.addEventListener || function(eventName, listener) {
|
|
|
return attachEvent('on' + eventName, listener);
|
|
|
};
|
|
|
|
|
|
// Depending on whether we're using pushState or hashes, and whether
|
|
|
// 'onhashchange' is supported, determine how we check the URL state.
|
|
|
if (this._usePushState) {
|
|
|
addEventListener('popstate', this.checkUrl, false);
|
|
|
} else if (this._useHashChange && !this.iframe) {
|
|
|
addEventListener('hashchange', this.checkUrl, false);
|
|
|
} else if (this._wantsHashChange) {
|
|
|
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
|
|
|
}
|
|
|
|
|
|
if (!this.options.silent) return this.loadUrl();
|
|
|
},
|
|
|
|
|
|
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
|
|
|
// but possibly useful for unit testing Routers.
|
|
|
stop: function() {
|
|
|
// Add a cross-platform `removeEventListener` shim for older browsers.
|
|
|
var removeEventListener = window.removeEventListener || function(eventName, listener) {
|
|
|
return detachEvent('on' + eventName, listener);
|
|
|
};
|
|
|
|
|
|
// Remove window listeners.
|
|
|
if (this._usePushState) {
|
|
|
removeEventListener('popstate', this.checkUrl, false);
|
|
|
} else if (this._useHashChange && !this.iframe) {
|
|
|
removeEventListener('hashchange', this.checkUrl, false);
|
|
|
}
|
|
|
|
|
|
// Clean up the iframe if necessary.
|
|
|
if (this.iframe) {
|
|
|
document.body.removeChild(this.iframe);
|
|
|
this.iframe = null;
|
|
|
}
|
|
|
|
|
|
// Some environments will throw when clearing an undefined interval.
|
|
|
if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
|
|
|
History.started = false;
|
|
|
},
|
|
|
|
|
|
// Add a route to be tested when the fragment changes. Routes added later
|
|
|
// may override previous routes.
|
|
|
route: function(route, callback) {
|
|
|
this.handlers.unshift({route: route, callback: callback});
|
|
|
},
|
|
|
|
|
|
// Checks the current URL to see if it has changed, and if it has,
|
|
|
// calls `loadUrl`, normalizing across the hidden iframe.
|
|
|
checkUrl: function(e) {
|
|
|
var current = this.getFragment();
|
|
|
|
|
|
// If the user pressed the back button, the iframe's hash will have
|
|
|
// changed and we should use that for comparison.
|
|
|
if (current === this.fragment && this.iframe) {
|
|
|
current = this.getHash(this.iframe.contentWindow);
|
|
|
}
|
|
|
|
|
|
if (current === this.fragment) {
|
|
|
if (!this.matchRoot()) return this.notfound();
|
|
|
return false;
|
|
|
}
|
|
|
if (this.iframe) this.navigate(current);
|
|
|
this.loadUrl();
|
|
|
},
|
|
|
|
|
|
// Attempt to load the current URL fragment. If a route succeeds with a
|
|
|
// match, returns `true`. If no defined routes matches the fragment,
|
|
|
// returns `false`.
|
|
|
loadUrl: function(fragment) {
|
|
|
// If the root doesn't match, no routes can match either.
|
|
|
if (!this.matchRoot()) return this.notfound();
|
|
|
fragment = this.fragment = this.getFragment(fragment);
|
|
|
return _.some(this.handlers, function(handler) {
|
|
|
if (handler.route.test(fragment)) {
|
|
|
handler.callback(fragment);
|
|
|
return true;
|
|
|
}
|
|
|
}) || this.notfound();
|
|
|
},
|
|
|
|
|
|
// When no route could be matched, this method is called internally to
|
|
|
// trigger the `'notfound'` event. It returns `false` so that it can be used
|
|
|
// in tail position.
|
|
|
notfound: function() {
|
|
|
this.trigger('notfound');
|
|
|
return false;
|
|
|
},
|
|
|
|
|
|
// Save a fragment into the hash history, or replace the URL state if the
|
|
|
// 'replace' option is passed. You are responsible for properly URL-encoding
|
|
|
// the fragment in advance.
|
|
|
//
|
|
|
// The options object can contain `trigger: true` if you wish to have the
|
|
|
// route callback be fired (not usually desirable), or `replace: true`, if
|
|
|
// you wish to modify the current URL without adding an entry to the history.
|
|
|
navigate: function(fragment, options) {
|
|
|
if (!History.started) return false;
|
|
|
if (!options || options === true) options = {trigger: !!options};
|
|
|
|
|
|
// Normalize the fragment.
|
|
|
fragment = this.getFragment(fragment || '');
|
|
|
|
|
|
// Strip trailing slash on the root unless _trailingSlash is true
|
|
|
var rootPath = this.root;
|
|
|
if (!this._trailingSlash && (fragment === '' || fragment.charAt(0) === '?')) {
|
|
|
rootPath = rootPath.slice(0, -1) || '/';
|
|
|
}
|
|
|
var url = rootPath + fragment;
|
|
|
|
|
|
// Strip the fragment of the query and hash for matching.
|
|
|
fragment = fragment.replace(pathStripper, '');
|
|
|
|
|
|
// Decode for matching.
|
|
|
var decodedFragment = this.decodeFragment(fragment);
|
|
|
|
|
|
if (this.fragment === decodedFragment) return;
|
|
|
this.fragment = decodedFragment;
|
|
|
|
|
|
// If pushState is available, we use it to set the fragment as a real URL.
|
|
|
if (this._usePushState) {
|
|
|
this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
|
|
|
|
|
|
// If hash changes haven't been explicitly disabled, update the hash
|
|
|
// fragment to store history.
|
|
|
} else if (this._wantsHashChange) {
|
|
|
this._updateHash(this.location, fragment, options.replace);
|
|
|
if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
|
|
|
var iWindow = this.iframe.contentWindow;
|
|
|
|
|
|
// Opening and closing the iframe tricks IE7 and earlier to push a
|
|
|
// history entry on hash-tag change. When replace is true, we don't
|
|
|
// want this.
|
|
|
if (!options.replace) {
|
|
|
iWindow.document.open();
|
|
|
iWindow.document.close();
|
|
|
}
|
|
|
|
|
|
this._updateHash(iWindow.location, fragment, options.replace);
|
|
|
}
|
|
|
|
|
|
// If you've told us that you explicitly don't want fallback hashchange-
|
|
|
// based history, then `navigate` becomes a page refresh.
|
|
|
} else {
|
|
|
return this.location.assign(url);
|
|
|
}
|
|
|
if (options.trigger) return this.loadUrl(fragment);
|
|
|
},
|
|
|
|
|
|
// Update the hash location, either replacing the current entry, or adding
|
|
|
// a new one to the browser history.
|
|
|
_updateHash: function(location, fragment, replace) {
|
|
|
if (replace) {
|
|
|
var href = location.href.replace(/(javascript:|#).*$/, '');
|
|
|
location.replace(href + '#' + fragment);
|
|
|
} else {
|
|
|
// Some browsers require that `hash` contains a leading #.
|
|
|
location.hash = '#' + fragment;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// Create the default Backbone.history.
|
|
|
Backbone.history = new History;
|
|
|
|
|
|
// Helpers
|
|
|
// -------
|
|
|
|
|
|
// Helper function to correctly set up the prototype chain for subclasses.
|
|
|
// Similar to `goog.inherits`, but uses a hash of prototype properties and
|
|
|
// class properties to be extended.
|
|
|
var extend = function(protoProps, staticProps) {
|
|
|
var parent = this;
|
|
|
var child;
|
|
|
|
|
|
// The constructor function for the new subclass is either defined by you
|
|
|
// (the "constructor" property in your `extend` definition), or defaulted
|
|
|
// by us to simply call the parent constructor.
|
|
|
if (protoProps && _.has(protoProps, 'constructor')) {
|
|
|
child = protoProps.constructor;
|
|
|
} else {
|
|
|
child = function(){ return parent.apply(this, arguments); };
|
|
|
}
|
|
|
|
|
|
// Add static properties to the constructor function, if supplied.
|
|
|
_.extend(child, parent, staticProps);
|
|
|
|
|
|
// Set the prototype chain to inherit from `parent`, without calling
|
|
|
// `parent`'s constructor function and add the prototype properties.
|
|
|
child.prototype = _.create(parent.prototype, protoProps);
|
|
|
child.prototype.constructor = child;
|
|
|
|
|
|
// Set a convenience property in case the parent's prototype is needed
|
|
|
// later.
|
|
|
child.__super__ = parent.prototype;
|
|
|
|
|
|
return child;
|
|
|
};
|
|
|
|
|
|
// Set up inheritance for the model, collection, router, view and history.
|
|
|
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
|
|
|
|
|
|
// Throw an error when a URL is needed, and none is supplied.
|
|
|
var urlError = function() {
|
|
|
throw new Error('A "url" property or function must be specified');
|
|
|
};
|
|
|
|
|
|
// Wrap an optional error callback with a fallback error event.
|
|
|
var wrapError = function(model, options) {
|
|
|
var error = options.error;
|
|
|
options.error = function(resp) {
|
|
|
if (error) error.call(options.context, model, resp, options);
|
|
|
model.trigger('error', model, resp, options);
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// Provide useful information when things go wrong. This method is not meant
|
|
|
// to be used directly; it merely provides the necessary introspection for the
|
|
|
// external `debugInfo` function.
|
|
|
Backbone._debug = function() {
|
|
|
return {root: root, _: _};
|
|
|
};
|
|
|
|
|
|
return Backbone;
|
|
|
});
|
|
|
|
|
|
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
|
|
|
|
|
|
/***/ }),
|
|
|
/* 8 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_8__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 9 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_9__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 10 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_10__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 11 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_11__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 12 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, module) {
|
|
|
var $ = __webpack_require__(1);
|
|
|
var _ = __webpack_require__(2);
|
|
|
var mvc = __webpack_require__(8);
|
|
|
|
|
|
var EntityContextPanelTokenUtils = {
|
|
|
/**
|
|
|
* Submits the given tokens to the appropriate token models
|
|
|
*
|
|
|
* @param {Object} tokens
|
|
|
*/
|
|
|
submitTokens: _.debounce(function (tokens) {
|
|
|
var defaultTokenModel = mvc.Components.getInstance('default', {create: true});
|
|
|
defaultTokenModel.set(tokens);
|
|
|
var submittedTokenModel = mvc.Components.getInstance('submitted');
|
|
|
if (submittedTokenModel) {
|
|
|
submittedTokenModel.set(defaultTokenModel.toJSON());
|
|
|
}
|
|
|
|
|
|
// For URL token model, only deploy the timepicker tokens
|
|
|
var urlTokenModel = mvc.Components.getInstance('url');
|
|
|
if (urlTokenModel) {
|
|
|
urlTokenModel.save(_.pick(tokens, ['earliest', 'latest']));
|
|
|
}
|
|
|
})
|
|
|
};
|
|
|
|
|
|
return EntityContextPanelTokenUtils;
|
|
|
}.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
/* 13 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_RESULT__ = function(require) {
|
|
|
|
|
|
var $ = __webpack_require__(1);
|
|
|
var _ = __webpack_require__(2);
|
|
|
var Backbone = __webpack_require__(7);
|
|
|
var mvc = __webpack_require__(8);
|
|
|
|
|
|
var MaintenanceModeBannerView = Backbone.View.extend({
|
|
|
|
|
|
events: {
|
|
|
'click .close-btn' : 'hide'
|
|
|
},
|
|
|
|
|
|
_bannerTemplate: [
|
|
|
'<div class="maintenance-mode-banner">',
|
|
|
' <div class="info-icon">',
|
|
|
' <i class="icon-info-circle"></i>',
|
|
|
' </div>',
|
|
|
' <div class="maintenance-mode-banner-text"><%= message %></div>',
|
|
|
' <div class="close-btn">',
|
|
|
' <i class="icon-x"></i>',
|
|
|
' </div>',
|
|
|
'</div>'
|
|
|
].join('\n'),
|
|
|
|
|
|
initialize: function(options) {
|
|
|
Backbone.View.prototype.initialize.apply(this, arguments);
|
|
|
this.message = options.message || _('Please enter a message for this banner.').t();
|
|
|
this.disableBanner = false;
|
|
|
},
|
|
|
|
|
|
render: function() {
|
|
|
var variables = {message: _.escape(this.message)};
|
|
|
var compiledTemplate = _.template(this._bannerTemplate, variables);
|
|
|
this.$el.append(compiledTemplate);
|
|
|
},
|
|
|
|
|
|
hide: function () {
|
|
|
this.$('.maintenance-mode-banner').fadeOut('fast','swing');
|
|
|
this.disableBanner = true;
|
|
|
},
|
|
|
|
|
|
show: function() {
|
|
|
if (!this.disableBanner) {
|
|
|
this.$('.maintenance-mode-banner').fadeIn('fast','swing');
|
|
|
this._adjustBannerWidth();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
_adjustBannerWidth: function () {
|
|
|
var $textEl = this.$('.maintenance-mode-banner-text');
|
|
|
if ($textEl.outerWidth() > 800) {
|
|
|
var shortMessage = $textEl.text().substr(0,100).concat('...');
|
|
|
$textEl.text(_.escape(shortMessage));
|
|
|
$textEl.attr('title', _.escape(this.message));
|
|
|
this.$('.maintenance-mode-banner').css({
|
|
|
cursor: 'pointer'
|
|
|
});
|
|
|
}
|
|
|
var textWidth = $textEl.outerWidth();
|
|
|
var width = textWidth + 62;
|
|
|
this.$('.maintenance-mode-banner').css({
|
|
|
width: width
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return MaintenanceModeBannerView;
|
|
|
}.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
/***/ }),
|
|
|
/* 14 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(15), __webpack_require__(9)], __WEBPACK_AMD_DEFINE_RESULT__ = function() {
|
|
|
|
|
|
/*
|
|
|
* monkey patches to the i18n library
|
|
|
* see SPL-75565
|
|
|
*/
|
|
|
|
|
|
// NOTE: this code block is duplicated in the util/test_i18n_patches.html unit test.
|
|
|
// any changes made here must also be reflected there.
|
|
|
if(window.locale_name() in { ko_KR: true, zh_CN: true, zh_TW: true }) {
|
|
|
window._i18n_locale.date_formats['short'] = { "pattern": "yy/MM/dd", "format": "%(yy)s/%(MM)s/%(dd)s" };
|
|
|
window._i18n_locale.date_formats['medium'] = {"pattern": "yyyy/MM/dd", "format": "%(yyyy)s/%(MM)s/%(dd)s"};
|
|
|
window._i18n_locale.time_formats['short'] = { "pattern": "H:mm", "format": "%(H)s:%(mm)s" };
|
|
|
window._i18n_locale.time_formats['medium'] = { "pattern": "H:mm:ss", "format": "%(H)s:%(mm)s:%(ss)s" };
|
|
|
}
|
|
|
|
|
|
var asianLanguageDateReplacements = [
|
|
|
[/^ccc MMM d$/, 'M/d (ccc)'],
|
|
|
[/^YYYY-MM/, 'YYYY/MM'],
|
|
|
[/MM-dd/, 'MM/dd']
|
|
|
];
|
|
|
|
|
|
// maintain a hash of locales where custom string replacements are needed to get correct translation
|
|
|
var FORMAT_DATE_REPLACEMENTS = {
|
|
|
'ja_JP': asianLanguageDateReplacements,
|
|
|
'ko_KR': asianLanguageDateReplacements,
|
|
|
'zh_CN': asianLanguageDateReplacements,
|
|
|
'zh_TW': asianLanguageDateReplacements
|
|
|
};
|
|
|
|
|
|
// maintain a list of replacements needed when a locale specifies that day comes before month
|
|
|
var DAY_FIRST_FORMATS = [
|
|
|
['MMM d', 'd MMM']
|
|
|
];
|
|
|
|
|
|
var oldFormatDate = window.format_date;
|
|
|
window.format_date = function(date, format) {
|
|
|
format = format || 'medium';
|
|
|
var i, replacements,
|
|
|
locale = window.locale_name();
|
|
|
|
|
|
if(format && (typeof format === 'string') && window.locale_uses_day_before_month()) {
|
|
|
replacements = DAY_FIRST_FORMATS;
|
|
|
for(i = 0; i < replacements.length; i++) {
|
|
|
format = format.replace(replacements[i][0], replacements[i][1]);
|
|
|
}
|
|
|
}
|
|
|
if(format && (typeof format === 'string') && locale in FORMAT_DATE_REPLACEMENTS) {
|
|
|
replacements = FORMAT_DATE_REPLACEMENTS[locale];
|
|
|
|
|
|
for(i = 0; i < replacements.length; i++) {
|
|
|
var replacement = replacements[i];
|
|
|
format = format.replace(replacement[0], replacement[1]);
|
|
|
}
|
|
|
}
|
|
|
return oldFormatDate(date, format);
|
|
|
};
|
|
|
|
|
|
return ({
|
|
|
_: window.gettext,
|
|
|
ungettext: window.ungettext,
|
|
|
format_decimal: window.format_decimal,
|
|
|
format_number: window.format_number,
|
|
|
format_percent: window.format_percent,
|
|
|
format_scientific: window.format_scientific,
|
|
|
format_date: window.format_date,
|
|
|
format_datetime: window.format_datetime,
|
|
|
format_time: window.format_time,
|
|
|
format_datetime_microseconds: window.format_datetime_microseconds,
|
|
|
format_time_microseconds: window.format_time_microseconds,
|
|
|
locale_uses_day_before_month: window.locale_uses_day_before_month,
|
|
|
format_datetime_range: window.format_datetime_range,
|
|
|
locale_name: window.locale_name,
|
|
|
DateTime: window.DateTime,
|
|
|
moment_install: window.moment_install,
|
|
|
numeral_install: window.numeral_install,
|
|
|
jQuery_ui_datepicker_install: window.jQuery_ui_datepicker_install
|
|
|
});
|
|
|
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
/* 15 */
|
|
|
/***/ (function(module, exports) {
|
|
|
|
|
|
module.exports = __WEBPACK_EXTERNAL_MODULE_15__;
|
|
|
|
|
|
/***/ }),
|
|
|
/* 16 */
|
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
|
|
var __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, module) {
|
|
|
var $ = __webpack_require__(1);
|
|
|
var _ = __webpack_require__(2);
|
|
|
var Backbone = __webpack_require__(7);
|
|
|
var mvc = __webpack_require__(8);
|
|
|
var TokenUtils = __webpack_require__(12);
|
|
|
var splunkUtil = __webpack_require__(9);
|
|
|
|
|
|
|
|
|
var EntityContextPanelModel = Backbone.Model.extend({
|
|
|
defaults: function () {
|
|
|
return {
|
|
|
header_id: '',
|
|
|
requested_entity_tokens: [],
|
|
|
entities: [],
|
|
|
current_entity: {},
|
|
|
current_entity_info: {},
|
|
|
|
|
|
// Used to render context link at top of panel
|
|
|
context_content_title: '',
|
|
|
context_content_value: '',
|
|
|
context_content_link: '',
|
|
|
|
|
|
// itsi context
|
|
|
service_id: '',
|
|
|
kpi_id: '',
|
|
|
service_title: '',
|
|
|
service_url: '',
|
|
|
service_detail_url: '',
|
|
|
kpi_title: '',
|
|
|
kpi_url: '',
|
|
|
entity_search_filter: ''
|
|
|
};
|
|
|
},
|
|
|
|
|
|
serviceCollection: null,
|
|
|
|
|
|
/**
|
|
|
* Initializes the model
|
|
|
*
|
|
|
* @param {Object} attributes
|
|
|
* @param {Object} options
|
|
|
* @public
|
|
|
*/
|
|
|
initialize: function (attributes, options) {
|
|
|
Backbone.Model.prototype.initialize.apply(this, arguments);
|
|
|
|
|
|
var tokens = mvc.Components.getInstance('default');
|
|
|
var tokensToSubmit = {};
|
|
|
var context = {};
|
|
|
|
|
|
var contextFields = ['service_id', 'service_title', 'kpi_id', 'kpi_title'];
|
|
|
var fromPayload = function(payload) {
|
|
|
return {
|
|
|
fields: _.pick(payload.context, contextFields),
|
|
|
entities: payload.entities
|
|
|
};
|
|
|
};
|
|
|
|
|
|
if (_.isEmpty(tokens.get('drilldown_payload'))) {
|
|
|
context = {
|
|
|
fields: tokens.pick(contextFields),
|
|
|
entities: _.reduce(tokens.attributes, function (memo, value, key) {
|
|
|
var parts = key.split('.');
|
|
|
if (parts[0] === 'entity') {
|
|
|
memo[0] = memo[0] || {};
|
|
|
memo[0][parts[1]] = value;
|
|
|
}
|
|
|
return memo;
|
|
|
}, [])
|
|
|
};
|
|
|
|
|
|
if (_.isEmpty(tokens.get('earliest')) || _.isEmpty(tokens.get('latest'))) {
|
|
|
tokensToSubmit.earliest = '-24h';
|
|
|
tokensToSubmit.latest = 'now';
|
|
|
} else {
|
|
|
tokensToSubmit.earliest = tokens.get('earliest');
|
|
|
tokensToSubmit.latest = tokens.get('latest');
|
|
|
}
|
|
|
} else {
|
|
|
var payload = JSON.parse(tokens.get('drilldown_payload'));
|
|
|
context = fromPayload(payload);
|
|
|
if (_.isEmpty(tokens.get('earliest')) || _.isEmpty(tokens.get('latest'))) {
|
|
|
if (payload.context.earliest !== undefined && payload.context.latest !== undefined){
|
|
|
tokensToSubmit.earliest = payload.context.earliest;
|
|
|
tokensToSubmit.latest = payload.context.latest;
|
|
|
} else {
|
|
|
tokensToSubmit.earliest = '-24h';
|
|
|
tokensToSubmit.latest = 'now';
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.set(_.extend(context.fields, {
|
|
|
'current_entity': _.first(context.entities)
|
|
|
}));
|
|
|
|
|
|
// Set tokens for the first entity on the page
|
|
|
_.each(this.get('current_entity'), function (value, token) {
|
|
|
tokensToSubmit[token] = value;
|
|
|
}, this);
|
|
|
|
|
|
TokenUtils.submitTokens(tokensToSubmit);
|
|
|
|
|
|
var currentPage = _.last(window.location.pathname.split('/'));
|
|
|
var appName = currentPage.substring(0, _.lastIndexOf(currentPage, '-'));
|
|
|
var configUrl = 'configs/conf-itsi_module_viz/' + currentPage;
|
|
|
|
|
|
var initViewWithConfig = _.bind(function(response) {
|
|
|
var config = this._parseResponse(response);
|
|
|
this.set('entity_search_filter', config.entity_search_filter);
|
|
|
this.set('requested_entity_tokens', config.requested_entity_tokens);
|
|
|
}, this);
|
|
|
|
|
|
var configFetch = this._getConfig(configUrl, appName).then(initViewWithConfig);
|
|
|
|
|
|
if (_.isEmpty(this.get('entities'))) {
|
|
|
if (!_.isEmpty(this.get('current_entity'))) {
|
|
|
var entityUrl = Splunk.util.make_url([
|
|
|
'splunkd',
|
|
|
'__raw',
|
|
|
'servicesNS',
|
|
|
'nobody',
|
|
|
'SA-ITOA',
|
|
|
'itoa_interface',
|
|
|
'entity',
|
|
|
this.get('current_entity').id
|
|
|
].join('/'));
|
|
|
|
|
|
var entityFetch = _.bind(function () {
|
|
|
return $.ajax({
|
|
|
url: entityUrl,
|
|
|
type: 'GET',
|
|
|
dataType: 'json',
|
|
|
async: true
|
|
|
})
|
|
|
}, this);
|
|
|
configFetch.then(entityFetch).done(_.bind(this._setEntities, this));
|
|
|
} else {
|
|
|
var baseUrl = Splunk.util.make_url([
|
|
|
'splunkd',
|
|
|
'__raw',
|
|
|
'servicesNS',
|
|
|
'nobody',
|
|
|
'SA-ITOA',
|
|
|
'itoa_interface',
|
|
|
'get_entity_filter',
|
|
|
].join('/'));
|
|
|
var entityFilterFetch = _.bind(function () {
|
|
|
return $.ajax({
|
|
|
url: baseUrl,
|
|
|
type: 'GET',
|
|
|
dataType: 'json',
|
|
|
async: true,
|
|
|
data: {entity_filter: JSON.stringify(this.get('entity_search_filter'))}
|
|
|
})
|
|
|
}, this);
|
|
|
configFetch.then(entityFilterFetch).done(_.bind(this._setEntities, this));
|
|
|
}
|
|
|
} else {
|
|
|
this.set('current_entity', _.first(this.get('entities')));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Fetches service data from ITSI
|
|
|
*
|
|
|
* @param {Object} options
|
|
|
* @returns {XHR} the fetch XHR
|
|
|
* @public
|
|
|
*/
|
|
|
fetch: function (options) {
|
|
|
var promise = $.Deferred();
|
|
|
|
|
|
if (this._isFetchPossible()) {
|
|
|
if (this._isFetchRequired()) {
|
|
|
this._fetchServiceCollection().done(function(services) {
|
|
|
// Success
|
|
|
if (services.length < 1) {
|
|
|
promise.reject(null, _('Service not found').t(), options);
|
|
|
}
|
|
|
|
|
|
var serviceModel = services[0];
|
|
|
var kpiModel = _.first(_.where(serviceModel.kpis, {_key: this.get('kpi_id')}));
|
|
|
if (_.isUndefined(kpiModel)) {
|
|
|
promise.reject(null, _('KPI not found').t(), options);
|
|
|
}
|
|
|
|
|
|
this.set('service_title', serviceModel.title);
|
|
|
this.set('kpi_title', kpiModel.title);
|
|
|
}).fail(function() {
|
|
|
// Failure
|
|
|
promise.reject(null, _('Error fetching ITSI services').t(), options);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
this.set('service_url', this._getServiceUrl(this.get('service_id')));
|
|
|
this.set('kpi_url', this._getKpisUrl(this.get('service_id')));
|
|
|
this.set('service_detail_url', this._getServiceDetailUrl(this.get('service_id')));
|
|
|
|
|
|
this.set('context_content_title', _('Service').t());
|
|
|
this.set('context_content_value', this.get('service_title'));
|
|
|
this.set('context_content_link', this.get('service_detail_url'));
|
|
|
}
|
|
|
promise.resolve(this, _('Success').t(), options);
|
|
|
return promise;
|
|
|
},
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Converts entity REST call results into a dictionary
|
|
|
* and set entities fields based on where it's navigating from
|
|
|
*
|
|
|
* @private
|
|
|
*/
|
|
|
_setEntities: function(results) {
|
|
|
if (!_.isArray(results)) {
|
|
|
results = [results];
|
|
|
}
|
|
|
var currentEntity = this.get('current_entity');
|
|
|
if (!_.isEmpty(currentEntity)){
|
|
|
// filter entities by key and title if navigating from entity details
|
|
|
this.set('entities',
|
|
|
_.filter(results, function(result){
|
|
|
return result._key === currentEntity.id &&
|
|
|
result.title === currentEntity.title;
|
|
|
}, this));
|
|
|
}
|
|
|
else {
|
|
|
// filter entities by service id if navigating from deep dive
|
|
|
var filteredEntities = _.filter(results, function(result){
|
|
|
return _.any(result.services, function(service) {
|
|
|
return service._key === this.get('service_id');
|
|
|
},this);
|
|
|
}, this);
|
|
|
|
|
|
var currentEntityIncluded = _.any(filteredEntities,function(entity){
|
|
|
return entity._key === this.get('current_entity').id;
|
|
|
},this);
|
|
|
|
|
|
// merge filteredEntities with current_entity if current_entity
|
|
|
// is not included
|
|
|
if (!currentEntityIncluded)
|
|
|
filteredEntities.push(this.get('current_entity'));
|
|
|
|
|
|
// sort entities by title
|
|
|
filteredEntities.sort(this._sortByAlphabet('title'));
|
|
|
this.set('entities', filteredEntities);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Converts entity info search results into a dictionary
|
|
|
*
|
|
|
*/
|
|
|
setEntityInfo: function(data) {
|
|
|
if (data){
|
|
|
this.set('current_entity_info',
|
|
|
this._rowToDictionary(
|
|
|
_.first(data.rows),
|
|
|
data.fields));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Convert a Splunk search result row into a dictionary
|
|
|
*
|
|
|
* @param {Array} row
|
|
|
* @param {Array} fields
|
|
|
* @returns {Object}
|
|
|
* @private
|
|
|
*/
|
|
|
_rowToDictionary: function(row, fields) {
|
|
|
var rowItem = {};
|
|
|
_.each(row, function(item, index) {
|
|
|
rowItem[fields[index]] = item;
|
|
|
});
|
|
|
|
|
|
return rowItem;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Sort the array of objects by property name in an alphabetical manner
|
|
|
* @param {propName} property of object by which we want to sort alphabetically
|
|
|
*/
|
|
|
_sortByAlphabet: function (propName) {
|
|
|
return function (obj1, obj2) {
|
|
|
return obj1[propName].localeCompare(obj2[propName]);
|
|
|
};
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Validates the model
|
|
|
*
|
|
|
* @param {Object} attributes
|
|
|
* @param {Object} options
|
|
|
* @returns {Boolean}
|
|
|
* @public
|
|
|
*/
|
|
|
validate: function (attributes, options) {
|
|
|
return EntityContextPanelModel.prototype.validate.apply(this, arguments);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Checks internal fields to determine if a fetch is required
|
|
|
*
|
|
|
* @returns {Boolean}
|
|
|
*/
|
|
|
_isFetchRequired: function () {
|
|
|
if (!_.isEmpty(this.get('current_entity'))){
|
|
|
return false;
|
|
|
} else{
|
|
|
return (_.isEmpty(this.get('service_title')) ||
|
|
|
_.isEmpty(this.get('kpi_title')));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Checks internal fields to determine if a fetch is possible
|
|
|
*
|
|
|
* @returns {Boolean}
|
|
|
*/
|
|
|
_isFetchPossible: function () {
|
|
|
if (!_.isEmpty(this.get('current_entity'))){
|
|
|
return true;
|
|
|
} else{
|
|
|
return !(_.isEmpty(this.get('service_id')) ||
|
|
|
_.isEmpty(this.get('kpi_id')));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Invokes the ITSI service REST endpoint for the service collection
|
|
|
*
|
|
|
* @returns {Deferred}
|
|
|
*/
|
|
|
_fetchServiceCollection: function () {
|
|
|
this.serviceCollection = new ServiceModelCollectionStub();
|
|
|
this.serviceCollection.setServiceFilter(this.get('service_id'));
|
|
|
return this.serviceCollection.getTitlesAndKeysWithKpis();
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Gets the url for service detail page given a service id.
|
|
|
*
|
|
|
**/
|
|
|
_getServiceDetailUrl: function (serviceId) {
|
|
|
return splunkUtil.make_url([
|
|
|
'app',
|
|
|
'itsi',
|
|
|
'service_detail?serviceId=' + serviceId
|
|
|
].join('/'));
|
|
|
},
|
|
|
|
|
|
// Taken from ServiceModel in ITSI
|
|
|
_getServiceUrl: function (serviceId) {
|
|
|
return splunkUtil.make_url([
|
|
|
'app',
|
|
|
'itsi',
|
|
|
'service_definition?serviceId=' + serviceId + '&tab=info'
|
|
|
].join('/'));
|
|
|
},
|
|
|
|
|
|
// Taken from ServiceModel in ITSI
|
|
|
_getKpisUrl: function (serviceId) {
|
|
|
return this._getServiceUrl(serviceId) + '§ion=kpis';
|
|
|
},
|
|
|
|
|
|
_getConfig: function (configUrl, appName) {
|
|
|
var splunkService = mvc.createService();
|
|
|
splunkService.app = appName;
|
|
|
splunkService.owner = 'nobody';
|
|
|
return splunkService.get(configUrl);
|
|
|
},
|
|
|
|
|
|
_parseResponse: function (response) {
|
|
|
var content = {};
|
|
|
var config = {};
|
|
|
|
|
|
try {
|
|
|
content = _.first(JSON.parse(response).entry).content;
|
|
|
config = {
|
|
|
entity_search_filter: JSON.parse(content.entity_search_filter),
|
|
|
requested_entity_tokens: _.map(
|
|
|
content.requested_entity_tokens.split(','),
|
|
|
function(val) {return val.trim()}
|
|
|
)
|
|
|
};
|
|
|
}
|
|
|
catch(e) {
|
|
|
console.error('Failed to parse the response');
|
|
|
throw e;
|
|
|
}
|
|
|
return config;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return EntityContextPanelModel;
|
|
|
}.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
|
|
|
|
|
|
|
/***/ })
|
|
|
/******/ ])});; |