' : '
', {
target: '_blank',
'class': 'chartWrapperTitleInner'
}));
// set the title above the chart
self.updateChartTitle
?self.updateChartTitle(titleDivInner)
:titleDivInner.text(driverName);
prompt.div.hide();
mainDiv.show();
$.each(outputListeners, function(i, listener) {
listener(driverId);
});
}
/**
* @param {Array of ForecastModel} models
* @return {int}
*/
function getIndexOfUserModel(models) {
var result = -1;
$.each(models, function(i, model) {
if(modelManager.get('active') == model)
result = i;
});
return result;
}
/**
* Creates the addition CSS classes for the toggles. Returns a two-element array where the first element is the class for the first
* toggle period and the second one is the class for the second period.
*/
function createAdditionalToggleClasses(togglePeriods, baseModelChartData) {
return $.map(togglePeriods, function(periodType) {
var chartDataFilteredByPeriodType = pickPeriod(baseModelChartData, periodType);
if(_.find(chartDataFilteredByPeriodType.array, function(point){ return !point.Fixed && point.AffectsAnyOutputs !== undefined && !point.AffectsAnyOutputs; })) {
if(tf.showRedOnInspector()) {
return 'red';
} else {
return '';
}
} else {
return '';
};
});
}
/**
* @param {String} driverId
* @return {boolean} whether the user model has modified values for this driver
*/
function getIsUserLineModified(driverId) {
var usersModel = modelManager.get('active');
// if this is an output instead of a driver, we want to exit
if (!usersModel.companyData.getChartData(driverId)){
return false;
}
if(usersModel.isModifiedDriver(driverId))
return true;
// calculate if any of the driver points have different values in the user's model compared to the base model
// iterate through all the modified drivers, and for each point check against the base model for any difference
var anyDriverPointsDifferentThanBaseModel = false;
$.each (usersModel.companyData.getChartData(driverId).array, function( index, driverPoint ) {
// fb3111: the values from the user model may be undefined if they're constants and just aren't recalculated
// by the model, so don't consider undefined values as differences
if (usersModel.cs.values[driverPoint.Identifier] !== undefined && usersModel.cs.values[driverPoint.Identifier] != driverPoint.Value) {
if (!MathUtil.equalsWithinTolerance(usersModel.cs.values[driverPoint.Identifier], driverPoint.Value, 0.01)){
anyDriverPointsDifferentThanBaseModel = true;
return false; // this is how we 'break' with $.each
}
}
});
if(anyDriverPointsDifferentThanBaseModel)
return true;
return false;
}
/**
* @return {Function} a function that can be called to reset the data onto the DraggableChart. The function should be
* called with a single argument: the current period type (an instance of the tf.period.type enum).
* The returned function effectively caches the state of the arguments like chartData, terminalGrowthDaat, etc., so they
* can be reset on the charts with a different period type.
*/
function createSetDataForPeriodType(chartData, terminalGrowthData, ids, labels, colors, bubbleCallOuts, editableIndex, isUserLineModified,
driverName, showingTerminalGrowth, companyData, driverId, isIntegerValuesOnly, gridViewMode, eventData) {
return function(periodType) {
selectedPeriodType = periodType;
var periodTypeFilteredChartData = $.map(chartData, function(datum){return pickPeriod(datum, periodType)});
mainChart.setData(
periodTypeFilteredChartData,
ids,
labels,
colors,
bubbleCallOuts,
editableIndex,
isUserLineModified,
driverName,
(showingTerminalGrowth || companyData.isDriverNonNegative(driverId)) ? 0 : companyData.getDriverMinAllowedValue(driverId),
companyData.getDriverMaxAllowedValue(driverId),
isIntegerValuesOnly,
gridViewMode,
companyData.md.yAxisLabels ? companyData.md.yAxisLabels[driverId] : null,
eventData
);
// in case the user model has any values, immediately update them on the chart. This way,
// if the user modifies a quarterly line then switches the period type to annuals,
// the annual data will reflect the new values that the user modified
mainChart.updateData(modelManager.get('active').cs.values);
tgrDiv.toggle(showingTerminalGrowth);
if(showingTerminalGrowth) {
tgrChart.setData(
terminalGrowthData,
ids,
[], // the terminal growth chart never has labels
[], // or colors
[], // or bubble callouts
editableIndex,
"Terminal Growth Rate",
true,
mainChart.getScaledValue(0) * 0.9,
false);
tgrChart.updateData(modelManager.get('active').cs.values);
}
// now that the charts have rendered, we need to re-position the orange prompt
//
positionPrompt();
}
}
/**
* @param {Object} chartDatum
* @param {int} periodType
* @return {Object} a copy of the given chartDatum, but with the data points filtered
* so only the periods that match the given type will be included
*/
function pickPeriod(chartDatum, periodType) {
var result = $.extend(true, {}, chartDatum);
result.array = $.grep(chartDatum.array, function(point, i) {
return tf.period.typeOf(point.Year) == periodType;
});
return result;
}
/**
* Gives the abbreviation to be used for the given period type in the toggle.
* The string also includes a count of how many data points there are of that type.
*
* @param {Object} chartDatum default chart data
* @param {int} periodType
* @return {String}
*/
function abbreviateType(chartDatum, periodType) {
return tf.period.typeAbbrevs[periodType] + ' (' + tf.period.getCountMap(chartDatum.array)[periodType] + ')';
}
/**
* Helper function that creates a new instance of chart data with modified values
*
* @param {Object} trefisChartData the trefis chart data object from the company model data
* @param {String} newName the name/title of the resulting series
* @param {int}newType the type (see ChartType) of the resulting series
* @param {Object} valueProvider provides any values that differ from the Trefis chart line;
* must have the getValue function
*/
function copyChartData(trefisChartData, newName, newType, valueProvider) {
var result = $.extend(true, {}, trefisChartData, {title: newName, type: newType}),
value;
if(valueProvider) {
$.each(result.array, function(pointIndex, point) {
value = valueProvider.getValue(point.Identifier);
if(value !== undefined)
point.Value = value;
});
}
return result;
}
/**
* registers a new series with the chart. Useful for adding competition lines, etc.
* Should only be called after setData is called with the appropriate driver
*
* @param {String} id the unique id of this series; used for referencing the series in calls to e.g. setSeriesVisible
* @param {String} label the user-visible label which should be applied to this series
* @param {Object} chartDatum the actual data of the series
* @param {int} competitionIndexForColor the index of this series, if it's a competition series; used for determining
* the color of the line. If this isn't a competition or benchmark series, can be undefined.
* @return the color of the registered line
* @type String
*/
function registerSeries(id, label, chartDatum, competitionIndexForColor) {
if(chartDatum.hasTerminalGrowth) {
var extracted = extractTerminalGrowth(chartDatum);
terminalGrowthChart.registerSeries(id, null, extracted[1], competitionIndexForColor);
return mainChart.registerSeries(id, label, extracted[0], competitionIndexForColor);
} else {
return mainChart.registerSeries(id, label, chartDatum, competitionIndexForColor);
}
}
/**
* Sets a particular series to be visible/invisible. The series must have been added in setData
* or registered afterwards with registerSeries. Setting a series visible/invisible will also set
* its label visible/invisible.
*
* @param {String} id the unique id of the series
* @param {boolean} visible whether the series should be visible
*/
function setSeriesVisible(id, visible) {
mainChart.setSeriesVisible(id, visible);
if(showingTerminalGrowth)
terminalGrowthChart.setSeriesVisible(id, visible);
}
/** Check whether the series by id is visible. */
function isSeriesVisible(id) {
return mainChart.isSeriesVisible(id);
}
/**
* Helper function that splits chart data into data for the main chart and data for the TGR chart.
* Assumes the chart data actually has a TGR value in it.
*
* @param {Object} data chart data
* @return an array whose 0th element is the main chart data object, and whose 1st element is the
* TGR chart data objects
* @type Array
*/
function extractTerminalGrowth(data) {
// copy the data so we don't mess up the original
var discountRateData = $.merge(true, {}, data),
terminalGrowthData = $.merge(true, {}, data);
delete discountRateData.array[discountRateData.array.length - 1];
terminalGrowthData.array = [ terminalGrowthData.array[terminalGrowthData.array.length - 1] ];
return [discountRateData, terminalGrowthData];
}
/**
* @return a map from year identifiers to values, giving the values of the editable line. Can return undefined
* if there is no editable line. If there is an editable line, but it defaulted to the Trefis values and hasn't been
* changed yet, this method will return a map that includes the Trefis values.
* @type Object
*/
function getData() {
// because there are two charts, we need to combine the data
// from each. Start with the data from the mainChart, then
// add in the data from the terminal growth chart
return showingTerminalGrowth ? $.merge(mainChart.getData(), terminalGrowthChart.getData()) : mainChart.getData();
}
/**
* Resets the editable line back to the Trefis default
*/
var analystWarningDisplayReset = true
function reset() {
// set the data in the chart back to the Trefis default
mainChart.updateData(defaultData);
if(showingTerminalGrowth)
tgrChart.updateData(defaultData);
mainChart.stopDispatchTimer();
if(showingTerminalGrowth)
tgrChart.stopDispatchTimer();
// since the discount rate may have changed by resetting, we need to update the maximum on the TGR chart
if(showingTerminalGrowth)
tgrChart.setMaxAllowedValue(mainChart.getScaledValue(0) * 0.9);
// undo the editable series change
//
if (!gridViewMode)
mainChart.undoEditableSeriesTypeChange();
if(showingTerminalGrowth)
tgrChart.undoEditableSeriesTypeChange();
mainChart.setLabelVisible("trefis", false);
mainChart.setLabelVisible("yours", false);
// this signals to the gridview that this chart has been reset by the user,
// so the gridview will update colors
// gridview will reset this value
chartReseted = true;
// recompute
modelManager.get('active').doComputation(self.getData(), currentDivision, currentDriver, true, true);
modelManager.trigger('change:active');
}
/**
* Updates the heights and Y-coordinates of the charts and other divs. Should be called whenever
* the heights of the chart title or container div change.
*/
function updateHeights() {
var chartY = (gapAboveChart ? 20 : 0)+ (titleDiv.is(":visible") ? titleDiv.height() : 0),
totalHeight = containerDiv.height();
if(shareOnChart) {
$(shareManager.linkDiv)
.addClass("shareLinkOnChart")
.removeClass("shareLinkAboveChart")
.add(shareManager.dropdownDiv)
.css({
top: chartY - 12 + (shareLinkOffset || 0)
});
} else {
$(shareManager.linkDiv)
.addClass("shareLinkAboveChart")
.removeClass("shareLinkOnChart");
$(shareManager.dropdownDiv).css({top: 10});
}
$.each(chartPositionListeners, function(i, listener) {
listener(chartY - 12);
});
mainDiv
.add(tgrDiv)
.css({
width:'100%',
height: totalHeight - chartY,
top: chartY
});
if(mainChart) {
mainChart.reflow();
tgrChart.reflow();
mainChart.tooltipDiv.css({top: chartY - (noElevatedMouseover ? 0 : 10)});
}
if(chartY != lastChartY) {
lastChartY = chartY;
if(mainChart)
setTimeout(mainChart.updateLabelPositions, 20);
}
}
/**
* Positions the "drag the trendline below" prompt appropriately on the chart, and sets it
* to be visible/invisible appropriately. Should be called whenever the screen position
* of the editable trendline may have moved, or when the visibility of the prompt may need to be changed.
*/
function positionPrompt() {
prompt.div.toggle(!!promptEnabled);
if(promptEnabled) {
var numPoints = mainChart.getNumPoints();
if(promptOnChart && numPoints > 5) {
var leftPoint = mainChart.getGlobalPosition(Math.round(numPoints/2)),
rightPoint = mainChart.getGlobalPosition(Math.round(numPoints/4)),
useRight,
pointToUse;
if(leftPoint && rightPoint) {
useRight = leftPoint.y < rightPoint.y;
pointToUse = useRight ? rightPoint : leftPoint;
prompt.caratOnRight(useRight);
prompt.setText(promptOverrideText || "Drag or click the trendline");
prompt.div.css({
top: 'auto',
bottom: pointToUse.y + 50,
left: pointToUse.x + (useRight ? 38 - prompt.div.width() : -20)
});
}
} else {
prompt.setText(promptOverrideText || "Drag the trendline below");
prompt.div.css({
top: (titleDiv.is(":visible") ? titleDiv.height() : 0) + 4,
bottom: 'auto',
left: (containerDiv.width() - prompt.div.width()) / 2
});
}
prompt.div.addClass(promptClass);
}
// this trigger is used to create an event to be used by Jasmine
$( "html" ).trigger( "positionPromptExecuted" );
}
/**
* Adds a listener which is called with no arguments whenever the user grabs (i.e., mouses down on) the editable line
*
* @param {Function} listener
*/
function addMouseDownListener(listener) {
mouseDownListeners.push(listener);
}
/**
* Adds a listener which is called whenever the y positions of the chart or driver label change.
*
* @param {Function} listener called with one argument, which is the approximate y coordinate of the
* top of the gap between the chart and the driver label
*/
function addChartPositionListener(listener) {
chartPositionListeners.push(listener);
}
function addDriverListener(listener) {
driverListeners.push(listener);
}
function addOutputListener(listener) {
outputListeners.push(listener);
}
/**
* Sets whether the chart is in smoothing mode (line chart) or not (bar chart)
*
* @param {boolean} smoothing
*/
function setSmoothing(smoothing) {
mainDiv.hide();
mainChart.setSmoothing(smoothing);
mainDiv.show();
// TGR chart only has one element, so it doesn't care about smoothing
}
/**
* @return the CompanyData used to render the current chart
* @type CompanyData
*/
function getCompanyData() {
return currentCompanyData;
}
/**
* @return the driver which the chart currently shows. For ouptut charts this will
* be the referenced collection identifier and may not be a real driver
* @type String
*/
function getDriver() {
return currentDriver;
}
function getDivision() {
return currentDivision;
}
function setEditable(val) {
editable = val;
promptEnabled = editable;
positionPrompt();
}
function setPromptOnChart(val) {
promptOnChart = val;
positionPrompt();
}
function setShareOnChart(val) {
shareOnChart = val;
updateHeights();
}
function setGapAboveChart(val) {
gapAboveChart = val;
updateHeights();
}
function setSuppressTypeChange(newSuppressTypeChange) {
suppressTypeChange = newSuppressTypeChange;
}
function onPeriodTypeChange(selectedValue) {
userSelectedPeriodType = selectedValue;
setDataForPeriodType(selectedValue);
}
function setPeriodSelectionManager(newPeriodSelectionManager) {
mainChart.setPeriodSelectionManager(newPeriodSelectionManager);
newPeriodSelectionManager.addChangeListener(updateSelectedPeriod);
}
function updateSelectedPeriod() {
// ideally we could just update the orange/blue dots on the editable series of the chart,
// but highcharts seems to have a bug where the point markers don't update correctly. So
// instead, when the selected period changes, we re-render the whole chart, which is inefficient
// but it works. Note that the easiest way to re-render the chart is just to use the setDataForPeriodType
// function and pass it the same period type it's using currently
if(setDataForPeriodType && selectedPeriodType)
setDataForPeriodType(selectedPeriodType);
}
function updateDataForCurrentDriver() {
mainChart.updateData(modelManager.get('active').cs.values);
if(getIsUserLineModified(currentDriver))
mainChart.doEditableSeriesTypeChange();
}
function setChartReseted(inputChartReseted) {
chartReseted = inputChartReseted;
}
function getChartReseted() {
return chartReseted;
}
function updateNonEditableData(usersValueProvider, modelId) {
mainChart.updateNonEditableData(usersValueProvider, modelId);
}
function setModelsAndColors(models, colors) {
allModels = models;
allColors = colors;
}
function getCurrentPeriodType(){
if(outputPeriodType!=null)
return outputPeriodType;
var p = mainChart.getChartData()['base']['array'][0]['Year'];
return tf.period.typeOf(p)
}
/** Refactor chart data creation from SOLR style doc. */
function createChartData(doc) {
var currentPeriodType = this.getCurrentPeriodType();
var trefisChartData = $.parseJSON(doc.trefisData).chartData[0]; //trefisData is json stored as a string in solr, here we parse the strign back to json
trefisChartData = this.pickPeriod(trefisChartData, currentPeriodType);
trefisChartData.type=9; //set chart type as benchmark
trefisChartData.title = doc.title;
trefisChartData.modelName = doc.modelName;
if(doc.lead_symbol) {
trefisChartData.title += ' for ' + doc.lead_symbol;
}
if(doc.providerString) { // Capitalize provider
trefisChartData.title += '(' + doc.providerString.charAt(0).toUpperCase() + doc.providerString.slice(1) + ')';
}
return trefisChartData;
}
$.extend(self, {
mainChart: mainChart,
tgrChart: tgrChart,
setData: setData,
setOutputData: setOutputData,
getData: getData,
registerSeries: registerSeries,
setSeriesVisible: setSeriesVisible,
isSeriesVisible: isSeriesVisible,
copyChartData: copyChartData,
addMouseDownListener: addMouseDownListener,
addChartPositionListener: addChartPositionListener,
addDriverListener: addDriverListener,
addOutputListener: addOutputListener,
setSmoothing: setSmoothing,
reset: reset,
getCompanyData: getCompanyData,
getDriver: getDriver,
getDivision: getDivision,
setEditable:setEditable,
setPromptOnChart: setPromptOnChart,
setShareOnChart: setShareOnChart,
setGapAboveChart: setGapAboveChart,
setSuppressTypeChange: setSuppressTypeChange,
divId: containerDivId,
div: containerDiv,
mainDiv: mainDiv,
modelManager: modelManager,
updateHeights: updateHeights,
setPeriodSelectionManager: setPeriodSelectionManager,
updateDataForCurrentDriver: updateDataForCurrentDriver,
setChartReseted: setChartReseted,
getChartReseted: getChartReseted,
updateNonEditableData: updateNonEditableData,
setModelsAndColors: setModelsAndColors,
getCurrentPeriodType: getCurrentPeriodType,
pickPeriod: pickPeriod,
createChartData: createChartData
});
}
/**
* Constants indicating various types of chart lines.
*/
ChartType = {
yours: 0,
trefis: 1,
community: 2,
competition: 3,
otherUser: 6,
comparable: 7,
comparisonOtherUser: 8,
benchmark: 9
};
;;
// lib/jquery.min.js
/**
* Manages driver selection. Keeps track of a list of drivers, displays the left/right arrows,
* and fires off events when the driver is changed.
*
* @constructor
* @param {ChartWrapper} chartWrapper the chart in which the filmstrip should be embeded.
* The chart is only used for inserting a div; it isn't used for any control logic. Cannot be null.
* @param {WidgetTracker} tracker the filmstrip will track driver changes using the given tracker.
* Cannot be null.
*/
function DriverFilmstrip(chartWrapper, tracker, baseClassOverride) {
var self = this,
baseClass = baseClassOverride || "widgetFilmstrip",
div, // containing div
leftButton, // left arrow button
rightButton, // right arrow button
label, // "1 of 7" label span
driverList, // array of drivers (Strings)
currentPosition, // int; index into driverList array giving currently selected driver
listeners = [] // array of callback functions to be called when driver changes
;
/*
* Constructor
*/
$("#" + chartWrapper.divId)
.prepend(div = $("", {
"class": baseClass
})
.append(rightButton = $("", {
"class": baseClass + "-right",
text: "Next",
click: function() {
changeDriver(currentPosition + 1);
tracker.driverChanged(driverList[currentPosition]);
return false;
}
}))
.append(leftButton = $("
", {
"class": baseClass + "-left",
text: "Previous",
click: function() {
changeDriver(currentPosition - 1);
tracker.driverChanged(driverList[currentPosition]);
return false;
}
}))
.append(label = $("
")));
/*
* Methods
*/
/**
* Sets the driver list of the filmstrip.
*
* @param {Array} drivers array of identifiers (Strings) for the driver list
* @param {String} driver default driver to be selected; can be null, in which case the first driver
* from the resulting list is selected by default
*/
function setDrivers(drivers, driver) {
driverList = drivers;
div.toggle(drivers.length > 1);
changeDriver(driver || 0);
}
/**
* Changes the currently selected driver. Does not change the list of available drivers.
*
* @param {String or int} driver the driver to be selected, or the index into the current driver
* list that should be selected
*/
function changeDriver(driver) {
if("string" == typeof driver)
driver = $.inArray(driverList, driver);
if(driver >= 0 && driver < driverList.length) {
currentPosition = driver;
self.driver = driverList[currentPosition];
updateView();
$.each(listeners, function(i, listener) {
listener(driverList[currentPosition]);
});
}
}
/**
* updates the arrows, label, etc. according to the current state (currentPosition and driverList)
*/
function updateView() {
label.text((currentPosition + 1) + " of " + driverList.length);
leftButton.toggleClass(baseClass + "-left-disabled", !currentPosition);
rightButton.toggleClass(baseClass + "-right-disabled", currentPosition == driverList.length - 1);
}
/**
* Adds a listener for when the driver changes. Listeners will be called with one argument: the
* String identifier of the newly selected driver.
*/
function addListener(callback) {
listeners.push(callback);
}
$.extend(self, {
setDrivers: setDrivers,
changeDriver: changeDriver,
addListener: addListener,
getPosition: function () {return currentPosition},
getDrivers: function(){return driverList}
});
}
;;
// @include lib/underscore.js
// @include lib/json2.js
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){
// Initial Setup
// -------------
// Save a reference to the global object (`window` in the browser, `global`
// on the server).
var root = this;
// 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 slice/splice.
var slice = Array.prototype.slice;
var splice = Array.prototype.splice;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
var Backbone;
if (typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.2';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
var $ = root.jQuery || root.Zepto || root.ender;
// Set the JavaScript library that will be used for DOM manipulation and
// Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
// Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
// alternate JavaScript library (or a mock library for testing your views
// outside of a browser).
Backbone.setDomLibrary = function(lib) {
$ = lib;
};
// 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 `"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 ... 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
// -----------------
// Regular expression used to split event strings
var eventSplitter = /\s+/;
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback functions
// to an event; 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 = {
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
var calls, event, node, tail, list;
if (!callback) return this;
events = events.split(eventSplitter);
calls = this._callbacks || (this._callbacks = {});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while (event = events.shift()) {
list = calls[event];
node = list ? list.tail : {};
node.next = tail = {};
node.context = context;
node.callback = callback;
calls[event] = {tail: tail, next: list ? list.next : node};
}
return this;
},
// 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 `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
if (!(calls = this._callbacks)) return;
if (!(events || callback || context)) {
delete this._callbacks;
return this;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events = events ? events.split(eventSplitter) : _.keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) continue;
// Create a new list, omitting the indicated callbacks.
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// 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).
trigger: function(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
all = calls.all;
events = events.split(eventSplitter);
rest = slice.call(arguments, 1);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) {
var defaults;
attributes || (attributes = {});
if (options && options.parse) attributes = this.parse(attributes);
if (defaults = getValue(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
}
if (options && options.collection) this.collection = options.collection;
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
this.changed = {};
this._silent = {};
this._pending = {};
this.set(attributes, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);
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,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
// 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);
},
// Get the value of an attribute.
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
if (!this._validate(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
// Remove an attribute from the model, firing `"change"` unless you choose
// to silence it. `unset` is a noop if the attribute doesn't exist.
unset: function(attr, options) {
(options || (options = {})).unset = true;
return this.set(attr, null, options);
},
// Clear all attributes on the model, firing `"change"` unless you choose
// to silence it.
clear: function(options) {
(options || (options = {})).unset = true;
return this.set(_.clone(this.attributes), options);
},
// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overriden,
// triggering a `"change"` event.
fetch: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp);
};
options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, '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, value, options) {
var attrs, current;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
options = options ? _.clone(options) : {};
// If we're "wait"-ing to set changed attributes, validate early.
if (options.wait) {
if (!this._validate(attrs, options)) return false;
current = _.clone(this.attributes);
}
// Regular saves `set` attributes before persisting to the server.
var silentOptions = _.extend({}, options, {silent: true});
if (attrs && !this.set(attrs, options.wait ? silentOptions : 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;
options.success = function(resp, status, xhr) {
var serverAttrs = model.parse(resp, xhr);
if (options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
if (!model.set(serverAttrs, options)) return false;
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
if (options.wait) this.set(current, silentOptions);
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 triggerDestroy = function() {
model.trigger('destroy', model, model.collection, options);
};
if (this.isNew()) {
triggerDestroy();
return false;
}
options.success = function(resp) {
if (options.wait) triggerDestroy();
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
options.error = Backbone.wrapError(options.error, model, options);
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
if (!options.wait) triggerDestroy();
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 = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.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, xhr) {
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.id == null;
},
// Call this method to manually fire a `"change"` event for this model and
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
options || (options = {});
var changing = this._changing;
this._changing = true;
// Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true;
// Silent changes are triggered.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options);
}
if (changing) return this;
// Continue firing `"change"` events while there are pending changes.
while (!_.isEmpty(this._pending)) {
this._pending = {};
this.trigger('change', this, options);
// Pending and silent changes still remain.
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._changing = false;
return this;
},
// 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 (!arguments.length) 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 val, changed = false, old = this._previousAttributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
},
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous: function(attr) {
if (!arguments.length || !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);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid: function() {
return !this.validate(this.attributes);
},
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
if (options && options.error) {
options.error(this, error, options);
} else {
this.trigger('error', this, error, options);
}
return false;
}
});
// Backbone.Collection
// -------------------
// Provides a standard collection class for our sets of models, ordered
// or unordered. 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 = {});
if (options.model) this.model = options.model;
if (options.comparator) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
};
// 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,
// 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); });
},
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added.
for (i = 0, length = models.length; i < length; i++) {
if (!(model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
cids[cid] = ids[id] = model;
}
// Remove duplicates.
i = dups.length;
while (i--) {
models.splice(dups[i], 1);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
// Insert models into the collection, re-sorting if needed, and triggering
// `add` events unless silenced.
this.length += length;
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
if (this.comparator) this.sort({silent: true});
if (options.silent) return this;
for (i = 0, length = this.models.length; i < length; i++) {
if (!cids[(model = this.models[i]).cid]) continue;
options.index = i;
model.trigger('add', model, this, options);
}
return this;
},
// Remove a model, or a list of models from the set. Pass silent to avoid
// firing the `remove` event for every model removed.
remove: function(models, options) {
var i, l, index, model;
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
for (i = 0, l = models.length; i < l; i++) {
model = this.getByCid(models[i]) || this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byCid[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
this._removeReference(model);
}
return this;
},
// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, options);
return model;
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));
return model;
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
// Get a model from the set by client id.
getByCid: function(cid) {
return cid && this._byCid[cid.cid || cid];
},
// Get the model at the given index.
at: function(index) {
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
where: function(attrs) {
if (_.isEmpty(attrs)) return [];
return this.filter(function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return 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) {
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
var boundComparator = _.bind(this.comparator, this);
if (this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
this.models.sort(boundComparator);
}
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return _.map(this.models, function(model){ return model.get(attr); });
},
// 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 `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) {
models || (models = []);
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
this._reset();
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of resetting.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
options.error = Backbone.wrapError(options.error, collection, options);
return (this.sync || Backbone.sync).call(this, '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) {
var coll = this;
options = options ? _.clone(options) : {};
model = this._prepareModel(model, options);
if (!model) return false;
if (!options.wait) coll.add(model, options);
var success = options.success;
options.success = function(nextModel, resp, xhr) {
if (options.wait) coll.add(nextModel, options);
if (success) {
success(nextModel, resp);
} else {
nextModel.trigger('sync', model, resp, options);
}
};
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, xhr) {
return resp;
},
// Proxy to _'s chain. Can't be proxied the same way the rest of the
// underscore methods are proxied because it relies on the underscore
// constructor.
chain: function () {
return _(this.models).chain();
},
// Reset all internal state. Called when the collection is reset.
_reset: function(options) {
this.length = 0;
this.models = [];
this._byId = {};
this._byCid = {};
},
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel: function(model, options) {
options || (options = {});
if (!(model instanceof Model)) {
var attrs = model;
options.collection = this;
model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) model = false;
} else if (!model.collection) {
model.collection = this;
}
return model;
},
// Internal method to remove a model's ties to a collection.
_removeReference: function(model) {
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 ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
this._byId[model.id] = model;
}
this.trigger.apply(this, arguments);
}
});
// Underscore methods that we want to implement on the Collection.
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(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 = {});
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 namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
// 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) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
},
// 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;
var routes = [];
for (var route in this.routes) {
routes.unshift([route, this.routes[route]]);
}
for (var i = 0, l = routes.length; i < l; i++) {
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters: function(route, fragment) {
return route.exec(fragment).slice(1);
}
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
};
// Cached regex for cleaning leading hashes and slashes .
var routeStripper = /^[#\/]/;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
// 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,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(windowOverride) {
var loc = windowOverride ? windowOverride.location : window.location;
var match = loc.href.match(/#(.*)$/);
return match ? match[1] : '';
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || forcePushState) {
fragment = window.location.pathname;
var search = window.location.search;
if (search) fragment += search;
} else {
fragment = this.getHash();
}
}
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
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._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('
').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
$(window).bind('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
$(window).bind('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
// 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._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
window.location.replace(this.options.root + '#' + this.fragment);
// 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._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
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() {
$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
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 (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(this.getHash());
},
// 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(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
return matched;
},
// 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};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag) return;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
this.fragment = frag;
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// 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) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
window.location.assign(this.options.root + fragment);
}
if (options.trigger) 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) {
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
} else {
location.hash = fragment;
}
}
});
// Backbone.View
// -------------
// 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._configure(options || {});
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
// 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 prefered to global lookups where possible.
$: function(selector) {
return this.$el.find(selector);
},
// 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 from the DOM. Note that the view isn't present in the
// DOM by default, so calling this method may be a no-op.
remove: function() {
this.$el.remove();
return this;
},
// For small amounts of DOM Elements, where a full-blown template isn't
// needed, use **make** to manufacture elements, one at a time.
//
// var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
//
make: function(tagName, attributes, content) {
var el = document.createElement(tagName);
if (attributes) $(el).attr(attributes);
if (content) $(el).html(content);
return el;
},
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
},
// 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`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) {
if (!(events || (events = getValue(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
if (selector === '') {
this.$el.bind(eventName, method);
} else {
this.$el.delegate(selector, eventName, method);
}
}
},
// Clears all callbacks previously bound to the view with `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() {
this.$el.unbind('.delegateEvents' + this.cid);
},
// Performs the initial configuration of a View with a set of options.
// Keys with special meaning *(model, collection, id, className)*, are
// attached directly to the view.
_configure: function(options) {
if (this.options) options = _.extend({}, this.options, options);
for (var i = 0, l = viewOptions.length; i < l; i++) {
var attr = viewOptions[i];
if (options[attr]) this[attr] = options[attr];
}
this.options = options;
},
// 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 = getValue(this, 'attributes') || {};
if (this.id) attrs.id = this.id;
if (this.className) attrs['class'] = this.className;
this.setElement(this.make(this.tagName, attrs), false);
} else {
this.setElement(this.el, false);
}
}
});
// The self-propagating extend function that Backbone classes use.
var extend = function (protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
// Set up inheritance for the model, collection, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync
// -------------
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read': 'GET'
};
// 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.
options || (options = {});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
if (!options.url) {
params.url = getValue(model, 'url') || urlError();
}
// Ensure that we have the appropriate request data.
if (!options.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (Backbone.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 (Backbone.emulateHTTP) {
if (type === 'PUT' || type === 'DELETE') {
if (Backbone.emulateJSON) params.data._method = type;
params.type = 'POST';
params.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !Backbone.emulateJSON) {
params.processData = false;
}
// Make the request, allowing the user to override any Ajax options.
return $.ajax(_.extend(params, options));
};
// Wrap an optional error callback with a fallback error event.
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// 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 inherits = function(parent, protoProps, staticProps) {
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's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ parent.apply(this, arguments); };
}
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// 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');
};
}).call(this);
;;
;(function (global, factory) {
if (typeof define === "function" && define.amd) {
define('Reselect', ['exports'], factory);
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.Reselect = mod.exports;
}
})(this, function (exports) {
'use strict';
exports.__esModule = true;
exports.defaultMemoize = defaultMemoize;
exports.createSelectorCreator = createSelectorCreator;
exports.createStructuredSelector = createStructuredSelector;
function defaultEqualityCheck(a, b) {
return a === b;
}
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false;
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
var length = prev.length;
for (var i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false;
}
}
return true;
}
function defaultMemoize(func) {
var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;
var lastArgs = null;
var lastResult = null;
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments);
}
lastArgs = arguments;
return lastResult;
};
}
function getDependencies(funcs) {
var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;
if (!dependencies.every(function (dep) {
return typeof dep === 'function';
})) {
var dependencyTypes = dependencies.map(function (dep) {
return typeof dep;
}).join(', ');
throw new Error('Selector creators expect all input-selectors to be functions, ' + ('instead received the following types: [' + dependencyTypes + ']'));
}
return dependencies;
}
function createSelectorCreator(memoize) {
for (var _len = arguments.length, memoizeOptions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
memoizeOptions[_key - 1] = arguments[_key];
}
return function () {
for (var _len2 = arguments.length, funcs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
funcs[_key2] = arguments[_key2];
}
var recomputations = 0;
var resultFunc = funcs.pop();
var dependencies = getDependencies(funcs);
var memoizedResultFunc = memoize.apply(undefined, [function () {
recomputations++;
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments);
}].concat(memoizeOptions));
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
var selector = defaultMemoize(function () {
var params = [];
var length = dependencies.length;
for (var i = 0; i < length; i++) {
// apply arguments instead of spreading and mutate a local list of params for performance.
params.push(dependencies[i].apply(null, arguments));
}
// apply arguments instead of spreading for performance.
return memoizedResultFunc.apply(null, params);
});
selector.resultFunc = resultFunc;
selector.recomputations = function () {
return recomputations;
};
selector.resetRecomputations = function () {
return recomputations = 0;
};
return selector;
};
}
var createSelector = exports.createSelector = createSelectorCreator(defaultMemoize);
function createStructuredSelector(selectors) {
var selectorCreator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : createSelector;
if (typeof selectors !== 'object') {
throw new Error('createStructuredSelector expects first argument to be an object ' + ('where each property is a selector, instead received a ' + typeof selectors));
}
var objectKeys = Object.keys(selectors);
return selectorCreator(objectKeys.map(function (key) {
return selectors[key];
}), function () {
for (var _len3 = arguments.length, values = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
values[_key3] = arguments[_key3];
}
return values.reduce(function (composition, value, index) {
composition[objectKeys[index]] = value;
return composition;
}, {});
});
}
});
;;
FpoDataUtils = {
"sheetIndexNumber": 0,
"code": "5.DhJAv",
"modelId": 1296,
"type": "ROW",
"tags": {
"SECONDARY": true
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A5.0BpE6",
"Year": "-1781726935|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, LINE), SECONDARY, DECIMAL_POINTS(1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B5.3MeWU",
"Year": "-1781726935|1|5||B",
"Column": "B",
"Value": "Value per Share (2022)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "C5.To8Jm",
"Year": "-1781726935|2|5||C",
"Column": "C",
"Value": "$",
"Hidden": true,
"SortOrder": "C",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D5.Lwikp",
"Year": "-1781726935|3|1|2014",
"Column": "D",
"Value": 82.08452226758979,
"Hidden": false,
"SortOrder": "D",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E5.zGDzo",
"Year": "-1781726935|4|1|2015",
"Column": "E",
"Value": 77.00794428658524,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "F5.VBp7f",
"Year": "-1781726935|5|1|2016",
"Column": "F",
"Value": 90.12224471197842,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "G5.VdMh1",
"Year": "-1781726935|6|1|2017",
"Column": "G",
"Value": 95.67522035065414,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "H5.PkYOL",
"Year": "-1781726935|7|1|2018",
"Column": "H",
"Value": 111.82866519759699,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I5.byd4M",
"Year": "-1781726935|8|1|2019",
"Column": "I",
"Value": 131.4,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J5.LqG66",
"Year": "-1781726935|9|1|2020",
"Column": "J",
"Value": 150.36093794765316,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K5.6AvlD",
"Year": "-1781726935|10|1|2021",
"Column": "K",
"Value": 172.8350877835848,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L5.mUCJI",
"Year": "-1781726935|11|1|2022",
"Column": "L",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M5.OZm6J",
"Year": "-1781726935|12|1|2023",
"Column": "M",
"Value": 220.32873057257208,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N5.JaHZ9",
"Year": "-1781726935|13|1|2024",
"Column": "N",
"Value": 249.94919088283112,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O5.RiDye",
"Year": "-1781726935|14|5|2022-06-30",
"Column": "O",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"components": [
{
"isNegative": false,
"code": "4.hm8cp",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": 0
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A4.moKGF",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 1",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B4.vyjMR",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution,1)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C4.Ve4rR",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 50,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D4.GVbFf",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 50,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E4.A7z8S",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 50,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|4.hm8cp",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 1",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|4.hm8cp",
"rowNumber": "4"
},
{
"isNegative": false,
"code": "5.zhWCY",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": -1
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A5.yU4iS",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 2",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B5.ZvhNA",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C5.JlJKh",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 50,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D5.FwuOS",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 50,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E5.P4VKt",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 50,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|5.zhWCY",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 2",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|5.zhWCY",
"rowNumber": "5"
},
{
"isNegative": false,
"code": "6.VHkVZ",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": 1
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A6.mecrY",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 3",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B6.MW4Hq",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution,2),MIN(25),MAX(35)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C6.diFV9",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 25,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D6.a0DOR",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 35,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E6.uJ7Wz",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 45,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|6.VHkVZ",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 3",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|6.VHkVZ",
"rowNumber": "6"
},
{
"isNegative": false,
"code": "7.BsI50",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": 3
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A7.aaxLH",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 4",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B7.8lF9o",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution,4),MIN(10),MAX(50)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C7.wVbyZ",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 35,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D7.4Bhfg",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 45,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E7.vU4xQ",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 55,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|7.BsI50",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 4",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|7.BsI50",
"rowNumber": "7"
},
{
"isNegative": false,
"code": "8.ILczK",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": -1
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A8.QIt1i",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 5 min/max limits",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B8.2Nafq",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "MIN(15), MAX(85), DRIVER_GROUP(a),SECTION(distribution)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C8.IiwMp",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 47,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D8.WvKG8",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 50,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E8.xBPg2",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 65,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|8.ILczK",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 5 min/max limits",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|8.ILczK",
"rowNumber": "8"
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowType": "OTHER",
"unit": "$",
"name": "",
"bullwhipCharts": [
{
"lo": {
"sheetIndexNumber": 1,
"code": "310.H1J8o",
"modelId": 1296,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A310.rfXry",
"Year": "3211434|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, LOW)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "E310.4H6nl",
"Year": "3211434|4|5||FYE Date",
"Column": "E",
"Value": "Low",
"Hidden": true,
"SortOrder": "E",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I310.wSQfb",
"Year": "3211434|8|1|2010",
"Column": "I",
"Value": 22,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J310.qjEQ6",
"Year": "3211434|9|1|2011",
"Column": "J",
"Value": 22.73,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "K310.inreO",
"Year": "3211434|10|1|2012",
"Column": "K",
"Value": 23.79,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "L310.M55Em",
"Year": "3211434|11|1|2013",
"Column": "L",
"Value": 26.26,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "M310.Gz56i",
"Year": "3211434|12|1|2014",
"Column": "M",
"Value": 30.84,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "N310.Xp4SF",
"Year": "3211434|13|1|2015",
"Column": "N",
"Value": 40.12,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "O310.NFFRI",
"Year": "3211434|14|1|2016",
"Column": "O",
"Value": 39.72,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "P310.OxdJr",
"Year": "3211434|15|1|2017",
"Column": "P",
"Value": 50.39,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "Q310.n2Uc2",
"Year": "3211434|16|1|2018",
"Column": "Q",
"Value": 68.02,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "R310.s1lMa",
"Year": "3211434|17|1|2019",
"Column": "R",
"Value": 93.96,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "S310.QGAj4",
"Year": "3211434|18|1|2020",
"Column": "S",
"Value": "--",
"Hidden": true,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|310.H1J8o",
"rowType": "OTHER",
"unit": "",
"name": "Low",
"sheet": "iTSR",
"id": "PO62pShaGaJk6cSovNiqwd9L|310.H1J8o",
"rowNumber": "310"
},
"line": {
"sheetIndexNumber": 0,
"code": "5.DhJAv",
"modelId": 1296,
"type": "ROW",
"tags": {
"SECONDARY": true
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A5.0BpE6",
"Year": "-1781726935|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, LINE), SECONDARY, DECIMAL_POINTS(1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B5.3MeWU",
"Year": "-1781726935|1|5||B",
"Column": "B",
"Value": "Value per Share (2022)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "C5.To8Jm",
"Year": "-1781726935|2|5||C",
"Column": "C",
"Value": "$",
"Hidden": true,
"SortOrder": "C",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D5.Lwikp",
"Year": "-1781726935|3|1|2014",
"Column": "D",
"Value": 82.08452226758979,
"Hidden": false,
"SortOrder": "D",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E5.zGDzo",
"Year": "-1781726935|4|1|2015",
"Column": "E",
"Value": 77.00794428658524,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "F5.VBp7f",
"Year": "-1781726935|5|1|2016",
"Column": "F",
"Value": 90.12224471197842,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "G5.VdMh1",
"Year": "-1781726935|6|1|2017",
"Column": "G",
"Value": 95.67522035065414,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "H5.PkYOL",
"Year": "-1781726935|7|1|2018",
"Column": "H",
"Value": 111.82866519759699,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I5.byd4M",
"Year": "-1781726935|8|1|2019",
"Column": "I",
"Value": 131.4,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J5.LqG66",
"Year": "-1781726935|9|1|2020",
"Column": "J",
"Value": 150.36093794765316,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K5.6AvlD",
"Year": "-1781726935|10|1|2021",
"Column": "K",
"Value": 172.8350877835848,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L5.mUCJI",
"Year": "-1781726935|11|1|2022",
"Column": "L",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M5.OZm6J",
"Year": "-1781726935|12|1|2023",
"Column": "M",
"Value": 220.32873057257208,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N5.JaHZ9",
"Year": "-1781726935|13|1|2024",
"Column": "N",
"Value": 249.94919088283112,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O5.RiDye",
"Year": "-1781726935|14|5|2022-06-30",
"Column": "O",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowType": "OTHER",
"unit": "$",
"name": "Value per Share (2022)",
"bullwhipCharts": [
{
"lo": "310.H1J8o",
"line": "5.DhJAv",
"range": "309.EI2VL",
"yearEnd": "311.R7G9f"
}
],
"sheet": "Trefis",
"id": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowNumber": "5"
},
"range": {
"sheetIndexNumber": 1,
"components": [
{
"isNegative": true,
"code": "310.H1J8o"
},
{
"isNegative": false,
"code": "441.jMH2C"
}
],
"code": "309.EI2VL",
"modelId": 1296,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A309.Q5iWH",
"Year": "3211434|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, RANGE)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "E309.Gi8G6",
"Year": "3211434|4|5||FYE Date",
"Column": "E",
"Value": "Historical Trading Range",
"Hidden": true,
"SortOrder": "E",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I309.QNf8j",
"Year": "3211434|8|1|2010",
"Column": "I",
"Value": 9.579999999999998,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J309.SdgGc",
"Year": "3211434|9|1|2011",
"Column": "J",
"Value": 6.73,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "K309.KCx6p",
"Year": "3211434|10|1|2012",
"Column": "K",
"Value": 9.160000000000004,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "L309.i4dYK",
"Year": "3211434|11|1|2013",
"Column": "L",
"Value": 9.52,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "M309.L6m6K",
"Year": "3211434|12|1|2014",
"Column": "M",
"Value": 11.45,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "N309.ypW2c",
"Year": "3211434|13|1|2015",
"Column": "N",
"Value": 9.925000000000004,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "O309.xhK9j",
"Year": "3211434|14|1|2016",
"Column": "O",
"Value": 17.130000000000003,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "P309.qQTs1",
"Year": "3211434|15|1|2017",
"Column": "P",
"Value": 22.5,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "Q309.kNiWv",
"Year": "3211434|16|1|2018",
"Column": "Q",
"Value": 34.67,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "R309.2ZMsK",
"Year": "3211434|17|1|2019",
"Column": "R",
"Value": 38.290000000000006,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": false,
"Fixed": true
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|309.EI2VL",
"rowType": "OTHER",
"unit": "",
"name": "Historical Trading Range",
"sheet": "iTSR",
"id": "PO62pShaGaJk6cSovNiqwd9L|309.EI2VL",
"rowNumber": "309"
},
"yearEnd": {
"sheetIndexNumber": 1,
"code": "311.R7G9f",
"modelId": 1296,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A311.y8lRV",
"Year": "3211434|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, YEAREND)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "E311.t7aIW",
"Year": "3211434|4|5||FYE Date",
"Column": "E",
"Value": "Fiscal Year Close Price",
"Hidden": true,
"SortOrder": "E",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I311.Mx5q3",
"Year": "3211434|8|1|2010",
"Column": "I",
"Value": 23.01,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J311.ThEcC",
"Year": "3211434|9|1|2011",
"Column": "J",
"Value": 26,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "K311.GXBtv",
"Year": "3211434|10|1|2012",
"Column": "K",
"Value": 30.59,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "L311.CPXSN",
"Year": "3211434|11|1|2013",
"Column": "L",
"Value": 34.545,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "M311.iYZuA",
"Year": "3211434|12|1|2014",
"Column": "M",
"Value": 41.7,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "N311.p45O7",
"Year": "3211434|13|1|2015",
"Column": "N",
"Value": 44.15,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "O311.qqYKa",
"Year": "3211434|14|1|2016",
"Column": "O",
"Value": 51.17,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "P311.VtadZ",
"Year": "3211434|15|1|2017",
"Column": "P",
"Value": 68.93,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "Q311.G8q4Z",
"Year": "3211434|16|1|2018",
"Column": "Q",
"Value": 98.61,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "R311.TnYut",
"Year": "3211434|17|1|2019",
"Column": "R",
"Value": 131.4,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "S311.yDZQM",
"Year": "3211434|18|1|2020",
"Column": "S",
"Value": "--",
"Hidden": true,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|311.R7G9f",
"rowType": "OTHER",
"unit": "",
"name": "Fiscal Year Close Price",
"sheet": "iTSR",
"id": "PO62pShaGaJk6cSovNiqwd9L|311.R7G9f",
"rowNumber": "311"
}
}
],
"sheet": "Trefis",
"id": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowNumber": "5",
"waveCharts": [
[
[
{
"sheetIndexNumber": 0,
"code": "10.9w9aX",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A10.enPDp",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,2)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B10.KLga3",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "23",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C10.m83H4",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.25,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D10.MkcEb",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E10.6yqxo",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.44761858348852,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F10.DaHfK",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.12510445167724,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G10.wSWXx",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.20606276326206,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H10.VT0Bn",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.26701414217157,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I10.C2hlw",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.31510152628277,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J10.EYy7n",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.4887497888584,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K10.Psmcx",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.01789887383127,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L10.wltS4",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 3.87839329817433,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M10.sueCc",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.10287407399784,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N10.QQ112",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 3.89643401165626,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O10.iGyw5",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.78390600798921,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P10.ftfCm",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.03264392797899,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q10.gfAMi",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 3.86467822104392,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R10.624Hm",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 3.94619846562543,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S10.GA8u7",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 3.65892111950984,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T10.hXEm4",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.51597859136862,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U10.b2wO1",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 3.53469750634482,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V10.GdWde",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.44799496331684,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W10.CXY1c",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.43400967753191,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X10.qFF9C",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.41634330293017,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y10.5Sb7Q",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 3.58646142467766,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z10.k5q34",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 3.70266356967258,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|10.9w9aX",
"rowType": "OTHER",
"unit": "",
"name": "23",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|10.9w9aX",
"rowNumber": "10"
},
{
"sheetIndexNumber": 0,
"code": "15.TmHcR",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A15.ZQZif",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,2)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B15.yTfiB",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "50% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C15.Y8Mhn",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.75,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D15.akpSc",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E15.gWJwE",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.38542958074896,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F15.C07jL",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 2.0114682568435,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G15.JHrwD",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.98871682001293,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H15.8I2A4",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 4.08786666543376,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I15.3jcjo",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 4.17225232246308,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J15.6fl0D",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.42546578467505,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K15.KraDH",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.9504652937651,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L15.1PGPw",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.73152148798984,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M15.NRa8I",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.90462136713127,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N15.aKJSc",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.79272663949795,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O15.RVtOT",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.68816791291389,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P15.0pw8v",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.8554583614677,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q15.qAC2P",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.74424118551776,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R15.hU8Q8",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.85866777651238,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S15.JNxcp",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.60001630096601,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T15.VG1Bj",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.53634037055152,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U15.UQXS3",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.60911975794198,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V15.KB876",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.48887166165155,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W15.6qwh0",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.62374400872323,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X15.hBkto",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 4.66887857544031,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y15.vPRFZ",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.8772084752401,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z15.ysbxe",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 5.057109281652,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|15.TmHcR",
"rowType": "OTHER",
"unit": "%",
"name": "50% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|15.TmHcR",
"rowNumber": "15"
}
],
[
{
"sheetIndexNumber": 0,
"code": "11.G4RGz",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A11.VCouV",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,3)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B11.R5o9v",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "25",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C11.1U6BC",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.35,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D11.zvF5x",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E11.QyyW6",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.65049934177424,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F11.zZiCV",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.34369270886142,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G11.VMBTT",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.39718622228391,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H11.3F0Wy",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.44964827107964,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I11.GLSd3",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.49655996357102,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J11.erkBn",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.6929458197106,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K11.GCi64",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.22873957636848,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L11.wBkE8",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.06688341580177,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M11.FLUss",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.27472794226095,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N11.uqYoh",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.10978805112646,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O11.uVHDN",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.97009322425243,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P11.K095v",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.21081641902462,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q11.k2Oc4",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.01741969717194,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R11.18iVu",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.17062144138834,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S11.jxnpa",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 3.85538002004597,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T11.BNRRc",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.7509323016142,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U11.vgWVp",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 3.74527631771809,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V11.i2ICK",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.6513675369645,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W11.Mzl06",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.64182439495674,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X11.pKJch",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.68167603541352,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y11.dwcLh",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 3.86026520660245,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z11.g2mLZ",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 3.93433272864492,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|11.G4RGz",
"rowType": "OTHER",
"unit": "",
"name": "25",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|11.G4RGz",
"rowNumber": "11"
},
{
"sheetIndexNumber": 0,
"code": "14.NE0PQ",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A14.Ycw6T",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,3)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B14.2mBRE",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "30% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C14.sjMY3",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.65,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D14.tNLLQ",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E14.Y1oMK",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.18475320699325,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F14.EnkGg",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.84632493878424,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G14.VlidV",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.83367289365126,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H14.hmbOd",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.91735375440642,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I14.B0qcY",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.97606059170247,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J14.YgVEq",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.26405931445818,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K14.JfNgT",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.77972642471915,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L14.sZoGo",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.55594477583989,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M14.NBAYQ",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.69756459206451,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N14.o8tcN",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.59117127731521,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O14.VF7CP",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.48972328410019,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P14.ONf5J",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.67157689228952,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q14.Notlp",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.52567295377768,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R14.iRxrE",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.67667074223868,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S14.vTdKz",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.41202785350189,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T14.lKMRH",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.29938497318696,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U14.d2bHL",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.41337729316858,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V14.e2jVN",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.26179300775642,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W14.EN0rF",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.33931014291839,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X14.lTYfj",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 4.38619163278406,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y14.OqdE5",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.60286250277338,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z14.ApYHU",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 4.69336505393097,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|14.NE0PQ",
"rowType": "OTHER",
"unit": "%",
"name": "30% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|14.NE0PQ",
"rowNumber": "14"
}
],
[
{
"sheetIndexNumber": 0,
"code": "13.NyAsa",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A13.zZPOl",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,4)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B13.2dlry",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "10% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C13.OGKhw",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.55,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D13.qaUX5",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E13.KqwWD",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.01914872987542,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F13.W7wyv",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.67490962335373,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G13.Z74Pa",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.69612416301491,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H13.IzkaM",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.76542533769233,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I13.KRXDO",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.83379937554498,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J13.XP03s",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.09204726052771,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K13.ZtSJu",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.60360267619505,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L13.B6ISE",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.3992453039724,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M13.eruea",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.55368823129437,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N13.rU7H1",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.42988753153423,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O13.vPgzE",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.32074387187421,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P13.jGDHg",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.53208591883676,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q13.7lqx2",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.32203078099272,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R13.C6xTh",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.50190404366357,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S13.GyaJF",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.24277029256447,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T13.SRbUu",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.14618118966009,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U13.7JaOi",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.21708437889921,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V13.aG2Za",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.06496320230981,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W13.BJFSi",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.1061153501189,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X13.cm0vo",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 4.12028680700074,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y13.lrEJG",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.33197478139822,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z13.0KVk2",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 4.49364656425163,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|13.NyAsa",
"rowType": "OTHER",
"unit": "%",
"name": "10% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|13.NyAsa",
"rowNumber": "13"
},
{
"sheetIndexNumber": 0,
"code": "12.67Qxh",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A12.1D1GZ",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,4)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B12.XPk0B",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "27",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C12.xkia4",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.45,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D12.WZ4iL",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E12.V5h6R",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.84969908875024,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F12.urAwk",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.5276162667769,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G12.MKpFX",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.5528644198955,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H12.WDtSb",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.6395157223555,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I12.Dhp65",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.68182882051294,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J12.WuTVJ",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.89974774395862,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K12.vYLuL",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.41868001666331,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L12.gUyEV",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.23512404902589,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M12.a5wXw",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.4246910590542,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N12.t9X10",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.28162268884231,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O12.jVVCI",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.17262140132989,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P12.938fp",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.33142485719705,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q12.CqQ0h",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.17137100621889,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R12.a8MLh",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.32362608065492,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S12.rqJLL",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.02929754012317,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T12.Rhbrt",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.96376197209218,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U12.jmrWS",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.01131770235969,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V12.7xkYd",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.86885399425363,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W12.DCyZj",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.87662751477671,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X12.QIucg",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.9441904196527,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y12.IUoyM",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.05593716412018,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z12.pHi6Z",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 4.18380330819419,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|12.67Qxh",
"rowType": "OTHER",
"unit": "",
"name": "27",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|12.67Qxh",
"rowNumber": "12"
}
],
[
{
"sheetIndexNumber": 0,
"code": "8.qUsJV",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A8.Po3ry",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,0)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B8.ZJUbs",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "18",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C8.GU1Vz",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.05,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D8.dI9rC",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E8.iPuLv",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 0.792290562781484,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F8.6NGdY",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 0.471460136184987,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G8.ACW8Y",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 1.58343739651616,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H8.17xMq",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 2.65668897981891,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I8.4Ym4d",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 2.74455438983238,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J8.ezIFp",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 2.84678451246267,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K8.fEcqN",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 3.3491330341183,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L8.s0cvu",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 3.18890740448467,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M8.SnsfJ",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 3.36144207766492,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N8.3Tdz1",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 3.20241690866086,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O8.LeANw",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.14825117348123,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P8.tQEiH",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 3.45808314614641,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q8.AaY5W",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 3.20289574083373,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R8.kavzB",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 3.23872220464816,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S8.u3n9t",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 2.94668105064933,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T8.h9n9x",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 2.86679503872486,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U8.Bluq0",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 2.81164406384391,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V8.FUYav",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 2.65090007188553,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W8.6LFy3",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 2.55918385006273,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X8.SbPv2",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 2.5278759666361,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y8.7agzy",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 2.62129780734608,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z8.kPqFm",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 2.65223600365525,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|8.qUsJV",
"rowType": "OTHER",
"unit": "",
"name": "18",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|8.qUsJV",
"rowNumber": "8"
},
{
"sheetIndexNumber": 0,
"code": "17.kjxWW",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A17.IYJib",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,0)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B17.X6U3I",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "90% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C17.8qgsv",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.95,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D17.HwxI8",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E17.jgn4d",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 3.02823612420651,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F17.VtQz3",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 2.74451739454087,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G17.sqLnQ",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 3.62847344136357,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H17.gpibI",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 4.69951180860983,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I17.rcx1y",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 4.84150270740232,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J17.XiyNm",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 5.00358698475334,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K17.NXSRE",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 5.51176668161677,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L17.cDhS3",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 5.37414568333028,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M17.OBBOs",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 5.55968598082442,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N17.Ce3X9",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 5.533467523549,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O17.NlBWF",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 5.32184661859603,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P17.glwic",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 5.45303953913678,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q17.5krZB",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 5.33651667593516,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R17.YUPtQ",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 5.57117683425008,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S17.lXcIi",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 5.3348503096546,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T17.oKxIP",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 5.27626912528891,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U17.Jjfdj",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 5.33652059543849,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V17.Nwdfh",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 5.33010881163937,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W17.GBxzR",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 5.42547320747939,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X17.SnyhP",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 5.64147382805981,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y17.jHeSm",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 5.81580392450568,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z17.Z1lbz",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 5.99261319211615,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|17.kjxWW",
"rowType": "OTHER",
"unit": "%",
"name": "90% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|17.kjxWW",
"rowNumber": "17"
}
],
[
{
"sheetIndexNumber": 0,
"code": "9.TSKQY",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A9.nfnBx",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B9.afCYZ",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "21",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C9.34kDa",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.15,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D9.GwitE",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E9.5NBJp",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.23574824023722,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F9.BAkSa",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 0.827463318918346,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G9.GpFeH",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 1.97847082556786,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H9.6LJqz",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.05895245686113,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I9.BdbWP",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.07885879648688,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J9.XoLf2",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.21887371716507,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K9.V5P4k",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 3.79007392020362,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L9.11J3k",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 3.64969644654652,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M9.942KA",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 3.85017892073477,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N9.EQDPq",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 3.63471687120925,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O9.83Rzn",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.61035185880034,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P9.8Jse6",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 3.83018983964336,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q9.QLeGr",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 3.58199798825209,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R9.UrR95",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 3.65775156764448,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S9.7xhSS",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 3.38081072107724,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T9.FlIDH",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.27793259509821,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U9.hD8Oo",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 3.26320466422842,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V9.VdtFC",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.19398699960968,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W9.vz08J",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.13003458407333,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X9.kGb2l",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.10275461544927,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y9.U8mER",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 3.17976599414498,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z9.VfGig",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 3.27453432479056,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|9.TSKQY",
"rowType": "OTHER",
"unit": "",
"name": "21",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|9.TSKQY",
"rowNumber": "9"
},
{
"sheetIndexNumber": 0,
"code": "16.AU4GM",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A16.x7Ftm",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B16.zDKlL",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "70% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C16.VqsGr",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.85,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D16.GzQeo",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E16.P6DiP",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.5902958762612,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F16.U0Rr9",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 2.27119147965474,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G16.2qs9y",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 3.24109602743678,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H16.McLTG",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 4.3133431608711,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I16.f6VC5",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 4.37734803021363,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J16.3IEb6",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.63063353556282,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K16.iKIks",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 5.21050473085229,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L16.EuXbu",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.97426427051326,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M16.MfUT8",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 5.13940275876477,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N16.8EjI3",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 5.05821236053366,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O16.3weST",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.93988336546871,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P16.sl7dp",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 5.06576652769494,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q16.vbuhB",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.98626986981291,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R16.oMKWs",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 5.13252016236625,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S16.Hm9uT",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.93065222023955,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T16.kFCEp",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.80044052543481,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U16.EqkVB",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.8712805447744,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V16.aF2dM",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.81098634272072,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W16.UdISJ",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.916883701702,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X16.GgyOZ",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 5.05757805893093,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y16.L0EQE",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 5.20282095287024,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z16.Voh9X",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 5.39532090229855,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|16.AU4GM",
"rowType": "OTHER",
"unit": "%",
"name": "70% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|16.AU4GM",
"rowNumber": "16"
}
]
]
]
}
;;
// @include presentations/utils/fpoDataUtils.js
var Service = {
saveState: function (state, modelId) {
var self = this;
var $dfd = $.Deferred();
if (window.properties.isTemplateAuthoring()) {
$dfd.resolve({ presentationId: -1, status: "success" })
} else if (state.get("persistedId") < 0){
$dfd.resolve({ presentationId: -1, status: "success" })
} else {
if(!modelId)modelId=properties.modelId();
var req = $.ajax({
data: {
state: JSON.stringify(state.toJSON()),
timestamp: new Date().getTime(),
modelId: modelId,
presentationId:state.get("persistedId")
},
url: getHost() + "/api/presentations/save",
type: "POST",
})
req.done(function (resp) {
// we are removing the translated content form the presentation when we save the the translated state for a unlink dashboard
if (window.translatedContent){
self.removeTranslatedState(state.get("persistedId"));
}
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
}
return $dfd.promise()
},
removeTranslatedState: function (presentationId) {
var req = $.ajax({
url: getHost() + "/api/presentations/deleteTranslatedContent?presentationId="+presentationId,
type: "GET",
})
req.done(function (resp) {
window.translatedContent = undefined;
}).fail(function (error) {
console.error(error);
});
},
unlinkDashboard: function(presentationId,copy){
var self = this;
var $dfd = $.Deferred();
// TODO: whatever the unlink service is - populate it here. SHould only need a presentation ID
var req = $.ajax({
method: 'POST',
data:{
timestamp: new Date().getTime(),
modelId: properties.modelId(),
presentationId:presentationId,
copy:copy,
},
url: getHost() + "/api/presentations/unlink",
dataType: "json"
});
req.done(function (resp) {
if(resp.isCopy){
// updating the presentation id
var newPresentationId = resp.newPresentationId;
var newModelId = resp.newModelId;
var newState = toPersistState(store.getState()).set("persistedId", newPresentationId);
self.saveState(newState, newModelId).done(function(){
window.location = resp.url;
})
} else {
// non eed to resolve if its isCopy - we're off to somewhere else
$dfd.resolve({});
}
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
//$dfd.resolve({persistedId:presentationId});
return $dfd.promise();
},
// we are storing the meta keywords, meta description, templateTopicTags and title outside of the
// state - so that we don't have to parse the state to go looking for the,
// TODO: we will also store the 'translation' request here as well at a later date
getMetaData: function(state){
return $.extend({},state.getIn(['meta']) ? state.getIn(['meta']).toJS():{},{
title:state.getIn(['title','text'])
})
},
getVerifyBlock: function(state){
return {
// heades up - this function available throgugh javascript funciton hoisting and not
// through explicitly ordering of scripts...
verifyBlock:JSON.stringify(getVerifiedDynamicContentsRequest(state))
}
},
getCacheBlock: function(state){
return {
// heades up - this function available throgugh javascript funciton hoisting and not
// through explicitly ordering of scripts...
cacheBlock:JSON.stringify(getDynamicContentsRequest(state))
}
},
saveTemplate: function (template, overWrite) {
var templateName;
var templateId;
var state = store.getState();
var metaData = this.getMetaData(state);
var verifyBlock = this.getVerifyBlock(state);
var cacheBlock = this.getCacheBlock(state);
if (overWrite) {
templateId = state.getIn(['ui', 'currentTemplateId']);
if (!!state.getIn(['ui', 'templates'])) {
state.getIn(['ui', 'templates']).map(function (template) {
templateName = template.get('id') === templateId ? template.get('name') : templateName;
});
}
}
var $dfd = $.Deferred();
var payload = $.extend(true,{},{
execution: properties.executionString(),
name: template.name,
template: JSON.stringify(template.json.toJS()),
id: !template.id ? '' : template.id,
isGlobal: template.isGlobal
},metaData,verifyBlock,cacheBlock); // adding our rmetaData and verifyBLock FET-153, 154, 155, 156..
console.log('p',payload);
var req = $.ajax({
method: 'POST',
data:payload,
url: properties.urlContext() + "api/presentations/template/saveas",
dataType: "json"
});
req.done(function (resp) {
$dfd.resolve({ id: resp.data, json: JSON.stringify(template.json.toJS()), isGlobal: template.isGlobal, name: template.name, isMine: template.isMine });
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise();
},
saveTemplateSlug: function (template, overWrite) {
var $dfd = $.Deferred();
var state = store.getState();
var templateId=state.getIn(['ui', 'currentTemplateId']);
var req = $.ajax({
method: 'POST',
data: {
id: templateId,
templateSlug:template.templateSlug,
},
url: properties.urlContext() + "api/presentations/template/saveSlug",
dataType: "json"
});
req.done(function (resp) {
$dfd.resolve(resp);
if(resp.status && resp.status=="error"){
alert(resp.message);
}
}).fail(function (a, b, c) {
$dfd.reject(a, b, c);
});
return $dfd.promise();
},
getTemplateState: function (id) {
var $dfd = $.Deferred();
var req = $.ajax({
method: 'GET',
url: properties.urlContext() + "api/presentations/template/state?id=" + id
});
req.done(function (resp) {
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise()
},
getTemplates: function () {
var $dfd = $.Deferred();
var req = $.ajax({
data: { execution: properties.executionString() },
url: properties.urlContext() + "api/presentations/template/list",
dataType: "json",
});
req.done(function (resp) {
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise()
},
deleteTemplate: function (templateId) {
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.urlContext() + 'api/presentations/template/delete?id=' + templateId,
method: 'GET',
});
req.done(function (resp) {
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise()
},
// When results come back as rows:{{code:data},...} we need to stitch the 'resources onto them'
// we have mergeResources, getResourceIds, etc
addResourceData: function (data) {
var _data = $.extend(true, {}, data);
var dataRows = _data.rows;
var self = this;
var updatedData = Object.keys(dataRows).reduce(function (acc, plotCode) {
var plotData = $.extend(true, {}, dataRows[plotCode]);
acc[plotCode] = self.mergeResources(plotData, dataRows)
return acc;
}, {});
_data.rows = updatedData;
return _data;
},
// modelId - take into account the model they are from. ie, only this model
getAllPlotCodes: function (skipResources,modelId) {
var self = this;
var codes = store.getState().getIn(['components']).reduce(function (acc, component, i) {
// first get all the chart codes
// if we pass in a a modelId, make sure the component is from the modelId
// important to check isFromCurrent model and NOT component.get('modelId') === modelId
// to be able to support reuploads, we don't store the modelId on components FROM the model
// only store them if they are FROM DIFFERENT models...
if (component.get('type') === 'chart' && (modelId ? PlotUtils.isFromCurrentModel(component) : true)) {
acc.push(component.get('code'))
// now, check if this chart, has components. If so, we might as well request that data now
if (!skipResources) {
// self.getResourceIds(component)
var plot = component.getIn(['ui', 'plots', 0, 'data']);
plot = plot ? plot.toJS() : {};
var ids = self.getPlotResourceIds(plot,modelId);
acc = acc.concat(ids)
}
}
return acc;
}, []);
// clean up results - no False/undefined and no duplicates
return codes.filter(Boolean).filter(function (code, i) {
return codes.indexOf(code) === i
});
},
easyAccessToken: function () {
return properties.modelContext().easyAccessToken ?
{ easyAccessToken: properties.modelContext().easyAccessToken } : {}
},
getAllTags: function () {
var $dfd = $.Deferred();
if (window.properties.isTemplateAuthoring()) $dfd.resolve({});
else {
var req = $.ajax({
data: {
model: properties.modelId(),
},
url: properties.urlContext() + "api/tags",
dataType: "json",
});
req.done(function (resp) {
if (resp.tags) {
$dfd.resolve(resp.tags);
} else {
$dfd.reject({});
}
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
}
return $dfd.promise()
},
getDummyPlot: function () {
return FpoDataUtils;
},
// expression === selector === ${TOTAL_REVENUE(-2)+TOTAL_REVENUE(-1)} etc
getExpressionPlot: function(component){
var $dfd = $.Deferred();
// no more double translating. Expressions are transalated once at the top of the page load
// if code make it to here, it doesn't need to be translated again.... and the component didn't get removed
// as part of the original translation (no data === no chart/component on FET)
// you cna largely do a find in the presentations directory for 'expressionPlot' to see how it ends up here
// used to pass the expression to here, but all has been updated to be the expression, and then just returned
// expression charts don't update with scenarios, and if thag becomes a requirement, this will all need to change
if (component.get('charts') && component.get('charts').size){
// THIS 'selector' is important, but likely shouldn't be. These operate in place of 'code' for model charts
// however, without it, scenarios vs series (in highcharts) get out of whack, and redraws of chart dupliacte series
// think we use this as the series ID maybe - but should probably just use the charts ID (as there could be tons of the same selector, but only 1 numeric id)
var chart = component.get('charts').first().set('selector', component.get('selector') || component.get('code'));
$dfd.resolve(chart.toJS());
} else {
var payload = {
model: properties.modelId(),
expression: component.get('expression'),
type: 'chart'
}
var urlParams = $.param($.extend(true,{
model:properties.modelId()
},this.easyAccessToken()))
// shouldn't ever hit this....
var req = $.ajax({
// data: $.extend({
// model: properties.modelId(),
// plot:code,
// addSumComponents:1
// }, this.easyAccessToken()),
type: "POST",
data: payload,
url: properties.urlContext() + "q/expression?"+urlParams,
dataType: "json",
});
req.done(function (resp) {
// if (resp.rows && resp.rows.length){
if (resp && PlotUtils.isSuccessfulPlotTranslation(resp)) {
var plot = JSON.parse((resp.result));
// HEADS UP: on update, we need to use this expression to find the correct component to update
// 1 expression could be used multiple times - our modelPlots - we get all the codes and remove duplicates
// if multiple components have the same code, then the data will be the same from the backend/
plot.selector = expression;
$dfd.resolve(plot);
} else {
$dfd.reject(resp)
}
}).fail(function (a, b, c) {
console.log('getPlot', a, b, c)
$dfd.reject(a, b, c)
});
}
// return !getResources ? $dfd.promise() : this.getPlotWithResources(code)
return $dfd.promise()
},
getPlotData: function(component){
//
return PlotUtils.isModelPlot(component) ?
this.getPlot(component.get('code'),PlotUtils.getComponentModelId(component),true) :
this.getExpressionPlot(component)
},
getPlot: function (code, modelId, getResources) {
var self = this;
if (getResources) {
return this.getPlotWithResources(code,modelId)
}
var $dfd = $.Deferred();
var req = $.ajax({
data: $.extend({
model: modelId,
plot: code,
addSumComponents: 1
}, this.easyAccessToken()),
url: properties.urlContext() + "api/plot/get",
dataType: "json",
});
req.done(function (resp) {
if (resp.rows && resp.rows.length) {
$dfd.resolve(resp.rows[0]);
} else {
//creating a dummy object for unavailable plots
var resp = { code: code, chartData: [{ array: [] }] };
$dfd.resolve(resp);
//console.log('\ngetPlot,',code,',req.done but something not right>>',resp)
//$dfd.reject({msg:code+'-no data found - solr should throw an error'})
}
}).fail(function (a, b, c) {
console.log('getPlot', a, b, c)
$dfd.reject(a, b, c)
});
// return !getResources ? $dfd.promise() : this.getPlotWithResources(code)
return $dfd.promise()
},
// just going to run a get request...
updateDifferentModelPlots: function(components){
// going to be similar to getPlots, except modelId's will ne different
// create an array of getPlot()
console.log('updateDifferentModelPlots',components)
if (!components || !components.length){
return $.Deferred().resolve({});
}
var self = this;
var deferredPromises = components.reduce(function(deferreds,component){
deferreds.push(self.getPlot(component.get('code'), PlotUtils.getComponentModelId(component), true))
return deferreds
},[])
return this.createPromiseAll(deferredPromises)
},
// only do these after an update... these will for now, be the same as their previous values
// however, an expression on the current model, *should* return different values if passed a different scenarioId
updateExpressionPlots: function(components){
if (!components || !components.length){
return $.Deferred().resolve({});
}
var self = this;
var deferredPromises = components.reduce(function(deferreds,component){
// TODO: we should make use of component.modelId, or a passed in one, in case a new scenarioId
// in this case, the one just created by an update, reutrns updated data...
// this may require tracking the modelId on an expression chart (may, or may not happen - they don't really work for me)
deferreds.push(self.getExpressionPlot(component))
return deferreds;
},[])
return this.createPromiseAll(deferredPromises)
},
getExpressionPlots: function(components){
// right now, these do the same - we have no 'scenario' on expression plots
return this.updateExpressionPlots(components)
},
getPlots: function (codes, modelId, scenarioId, getResources) {
var self = this;
var $dfd = $.Deferred();
var $dfds = codes.reduce(function (deferreds, code) {
deferreds.push(
!scenarioId ?
self.getPlot(code, modelId, getResources) :
self.getScenarioForPlot(code, scenarioId, getResources)
);
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// TODO: refactor - getPlot (getPlot/getPlotWithResources) should take a scenarioId, and use the getScenarioForPlot
getPlotWithResources: function (code, modelId, scenarioId) {
var self = this;
var $dfd = $.Deferred();
this.getPlot(code,modelId).done(function (plot) {
if (plot && (self.hasResources(plot))) {
self.getResources(plot, modelId, scenarioId, true).done(function () {
$dfd.resolve(plot)
}).fail(function (a, b, c) {
console.log('getResourcesFail', a, b, c)
})
} else if (plot) {
$dfd.resolve(plot)
}
});
return $dfd.promise();
},
hasResources: function (plot) {
return plot.components || plot.sumMaxRowIds || plot.sumMinRowIds || plot.bullwhipCharts || plot.waveCharts;
},
// this is the request created by the chartDrawer/default charts
getDefaultPlots: function () {
var $dfd = $.Deferred();
$.ajax(
{
data: {
model: properties.modelId()
},
url: properties.urlContext() + "api/plots/default",
dataType: "json",
}
).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
// dispatch(ActionCreators.searchError(true))
});
return $dfd.promise();
},
createScenario: function () {
var $dfd = $.Deferred();
$.ajax({
data: $.extend({
model: properties.modelId()
}, this.easyAccessToken()),
url: properties.urlContext() + "api/scenario/create",
dataType: "json"
}).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
$dfd.reject()
});
return $dfd.promise();
},
copyScenario: function (scenarioId) {
var $dfd = $.Deferred();
$.ajax({
data: {
model: properties.modelId(),
scenario: scenarioId
},
url: properties.urlContext() + "api/scenario/copy",
dataType: "json"
}).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
//console.log('Service.createScenario failed',a,b,c);
$dfd.reject()
});
return $dfd.promise();
},
deleteScenario: function (model) {
var $dfd = $.Deferred();
if (model.modelId) {
if (tf.editMode) {
gavpv('delete/Scenario/mode/edit', 'Delete scenario in edit mode');
} else {
gavpvl('delete/Scenario/mode/view', 'Delete scenario in view mode');
}
$.ajax({
data: {
scenario: model.modelId
},
url: properties.urlContext() + "api/scenario/delete",
dataType: "json"
}).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
$dfd.reject()
});
}
return $dfd.promise();
},
getScenarios: function (modelId) {
var $dfd = $.Deferred();
if (window.properties.isTemplateAuthoring()){
$dfd.resolve({});
}
else {
$.ajax({
data: {
model: properties.modelId()
},
url: properties.urlContext() + 'api/scenario/list',
dataType: 'json'
}).done(function (resp) {
$dfd.resolve(resp.scenarios);
}).fail(function (a, b, c) {
//console.log('Service.getScenarios failed',a,b,c);
});
}
return $dfd.promise()
},
updatePlot: function (model, toRecalc, isFinal, isNew, component) {
//console.log('service.updatePlot',plotCode)
// this one we can just sort by prroperties.modelId() - we can only update those this way
var self = this;
if (!isFinal || !model) return;
// this is the base modelId....
var plotCodes=this.getAllPlotCodes(false,properties.modelId());
var $dfd = $.Deferred();
var payload = {
recalc: toRecalc,
derivedModel: model.modelId,
plots: plotCodes
}
$.ajax({
type: "POST",
url: properties.urlContext() + "api/scenario/plot/update?new=" + isNew + "&easyAccessToken=" + properties.modelContext().easyAccessToken,
dataType: "json",
data: JSON.stringify(payload),
contentType: "application/json",
}).done(function (resultData) {
// TODO: use the mergeComponents...
resultData = self.addResourceData(resultData);
if (resultData && resultData.status == "success") {
$dfd.resolve(resultData)
// TODO: some more childScenarioStuff
// for (var childScenarioIndex = 0; childScenarioIndex < model.childScenarios.length; childScenarioIndex++) {
// var childScenario = model.childScenarios[childScenarioIndex];
// childScenario.updateWithParentDriverChanges(copyOfDirtyDrivers, model);
// }
} else if (resultData && resultData.status == "error") {
$dfd.reject();
}
}).fail(function (a, b, c) {
gaEvent(['modelwidget', 'cannotsave']);
$dfd.reject()
});
return $dfd.promise();
},
// TODO: going to need similar treatment to the update...
resetScenarioPlot: function (scenarioId, plotCode) {
var $dfd = $.Deferred(),
self = this;
var payload = {
code: plotCode,
derivedModel: scenarioId,
// modelID will filter out non modesl plots from request...
plots: this.getAllPlotCodes(false,properties.modelId())
}
$.ajax({
type: "POST",
data: JSON.stringify(payload),
url: properties.urlContext() + "api/scenario/plot/reset",
dataType: "json",
contentType: "application/json"
}).done(function (resp) {
resp = self.addResourceData(resp);
if (resp && resp.status == "success") {
$dfd.resolve(resp)
// TODO: some more childScenarioStuff
// for (var childScenarioIndex = 0; childScenarioIndex < model.childScenarios.length; childScenarioIndex++) {
// var childScenario = model.childScenarios[childScenarioIndex];
// childScenario.updateWithParentDriverChanges(copyOfDirtyDrivers, model);
// }
} else if (resp && resp.status == "error") {
$dfd.reject();
}
}).fail(function (a, b, c) {
$dfd.reject(arguments)
});
return $dfd.promise();
},
getScenarioForPlot: function (plotCode, scenarioId, getResources, newFlag) {
var $dfd = $.Deferred(),
self = this;
$.ajax({
data: {
scenario: scenarioId,
code: plotCode,
new: newFlag,
easyAccessToken: properties.modelContext().easyAccessToken,
addSumComponents: 1
},
url: properties.urlContext() + "api/scenario/plot/get",
dataType: "json"
}).done(function (resp) {
var isSuccessful = resp.success && (resp.status === 'success') && resp.rows[plotCode],
plot;
if (isSuccessful) {
plot = resp.rows[plotCode];
}
if (getResources) {
if (self.hasResources(plot)) {
self.getScenarioForPlotsResources(plot, scenarioId, newFlag).done(function (resp) {
$dfd.resolve(self.mergeResources(plot, resp));
})
} else {
$dfd.resolve(plot);
}
// go get components...
} else {
$dfd.resolve(plot);
}
}).fail(function (a, b, c) {
//$dfd.reject(arguments)
var plot = {
code: plotCode,
chartData: [{ array: [] }]
}
$dfd.resolve(plot);
});
return $dfd.promise();
},
// make sure to account for modleId if passed in
getPlotResourceIds: function (plot,modelId) {
var _plot = [plot];
var a = _plot.reduce(function (acc, plot) {
var componentIds = [];
// modelId ? plot.modelId === modelId : true
// only consider if its passed in///
if (modelId ? plot.modelId === modelId : true){
if (plot.components && plot.components.length) {
var components = plot.components.map(function (component) {
return component.code
})
componentIds.push(components);
}
if (plot.waveCharts && plot.waveCharts.length) {
var waveIds = plot.waveCharts.flat();
componentIds.push(waveIds.flat())
}
if (plot.bullwhipCharts && plot.bullwhipCharts.length) {
var bullwhipCharts = plot.bullwhipCharts.reduce(function (acc, bullwhipMap) {
var components = Object.keys(bullwhipMap);
components.forEach(function (component) {
// we do replacement of the code wth the actual plot for plotting
// so on page load, it will be {lo:'code'} and on update it will be {lo:{code:'code',chartData:[]}}
acc.push(typeof bullwhipMap[component] === 'string' ? bullwhipMap[component] : bullwhipMap[component].code)
});
return acc;
}, [])
componentIds.push(bullwhipCharts)
}
if (plot.sumMaxRowIds && plot.sumMaxRowIds.length) {
var sumMax = plot.sumMaxRowIds.map(function (code) {
return code
})
componentIds.push(sumMax);
}
if (plot.sumMinRowIds && plot.sumMinRowIds.length) {
var sumMin = plot.sumMinRowIds.map(function (code) {
return code
})
componentIds.push(sumMin);
}
}
return acc.concat.apply([], componentIds);
}, []);
return a;
},
// resources are components - but also the remote min/max...
getScenarioForPlotsResources: function (plot, scenario, newFlag) {
var self = this;
var $dfd = $.Deferred();
var componentIds = this.getPlotResourceIds(plot);
var $dfds = componentIds.reduce(function (deferreds, code) {
deferreds.push(self.getScenarioForPlot(code, scenario, false, newFlag));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// plot is the base - its 'components' starts out as an array of codes
// we fetch the codes, their scenarios, etc, and then merge it
getResources: function (plot, modelId, scenarioId, mergeResources) {
var self = this;
var $dfd = $.Deferred();
var componentCodes = this.getPlotResourceIds(plot)
// prob need a better way of handling...
if (scenarioId && scenarioId !== 'undefined.trefis') {
self.getScenarioForPlotsResources(plot, scenarioId).done(function (scenarioComponentsData) {
if (mergeResources) {
$dfd.resolve(self.mergeResources(plot, scenarioComponentsData));
} else {
$dfd.resolve(scenarioComponentsData)
}
}).fail(function (a, b, c) {
console.log('getResources FAIL', a, b, c)
})
} else {
self.getPlots(componentCodes,modelId).done(function (componentsData) {
if (mergeResources) {
$dfd.resolve(self.mergeResources(plot, componentsData));
} else {
$dfd.resolve(componentsData);
}
}).fail(function (a, b, c) {
console.log('getResourcesFail', a, b, c)
})
}
return $dfd.promise();
},
mergeResources: function (plot, resources) {
// from our update - all the plot adata comes back as rows:{{code:plotData}} - but from a get resources for a plt, it comes back as array
var map = Array.isArray(resources) ? resources.reduce(function (obj, component) {
obj[component.code] = component;
return obj;
}, {}) : resources;
// now syn them up to 1 or n places
if (plot.components && plot.components.length) {
var _components = plot.components.map(function (comp) {
return $.extend(true, {}, comp, map[comp.code])
});
plot.components = _components;
}
if (plot.sumMaxRowIds && plot.sumMaxRowIds.length) {
plot.sumMaxComponents = plot.sumMaxRowIds.reduce(function (acc, code) {
acc[code] = $.extend(true, {}, map[code])
return acc;
}, {})
}
if (plot.sumMinRowIds && plot.sumMinRowIds.length) {
plot.sumMinComponents = plot.sumMinRowIds.reduce(function (acc, code) {
acc[code] = $.extend(true, {}, map[code])
return acc;
}, {})
}
if (plot.bullwhipCharts && plot.bullwhipCharts.length) {
plot.bullwhipCharts = plot.bullwhipCharts.reduce(function (acc, bullwhipMap) {
var components = Object.keys(bullwhipMap).reduce(function (acc, componentName) {
acc[componentName] = $.extend(true, {}, map[bullwhipMap[componentName]])
return acc;
}, {})
acc.push(components)
return acc;
}, [])
}
if (plot.waveCharts && plot.waveCharts.length) {
// need to add the actual chart data to the pairs - currently just codes
plot.waveCharts = plot.waveCharts.reduce(function (acc, waveChartPairs) {
var pairWithData = waveChartPairs.map(function (pair) {
return pair.map(function (code) {
return map[code];
})
});
acc.push(pairWithData);
return acc;
}, [])
}
return $.extend(true, {}, plot)
},
getScenariosForDifferentModelPlot: function(code,modelId, scenarioList){
var self = this;
var $dfd = $.Deferred();
var $dfds = scenarioList.reduce(function (deferreds, scenarioId) {
deferreds.push(self.getPlot(code, modelId,true));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
getScenariosForExpressionPlot: function(component, scenarioList,getResources){
var self = this;
var $dfd = $.Deferred();
// this.getExpressionPlots
//updateExpressionPlots: function(components){
// we don't have scenarios on expressions as of now. However, it may be likely that all the expressions in the
// dashboard are built from items in the model (row a + totalRevenue) - or whatever that will be actually updated
// on a new scenario - ie, cogs and
var $dfds = scenarioList.reduce(function (deferreds, scenarioId) {
deferreds.push(self.getExpressionPlot(component));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// Other modelPlots will use this method - their scenarioList will be used for length. Need to address
// scenario assignment
getScenariosForPlot: function (code, scenariosList, getResources) {
var self = this;
var $dfd = $.Deferred();
var $dfds = scenariosList.reduce(function (deferreds, scenarioId) {
deferreds.push(self.getScenarioForPlot(code, scenarioId, getResources));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// pass the original promise (deferred that we return, as well as the list of defferreds that are going to be resolved
/**
* returns a promsie ($dfd, the original deferred) comprised of the resolution of an array of promises/deferreds
* jquery equivalent of a Promise.all
* Promise.all is actually a promise that takes an array of promises as an input (an iterable). ... So you are passing all ten promises to Promise.all. Then, Promise.all itself as a promise will get resolved once all the ten promises get resolved or any of the ten promises get rejected with an error.
* @param $dfds the array of deferreds/promises that when all resolved, will resolve the original deferred
* @param $dfd the original deferred/promise to resolve - optional
* @returns {*}
*/
// https://stackoverflow.com/questions/5627284/pass-in-an-array-of-deferreds-to-when
// second top rated answer - need to make sure you get all the data back. resp would just be the first returned...
createPromiseAll: function($dfds,$dfd){
var _$dfd = $dfd ? $dfd : $.Deferred();
$.when.apply($, $dfds).then(function (resp) {
_$dfd.resolveWith(this, [Array.prototype.slice.call(arguments)]);
}, function (resp) {
// TODO: not sure what actually to throw/call here... what if 2 fail and one doesnt?
console.warn('fail',resp)
_$dfd.rejectWith(this, [Array.prototype.slice.call(arguments)]);
});
return _$dfd.promise()
},
// @deprecated?
getScenariosForPlots: function (plots, scenarios) {
var self = this;
var $dfd = $.Deferred();
var plotList = plots;
var $dfds = plotList.reduce(function (deferreds, plotCode) {
deferreds.push(self.getScenariosForPlot(plotCode, scenarios, false));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd);
},
// this code starts in modelManager.update()...
updateScenarioAttributes: function (scenario, attributes) {
var $dfd = $.Deferred(),
payload = $.extend({}, {
scenario: scenario.modelId,
}, attributes);
$.ajax({
type: "POST",
url: getHost() + "/api/scenario/update",
dataType: "json",
data: JSON.stringify(payload),
contentType: "application/json",
}).done(function (resultData) {
if (resultData && resultData.status == "success") {
// we currently change everything by reference...
// resultData = scenarioPropteries (but may be slighlty different, we just merge the attributes
$dfd.resolve(resultData);
} else if (resultData && resultData.status == "error") {
$dfd.reject({ success: false, resp: resultData })
}
}).fail(function () {
console.log('update failed', arguments)
$dfd.reject(arguments)
})
return $dfd.promise();
},
// heads up - jquery usese promises, but we need this to block the load (or figure out how to keep
// all state objects from throwing errors (think the tour reducer is deprecated)
// so note the async:false below...
fetchDynamicContent: function(payload){
// need modelId & optional easy access token
var urlParams = $.param($.extend(true,{
model:properties.modelId()
},this.easyAccessToken()))
var $dfd = $.Deferred();
// if translatedContent is present then we are not going to call the api
if (window.translatedContent) {
$dfd.resolve(window.translatedContent)
} else {
$.ajax({
type: "POST",
url: getHost() + '/api/template/content?' + urlParams,
async: false,
data: JSON.stringify(payload),
contentType: "application/json",
}).done(function(content){
$dfd.resolve(content)
}).fail(function () {
console.log('fail', arguments);
$dfd.reject(arguments)
})
}
return $dfd.promise();
},
storeTitle: function(presentationId, dashboardTitle) {
var $dfd = $.Deferred();
if (presentationId < 0){
$dfd.resolve({});
}
var req = $.ajax({
type: "POST",
url: properties.urlContext() + "api/presentations/saveTitle",
data: {
presentationId: presentationId,
title: dashboardTitle
}
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(a,b,c){
$dfd.reject(a,b,c);
});
return $dfd.promise()
},
toggleDashboardVisibility: function(apiUrl){
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: apiUrl,
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(a,b,c){
$dfd.reject(a,b,c);
});
return $dfd.promise()
},
hideAllCompanyDashboardsByTopic: function (ticker, topic) {
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: window.getHost() + "/api/data/hideAllTopicDash/" + ticker + (topic ? "?topic=" + topic : ""),
});
req.done(function (resultData) {
$dfd.resolve(resultData);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c);
});
return $dfd.promise()
},
hideAllCompanyDashboards: function (ticker) {
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: window.getHost() + "/api/data/hideAllDash/" + ticker,
});
req.done(function (resultData) {
$dfd.resolve(resultData);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c);
});
return $dfd.promise()
},
deleteGlobalFunction: function(apiUrl){
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: apiUrl,
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveGlobalFunction: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: window.getHost() + '/api/template/funcs/global/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveTopicTag: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: window.getHost() + '/topictags/api/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deleteTopicTag: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: window.getHost() + '/topictags/api/delete',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveTickerCompany: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: window.getHost() + '/dataworks/api/tickers/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveJob: function(params){
// no id, create...
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: window.getHost() + '/api/dataworks/scheduled/job/save',
contentType: 'application/json',
data: JSON.stringify(params),
});
// // for local testing...
// // get scheduledJobs, push _params to it..
// var _params = params.id ? params : $.extend({},params,{id:uuidv4()});
// tf.clientStore.set(_params.id,_params,{isSession:false})
// $dfd.resolve(_params);
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
savePeerGroupChanges: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: window.getHost() + '/dataworks/api/peerGroupChanges/save',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deleteJob: function(id){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: window.getHost() + '/api/dataworks/scheduled/job/delete',
contentType: 'application/json',
data: JSON.stringify({id:id}),
});
// local testing...
// tf.clientStore.set(id);
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deletePeerGroup: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: window.getHost() + '/dataworks/api/peerGroupChanges/delete',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
runJob: function(id){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: window.getHost() + '/api/dataworks/scheduled/job/run',
contentType: 'application/json',
data: JSON.stringify({id:id}),
});
req.done(function(resultData){
$dfd.resolve(resultData?JSON.parse(resultData):resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
// api url that the dataviewer payload would produce...
// on the create, maybe i want to load the setings, and save as someting new... gotta fetch the jobs
fetchScheduledJobs: function(){
},
// comes currently from tthe JSP - lets make an endpoint for it
fetchDataSources: function(){
},
// comes currently from the JSP - lets make an enpoint
fetchTickerLists: function(){
},
saveTickerList: function(params){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/api/namedtickers/save',
contentType: 'application/json',
data: JSON.stringify(params),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
// POST /api/namedtickers/save ==> save/update
},
deleteTickerList: function(id){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/api/namedtickers/delete',
contentType: 'application/json',
data: JSON.stringify({id:id}),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
// don't use this function. use getTemplateTagsById function.
getTemplateTags: function (templateName) {
var $dfd = $.Deferred();
var req = $.get(
properties.host() + '/api/topicTags/getTemplateTags/' +
templateName)
req.done(function (templateTags) {
$dfd.resolve(templateTags)
}).fail(function () {
$dfd.reject('could not get topic tags for model: '+properties.modelId())
})
return $dfd.promise()
},
getTemplateTagsById: function (templateId) {
var $dfd = $.Deferred();
var req = $.get(
properties.host() + '/api/topicTags/getTemplateTagsById/' +
templateId)
req.done(function (templateTags) {
$dfd.resolve(templateTags)
}).fail(function () {
$dfd.reject('could not get topic tags for the template : '+templateId)
})
return $dfd.promise()
},
setTemplateTags: function (templateId, tags) {
var $dfd = $.Deferred();
$.ajax({
type: 'POST',
url: properties.host() + '/api/topicTags/setTemplateTags/' + templateId,
data: JSON.stringify(tags),
contentType: 'application/json; charset=utf-8',
}).done(function () {
$dfd.resolve()
}).fail(function () {
$dfd.reject()
})
return $dfd.promise();
},
getAvailableTags: function(){
var $dfd = $.Deferred();
var req = $.get(properties.host() + '/api/topicTags/availableTags');
req.done(function (availableTags) {
$dfd.resolve(availableTags)
}).fail(function(){
$dfd.reject({})
})
return $dfd.promise()
},
savePeerGroupChanges: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: window.getHost() + '/dataworks/api/peerGroupChanges/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
// TODO: user should be able to download a specific ticker list
downloadTickerList: function(id){
},
downloadTickerLists: function(){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'get',
url:properties.host() + '/api/tickerListModify/download',
contentType: 'application/json',
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
},
uploadTickerList: function(){
// POST /api/tickerListModify/upload ==> upload the same downloaded json file to upload
},
deletePresentation:function(modelId){
var self = this;
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/presentations/' + modelId,
type: 'DELETE'
});
req.done(function(){
self.getPresentations().done(function(presentations){
$dfd.resolve(presentations)
});
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
getPresentations: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/presentations/',
type: 'GET'
})
req.done(function(presentations) {
for(var i in presentations){ //for efficiency returning state as string here
if(presentations[i].state) //so we parse it into json on the front end
presentations[i].state = JSON.parse(presentations[i].state)
}
$dfd.resolve(presentations)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
},
searchTicker: function(ticker){
///
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/data/tickerSearch?ticker='+ticker,
type: 'GET',
})
req.done(function(tickers) {
$dfd.resolve(tickers)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
},
getAllNotHiddenTicker: function () {
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/data/allNotHiddenTicker',
type: 'GET'
})
req.done(function (tickers) {
$dfd.resolve(tickers)
}).fail(function () {
$dfd.reject(arguments)
});
return $dfd.promise()
},
getDefaultDataSource: function(currentTemplateId){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/template/getDefaultDataSource?id='+currentTemplateId,
type: 'GET'
})
req.done(function(dataSource) {
$dfd.resolve(dataSource)
}).fail(function(error){
$dfd.reject(error)
});
return $dfd.promise()
},
setDefaultDataSource: function (currentTemplateId, dataSource) {
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/template/setDefaultDataSource?id=' + currentTemplateId + '&dataSource=' + dataSource,
type: 'GET'
})
req.done(function (dataSource) {
$dfd.resolve(dataSource)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
getAllTickers: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/data/allTickers',
type: 'GET',
})
req.done(function(tickers) {
$dfd.resolve(tickers)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
},
// these api urls are found on the pageStore payload. the components that use them
// are fairly far down the chain, and seemed easier to place them here, as opposed to passing them as props
standardizeContext: function(standardContext,rows,ticker){
var payload = {
"standardContext":standardContext,
"rows":rows,
"standardConcept":null,
"ticker":ticker
}
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/xbrl/api/standardize/context',
type: 'POST',
data: JSON.stringify(payload),// think we need this - otherwise it looks like it gets encoded...
contentType: 'application/json',
processData:false
})
req.done(function (payload) {
if (payload.error){
$dfd.reject(payload)
} else {
$dfd.resolve(payload)
}
}).fail(function (error) {
$dfd.reject({message:'Request Failed',error:error})
});
return $dfd.promise()
},
// these api urls are found on the pageStore payload. the components that use them
// are fairly far down the chain, and seemed easier to place them here, as opposed to passing them as props
getStandardContexts: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host()+"/xbrl/api/standard/context/list",
type: 'GET'
})
req.done(function (contexts) {
// comes in a payload shape...
$dfd.resolve(contexts)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
getStandardConcepts: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host()+"/xbrl/api/standard/concept/list",
type: 'GET'
})
req.done(function (concepts) {
// comes in a payload shape...
$dfd.resolve(concepts)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
submitForIndexingApi: function(presentationId) {
var $dfd = $.Deferred();
var request= $.ajax({
url: properties.host() + '/indexing/submit/'+presentationId,
type: 'GET'
})
request.done(function (result) {
$dfd.resolve(result)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
setStandardConcept: function(standardConcept,rows,ticker){
var self = this;
var payload = {
"standardContext":null,
"rows":rows,
"standardConcept":standardConcept,
"ticker":ticker
}
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/xbrl/api/standardize/concept',
type: 'POST',
data: JSON.stringify(payload),// think we need this - otherwise it looks like it gets encoded...
contentType: 'application/json',
processData:false
})
req.done(function (payload) {
if (payload.error){
$dfd.reject(payload)
} else {
$dfd.resolve(payload)
}
}).fail(function (jqXHR,textStatus,errorThrown) {
$dfd.reject({message:'Request Failed',error:arguments})
});
return $dfd.promise()
},
getXbrlStats: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/xbrl/api/bragbox',
type: 'GET'
})
req.done(function (stats) {
$dfd.resolve(stats)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
getXbrlTickers: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/xbrl/api/tickers',
type: 'GET'
})
req.done(function(tickers) {
$dfd.resolve(tickers)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
}
};
;;
// @include lib/immutable.js
// @include properties.js
// @include presentations/utils/services.js
// todo wrap this as a module
var ActionTypes = {
ADD_SECTION : "ADD_SECTION",
ADD_SECTION_MODAL_HIDE : "ADD_SECTION_MODAL_HIDE",// DO WE NEED THESE?
ADD_SECTION_MODAL_SHOW : "ADD_SECTION_MODAL_SHOW",// DO WE NEED THESE?
UPDATE_TITLE: 'UPDATE_TITLE',
UPDATE_TITLE_CONTENTS: 'UPDATE_TITLE_CONTENTS',
UPDATE_SECTION_TITLE: 'UPDATE_SECTION_TITLE',
UPDATE_SECTION_TITLE_CONTENTS: 'UPDATE_SECTION_TITLE_CONTENTS',
CHART_UPDATE_UI: 'CHART_UPDATE_UI',
SET_TEXT_BLOCK: 'SET_TEXT_BLOCK',
TEXT_BLOCK_MODAL: 'TEXT_BLOCK_MODAL',
CONSENSUS_EXPLAINED_MODAL: 'CONSENSUS_EXPLAINED_MODAL',
COPY_COMPONENT: 'COPY_COMPONENT',
FLASH_HELP_ICON: 'FLASH_HELP_ICON',
VIDEO_MODAL: 'VIDEO_MODAL',
IMAGE_MODAL: 'IMAGE_MODAL',
CONFIRM_DELETE_MODAL: 'CONFIRM_DELETE_MODAL',
SEND_INVITE_MODAL: 'SEND_INVITE_MODAL',
INFO_MODAL: 'INFO_MODAL',
SET_BASE_MODEL_TITLE: 'SET_BASE_MODEL_TITLE',
ORDER_SCENARIO_BEFORE: 'ORDER_SCENARIO_BEFORE',
ORDER_SCENARIO_AFTER: 'ORDER_SCENARIO_AFTER',
TOGGLE_REORDER_SCENARIOS: 'TOGGLE_REORDER_SCENARIOS',
UPDATE_FORECAST_MODELS_ORDER: 'UPDATE_FORECAST_MODELS_ORDER',
CREATE_CONSENSUS_SCENARIO: 'CREATE_CONSENSUS_SCENARIO',
TOGGLE_SHOW_SCENARIO_CREATOR: 'TOGGLE_SHOW_SCENARIO_CREATOR',
SET_HELP_URLS: 'SET_HELP_URLS',
SET_PRESENTATIONS: 'SET_PRESENTATIONS',
DRAG_DROP_COMPONENT: 'DRAG_DROP_COMPONENT',
ADD_CONTAINER: "ADD_CONTAINER",
CANCEL_REMOVE_COMPONENT : 'CANCEL_REMOVE_COMPONENT',
CHART_MODAL : 'CHART_MODAL',
CHART_POPUP_MODAL:'CHART_POPUP_MODAL',
CHART_MODAL_TAB_CHANGE:'CHART_MODAL_TAB_CHANGE',
USERLOGNESTEDMAP_MODAL : 'USERLOGNESTEDMAP_MODAL',
DRAFTDASHBOARDREADY_MODAL : 'DRAFTDASHBOARDREADY_MODAL',
COLLAPSE_SECTION : "COLLAPSE_SECTION",
EXPAND_SECTION : "EXPAND_SECTION",
EDIT_CONTAINER_ADVANCED: "EDIT_CONTAINER_ADVANCED",
DESTROY_MODAL : 'DESTROY_MODAL',
HIDE_MODAL : 'HIDE_MODAL',
SHOW_MODAL:'SHOW_MODAL',
REMOVE_COMPONENT : 'REMOVE_COMPONENT',
REMOVE_CONTAINER: "REMOVE_CONTAINER",
SCROLL : "SCROLL",
SET_SHARE_LINKS : "SET_SHARE_LINKS",
SHARE_LINKS_MODAL : 'SHARE_LINKS_MODAL',
SHOULD_CONFIRM_REMOVE_CHART : 'SHOULD_CONFIRM_REMOVE_CHART',
SHOULD_REMOVE_COMPONENT : 'SHOULD_REMOVE_COMPONENT',
TOGGLE_EXPAND_COLLAPSE_SECTION : "TOGGLE_EXPAND_COLLAPSE_SECTION",
VALUE_OUTPUT_MODAL : 'VALUE_OUTPUT_MODAL',
SET_CHART_TOOLTIP_POSITION : 'SET_CHART_TOOLTIP_POSITION',
SET_SECTION_NAVIGATION_ENABLED: 'SET_SECTION_NAVIGATION_ENABLED',
SET_SECTION_NAVIGATION_LOCATION: 'SET_SECTION_NAVIGATION_LOCATION',
SET_DISABLE_SHARED_SCENARIOS: 'SET_DISABLE_SHARED_SCENARIOS',
SET_ENABLE_DOWNLOAD_EXCEL: 'SET_ENABLE_DOWNLOAD_EXCEL',
SET_ENABLE_DOWNLOAD_SCENARIO_DATA: 'SET_ENABLE_DOWNLOAD_SCENARIO_DATA',
SET_CONTAINER_IDENTIFIER:'SET_CONTAINER_IDENTIFIER',
SET_BENCHMARKS_ENABLED: 'SET_BENCHMARKS_ENABLED',
OPEN_ADVANCED_CHART_SEARCH:'OPEN_ADVANCED_CHART_SEARCH',
CLOSE_ADVANCED_CHART_SEARCH:'CLOSE_ADVANCED_CHART_SEARCH',
OPEN_EXPORT_CHART:'OPEN_EXPORT_CHART',
GET_SEARCH_DATA:'GET_SEARCH_DATA',
GET_COMPONENT_FOR_CHART_SEARCH: 'GET_COMPONENT_FOR_CHART_SEARCH',
RELOAD_COMPONENT_FROM_ADVANCED: 'RELOAD_COMPONENT_FROM_ADVANCED',
COMPUTING: 'COMPUTING',
TOGGLE_SCENARIO_TAB_VISIBILITY: 'TOGGLE_SCENARIO_TAB_VISIBILITY',
INITIALIZE_SCENARIO_TAB_VISIBILITY: 'INITIALIZE_SCENARIO_TAB_VISIBILITY',
UPDATE_CONTAINER: "UPDATE_CONTAINER",
UPDATE_SECTION_DISPLAY: 'UPDATE_SECTION_DISPLAY',
PRESENTATION_TOUR_MODAL : 'PRESENTATION_TOUR_MODAL',
CLOSE_PRESENTATION_TOUR_MODAL : 'CLOSE_PRESENTATION_TOUR_MODAL',
EDITING_TOUR_MODAL: 'EDITING_TOUR_MODAL',
// TODO: sections and containers should use this as well
TOGGLE_COMPONENT_DROPDOWN: 'TOGGLE_COMPONENT_DROPDOWN',
SHOW_PREVIEW: 'SHOW_PREVIEW',
CLOSE_PREVIEW: 'CLOSE_PREVIEW',
UPDATE_DATA_AND_FILTERED_DATA : 'UPDATE_DATA_AND_FILTERED_DATA',
SHOW_PRESENTATION_TOUR_AGAIN : 'SHOW_PRESENTATION_TOUR_AGAIN',
REFRESH : "REFRESH",
INSTANCE_CHART_TYPE : 'INSTANCE_CHART_TYPE',
INSTANCE_CHART_ACTIVEPERIODTYPE: 'INSTANCE_CHART_ACTIVEPERIODTYPE',
INSTANCE_CHART_ACTIVELABEL_ID: 'INSTANCE_CHART_ACTIVELABEL_ID',
CHANGE_TOOL: 'CHANGE_TOOL',
UPDATE_SERVERSIDEPOSTPROGRESS : "UPDATE_SERVERSIDEPOSTPROGRESS",
SET_DEFAULT_CHART_DATA : "SET_DEFAULT_CHART_DATA",
ADD_XAXIS_HISTORY : 'ADD_XAXIS_HISTORY',
UNDO_XAXIS_HISTORY : 'UNDO_XAXIS_HISTORY',
UPDATE_XAXIS : "UPDATE_XAXIS",
UPDATE_XAXIS_DISPLAY: "UPDATE_XAXIS_DISPLAY",
BULK_UPDATE_XAXIS_COLUMNS: "BULK_UPDATE_XAXIS_COLUMNS",
BULK_UPDATE_XAXIS_COLUMNS_VARIADIC: "BULK_UPDATE_XAXIS_COLUMNS_VARIADIC",
BULK_GLOBAL_UPDATE_XAXIS_COLUMNS: "BULK_GLOBAL_UPDATE_XAXIS_COLUMNS",
UPDATE_CHART_DISPLAY:"UPDATE_CHART_DISPLAY",
UPDATE_YAXIS:"UPDATE_YAXIS",
SHOW_EDIT_SCENARIO_TITLE : 'SHOW_EDIT_SCENARIO_TITLE',
HIDE_EDIT_SCENARIO_TITLE : 'HIDE_EDIT_SCENARIO_TITLE',
UPDATE_CHART_INFO_OVERRIDE : 'UPDATE_CHART_INFO_OVERRIDE',
FIX_CHART_WRAPPER_TITLE : 'FIX_CHART_WRAPPER_TITLE',
PERSIST_READY: 'PERSIST_READY',
CHART_SEARCH_RESULT: 'CHART_SEARCH_RESULT',
CHART_SEARCH_STARTED: 'CHART_SEARCH_STARTED',
CHART_SEARCH_CLEARED: 'CHART_SEARCH_CLEARED',
CHART_DEFAULT_SEARCH: 'CHART_DEFAULT_SEARCH',
TIP_DISPLAYED: 'TIP_DISPLAYED',
REMOVE_FROM_TIP_DISPLAYED:'REMOVE_FROM_TIP_DISPLAYED',
TIP_REMOVE:'TIP_REMOVE',
TIPS_ON:'TIPS_ON',
TIPS_ADD:'TIP_ADDS',
SILENCE_ALL_TIPS:'SILENCE_ALL_TIPS',
VALIDATIONS_ADD:'VALIDATIONS_ADD',
SCENARIO_NOTIFICATIONS_ADD:'SCENARIO_NOTIFICATIONS_ADD',
SCENARIO_NOTIFICATIONS_CLEAR: 'SCENARIO_NOTIFICATIONS_CLEAR',
SCENARIO_NOTIFICATIONS_REMOVE:'SCENARIO_NOTIFICATIONS_REMOVE',
LAST_SECTION_SHOWN:'LAST_SECTION_SHOWN',
USER_RENAMED_SCENARIO:'USER_RENAMED_SCENARIO',
CHART_UPDATING:'CHART_UPDATING',
// TODO: not sure if this is in use...
CHART_LOADING:'CHART_LOADING',
CHART_DISPATCH_RESULT:'CHART_DISPATCH_RESULT',
CHART_RECALC:'CHART_RECALC',
CHART_RECALC_CREATEDSCENARIO:'CHART_RECALC_CREATEDSCENARIO',
CHART_RESET:'CHART_RESET',
USER_WANTS_EXIT:'USER_WANTS_EXIT',
USER_CAN_EXIT:'USER_CAN_EXIT',
UPDATE_AVAILABLE:'UPDATE_AVAILABLE',// we use this to notify the charts to show their loader
NOTIFY_GET_NEW_SCENARIO:'NOTIFY_GET_NEW_SCENARIO',// we need a different flag for whena new scneario is created from the button
UPDATE_SCENARIO_PLOT:'UPDATE_SCENARIO_PLOT',
REMOVE_SCENARIO_PLOT:'REMOVE_SCENARIO_PLOT',
UPDATE_ALL_PLOTS:'UPDATE_ALL_PLOTS',
DOWNLOAD_EXCEL: 'DOWNLOAD_EXCEL',
DOWNLOAD_DATA: 'DOWNLOAD_DATA',
HIDE_INTRO_VIDEO_MODAL: 'HIDE_INTRO_VIDEO_MODAL',
SHOW_BLANK_CHART_MESSAGE : 'SHOW_BLANK_CHART_MESSAGE',
DISMISS_BLANK_CHART_MESSAGE : 'DISMISS_BLANK_CHART_MESSAGE',
BULK_COPY_Y_AXIS_UNIT: 'BULK_COPY_Y_AXIS_UNIT',
BULK_COPY_Y_AXIS_LABEL: 'BULK_COPY_Y_AXIS_LABEL',
PLOT_ALL_SCENARIOS: 'PLOT_ALL_SCENARIOS',
UNPLOT_ALL_OTHER_SCENARIOS: 'UNPLOT_ALL_OTHER_SCENARIOS',
// websockets
UPDATE_PARSED_PROGRESS:'UPDATE_PARSED_PROGRESS',
UPDATE_DEFAULT_CHARTS:'UPDATE_DEFAULT_CHARTS',
SCREEN_SIZE_CHANGED:'SCREEN_SIZE_CHANGED',
UPDATE_PARSING_PROGRESS:'UPDATE_PARSING_PROGRESS',
UPDATE_SHEET_NAMES:'UPDATE_SHEET_NAMES',
SET_COMPONENT_PREVIEW:'SET_COMPONENT_PREVIEW',
SET_VALIDATION_CHECK:'SET_VALIDATION_CHECK',
UPDATE_TITLE_PREVIEW: 'UPDATE_TITLE_PREVIEW',
POPULATE_MODEL_DATA:'POPULATE_MODEL_DATA',
SET_META_TAG:'SET_META_TAG',
PROCESS_TAGS:'PROCESS_TAGS',
UPDATE_MODEL_INDEX:'UPDATE_MODEL_INDEX',
SET_SELECTOR_CODE: 'SET_SELECTOR_CODE',
SET_SELECTOR_ACTIVE_PERIOD:'SET_SELECTOR_ACTIVE_PERIOD',
// DYNAMIC_TEXT_MODAL:'DYNAMIC_TEXT_MODAL',
DYNAMIC_CHART_MODAL:'DYNAMIC_CHART_MODAL',
UPDATE_TEMPLATE_TOPIC_TAGS:'UPDATE_TEMPLATE_TOPIC_TAGS',
DELETE_TEMPLATE:'DELETE_TEMPLATE',
REPLACE_TEMPLATE_BLOCKS:'REPLACE_TEMPLATE_BLOCKS',
AUTO_POPULATE_TEMPLATE:'AUTO_POPULATE_TEMPLATE',
TRANSLATE_TEMPLATE: 'TRANSLATE_TEMPLATE',
SET_TEMPLATES: 'SET_TEMPLATES',
SHOW_TEMPLATES : 'SHOW_TEMPLATES',
LOAD_TEMPLATE : 'LOAD_TEMPLATE',
SHOW_SAVE_TEMPLATE : 'SHOW_SAVE_TEMPLATE',
SAVE_TEMPLATE : 'SAVE_TEMPLATE',
SAVE_TEMPLATE_SLUG : 'SAVE_TEMPLATE_SLUG',
IS_LOADED:'IS_LOADED',// TEMPLATE OR NOT, ONCE THE LOADER DOES ITS THING, THIS GETS SET TO TRUE. A TEMPLATE WILL BERFRESHED ON ALL PAGE VIEWS, BUT WE MAY NEED TO KEY OFF THIS TO PERSIST USER CHANGES VS... ALSO WILL BE USED TO KEEP EDITMODE FROM BEING AUTO EXITED...
UNLINK_DASHBOARD:'UNLINK_DASHBOARD',
UNLINK_DASHBOARD_FAIL:'UNLINK_DASHBOARD_FAIL',
SET_CONTAINER_DATA_CONTROL:'SET_CONTAINER_DATA_CONTROL',
SET_LOCAL_VARIABLES: 'SET_LOCAL_VARIABLES'
}
// action creators
// optional but highly recommended
var ActionCreators = {
processTags: function(tagsMap){
return {
type:ActionTypes.PROCESS_TAGS,
tags:tagsMap
}
},
screenSizeChanged: function(isSmallScreen){
return {
type:ActionTypes.SCREEN_SIZE_CHANGED,
isSmallScreen: isSmallScreen
}
},
collapseSection : function (id) {
return {
type : ActionTypes.COLLAPSE_SECTION,
id:id
}
},
expandSection : function (id) {
return {
type : ActionTypes.EXPAND_SECTION,
id:id
}
},
toggleExpandCollapseSection : function (id) {
return {
type : ActionTypes.TOGGLE_EXPAND_COLLAPSE_SECTION,
id:id
}
},
toggleExpandCollapseSections : function (ids) {
return {
type : ActionTypes.TOGGLE_EXPAND_COLLAPSE_SECTION,
ids : ids
}
},
editChartModalTabChange: function(activeTab){
return {
type: ActionTypes.CHART_MODAL_TAB_CHANGE,
activeTab: activeTab
}
},
addSectionModalShow : function() {
return {
type : ActionTypes.ADD_SECTION_MODAL_SHOW
}
},
addSectionModalHide : function() {
return {
type : ActionTypes.ADD_SECTION_MODAL_HIDE
}
},
addSection : function (openEditModal){
return {
type : ActionTypes.ADD_SECTION
}
},
setShareLinks: function(links) {
return {
type: ActionTypes.SET_SHARE_LINKS,
links: Immutable.fromJS(links)
}
},
updateSectionDisplay: function(id,display){
return {
type: ActionTypes.UPDATE_SECTION_DISPLAY,
id:id,
display:display
}
},
setContainerIdentifier: function(id,containerIdentifier){
return {
type: ActionTypes.SET_CONTAINER_IDENTIFIER,
identifier: containerIdentifier,
id:id
}
},
// used as part of the template translating process. It will look up if the value can return a chart or a translated string
// if either is true, the section will be shown. If both fail, section is hidden.
setContainerDataControl: function(id, control){
return {
type: ActionTypes.SET_CONTAINER_DATA_CONTROL,
dataControl:control,
id:id
}
},
// used as part of the template selector controls. could be in chart or section/container
// ${TOTAL_REVENUE}
setSelectorCode: function(id,selector){
return {
type: ActionTypes.SET_SELECTOR_CODE,
id:id,
selector:selector
}
},
updateTemplateTopicTags: function(tags){
return {
type: ActionTypes.UPDATE_TEMPLATE_TOPIC_TAGS,
tags:tags
}
},
updateContainer: function(id,display){
return {
type: ActionTypes.UPDATE_CONTAINER,
id: id,
display: display
}
},
addContainer: function(id){
return {
type: ActionTypes.ADD_CONTAINER,
id:id
}
},
removeContainer: function(id){
return {
type: ActionTypes.REMOVE_CONTAINER,
id:id
}
},
removeComponent: function(id){
return {
type: ActionTypes.REMOVE_COMPONENT,
id:id
}
},
editContainerAdvanced: function(id,isAdvancedEdit,updateChildren){
return {
type: ActionTypes.EDIT_CONTAINER_ADVANCED,
id:id,
isAdvancedEdit:isAdvancedEdit,
updateChildren:updateChildren
}
},
presentationTourModal: function() {
return {
type: ActionTypes.PRESENTATION_TOUR_MODAL
}
},
closePresentationTourModal: function(dontShowPresentationTourAgain) {
return {
type: ActionTypes.CLOSE_PRESENTATION_TOUR_MODAL,
dontShowPresentationTourAgain: dontShowPresentationTourAgain
}
},
editingTourModal: function(dontShowPresentationTourAgain) {
return {
type: ActionTypes.EDITING_TOUR_MODAL,
dontShowPresentationTourAgain: dontShowPresentationTourAgain
}
},
updateDataAndFilteredData : function(code, title, isKeyDriver, keyDrivers, inputOrOutput, unit, hasChartInfoOverride) {
return {
type : ActionTypes.UPDATE_DATA_AND_FILTERED_DATA,
code : code,
title : title,
isKeyDriver : isKeyDriver,
keyDrivers : keyDrivers,
inputOrOutput : inputOrOutput,
unit : unit,
hasChartInfoOverride : hasChartInfoOverride
};
},
showPresentationTourAgain: function(dontShowPresentationTourAgain) {
return {
type: ActionTypes.SHOW_PRESENTATION_TOUR_AGAIN,
dontShowPresentationTourAgain: dontShowPresentationTourAgain
}
},
refresh : function() {
return {
type : ActionTypes.REFRESH,
time : new Date().getTime()
}
},
chartTooltipPosition: function(position) {
return {
type : ActionTypes.SET_CHART_TOOLTIP_POSITION,
position : position
}
},
instanceChartType: function(id, chartType) {
return {
type: ActionTypes.INSTANCE_CHART_TYPE,
chartType: chartType,
id: id
}
},
instanceChartActivePeriodType: function(id, activePeriodType){
return {
type: ActionTypes.INSTANCE_CHART_ACTIVEPERIODTYPE,
activePeriodType: activePeriodType,
id: id
}
},
/**
* For Number only visualizations - this determines wich cell (label) in the category
* to return a value for. IE: which quarterly value the user wants to display.
* PeriodType: Quaterly (0) Label: 2018Q2
* @param id
* @param activeLabelId
* @returns {{type: *, activeLabelId: *, id: *}}
*/
instanceChartActiveLabelId: function(id,activeLabelId){
return {
type: ActionTypes.INSTANCE_CHART_ACTIVELABEL_ID,
activeLabelId: activeLabelId,
id: id
}
},
selectorSetActivePeriod: function(id,periodSelector){
return {
type: ActionTypes.SET_SELECTOR_ACTIVE_PERIOD,
periodSelector:periodSelector,
id:id
}
},
updatesServerSidePostProgress : function (serverSidePostProgress) {
return {
type : ActionTypes.UPDATE_SERVERSIDEPOSTPROGRESS,
serverSidePostProgress : serverSidePostProgress
}
},
updateXAxisColumnData : function (componentId,columnId, fieldName, fieldValue) {
return {
type : ActionTypes.UPDATE_XAXIS,
id: componentId,
columnId:columnId,
fieldName: fieldName,
fieldValue: fieldValue
}
},
updateXAxisDisplay: function(componentId,fieldName,fieldValue){
return {
type: ActionTypes.UPDATE_XAXIS_DISPLAY,
id: componentId,
fieldName:fieldName,
fieldValue:fieldValue
}
},
bulkUpdateXAxisColumnData: function(componentId,fieldName,fieldValue,columnIds){
return {
type : ActionTypes.BULK_UPDATE_XAXIS_COLUMNS,
id:componentId,
fieldName:fieldName,
fieldValue:fieldValue,
columnIds:columnIds
}
},
bulkUpdateVariadic: function(componentId, fieldName, columnsMap) {
return {
type: ActionTypes.BULK_UPDATE_XAXIS_COLUMNS_VARIADIC,
id: componentId,
fieldName: fieldName,
columnsMap: columnsMap
}
},
bulkUpdateYAxisUnit: function(sourceUnit, chartType, section, unit) {
return {
type: 'BULK_COPY_Y_AXIS_UNIT',
sourceUnit: sourceUnit,
chartType: chartType,
section: section,
unit: unit
};
},
bulkUpdateYAxisLabel: function(sourceLabel, chartType, section, unit) {
return {
type: 'BULK_COPY_Y_AXIS_LABEL',
sourceLabel: sourceLabel,
chartType: chartType,
section: section,
unit: unit
};
},
setDefaultChartData: function(columnInfoList,id){
return {
type: ActionTypes.SET_DEFAULT_CHART_DATA,
columnInfoList: columnInfoList,
id:id
}
},
// This is being used in the connect component in chart.js
// TODO: probably better as a util somewhere...
// @deprecated
packageUpdatedPlotData:function(plotData,scenarioId,componentId,userModified){
return {
plotData:plotData,
scenarioId:scenarioId,
componentId:componentId,
userModified:userModified
}
},
// could be a 1 as an object, or array of objects...
setUpdatedPlotData: function(plotData){
return {
type: ActionTypes.UPDATE_SCENARIO_PLOT,
plotData: plotData
}
},
updateChartDisplay : function(componentId,field,value){
return {
type:ActionTypes.UPDATE_CHART_DISPLAY,
fieldName: field,
fieldValue: value,
id: componentId
}
},
updateYAxis: function(componentId,field,value){
return {
type: ActionTypes.UPDATE_YAXIS,
fieldName:field,
fieldValue:value,
id:componentId
}
},
toggleScenarioTabVisibility: function(scenarioId) {
return {
type: ActionTypes.TOGGLE_SCENARIO_TAB_VISIBILITY,
id: scenarioId
}
},
createConsensusScenario: function(){
return {
type: ActionTypes.CREATE_CONSENSUS_SCENARIO,
id: scenarioId
}
},
showEditScenarioTitle: function(id) {
return {
type: ActionTypes.SHOW_EDIT_SCENARIO_TITLE,
id: id
}
},
hideEditScenarioTitle: function(id) {
return {
type: ActionTypes.HIDE_EDIT_SCENARIO_TITLE
}
},
openUserLogNestedMapModal : function() {
return {
type: ActionTypes.USERLOGNESTEDMAP_MODAL
};
},
openDraftDashboardReadyModal : function() {
return {
type: ActionTypes.DRAFTDASHBOARDREADY_MODAL
};
},
updateChartInfoOverride : function(code, chartInfo) {
return {
type : ActionTypes.UPDATE_CHART_INFO_OVERRIDE,
code : code,
chartInfo : chartInfo
}
},
fixChartWrapperTitle : function() {
return {
type : ActionTypes.FIX_CHART_WRAPPER_TITLE
}
},
flashHelpIcon: function(shouldFlash) {
return {
type: ActionTypes.FLASH_HELP_ICON,
shouldFlash: shouldFlash
}
},
showIntroVideoModal: function() {
var title = '2 Min Video to Get Started';
if(tf.editMode){
gavpv('open_video/' + VideoUtil.intro.title.replace(/\s/g, '') + '/mode/edit' , VideoUtil.intro.title + ' [edit]');
} else {
gavpv('open_video/' + VideoUtil.intro.title.replace(/\s/g, '') + '/mode/view' , VideoUtil.intro.title + ' [view]');
}
return {
type: ActionTypes.VIDEO_MODAL,
mimetype: 'video/mp4',
url: VideoUtil.intro.url,
title: VideoUtil.intro.title,
closeButtonTitle: 'Begin Editing Now',
hideAction: this.flashHelpIcon(true)
}
},
showVideoModal: function(options) {
if(tf.editMode){
gavpv('open_video/' + options.title.replace(/\s/g, '') + '/mode/edit' , options.title + ' [edit]');
} else {
gavpv('open_video/' + options.title.replace(/\s/g, '') + '/mode/view' , options.title + ' [view]');
}
return {
type: ActionTypes.VIDEO_MODAL,
mimetype: 'video/mp4',
url: options.url,
title: options.title || 'Video',
closeButtonTitle: options.closeButtonTitle,
hideAction: options.hideAction
}
},
showImageModal: function(options) {
if(tf.editMode){
gavpv('open_image/' + options.title.replace(/\s/g, '') + '/mode/edit' , options.title + ' [edit]');
} else {
gavpv('open_image/' + options.title.replace(/\s/g, '') + '/mode/view' , options.title + ' [view]');
}
return {
type: ActionTypes.IMAGE_MODAL,
url: options.url,
title: options.title || 'Image',
width: options.width,
closeButtonTitle: options.closeButtonTitle,
hideAction: options.hideAction
}
},
//mode = editTipsOn / viewTipsOn
tipsOn: function(mode,boolean){
return {
type: ActionTypes.TIPS_ON,
mode:mode,
display: boolean
}
},
silenceAllTips: function(){
return {
type:ActionTypes.SILENCE_ALL_TIPS
}
},
tipDisplayed: function(tipUID){
return {
type: ActionTypes.TIP_DISPLAYED,
id: tipUID
}
},
removeFromTipDisplayed: function(tip){
return {
type: ActionTypes.REMOVE_FROM_TIP_DISPLAYED,
tip: tip
}
},
tipsAdd: function(tips){
return {
type:ActionTypes.TIPS_ADD,
tips:tips
}
},
tipRemove: function(tip){
return {
type:ActionTypes.TIP_REMOVE,
tip:tip
}
},
validationsAdd: function(validations){
return {
type:ActionTypes.VALIDATIONS_ADD,
validations:validations
}
},
scenarioNotificationsAdd: function(notifications){
return {
type: ActionTypes.SCENARIO_NOTIFICATIONS_ADD,
notifications: notifications
}
},
scenarioNotificationsRemove: function(notification){
return {
type: ActionTypes.SCENARIO_NOTIFICATIONS_REMOVE,
notification:notification
}
},
scenarioNotificationsClear: function(){
return {
type: ActionTypes.SCENARIO_NOTIFICATIONS_CLEAR,
notifications: []
}
},
// set when the recalc starts, set at the end of the of the recalc/change in modelChart
computing: function(isComputing){
return {
type:ActionTypes.COMPUTING,
isComputing:isComputing
}
},
chartRecalc: function(code,scenarioId){
return {
type:ActionTypes.CHART_RECALC,
code:code,
scenarioId: scenarioId
}
},
chartReset: function(code,scenarioId){
return {
type:ActionTypes.CHART_RESET,
code:code,
scenarioId: scenarioId
}
},
newScenarioFromRecalc: function(scenarioId){
return {
type: ActionTypes.CHART_RECALC_CREATEDSCENARIO,
scenarioId:scenarioId,
}
},
userWantsExit: function(wants){
return {
type: ActionTypes.USER_WANTS_EXIT,
wants:wants //we toggle this if they cancel the exit via tip
}
},
userCanExit: function(boolean){
return {
type: ActionTypes.USER_CAN_EXIT,
canExit:boolean
}
},
showBlankChartMessage : function(chartCode) {
return {
type : ActionTypes.SHOW_BLANK_CHART_MESSAGE,
chartCode : chartCode
}
},
lastSectionShown: function(isShowing){
return {
type: ActionTypes.LAST_SECTION_SHOWN,
isShowing:isShowing
}
},
userRenamedScenario: function(modelId){
return {
type: ActionTypes.USER_RENAMED_SCENARIO,
modelId: modelId
}
},
searchStarted: function(){
return {
type: ActionTypes.CHART_SEARCH_STARTED
}
},
dispatchSearchResult: function(resp){
// this guy sets CHART_SEARCH_STARTED to false...
return {
type: ActionTypes.CHART_SEARCH_RESULT,
data: {
// searchTerm:... // the addChartTool doesn't use this yet
// TODO: allsearching should be through here
searchResults: resp
}
}
},
chartUpdating: function(id,isUpdating){
return {
type: ActionTypes.CHART_UPDATING,
id:id,
isUpdating: isUpdating
}
},
/**
* Adding the actions that are going to use thunk here. Not 100% sold on using thunk - seems a bit of overkill
* but its the redux way to do ajax (supposedly - go jquery!)
*
*/
getDefaultCharts: function(options){
return function(dispatch){
dispatch(ActionCreators.searchStarted());
Service.getDefaultPlots().done(function(resp){
dispatch(ActionCreators.dispatchSearchResult(resp))
})
}
},
/**
* Could be a plot frorm this model (properties.modelId() or another model)
* has a code and model property on it
* @param code - plotCode
* @param modelId - the base model to fetch it from
* @param getResources - we always get All resources..
* @returns {function(*): *}
*/
getModelPlot: function(code, modelId,getResources){
return Service.getPlot(code, modelId,true);
},
/**
* no plot code, no 'modelId' - it will only be translated once, and any other time,
* just echo back that first translation. Used to go to our expression service, but no longer
*
* NOTE: All live templates start out as selectors, but they get translated and their code/modelId used to fetch their data
* @param component
* @param getResources
* @returns {function(): *}
*/
getExpressionPlot: function(component,getResources){
return Service.getExpressionPlot(component)
},
getDummyPlot: function(dispatch){
return function(dispatch){
return Service.getDummyPlot();
}
},
getScenarioForPlot: function(plotCode,scenario,getComponents,newFlag){
return Service.getScenarioForPlot(plotCode, scenario,getComponents,newFlag)
},
getScenariosForPlot: function(plotCode,scenarioIds,getComponents){
return Service.getScenariosForPlot(plotCode,scenarioIds,getComponents)
},
getScenarioForPlotWithComponents: function(plotCode,scenario){
return function(dispatch){
return Service.getScenarioForPlotWithComponents(plotCode,scenario)
}
},
getScenariosForDifferentModelPlot: function(code, modelId, scenarios){
return Service.getScenariosForDifferentModelPlot(code, modelId, scenarios)
},
getScenariosForExpressionPlot: function(component,scenarios){
return Service.getScenariosForExpressionPlot(component,scenarios)
},
// doesn't get called
// @deprecated - can't find anythnig that uses this as an actioncreator.
getPlots: function(plotCodes,scenarioId){
return function(dispatch){
return Service.getPlots(plotCodes,scenarioId)
}
},
// update plot uses similar/same methods. could maybe combine...
// there are a series of actions that will happen when we make a scenario.
// we need to launch a loader icon
// create the scenario
// notify application that there is an update
// the second dispatch about the update is possibly unnecessary
// turn off the loader..
// thjis is all done in an effort to keep one dispatch to one update - ie, update doesnt change multip propteries at once
createScenario: function(model,fromScenarioButton){
store.dispatch(ActionCreators.computing(true));
var self = this;
return function(dispatch) {
var modelManager = window.store.getState().get('modelManager');
modelManager.createNewUserModel(!model,(model && model.published)).done(function(createdScenario){
self.getCopiedScenarioPlots(createdScenario).done(function(plots){
dispatch(ActionCreators.updateAllPlots(plots,createdScenario.modelId));
dispatch({ type: ActionTypes.UPDATE_FORECAST_MODELS_ORDER });
modelManager.updateStaleConsensusModels();
// maybe superfulous as the above calls calculate...
ConsensusScenarios.refreshConsensus(modelManager);
});
})
}
},
/**
* Jumbo method - gets all plots (model, different model, expression) then rerturns them
* @param scenario
* @param dispatch
* @returns {*}
*/
getCopiedScenarioPlots: function(scenario,dispatch){
var $dfd = $.Deferred();
var allPlots = PlotUtils.getPlotsByType()
// there could be problems if all itesm don't return successfully
// TODO: for example, expression plots are removed when they are fitst loaded.
// however, they may have not been loaded, but we are looking at the whole store here
// and not every component will have loaded (in hidden section, not on screen, etc..)
// forr expression charts, this means they will not have been removed, but .done won;te get fired
// because we failed them.
Service.createPromiseAll([
Service.getPlots(allPlots.currentModel.map(function(component){
return component.get('code')
}),null, scenario.modelId),
Service.updateDifferentModelPlots(allPlots.differentModel),
Service.getExpressionPlots(allPlots.expression)
]).done(function(resp){
var plotsMap = PlotUtils.mergeAllPlots(undefined,resp);
var plots = Service.addResourceData({rows:plotsMap});
$dfd.resolve(plots)
});
return $dfd.promise();
},
copyScenario: function(modelManager,scenario){
store.dispatch(ActionCreators.computing(true));
var self = this;
return function(dispatch){
var modelManager = window.store.getState().get('modelManager');
scenario.modelId === 'undefined.trefis' ?
dispatch(ActionCreators.createScenario(modelManager.get('trefis'),false))
:
modelManager.copyModel(scenario).done(function(createdScenario){
self.getCopiedScenarioPlots(createdScenario).done(function(copiedPlots){
dispatch(ActionCreators.updateAllPlots(copiedPlots,createdScenario.modelId));
dispatch({ type: ActionTypes.UPDATE_FORECAST_MODELS_ORDER });
});
})
}
},
updateAllPlots: function(responseData,scenarioId){
return {
type: ActionTypes.UPDATE_ALL_PLOTS,
plots: responseData,
scenarioId:scenarioId,
isComputing:false
}
},
// this code is just about the same as the updatePlot...
updateConsensusModel: function(model,toRecalc,isFinal){
store.dispatch(ActionCreators.computing(true));
var id = model.modelId;
return function(dispatch){
dispatch(ActionCreators.dispatchUpdateAvailable(true,id,true));
Service.updatePlot(model,toRecalc,isFinal,true,componentId).done(function(plots) {
dispatch(ActionCreators.updateAllPlots(plots, id));
}).fail(function(a,b,c){
console.log('action updatePlot/updateConsensus Failed',a,b,c)
})
}
},
/**
* Update plot actuall does more thn this - it updates All the plots. This can mean both
* plots from this model, plots from other models, or expression plots
* @param model
* @param [{
* collectionIdentifierCode;'
,
* derivedValues:[...ALL plot data, with modified values]
* }]]
* @param isFinal
* @param plotsByType - we have potentially several different types of plots now:
* modelPlots - plots from this mode; have a plot code and a modelId that matches properties.modelId()
* otherModelPlots = plots that come from 'A' model, have a plot code and a modelId but not the one we are currently on
* expressionPlots = have no code (and we don't track a modelId currently, are looked up using the expression service passing in the current modelid
* todo: define for expressionPlots if we can pass in a newly created modelId (or scenario id)
* TOdo: might be better to just grab each type explicity instead of packaged as an object
* @returns {Function}
*/
updatePlot: function(model,toRecalc,isFinal,plotsByType){
store.dispatch(ActionCreators.computing(true));
return function(dispatch){
var modelManager = window.store.getState().get('modelManager');
if (!model.yours){
modelManager.createNewUserModel(false,(model && model.published)).done(function(createdModel){
var id = createdModel.modelId;
dispatch(ActionCreators.dispatchUpdateAvailable(true,id,true));
Service.updatePlot(createdModel,toRecalc,isFinal,true).done(function(plots){
// remember the current model plots come back wiith the response.
// different model plots are treated the same as expresions now...
Service.createPromiseAll([
Service.updateExpressionPlots(plotsByType['expression']),
]).done(function(resp){
// resp = array of arrays. array for each promise above, then an array for each 'plot'
var plotMap = PlotUtils.mergeAllPlots(plots,resp)
dispatch(ActionCreators.updateAllPlots(plotMap,id))
dispatch(ActionCreators.newScenarioFromRecalc(id))
})
// if you have modified something, need to notifiy consensus to update...
if (!createdModel.isConsensusModel()){
ConsensusScenarios.refreshConsensus(modelManager);
}
// if (createdScenario.isConsensusModel()) ConsensusScenarios.calculateConsensus(modelManager,createdScenario)
}).fail(function(a,b,c){
console.log('action updatePlot Failed',a,b,c)
})
})
} else {
var id = model.modelId;
dispatch(ActionCreators.dispatchUpdateAvailable(true,id))
Service.updatePlot(model,toRecalc,isFinal).done(function(plots){
Service.createPromiseAll([
Service.updateExpressionPlots(plotsByType['expression']),
// Service.updateDifferentModelPlots(plotsByType['differentModel'])
]).done(function(resp){
// resp = array of arrays. array for each promise above, then an array for each 'plot'
var plotMap = PlotUtils.mergeAllPlots(plots,resp)
dispatch(ActionCreators.updateAllPlots(plotMap,id))
dispatch(ActionCreators.newScenarioFromRecalc(id))
})
if (!model.isConsensusModel()){
ConsensusScenarios.refreshConsensus(modelManager);
}
}).fail(function(a,b,c){
console.log('Service.updatePlot failed',a,b,c);
})
}
}
},
resetPlot: function(scenarioId,plotCode){
store.dispatch(ActionCreators.computing(true));
return function(dispatch){
// TODO: wire up to updatePlot (recalc) passing a reset:true
// this should return a set of plotCodes to be used for updating
Service.resetScenarioPlot(scenarioId,plotCode).done(function(plots){
// this should be fine as non model plots would not have been affected.
// ALSO, we only pass base scenario for expressionPlots. If that changes
// we will need to run something similar to updateAllModelPlots
dispatch(ActionCreators.updateAllPlots(plots,scenarioId));
ConsensusScenarios.refreshConsensus(store.getState().get('modelManager'));
})
}
},
// template is likely a string JSON of the desired template. seems like this is something the reducer could hanlde
// better.
fetchTemplates: function(){
return function(dispatch){
Service.getTemplates().done(function(resp){
dispatch({ type: ActionTypes.SET_TEMPLATES, templates:resp.data});
})
}
},
loadTemplate: function (templateId, template) {
return function (dispatch) {
if (!template) {
Service.getTemplates().done(function (resp) {
// THIS POPULATES TEMPLATE...
Service.getTemplateState(templateId).done(function (templateState) {
dispatch({
type: ActionTypes.LOAD_TEMPLATE,
template: JSON.parse(templateState),
currentTemplateId: templateId,
templates: resp.data
});
})
})
} else {
dispatch({
type: ActionTypes.LOAD_TEMPLATE,
template: template,
currentTemplateId: templateId
});
}
}
},
populateTemplate: function(templateId,skipPreview){
return function(dispatch){
// a little messy - need to grab the tags first...
// TODO: we assume we are getting them successfully -
// if something goes wrong getting them, its not the end of the world, just wont populate template...
// TODO: put tags somewhere else - maybe [ui, tags]?
Service.getAllTags().done(function(tagsMap){
if (tagsMap){
// TODO: just access these through the processTags...
window.tagsMap = tagsMap;
}
store.dispatch(ActionCreators.processTags(tagsMap));
Service.getTemplates().done(function(resp){
var templateToUse = resp.data.filter(function(template){
return (template.id === templateId)
});
var template = templateToUse.length ? JSON.parse(templateToUse[0].json) : null
// THIS POPULATES TEMPLATE...
dispatch({type: ActionTypes.AUTO_POPULATE_TEMPLATE, template:template, skipPreview: skipPreview})
})
})
// Service.getAllTags().always(function(){
// })
}
},
translateTemplate: function(){
return {
type: ActionTypes.TRANSLATE_TEMPLATE
}
},
deleteTemplate: function(templateId){
return {
type:ActionTypes.DELETE_TEMPLATE,
templateId:templateId
}
},
/**
* This action is used if the user unlinks and edits the CURRENT url (does not choose to copy to a new url)
* The service will mark the presentatio as isLiveTEmplate:false. WE will also do this on the front end in the
* UI property, as well as add the presentation ID to the state, triggering a save...
*
* If the user chooses the copy path, the form submission we use for copying a dashboard will do the page navigation
*
* @param presentationId
* @returns {Function}
*/
unlinkDashboard: function(presentationId,copy){
return function(dispatch) {
Service.unlinkDashboard(presentationId,copy).done(function(resp){
dispatch({type: ActionTypes.UNLINK_DASHBOARD,persistedId:presentationId,isLiveTemplate:false})
}).fail(function(){
dispatch({type:ActionTypes.UNLINK_DASHBOARD_FAIL})
// this will flash a message to the user, and take them out of edit mode
})
}
},
deleteScenario: function(scenarioId){
return {
type: ActionTypes.REMOVE_SCENARIO_PLOT,
scenarioId: scenarioId
}
},
setSheetNames : function (sheetNames) {
return {
type : ActionTypes.UPDATE_SHEET_NAMES,
sheetNames:sheetNames
}
},
dispatchUpdateAvailable: function(isAvailable,scenarioId,hasNewFlag){
return {
type: ActionTypes.UPDATE_AVAILABLE,
updateAvailable:isAvailable,
scenarioId:scenarioId,
newFlag:hasNewFlag // what is this? in part of the sharepoint speedup, this is/was needed / requestsed. ONly to be sent for the get requests of of update available (all plots that are requesting new scenario plot data for the first time..
}
},
setBenchmarksEnabled: function(benchmarksEnabled){
return {
type: ActionTypes.SET_BENCHMARKS_ENABLED,
enabled: benchmarksEnabled
}
},
openAdvancedChartSearch: function(id){
return {
type:ActionTypes.OPEN_ADVANCED_CHART_SEARCH,
id:id
}
},
closeAdvancedChartSearch: function(){
return {
type:ActionTypes.CLOSE_ADVANCED_CHART_SEARCH
}
},
openExportChart: function(chart){
return {
type:ActionTypes.OPEN_EXPORT_CHART,
chart:chart
}
},
reloadBenchmarks: function(componentId,shouldUpdate){
return {
type: ActionTypes.RELOAD_COMPONENT_FROM_ADVANCED,
componentId: componentId,
shouldUpdate: shouldUpdate
}
},
getSearchData: function(){
return {
type: ActionTypes.GET_SEARCH_DATA
}
},
updateParsedProgress: function(websocketResp){
return {
type:ActionTypes.UPDATE_PARSED_PROGRESS,
data:websocketResp,
}
},
toggleIndexDashboard: function(toggleIndex){
return {
type:ActionTypes.UPDATE_MODEL_INDEX,
data:toggleIndex
}
},
setLocalVariables: function(localVariables){
return {
type:ActionTypes.SET_LOCAL_VARIABLES,
data:localVariables
}
},
};
;;
var ConsensusScenarios = {
create: function(modelManager, forecastModelIds, autoRefresh, withUnmodified, addNewScenarios,includeBaseModel) {
var self = this;
// store.dispatch(ActionCreators.createConsensusScenario(modelManager,newModelOptions,consensusOptions));
// looking at the code, we don't support creating with options set anymore...
var consensusModel = modelManager.createNewUserModel(true, null, null, null).done(function(consensus){
modelManager.updateModel(consensus,{consensusOptions:{
forecastModelIds: forecastModelIds,
autoRefresh: autoRefresh,
withUnmodified: withUnmodified,
addNewScenarios: addNewScenarios,
includeBaseModel:includeBaseModel
}},function(){
// scenario here woul donly be the scenario and its attributes, but wouldn't be a forecastModel class -
// missing the class prototype methods like isConsensusModel, etc...
var consensusScenario = modelManager.get('models').filter(function(model){
return model.modelId === consensus.modelId
})[0];
self.calculateConsensus(modelManager, consensusScenario)
})
})
},
createDefault: function(modelManager) {
var autoRefresh = true
, withUnmodified = false
, addNewScenarios = true
, includeBaseModel = true
var forecastModelIds = modelManager.get('models')
.filter(this.modelAvailableForConsensusModels, this)
.map(modelManager.modelId)
// first things first - do we have this scenarioData?\
// we should probably dispatch an action that does all this... then populates a plots
// this.checkStoreForModelData(forecastModelIds) ? this.create() : store.dispatch('Time to dispatch');
// return;
this.create(modelManager, forecastModelIds, autoRefresh, withUnmodified, addNewScenarios,includeBaseModel)
},
// Calculates the average and updates the consensus scenario given the valuesMap and driver from the modelChart update
/*
* TODO:
* if modelManager gets refactored so that doSaveNewModel returns the ajax promise, and is then returned in
* doSave, recalculate, and doComputation, then modelChart could just wait for that promise to resolve when calling
* doComputation and then call calculateConsensus instead. Then this update method and the valuesMapToDriversMap won't be needed
*/
update: function(modelManager, consensusModel, valuesMap, driver) {
// not sure why update woulndn't just use the calculate again?
this.calculateConsensus(modelManager,consensusModel);
},
isModelModified: function(model){
var modifiedScenarios = store.getState().getIn(['ui','modifiedPlots']);
modifiedScenarios = modifiedScenarios ? modifiedScenarios.toJS() : {};
// its a immutble store object, should probably use keySeq()
return model.modelId !== 'undefined.trefis' ? Object.keys(modifiedScenarios).indexOf(model.modelId.toString()) > -1 : true;
// return Object.keys(store.getState().getIn(['ui','modifiedPlots']).toJS()).indexOf(model.modelId) > -1;
},
// Calculates the average and updates the consensus scenarioi based on existing modified drivers in models
calculateConsensus: function(modelManager, consensusModel, forecastModelIds) {
if (forecastModelIds)
this.setForecastModelIds(consensusModel, forecastModelIds)
var modelsForConsensus = modelManager.getModels(this.consensusModelIds(consensusModel))
var noDerivedModelsInConsensus = !modelsForConsensus.some(function(model) { return model.isDerived })
var noModelsModified = modelsForConsensus.every(function(model) {
return !this.isModelModified(model);
},this);
if (noDerivedModelsInConsensus) {
consensusModel.consensusOptions.includeBaseModel = true // must always have at least one model, so make sure at least base is included
consensusModel.clear()
}
else if (noModelsModified) {
consensusModel.clear(true)
}
else {
var driversMap = this.driverValuesMap(
this.modelsToDriversMap(modelsForConsensus),
modelsForConsensus,
modelManager.get('trefis'),
this.arithmeticMean,
consensusModel.consensusOptions.withUnmodified
)
// for each Driver, we need to get the plots that are un changed - basically, mimicking what the chart update does
// we pass all the values changed and unchanged
// for now, just get the base data for the plot
var plotsToUpdate = [];
$.each(driversMap, function(driver, valuesMap) {
// on d_new we have to pass ALL th data, so we will grab it by default the consensus is made by copying the base model...
var plotData = PlotUtils.getPlotForScenario('undefined.trefis',driver);
// get a merged valuesMap - items that have 'had the consensus plotted against them as well the original values for the others
// console.log('PLOTDATA',plotData);
var allValuesMap = SeriesUtil.getChartData({data:plotData}).reduce(function(acc,datum){
acc[datum.Identifier] = valuesMap[datum.Identifier] || datum.Value;
return acc;
},{});
// we pass the data in a new way value=/stringValue=/ userModifieda
var values = PlotUtils.packagePlotDataForUpdate({data:plotData},allValuesMap,valuesMap)
// we should actually build this up, then pass at once. backend should be able to handle arrays...
plotsToUpdate.push(PlotUtils.packageForRecalc(driver,values))
// consensusModel.doComputation(valuesMap, null, driver, true)
});
// i think thi screates a circular recerence, since the update probably notifies others to update...
store.dispatch(ActionCreators.updateConsensusModel(consensusModel,plotsToUpdate,true));
}
},
setForecastModelIds: function(consensusModel, forecastModelIds) {
consensusModel.consensusOptions = $.extend({}, consensusModel.consensusOptions, this.partitionModelIds(forecastModelIds))
this.updateConsensusOptions(store.getState().get('modelManager'),consensusModel,{consensusOptions:consensusModel.consensusOptions});
},
// Helper function; the forecastModelIds array shouldn't include the base model,
// which is included by toggling the includeBaseModel flag
partitionModelIds: function(forecastModelIds) {
var modelIds = forecastModelIds.filter(function(id) { return id !== 'undefined.trefis' })
return { forecastModelIds: modelIds, includeBaseModel: modelIds.length < forecastModelIds.length }
},
// Similar to partitionModelIds, checks includeBaseModel and adds it to the returned array of modelIds
consensusModelIds: function(consensusModel) {
return (consensusModel.consensusOptions.includeBaseModel ? ['undefined.trefis'] : [])
.concat(consensusModel.consensusOptions.forecastModelIds)
},
isModelInConsensus: function(model) {
var self = this
return function(consensusModel) {
return self.consensusModelIds(consensusModel).indexOf(model.modelId) > -1
}
},
// could be newer cerated, or newer updated, as in, i publish a model, i need the consensus to update...
isModelNewerThanConsensus: function(model, consensusModel) {
return new Date(model.createdAt).getTime() >= new Date(consensusModel.updatedAt).getTime() ||
new Date(model.updatedAt).getTime() >= new Date(consensusModel.updatedAt).getTime()
},
// Checks if the model is available for consensus models in general
modelAvailableForConsensusModels: function(model) {
return model.isDerived && model.published
},
// Checks if the model is available for a specific consensus model
modelAvailableForConsensus: function(consensusModel, includeManuallyAddable) {
var self = this
return function(model) {
var isAvailable = consensusModel !== model
&& (self.modelAvailableForConsensusModels(model)
|| (consensusModel.consensusOptions.includeBaseModel && model.modelId === 'undefined.trefis'))
&& (consensusModel.consensusOptions.addNewScenarios && !includeManuallyAddable ?
self.isModelInConsensus(model)(consensusModel) || self.isModelNewerThanConsensus(model, consensusModel) : true);
return isAvailable
}
},
modelsAvailableForConsensus: function(consensusModel) {
return function(models) {
return models.filter(ConsensusScenarios.modelAvailableForConsensus(consensusModel))
}
},
modelIdsAvailableForConsensus: function(consensusModel) {
var modelsAvailableForConsensus = this.modelsAvailableForConsensus(consensusModel)
return function(models) {
return modelsAvailableForConsensus(models).map(function(model) { return model.modelId })
}
},
updateConsensusOptions: function(modelManager, consensusModel, consensusOptions, cb) {
var withUnmodifiedChanged = consensusModel.consensusOptions.withUnmodified
!== consensusOptions.withUnmodified
modelManager.updateModel(consensusModel, consensusOptions, function() {
if (withUnmodifiedChanged) this.calculateConsensus(modelManager, consensusModel)
}.bind(this))
},
// predicate function to disclude unmodified values from user scenarios
isModified: function(model, baseModel, identifier) {
var isBaseModel = model.modelId === baseModel.modelId
, valueWasModified = this.getValue(model,identifier) !== this.getValue(baseModel,identifier)
return isBaseModel || valueWasModified
},
arithmeticMean: function(values) {
return values.reduce(function(acc, value) { return acc + value }, 0) / values.length
},
geometricMean: function(values) {
return Math.pow(values.reduce(function(acc, value) { return acc * value }, 1), 1 / values.length)
},
mode: function(values) {
return Immutable.List(values).groupBy(function(v){return v}).map(function(v){return v.size}).sort().keySeq().last()
},
/**
* We used to have a bunhc of maps in companyData that were copied onto the models. This replicates that,
* just that we store these on the store, and they are populated from the data returned from the services...
*
* @param scenario
* @param identifier
* @returns An immutable Map of the cell data (period driver code, value, type, basically a chart datapoint)
*/
getCellFromStore: function(scenario,identifier){
return store.getState().getIn(['ui','modelData',scenario.modelId,'values',identifier])
},
isNumeric: function(cell){
// we now have strings (tag column etc in data...)
return cell.get('Type') === 'NUMBER'
},
getValue: function(scenario,code){
var cell = this.getCellFromStore(scenario,code);
// in theory, at leats for these purposes, you can't modify a string since it doesn't appear in the chart
// and the calling code for this works on modified drivers and their periodCodes...
return this.isNumeric(cell) ? cell.get('Value') : 0
},
// returns an averaged drivers map in the form { [driver]: { [period driver]: value } }
driverValuesMap: function(driversMap, modelsForConsensus, baseModel, averagingFn, withUnmodified) {
var self = this
var driverValuesMap = $.extend({}, driversMap)
$.each(driverValuesMap, function(driver, periodDrivers) {
$.each(periodDrivers, function(periodDriver) {
var values = modelsForConsensus
.filter(function(model) {
return withUnmodified ? true : self.isModified(model, baseModel, periodDriver)
})
.map(function(model) {
return self.getValue(model,periodDriver)
})
// toggleDriver????
console.warn('base.companyData.isToggle() consensusScenarios is doing a check, but just returning true')
driverValuesMap[driver][periodDriver] = false ? self.mode(values) : averagingFn(values)
})
})
// console.log('value',{
// driversMap:driversMap,
// driverValuesMap:driverValuesMap,
// })
return driverValuesMap
},
getModifiedDriversForScenario: function(scenario){
return store.getState().getIn(['ui','modifiedPlots',scenario.modelId]) ? store.getState().getIn(['ui','modifiedPlots',scenario.modelId]).toJS() : []
},
// returns a drivers map in the form { [plotCode]: { [Identifier (chartData[0].array- ).Identifier]: [] } }
modelsToDriversMap: function(scenariosForConsensus) {
var self = this;
return scenariosForConsensus.reduce(function(modifiedDrivers, scenario) {
var scenarioModifiedDrivers = self.getModifiedDriversForScenario(scenario);
$.each(scenarioModifiedDrivers, function(driver, periodDrivers) {
modifiedDrivers[driver] = modifiedDrivers[driver] || {}
if (periodDrivers.length){
periodDrivers.forEach(function(periodDriver) {
modifiedDrivers[driver][periodDriver] = []
})
}
})
return modifiedDrivers
}, {})
},
// returns a drivers map in the form { [driver]: { [period driver]: [] } }
valuesMapToDriversMap: function(valuesMap, driver) {
return Object.keys(valuesMap).reduce(function(modifiedDrivers, periodDriver) {
modifiedDrivers[driver] = modifiedDrivers[driver] || {}
modifiedDrivers[driver][periodDriver] = []
return modifiedDrivers
}, {})
},
// This is called from modelChart to update the autoRefresh consensus scenarios
updateAutoRefresh: function(modelManager, valuesMap, driver) {
return modelManager.getConsensusModels()
.filter(function(consensusModel) {
// console.log(ConsensusScenarios.isModelInConsensus(modelManager.get('active'))(consensusModel))
return ConsensusScenarios.isModelInConsensus(modelManager.get('active'))(consensusModel)
&& consensusModel.consensusOptions.autoRefresh
}).forEach(function(consensusModel,a,b) {
this.update(modelManager, consensusModel, valuesMap, driver)
}, this)
},
refreshConsensus: function(modelManager){
return modelManager.getConsensusModels().filter(function(consensusModel){
return consensusModel.consensusOptions.autoRefresh
}).forEach(function(consensusModel){
// this.update(modelManager,consensusModel)
},this)
}
};
;;
// lib/jquery.min.js
// @include infrastructure/polyfills.js
// @include lib/underscore.js
// @include lib/backbone.js
// @include common.js
// @include infrastructure/forecastModel.js
// @include properties.js
// @include lib/reselect.js
// @include presentations/actions/index.js
// @include presentations/store.js
// @include presentations/consensusScenarios.js
/**
* Stores the various ForecastModels that can be used in the client. Must be initialized
* by calling setCompany and passing the companyData.
*
* Built as a Backbone model.
*
* Exposes the following attributes on the model:
* models: Array of ForecastModel with all models currently loaded (generally 1 base model, and 0 or more derived models)
* trefis: ForecastModel of base model
* active: ForecastModel of the "active" model that should be shown in the sankey.
* Generally points to the same ForecastModel instance as either the user model,
* or one of the comparison models
* publishedModels: Array of ForecastModels, that is a subset of 'models'. Includes only the models that are published scenarios
*
* In addition, exposes the following events:
* userChange: fired when the user's model changes, either because the user dragged a line,
* or the user switched models
* titleChange: fired when the user edits the title of one of the models. Has one argument,
* a boolean which is true iff the title change is really coming from the
* user modifying a line thus creating a new user model
* modelDeleted: fired when a model is deleted from the filmstrip. Has one argument,
* the ForecastModel that was deleted
* model:added: fired when a new model is added to the models array. Has one argument,
* the ForecastModel that was added
*/
var ModelManager = Backbone.Model.extend({
/*
* This dispatches the key/value pair from Backbone.Model.set to redux
*/
dispatch: function(key, value) {
if (key === 'companyData')
store.dispatch({ type: 'SET_COMPANY_DATA', companyData: value });
else if (key === 'trefis')
store.dispatch({ type: 'SET_BASE_FORECAST_MODEL', baseForecastModel: value });
else if (key === 'models')
store.dispatch({ type: 'SET_FORECAST_MODELS', forecastModels: value });
else if (key === 'active')
store.dispatch({ type: 'SET_ACTIVE_FORECAST_MODEL', activeForecastModel: value });
else if (key === 'hiddenForecastModels')
store.dispatch({type:'INITIALIZE_SCENARIO_TAB_VISIBILITY',hiddenForecastModels:value})
else if (key === 'selectedPeriod')
store.dispatch({ type: 'SET_SELECTED_PERIOD', selectedPeriod: value });
else if (key === 'periodSelectionManager')
store.dispatch({ type: 'SET_PERIOD_SELECTION_MANAGER', periodSelectionManager: value });
else if (key === 'publishedModels')
store.dispatch({ type: 'SET_PUBLISHED_FORECAST_MODELS', publishedForecastModels: value });
else if (key === 'modelManager')
store.dispatch({type:'SET_MODEL_MANAGER', modelManager: value});
else if (key === 'parentChildrenManager')
store.dispatch({type:'SET_PARENT_CHILD_MANAGER', parentChildrenManager:value});
},
/* A map of modelManager attributes to it's redux counterpart */
modelManagerToReduxProps: {
companyData: 'companyData',
trefis: 'baseForecastModel',
models: 'forecastModels',
active: 'activeForecastModel',
periodSelectionManager: 'periodSelectionManager',
publishedModels: 'publishedForecastModels',
modelManager: 'modelManager',
parentChildrenManager: 'parentChildrenManager'
},
/*
* This overrides the backbone set and get methods so that we can start using the redux store to
* handle the model layer and still maintain backwards compatibility with older code
*/
set: function(key, value, options) {
if (!properties.presentationMode())
return Backbone.Model.prototype.set.call(this, key, value, options);
if (key == null) return this;
if (typeof key === 'object') {
for (var k in key)
this.dispatch(k, key[k])
}
else if (typeof key === 'string') {
this.dispatch(key, value)
}
return this
},
get: function(attr) {
if (!properties.presentationMode())
return Backbone.Model.prototype.get.call(this, attr);
var property = store.getState().get(this.modelManagerToReduxProps[attr]);
return Immutable.isImmutable(property) ? property.toJS() : property
},
nextTitleId: 1,
initialize: function() {
// If it's in presentation mode, pass the model manager up to the store...
if (properties.presentationMode()) {
var self = this;
// Listen to any events that get fired when a forecast model changes, and update the
// forecastModels in the store. This way react picks up all changes to forecast models
// since forecastModels isn't entirely an immutablejs collection (only shallowly an immutable list)
this.set({ modelManager: this });
// TODO: would really like to get this out of here... this leads to a lot of store updates
this.on('all', function() {
store.dispatch({ type: 'SET_FORECAST_MODELS', forecastModels: self.get('models') })
});
/* Subscribe to the redux store to trigger the model manager events */
store.subscribe(reduxWatch(function() { return store.getState().get('companyData') })(function() {
demoWidget.modelManager.trigger('change:companyData')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('baseForecastModel') })(function() {
demoWidget.modelManager.trigger('change:trefis')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('forecastModels') })(function(oldVal, newVal) {
// Only trigger the change:models event from the store if the length changes, which is currently
// the only change you could do from the store that can be tracked anyway
if (oldVal && newVal && oldVal.size !== newVal.size) {
new ShareLinksModel(self, store).shareLinks.done(function(links) {
store.dispatch(ActionCreators.setShareLinks(links))
});
}
}));
store.subscribe(reduxWatch(function() { return store.getState().get('activeForecastModel') })(function() {
demoWidget.modelManager.trigger('change:active selected:model')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('periodSelectionManager') })(function() {
demoWidget.modelManager.trigger('change:periodSelectionManager')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('publishedForecastModels') })(function() {
demoWidget.modelManager.trigger('change:publishedModels')
}))
store.subscribe(reduxWatch(function() { return store.getState().getIn(['ui', 'hiddenScenarioTabs']) })(function(a,b) {
demoWidget.modelManager.get('models').forEach(function(model) {
var isVisible = !store.getState().getIn(['ui', 'hiddenScenarioTabs']).includes(model.modelId)
if (isVisible !== model.displayProperties.visible) {
// toggle our modelInQuestion's display properties
model.displayProperties.visible = isVisible
// charts hear this, drop the series...
demoWidget.modelManager.trigger("displayPropertyChanged", model);
}
})
}))
}
// Otherwise, perform the old logic
else {
this.on('change:models', function(){
this.rebuildPublishedModels()
});
}
},
/**
* Sets the company data for this model. Should be called to initialize the model manager.
* After this is called, the trefis, user, active, and comparisons fields will be set.
*
* @param {CompanyData} companyData
* @param {boolean} useDerivedModels if true, we'll preload any derived models included in the initialLoad.
* If false, we'll ignore any derived models
* @param {function} callback -- optional parameter. If specified the function will be called after all models' promises have been calculated
*/
setCompany: function(companyData, useDerivedModels, callback) {
var $dfd = $.Deferred(),
self = this,
loggedInUser = properties.loggedInUser() || {},
publishedModels = [],
userModelData,
modelPromises = [],
useWebWorkers = !!callback; // Only if a callback function is provided we will attempt using webworkers. Otherwise we use calculate synchronously.
var baseModel = ForecastModel.createTrefisModel(useWebWorkers, companyData);
// TODO: does baseModel also need a listener? On load, its the only model,
// needs to trigger events from modelChart?
modelPromises.push(baseModel);
var urlVars = PresentUtil.getUrlVars();
if (urlVars && (urlVars['skipScenarioLoad']==='true')){
self.doModelManagerSetting($dfd,modelPromises,baseModel,companyData,callback);
return $dfd.promise()
} else {
if(useDerivedModels) {
Service.getScenarios().done(function (scenarios) {
console.log(scenarios)
$.each(scenarios, function(i, scenario) {
if(scenario.forecastType == ForecastModel.TYPE_YOURS) {
var model = ForecastModel.createUsersModel(
useWebWorkers,
baseModel,
companyData,
scenario,
loggedInUser.userId,
loggedInUser.firstName,
loggedInUser.lastName
);
modelPromises.push(model);
} else if(scenario.forecastType == ForecastModel.TYPE_OTHER) {
var model = ForecastModel.createComparisonModel(useWebWorkers, baseModel, companyData, scenario);
modelPromises.push(model);
}
});
self.doModelManagerSetting($dfd,modelPromises,baseModel,companyData,callback);
}).fail(function(a,b,c){
console.log('set company data - fail from service',a,b,c)
})
}
return $dfd.promise();
}
},
doModelManagerSetting: function($dfd,modelPromises,baseModel,companyData,callback){
var activeModel = baseModel;
var self = this;
// The backend controller sets showDerivedModel based on the url query parameter from a share link url
if(properties.showDerivedModel() != null){
//TREF-6713: set derived model as active model if derived was pass in as parameter
for (var i = 0; i < modelPromises.length; i++) {
var model = modelPromises[i];
if(model.modelId == properties.showDerivedModel()) {
activeModel = model;
}
}
}
self.set({
companyData: companyData, // store the companyData since we may need it later
trefis: baseModel,
models: modelPromises,
active: activeModel
});
// set child/parent relationships loaded from server
var models = self.get('models');
for (var i = 0; i < models.length; i++) {
var model = models[i];
if (model.parentModelId){
var parentChildrenManager = self.getParentChildrenManager();
var parentID = model.parentModelId;
var parentModels = $.grep(models, function(model) {
return model.modelId == parentID;
});
if (parentChildrenManager && (parentModels.length > 0)){
parentChildrenManager.addChildAndParentRelationship(model, parentModels[0]);
}
}
}
// why not just do all scenario visibility stuff here?
var selectedState = properties.modelContext().selectedState;
//var modelIds = Immutable.List( state.get('forecastModels').toJS().map(function(model) { return model.modelId }) );
var modelIds = self.get('models').map(function(model){
return model.modelId
})
var hiddenScenarioTabs;
var activeModelId = self.get('active').modelId;
if (selectedState[0]=='default'){
// if there is not selected state, then make sure base and active model are not hidden
hiddenScenarioTabs = modelIds.reduce(function(hiddenScenarios,modelId){
if (modelId != 'undefined.trefis' && modelId != activeModelId){
hiddenScenarios.push(modelId)
}
return hiddenScenarios;
},[]);
} else {
// selected for comparison
// ["undefined.trefis", 12072, 12073]
// modelIds....
// ['undeifined.trefis',12072,12073,12074]
// properties.showDerivedModel()
// 12072 === active...
hiddenScenarioTabs = modelIds.reduce(function (hiddenScenarios, modelId) {
if (!(selectedState.indexOf(modelId) > -1)) {
hiddenScenarios.push(modelId)
}
return hiddenScenarios;
}, []);
}
// probably should only be done in store - making it immutable ....
self.set('hiddenForecastModels',Immutable.fromJS(hiddenScenarioTabs))
self.set('publishedModels',Immutable.fromJS(self.get('models').filter(function(){
return model.published
})))
// these will fetch as regular when selected
// self.updateStaleConsensusModels();
self.publishModelOnParam();
if(callback)
callback();
// a one time event, to be used by widgets that previously relied on filmstrip $.deferred
self.trigger('loaded:models');
$dfd.resolve(true);
},
publishModelOnParam: function() {
var params = new URLSearchParams(location.search.slice(1));
if (params.has('publishModelId')) {
var model = this.get('models').find(function(model) {
return model.modelId == params.get('publishModelId')
});
if (model && model.yours && !model.published)
this.togglePublished(model, true)
}
},
updateConsensusModelsBasedOffModel: function(model) {
this.get('models')
.filter(this.isConsensusModel)
.filter(ConsensusScenarios.isModelInConsensus(model))
.forEach(function(consensusModel) {
return ConsensusScenarios.calculateConsensus(this, consensusModel)
}, this)
},
// TODO: might be better to have the backend ON DELETE CASCADE consensusOptions.forecastModelIds that
// won't exist anymore than to deal with this on the frontend
updateStaleConsensusModels: function() {
this.get('models')
.filter(this.isConsensusModel)
.filter(this.isConsensusStale, this)
.forEach(function(consensusModel) {
ConsensusScenarios.calculateConsensus(
this,
consensusModel,
ConsensusScenarios.modelIdsAvailableForConsensus(consensusModel)(this.get('models'))
)
}, this)
},
modelExists: function(modelId) {
return !!this.getModel(modelId)
},
getModel: function(modelId) {
return this.get('models').find(function(model) { return model.modelId == modelId })
},
getModels: function(modelIds) {
return this.get('models')
.filter(function(model) { return modelIds.indexOf(model.modelId) > -1 })
},
modelId: function(model) {
return model.modelId
},
// Checks if the consensus has any new or no longer existing modelIds if autoRefresh is enabled
isConsensusStale: function(consensusModel) {
var comparator = consensusModel.consensusOptions.addNewScenarios ? 'equals' : 'isSubset';
var isStale = consensusModel.consensusOptions.autoRefresh
&& !Immutable.Set(ConsensusScenarios.consensusModelIds(consensusModel))[comparator](
Immutable.Set(ConsensusScenarios.modelIdsAvailableForConsensus(consensusModel)(this.get('models')))
);
return isStale;
},
isConsensusModel: function(model) {
return model.isConsensusModel()
},
hasSubmitToView: function() {
return this.get('models').filter(function(model) {
return model.isSubmitToViewConsensus()
}).length > 0
},
yourModels: function() {
return this.get('models').filter(function(model) { return model.yours })
},
yourPublishedModels: function() {
return this.get('models').filter(function(model) {
return model.yours && model.published
})
},
isConsensusAvailable: function(model) {
return !model.yours && model.isSubmitToViewConsensus() ? this.yourPublishedModels().length > 0 : true
},
hasSomeUnavailableConsensus: function() {
return this.hasConsensusModels() && this.get('models').some(function(model) {
return !this.isConsensusAvailable(model)
}, this)
},
isModelAvailable: function(model) {
return model.isConsensusModel() ? this.isConsensusAvailable(model) : true
},
getUnavailableConsensus: function() {
return this.getConsensusModels().filter(function(consensusModel) {
return !this.isConsensusAvailable(consensusModel)
}, this)
},
modelInUnavailableConsensus: function(model) {
return this.getUnavailableConsensus().some(function(consensusModel) {
if (model.isDerived)
return consensusModel.consensusOptions.forecastModelIds.indexOf(model.modelId)>-1;
else
return false // base model should always be shown
})
},
modelNotInUnavailableConsensus: function(model) {
return !this.modelInUnavailableConsensus(model)
},
unavailableModelsInConsensus: function(consensusModel) {
return consensusModel.consensusOptions.forecastModelIds
.map(this.getModel, this)
.some(this.modelInUnavailableConsensus, this)
},
/**
* Creates a new user model whose values are identical to the base model,
* and sets this new model as the active model.
*
* @param {boolean} fromNewScenarioButton if true, the newly created model will have no grid view
* preferences (defaulting to whatever's in the JSON data). If false, the newly created model
* will have grid view preferences copied from the current preferences on the base model
*
* @param {boolean} fromModifyingPUblishedScenario if true, we need to show the warning for new
* @param {ForecastModel, optional} parentDerivedModel used to copy from
* model creation.
*
* @param {ForecastModel} derivedModel model to based it off of (optional)
*/
//createNewUserModel: function(fromNewScenarioButton, fromModifyingPublishedScenario, derivedModel) {
createNewUserModel: function(fromNewScenarioButton, fromModifyingPublishedScenario, derivedModel, parentDerivedModel, consensusOptions) {
var self = this;
var loggedInUser = properties.loggedInUser() || {};
var $dfd = $.Deferred();
//TREF-4589 get active model grid view preferences to copy to new user model (from modifying derived models)
// cannot always get the active model, since we could it could fromNewScenarioButton while a derived model is active
Service.createScenario().done(function(scenario){
var activeDerivedModelGridViewPreferences = (fromModifyingPublishedScenario ? self.get('active').gridViewPreferences : null);
var userModel = ForecastModel.createUsersModel(
false,
self.get('trefis'),
self.get('companyData'),
scenario || null, // passing no data here means it'll default its values to the base model
loggedInUser.userId,
loggedInUser.firstName,
loggedInUser.lastName,
activeDerivedModelGridViewPreferences,
parentDerivedModel,
consensusOptions
);
// TODO: shouldbe be necessary as we are passing in a derived model...
if(tf.editMode){
gavpv('create/Scenario/mode/edit','Create scenario in edit mode');
} else {
gavpvl('create/Scenario/mode/view','Create scenario in view mode');
}
var title = '' + (self.nextTitleId++);
// For modelChart.js, demoWidget.js
// Adding these so that anything listenting to change:models would have some insight into
// where the change came from.
// scenario modification behavior will use origin.publishedScenario to determin whether
// to show the new scenario message even if the user has seen it from modifying a point on base
var origin = {};
origin.publishedScenario = fromModifyingPublishedScenario;
origin.newScenarioButton = fromNewScenarioButton;
// set the parent child relationship
if (parentDerivedModel){
var parentChildrenManager = this.getParentChildrenManager();
parentChildrenManager.addChildAndParentRelationship(userModel, parentDerivedModel);
}
// TODO: errors if we don't add these here...
self.set({
models: self.get('models').concat([userModel]),
active: userModel
});
// may be an issue - we don't pass any options to the new scenario service (consensus... that was done in the doComputation/save)
$dfd.resolve(userModel);
});
return $dfd.promise();
},
copyModel: function(scenarioToCopy){
var self = this;
var $dfd = $.Deferred();
var loggedInUser = properties.loggedInUser() || {};
var activeDerivedModelGridViewPreferences = (scenarioToCopy.published ? this.get('active').gridViewPreferences : null);
Service.copyScenario(scenarioToCopy.modelId).done(function(newScenario){
// TODO: we can probably do away with a bunch of this - just adding the parent/child methods and consensus methods
var userModel = ForecastModel.createUsersModel(
false,
self.get('trefis'),
self.get('companyData'),
newScenario || null, // passing no data here means it'll default its values to the base model
loggedInUser.userId,
loggedInUser.firstName,
loggedInUser.lastName,
activeDerivedModelGridViewPreferences,
null,
scenarioToCopy.consensusOptions // i guess if the scenario already had consensus Options, they would be on the return scenario as well
);
self.set({
models: self.get('models').concat([userModel]),
active: userModel
});
$dfd.resolve(userModel);
})
return $dfd.promise();
},
addModel: function(model,setAsActive){
var self = this;
self.set({
models: self.get('models').concat([model]),
});
self.trigger('add:model',model);
if (setAsActive){
self.set({
active: model
});
self.trigger('change:models');
}
},
/**
* Internal helper method that adds various listeners to models.
* Should be called once on any new user models.
* @param {ForecastModel} userModel
*/
addListeners: function(model) {
var self = this;
model.addChangeListener(_.bind(function(isFinal, driverSetChanged) {
// this get set to start in a modelChart in the chartChangeListener
// probably can just be this...
this.trigger('modelRecalc', model, isFinal, driverSetChanged, false);
self.getStore().dispatch(ActionCreators.computing(false))
}, this));
model.addBeforeChangeListener(_.bind(function(isFinal, driverSetChanged) {
}, this));
},
/**
* Unloads the current comparison model, so user is viewing their own model again.
*/
selectBaseAsActive: function() {
this.set({
active: this.get('trefis')
});
},
/**
* Loads the given model and makes it the active model -- meant to be called e.g.
* when the user clicks on a tab.
* @param {ForecastModel} model
* @return {Deferred}
*/
loadAndMakeActive: function(model) {
// no need to load anything currently, just set it to active model.
// in the future, we may need to do some loading or initializing of the ForecastModel
// before it can be used as the active model.
//var deferred = $.Deferred();
this.set({
active: model
});
if(model.defaultPeriod && this.get('periodSelectionManager')) {
this.get('periodSelectionManager').setSelectedPeriod(model.defaultPeriod);
}
// internally, this will ALSO trigger change:active
// that event though gets triggered whether a user selected the model, or a new model was created
// insector listens to this
this.trigger('selected:model');
//deferred.resolve(model);
//return deferred;
},
/**
* toggles whether the given model is a published analyst model or not
* @param {ForecastModel} model
* @param {boolean} isPublishedModel
*/
togglePublished: function(model, isPublishedModel) {
var self = this;
this.updateModel(model, {published: !!isPublishedModel}, function(success) {
// we are still modifying by reference - this seems to trigger the accurate state/re-render
self.rebuildPublishedModels();
self.updateStaleConsensusModels()
// TODO: Logic was copied from scenariosTool.js -> toggleScenarioTabVisibility (TREF-7689)
// but shouldn't have to manually check and trigger this kind of thing anywhere
;(function toggleSeriesInConsensus() {
self.getConsensusModels().forEach(function(model) {
// Hides submit to view consensus if user no longer has any published models
if (!self.isConsensusAvailable(model)) {
store.dispatch({ type: 'HIDE_SCENARIO_TAB', id: model.modelId })
model.displayProperties.visible = false
// this is what actually controls the visible/private toggle. Thre is code for whenevery
// something is 'set' in modelManager to trigger the set_forecast_model
self.trigger("displayPropertyChanged", model);
}
})
})()
},
true);
},
rebuildPublishedModels: function() {
var publishedModels = $.grep(this.get('models'), function(model) {
return model.published;
});
this.set({publishedModels: publishedModels});
},
deleteModel: function(model) {
var self = this;
Service.deleteScenario(model).done(function(){
self.cleanUpDeletedModel(model);
self.updateStaleConsensusModels()
})
},
/**
* instead of storing model objects on the filmstrip (or any model display switcher),
* we can just store an id.
* @param id
*/
getModelById: function(id){
var model;
return model;
},
cleanUpDeletedModel: function(model) {
var models = this.get('models');
models = $.grep(models, function(m) {
return m !== model;
});
// this will trigger models:change
this.set({models: models});
// modelChart uses the model/id to remove from its series
// gridview uses this event to trigger its loadModels
this.trigger('model:deleted', model);
if(model === this.get('active')) {
this.set({active: this.get('trefis')});
}
this.getParentChildrenManager().removeChildScenario(model);
},
/**
* Should be called to indicate when the user clicks on the "request scenario" tab, to show the form
*/
openRequestScenario: function(){
$.ajax({
type: 'POST',
url: getHost()+'/emailfeedback',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
modelId: properties.modelId(),
type: "REQUESTCLICK"
}),
success: $.noop,
error: $.noop
});
},
/**
* Called to indicate when the user has submitted the "request scenario" form
*
* @param {String} userName
* @param {String} userEmail
* @param {String} customField
* @return {boolean} success
*/
sendRequestScenario: function(userName, userEmail, customField) {
if(userName == "Your Name"){
alert("Please enter your name.");
return false;
}
if(!tf.mailRE.test(userEmail)){
alert("Email format is invalid.");
return false;
}
$.ajax({
type: 'POST',
url: getHost()+'/emailfeedback',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
modelId: properties.modelId(),
type: "REQUESTINFO",
userName: userName,
userEmail: userEmail,
customField: customField
}),
success: $.noop,
error: $.noop
});
return true;
},
/**
* Updates our models to be persisted in session. User changes the order,
* we should have that order reflected in this.get('models')
* FOR NOW, getting models has [base,derived,derived...]
* we add 1 to our 'desired' index to accomodate for this, since a user can't re-arrange
* the base model
* @param {Object} desiredOrderMap
*/
updateModelOrder: function(desiredOrderMap){
var models = this.get('models');
var newModelsOrder = [];
_.forEach(models,function(model,i){
var id = model.modelId;
// ignore first, we don't want it moved
// it is our base model.
if (i > 0){
// this preserves our true get('models') order
// since desiredOrderMap will have something at "0", "1" but those
// will be actually be 1,2
// 0 is currently reserved for the base model.
var index = desiredOrderMap[id] + 1;
move(models,i,index);
}
});
// TODO: create as a mixin for underscore/lo-dash
function move(array, fromIndex, toIndex) {
array.splice(toIndex, 0, array.splice(fromIndex, 1)[0] );
return array;
}
},
/**
* Persists a new order of derived model tabs to the server. Should be called when the user drag-n-drops the derived model tabs.
* @param {Object} newOrder map from derived model id to desired order index. E.g. if you have three models with ids 5, 6, and 7,
* and you want to arrange them in the order: 7, 5, 6, then you would pass in {"7": 0, "5": 1, "6": 2}
*/
saveOrdering: function(newOrder) {
// update our model order..
this.updateModelOrder(newOrder);
this.trigger("reorder:models",this.get('models'));
var companyId = this.get('trefis').companyData.il.id;
if(companyId) {
$.ajax({
type: 'POST',
url: getHost()+'/api/derivedmodel/ordering/' + companyId + '?_method=PUT',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(newOrder),
success: $.noop,
//error: tf.ajaxError
error: function(error){
// console.warn('save ordering you got an error',error)
}
});
}
},
/**
* Updates attributes on the given model and saves the result to the server. Also automatically
* handles events, and rollback of changes if the request to the server fails.
*
* Added as part of TREF-4392.
*
* @param {ForecastModel} model - the model that should be modified
* @param {Object} attributes - fields that should be updated on the model. Keys can
* include: scenarioExplanation, title, defaultPeriod, published
* @param {Function} callback - optional callback function that will be called
* once the request to server finishes. Will be passed a single boolean argument
* indicating whether the request was successful
* @param {boolean} forceAnalystSave - optional. published analyst models are usually not saved to the server when updated.
* If this param is true, we'll save anyway.
*/
updateModel: function(model, attributes, callback, forceAnalystSave) {
// TODO: we used to have a bunhc of listeners scattered through out the codebase
// now we mainly used random sets. IN the base of publishing, its the setting of the new publishedModels array
// that triggers the redraw. We should probably set the the new model on the forecastModel array to be more explicit
Service.updateScenarioAttributes(model,attributes).done(function(scenarioPropertiesFromServer){
$.extend(model,scenarioPropertiesFromServer);
if (callback){
callback()
}
})
},
/**
* Need a business logic class to loop through?
* Business logic for these?
* @returns {*} array of all notifications
*
* TODO: this is just for the charts to call? however its sprinkled all over modelChart
* // there is a watcher on store.js that i believe handles the actual scenario notification...
*/
getNotifications: function(){
var notifications = [],
scenario = this.get('active'),
exceededDriverLimit = PlotUtils.hasExceededDriverLimit();
// TODO: need to have something that loops through all the potential business rules
// and generates the notifications aray from it..
if (exceededDriverLimit){
notifications.push(SCENARIO_NOTIFICATIONS_MSGS['exceededDriverLimitCHARTMSG'](PlotUtils.getDriverLimit()))
}
return notifications;
},
getParentChildrenManager: function(){
return this.get('parentChildrenManager');
},
hasConsensusModels: function() {
return this.getConsensusModels().length > 0
},
getConsensusModels: function(models) {
return (models || this.get('models')).filter(function(model) { return model.isConsensusModel() })
},
getStore: function() {
return store
}
});
;;
// lib/jquery.min.js
// @include infrastructure/chartWrapper.js
// @include infrastructure/driverFilmstrip.js
// @include infrastructure/forecastModel.js
// @include infrastructure/modelManager.js
ChartUtil = {
/**
* @param {Object} comments an array of comments objects
* @param {Object} companyData hash from symbol to data for company on which to base charts
* @param {String} divIdPrefix divIdPrefix + postId should be the id of the div in which the chart should go
* @param {Object} tracker a tracker for filmstrip events.
*/
addCommentCharts: function(comments, companyDatas, divIdPrefix, tracker) {
$.each(comments, function(i,x) {
if (x.model && companyDatas[x.symbol] && $("#"+divIdPrefix + x.postId +" .cts_filmstrip").length == 0) {
var modelManager = new ModelManager(),
chartWrapper = new ChartWrapper(divIdPrefix + x.postId, modelManager, tracker),
companyData = companyDatas[x.symbol],
forecastModel = ForecastModel.createComparisonModel(false, null, companyData, x.model),
filmstrip = new DriverFilmstrip(chartWrapper, tracker, "cts_filmstrip")
;
chartWrapper.setEditable(false);
chartWrapper.mainChart.verboseMouseover = false;
modelManager.setCompany(companyData, 0);
filmstrip.addListener(function(driver) {
chartWrapper.setData(companyData, null, driver, null, [x.model.creatorFirstName], null,
filmstrip.getDrivers().length >1 ?
(filmstrip.getPosition()+1) + " of " + filmstrip.getDrivers().length + " - "
:undefined);
});
filmstrip.setDrivers(forecastModel.modifiedDriverList());
}
});
},
};
;;
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.DeepDiff = factory());
}(this, (function () { 'use strict';
var $scope;
var conflict;
var conflictResolution = [];
if (typeof global === 'object' && global) {
$scope = global;
} else if (typeof window !== 'undefined') {
$scope = window;
} else {
$scope = {};
}
conflict = $scope.DeepDiff;
if (conflict) {
conflictResolution.push(
function() {
if ('undefined' !== typeof conflict && $scope.DeepDiff === accumulateDiff) {
$scope.DeepDiff = conflict;
conflict = undefined;
}
});
}
// nodejs compatible on server side and in the browser.
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
function Diff(kind, path) {
Object.defineProperty(this, 'kind', {
value: kind,
enumerable: true
});
if (path && path.length) {
Object.defineProperty(this, 'path', {
value: path,
enumerable: true
});
}
}
function DiffEdit(path, origin, value) {
DiffEdit.super_.call(this, 'E', path);
Object.defineProperty(this, 'lhs', {
value: origin,
enumerable: true
});
Object.defineProperty(this, 'rhs', {
value: value,
enumerable: true
});
}
inherits(DiffEdit, Diff);
function DiffNew(path, value) {
DiffNew.super_.call(this, 'N', path);
Object.defineProperty(this, 'rhs', {
value: value,
enumerable: true
});
}
inherits(DiffNew, Diff);
function DiffDeleted(path, value) {
DiffDeleted.super_.call(this, 'D', path);
Object.defineProperty(this, 'lhs', {
value: value,
enumerable: true
});
}
inherits(DiffDeleted, Diff);
function DiffArray(path, index, item) {
DiffArray.super_.call(this, 'A', path);
Object.defineProperty(this, 'index', {
value: index,
enumerable: true
});
Object.defineProperty(this, 'item', {
value: item,
enumerable: true
});
}
inherits(DiffArray, Diff);
function arrayRemove(arr, from, to) {
var rest = arr.slice((to || from) + 1 || arr.length);
arr.length = from < 0 ? arr.length + from : from;
arr.push.apply(arr, rest);
return arr;
}
function realTypeOf(subject) {
var type = typeof subject;
if (type !== 'object') {
return type;
}
if (subject === Math) {
return 'math';
} else if (subject === null) {
return 'null';
} else if (Array.isArray(subject)) {
return 'array';
} else if (Object.prototype.toString.call(subject) === '[object Date]') {
return 'date';
} else if (typeof subject.toString === 'function' && /^\/.*\//.test(subject.toString())) {
return 'regexp';
}
return 'object';
}
function deepDiff(lhs, rhs, changes, prefilter, path, key, stack) {
path = path || [];
var currentPath = path.slice(0);
if (typeof key !== 'undefined') {
if (prefilter) {
if (typeof(prefilter) === 'function' && prefilter(currentPath, key)) { return; }
else if (typeof(prefilter) === 'object') {
if (prefilter.prefilter && prefilter.prefilter(currentPath, key)) { return; }
if (prefilter.normalize) {
var alt = prefilter.normalize(currentPath, key, lhs, rhs);
if (alt) {
lhs = alt[0];
rhs = alt[1];
}
}
}
}
currentPath.push(key);
}
// Use string comparison for regexes
if (realTypeOf(lhs) === 'regexp' && realTypeOf(rhs) === 'regexp') {
lhs = lhs.toString();
rhs = rhs.toString();
}
var ltype = typeof lhs;
var rtype = typeof rhs;
if (ltype === 'undefined') {
if (rtype !== 'undefined') {
changes(new DiffNew(currentPath, rhs));
} else {
changes(new DiffDeleted(currentPath, lhs));
}
} else if (rtype === 'undefined') {
changes(new DiffDeleted(currentPath, lhs));
} else if (realTypeOf(lhs) !== realTypeOf(rhs)) {
changes(new DiffEdit(currentPath, lhs, rhs));
} else if (realTypeOf(lhs) === 'date' && (lhs - rhs) !== 0) {
changes(new DiffEdit(currentPath, lhs, rhs));
} else if (ltype === 'object' && lhs !== null && rhs !== null) {
stack = stack || [];
if (stack.indexOf(lhs) < 0) {
stack.push(lhs);
if (Array.isArray(lhs)) {
var i, len = lhs.length;
for (i = 0; i < lhs.length; i++) {
if (i >= rhs.length) {
changes(new DiffArray(currentPath, i, new DiffDeleted(undefined, lhs[i])));
} else {
deepDiff(lhs[i], rhs[i], changes, prefilter, currentPath, i, stack);
}
}
while (i < rhs.length) {
changes(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i++])));
}
} else {
var akeys = Object.keys(lhs);
var pkeys = Object.keys(rhs);
akeys.forEach(function(k, i) {
var other = pkeys.indexOf(k);
if (other >= 0) {
deepDiff(lhs[k], rhs[k], changes, prefilter, currentPath, k, stack);
pkeys = arrayRemove(pkeys, other);
} else {
deepDiff(lhs[k], undefined, changes, prefilter, currentPath, k, stack);
}
});
pkeys.forEach(function(k) {
deepDiff(undefined, rhs[k], changes, prefilter, currentPath, k, stack);
});
}
stack.length = stack.length - 1;
} else if (lhs !== rhs) {
// lhs is contains a cycle at this element and it differs from rhs
changes(new DiffEdit(currentPath, lhs, rhs));
}
} else if (lhs !== rhs) {
if (!(ltype === 'number' && isNaN(lhs) && isNaN(rhs))) {
changes(new DiffEdit(currentPath, lhs, rhs));
}
}
}
function accumulateDiff(lhs, rhs, prefilter, accum) {
accum = accum || [];
deepDiff(lhs, rhs,
function(diff) {
if (diff) {
accum.push(diff);
}
},
prefilter);
return (accum.length) ? accum : undefined;
}
function applyArrayChange(arr, index, change) {
if (change.path && change.path.length) {
var it = arr[index],
i, u = change.path.length - 1;
for (i = 0; i < u; i++) {
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
applyArrayChange(it[change.path[i]], change.index, change.item);
break;
case 'D':
delete it[change.path[i]];
break;
case 'E':
case 'N':
it[change.path[i]] = change.rhs;
break;
}
} else {
switch (change.kind) {
case 'A':
applyArrayChange(arr[index], change.index, change.item);
break;
case 'D':
arr = arrayRemove(arr, index);
break;
case 'E':
case 'N':
arr[index] = change.rhs;
break;
}
}
return arr;
}
function applyChange(target, source, change) {
if (target && source && change && change.kind) {
var it = target,
i = -1,
last = change.path ? change.path.length - 1 : 0;
while (++i < last) {
if (typeof it[change.path[i]] === 'undefined') {
it[change.path[i]] = (typeof change.path[i] === 'number') ? [] : {};
}
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
applyArrayChange(change.path ? it[change.path[i]] : it, change.index, change.item);
break;
case 'D':
delete it[change.path[i]];
break;
case 'E':
case 'N':
it[change.path[i]] = change.rhs;
break;
}
}
}
function revertArrayChange(arr, index, change) {
if (change.path && change.path.length) {
// the structure of the object at the index has changed...
var it = arr[index],
i, u = change.path.length - 1;
for (i = 0; i < u; i++) {
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
revertArrayChange(it[change.path[i]], change.index, change.item);
break;
case 'D':
it[change.path[i]] = change.lhs;
break;
case 'E':
it[change.path[i]] = change.lhs;
break;
case 'N':
delete it[change.path[i]];
break;
}
} else {
// the array item is different...
switch (change.kind) {
case 'A':
revertArrayChange(arr[index], change.index, change.item);
break;
case 'D':
arr[index] = change.lhs;
break;
case 'E':
arr[index] = change.lhs;
break;
case 'N':
arr = arrayRemove(arr, index);
break;
}
}
return arr;
}
function revertChange(target, source, change) {
if (target && source && change && change.kind) {
var it = target,
i, u;
u = change.path.length - 1;
for (i = 0; i < u; i++) {
if (typeof it[change.path[i]] === 'undefined') {
it[change.path[i]] = {};
}
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
// Array was modified...
// it will be an array...
revertArrayChange(it[change.path[i]], change.index, change.item);
break;
case 'D':
// Item was deleted...
it[change.path[i]] = change.lhs;
break;
case 'E':
// Item was edited...
it[change.path[i]] = change.lhs;
break;
case 'N':
// Item is new...
delete it[change.path[i]];
break;
}
}
}
function applyDiff(target, source, filter) {
if (target && source) {
var onChange = function(change) {
if (!filter || filter(target, source, change)) {
applyChange(target, source, change);
}
};
deepDiff(target, source, onChange);
}
}
Object.defineProperties(accumulateDiff, {
diff: {
value: accumulateDiff,
enumerable: true
},
observableDiff: {
value: deepDiff,
enumerable: true
},
applyDiff: {
value: applyDiff,
enumerable: true
},
applyChange: {
value: applyChange,
enumerable: true
},
revertChange: {
value: revertChange,
enumerable: true
},
isConflict: {
value: function() {
return 'undefined' !== typeof conflict;
},
enumerable: true
},
noConflict: {
value: function() {
if (conflictResolution) {
conflictResolution.forEach(function(it) {
it();
});
conflictResolution = null;
}
return accumulateDiff;
},
enumerable: true
}
});
return accumulateDiff;
})));
;;
/**
* @license
* Lo-Dash 2.4.1 (Custom Build)
* Build: `lodash underscore`
* Copyright 2012-2013 The Dojo Foundation
* Based on Underscore.js 1.5.2
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license
*/
;(function() {
/** Used as a safe reference for `undefined` in pre ES5 environments */
var undefined;
/** Used to generate unique IDs */
var idCounter = 0;
/** Used internally to indicate various things */
var indicatorObject = {};
/** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
var keyPrefix = +new Date + '';
/** Used to match "interpolate" template delimiters */
var reInterpolate = /<%=([\s\S]+?)%>/g;
/** Used to ensure capturing order of template delimiters */
var reNoMatch = /($^)/;
/** Used to match unescaped characters in compiled string literals */
var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
/** `Object#toString` result shortcuts */
var argsClass = '[object Arguments]',
arrayClass = '[object Array]',
boolClass = '[object Boolean]',
dateClass = '[object Date]',
funcClass = '[object Function]',
numberClass = '[object Number]',
objectClass = '[object Object]',
regexpClass = '[object RegExp]',
stringClass = '[object String]';
/** Used to determine if values are of the language type Object */
var objectTypes = {
'boolean': false,
'function': true,
'object': true,
'number': false,
'string': false,
'undefined': false
};
/** Used to escape characters for inclusion in compiled string literals */
var stringEscapes = {
'\\': '\\',
"'": "'",
'\n': 'n',
'\r': 'r',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
/** Used as a reference to the global object */
var root = (objectTypes[typeof window] && window) || this;
/** Detect free variable `exports` */
var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
/** Detect free variable `module` */
var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports` */
var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
/** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
var freeGlobal = objectTypes[typeof global] && global;
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
root = freeGlobal;
}
/*--------------------------------------------------------------------------*/
/**
* The base implementation of `_.indexOf` without support for binary searches
* or `fromIndex` constraints.
*
* @private
* @param {Array} array The array to search.
* @param {*} value The value to search for.
* @param {number} [fromIndex=0] The index to search from.
* @returns {number} Returns the index of the matched value or `-1`.
*/
function baseIndexOf(array, value, fromIndex) {
var index = (fromIndex || 0) - 1,
length = array ? array.length : 0;
while (++index < length) {
if (array[index] === value) {
return index;
}
}
return -1;
}
/**
* Used by `sortBy` to compare transformed `collection` elements, stable sorting
* them in ascending order.
*
* @private
* @param {Object} a The object to compare to `b`.
* @param {Object} b The object to compare to `a`.
* @returns {number} Returns the sort order indicator of `1` or `-1`.
*/
function compareAscending(a, b) {
var ac = a.criteria,
bc = b.criteria,
index = -1,
length = ac.length;
while (++index < length) {
var value = ac[index],
other = bc[index];
if (value !== other) {
if (value > other || typeof value == 'undefined') {
return 1;
}
if (value < other || typeof other == 'undefined') {
return -1;
}
}
}
// Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
// that causes it, under certain circumstances, to return the same value for
// `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
//
// This also ensures a stable sort in V8 and other engines.
// See http://code.google.com/p/v8/issues/detail?id=90
return a.index - b.index;
}
/**
* Used by `template` to escape characters for inclusion in compiled
* string literals.
*
* @private
* @param {string} match The matched character to escape.
* @returns {string} Returns the escaped character.
*/
function escapeStringChar(match) {
return '\\' + stringEscapes[match];
}
/**
* Slices the `collection` from the `start` index up to, but not including,
* the `end` index.
*
* Note: This function is used instead of `Array#slice` to support node lists
* in IE < 9 and to ensure dense arrays are returned.
*
* @private
* @param {Array|Object|string} collection The collection to slice.
* @param {number} start The start index.
* @param {number} end The end index.
* @returns {Array} Returns the new array.
*/
function slice(array, start, end) {
start || (start = 0);
if (typeof end == 'undefined') {
end = array ? array.length : 0;
}
var index = -1,
length = end - start || 0,
result = Array(length < 0 ? 0 : length);
while (++index < length) {
result[index] = array[start + index];
}
return result;
}
/*--------------------------------------------------------------------------*/
/**
* Used for `Array` method references.
*
* Normally `Array.prototype` would suffice, however, using an array literal
* avoids issues in Narwhal.
*/
var arrayRef = [];
/** Used for native method references */
var objectProto = Object.prototype;
/** Used to restore the original `_` reference in `noConflict` */
var oldDash = root._;
/** Used to resolve the internal [[Class]] of values */
var toString = objectProto.toString;
/** Used to detect if a method is native */
var reNative = RegExp('^' +
String(toString)
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/toString| for [^\]]+/g, '.*?') + '$'
);
/** Native method shortcuts */
var ceil = Math.ceil,
floor = Math.floor,
hasOwnProperty = objectProto.hasOwnProperty,
push = arrayRef.push,
propertyIsEnumerable = objectProto.propertyIsEnumerable;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
nativeIsFinite = root.isFinite,
nativeIsNaN = root.isNaN,
nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
nativeMax = Math.max,
nativeMin = Math.min,
nativeRandom = Math.random;
/*--------------------------------------------------------------------------*/
/**
* Creates a `lodash` object which wraps the given value to enable intuitive
* method chaining.
*
* In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
* `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
* and `unshift`
*
* Chaining is supported in custom builds as long as the `value` method is
* implicitly or explicitly included in the build.
*
* The chainable wrapper functions are:
* `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
* `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
* `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
* `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
* `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
* `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
* `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
* `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
* `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
* `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
* and `zip`
*
* The non-chainable wrapper functions are:
* `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
* `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
* `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
* `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
* `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
* `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
* `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
* `template`, `unescape`, `uniqueId`, and `value`
*
* The wrapper functions `first` and `last` return wrapped values when `n` is
* provided, otherwise they return unwrapped values.
*
* Explicit chaining can be enabled by using the `_.chain` method.
*
* @name _
* @constructor
* @category Chaining
* @param {*} value The value to wrap in a `lodash` instance.
* @returns {Object} Returns a `lodash` instance.
* @example
*
* var wrapped = _([1, 2, 3]);
*
* // returns an unwrapped value
* wrapped.reduce(function(sum, num) {
* return sum + num;
* });
* // => 6
*
* // returns a wrapped value
* var squares = wrapped.map(function(num) {
* return num * num;
* });
*
* _.isArray(squares);
* // => false
*
* _.isArray(squares.value());
* // => true
*/
function lodash(value) {
return (value instanceof lodash)
? value
: new lodashWrapper(value);
}
/**
* A fast path for creating `lodash` wrapper objects.
*
* @private
* @param {*} value The value to wrap in a `lodash` instance.
* @param {boolean} chainAll A flag to enable chaining for all methods
* @returns {Object} Returns a `lodash` instance.
*/
function lodashWrapper(value, chainAll) {
this.__chain__ = !!chainAll;
this.__wrapped__ = value;
}
// ensure `new lodashWrapper` is an instance of `lodash`
lodashWrapper.prototype = lodash.prototype;
/**
* An object used to flag environments features.
*
* @static
* @memberOf _
* @type Object
*/
var support = {};
(function() {
var object = { '0': 1, 'length': 1 };
/**
* Detect if `Array#shift` and `Array#splice` augment array-like objects correctly.
*
* Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
* and `splice()` functions that fail to remove the last element, `value[0]`,
* of array-like objects even though the `length` property is set to `0`.
* The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
* is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
*
* @memberOf _.support
* @type boolean
*/
support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);
}(1));
/**
* By default, the template delimiters used by Lo-Dash are similar to those in
* embedded Ruby (ERB). Change the following template settings to use alternative
* delimiters.
*
* @static
* @memberOf _
* @type Object
*/
lodash.templateSettings = {
/**
* Used to detect `data` property values to be HTML-escaped.
*
* @memberOf _.templateSettings
* @type RegExp
*/
'escape': /<%-([\s\S]+?)%>/g,
/**
* Used to detect code to be evaluated.
*
* @memberOf _.templateSettings
* @type RegExp
*/
'evaluate': /<%([\s\S]+?)%>/g,
/**
* Used to detect `data` property values to inject.
*
* @memberOf _.templateSettings
* @type RegExp
*/
'interpolate': reInterpolate,
/**
* Used to reference the data object in the template text.
*
* @memberOf _.templateSettings
* @type string
*/
'variable': ''
};
/*--------------------------------------------------------------------------*/
/**
* The base implementation of `_.bind` that creates the bound function and
* sets its meta data.
*
* @private
* @param {Array} bindData The bind data array.
* @returns {Function} Returns the new bound function.
*/
function baseBind(bindData) {
var func = bindData[0],
partialArgs = bindData[2],
thisArg = bindData[4];
function bound() {
// `Function#bind` spec
// http://es5.github.io/#x15.3.4.5
if (partialArgs) {
// avoid `arguments` object deoptimizations by using `slice` instead
// of `Array.prototype.slice.call` and not assigning `arguments` to a
// variable as a ternary expression
var args = slice(partialArgs);
push.apply(args, arguments);
}
// mimic the constructor's `return` behavior
// http://es5.github.io/#x13.2.2
if (this instanceof bound) {
// ensure `new bound` is an instance of `func`
var thisBinding = baseCreate(func.prototype),
result = func.apply(thisBinding, args || arguments);
return isObject(result) ? result : thisBinding;
}
return func.apply(thisArg, args || arguments);
}
return bound;
}
/**
* The base implementation of `_.create` without support for assigning
* properties to the created object.
*
* @private
* @param {Object} prototype The object to inherit from.
* @returns {Object} Returns the new object.
*/
function baseCreate(prototype, properties) {
return isObject(prototype) ? nativeCreate(prototype) : {};
}
// fallback for browsers without `Object.create`
if (!nativeCreate) {
baseCreate = (function() {
function Object() {}
return function(prototype) {
if (isObject(prototype)) {
Object.prototype = prototype;
var result = new Object;
Object.prototype = null;
}
return result || root.Object();
};
}());
}
/**
* The base implementation of `_.createCallback` without support for creating
* "_.pluck" or "_.where" style callbacks.
*
* @private
* @param {*} [func=identity] The value to convert to a callback.
* @param {*} [thisArg] The `this` binding of the created callback.
* @param {number} [argCount] The number of arguments the callback accepts.
* @returns {Function} Returns a callback function.
*/
function baseCreateCallback(func, thisArg, argCount) {
if (typeof func != 'function') {
return identity;
}
// exit early for no `thisArg` or already bound by `Function#bind`
if (typeof thisArg == 'undefined' || !('prototype' in func)) {
return func;
}
switch (argCount) {
case 1: return function(value) {
return func.call(thisArg, value);
};
case 2: return function(a, b) {
return func.call(thisArg, a, b);
};
case 3: return function(value, index, collection) {
return func.call(thisArg, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(thisArg, accumulator, value, index, collection);
};
}
return bind(func, thisArg);
}
/**
* The base implementation of `createWrapper` that creates the wrapper and
* sets its meta data.
*
* @private
* @param {Array} bindData The bind data array.
* @returns {Function} Returns the new function.
*/
function baseCreateWrapper(bindData) {
var func = bindData[0],
bitmask = bindData[1],
partialArgs = bindData[2],
partialRightArgs = bindData[3],
thisArg = bindData[4],
arity = bindData[5];
var isBind = bitmask & 1,
isBindKey = bitmask & 2,
isCurry = bitmask & 4,
isCurryBound = bitmask & 8,
key = func;
function bound() {
var thisBinding = isBind ? thisArg : this;
if (partialArgs) {
var args = slice(partialArgs);
push.apply(args, arguments);
}
if (partialRightArgs || isCurry) {
args || (args = slice(arguments));
if (partialRightArgs) {
push.apply(args, partialRightArgs);
}
if (isCurry && args.length < arity) {
bitmask |= 16 & ~32;
return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
}
}
args || (args = arguments);
if (isBindKey) {
func = thisBinding[key];
}
if (this instanceof bound) {
thisBinding = baseCreate(func.prototype);
var result = func.apply(thisBinding, args);
return isObject(result) ? result : thisBinding;
}
return func.apply(thisBinding, args);
}
return bound;
}
/**
* The base implementation of `_.difference` that accepts a single array
* of values to exclude.
*
* @private
* @param {Array} array The array to process.
* @param {Array} [values] The array of values to exclude.
* @returns {Array} Returns a new array of filtered values.
*/
function baseDifference(array, values) {
var index = -1,
indexOf = getIndexOf(),
length = array ? array.length : 0,
result = [];
while (++index < length) {
var value = array[index];
if (indexOf(values, value) < 0) {
result.push(value);
}
}
return result;
}
/**
* The base implementation of `_.flatten` without support for callback
* shorthands or `thisArg` binding.
*
* @private
* @param {Array} array The array to flatten.
* @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
* @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
* @param {number} [fromIndex=0] The index to start from.
* @returns {Array} Returns a new flattened array.
*/
function baseFlatten(array, isShallow, isStrict, fromIndex) {
var index = (fromIndex || 0) - 1,
length = array ? array.length : 0,
result = [];
while (++index < length) {
var value = array[index];
if (value && typeof value == 'object' && typeof value.length == 'number'
&& (isArray(value) || isArguments(value))) {
// recursively flatten arrays (susceptible to call stack limits)
if (!isShallow) {
value = baseFlatten(value, isShallow, isStrict);
}
var valIndex = -1,
valLength = value.length,
resIndex = result.length;
result.length += valLength;
while (++valIndex < valLength) {
result[resIndex++] = value[valIndex];
}
} else if (!isStrict) {
result.push(value);
}
}
return result;
}
/**
* The base implementation of `_.isEqual`, without support for `thisArg` binding,
* that allows partial "_.where" style comparisons.
*
* @private
* @param {*} a The value to compare.
* @param {*} b The other value to compare.
* @param {Function} [callback] The function to customize comparing values.
* @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
* @param {Array} [stackA=[]] Tracks traversed `a` objects.
* @param {Array} [stackB=[]] Tracks traversed `b` objects.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
*/
function baseIsEqual(a, b, stackA, stackB) {
if (a === b) {
return a !== 0 || (1 / a == 1 / b);
}
var type = typeof a,
otherType = typeof b;
if (a === a &&
!(a && objectTypes[type]) &&
!(b && objectTypes[otherType])) {
return false;
}
if (a == null || b == null) {
return a === b;
}
var className = toString.call(a),
otherClass = toString.call(b);
if (className != otherClass) {
return false;
}
switch (className) {
case boolClass:
case dateClass:
return +a == +b;
case numberClass:
return a != +a
? b != +b
: (a == 0 ? (1 / a == 1 / b) : a == +b);
case regexpClass:
case stringClass:
return a == String(b);
}
var isArr = className == arrayClass;
if (!isArr) {
var aWrapped = a instanceof lodash,
bWrapped = b instanceof lodash;
if (aWrapped || bWrapped) {
return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, stackA, stackB);
}
if (className != objectClass) {
return false;
}
var ctorA = a.constructor,
ctorB = b.constructor;
if (ctorA != ctorB &&
!(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
('constructor' in a && 'constructor' in b)
) {
return false;
}
}
stackA || (stackA = []);
stackB || (stackB = []);
var length = stackA.length;
while (length--) {
if (stackA[length] == a) {
return stackB[length] == b;
}
}
var result = true,
size = 0;
stackA.push(a);
stackB.push(b);
if (isArr) {
size = b.length;
result = size == a.length;
if (result) {
while (size--) {
if (!(result = baseIsEqual(a[size], b[size], stackA, stackB))) {
break;
}
}
}
}
else {
forIn(b, function(value, key, b) {
if (hasOwnProperty.call(b, key)) {
size++;
return !(result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, stackA, stackB)) && indicatorObject;
}
});
if (result) {
forIn(a, function(value, key, a) {
if (hasOwnProperty.call(a, key)) {
return !(result = --size > -1) && indicatorObject;
}
});
}
}
stackA.pop();
stackB.pop();
return result;
}
/**
* The base implementation of `_.random` without argument juggling or support
* for returning floating-point numbers.
*
* @private
* @param {number} min The minimum possible value.
* @param {number} max The maximum possible value.
* @returns {number} Returns a random number.
*/
function baseRandom(min, max) {
return min + floor(nativeRandom() * (max - min + 1));
}
/**
* The base implementation of `_.uniq` without support for callback shorthands
* or `thisArg` binding.
*
* @private
* @param {Array} array The array to process.
* @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
* @param {Function} [callback] The function called per iteration.
* @returns {Array} Returns a duplicate-value-free array.
*/
function baseUniq(array, isSorted, callback) {
var index = -1,
indexOf = getIndexOf(),
length = array ? array.length : 0,
result = [],
seen = callback ? [] : result;
while (++index < length) {
var value = array[index],
computed = callback ? callback(value, index, array) : value;
if (isSorted
? !index || seen[seen.length - 1] !== computed
: indexOf(seen, computed) < 0
) {
if (callback) {
seen.push(computed);
}
result.push(value);
}
}
return result;
}
/**
* Creates a function that aggregates a collection, creating an object composed
* of keys generated from the results of running each element of the collection
* through a callback. The given `setter` function sets the keys and values
* of the composed object.
*
* @private
* @param {Function} setter The setter function.
* @returns {Function} Returns the new aggregator function.
*/
function createAggregator(setter) {
return function(collection, callback, thisArg) {
var result = {};
callback = createCallback(callback, thisArg, 3);
var index = -1,
length = collection ? collection.length : 0;
if (typeof length == 'number') {
while (++index < length) {
var value = collection[index];
setter(result, value, callback(value, index, collection), collection);
}
} else {
forOwn(collection, function(value, key, collection) {
setter(result, value, callback(value, key, collection), collection);
});
}
return result;
};
}
/**
* Creates a function that, when called, either curries or invokes `func`
* with an optional `this` binding and partially applied arguments.
*
* @private
* @param {Function|string} func The function or method name to reference.
* @param {number} bitmask The bitmask of method flags to compose.
* The bitmask may be composed of the following flags:
* 1 - `_.bind`
* 2 - `_.bindKey`
* 4 - `_.curry`
* 8 - `_.curry` (bound)
* 16 - `_.partial`
* 32 - `_.partialRight`
* @param {Array} [partialArgs] An array of arguments to prepend to those
* provided to the new function.
* @param {Array} [partialRightArgs] An array of arguments to append to those
* provided to the new function.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {number} [arity] The arity of `func`.
* @returns {Function} Returns the new function.
*/
function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
var isBind = bitmask & 1,
isBindKey = bitmask & 2,
isCurry = bitmask & 4,
isCurryBound = bitmask & 8,
isPartial = bitmask & 16,
isPartialRight = bitmask & 32;
if (!isBindKey && !isFunction(func)) {
throw new TypeError;
}
if (isPartial && !partialArgs.length) {
bitmask &= ~16;
isPartial = partialArgs = false;
}
if (isPartialRight && !partialRightArgs.length) {
bitmask &= ~32;
isPartialRight = partialRightArgs = false;
}
// fast path for `_.bind`
var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
}
/**
* Used by `escape` to convert characters to HTML entities.
*
* @private
* @param {string} match The matched character to escape.
* @returns {string} Returns the escaped character.
*/
function escapeHtmlChar(match) {
return htmlEscapes[match];
}
/**
* Gets the appropriate "indexOf" function. If the `_.indexOf` method is
* customized, this method returns the custom method, otherwise it returns
* the `baseIndexOf` function.
*
* @private
* @returns {Function} Returns the "indexOf" function.
*/
function getIndexOf() {
var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
return result;
}
/**
* Checks if `value` is a native function.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
*/
function isNative(value) {
return typeof value == 'function' && reNative.test(value);
}
/**
* Used by `unescape` to convert HTML entities to characters.
*
* @private
* @param {string} match The matched character to unescape.
* @returns {string} Returns the unescaped character.
*/
function unescapeHtmlChar(match) {
return htmlUnescapes[match];
}
/*--------------------------------------------------------------------------*/
/**
* Checks if `value` is an `arguments` object.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
* @example
*
* (function() { return _.isArguments(arguments); })(1, 2, 3);
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/
function isArguments(value) {
return value && typeof value == 'object' && typeof value.length == 'number' &&
toString.call(value) == argsClass || false;
}
// fallback for browsers that can't detect `arguments` objects by [[Class]]
if (!isArguments(arguments)) {
isArguments = function(value) {
return value && typeof value == 'object' && typeof value.length == 'number' &&
hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false;
};
}
/**
* Checks if `value` is an array.
*
* @static
* @memberOf _
* @type Function
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is an array, else `false`.
* @example
*
* (function() { return _.isArray(arguments); })();
* // => false
*
* _.isArray([1, 2, 3]);
* // => true
*/
var isArray = nativeIsArray || function(value) {
return value && typeof value == 'object' && typeof value.length == 'number' &&
toString.call(value) == arrayClass || false;
};
/**
* A fallback implementation of `Object.keys` which produces an array of the
* given object's own enumerable property names.
*
* @private
* @type Function
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property names.
*/
var shimKeys = function(object) {
var index, iterable = object, result = [];
if (!iterable) return result;
if (!(objectTypes[typeof object])) return result;
for (index in iterable) {
if (hasOwnProperty.call(iterable, index)) {
result.push(index);
}
}
return result
};
/**
* Creates an array composed of the own enumerable property names of an object.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property names.
* @example
*
* _.keys({ 'one': 1, 'two': 2, 'three': 3 });
* // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
*/
var keys = !nativeKeys ? shimKeys : function(object) {
if (!isObject(object)) {
return [];
}
return nativeKeys(object);
};
/**
* Used to convert characters to HTML entities:
*
* Though the `>` character is escaped for symmetry, characters like `>` and `/`
* don't require escaping in HTML and have no special meaning unless they're part
* of a tag or an unquoted attribute value.
* http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
*/
var htmlEscapes = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
/** Used to convert HTML entities to characters */
var htmlUnescapes = invert(htmlEscapes);
/** Used to match HTML entities and HTML characters */
var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
/*--------------------------------------------------------------------------*/
/**
* Assigns own enumerable properties of source object(s) to the destination
* object. Subsequent sources will overwrite property assignments of previous
* sources. If a callback is provided it will be executed to produce the
* assigned values. The callback is bound to `thisArg` and invoked with two
* arguments; (objectValue, sourceValue).
*
* @static
* @memberOf _
* @type Function
* @alias extend
* @category Objects
* @param {Object} object The destination object.
* @param {...Object} [source] The source objects.
* @param {Function} [callback] The function to customize assigning values.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns the destination object.
* @example
*
* _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
* // => { 'name': 'fred', 'employer': 'slate' }
*
* var defaults = _.partialRight(_.assign, function(a, b) {
* return typeof a == 'undefined' ? b : a;
* });
*
* var object = { 'name': 'barney' };
* defaults(object, { 'name': 'fred', 'employer': 'slate' });
* // => { 'name': 'barney', 'employer': 'slate' }
*/
function assign(object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
object[key] = iterable[key];
}
}
}
return object;
}
/**
* Creates a clone of `value`. If `isDeep` is `true` nested objects will also
* be cloned, otherwise they will be assigned by reference. If a callback
* is provided it will be executed to produce the cloned values. If the
* callback returns `undefined` cloning will be handled by the method instead.
* The callback is bound to `thisArg` and invoked with one argument; (value).
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to clone.
* @param {boolean} [isDeep=false] Specify a deep clone.
* @param {Function} [callback] The function to customize cloning values.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the cloned value.
* @example
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* var shallow = _.clone(characters);
* shallow[0] === characters[0];
* // => true
*
* var deep = _.clone(characters, true);
* deep[0] === characters[0];
* // => false
*
* _.mixin({
* 'clone': _.partialRight(_.clone, function(value) {
* return _.isElement(value) ? value.cloneNode(false) : undefined;
* })
* });
*
* var clone = _.clone(document.body);
* clone.childNodes.length;
* // => 0
*/
function clone(value) {
return isObject(value)
? (isArray(value) ? slice(value) : assign({}, value))
: value;
}
/**
* Assigns own enumerable properties of source object(s) to the destination
* object for all destination properties that resolve to `undefined`. Once a
* property is set, additional defaults of the same property will be ignored.
*
* @static
* @memberOf _
* @type Function
* @category Objects
* @param {Object} object The destination object.
* @param {...Object} [source] The source objects.
* @param- {Object} [guard] Allows working with `_.reduce` without using its
* `key` and `object` arguments as sources.
* @returns {Object} Returns the destination object.
* @example
*
* var object = { 'name': 'barney' };
* _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
* // => { 'name': 'barney', 'employer': 'slate' }
*/
function defaults(object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
if (typeof object[key] == 'undefined') {
object[key] = iterable[key];
}
}
}
}
return object;
}
/**
* Iterates over own and inherited enumerable properties of an object,
* executing the callback for each property. The callback is bound to `thisArg`
* and invoked with three arguments; (value, key, object). Callbacks may exit
* iteration early by explicitly returning `false`.
*
* @static
* @memberOf _
* @type Function
* @category Objects
* @param {Object} object The object to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns `object`.
* @example
*
* function Shape() {
* this.x = 0;
* this.y = 0;
* }
*
* Shape.prototype.move = function(x, y) {
* this.x += x;
* this.y += y;
* };
*
* _.forIn(new Shape, function(value, key) {
* console.log(key);
* });
* // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
*/
var forIn = function(collection, callback) {
var index, iterable = collection, result = iterable;
if (!iterable) return result;
if (!objectTypes[typeof iterable]) return result;
for (index in iterable) {
if (callback(iterable[index], index, collection) === indicatorObject) return result;
}
return result
};
/**
* Iterates over own enumerable properties of an object, executing the callback
* for each property. The callback is bound to `thisArg` and invoked with three
* arguments; (value, key, object). Callbacks may exit iteration early by
* explicitly returning `false`.
*
* @static
* @memberOf _
* @type Function
* @category Objects
* @param {Object} object The object to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns `object`.
* @example
*
* _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
* console.log(key);
* });
* // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
*/
var forOwn = function(collection, callback) {
var index, iterable = collection, result = iterable;
if (!iterable) return result;
if (!objectTypes[typeof iterable]) return result;
for (index in iterable) {
if (hasOwnProperty.call(iterable, index)) {
if (callback(iterable[index], index, collection) === indicatorObject) return result;
}
}
return result
};
/**
* Creates a sorted array of property names of all enumerable properties,
* own and inherited, of `object` that have function values.
*
* @static
* @memberOf _
* @alias methods
* @category Objects
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property names that have function values.
* @example
*
* _.functions(_);
* // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
*/
function functions(object) {
var result = [];
forIn(object, function(value, key) {
if (isFunction(value)) {
result.push(key);
}
});
return result.sort();
}
/**
* Checks if the specified property name exists as a direct property of `object`,
* instead of an inherited property.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to inspect.
* @param {string} key The name of the property to check.
* @returns {boolean} Returns `true` if key is a direct property, else `false`.
* @example
*
* _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
* // => true
*/
function has(object, key) {
return object ? hasOwnProperty.call(object, key) : false;
}
/**
* Creates an object composed of the inverted keys and values of the given object.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to invert.
* @returns {Object} Returns the created inverted object.
* @example
*
* _.invert({ 'first': 'fred', 'second': 'barney' });
* // => { 'fred': 'first', 'barney': 'second' }
*/
function invert(object) {
var index = -1,
props = keys(object),
length = props.length,
result = {};
while (++index < length) {
var key = props[index];
result[object[key]] = key;
}
return result;
}
/**
* Checks if `value` is a boolean value.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
* @example
*
* _.isBoolean(null);
* // => false
*/
function isBoolean(value) {
return value === true || value === false ||
value && typeof value == 'object' && toString.call(value) == boolClass || false;
}
/**
* Checks if `value` is a date.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a date, else `false`.
* @example
*
* _.isDate(new Date);
* // => true
*/
function isDate(value) {
return value && typeof value == 'object' && toString.call(value) == dateClass || false;
}
/**
* Checks if `value` is a DOM element.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
* @example
*
* _.isElement(document.body);
* // => true
*/
function isElement(value) {
return value && value.nodeType === 1 || false;
}
/**
* Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
* length of `0` and objects with no own enumerable properties are considered
* "empty".
*
* @static
* @memberOf _
* @category Objects
* @param {Array|Object|string} value The value to inspect.
* @returns {boolean} Returns `true` if the `value` is empty, else `false`.
* @example
*
* _.isEmpty([1, 2, 3]);
* // => false
*
* _.isEmpty({});
* // => true
*
* _.isEmpty('');
* // => true
*/
function isEmpty(value) {
if (!value) {
return true;
}
if (isArray(value) || isString(value)) {
return !value.length;
}
for (var key in value) {
if (hasOwnProperty.call(value, key)) {
return false;
}
}
return true;
}
/**
* Performs a deep comparison between two values to determine if they are
* equivalent to each other. If a callback is provided it will be executed
* to compare values. If the callback returns `undefined` comparisons will
* be handled by the method instead. The callback is bound to `thisArg` and
* invoked with two arguments; (a, b).
*
* @static
* @memberOf _
* @category Objects
* @param {*} a The value to compare.
* @param {*} b The other value to compare.
* @param {Function} [callback] The function to customize comparing values.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'name': 'fred' };
* var copy = { 'name': 'fred' };
*
* object == copy;
* // => false
*
* _.isEqual(object, copy);
* // => true
*
* var words = ['hello', 'goodbye'];
* var otherWords = ['hi', 'goodbye'];
*
* _.isEqual(words, otherWords, function(a, b) {
* var reGreet = /^(?:hello|hi)$/i,
* aGreet = _.isString(a) && reGreet.test(a),
* bGreet = _.isString(b) && reGreet.test(b);
*
* return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
* });
* // => true
*/
function isEqual(a, b) {
return baseIsEqual(a, b);
}
/**
* Checks if `value` is, or can be coerced to, a finite number.
*
* Note: This is not the same as native `isFinite` which will return true for
* booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is finite, else `false`.
* @example
*
* _.isFinite(-101);
* // => true
*
* _.isFinite('10');
* // => true
*
* _.isFinite(true);
* // => false
*
* _.isFinite('');
* // => false
*
* _.isFinite(Infinity);
* // => false
*/
function isFinite(value) {
return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
}
/**
* Checks if `value` is a function.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*/
function isFunction(value) {
return typeof value == 'function';
}
// fallback for older versions of Chrome and Safari
if (isFunction(/x/)) {
isFunction = function(value) {
return typeof value == 'function' && toString.call(value) == funcClass;
};
}
/**
* Checks if `value` is the language type of Object.
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(1);
* // => false
*/
function isObject(value) {
// check if the value is the ECMAScript language type of Object
// http://es5.github.io/#x8
// and avoid a V8 bug
// http://code.google.com/p/v8/issues/detail?id=2291
return !!(value && objectTypes[typeof value]);
}
/**
* Checks if `value` is `NaN`.
*
* Note: This is not the same as native `isNaN` which will return `true` for
* `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
* @example
*
* _.isNaN(NaN);
* // => true
*
* _.isNaN(new Number(NaN));
* // => true
*
* isNaN(undefined);
* // => true
*
* _.isNaN(undefined);
* // => false
*/
function isNaN(value) {
// `NaN` as a primitive is the only value that is not equal to itself
// (perform the [[Class]] check first to avoid errors with some host objects in IE)
return isNumber(value) && value != +value;
}
/**
* Checks if `value` is `null`.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
* @example
*
* _.isNull(null);
* // => true
*
* _.isNull(undefined);
* // => false
*/
function isNull(value) {
return value === null;
}
/**
* Checks if `value` is a number.
*
* Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a number, else `false`.
* @example
*
* _.isNumber(8.4 * 5);
* // => true
*/
function isNumber(value) {
return typeof value == 'number' ||
value && typeof value == 'object' && toString.call(value) == numberClass || false;
}
/**
* Checks if `value` is a regular expression.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
* @example
*
* _.isRegExp(/fred/);
* // => true
*/
function isRegExp(value) {
return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false;
}
/**
* Checks if `value` is a string.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a string, else `false`.
* @example
*
* _.isString('fred');
* // => true
*/
function isString(value) {
return typeof value == 'string' ||
value && typeof value == 'object' && toString.call(value) == stringClass || false;
}
/**
* Checks if `value` is `undefined`.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
* @example
*
* _.isUndefined(void 0);
* // => true
*/
function isUndefined(value) {
return typeof value == 'undefined';
}
/**
* Creates a shallow clone of `object` excluding the specified properties.
* Property names may be specified as individual arguments or as arrays of
* property names. If a callback is provided it will be executed for each
* property of `object` omitting the properties the callback returns truey
* for. The callback is bound to `thisArg` and invoked with three arguments;
* (value, key, object).
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The source object.
* @param {Function|...string|string[]} [callback] The properties to omit or the
* function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns an object without the omitted properties.
* @example
*
* _.omit({ 'name': 'fred', 'age': 40 }, 'age');
* // => { 'name': 'fred' }
*
* _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
* return typeof value == 'number';
* });
* // => { 'name': 'fred' }
*/
function omit(object) {
var props = [];
forIn(object, function(value, key) {
props.push(key);
});
props = baseDifference(props, baseFlatten(arguments, true, false, 1));
var index = -1,
length = props.length,
result = {};
while (++index < length) {
var key = props[index];
result[key] = object[key];
}
return result;
}
/**
* Creates a two dimensional array of an object's key-value pairs,
* i.e. `[[key1, value1], [key2, value2]]`.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to inspect.
* @returns {Array} Returns new array of key-value pairs.
* @example
*
* _.pairs({ 'barney': 36, 'fred': 40 });
* // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
*/
function pairs(object) {
var index = -1,
props = keys(object),
length = props.length,
result = Array(length);
while (++index < length) {
var key = props[index];
result[index] = [key, object[key]];
}
return result;
}
/**
* Creates a shallow clone of `object` composed of the specified properties.
* Property names may be specified as individual arguments or as arrays of
* property names. If a callback is provided it will be executed for each
* property of `object` picking the properties the callback returns truey
* for. The callback is bound to `thisArg` and invoked with three arguments;
* (value, key, object).
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The source object.
* @param {Function|...string|string[]} [callback] The function called per
* iteration or property names to pick, specified as individual property
* names or arrays of property names.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns an object composed of the picked properties.
* @example
*
* _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
* // => { 'name': 'fred' }
*
* _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
* return key.charAt(0) != '_';
* });
* // => { 'name': 'fred' }
*/
function pick(object) {
var index = -1,
props = baseFlatten(arguments, true, false, 1),
length = props.length,
result = {};
while (++index < length) {
var key = props[index];
if (key in object) {
result[key] = object[key];
}
}
return result;
}
/**
* Creates an array composed of the own enumerable property values of `object`.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property values.
* @example
*
* _.values({ 'one': 1, 'two': 2, 'three': 3 });
* // => [1, 2, 3] (property order is not guaranteed across environments)
*/
function values(object) {
var index = -1,
props = keys(object),
length = props.length,
result = Array(length);
while (++index < length) {
result[index] = object[props[index]];
}
return result;
}
/*--------------------------------------------------------------------------*/
/**
* Checks if a given value is present in a collection using strict equality
* for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
* offset from the end of the collection.
*
* @static
* @memberOf _
* @alias include
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {*} target The value to check for.
* @param {number} [fromIndex=0] The index to search from.
* @returns {boolean} Returns `true` if the `target` element is found, else `false`.
* @example
*
* _.contains([1, 2, 3], 1);
* // => true
*
* _.contains([1, 2, 3], 1, 2);
* // => false
*
* _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
* // => true
*
* _.contains('pebbles', 'eb');
* // => true
*/
function contains(collection, target) {
var indexOf = getIndexOf(),
length = collection ? collection.length : 0,
result = false;
if (length && typeof length == 'number') {
result = indexOf(collection, target) > -1;
} else {
forOwn(collection, function(value) {
return (result = value === target) && indicatorObject;
});
}
return result;
}
/**
* Creates an object composed of keys generated from the results of running
* each element of `collection` through the callback. The corresponding value
* of each key is the number of times the key was returned by the callback.
* The callback is bound to `thisArg` and invoked with three arguments;
* (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns the composed aggregate object.
* @example
*
* _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
* // => { '4': 1, '6': 2 }
*
* _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
* // => { '4': 1, '6': 2 }
*
* _.countBy(['one', 'two', 'three'], 'length');
* // => { '3': 2, '5': 1 }
*/
var countBy = createAggregator(function(result, value, key) {
(hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
});
/**
* Checks if the given callback returns truey value for **all** elements of
* a collection. The callback is bound to `thisArg` and invoked with three
* arguments; (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias all
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {boolean} Returns `true` if all elements passed the callback check,
* else `false`.
* @example
*
* _.every([true, 1, null, 'yes']);
* // => false
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* // using "_.pluck" callback shorthand
* _.every(characters, 'age');
* // => true
*
* // using "_.where" callback shorthand
* _.every(characters, { 'age': 36 });
* // => false
*/
function every(collection, callback, thisArg) {
var result = true;
callback = createCallback(callback, thisArg, 3);
var index = -1,
length = collection ? collection.length : 0;
if (typeof length == 'number') {
while (++index < length) {
if (!(result = !!callback(collection[index], index, collection))) {
break;
}
}
} else {
forOwn(collection, function(value, index, collection) {
return !(result = !!callback(value, index, collection)) && indicatorObject;
});
}
return result;
}
/**
* Iterates over elements of a collection, returning an array of all elements
* the callback returns truey for. The callback is bound to `thisArg` and
* invoked with three arguments; (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias select
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a new array of elements that passed the callback check.
* @example
*
* var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
* // => [2, 4, 6]
*
* var characters = [
* { 'name': 'barney', 'age': 36, 'blocked': false },
* { 'name': 'fred', 'age': 40, 'blocked': true }
* ];
*
* // using "_.pluck" callback shorthand
* _.filter(characters, 'blocked');
* // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
*
* // using "_.where" callback shorthand
* _.filter(characters, { 'age': 36 });
* // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
*/
function filter(collection, callback, thisArg) {
var result = [];
callback = createCallback(callback, thisArg, 3);
var index = -1,
length = collection ? collection.length : 0;
if (typeof length == 'number') {
while (++index < length) {
var value = collection[index];
if (callback(value, index, collection)) {
result.push(value);
}
}
} else {
forOwn(collection, function(value, index, collection) {
if (callback(value, index, collection)) {
result.push(value);
}
});
}
return result;
}
/**
* Iterates over elements of a collection, returning the first element that
* the callback returns truey for. The callback is bound to `thisArg` and
* invoked with three arguments; (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias detect, findWhere
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the found element, else `undefined`.
* @example
*
* var characters = [
* { 'name': 'barney', 'age': 36, 'blocked': false },
* { 'name': 'fred', 'age': 40, 'blocked': true },
* { 'name': 'pebbles', 'age': 1, 'blocked': false }
* ];
*
* _.find(characters, function(chr) {
* return chr.age < 40;
* });
* // => { 'name': 'barney', 'age': 36, 'blocked': false }
*
* // using "_.where" callback shorthand
* _.find(characters, { 'age': 1 });
* // => { 'name': 'pebbles', 'age': 1, 'blocked': false }
*
* // using "_.pluck" callback shorthand
* _.find(characters, 'blocked');
* // => { 'name': 'fred', 'age': 40, 'blocked': true }
*/
function find(collection, callback, thisArg) {
callback = createCallback(callback, thisArg, 3);
var index = -1,
length = collection ? collection.length : 0;
if (typeof length == 'number') {
while (++index < length) {
var value = collection[index];
if (callback(value, index, collection)) {
return value;
}
}
} else {
var result;
forOwn(collection, function(value, index, collection) {
if (callback(value, index, collection)) {
result = value;
return indicatorObject;
}
});
return result;
}
}
/**
* Examines each element in a `collection`, returning the first that
* has the given properties. When checking `properties`, this method
* performs a deep comparison between values to determine if they are
* equivalent to each other.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Object} properties The object of property values to filter by.
* @returns {*} Returns the found element, else `undefined`.
* @example
*
* var food = [
* { 'name': 'apple', 'organic': false, 'type': 'fruit' },
* { 'name': 'banana', 'organic': true, 'type': 'fruit' },
* { 'name': 'beet', 'organic': false, 'type': 'vegetable' }
* ];
*
* _.findWhere(food, { 'type': 'vegetable' });
* // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' }
*/
function findWhere(object, properties) {
return where(object, properties, true);
}
/**
* Iterates over elements of a collection, executing the callback for each
* element. The callback is bound to `thisArg` and invoked with three arguments;
* (value, index|key, collection). Callbacks may exit iteration early by
* explicitly returning `false`.
*
* Note: As with other "Collections" methods, objects with a `length` property
* are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
* may be used for object iteration.
*
* @static
* @memberOf _
* @alias each
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array|Object|string} Returns `collection`.
* @example
*
* _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
* // => logs each number and returns '1,2,3'
*
* _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
* // => logs each number and returns the object (property order is not guaranteed across environments)
*/
function forEach(collection, callback, thisArg) {
var index = -1,
length = collection ? collection.length : 0;
callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
if (typeof length == 'number') {
while (++index < length) {
if (callback(collection[index], index, collection) === indicatorObject) {
break;
}
}
} else {
forOwn(collection, callback);
}
}
/**
* This method is like `_.forEach` except that it iterates over elements
* of a `collection` from right to left.
*
* @static
* @memberOf _
* @alias eachRight
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array|Object|string} Returns `collection`.
* @example
*
* _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
* // => logs each number from right to left and returns '3,2,1'
*/
function forEachRight(collection, callback) {
var length = collection ? collection.length : 0;
if (typeof length == 'number') {
while (length--) {
if (callback(collection[length], length, collection) === false) {
break;
}
}
} else {
var props = keys(collection);
length = props.length;
forOwn(collection, function(value, key, collection) {
key = props ? props[--length] : --length;
return callback(collection[key], key, collection) === false && indicatorObject;
});
}
}
/**
* Creates an object composed of keys generated from the results of running
* each element of a collection through the callback. The corresponding value
* of each key is an array of the elements responsible for generating the key.
* The callback is bound to `thisArg` and invoked with three arguments;
* (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns the composed aggregate object.
* @example
*
* _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
* // => { '4': [4.2], '6': [6.1, 6.4] }
*
* _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
* // => { '4': [4.2], '6': [6.1, 6.4] }
*
* // using "_.pluck" callback shorthand
* _.groupBy(['one', 'two', 'three'], 'length');
* // => { '3': ['one', 'two'], '5': ['three'] }
*/
var groupBy = createAggregator(function(result, value, key) {
(hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
});
/**
* Creates an object composed of keys generated from the results of running
* each element of the collection through the given callback. The corresponding
* value of each key is the last element responsible for generating the key.
* The callback is bound to `thisArg` and invoked with three arguments;
* (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns the composed aggregate object.
* @example
*
* var keys = [
* { 'dir': 'left', 'code': 97 },
* { 'dir': 'right', 'code': 100 }
* ];
*
* _.indexBy(keys, 'dir');
* // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
*
* _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
* // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
*
* _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
* // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
*/
var indexBy = createAggregator(function(result, value, key) {
result[key] = value;
});
/**
* Invokes the method named by `methodName` on each element in the `collection`
* returning an array of the results of each invoked method. Additional arguments
* will be provided to each invoked method. If `methodName` is a function it
* will be invoked for, and `this` bound to, each element in the `collection`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|string} methodName The name of the method to invoke or
* the function invoked per iteration.
* @param {...*} [arg] Arguments to invoke the method with.
* @returns {Array} Returns a new array of the results of each invoked method.
* @example
*
* _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
* // => [[1, 5, 7], [1, 2, 3]]
*
* _.invoke([123, 456], String.prototype.split, '');
* // => [['1', '2', '3'], ['4', '5', '6']]
*/
function invoke(collection, methodName) {
var args = slice(arguments, 2),
index = -1,
isFunc = typeof methodName == 'function',
length = collection ? collection.length : 0,
result = Array(typeof length == 'number' ? length : 0);
forEach(collection, function(value) {
result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
});
return result;
}
/**
* Creates an array of values by running each element in the collection
* through the callback. The callback is bound to `thisArg` and invoked with
* three arguments; (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias collect
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a new array of the results of each `callback` execution.
* @example
*
* _.map([1, 2, 3], function(num) { return num * 3; });
* // => [3, 6, 9]
*
* _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
* // => [3, 6, 9] (property order is not guaranteed across environments)
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* // using "_.pluck" callback shorthand
* _.map(characters, 'name');
* // => ['barney', 'fred']
*/
function map(collection, callback, thisArg) {
var index = -1,
length = collection ? collection.length : 0;
callback = createCallback(callback, thisArg, 3);
if (typeof length == 'number') {
var result = Array(length);
while (++index < length) {
result[index] = callback(collection[index], index, collection);
}
} else {
result = [];
forOwn(collection, function(value, key, collection) {
result[++index] = callback(value, key, collection);
});
}
return result;
}
/**
* Retrieves the maximum value of a collection. If the collection is empty or
* falsey `-Infinity` is returned. If a callback is provided it will be executed
* for each value in the collection to generate the criterion by which the value
* is ranked. The callback is bound to `thisArg` and invoked with three
* arguments; (value, index, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the maximum value.
* @example
*
* _.max([4, 2, 8, 6]);
* // => 8
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* _.max(characters, function(chr) { return chr.age; });
* // => { 'name': 'fred', 'age': 40 };
*
* // using "_.pluck" callback shorthand
* _.max(characters, 'age');
* // => { 'name': 'fred', 'age': 40 };
*/
function max(collection, callback, thisArg) {
var computed = -Infinity,
result = computed;
// allows working with functions like `_.map` without using
// their `index` argument as a callback
if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
callback = null;
}
var index = -1,
length = collection ? collection.length : 0;
if (callback == null && typeof length == 'number') {
while (++index < length) {
var value = collection[index];
if (value > result) {
result = value;
}
}
} else {
callback = createCallback(callback, thisArg, 3);
forEach(collection, function(value, index, collection) {
var current = callback(value, index, collection);
if (current > computed) {
computed = current;
result = value;
}
});
}
return result;
}
/**
* Retrieves the minimum value of a collection. If the collection is empty or
* falsey `Infinity` is returned. If a callback is provided it will be executed
* for each value in the collection to generate the criterion by which the value
* is ranked. The callback is bound to `thisArg` and invoked with three
* arguments; (value, index, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the minimum value.
* @example
*
* _.min([4, 2, 8, 6]);
* // => 2
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* _.min(characters, function(chr) { return chr.age; });
* // => { 'name': 'barney', 'age': 36 };
*
* // using "_.pluck" callback shorthand
* _.min(characters, 'age');
* // => { 'name': 'barney', 'age': 36 };
*/
function min(collection, callback, thisArg) {
var computed = Infinity,
result = computed;
// allows working with functions like `_.map` without using
// their `index` argument as a callback
if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
callback = null;
}
var index = -1,
length = collection ? collection.length : 0;
if (callback == null && typeof length == 'number') {
while (++index < length) {
var value = collection[index];
if (value < result) {
result = value;
}
}
} else {
callback = createCallback(callback, thisArg, 3);
forEach(collection, function(value, index, collection) {
var current = callback(value, index, collection);
if (current < computed) {
computed = current;
result = value;
}
});
}
return result;
}
/**
* Retrieves the value of a specified property from all elements in the collection.
*
* @static
* @memberOf _
* @type Function
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {string} property The name of the property to pluck.
* @returns {Array} Returns a new array of property values.
* @example
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* _.pluck(characters, 'name');
* // => ['barney', 'fred']
*/
var pluck = map;
/**
* Reduces a collection to a value which is the accumulated result of running
* each element in the collection through the callback, where each successive
* callback execution consumes the return value of the previous execution. If
* `accumulator` is not provided the first element of the collection will be
* used as the initial `accumulator` value. The callback is bound to `thisArg`
* and invoked with four arguments; (accumulator, value, index|key, collection).
*
* @static
* @memberOf _
* @alias foldl, inject
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [accumulator] Initial value of the accumulator.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the accumulated value.
* @example
*
* var sum = _.reduce([1, 2, 3], function(sum, num) {
* return sum + num;
* });
* // => 6
*
* var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
* result[key] = num * 3;
* return result;
* }, {});
* // => { 'a': 3, 'b': 6, 'c': 9 }
*/
function reduce(collection, callback, accumulator, thisArg) {
if (!collection) return accumulator;
var noaccum = arguments.length < 3;
callback = createCallback(callback, thisArg, 4);
var index = -1,
length = collection.length;
if (typeof length == 'number') {
if (noaccum) {
accumulator = collection[++index];
}
while (++index < length) {
accumulator = callback(accumulator, collection[index], index, collection);
}
} else {
forOwn(collection, function(value, index, collection) {
accumulator = noaccum
? (noaccum = false, value)
: callback(accumulator, value, index, collection)
});
}
return accumulator;
}
/**
* This method is like `_.reduce` except that it iterates over elements
* of a `collection` from right to left.
*
* @static
* @memberOf _
* @alias foldr
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [accumulator] Initial value of the accumulator.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the accumulated value.
* @example
*
* var list = [[0, 1], [2, 3], [4, 5]];
* var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
* // => [4, 5, 2, 3, 0, 1]
*/
function reduceRight(collection, callback, accumulator, thisArg) {
var noaccum = arguments.length < 3;
callback = createCallback(callback, thisArg, 4);
forEachRight(collection, function(value, index, collection) {
accumulator = noaccum
? (noaccum = false, value)
: callback(accumulator, value, index, collection);
});
return accumulator;
}
/**
* The opposite of `_.filter` this method returns the elements of a
* collection that the callback does **not** return truey for.
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a new array of elements that failed the callback check.
* @example
*
* var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
* // => [1, 3, 5]
*
* var characters = [
* { 'name': 'barney', 'age': 36, 'blocked': false },
* { 'name': 'fred', 'age': 40, 'blocked': true }
* ];
*
* // using "_.pluck" callback shorthand
* _.reject(characters, 'blocked');
* // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
*
* // using "_.where" callback shorthand
* _.reject(characters, { 'age': 36 });
* // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
*/
function reject(collection, callback, thisArg) {
callback = createCallback(callback, thisArg, 3);
return filter(collection, function(value, index, collection) {
return !callback(value, index, collection);
});
}
/**
* Retrieves a random element or `n` random elements from a collection.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to sample.
* @param {number} [n] The number of elements to sample.
* @param- {Object} [guard] Allows working with functions like `_.map`
* without using their `index` arguments as `n`.
* @returns {Array} Returns the random sample(s) of `collection`.
* @example
*
* _.sample([1, 2, 3, 4]);
* // => 2
*
* _.sample([1, 2, 3, 4], 2);
* // => [3, 1]
*/
function sample(collection, n, guard) {
if (collection && typeof collection.length != 'number') {
collection = values(collection);
}
if (n == null || guard) {
return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
}
var result = shuffle(collection);
result.length = nativeMin(nativeMax(0, n), result.length);
return result;
}
/**
* Creates an array of shuffled values, using a version of the Fisher-Yates
* shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to shuffle.
* @returns {Array} Returns a new shuffled collection.
* @example
*
* _.shuffle([1, 2, 3, 4, 5, 6]);
* // => [4, 1, 6, 3, 5, 2]
*/
function shuffle(collection) {
var index = -1,
length = collection ? collection.length : 0,
result = Array(typeof length == 'number' ? length : 0);
forEach(collection, function(value) {
var rand = baseRandom(0, ++index);
result[index] = result[rand];
result[rand] = value;
});
return result;
}
/**
* Gets the size of the `collection` by returning `collection.length` for arrays
* and array-like objects or the number of own enumerable properties for objects.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to inspect.
* @returns {number} Returns `collection.length` or number of own enumerable properties.
* @example
*
* _.size([1, 2]);
* // => 2
*
* _.size({ 'one': 1, 'two': 2, 'three': 3 });
* // => 3
*
* _.size('pebbles');
* // => 7
*/
function size(collection) {
var length = collection ? collection.length : 0;
return typeof length == 'number' ? length : keys(collection).length;
}
/**
* Checks if the callback returns a truey value for **any** element of a
* collection. The function returns as soon as it finds a passing value and
* does not iterate over the entire collection. The callback is bound to
* `thisArg` and invoked with three arguments; (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias any
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {boolean} Returns `true` if any element passed the callback check,
* else `false`.
* @example
*
* _.some([null, 0, 'yes', false], Boolean);
* // => true
*
* var characters = [
* { 'name': 'barney', 'age': 36, 'blocked': false },
* { 'name': 'fred', 'age': 40, 'blocked': true }
* ];
*
* // using "_.pluck" callback shorthand
* _.some(characters, 'blocked');
* // => true
*
* // using "_.where" callback shorthand
* _.some(characters, { 'age': 1 });
* // => false
*/
function some(collection, callback, thisArg) {
var result;
callback = createCallback(callback, thisArg, 3);
var index = -1,
length = collection ? collection.length : 0;
if (typeof length == 'number') {
while (++index < length) {
if ((result = callback(collection[index], index, collection))) {
break;
}
}
} else {
forOwn(collection, function(value, index, collection) {
return (result = callback(value, index, collection)) && indicatorObject;
});
}
return !!result;
}
/**
* Creates an array of elements, sorted in ascending order by the results of
* running each element in a collection through the callback. This method
* performs a stable sort, that is, it will preserve the original sort order
* of equal elements. The callback is bound to `thisArg` and invoked with
* three arguments; (value, index|key, collection).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an array of property names is provided for `callback` the collection
* will be sorted by each property value.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Array|Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a new array of sorted elements.
* @example
*
* _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
* // => [3, 1, 2]
*
* _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
* // => [3, 1, 2]
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 },
* { 'name': 'barney', 'age': 26 },
* { 'name': 'fred', 'age': 30 }
* ];
*
* // using "_.pluck" callback shorthand
* _.map(_.sortBy(characters, 'age'), _.values);
* // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
*
* // sorting by multiple properties
* _.map(_.sortBy(characters, ['name', 'age']), _.values);
* // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
*/
function sortBy(collection, callback, thisArg) {
var index = -1,
length = collection ? collection.length : 0,
result = Array(typeof length == 'number' ? length : 0);
callback = createCallback(callback, thisArg, 3);
forEach(collection, function(value, key, collection) {
result[++index] = {
'criteria': [callback(value, key, collection)],
'index': index,
'value': value
};
});
length = result.length;
result.sort(compareAscending);
while (length--) {
result[length] = result[length].value;
}
return result;
}
/**
* Converts the `collection` to an array.
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object|string} collection The collection to convert.
* @returns {Array} Returns the new converted array.
* @example
*
* (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
* // => [2, 3, 4]
*/
function toArray(collection) {
if (isArray(collection)) {
return slice(collection);
}
if (collection && typeof collection.length == 'number') {
return map(collection);
}
return values(collection);
}
/**
* Performs a deep comparison of each element in a `collection` to the given
* `properties` object, returning an array of all elements that have equivalent
* property values.
*
* @static
* @memberOf _
* @type Function
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Object} props The object of property values to filter by.
* @returns {Array} Returns a new array of elements that have the given properties.
* @example
*
* var characters = [
* { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
* { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
* ];
*
* _.where(characters, { 'age': 36 });
* // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
*
* _.where(characters, { 'pets': ['dino'] });
* // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
*/
function where(collection, properties, first) {
return (first && isEmpty(properties))
? undefined
: (first ? find : filter)(collection, properties);
}
/*--------------------------------------------------------------------------*/
/**
* Creates an array with all falsey values removed. The values `false`, `null`,
* `0`, `""`, `undefined`, and `NaN` are all falsey.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to compact.
* @returns {Array} Returns a new array of filtered values.
* @example
*
* _.compact([0, 1, false, 2, '', 3]);
* // => [1, 2, 3]
*/
function compact(array) {
var index = -1,
length = array ? array.length : 0,
result = [];
while (++index < length) {
var value = array[index];
if (value) {
result.push(value);
}
}
return result;
}
/**
* Creates an array excluding all values of the provided arrays using strict
* equality for comparisons, i.e. `===`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to process.
* @param {...Array} [values] The arrays of values to exclude.
* @returns {Array} Returns a new array of filtered values.
* @example
*
* _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
* // => [1, 3, 4]
*/
function difference(array) {
return baseDifference(array, baseFlatten(arguments, true, true, 1));
}
/**
* Gets the first element or first `n` elements of an array. If a callback
* is provided elements at the beginning of the array are returned as long
* as the callback returns truey. The callback is bound to `thisArg` and
* invoked with three arguments; (value, index, array).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias head, take
* @category Arrays
* @param {Array} array The array to query.
* @param {Function|Object|number|string} [callback] The function called
* per element or the number of elements to return. If a property name or
* object is provided it will be used to create a "_.pluck" or "_.where"
* style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the first element(s) of `array`.
* @example
*
* _.first([1, 2, 3]);
* // => 1
*
* _.first([1, 2, 3], 2);
* // => [1, 2]
*
* _.first([1, 2, 3], function(num) {
* return num < 3;
* });
* // => [1, 2]
*
* var characters = [
* { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
* { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
* { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
* ];
*
* // using "_.pluck" callback shorthand
* _.first(characters, 'blocked');
* // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
*
* // using "_.where" callback shorthand
* _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
* // => ['barney', 'fred']
*/
function first(array, callback, thisArg) {
var n = 0,
length = array ? array.length : 0;
if (typeof callback != 'number' && callback != null) {
var index = -1;
callback = createCallback(callback, thisArg, 3);
while (++index < length && callback(array[index], index, array)) {
n++;
}
} else {
n = callback;
if (n == null || thisArg) {
return array ? array[0] : undefined;
}
}
return slice(array, 0, nativeMin(nativeMax(0, n), length));
}
/**
* Flattens a nested array (the nesting can be to any depth). If `isShallow`
* is truey, the array will only be flattened a single level. If a callback
* is provided each element of the array is passed through the callback before
* flattening. The callback is bound to `thisArg` and invoked with three
* arguments; (value, index, array).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to flatten.
* @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a new flattened array.
* @example
*
* _.flatten([1, [2], [3, [[4]]]]);
* // => [1, 2, 3, 4];
*
* _.flatten([1, [2], [3, [[4]]]], true);
* // => [1, 2, 3, [[4]]];
*
* var characters = [
* { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
* { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
* ];
*
* // using "_.pluck" callback shorthand
* _.flatten(characters, 'pets');
* // => ['hoppy', 'baby puss', 'dino']
*/
function flatten(array, isShallow) {
return baseFlatten(array, isShallow);
}
/**
* Gets the index at which the first occurrence of `value` is found using
* strict equality for comparisons, i.e. `===`. If the array is already sorted
* providing `true` for `fromIndex` will run a faster binary search.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to search.
* @param {*} value The value to search for.
* @param {boolean|number} [fromIndex=0] The index to search from or `true`
* to perform a binary search on a sorted array.
* @returns {number} Returns the index of the matched value or `-1`.
* @example
*
* _.indexOf([1, 2, 3, 1, 2, 3], 2);
* // => 1
*
* _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
* // => 4
*
* _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
* // => 2
*/
function indexOf(array, value, fromIndex) {
if (typeof fromIndex == 'number') {
var length = array ? array.length : 0;
fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
} else if (fromIndex) {
var index = sortedIndex(array, value);
return array[index] === value ? index : -1;
}
return baseIndexOf(array, value, fromIndex);
}
/**
* Gets all but the last element or last `n` elements of an array. If a
* callback is provided elements at the end of the array are excluded from
* the result as long as the callback returns truey. The callback is bound
* to `thisArg` and invoked with three arguments; (value, index, array).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to query.
* @param {Function|Object|number|string} [callback=1] The function called
* per element or the number of elements to exclude. If a property name or
* object is provided it will be used to create a "_.pluck" or "_.where"
* style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a slice of `array`.
* @example
*
* _.initial([1, 2, 3]);
* // => [1, 2]
*
* _.initial([1, 2, 3], 2);
* // => [1]
*
* _.initial([1, 2, 3], function(num) {
* return num > 1;
* });
* // => [1]
*
* var characters = [
* { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
* { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
* { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
* ];
*
* // using "_.pluck" callback shorthand
* _.initial(characters, 'blocked');
* // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }]
*
* // using "_.where" callback shorthand
* _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
* // => ['barney', 'fred']
*/
function initial(array, callback, thisArg) {
var n = 0,
length = array ? array.length : 0;
if (typeof callback != 'number' && callback != null) {
var index = length;
callback = createCallback(callback, thisArg, 3);
while (index-- && callback(array[index], index, array)) {
n++;
}
} else {
n = (callback == null || thisArg) ? 1 : callback || n;
}
return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
}
/**
* Creates an array of unique values present in all provided arrays using
* strict equality for comparisons, i.e. `===`.
*
* @static
* @memberOf _
* @category Arrays
* @param {...Array} [array] The arrays to inspect.
* @returns {Array} Returns an array of shared values.
* @example
*
* _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
* // => [1, 2]
*/
function intersection() {
var args = [],
argsIndex = -1,
argsLength = arguments.length;
while (++argsIndex < argsLength) {
var value = arguments[argsIndex];
if (isArray(value) || isArguments(value)) {
args.push(value);
}
}
var array = args[0],
index = -1,
indexOf = getIndexOf(),
length = array ? array.length : 0,
result = [];
outer:
while (++index < length) {
value = array[index];
if (indexOf(result, value) < 0) {
var argsIndex = argsLength;
while (--argsIndex) {
if (indexOf(args[argsIndex], value) < 0) {
continue outer;
}
}
result.push(value);
}
}
return result;
}
/**
* Gets the last element or last `n` elements of an array. If a callback is
* provided elements at the end of the array are returned as long as the
* callback returns truey. The callback is bound to `thisArg` and invoked
* with three arguments; (value, index, array).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to query.
* @param {Function|Object|number|string} [callback] The function called
* per element or the number of elements to return. If a property name or
* object is provided it will be used to create a "_.pluck" or "_.where"
* style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {*} Returns the last element(s) of `array`.
* @example
*
* _.last([1, 2, 3]);
* // => 3
*
* _.last([1, 2, 3], 2);
* // => [2, 3]
*
* _.last([1, 2, 3], function(num) {
* return num > 1;
* });
* // => [2, 3]
*
* var characters = [
* { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
* { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
* { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
* ];
*
* // using "_.pluck" callback shorthand
* _.pluck(_.last(characters, 'blocked'), 'name');
* // => ['fred', 'pebbles']
*
* // using "_.where" callback shorthand
* _.last(characters, { 'employer': 'na' });
* // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
*/
function last(array, callback, thisArg) {
var n = 0,
length = array ? array.length : 0;
if (typeof callback != 'number' && callback != null) {
var index = length;
callback = createCallback(callback, thisArg, 3);
while (index-- && callback(array[index], index, array)) {
n++;
}
} else {
n = callback;
if (n == null || thisArg) {
return array ? array[length - 1] : undefined;
}
}
return slice(array, nativeMax(0, length - n));
}
/**
* Gets the index at which the last occurrence of `value` is found using strict
* equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
* as the offset from the end of the collection.
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to search.
* @param {*} value The value to search for.
* @param {number} [fromIndex=array.length-1] The index to search from.
* @returns {number} Returns the index of the matched value or `-1`.
* @example
*
* _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
* // => 4
*
* _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
* // => 1
*/
function lastIndexOf(array, value, fromIndex) {
var index = array ? array.length : 0;
if (typeof fromIndex == 'number') {
index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
}
while (index--) {
if (array[index] === value) {
return index;
}
}
return -1;
}
/**
* Creates an array of numbers (positive and/or negative) progressing from
* `start` up to but not including `end`. If `start` is less than `stop` a
* zero-length range is created unless a negative `step` is specified.
*
* @static
* @memberOf _
* @category Arrays
* @param {number} [start=0] The start of the range.
* @param {number} end The end of the range.
* @param {number} [step=1] The value to increment or decrement by.
* @returns {Array} Returns a new range array.
* @example
*
* _.range(4);
* // => [0, 1, 2, 3]
*
* _.range(1, 5);
* // => [1, 2, 3, 4]
*
* _.range(0, 20, 5);
* // => [0, 5, 10, 15]
*
* _.range(0, -4, -1);
* // => [0, -1, -2, -3]
*
* _.range(1, 4, 0);
* // => [1, 1, 1]
*
* _.range(0);
* // => []
*/
function range(start, end, step) {
start = +start || 0;
step = (+step || 1);
if (end == null) {
end = start;
start = 0;
}
// use `Array(length)` so engines like Chakra and V8 avoid slower modes
// http://youtu.be/XAqIpGU8ZZk#t=17m25s
var index = -1,
length = nativeMax(0, ceil((end - start) / step)),
result = Array(length);
while (++index < length) {
result[index] = start;
start += step;
}
return result;
}
/**
* The opposite of `_.initial` this method gets all but the first element or
* first `n` elements of an array. If a callback function is provided elements
* at the beginning of the array are excluded from the result as long as the
* callback returns truey. The callback is bound to `thisArg` and invoked
* with three arguments; (value, index, array).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias drop, tail
* @category Arrays
* @param {Array} array The array to query.
* @param {Function|Object|number|string} [callback=1] The function called
* per element or the number of elements to exclude. If a property name or
* object is provided it will be used to create a "_.pluck" or "_.where"
* style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a slice of `array`.
* @example
*
* _.rest([1, 2, 3]);
* // => [2, 3]
*
* _.rest([1, 2, 3], 2);
* // => [3]
*
* _.rest([1, 2, 3], function(num) {
* return num < 3;
* });
* // => [3]
*
* var characters = [
* { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
* { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
* { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
* ];
*
* // using "_.pluck" callback shorthand
* _.pluck(_.rest(characters, 'blocked'), 'name');
* // => ['fred', 'pebbles']
*
* // using "_.where" callback shorthand
* _.rest(characters, { 'employer': 'slate' });
* // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
*/
function rest(array, callback, thisArg) {
if (typeof callback != 'number' && callback != null) {
var n = 0,
index = -1,
length = array ? array.length : 0;
callback = createCallback(callback, thisArg, 3);
while (++index < length && callback(array[index], index, array)) {
n++;
}
} else {
n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
}
return slice(array, n);
}
/**
* Uses a binary search to determine the smallest index at which a value
* should be inserted into a given sorted array in order to maintain the sort
* order of the array. If a callback is provided it will be executed for
* `value` and each element of `array` to compute their sort ranking. The
* callback is bound to `thisArg` and invoked with one argument; (value).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to inspect.
* @param {*} value The value to evaluate.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {number} Returns the index at which `value` should be inserted
* into `array`.
* @example
*
* _.sortedIndex([20, 30, 50], 40);
* // => 2
*
* // using "_.pluck" callback shorthand
* _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
* // => 2
*
* var dict = {
* 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
* };
*
* _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
* return dict.wordToNumber[word];
* });
* // => 2
*
* _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
* return this.wordToNumber[word];
* }, dict);
* // => 2
*/
function sortedIndex(array, value, callback, thisArg) {
var low = 0,
high = array ? array.length : low;
// explicitly reference `identity` for better inlining in Firefox
callback = callback ? createCallback(callback, thisArg, 1) : identity;
value = callback(value);
while (low < high) {
var mid = (low + high) >>> 1;
(callback(array[mid]) < value)
? low = mid + 1
: high = mid;
}
return low;
}
/**
* Creates an array of unique values, in order, of the provided arrays using
* strict equality for comparisons, i.e. `===`.
*
* @static
* @memberOf _
* @category Arrays
* @param {...Array} [array] The arrays to inspect.
* @returns {Array} Returns an array of combined values.
* @example
*
* _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
* // => [1, 2, 3, 5, 4]
*/
function union() {
return baseUniq(baseFlatten(arguments, true, true));
}
/**
* Creates a duplicate-value-free version of an array using strict equality
* for comparisons, i.e. `===`. If the array is sorted, providing
* `true` for `isSorted` will use a faster algorithm. If a callback is provided
* each element of `array` is passed through the callback before uniqueness
* is computed. The callback is bound to `thisArg` and invoked with three
* arguments; (value, index, array).
*
* If a property name is provided for `callback` the created "_.pluck" style
* callback will return the property value of the given element.
*
* If an object is provided for `callback` the created "_.where" style callback
* will return `true` for elements that have the properties of the given object,
* else `false`.
*
* @static
* @memberOf _
* @alias unique
* @category Arrays
* @param {Array} array The array to process.
* @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
* @param {Function|Object|string} [callback=identity] The function called
* per iteration. If a property name or object is provided it will be used
* to create a "_.pluck" or "_.where" style callback, respectively.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array} Returns a duplicate-value-free array.
* @example
*
* _.uniq([1, 2, 1, 3, 1]);
* // => [1, 2, 3]
*
* _.uniq([1, 1, 2, 2, 3], true);
* // => [1, 2, 3]
*
* _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
* // => ['A', 'b', 'C']
*
* _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
* // => [1, 2.5, 3]
*
* // using "_.pluck" callback shorthand
* _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
* // => [{ 'x': 1 }, { 'x': 2 }]
*/
function uniq(array, isSorted, callback, thisArg) {
// juggle arguments
if (typeof isSorted != 'boolean' && isSorted != null) {
thisArg = callback;
callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
isSorted = false;
}
if (callback != null) {
callback = createCallback(callback, thisArg, 3);
}
return baseUniq(array, isSorted, callback);
}
/**
* Creates an array excluding all provided values using strict equality for
* comparisons, i.e. `===`.
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to filter.
* @param {...*} [value] The values to exclude.
* @returns {Array} Returns a new array of filtered values.
* @example
*
* _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
* // => [2, 3, 4]
*/
function without(array) {
return baseDifference(array, slice(arguments, 1));
}
/**
* Creates an array of grouped elements, the first of which contains the first
* elements of the given arrays, the second of which contains the second
* elements of the given arrays, and so on.
*
* @static
* @memberOf _
* @alias unzip
* @category Arrays
* @param {...Array} [array] Arrays to process.
* @returns {Array} Returns a new array of grouped elements.
* @example
*
* _.zip(['fred', 'barney'], [30, 40], [true, false]);
* // => [['fred', 30, true], ['barney', 40, false]]
*/
function zip() {
var index = -1,
length = max(pluck(arguments, 'length')),
result = Array(length < 0 ? 0 : length);
while (++index < length) {
result[index] = pluck(arguments, index);
}
return result;
}
/**
* Creates an object composed from arrays of `keys` and `values`. Provide
* either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
* or two arrays, one of `keys` and one of corresponding `values`.
*
* @static
* @memberOf _
* @alias object
* @category Arrays
* @param {Array} keys The array of keys.
* @param {Array} [values=[]] The array of values.
* @returns {Object} Returns an object composed of the given keys and
* corresponding values.
* @example
*
* _.zipObject(['fred', 'barney'], [30, 40]);
* // => { 'fred': 30, 'barney': 40 }
*/
function zipObject(keys, values) {
var index = -1,
length = keys ? keys.length : 0,
result = {};
if (!values && length && !isArray(keys[0])) {
values = [];
}
while (++index < length) {
var key = keys[index];
if (values) {
result[key] = values[index];
} else if (key) {
result[key[0]] = key[1];
}
}
return result;
}
/*--------------------------------------------------------------------------*/
/**
* Creates a function that executes `func`, with the `this` binding and
* arguments of the created function, only after being called `n` times.
*
* @static
* @memberOf _
* @category Functions
* @param {number} n The number of times the function must be called before
* `func` is executed.
* @param {Function} func The function to restrict.
* @returns {Function} Returns the new restricted function.
* @example
*
* var saves = ['profile', 'settings'];
*
* var done = _.after(saves.length, function() {
* console.log('Done saving!');
* });
*
* _.forEach(saves, function(type) {
* asyncSave({ 'type': type, 'complete': done });
* });
* // => logs 'Done saving!', after all saves have completed
*/
function after(n, func) {
if (!isFunction(func)) {
throw new TypeError;
}
return function() {
if (--n < 1) {
return func.apply(this, arguments);
}
};
}
/**
* Creates a function that, when called, invokes `func` with the `this`
* binding of `thisArg` and prepends any additional `bind` arguments to those
* provided to the bound function.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to bind.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {...*} [arg] Arguments to be partially applied.
* @returns {Function} Returns the new bound function.
* @example
*
* var func = function(greeting) {
* return greeting + ' ' + this.name;
* };
*
* func = _.bind(func, { 'name': 'fred' }, 'hi');
* func();
* // => 'hi fred'
*/
function bind(func, thisArg) {
return arguments.length > 2
? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
: createWrapper(func, 1, null, null, thisArg);
}
/**
* Binds methods of an object to the object itself, overwriting the existing
* method. Method names may be specified as individual arguments or as arrays
* of method names. If no method names are provided all the function properties
* of `object` will be bound.
*
* @static
* @memberOf _
* @category Functions
* @param {Object} object The object to bind and assign the bound methods to.
* @param {...string} [methodName] The object method names to
* bind, specified as individual method names or arrays of method names.
* @returns {Object} Returns `object`.
* @example
*
* var view = {
* 'label': 'docs',
* 'onClick': function() { console.log('clicked ' + this.label); }
* };
*
* _.bindAll(view);
* jQuery('#docs').on('click', view.onClick);
* // => logs 'clicked docs', when the button is clicked
*/
function bindAll(object) {
var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
index = -1,
length = funcs.length;
while (++index < length) {
var key = funcs[index];
object[key] = createWrapper(object[key], 1, null, null, object);
}
return object;
}
/**
* Creates a function that is the composition of the provided functions,
* where each function consumes the return value of the function that follows.
* For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
* Each function is executed with the `this` binding of the composed function.
*
* @static
* @memberOf _
* @category Functions
* @param {...Function} [func] Functions to compose.
* @returns {Function} Returns the new composed function.
* @example
*
* var realNameMap = {
* 'pebbles': 'penelope'
* };
*
* var format = function(name) {
* name = realNameMap[name.toLowerCase()] || name;
* return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
* };
*
* var greet = function(formatted) {
* return 'Hiya ' + formatted + '!';
* };
*
* var welcome = _.compose(greet, format);
* welcome('pebbles');
* // => 'Hiya Penelope!'
*/
function compose() {
var funcs = arguments,
length = funcs.length;
while (length--) {
if (!isFunction(funcs[length])) {
throw new TypeError;
}
}
return function() {
var args = arguments,
length = funcs.length;
while (length--) {
args = [funcs[length].apply(this, args)];
}
return args[0];
};
}
/**
* Creates a function that will delay the execution of `func` until after
* `wait` milliseconds have elapsed since the last time it was invoked.
* Provide an options object to indicate that `func` should be invoked on
* the leading and/or trailing edge of the `wait` timeout. Subsequent calls
* to the debounced function will return the result of the last `func` call.
*
* Note: If `leading` and `trailing` options are `true` `func` will be called
* on the trailing edge of the timeout only if the the debounced function is
* invoked more than once during the `wait` timeout.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to debounce.
* @param {number} wait The number of milliseconds to delay.
* @param {Object} [options] The options object.
* @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
* @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
* @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // avoid costly calculations while the window size is in flux
* var lazyLayout = _.debounce(calculateLayout, 150);
* jQuery(window).on('resize', lazyLayout);
*
* // execute `sendMail` when the click event is fired, debouncing subsequent calls
* jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
* 'leading': true,
* 'trailing': false
* });
*
* // ensure `batchLog` is executed once after 1 second of debounced calls
* var source = new EventSource('/stream');
* source.addEventListener('message', _.debounce(batchLog, 250, {
* 'maxWait': 1000
* }, false);
*/
function debounce(func, wait, options) {
var args,
maxTimeoutId,
result,
stamp,
thisArg,
timeoutId,
trailingCall,
lastCalled = 0,
maxWait = false,
trailing = true;
if (!isFunction(func)) {
throw new TypeError;
}
wait = nativeMax(0, wait) || 0;
if (options === true) {
var leading = true;
trailing = false;
} else if (isObject(options)) {
leading = options.leading;
maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
trailing = 'trailing' in options ? options.trailing : trailing;
}
var delayed = function() {
var remaining = wait - (now() - stamp);
if (remaining <= 0) {
if (maxTimeoutId) {
clearTimeout(maxTimeoutId);
}
var isCalled = trailingCall;
maxTimeoutId = timeoutId = trailingCall = undefined;
if (isCalled) {
lastCalled = now();
result = func.apply(thisArg, args);
if (!timeoutId && !maxTimeoutId) {
args = thisArg = null;
}
}
} else {
timeoutId = setTimeout(delayed, remaining);
}
};
var maxDelayed = function() {
if (timeoutId) {
clearTimeout(timeoutId);
}
maxTimeoutId = timeoutId = trailingCall = undefined;
if (trailing || (maxWait !== wait)) {
lastCalled = now();
result = func.apply(thisArg, args);
if (!timeoutId && !maxTimeoutId) {
args = thisArg = null;
}
}
};
return function() {
args = arguments;
stamp = now();
thisArg = this;
trailingCall = trailing && (timeoutId || !leading);
if (maxWait === false) {
var leadingCall = leading && !timeoutId;
} else {
if (!maxTimeoutId && !leading) {
lastCalled = stamp;
}
var remaining = maxWait - (stamp - lastCalled),
isCalled = remaining <= 0;
if (isCalled) {
if (maxTimeoutId) {
maxTimeoutId = clearTimeout(maxTimeoutId);
}
lastCalled = stamp;
result = func.apply(thisArg, args);
}
else if (!maxTimeoutId) {
maxTimeoutId = setTimeout(maxDelayed, remaining);
}
}
if (isCalled && timeoutId) {
timeoutId = clearTimeout(timeoutId);
}
else if (!timeoutId && wait !== maxWait) {
timeoutId = setTimeout(delayed, wait);
}
if (leadingCall) {
isCalled = true;
result = func.apply(thisArg, args);
}
if (isCalled && !timeoutId && !maxTimeoutId) {
args = thisArg = null;
}
return result;
};
}
/**
* Defers executing the `func` function until the current call stack has cleared.
* Additional arguments will be provided to `func` when it is invoked.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to defer.
* @param {...*} [arg] Arguments to invoke the function with.
* @returns {number} Returns the timer id.
* @example
*
* _.defer(function(text) { console.log(text); }, 'deferred');
* // logs 'deferred' after one or more milliseconds
*/
function defer(func) {
if (!isFunction(func)) {
throw new TypeError;
}
var args = slice(arguments, 1);
return setTimeout(function() { func.apply(undefined, args); }, 1);
}
/**
* Executes the `func` function after `wait` milliseconds. Additional arguments
* will be provided to `func` when it is invoked.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to delay.
* @param {number} wait The number of milliseconds to delay execution.
* @param {...*} [arg] Arguments to invoke the function with.
* @returns {number} Returns the timer id.
* @example
*
* _.delay(function(text) { console.log(text); }, 1000, 'later');
* // => logs 'later' after one second
*/
function delay(func, wait) {
if (!isFunction(func)) {
throw new TypeError;
}
var args = slice(arguments, 2);
return setTimeout(function() { func.apply(undefined, args); }, wait);
}
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided it will be used to determine the cache key for storing the result
* based on the arguments provided to the memoized function. By default, the
* first argument provided to the memoized function is used as the cache key.
* The `func` is executed with the `this` binding of the memoized function.
* The result cache is exposed as the `cache` property on the memoized function.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] A function used to resolve the cache key.
* @returns {Function} Returns the new memoizing function.
* @example
*
* var fibonacci = _.memoize(function(n) {
* return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
* });
*
* fibonacci(9)
* // => 34
*
* var data = {
* 'fred': { 'name': 'fred', 'age': 40 },
* 'pebbles': { 'name': 'pebbles', 'age': 1 }
* };
*
* // modifying the result cache
* var get = _.memoize(function(name) { return data[name]; }, _.identity);
* get('pebbles');
* // => { 'name': 'pebbles', 'age': 1 }
*
* get.cache.pebbles.name = 'penelope';
* get('pebbles');
* // => { 'name': 'penelope', 'age': 1 }
*/
function memoize(func, resolver) {
var cache = {};
return function() {
var key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
return hasOwnProperty.call(cache, key)
? cache[key]
: (cache[key] = func.apply(this, arguments));
};
}
/**
* Creates a function that is restricted to execute `func` once. Repeat calls to
* the function will return the value of the first call. The `func` is executed
* with the `this` binding of the created function.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to restrict.
* @returns {Function} Returns the new restricted function.
* @example
*
* var initialize = _.once(createApplication);
* initialize();
* initialize();
* // `initialize` executes `createApplication` once
*/
function once(func) {
var ran,
result;
if (!isFunction(func)) {
throw new TypeError;
}
return function() {
if (ran) {
return result;
}
ran = true;
result = func.apply(this, arguments);
// clear the `func` variable so the function may be garbage collected
func = null;
return result;
};
}
/**
* Creates a function that, when called, invokes `func` with any additional
* `partial` arguments prepended to those provided to the new function. This
* method is similar to `_.bind` except it does **not** alter the `this` binding.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to partially apply arguments to.
* @param {...*} [arg] Arguments to be partially applied.
* @returns {Function} Returns the new partially applied function.
* @example
*
* var greet = function(greeting, name) { return greeting + ' ' + name; };
* var hi = _.partial(greet, 'hi');
* hi('fred');
* // => 'hi fred'
*/
function partial(func) {
return createWrapper(func, 16, slice(arguments, 1));
}
/**
* Creates a function that, when executed, will only call the `func` function
* at most once per every `wait` milliseconds. Provide an options object to
* indicate that `func` should be invoked on the leading and/or trailing edge
* of the `wait` timeout. Subsequent calls to the throttled function will
* return the result of the last `func` call.
*
* Note: If `leading` and `trailing` options are `true` `func` will be called
* on the trailing edge of the timeout only if the the throttled function is
* invoked more than once during the `wait` timeout.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to throttle.
* @param {number} wait The number of milliseconds to throttle executions to.
* @param {Object} [options] The options object.
* @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
* @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
* @returns {Function} Returns the new throttled function.
* @example
*
* // avoid excessively updating the position while scrolling
* var throttled = _.throttle(updatePosition, 100);
* jQuery(window).on('scroll', throttled);
*
* // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
* jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
* 'trailing': false
* }));
*/
function throttle(func, wait, options) {
var leading = true,
trailing = true;
if (!isFunction(func)) {
throw new TypeError;
}
if (options === false) {
leading = false;
} else if (isObject(options)) {
leading = 'leading' in options ? options.leading : leading;
trailing = 'trailing' in options ? options.trailing : trailing;
}
options = {};
options.leading = leading;
options.maxWait = wait;
options.trailing = trailing;
return debounce(func, wait, options);
}
/**
* Creates a function that provides `value` to the wrapper function as its
* first argument. Additional arguments provided to the function are appended
* to those provided to the wrapper function. The wrapper is executed with
* the `this` binding of the created function.
*
* @static
* @memberOf _
* @category Functions
* @param {*} value The value to wrap.
* @param {Function} wrapper The wrapper function.
* @returns {Function} Returns the new function.
* @example
*
* var p = _.wrap(_.escape, function(func, text) {
* return '
' + func(text) + '
';
* });
*
* p('Fred, Wilma, & Pebbles');
* // => 'Fred, Wilma, & Pebbles
'
*/
function wrap(value, wrapper) {
return createWrapper(wrapper, 16, [value]);
}
/*--------------------------------------------------------------------------*/
/**
* Produces a callback bound to an optional `thisArg`. If `func` is a property
* name the created callback will return the property value for a given element.
* If `func` is an object the created callback will return `true` for elements
* that contain the equivalent object properties, otherwise it will return `false`.
*
* @static
* @memberOf _
* @category Utilities
* @param {*} [func=identity] The value to convert to a callback.
* @param {*} [thisArg] The `this` binding of the created callback.
* @param {number} [argCount] The number of arguments the callback accepts.
* @returns {Function} Returns a callback function.
* @example
*
* var characters = [
* { 'name': 'barney', 'age': 36 },
* { 'name': 'fred', 'age': 40 }
* ];
*
* // wrap to create custom callback shorthands
* _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
* var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
* return !match ? func(callback, thisArg) : function(object) {
* return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
* };
* });
*
* _.filter(characters, 'age__gt38');
* // => [{ 'name': 'fred', 'age': 40 }]
*/
function createCallback(func, thisArg, argCount) {
var type = typeof func;
if (func == null || type == 'function') {
return baseCreateCallback(func, thisArg, argCount);
}
// handle "_.pluck" style callback shorthands
if (type != 'object') {
return property(func);
}
var props = keys(func);
return function(object) {
var length = props.length,
result = false;
while (length--) {
if (!(result = object[props[length]] === func[props[length]])) {
break;
}
}
return result;
};
}
/**
* Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
* corresponding HTML entities.
*
* @static
* @memberOf _
* @category Utilities
* @param {string} string The string to escape.
* @returns {string} Returns the escaped string.
* @example
*
* _.escape('Fred, Wilma, & Pebbles');
* // => 'Fred, Wilma, & Pebbles'
*/
function escape(string) {
return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
}
/**
* This method returns the first argument provided to it.
*
* @static
* @memberOf _
* @category Utilities
* @param {*} value Any value.
* @returns {*} Returns `value`.
* @example
*
* var object = { 'name': 'fred' };
* _.identity(object) === object;
* // => true
*/
function identity(value) {
return value;
}
/**
* Adds function properties of a source object to the destination object.
* If `object` is a function methods will be added to its prototype as well.
*
* @static
* @memberOf _
* @category Utilities
* @param {Function|Object} [object=lodash] object The destination object.
* @param {Object} source The object of functions to add.
* @param {Object} [options] The options object.
* @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
* @example
*
* function capitalize(string) {
* return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
* }
*
* _.mixin({ 'capitalize': capitalize });
* _.capitalize('fred');
* // => 'Fred'
*
* _('fred').capitalize().value();
* // => 'Fred'
*
* _.mixin({ 'capitalize': capitalize }, { 'chain': false });
* _('fred').capitalize();
* // => 'Fred'
*/
function mixin(object) {
forEach(functions(object), function(methodName) {
var func = lodash[methodName] = object[methodName];
lodash.prototype[methodName] = function() {
var args = [this.__wrapped__];
push.apply(args, arguments);
var result = func.apply(lodash, args);
return this.__chain__
? new lodashWrapper(result, true)
: result;
};
});
}
/**
* Reverts the '_' variable to its previous value and returns a reference to
* the `lodash` function.
*
* @static
* @memberOf _
* @category Utilities
* @returns {Function} Returns the `lodash` function.
* @example
*
* var lodash = _.noConflict();
*/
function noConflict() {
root._ = oldDash;
return this;
}
/**
* A no-operation function.
*
* @static
* @memberOf _
* @category Utilities
* @example
*
* var object = { 'name': 'fred' };
* _.noop(object) === undefined;
* // => true
*/
function noop() {
// no operation performed
}
/**
* Gets the number of milliseconds that have elapsed since the Unix epoch
* (1 January 1970 00:00:00 UTC).
*
* @static
* @memberOf _
* @category Utilities
* @example
*
* var stamp = _.now();
* _.defer(function() { console.log(_.now() - stamp); });
* // => logs the number of milliseconds it took for the deferred function to be called
*/
var now = isNative(now = Date.now) && now || function() {
return new Date().getTime();
};
/**
* Creates a "_.pluck" style function, which returns the `key` value of a
* given object.
*
* @static
* @memberOf _
* @category Utilities
* @param {string} key The name of the property to retrieve.
* @returns {Function} Returns the new function.
* @example
*
* var characters = [
* { 'name': 'fred', 'age': 40 },
* { 'name': 'barney', 'age': 36 }
* ];
*
* var getName = _.property('name');
*
* _.map(characters, getName);
* // => ['barney', 'fred']
*
* _.sortBy(characters, getName);
* // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }]
*/
function property(key) {
return function(object) {
return object[key];
};
}
/**
* Produces a random number between `min` and `max` (inclusive). If only one
* argument is provided a number between `0` and the given number will be
* returned. If `floating` is truey or either `min` or `max` are floats a
* floating-point number will be returned instead of an integer.
*
* @static
* @memberOf _
* @category Utilities
* @param {number} [min=0] The minimum possible value.
* @param {number} [max=1] The maximum possible value.
* @param {boolean} [floating=false] Specify returning a floating-point number.
* @returns {number} Returns a random number.
* @example
*
* _.random(0, 5);
* // => an integer between 0 and 5
*
* _.random(5);
* // => also an integer between 0 and 5
*
* _.random(5, true);
* // => a floating-point number between 0 and 5
*
* _.random(1.2, 5.2);
* // => a floating-point number between 1.2 and 5.2
*/
function random(min, max) {
if (min == null && max == null) {
max = 1;
}
min = +min || 0;
if (max == null) {
max = min;
min = 0;
} else {
max = +max || 0;
}
return min + floor(nativeRandom() * (max - min + 1));
}
/**
* Resolves the value of property `key` on `object`. If `key` is a function
* it will be invoked with the `this` binding of `object` and its result returned,
* else the property value is returned. If `object` is falsey then `undefined`
* is returned.
*
* @static
* @memberOf _
* @category Utilities
* @param {Object} object The object to inspect.
* @param {string} key The name of the property to resolve.
* @returns {*} Returns the resolved value.
* @example
*
* var object = {
* 'cheese': 'crumpets',
* 'stuff': function() {
* return 'nonsense';
* }
* };
*
* _.result(object, 'cheese');
* // => 'crumpets'
*
* _.result(object, 'stuff');
* // => 'nonsense'
*/
function result(object, key) {
if (object) {
var value = object[key];
return isFunction(value) ? object[key]() : value;
}
}
/**
* A micro-templating method that handles arbitrary delimiters, preserves
* whitespace, and correctly escapes quotes within interpolated code.
*
* Note: In the development build, `_.template` utilizes sourceURLs for easier
* debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
*
* For more information on precompiling templates see:
* http://lodash.com/custom-builds
*
* For more information on Chrome extension sandboxes see:
* http://developer.chrome.com/stable/extensions/sandboxingEval.html
*
* @static
* @memberOf _
* @category Utilities
* @param {string} text The template text.
* @param {Object} data The data object used to populate the text.
* @param {Object} [options] The options object.
* @param {RegExp} [options.escape] The "escape" delimiter.
* @param {RegExp} [options.evaluate] The "evaluate" delimiter.
* @param {Object} [options.imports] An object to import into the template as local variables.
* @param {RegExp} [options.interpolate] The "interpolate" delimiter.
* @param {string} [sourceURL] The sourceURL of the template's compiled source.
* @param {string} [variable] The data object variable name.
* @returns {Function|string} Returns a compiled function when no `data` object
* is given, else it returns the interpolated text.
* @example
*
* // using the "interpolate" delimiter to create a compiled template
* var compiled = _.template('hello <%= name %>');
* compiled({ 'name': 'fred' });
* // => 'hello fred'
*
* // using the "escape" delimiter to escape HTML in data property values
* _.template('<%- value %>', { 'value': '