Last active
November 10, 2016 01:41
-
-
Save kohlikohl/7ee8d1d280c3d70ea35583bf950fe553 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var win = window; | |
a(); | |
function a(exports) { | |
console.log('Loading'); | |
exports = win; | |
console.log('exports',exports); | |
// | |
// Array utilities | |
// | |
/* eslint no-cond-assign: 0 */ | |
function arrayForEach(array, action) { | |
for (var i = 0, j = array.length; i < j; i++) | |
action(array[i], i); | |
} | |
// | |
// This becomes ko.options | |
// -- | |
// | |
// This is the root 'options', which must be extended by others. | |
var options = { | |
deferUpdates: false, | |
useOnlyNativeEvents: false, | |
protoProperty: '__ko_proto__', | |
// Modify the default attribute from `data-bind`. | |
defaultBindingAttribute: 'data-bind', | |
// Enable/disable <!-- ko binding: ... -> style bindings | |
allowVirtualElements: true, | |
// Global variables that can be accessed from bindings. | |
bindingGlobals: window, | |
// An instance of the binding provider. | |
bindingProviderInstance: null, | |
// jQuery will be automatically set to window.jQuery in applyBindings | |
// if it is (strictly equal to) undefined. Set it to false or null to | |
// disable automatically setting jQuery. | |
jQuery: window && window.jQuery, | |
taskScheduler: null, | |
debug: false, | |
// Filters for bindings | |
// data-bind="expression | filter_1 | filter_2" | |
filters: {}, | |
onError: function (e) { throw e; }, | |
set: function (name, value) { | |
options[name] = value; | |
} | |
}; | |
Object.defineProperty(options, '$', { | |
get: function () { return options.jQuery; } | |
}); | |
function catchFunctionErrors(delegate) { | |
return options.onError ? function () { | |
try { | |
return delegate.apply(this, arguments); | |
} catch (e) { | |
options.onError(e); | |
} | |
} : delegate; | |
} | |
function deferError(error) { | |
safeSetTimeout(function () { options.onError(error); }, 0); | |
} | |
function safeSetTimeout(handler, timeout) { | |
return setTimeout(catchFunctionErrors(handler), timeout); | |
} | |
// | |
// Detection and Workarounds for Internet Explorer | |
// | |
/* eslint no-empty: 0 */ | |
// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness) | |
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10. | |
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser. | |
// If there is a future need to detect specific versions of IE10+, we will amend this. | |
var ieVersion = document && (function() { | |
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); | |
// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment | |
while ( | |
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', | |
iElems[0] | |
) {} | |
return version > 4 ? version : undefined; | |
}()); | |
var isIe6 = ieVersion === 6; | |
var isIe7 = ieVersion === 7; | |
// | |
// Object functions | |
// | |
function extend(target, source) { | |
if (source) { | |
for(var prop in source) { | |
if(source.hasOwnProperty(prop)) { | |
target[prop] = source[prop]; | |
} | |
} | |
} | |
return target; | |
} | |
function objectForEach(obj, action) { | |
for (var prop in obj) { | |
if (obj.hasOwnProperty(prop)) { | |
action(prop, obj[prop]); | |
} | |
} | |
} | |
function clonePlainObjectDeep(obj, seen) { | |
if (!seen) { seen = []; } | |
if (!obj || typeof obj !== 'object' | |
|| obj.constructor !== Object | |
|| seen.indexOf(obj) !== -1) { | |
return obj; | |
} | |
// Anything that makes it below is a plain object that has not yet | |
// been seen/cloned. | |
seen.push(obj); | |
var result = {}; | |
for (var prop in obj) { | |
if (obj.hasOwnProperty(prop)) { | |
result[prop] = clonePlainObjectDeep(obj[prop], seen); | |
} | |
} | |
return result; | |
} | |
var protoProperty = options.protoProperty; | |
// | |
// String (and JSON) | |
// | |
function stringTrim (string) { | |
return string === null || string === undefined ? '' : | |
string.trim ? | |
string.trim() : | |
string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); | |
} | |
// | |
// ES6 Symbols | |
// | |
var useSymbols = typeof Symbol === 'function'; | |
function createSymbolOrString(identifier) { | |
return useSymbols ? Symbol(identifier) : identifier; | |
} | |
// | |
// jQuery | |
// | |
// TODO: deprecate in favour of options.$ | |
var jQueryInstance = window && window.jQuery; | |
// | |
// DOM node data | |
// | |
// import {createSymbolOrString} from '../symbol.js' | |
var uniqueId = 0; | |
var dataStoreKeyExpandoPropertyName = "__ko__data" + new Date(); | |
function nextKey() { | |
return (uniqueId++) + dataStoreKeyExpandoPropertyName; | |
} | |
var domDataKey = nextKey(); | |
// Expose supplemental node cleaning functions. | |
var otherNodeCleanerFunctions = []; | |
// Special support for jQuery here because it's so commonly used. | |
// Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData | |
// so notify it to tear down any resources associated with the node & descendants here. | |
function cleanjQueryData(node) { | |
var jQueryCleanNodeFn = jQueryInstance | |
? jQueryInstance.cleanData : null; | |
if (jQueryCleanNodeFn) { | |
jQueryCleanNodeFn([node]); | |
} | |
} | |
otherNodeCleanerFunctions.push(cleanjQueryData); | |
var knownEvents = {}; | |
var knownEventTypesByEventName = {}; | |
var keyEventTypeName = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents'; | |
knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress']; | |
knownEvents['MouseEvents'] = [ | |
'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', | |
'mouseout', 'mouseenter', 'mouseleave']; | |
objectForEach(knownEvents, function(eventType, knownEventsForType) { | |
if (knownEventsForType.length) { | |
for (var i = 0, j = knownEventsForType.length; i < j; i++) | |
knownEventTypesByEventName[knownEventsForType[i]] = eventType; | |
} | |
}); | |
var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->"; | |
var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/; | |
function isStartComment(node) { | |
return (node.nodeType == 8) && startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue); | |
} | |
function virtualNodeBindingValue(node) { | |
var regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex); | |
return regexMatch ? regexMatch[1] : null; | |
} | |
var supportsTemplateTag = 'content' in document.createElement('template'); | |
var taskQueue = []; | |
var taskQueueLength = 0; | |
var nextIndexToProcess = 0; | |
if (window.MutationObserver && !(window.navigator && window.navigator.standalone)) { | |
// Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+ | |
// From https://github.com/petkaantonov/bluebird * Copyright (c) 2014 Petka Antonov * License: MIT | |
options.taskScheduler = (function (callback) { | |
var div = document.createElement("div"); | |
new MutationObserver(callback).observe(div, {attributes: true}); | |
return function () { div.classList.toggle("foo"); }; | |
})(scheduledProcess); | |
} else if (document && "onreadystatechange" in document.createElement("script")) { | |
// IE 6-10 | |
// From https://github.com/YuzuJS/setImmediate * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola * License: MIT | |
options.taskScheduler = function (callback) { | |
var script = document.createElement("script"); | |
script.onreadystatechange = function () { | |
script.onreadystatechange = null; | |
document.documentElement.removeChild(script); | |
script = null; | |
callback(); | |
}; | |
document.documentElement.appendChild(script); | |
}; | |
} else { | |
options.taskScheduler = function (callback) { | |
setTimeout(callback, 0); | |
}; | |
} | |
function processTasks() { | |
if (taskQueueLength) { | |
// Each mark represents the end of a logical group of tasks and the number of these groups is | |
// limited to prevent unchecked recursion. | |
var mark = taskQueueLength, countMarks = 0; | |
// nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issue | |
for (var task; nextIndexToProcess < taskQueueLength; ) { | |
if (task = taskQueue[nextIndexToProcess++]) { | |
if (nextIndexToProcess > mark) { | |
if (++countMarks >= 5000) { | |
nextIndexToProcess = taskQueueLength; // skip all tasks remaining in the queue since any of them could be causing the recursion | |
deferError(Error("'Too much recursion' after processing " + countMarks + " task groups.")); | |
break; | |
} | |
mark = taskQueueLength; | |
} | |
try { | |
task(); | |
} catch (ex) { | |
deferError(ex); | |
} | |
} | |
} | |
} | |
} | |
function scheduledProcess() { | |
processTasks(); | |
// Reset the queue | |
nextIndexToProcess = taskQueueLength = taskQueue.length = 0; | |
} | |
var stringDouble = '"(?:[^"\\\\]|\\\\.)*"'; | |
var stringSingle = "'(?:[^'\\\\]|\\\\.)*'"; | |
var stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*'; | |
var specials = ',"\'{}()/:[\\]'; | |
var everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']'; | |
var oneNotSpace = '[^\\s]'; | |
var bindingToken = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'); | |
var divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/; | |
var keywordRegexLookBehind = { | |
'in': 1, | |
'return': 1, | |
'typeof': 1 | |
}; | |
/** | |
* Break a binding string (data-bind='x: val, y: ..') into a stable array | |
* of {key: value}. | |
*/ | |
function parseObjectLiteral(objectLiteralString) { | |
// Trim leading and trailing spaces from the string | |
var str = stringTrim(objectLiteralString); | |
// Trim braces '{' surrounding the whole object literal | |
if (str.charCodeAt(0) === 123) str = str.slice(1, -1); | |
// Split into tokens | |
var result = [], | |
toks = str.match(bindingToken), | |
key, values = [], | |
depth = 0; | |
if (!toks) { return [] } | |
// Append a comma so that we don't need a separate code block to deal with the last item | |
toks.push(','); | |
for (var i = 0, tok; tok = toks[i]; ++i) { | |
var c = tok.charCodeAt(0); | |
// A comma signals the end of a key/value pair if depth is zero | |
if (c === 44) { // "," | |
if (depth <= 0) { | |
result.push((key && values.length) ? { | |
key: key, | |
value: values.join('') | |
} : { | |
'unknown': key || values.join('') | |
}); | |
key = depth = 0; | |
values = []; | |
continue; | |
} | |
// Simply skip the colon that separates the name and value | |
} else if (c === 58) { // ":" | |
if (!depth && !key && values.length === 1) { | |
key = values.pop(); | |
continue; | |
} | |
// A set of slashes is initially matched as a regular expression, but could be division | |
} else if (c === 47 && i && tok.length > 1) { // "/" | |
// Look at the end of the previous token to determine if the slash is actually division | |
var match = toks[i - 1].match(divisionLookBehind); | |
if (match && !keywordRegexLookBehind[match[0]]) { | |
// The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash) | |
str = str.substr(str.indexOf(tok) + 1); | |
toks = str.match(bindingToken); | |
toks.push(','); | |
i = -1; | |
// Continue with just the slash | |
tok = '/'; | |
} | |
// Increment depth for parentheses, braces, and brackets so that interior commas are ignored | |
} else if (c === 40 || c === 123 || c === 91) { // '(', '{', '[' | |
++depth; | |
} else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']' | |
--depth; | |
// The key will be the first token; if it's a string, trim the quotes | |
} else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'" | |
tok = tok.slice(1, -1); | |
} | |
values.push(tok); | |
} | |
return result; | |
} | |
// | |
// Array utilities | |
// | |
/* eslint no-cond-assign: 0 */ | |
function arrayForEach$1(array, action) { | |
for (var i = 0, j = array.length; i < j; i++) | |
action(array[i], i); | |
} | |
function arrayIndexOf$1(array, item) { | |
// IE9 | |
if (typeof Array.prototype.indexOf == "function") | |
return Array.prototype.indexOf.call(array, item); | |
for (var i = 0, j = array.length; i < j; i++) | |
if (array[i] === item) | |
return i; | |
return -1; | |
} | |
function arrayRemoveItem$1(array, itemToRemove) { | |
var index = arrayIndexOf$1(array, itemToRemove); | |
if (index > 0) { | |
array.splice(index, 1); | |
} | |
else if (index === 0) { | |
array.shift(); | |
} | |
} | |
// Go through the items that have been added and deleted and try to find matches between them. | |
function findMovesInArrayComparison$1(left, right, limitFailedCompares) { | |
if (left.length && right.length) { | |
var failedCompares, l, r, leftItem, rightItem; | |
for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) { | |
for (r = 0; rightItem = right[r]; ++r) { | |
if (leftItem['value'] === rightItem['value']) { | |
leftItem['moved'] = rightItem['index']; | |
rightItem['moved'] = leftItem['index']; | |
right.splice(r, 1); // This item is marked as moved; so remove it from right list | |
failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures | |
break; | |
} | |
} | |
failedCompares += r; | |
} | |
} | |
} | |
var statusNotInOld$1 = 'added'; | |
var statusNotInNew$1 = 'deleted'; | |
// Simple calculation based on Levenshtein distance. | |
function compareArrays$1(oldArray, newArray, options) { | |
// For backward compatibility, if the third arg is actually a bool, interpret | |
// it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }. | |
options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {}); | |
oldArray = oldArray || []; | |
newArray = newArray || []; | |
if (oldArray.length < newArray.length) | |
return compareSmallArrayToBigArray$1(oldArray, newArray, statusNotInOld$1, statusNotInNew$1, options); | |
else | |
return compareSmallArrayToBigArray$1(newArray, oldArray, statusNotInNew$1, statusNotInOld$1, options); | |
} | |
function compareSmallArrayToBigArray$1(smlArray, bigArray, statusNotInSml, statusNotInBig, options) { | |
var myMin = Math.min, | |
myMax = Math.max, | |
editDistanceMatrix = [], | |
smlIndex, smlIndexMax = smlArray.length, | |
bigIndex, bigIndexMax = bigArray.length, | |
compareRange = (bigIndexMax - smlIndexMax) || 1, | |
maxDistance = smlIndexMax + bigIndexMax + 1, | |
thisRow, lastRow, | |
bigIndexMaxForRow, bigIndexMinForRow; | |
for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) { | |
lastRow = thisRow; | |
editDistanceMatrix.push(thisRow = []); | |
bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange); | |
bigIndexMinForRow = myMax(0, smlIndex - 1); | |
for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) { | |
if (!bigIndex) | |
thisRow[bigIndex] = smlIndex + 1; | |
else if (!smlIndex) // Top row - transform empty array into new array via additions | |
thisRow[bigIndex] = bigIndex + 1; | |
else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1]) | |
thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit) | |
else { | |
var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion) | |
var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition) | |
thisRow[bigIndex] = myMin(northDistance, westDistance) + 1; | |
} | |
} | |
} | |
var editScript = [], meMinusOne, notInSml = [], notInBig = []; | |
for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) { | |
meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1; | |
if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) { | |
notInSml.push(editScript[editScript.length] = { // added | |
'status': statusNotInSml, | |
'value': bigArray[--bigIndex], | |
'index': bigIndex }); | |
} else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) { | |
notInBig.push(editScript[editScript.length] = { // deleted | |
'status': statusNotInBig, | |
'value': smlArray[--smlIndex], | |
'index': smlIndex }); | |
} else { | |
--bigIndex; | |
--smlIndex; | |
if (!options['sparse']) { | |
editScript.push({ | |
'status': "retained", | |
'value': bigArray[bigIndex] }); | |
} | |
} | |
} | |
// Set a limit on the number of consecutive non-matching comparisons; having it a multiple of | |
// smlIndexMax keeps the time complexity of this algorithm linear. | |
findMovesInArrayComparison$1(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10); | |
return editScript.reverse(); | |
} | |
// | |
// This becomes ko.options | |
// -- | |
// | |
// This is the root 'options', which must be extended by others. | |
var options$1 = { | |
deferUpdates: false, | |
useOnlyNativeEvents: false, | |
protoProperty: '__ko_proto__', | |
// Modify the default attribute from `data-bind`. | |
defaultBindingAttribute: 'data-bind', | |
// Enable/disable <!-- ko binding: ... -> style bindings | |
allowVirtualElements: true, | |
// Global variables that can be accessed from bindings. | |
bindingGlobals: window, | |
// An instance of the binding provider. | |
bindingProviderInstance: null, | |
// jQuery will be automatically set to window.jQuery in applyBindings | |
// if it is (strictly equal to) undefined. Set it to false or null to | |
// disable automatically setting jQuery. | |
jQuery: window && window.jQuery, | |
taskScheduler: null, | |
debug: false, | |
// Filters for bindings | |
// data-bind="expression | filter_1 | filter_2" | |
filters: {}, | |
onError: function (e) { throw e; }, | |
set: function (name, value) { | |
options$1[name] = value; | |
} | |
}; | |
Object.defineProperty(options$1, '$', { | |
get: function () { return options$1.jQuery; } | |
}); | |
function catchFunctionErrors$1(delegate) { | |
return options$1.onError ? function () { | |
try { | |
return delegate.apply(this, arguments); | |
} catch (e) { | |
options$1.onError(e); | |
} | |
} : delegate; | |
} | |
function deferError$1(error) { | |
safeSetTimeout$1(function () { options$1.onError(error); }, 0); | |
} | |
function safeSetTimeout$1(handler, timeout) { | |
return setTimeout(catchFunctionErrors$1(handler), timeout); | |
} | |
function throttleFn(callback, timeout) { | |
var timeoutInstance; | |
return function () { | |
if (!timeoutInstance) { | |
timeoutInstance = safeSetTimeout$1(function () { | |
timeoutInstance = undefined; | |
callback(); | |
}, timeout); | |
} | |
}; | |
} | |
function debounceFn(callback, timeout) { | |
var timeoutInstance; | |
return function () { | |
clearTimeout(timeoutInstance); | |
timeoutInstance = safeSetTimeout$1(callback, timeout); | |
}; | |
} | |
// | |
// Detection and Workarounds for Internet Explorer | |
// | |
/* eslint no-empty: 0 */ | |
// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness) | |
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10. | |
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser. | |
// If there is a future need to detect specific versions of IE10+, we will amend this. | |
var ieVersion$1 = document && (function() { | |
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); | |
// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment | |
while ( | |
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', | |
iElems[0] | |
) {} | |
return version > 4 ? version : undefined; | |
}()); | |
var isIe6$1 = ieVersion$1 === 6; | |
var isIe7$1 = ieVersion$1 === 7; | |
// | |
// Object functions | |
// | |
function extend$1(target, source) { | |
if (source) { | |
for(var prop in source) { | |
if(source.hasOwnProperty(prop)) { | |
target[prop] = source[prop]; | |
} | |
} | |
} | |
return target; | |
} | |
function objectForEach$1(obj, action) { | |
for (var prop in obj) { | |
if (obj.hasOwnProperty(prop)) { | |
action(prop, obj[prop]); | |
} | |
} | |
} | |
var protoProperty$2 = options$1.protoProperty; | |
var canSetPrototype$1 = ({ __proto__: [] } instanceof Array); | |
function setPrototypeOf$1(obj, proto) { | |
obj.__proto__ = proto; | |
return obj; | |
} | |
var setPrototypeOfOrExtend$1 = canSetPrototype$1 ? setPrototypeOf$1 : extend$1; | |
function hasPrototype$1(instance, prototype) { | |
if ((instance === null) || (instance === undefined) || (instance[protoProperty$2] === undefined)) return false; | |
if (instance[protoProperty$2] === prototype) return true; | |
return hasPrototype$1(instance[protoProperty$2], prototype); // Walk the prototype chain | |
} | |
// | |
// ES6 Symbols | |
// | |
var useSymbols$1 = typeof Symbol === 'function'; | |
function createSymbolOrString$1(identifier) { | |
return useSymbols$1 ? Symbol(identifier) : identifier; | |
} | |
// | |
// jQuery | |
// | |
// TODO: deprecate in favour of options.$ | |
var jQueryInstance$1 = window && window.jQuery; | |
// | |
// DOM node data | |
// | |
// import {createSymbolOrString} from '../symbol.js' | |
var uniqueId$1 = 0; | |
var dataStoreKeyExpandoPropertyName$1 = "__ko__data" + new Date(); | |
function nextKey$1() { | |
return (uniqueId$1++) + dataStoreKeyExpandoPropertyName$1; | |
} | |
var domDataKey$1 = nextKey$1(); | |
// Expose supplemental node cleaning functions. | |
var otherNodeCleanerFunctions$1 = []; | |
// Special support for jQuery here because it's so commonly used. | |
// Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData | |
// so notify it to tear down any resources associated with the node & descendants here. | |
function cleanjQueryData$1(node) { | |
var jQueryCleanNodeFn = jQueryInstance$1 | |
? jQueryInstance$1.cleanData : null; | |
if (jQueryCleanNodeFn) { | |
jQueryCleanNodeFn([node]); | |
} | |
} | |
otherNodeCleanerFunctions$1.push(cleanjQueryData$1); | |
var knownEvents$1 = {}; | |
var knownEventTypesByEventName$1 = {}; | |
var keyEventTypeName$1 = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents'; | |
knownEvents$1[keyEventTypeName$1] = ['keyup', 'keydown', 'keypress']; | |
knownEvents$1['MouseEvents'] = [ | |
'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', | |
'mouseout', 'mouseenter', 'mouseleave']; | |
objectForEach$1(knownEvents$1, function(eventType, knownEventsForType) { | |
if (knownEventsForType.length) { | |
for (var i = 0, j = knownEventsForType.length; i < j; i++) | |
knownEventTypesByEventName$1[knownEventsForType[i]] = eventType; | |
} | |
}); | |
var commentNodesHaveTextProperty$1 = document && document.createComment("test").text === "<!--test-->"; | |
var supportsTemplateTag$1 = 'content' in document.createElement('template'); | |
var taskQueue$1 = []; | |
var taskQueueLength$1 = 0; | |
var nextHandle$1 = 1; | |
var nextIndexToProcess$1 = 0; | |
if (window.MutationObserver && !(window.navigator && window.navigator.standalone)) { | |
// Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+ | |
// From https://github.com/petkaantonov/bluebird * Copyright (c) 2014 Petka Antonov * License: MIT | |
options$1.taskScheduler = (function (callback) { | |
var div = document.createElement("div"); | |
new MutationObserver(callback).observe(div, {attributes: true}); | |
return function () { div.classList.toggle("foo"); }; | |
})(scheduledProcess$1); | |
} else if (document && "onreadystatechange" in document.createElement("script")) { | |
// IE 6-10 | |
// From https://github.com/YuzuJS/setImmediate * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola * License: MIT | |
options$1.taskScheduler = function (callback) { | |
var script = document.createElement("script"); | |
script.onreadystatechange = function () { | |
script.onreadystatechange = null; | |
document.documentElement.removeChild(script); | |
script = null; | |
callback(); | |
}; | |
document.documentElement.appendChild(script); | |
}; | |
} else { | |
options$1.taskScheduler = function (callback) { | |
setTimeout(callback, 0); | |
}; | |
} | |
function processTasks$1() { | |
if (taskQueueLength$1) { | |
// Each mark represents the end of a logical group of tasks and the number of these groups is | |
// limited to prevent unchecked recursion. | |
var mark = taskQueueLength$1, countMarks = 0; | |
// nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issue | |
for (var task; nextIndexToProcess$1 < taskQueueLength$1; ) { | |
if (task = taskQueue$1[nextIndexToProcess$1++]) { | |
if (nextIndexToProcess$1 > mark) { | |
if (++countMarks >= 5000) { | |
nextIndexToProcess$1 = taskQueueLength$1; // skip all tasks remaining in the queue since any of them could be causing the recursion | |
deferError$1(Error("'Too much recursion' after processing " + countMarks + " task groups.")); | |
break; | |
} | |
mark = taskQueueLength$1; | |
} | |
try { | |
task(); | |
} catch (ex) { | |
deferError$1(ex); | |
} | |
} | |
} | |
} | |
} | |
function scheduledProcess$1() { | |
processTasks$1(); | |
// Reset the queue | |
nextIndexToProcess$1 = taskQueueLength$1 = taskQueue$1.length = 0; | |
} | |
function scheduleTaskProcessing$1() { | |
options$1.taskScheduler(scheduledProcess$1); | |
} | |
function schedule$1(func) { | |
if (!taskQueueLength$1) { | |
scheduleTaskProcessing$1(); | |
} | |
taskQueue$1[taskQueueLength$1++] = func; | |
return nextHandle$1++; | |
} | |
function cancel$1(handle) { | |
var index = handle - (nextHandle$1 - taskQueueLength$1); | |
if (index >= nextIndexToProcess$1 && index < taskQueueLength$1) { | |
taskQueue$1[index] = null; | |
} | |
} | |
function deferUpdates(target) { | |
if (!target._deferUpdates) { | |
target._deferUpdates = true; | |
target.limit(function (callback) { | |
var handle; | |
return function () { | |
cancel$1(handle); | |
handle = schedule$1(callback); | |
target.notifySubscribers(undefined, 'dirty'); | |
}; | |
}); | |
} | |
} | |
var primitiveTypes = { | |
'undefined': 1, 'boolean': 1, 'number': 1, 'string': 1 | |
}; | |
function valuesArePrimitiveAndEqual(a, b) { | |
var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes); | |
return oldValueIsPrimitive ? (a === b) : false; | |
} | |
function applyExtenders(requestedExtenders) { | |
var target = this; | |
if (requestedExtenders) { | |
objectForEach$1(requestedExtenders, function(key, value) { | |
var extenderHandler = extenders[key]; | |
if (typeof extenderHandler == 'function') { | |
target = extenderHandler(target, value) || target; | |
} else { | |
options$1.onError(new Error("Extender not found: " + key)); | |
} | |
}); | |
} | |
return target; | |
} | |
/* | |
--- DEFAULT EXTENDERS --- | |
*/ | |
// Change when notifications are published. | |
function notify(target, notifyWhen) { | |
target.equalityComparer = notifyWhen == "always" ? | |
null : // null equalityComparer means to always notify | |
valuesArePrimitiveAndEqual; | |
} | |
function deferred(target, option) { | |
if (option !== true) { | |
throw new Error('The \'deferred\' extender only accepts the value \'true\', because it is not supported to turn deferral off once enabled.'); | |
} | |
deferUpdates(target); | |
} | |
function rateLimit(target, options) { | |
var timeout, method, limitFunction; | |
if (typeof options == 'number') { | |
timeout = options; | |
} else { | |
timeout = options.timeout; | |
method = options.method; | |
} | |
// rateLimit supersedes deferred updates | |
target._deferUpdates = false; | |
limitFunction = method == 'notifyWhenChangesStop' ? debounceFn : throttleFn; | |
target.limit(function(callback) { | |
return limitFunction(callback, timeout); | |
}); | |
} | |
var extenders = { | |
notify: notify, | |
deferred: deferred, | |
rateLimit: rateLimit | |
}; | |
function subscription(target, callback, disposeCallback) { | |
this._target = target; | |
this.callback = callback; | |
this.disposeCallback = disposeCallback; | |
this.isDisposed = false; | |
} | |
subscription.prototype.dispose = function () { | |
this.isDisposed = true; | |
this.disposeCallback(); | |
}; | |
function subscribable() { | |
setPrototypeOfOrExtend$1(this, ko_subscribable_fn); | |
ko_subscribable_fn.init(this); | |
} | |
var defaultEvent = "change"; | |
var ko_subscribable_fn = { | |
init: function(instance) { | |
instance._subscriptions = {}; | |
instance._versionNumber = 1; | |
}, | |
subscribe: function (callback, callbackTarget, event) { | |
var self = this; | |
event = event || defaultEvent; | |
var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback; | |
var subscriptionInstance = new subscription(self, boundCallback, function () { | |
arrayRemoveItem$1(self._subscriptions[event], subscriptionInstance); | |
if (self.afterSubscriptionRemove) | |
self.afterSubscriptionRemove(event); | |
}); | |
if (self.beforeSubscriptionAdd) | |
self.beforeSubscriptionAdd(event); | |
if (!self._subscriptions[event]) | |
self._subscriptions[event] = []; | |
self._subscriptions[event].push(subscriptionInstance); | |
return subscriptionInstance; | |
}, | |
notifySubscribers: function (valueToNotify, event) { | |
event = event || defaultEvent; | |
if (event === defaultEvent) { | |
this.updateVersion(); | |
} | |
if (this.hasSubscriptionsForEvent(event)) { | |
try { | |
begin(); // Begin suppressing dependency detection (by setting the top frame to undefined) | |
for (var a = this._subscriptions[event].slice(0), i = 0, subscriptionInstance; subscriptionInstance = a[i]; ++i) { | |
// In case a subscription was disposed during the arrayForEach cycle, check | |
// for isDisposed on each subscription before invoking its callback | |
if (!subscriptionInstance.isDisposed) | |
subscriptionInstance.callback(valueToNotify); | |
} | |
} finally { | |
end(); // End suppressing dependency detection | |
} | |
} | |
}, | |
getVersion: function () { | |
return this._versionNumber; | |
}, | |
hasChanged: function (versionToCheck) { | |
return this.getVersion() !== versionToCheck; | |
}, | |
updateVersion: function () { | |
++this._versionNumber; | |
}, | |
hasSubscriptionsForEvent: function(event) { | |
return this._subscriptions[event] && this._subscriptions[event].length; | |
}, | |
getSubscriptionsCount: function (event) { | |
if (event) { | |
return this._subscriptions[event] && this._subscriptions[event].length || 0; | |
} else { | |
var total = 0; | |
objectForEach$1(this._subscriptions, function(eventName, subscriptions) { | |
if (eventName !== 'dirty') | |
total += subscriptions.length; | |
}); | |
return total; | |
} | |
}, | |
isDifferent: function(oldValue, newValue) { | |
return !this.equalityComparer || | |
!this.equalityComparer(oldValue, newValue); | |
}, | |
extend: applyExtenders | |
}; | |
// For browsers that support proto assignment, we overwrite the prototype of each | |
// observable instance. Since observables are functions, we need Function.prototype | |
// to still be in the prototype chain. | |
if (canSetPrototype$1) { | |
setPrototypeOf$1(ko_subscribable_fn, Function.prototype); | |
} | |
subscribable.fn = ko_subscribable_fn; | |
function isSubscribable(instance) { | |
return instance != null && typeof instance.subscribe == "function" && typeof instance.notifySubscribers == "function"; | |
} | |
var outerFrames = []; | |
var currentFrame; | |
var lastId = 0; | |
// Return a unique ID that can be assigned to an observable for dependency tracking. | |
// Theoretically, you could eventually overflow the number storage size, resulting | |
// in duplicate IDs. But in JavaScript, the largest exact integral value is 2^53 | |
// or 9,007,199,254,740,992. If you created 1,000,000 IDs per second, it would | |
// take over 285 years to reach that number. | |
// Reference http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html | |
function getId() { | |
return ++lastId; | |
} | |
function begin(options) { | |
outerFrames.push(currentFrame); | |
currentFrame = options; | |
} | |
function end() { | |
currentFrame = outerFrames.pop(); | |
} | |
function registerDependency(subscribable) { | |
if (currentFrame) { | |
if (!isSubscribable(subscribable)) | |
throw new Error("Only subscribable things can act as dependencies"); | |
currentFrame.callback.call(currentFrame.callbackTarget, subscribable, subscribable._id || (subscribable._id = getId())); | |
} | |
} | |
var observableLatestValue = createSymbolOrString$1('_latestValue'); | |
function observable(initialValue) { | |
function Observable() { | |
if (arguments.length > 0) { | |
// Write | |
// Ignore writes if the value hasn't changed | |
if (Observable.isDifferent(Observable[observableLatestValue], arguments[0])) { | |
Observable.valueWillMutate(); | |
Observable[observableLatestValue] = arguments[0]; | |
Observable.valueHasMutated(); | |
} | |
return this; // Permits chained assignments | |
} | |
else { | |
// Read | |
registerDependency(Observable); // The caller only needs to be notified of changes if they did a "read" operation | |
return Observable[observableLatestValue]; | |
} | |
} | |
Observable[observableLatestValue] = initialValue; | |
// Inherit from 'subscribable' | |
if (!canSetPrototype$1) { | |
// 'subscribable' won't be on the prototype chain unless we put it there directly | |
extend$1(Observable, subscribable.fn); | |
} | |
subscribable.fn.init(Observable); | |
// Inherit from 'observable' | |
setPrototypeOfOrExtend$1(Observable, observable.fn); | |
if (options$1.deferUpdates) { | |
deferUpdates(Observable); | |
} | |
return Observable; | |
} | |
// Define prototype for observables | |
observable.fn = { | |
equalityComparer: valuesArePrimitiveAndEqual, | |
peek: function() { return this[observableLatestValue]; }, | |
valueHasMutated: function () { this.notifySubscribers(this[observableLatestValue]); }, | |
valueWillMutate: function () { | |
this.notifySubscribers(this[observableLatestValue], 'beforeChange'); | |
} | |
}; | |
// Moved out of "limit" to avoid the extra closure | |
function limitNotifySubscribers(value, event) { | |
if (!event || event === defaultEvent) { | |
this._limitChange(value); | |
} else if (event === 'beforeChange') { | |
this._limitBeforeChange(value); | |
} else { | |
this._origNotifySubscribers(value, event); | |
} | |
} | |
// Add `limit` function to the subscribable prototype | |
subscribable.fn.limit = function limit(limitFunction) { | |
var self = this, selfIsObservable = isObservable(self), | |
ignoreBeforeChange, previousValue, pendingValue, beforeChange = 'beforeChange'; | |
if (!self._origNotifySubscribers) { | |
self._origNotifySubscribers = self.notifySubscribers; | |
self.notifySubscribers = limitNotifySubscribers; | |
} | |
var finish = limitFunction(function() { | |
self._notificationIsPending = false; | |
// If an observable provided a reference to itself, access it to get the latest value. | |
// This allows computed observables to delay calculating their value until needed. | |
if (selfIsObservable && pendingValue === self) { | |
pendingValue = self(); | |
} | |
ignoreBeforeChange = false; | |
if (self.isDifferent(previousValue, pendingValue)) { | |
self._origNotifySubscribers(previousValue = pendingValue); | |
} | |
}); | |
self._limitChange = function(value) { | |
self._notificationIsPending = ignoreBeforeChange = true; | |
pendingValue = value; | |
finish(); | |
}; | |
self._limitBeforeChange = function(value) { | |
if (!ignoreBeforeChange) { | |
previousValue = value; | |
self._origNotifySubscribers(value, beforeChange); | |
} | |
}; | |
}; | |
// Note that for browsers that don't support proto assignment, the | |
// inheritance chain is created manually in the observable constructor | |
if (canSetPrototype$1) { | |
setPrototypeOf$1(observable.fn, subscribable.fn); | |
} | |
var protoProperty$1 = observable.protoProperty = options$1.protoProperty; | |
observable.fn[protoProperty$1] = observable; | |
function isObservable(instance) { | |
return hasPrototype$1(instance, observable); | |
} | |
function unwrap(value) { | |
return isObservable(value) ? value() : value; | |
} | |
function isWriteableObservable(instance) { | |
// Observable | |
if ((typeof instance == 'function') && instance[protoProperty$1] === observable) | |
return true; | |
// Writeable dependent observable | |
if ((typeof instance == 'function') /* && (instance[protoProperty] === ko.dependentObservable)*/ && (instance.hasWriteFunction)) | |
return true; | |
// Anything else | |
return false; | |
} | |
var arrayChangeEventName = 'arrayChange'; | |
function trackArrayChanges(target, options) { | |
// Use the provided options--each call to trackArrayChanges overwrites the previously set options | |
target.compareArrayOptions = {}; | |
if (options && typeof options == "object") { | |
extend$1(target.compareArrayOptions, options); | |
} | |
target.compareArrayOptions.sparse = true; | |
// Only modify the target observable once | |
if (target.cacheDiffForKnownOperation) { | |
return; | |
} | |
var trackingChanges = false, | |
cachedDiff = null, | |
arrayChangeSubscription, | |
pendingNotifications = 0, | |
underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd, | |
underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove; | |
// Watch "subscribe" calls, and for array change events, ensure change tracking is enabled | |
target.beforeSubscriptionAdd = function (event) { | |
if (underlyingBeforeSubscriptionAddFunction) | |
underlyingBeforeSubscriptionAddFunction.call(target, event); | |
if (event === arrayChangeEventName) { | |
trackChanges(); | |
} | |
}; | |
// Watch "dispose" calls, and for array change events, ensure change tracking is disabled when all are disposed | |
target.afterSubscriptionRemove = function (event) { | |
if (underlyingAfterSubscriptionRemoveFunction) | |
underlyingAfterSubscriptionRemoveFunction.call(target, event); | |
if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) { | |
arrayChangeSubscription.dispose(); | |
trackingChanges = false; | |
} | |
}; | |
function trackChanges() { | |
// Calling 'trackChanges' multiple times is the same as calling it once | |
if (trackingChanges) { | |
return; | |
} | |
trackingChanges = true; | |
// Intercept "notifySubscribers" to track how many times it was called. | |
var underlyingNotifySubscribersFunction = target['notifySubscribers']; | |
target['notifySubscribers'] = function(valueToNotify, event) { | |
if (!event || event === defaultEvent) { | |
++pendingNotifications; | |
} | |
return underlyingNotifySubscribersFunction.apply(this, arguments); | |
}; | |
// Each time the array changes value, capture a clone so that on the next | |
// change it's possible to produce a diff | |
var previousContents = [].concat(target.peek() || []); | |
cachedDiff = null; | |
arrayChangeSubscription = target.subscribe(function(currentContents) { | |
// Make a copy of the current contents and ensure it's an array | |
currentContents = [].concat(currentContents || []); | |
// Compute the diff and issue notifications, but only if someone is listening | |
if (target.hasSubscriptionsForEvent(arrayChangeEventName)) { | |
var changes = getChanges(previousContents, currentContents); | |
} | |
// Eliminate references to the old, removed items, so they can be GCed | |
previousContents = currentContents; | |
cachedDiff = null; | |
pendingNotifications = 0; | |
if (changes && changes.length) { | |
target['notifySubscribers'](changes, arrayChangeEventName); | |
} | |
}); | |
} | |
function getChanges(previousContents, currentContents) { | |
// We try to re-use cached diffs. | |
// The scenarios where pendingNotifications > 1 are when using rate-limiting or the Deferred Updates | |
// plugin, which without this check would not be compatible with arrayChange notifications. Normally, | |
// notifications are issued immediately so we wouldn't be queueing up more than one. | |
if (!cachedDiff || pendingNotifications > 1) { | |
cachedDiff = trackArrayChanges.compareArrays(previousContents, currentContents, target.compareArrayOptions); | |
} | |
return cachedDiff; | |
} | |
target.cacheDiffForKnownOperation = function(rawArray, operationName, args) { | |
var index, argsIndex; | |
// Only run if we're currently tracking changes for this observable array | |
// and there aren't any pending deferred notifications. | |
if (!trackingChanges || pendingNotifications) { | |
return; | |
} | |
var diff = [], | |
arrayLength = rawArray.length, | |
argsLength = args.length, | |
offset = 0; | |
function pushDiff(status, value, index) { | |
return diff[diff.length] = { 'status': status, 'value': value, 'index': index }; | |
} | |
switch (operationName) { | |
case 'push': | |
offset = arrayLength; | |
case 'unshift': | |
for (index = 0; index < argsLength; index++) { | |
pushDiff('added', args[index], offset + index); | |
} | |
break; | |
case 'pop': | |
offset = arrayLength - 1; | |
case 'shift': | |
if (arrayLength) { | |
pushDiff('deleted', rawArray[offset], offset); | |
} | |
break; | |
case 'splice': | |
// Negative start index means 'from end of array'. After that we clamp to [0...arrayLength]. | |
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | |
var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength), | |
endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength), | |
endAddIndex = startIndex + argsLength - 2, | |
endIndex = Math.max(endDeleteIndex, endAddIndex), | |
additions = [], deletions = []; | |
for (index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) { | |
if (index < endDeleteIndex) | |
deletions.push(pushDiff('deleted', rawArray[index], index)); | |
if (index < endAddIndex) | |
additions.push(pushDiff('added', args[argsIndex], index)); | |
} | |
findMovesInArrayComparison$1(deletions, additions); | |
break; | |
default: | |
return; | |
} | |
cachedDiff = diff; | |
}; | |
} | |
// Expose compareArrays for testing. | |
trackArrayChanges.compareArrays = compareArrays$1; | |
// Add the trackArrayChanges extender so we can use | |
// obs.extend({ trackArrayChanges: true }) | |
extenders.trackArrayChanges = trackArrayChanges; | |
function observableArray(initialValues) { | |
initialValues = initialValues || []; | |
if (typeof initialValues != 'object' || !('length' in initialValues)) | |
throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined."); | |
var result = observable(initialValues); | |
setPrototypeOfOrExtend$1(result, observableArray.fn); | |
trackArrayChanges(result); | |
// ^== result.extend({ trackArrayChanges: true }) | |
return result; | |
} | |
observableArray.fn = { | |
remove: function (valueOrPredicate) { | |
var underlyingArray = this.peek(); | |
var removedValues = []; | |
var predicate = typeof valueOrPredicate == "function" && !isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; | |
for (var i = 0; i < underlyingArray.length; i++) { | |
var value = underlyingArray[i]; | |
if (predicate(value)) { | |
if (removedValues.length === 0) { | |
this.valueWillMutate(); | |
} | |
removedValues.push(value); | |
underlyingArray.splice(i, 1); | |
i--; | |
} | |
} | |
if (removedValues.length) { | |
this.valueHasMutated(); | |
} | |
return removedValues; | |
}, | |
removeAll: function (arrayOfValues) { | |
// If you passed zero args, we remove everything | |
if (arrayOfValues === undefined) { | |
var underlyingArray = this.peek(); | |
var allValues = underlyingArray.slice(0); | |
this.valueWillMutate(); | |
underlyingArray.splice(0, underlyingArray.length); | |
this.valueHasMutated(); | |
return allValues; | |
} | |
// If you passed an arg, we interpret it as an array of entries to remove | |
if (!arrayOfValues) | |
return []; | |
return this['remove'](function (value) { | |
return arrayIndexOf$1(arrayOfValues, value) >= 0; | |
}); | |
}, | |
destroy: function (valueOrPredicate) { | |
var underlyingArray = this.peek(); | |
var predicate = typeof valueOrPredicate == "function" && !isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; | |
this.valueWillMutate(); | |
for (var i = underlyingArray.length - 1; i >= 0; i--) { | |
var value = underlyingArray[i]; | |
if (predicate(value)) | |
underlyingArray[i]["_destroy"] = true; | |
} | |
this.valueHasMutated(); | |
}, | |
destroyAll: function (arrayOfValues) { | |
// If you passed zero args, we destroy everything | |
if (arrayOfValues === undefined) | |
return this.destroy(function() { return true; }); | |
// If you passed an arg, we interpret it as an array of entries to destroy | |
if (!arrayOfValues) | |
return []; | |
return this.destroy(function (value) { | |
return arrayIndexOf$1(arrayOfValues, value) >= 0; | |
}); | |
}, | |
indexOf: function (item) { | |
var underlyingArray = this(); | |
return arrayIndexOf$1(underlyingArray, item); | |
}, | |
replace: function(oldItem, newItem) { | |
var index = this.indexOf(oldItem); | |
if (index >= 0) { | |
this.valueWillMutate(); | |
this.peek()[index] = newItem; | |
this.valueHasMutated(); | |
} | |
} | |
}; | |
// Note that for browsers that don't support proto assignment, the | |
// inheritance chain is created manually in the ko.observableArray constructor | |
if (canSetPrototype$1) { | |
setPrototypeOf$1(observableArray.fn, observable.fn); | |
} | |
// Populate ko.observableArray.fn with read/write functions from native arrays | |
// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array | |
// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale | |
arrayForEach$1(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) { | |
observableArray.fn[methodName] = function () { | |
// Use "peek" to avoid creating a subscription in any computed that we're executing in the context of | |
// (for consistency with mutating regular observables) | |
var underlyingArray = this.peek(); | |
this.valueWillMutate(); | |
this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments); | |
var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments); | |
this.valueHasMutated(); | |
// The native sort and reverse methods return a reference to the array, but it makes more sense to return the observable array instead. | |
return methodCallResult === underlyingArray ? this : methodCallResult; | |
}; | |
}); | |
// Populate ko.observableArray.fn with read-only functions from native arrays | |
arrayForEach$1(["slice"], function (methodName) { | |
observableArray.fn[methodName] = function () { | |
var underlyingArray = this(); | |
return underlyingArray[methodName].apply(underlyingArray, arguments); | |
}; | |
}); | |
function Node(lhs, op, rhs) { | |
this.lhs = lhs; | |
this.op = op; | |
this.rhs = rhs; | |
} | |
/* Just a placeholder */ | |
function LAMBDA() {} | |
/** | |
* @ operator - recursively call the identifier if it's a function | |
* @param {operand} a ignored | |
* @param {operand} b The variable to be called (if a function) and unwrapped | |
* @return {value} The result. | |
*/ | |
function unwrapOrCall(a, b) { | |
while (typeof b === 'function') { b = b(); } | |
return b; | |
} | |
var operators$1 = { | |
// unary | |
'@': unwrapOrCall, | |
'=>': LAMBDA, | |
'!': function not(a, b) { return !b; }, | |
'!!': function notnot(a, b) { return !!b; }, | |
'++': function preinc(a, b) { return ++b; }, | |
'--': function preinc(a, b) { return --b; }, | |
// mul/div | |
'*': function mul(a, b) { return a * b; }, | |
'/': function div(a, b) { return a / b; }, | |
'%': function mod(a, b) { return a % b; }, | |
// sub/add | |
'+': function add(a, b) { return a + b; }, | |
'-': function sub(a, b) { return a - b; }, | |
// relational | |
'<': function lt(a, b) { return a < b; }, | |
'<=': function le(a, b) { return a <= b; }, | |
'>': function gt(a, b) { return a > b; }, | |
'>=': function ge(a, b) { return a >= b; }, | |
// TODO: 'in': function (a, b) { return a in b; }, | |
// TODO: 'instanceof': function (a, b) { return a instanceof b; }, | |
// equality | |
'==': function equal(a, b) { return a === b; }, | |
'!=': function ne(a, b) { return a !== b; }, | |
'===': function sequal(a, b) { return a === b; }, | |
'!==': function sne(a, b) { return a !== b; }, | |
// Fuzzy (bad) equality | |
'~==': function equal(a, b) { return a == b; }, | |
'~!=': function ne(a, b) { return a != b; }, | |
// bitwise | |
'&': function bit_and(a, b) { return a & b; }, | |
'^': function xor(a, b) { return a ^ b; }, | |
'|': function bit_or(a, b) { return a | b; }, | |
// logic | |
'&&': function logic_and(a, b) { return a && b; }, | |
'||': function logic_or(a, b) { return a || b; }, | |
// conditional/ternary | |
'?': function ternary(a, b) { return Node.value_of(a ? b.yes : b.no); } | |
}; | |
/* Order of precedence from: | |
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table | |
*/ | |
// Our operator - unwrap/call | |
operators$1['@'].precedence = 21; | |
// lambda | |
operators$1['=>'].precedence = 20; | |
// Logical not | |
operators$1['!'].precedence = 16; | |
operators$1['!!'].precedence = 16; // explicit double-negative | |
// Prefix inc/dec | |
operators$1['++'].precedence = 16; | |
operators$1['--'].precedence = 16; | |
// logic | |
operators$1['||'].precedence = 14; | |
operators$1['&&'].precedence = 13; | |
// mul/div/remainder | |
operators$1['%'].precedence = 14; | |
operators$1['*'].precedence = 14; | |
operators$1['/'].precedence = 14; | |
// add/sub | |
operators$1['+'].precedence = 13; | |
operators$1['-'].precedence = 13; | |
// bitwise | |
operators$1['|'].precedence = 12; | |
operators$1['^'].precedence = 11; | |
operators$1['&'].precedence = 10; | |
// comparison | |
operators$1['<'].precedence = 11; | |
operators$1['<='].precedence = 11; | |
operators$1['>'].precedence = 11; | |
operators$1['>='].precedence = 11; | |
// operators['in'].precedence = 8; | |
// operators['instanceof'].precedence = 8; | |
// equality | |
operators$1['=='].precedence = 10; | |
operators$1['!='].precedence = 10; | |
operators$1['==='].precedence = 10; | |
operators$1['!=='].precedence = 10; | |
// Fuzzy operators for backwards compat with the "evil twins" | |
// http://stackoverflow.com/questions/359494 | |
operators$1['~=='].precedence = 10; | |
operators$1['~!='].precedence = 10; | |
// Conditional/ternary | |
operators$1['?'].precedence = 4; | |
Node.operators = operators$1; | |
Node.prototype.get_leaf_value = function (leaf, member_of) { | |
if (typeof(leaf) === 'function') { | |
// Expressions on observables are nonsensical, so we unwrap any | |
// function values (e.g. identifiers). | |
return unwrap(leaf()); | |
} | |
// primitives | |
if (typeof(leaf) !== 'object') { | |
return member_of ? member_of[leaf] : leaf; | |
} | |
if (leaf === null) { return leaf; } | |
// Identifiers and Expressions | |
if (leaf[Node.isExpressionOrIdentifierSymbol]) { | |
// lhs is passed in as the parent of the leaf. It will be defined in | |
// cases like a.b.c as 'a' for 'b' then as 'b' for 'c'. | |
return unwrap(leaf.get_value(member_of)); | |
} | |
if (leaf instanceof Node) { | |
return leaf.get_node_value(member_of); | |
} | |
throw new Error("Invalid type of leaf node: " + leaf); | |
}; | |
/** | |
* Return a function that calculates and returns an expression's value | |
* when called. | |
* @param {array} ops The operations to perform | |
* @return {function} The function that calculates the expression. | |
* | |
* Note that for a lambda, we do not evaluate the RHS expression until | |
* the lambda is called. | |
*/ | |
Node.prototype.get_node_value = function () { | |
var node = this; | |
if (node.op === LAMBDA) { | |
return function () { return node.get_leaf_value(node.rhs); }; | |
} | |
return node.op(node.get_leaf_value(node.lhs), | |
node.get_leaf_value(node.rhs)); | |
}; | |
// | |
// Class variables. | |
// | |
Node.isExpressionOrIdentifierSymbol = createSymbolOrString("isExpressionOrIdentifierSymbol"); | |
Node.value_of = function value_of(item) { | |
if (item && item[Node.isExpressionOrIdentifierSymbol]) { | |
return item.get_value(); | |
} | |
return item; | |
}; | |
/** | |
* Convert an array of nodes to an executable tree. | |
* @return {object} An object with a `lhs`, `rhs` and `op` key, corresponding | |
* to the left hand side, right hand side, and | |
* operation function. | |
*/ | |
Node.create_root = function create_root(nodes) { | |
var root, leaf, op, value; | |
// Prime the leaf = root node. | |
leaf = root = new Node(nodes.shift(), nodes.shift(), nodes.shift()); | |
while (nodes) { | |
op = nodes.shift(); | |
value = nodes.shift(); | |
if (!op) { | |
break; | |
} | |
if (op.precedence < root.op.precedence) { | |
// rebase | |
root = new Node(root, op, value); | |
leaf = root; | |
} else { | |
leaf.rhs = new Node(leaf.rhs, op, value); | |
leaf = leaf.rhs; | |
} | |
} | |
// console.log("tree", root) | |
return root; | |
}; | |
function Expression(nodes) { | |
this.nodes = nodes; | |
this.root = Node.create_root(nodes); | |
} | |
// Exports for testing. | |
Expression.operators = Node.operators; | |
Expression.Node = Node; | |
/** | |
* Return the value of `this` Expression instance. | |
* | |
*/ | |
Expression.prototype.get_value = function () { | |
if (!this.root) { | |
this.root = Node.create_root(this.nodes); | |
} | |
return this.root.get_node_value(); | |
}; | |
Expression.prototype[Node.isExpressionOrIdentifierSymbol] = true; | |
function Arguments(parser, args) { | |
this.parser = parser; | |
this.args = args; | |
} | |
Arguments.prototype.get_value = function get_value(/* parent */) { | |
var dereffed_args = []; | |
for (var i = 0, j = this.args.length; i < j; ++i) { | |
dereffed_args.push(Node.value_of(this.args[i])); | |
} | |
return dereffed_args; | |
}; | |
Arguments.prototype[Node.isExpressionOrIdentifierSymbol] = true; | |
function Identifier(parser, token, dereferences) { | |
this.token = token; | |
this.dereferences = dereferences; | |
this.parser = parser; | |
} | |
/** | |
* Return the value of the given | |
* | |
* @param {Object} parent (optional) source of the identifier e.g. for | |
* membership. e.g. `a.b`, one would pass `a` in as | |
* the parent when calling lookup_value for `b`. | |
* @return {Mixed} The value of the token for this Identifier. | |
*/ | |
Identifier.prototype.lookup_value = function (parent) { | |
var token = this.token, | |
parser = this.parser, | |
$context = parser.context, | |
$data = $context.$data || {}, | |
globals = parser.globals || {}; | |
if (parent) { | |
return Node.value_of(parent)[token]; | |
} | |
// short circuits | |
switch (token) { | |
case '$element': return parser.node; | |
case '$context': return $context; | |
case '$data': return $context.$data; | |
default: | |
} | |
// instanceof Object covers 1. {}, 2. [], 3. function() {}, 4. new *; it excludes undefined, null, primitives. | |
if ($data instanceof Object && token in $data) { return $data[token]; } | |
if (token in $context) { return $context[token]; } | |
if (token in globals) { return globals[token]; } | |
throw new Error("The variable \"" + token + "\" was not found on $data, $context, or knockout options.bindingGlobals."); | |
}; | |
/** | |
* Apply all () and [] functions on the identifier to the lhs value e.g. | |
* a()[3] has deref functions that are essentially this: | |
* [_deref_call, _deref_this where this=3] | |
* | |
* @param {mixed} value Should be an object. | |
* @return {mixed} The dereferenced value. | |
*/ | |
Identifier.prototype.dereference = function (value) { | |
var member, | |
refs = this.dereferences || [], | |
parser = this.parser, | |
$context = parser.context || {}, | |
$data = $context.$data || {}, | |
self = { // top-level `this` in function calls | |
$context: $context, | |
$data: $data, | |
globals: parser.globals || {}, | |
$element: parser.node | |
}, | |
last_value, // becomes `this` in function calls to object properties. | |
i, n; | |
for (i = 0, n = refs.length; i < n; ++i) { | |
member = Node.value_of(refs[i]); | |
if (typeof value === 'function' && refs[i] instanceof Arguments) { | |
// fn(args) | |
value = value.apply(last_value || self, member); | |
last_value = value; | |
} else { | |
// obj[x] or obj.x dereference. Note that obj may be a function. | |
last_value = value; | |
value = Node.value_of(value[member]); | |
} | |
} | |
// With obj.x, make `obj = this` | |
if (typeof value === 'function' && n > 0 && last_value !== value) { | |
return value.bind(last_value); | |
} | |
return value; | |
}; | |
/** | |
* Return the value as one would get it from the top-level i.e. | |
* $data.token/$context.token/globals.token; this does not return intermediate | |
* values on a chain of members i.e. $data.hello.there -- requesting the | |
* Identifier('there').value will return $data/$context/globals.there. | |
* | |
* This will dereference using () or [arg] member. | |
* @param {object | Identifier | Expression} parent | |
* @return {mixed} Return the primitive or an accessor. | |
*/ | |
Identifier.prototype.get_value = function (parent) { | |
return this.dereference(this.lookup_value(parent)); | |
}; | |
Identifier.prototype.assign = function assign(object, property, value) { | |
if (isWriteableObservable(object[property])) { | |
object[property](value); | |
} else if (!isObservable(object[property])) { | |
object[property] = value; | |
} | |
}; | |
/** | |
* Set the value of the Identifier. | |
* | |
* @param {Mixed} new_value The value that Identifier is to be set to. | |
*/ | |
Identifier.prototype.set_value = function (new_value) { | |
var parser = this.parser, | |
$context = parser.context, | |
$data = $context.$data || {}, | |
globals = parser.globals || {}, | |
refs = this.dereferences || [], | |
leaf = this.token, | |
i, n, root; | |
if (Object.hasOwnProperty.call($data, leaf)) { | |
root = $data; | |
} else if (Object.hasOwnProperty.call($context, leaf)) { | |
root = $context; | |
} else if (Object.hasOwnProperty.call(globals, leaf)) { | |
root = globals; | |
} else { | |
throw new Error("Identifier::set_value -- " + | |
"The property '" + leaf + "' does not exist " + | |
"on the $data, $context, or globals."); | |
} | |
// Degenerate case. {$data|$context|global}[leaf] = something; | |
n = refs.length; | |
if (n === 0) { | |
this.assign(root, leaf, new_value); | |
return; | |
} | |
// First dereference is {$data|$context|global}[token]. | |
root = root[leaf]; | |
// We cannot use this.dereference because that gives the leaf; to evoke | |
// the ES5 setter we have to call `obj[leaf] = new_value` | |
for (i = 0; i < n - 1; ++i) { | |
leaf = refs[i]; | |
if (leaf instanceof Arguments) { | |
root = root(); | |
} else { | |
root = root[Node.value_of(leaf)]; | |
} | |
} | |
// We indicate that a dereference is a function when it is `true`. | |
if (refs[i] === true) { | |
throw new Error("Cannot assign a value to a function."); | |
} | |
// Call the setter for the leaf. | |
if (refs[i]) { | |
this.assign(root, Node.value_of(refs[i]), new_value); | |
} | |
}; | |
Identifier.prototype[Node.isExpressionOrIdentifierSymbol] = true; | |
function Ternary(yes, no) { | |
this.yes = yes; | |
this.no = no; | |
} | |
Ternary.prototype[Node.isExpressionOrIdentifierSymbol] = true; | |
Ternary.prototype.get_value = function () { return this; }; | |
var escapee = { | |
"'": "'", | |
'"': '"', | |
"`": "`", | |
'\\': '\\', | |
'/': '/', | |
'$': '$', | |
b: '\b', | |
f: '\f', | |
n: '\n', | |
r: '\r', | |
t: '\t' | |
}; | |
var operators = Node.operators; | |
/** | |
* Construct a new Parser instance with new Parser(node, context) | |
* @param {Node} node The DOM element from which we parsed the | |
* content. | |
* @param {object} context The Knockout context. | |
* @param {object} globals An object containing any desired globals. | |
*/ | |
function Parser(node, context, globals) { | |
this.node = node; | |
this.context = context; | |
this.globals = globals || {}; | |
} | |
// Exposed for testing. | |
Parser.Expression = Expression; | |
Parser.Identifier = Identifier; | |
Parser.Arguments = Arguments; | |
Parser.Node = Node; | |
Parser.prototype.white = function () { | |
var ch = this.ch; | |
while (ch && ch <= ' ') { | |
ch = this.next(); | |
} | |
return ch; | |
}; | |
Parser.prototype.next = function (c) { | |
if (c && c !== this.ch) { | |
this.error("Expected '" + c + "' but got '" + this.ch + "'"); | |
} | |
this.ch = this.text.charAt(this.at); | |
this.at += 1; | |
return this.ch; | |
}; | |
Parser.prototype.lookahead = function() { | |
return this.text[this.at]; | |
}; | |
Parser.prototype.error = function (m) { | |
throw { | |
name: 'SyntaxError', | |
message: m, | |
at: this.at, | |
text: this.text | |
}; | |
}; | |
Parser.prototype.name = function () { | |
// A name of a binding | |
var name = '', | |
enclosed_by; | |
this.white(); | |
var ch = this.ch; | |
if (ch === "'" || ch === '"') { | |
enclosed_by = ch; | |
ch = this.next(); | |
} | |
while (ch) { | |
if (enclosed_by && ch === enclosed_by) { | |
this.white(); | |
ch = this.next(); | |
if (ch !== ':' && ch !== ',') { | |
this.error( | |
"Object name: " + name + " missing closing " + enclosed_by | |
); | |
} | |
return name; | |
} else if (ch === ':' || ch <= ' ' || ch === ',') { | |
return name; | |
} | |
name += ch; | |
ch = this.next(); | |
} | |
return name; | |
}; | |
Parser.prototype.number = function () { | |
var number, | |
string = '', | |
ch = this.ch; | |
if (ch === '-') { | |
string = '-'; | |
ch = this.next('-'); | |
} | |
while (ch >= '0' && ch <= '9') { | |
string += ch; | |
ch = this.next(); | |
} | |
if (ch === '.') { | |
string += '.'; | |
ch = this.next(); | |
while (ch && ch >= '0' && ch <= '9') { | |
string += ch; | |
ch = this.next(); | |
} | |
} | |
if (ch === 'e' || ch === 'E') { | |
string += ch; | |
ch = this.next(); | |
if (ch === '-' || ch === '+') { | |
string += ch; | |
ch = this.next(); | |
} | |
while (ch >= '0' && ch <= '9') { | |
string += ch; | |
ch = this.next(); | |
} | |
} | |
number = +string; | |
if (!isFinite(number)) { | |
options.onError(new Error("Bad number: " + number + " in " + string)); | |
} else { | |
return number; | |
} | |
}; | |
/** | |
* Add a property to 'object' that equals the given value. | |
* @param {Object} object The object to add the value to. | |
* @param {String} key object[key] is set to the given value. | |
* @param {mixed} value The value, may be a primitive or a function. If a | |
* function it is unwrapped as a property. | |
*/ | |
Parser.prototype.object_add_value = function (object, key, value) { | |
if (value && value[Node.isExpressionOrIdentifierSymbol]) { | |
Object.defineProperty(object, key, { | |
get: function () { | |
return value.get_value(); | |
}, | |
enumerable: true | |
}); | |
} else { | |
// primitives | |
object[key] = value; | |
} | |
}; | |
Parser.prototype.object = function () { | |
var key, | |
object = {}, | |
ch = this.ch; | |
if (ch === '{') { | |
this.next('{'); | |
ch = this.white(); | |
if (ch === '}') { | |
ch = this.next('}'); | |
return object; | |
} | |
while (ch) { | |
if (ch === '"' || ch === "'" || ch === "`") { | |
key = this.string(); | |
} else { | |
key = this.name(); | |
} | |
this.white(); | |
ch = this.next(':'); | |
if (Object.hasOwnProperty.call(object, key)) { | |
this.error('Duplicate key "' + key + '"'); | |
} | |
this.object_add_value(object, key, this.expression()); | |
ch = this.white(); | |
if (ch === '}') { | |
ch = this.next('}'); | |
return object; | |
} | |
this.next(','); | |
ch = this.white(); | |
} | |
} | |
this.error("Bad object"); | |
}; | |
/** | |
* Read up to delim and return the string | |
* @param {string} delim The delimiter, either ' or " | |
* @return {string} The string read. | |
*/ | |
Parser.prototype.read_string = function (delim) { | |
var string = '', | |
nodes = [''], | |
plus_op = operators['+'], | |
hex, | |
i, | |
uffff, | |
interpolate = delim === "`", | |
ch = this.next(); | |
while (ch) { | |
if (ch === delim) { | |
ch = this.next(); | |
if (interpolate) { nodes.push(plus_op); } | |
nodes.push(string); | |
return nodes; | |
} | |
if (ch === '\\') { | |
ch = this.next(); | |
if (ch === 'u') { | |
uffff = 0; | |
for (i = 0; i < 4; i += 1) { | |
hex = parseInt(ch = this.next(), 16); | |
if (!isFinite(hex)) { | |
break; | |
} | |
uffff = uffff * 16 + hex; | |
} | |
string += String.fromCharCode(uffff); | |
} else if (typeof escapee[ch] === 'string') { | |
string += escapee[ch]; | |
} else { | |
break; | |
} | |
} else if (interpolate && ch === "$") { | |
ch = this.next(); | |
if (ch === '{') { | |
this.next('{'); | |
nodes.push(plus_op); | |
nodes.push(string); | |
nodes.push(plus_op); | |
nodes.push(this.expression()); | |
string = ''; | |
// this.next('}'); | |
} else { | |
string += "$" + ch; | |
} | |
} else { | |
string += ch; | |
} | |
ch = this.next(); | |
} | |
this.error("Bad string"); | |
}; | |
Parser.prototype.string = function () { | |
var ch = this.ch; | |
if (ch === '"') { | |
return this.read_string('"').join(''); | |
} else if (ch === "'") { | |
return this.read_string("'").join(''); | |
} else if (ch === "`") { | |
return Node.create_root(this.read_string("`")); | |
} | |
this.error("Bad string"); | |
}; | |
Parser.prototype.array = function () { | |
var array = [], | |
ch = this.ch; | |
if (ch === '[') { | |
ch = this.next('['); | |
this.white(); | |
if (ch === ']') { | |
ch = this.next(']'); | |
return array; | |
} | |
while (ch) { | |
array.push(this.expression()); | |
ch = this.white(); | |
if (ch === ']') { | |
ch = this.next(']'); | |
return array; | |
} | |
this.next(','); | |
ch = this.white(); | |
} | |
} | |
this.error("Bad array"); | |
}; | |
Parser.prototype.value = function () { | |
var ch; | |
this.white(); | |
ch = this.ch; | |
switch (ch) { | |
case '{': return this.object(); | |
case '[': return this.array(); | |
case '"': case "'": case "`": return this.string(); | |
case '-': return this.number(); | |
default: | |
return ch >= '0' && ch <= '9' ? this.number() : this.identifier(); | |
} | |
}; | |
/** | |
* Get the function for the given operator. | |
* A `.precedence` value is added to the function, with increasing | |
* precedence having a higher number. | |
* @return {function} The function that performs the infix operation | |
*/ | |
Parser.prototype.operator = function () { | |
var op = '', | |
op_fn, | |
ch = this.white(); | |
while (ch) { | |
if (is_identifier_char(ch) || ch <= ' ' || ch === '' || | |
ch === '"' || ch === "'" || ch === '{' || ch === '[' || | |
ch === '(' || ch === "`") { | |
break; | |
} | |
op += ch; | |
ch = this.next(); | |
// An infix followed by the prefix e.g. a + @b | |
// TODO: other prefix unary operators | |
if (ch === '@') { | |
break; | |
} | |
} | |
if (op !== '') { | |
op_fn = operators[op]; | |
if (!op_fn) { | |
this.error("Bad operator: '" + op + "'."); | |
} | |
} | |
return op_fn; | |
}; | |
/** | |
* Filters | |
* Returns what the Node interprets as an "operator". | |
* e.g. | |
* <span data-bind="text: name | fit:20 | uppercase"></span> | |
*/ | |
Parser.prototype.filter = function() { | |
var ch = this.next(), | |
args = [], | |
next_filter = function(v) { return v; }, | |
name = this.name(); | |
if (!options.filters[name]) { | |
options.onError("Cannot find filter by the name of: " + name); | |
} | |
while (ch) { | |
if (ch === ':') { | |
this.next(); | |
args.push(this.expression()); | |
} | |
if (ch === '|') { | |
next_filter = this.filter(); | |
break; | |
} | |
ch = this.white(); | |
} | |
var filter = function filter(value) { | |
var arg_values = [value]; | |
for (var i = 0, j = args.length; i < j; ++i) { | |
arg_values.push(Node.value_of(args[i])); | |
} | |
return next_filter(options.filters[name].apply(null, arg_values)); | |
}; | |
// Lowest precedence. | |
filter.precedence = 1; | |
return filter; | |
}; | |
/** | |
* Parse an expression – builds an operator tree, in something like | |
* Shunting-Yard. | |
* See: http://en.wikipedia.org/wiki/Shunting-yard_algorithm | |
* | |
* @return {function} A function that computes the value of the expression | |
* when called or a primitive. | |
*/ | |
Parser.prototype.expression = function (filterable) { | |
var op, | |
nodes = [], | |
filters = [], | |
ch = this.white(); | |
while (ch) { | |
// unary prefix operators | |
op = this.operator(); | |
if (op) { | |
nodes.push(undefined); // LHS Tree node. | |
nodes.push(op); | |
ch = this.white(); | |
} | |
if (ch === '(') { | |
this.next(); | |
nodes.push(this.expression()); | |
this.next(')'); | |
} else { | |
nodes.push(this.value()); | |
} | |
ch = this.white(); | |
if (ch === ':' || ch === '}' || ch === ',' || ch === ']' || | |
ch === ')' || ch === '' || ch === '`') { | |
break; | |
} | |
// filters | |
if (ch === '|' && this.lookahead() !== '|' && filterable) { | |
nodes.push(this.filter()); | |
nodes.push(undefined); | |
break; | |
} | |
// infix operators | |
op = this.operator(); | |
if (op === operators['?']) { | |
this.ternary(nodes); | |
break; | |
} else if (op) { | |
nodes.push(op); | |
} | |
ch = this.white(); | |
} | |
if (nodes.length === 0) { | |
return undefined; | |
} | |
if (nodes.length === 1) { | |
return nodes[0]; | |
} | |
return new Expression(nodes, filters); | |
}; | |
Parser.prototype.ternary = function(nodes) { | |
var ternary = new Ternary(); | |
ternary.yes = this.expression(); | |
this.next(":"); | |
ternary.no = this.expression(); | |
nodes.push(operators['?']); | |
nodes.push(ternary); | |
}; | |
/** | |
* Parse the arguments to a function, returning an Array. | |
* | |
*/ | |
Parser.prototype.func_arguments = function () { | |
var args = [], | |
ch = this.next('('); | |
while(ch) { | |
ch = this.white(); | |
if (ch === ')') { | |
this.next(')'); | |
return new Arguments(this, args); | |
} else { | |
args.push(this.expression()); | |
ch = this.white(); | |
} | |
if (ch !== ')') { this.next(','); } | |
} | |
this.error("Bad arguments to function"); | |
}; | |
/** | |
* A dereference applies to an identifer, being either a function | |
* call "()" or a membership lookup with square brackets "[member]". | |
* @return {fn or undefined} Dereference function to be applied to the | |
* Identifier | |
*/ | |
Parser.prototype.dereference = function () { | |
var member, | |
ch = this.white(); | |
while (ch) { | |
if (ch === '(') { | |
// a(...) function call | |
return this.func_arguments(); | |
} else if (ch === '[') { | |
// a[x] membership | |
this.next('['); | |
member = this.expression(); | |
this.white(); | |
this.next(']'); | |
return member; | |
} else if (ch === '.') { | |
// a.x membership | |
member = ''; | |
this.next('.'); | |
ch = this.white(); | |
while (ch) { | |
if (!is_identifier_char(ch)) { | |
break; | |
} | |
member += ch; | |
ch = this.next(); | |
} | |
return member; | |
} else { | |
break; | |
} | |
} | |
return; | |
}; | |
Parser.prototype.identifier = function () { | |
var token = '', ch, deref, dereferences = []; | |
ch = this.white(); | |
while (ch) { | |
if (!is_identifier_char(ch)) { | |
break; | |
} | |
token += ch; | |
ch = this.next(); | |
} | |
switch (token) { | |
case 'true': return true; | |
case 'false': return false; | |
case 'null': return null; | |
case 'undefined': return void 0; | |
default: | |
} | |
while (ch) { | |
deref = this.dereference(); | |
if (deref !== undefined) { | |
dereferences.push(deref); | |
} else { | |
break; | |
} | |
} | |
return new Identifier(this, token, dereferences); | |
}; | |
Parser.prototype.read_bindings = function () { | |
var key, | |
bindings = {}, | |
sep, | |
expr, | |
ch = this.ch; | |
while (ch) { | |
key = this.name(); | |
sep = this.white(); | |
if (!sep || sep === ',') { | |
if (sep) { | |
ch = this.next(','); | |
} else { | |
ch = ''; | |
} | |
// A "bare" binding e.g. "text"; substitute value of 'null' | |
// so it becomes "text: null". | |
bindings[key] = null; | |
} else { | |
if (key.indexOf('.') !== -1) { | |
// Namespaced – i.e. | |
// `attr.css: x` becomes `attr: { css: x }` | |
// ^^^ - key | |
key = key.split('.'); | |
bindings[key[0]] = bindings[key[0]] || {}; | |
if (key.length !== 2) { | |
options.onError("Binding " + key + " should have two parts (a.b)."); | |
} else if (bindings[key[0]].constructor !== Object) { | |
options.onError("Binding " + key[0] + "." + key[1] + " paired with a non-object."); | |
} | |
ch = this.next(':'); | |
this.object_add_value(bindings[key[0]], key[1], this.expression(true)); | |
} else { | |
ch = this.next(':'); | |
if (bindings[key] && typeof bindings[key] === 'object' && bindings[key].constructor === Object) { | |
// Extend a namespaced bindings e.g. we've previously seen | |
// on.x, now we're seeing on: { 'abc' }. | |
expr = this.expression(true); | |
if (typeof expr !== 'object' || expr.constructor !== Object) { | |
options.onError("Expected plain object for " + key + " value."); | |
} else { | |
extend(bindings[key], expr); | |
} | |
} else { | |
bindings[key] = this.expression(true); | |
} | |
} | |
this.white(); | |
if (this.ch) { | |
ch = this.next(','); | |
} else { | |
ch = ''; | |
} | |
} | |
} | |
return bindings; | |
}; | |
/** | |
* Convert result[name] from a value to a function (i.e. `valueAccessor()`) | |
* @param {object} result [Map of top-level names to values] | |
* @return {object} [Map of top-level names to functions] | |
* | |
* Accessors may be one of (below) constAccessor, identifierAccessor, | |
* expressionAccessor, or nodeAccessor. | |
*/ | |
Parser.prototype.convert_to_accessors = function (result) { | |
objectForEach(result, function (name, value) { | |
if (value instanceof Identifier) { | |
// Return a function that, with no arguments returns | |
// the value of the identifier, otherwise sets the | |
// value of the identifier to the first given argument. | |
Object.defineProperty(result, name, { | |
value: function (optionalValue, options) { | |
if (arguments.length === 0) { | |
return value.get_value(); | |
} | |
if (options && options.onlyIfChanged && optionalValue === value.get_value()) { | |
return; | |
} | |
return value.set_value(optionalValue); | |
} | |
}); | |
} else if (value instanceof Expression) { | |
result[name] = function expressionAccessor() { | |
return value.get_value(); | |
}; | |
} else if (value instanceof Node) { | |
result[name] = function nodeAccessor() { | |
return value.get_node_value(); | |
}; | |
} else if (typeof(value) !== 'function') { | |
result[name] = function constAccessor() { | |
return clonePlainObjectDeep(value); | |
}; | |
} else if (value === 'function') { | |
result[name] = function functionAccessor() { | |
return value(); | |
}; | |
} | |
}); | |
return result; | |
}; | |
/** | |
* Get the bindings as name: accessor() | |
* @param {string} source The binding string to parse. | |
* @return {object} Map of name to accessor function. | |
*/ | |
Parser.prototype.parse = function (source) { | |
this.text = (source || '').trim(); | |
this.at = 0; | |
this.ch = ' '; | |
if (!this.text) { | |
return null; | |
} | |
try { | |
var result = this.read_bindings(); | |
} catch (e) { | |
// `e` may be 1.) a proper Error; 2.) a parsing error; or 3.) a string. | |
var emsg = typeof e === Error ? | |
"\nMessage: <" + e.name + "> " + e.message : | |
typeof e === 'object' && 'at' in e ? | |
"\n" + e.name + " " + e.message + " of \n" | |
+ " " + e.text + "\n" | |
+ Array(e.at).join(" ") + "_/ 🔥 \\_\n" | |
: e; | |
options.onError(new Error(emsg)); | |
} | |
this.white(); | |
if (this.ch) { | |
this.error("Syntax Error"); | |
} | |
return this.convert_to_accessors(result); | |
}; | |
/** | |
* Determine if a character is a valid item in an identifier. | |
* Note that we do not check whether the first item is a number, nor do we | |
* support unicode identifiers here. | |
* | |
* See: http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm | |
* @param {String} ch The character | |
* @return {Boolean} True if [A-Za-z0-9_] | |
*/ | |
function is_identifier_char(ch) { | |
return (ch >= 'A' && ch <= 'Z') || | |
(ch >= 'a' && ch <= 'z') || | |
(ch >= '0' && ch <= 9) || | |
ch === '_' || ch === '$'; | |
} | |
function Provider(options) { | |
options = options || {}; | |
this.otherProviders = options.otherProviders || []; | |
this.bindingPreProcessors = options.bindingPreProcessors || []; | |
this.nodePreProcessors = options.preprocessors || []; | |
// the binding classes -- defaults to the bind's | |
// bindingsHandlers | |
var bindingHandlers = this.bindingHandlers = {}; | |
addGetterSetter(bindingHandlers); | |
// Cache the result of parsing binding strings. | |
// TODO | |
// this.cache = {}; | |
} | |
/** Add non-enumerable `get` and `set` properties. | |
*/ | |
// bindingHandlers.set(nameOrObject, value) | |
// --- | |
// Examples: | |
// | |
// bindingHandlers.set('name', bindingDefinition) | |
// bindingHandlers.set({ text: textBinding, input: inputBinding }) | |
function addGetterSetter(bindingHandlersObject) { | |
Object.defineProperties(bindingHandlersObject, { | |
'set': { | |
configurable: true, | |
value: function setBindingHandler(nameOrObject, value) { | |
if (typeof nameOrObject === 'string') { | |
bindingHandlersObject[nameOrObject] = value; | |
} else if (typeof nameOrObject === 'object') { | |
if (value !== undefined) { | |
options.onError( | |
new Error("Given extraneous `value` parameter (first param should be a string, but it was an object)." + nameOrObject)); | |
} | |
extend(bindingHandlersObject, nameOrObject); | |
} else { | |
options.onError( | |
new Error("Given a bad binding handler type: " + nameOrObject)); | |
} | |
} | |
}, | |
'get': { | |
configurable: true, | |
value: function getBindingHandler(name) { | |
// NOTE: Strict binding checking ought to occur here. | |
return bindingHandlersObject[name]; | |
} | |
} | |
}); | |
} | |
function nodeHasBindings(node) { | |
if (node.nodeType === node.ELEMENT_NODE) { | |
if (node.getAttribute(options.defaultBindingAttribute)) { return true; } | |
} else if (node.nodeType === node.COMMENT_NODE) { | |
if (options.allowVirtualElements && | |
isStartComment(node)) { | |
return true; | |
} | |
} | |
for (var i = 0, j = this.otherProviders.length; i < j; i++) { | |
if (this.otherProviders[i].nodeHasBindings(node)) { return true; } | |
} | |
return false; | |
} | |
function getBindingsString(node) { | |
switch (node.nodeType) { | |
case node.ELEMENT_NODE: | |
return node.getAttribute(options.defaultBindingAttribute); | |
case node.COMMENT_NODE: | |
return virtualNodeBindingValue(node); | |
default: | |
return null; | |
} | |
} | |
// Note we do not seem to need both getBindings and getBindingAccessors; just | |
// the latter appears to suffice. | |
// | |
// Return the name/valueAccessor pairs. | |
// (undocumented replacement for getBindings) | |
// see https://github.com/knockout/knockout/pull/742 | |
function getBindingAccessors(node, context) { | |
var bindings = {}, | |
parser = new Parser(node, context, options.bindingGlobals), | |
binding_string = this.getBindingsString(node); | |
if (binding_string) { | |
binding_string = this.preProcessBindings(binding_string); | |
bindings = parser.parse(binding_string || ''); | |
} | |
arrayForEach(this.otherProviders, function(p) { | |
extend(bindings, p.getBindingAccessors(node, context, parser, bindings)); | |
}); | |
objectForEach(bindings, this.preProcessBindings.bind(this)); | |
return bindings; | |
} | |
/** Call bindingHandler.preprocess on each respective binding string. | |
* | |
* The `preprocess` property of bindingHandler must be a static | |
* function (i.e. on the object or constructor). | |
*/ | |
function preProcessBindings(bindingString) { | |
var results = []; | |
var bindingHandlers = this.bindingHandlers; | |
var preprocessed; | |
// Check for a Provider.preprocessNode property | |
if (typeof this.preprocessNode === 'function') { | |
preprocessed = this.preprocessNode(bindingString, this); | |
if (preprocessed) { bindingString = preprocessed; } | |
} | |
for (var i = 0, j = this.bindingPreProcessors.length; i < j; ++i) { | |
preprocessed = this.bindingPreProcessors[i](bindingString, this); | |
if (preprocessed) { bindingString = preprocessed; } | |
} | |
function addBinding(name, value) { | |
results.push("'" + name + "':" + value); | |
} | |
function processBinding(key, value) { | |
// Get the "on" binding from "on.click" | |
var handler = bindingHandlers.get(key.split('.')[0]); | |
if (handler && typeof handler.preprocess === 'function') { | |
value = handler.preprocess(value, key, processBinding); | |
} | |
addBinding(key, value); | |
} | |
arrayForEach(parseObjectLiteral(bindingString), function(keyValueItem) { | |
processBinding( | |
keyValueItem.key || keyValueItem.unknown, | |
keyValueItem.value | |
); | |
}); | |
return results.join(','); | |
} | |
/** | |
* Run the preprocessors on a given node | |
* @param {HTMLElement} node The node to be modified/preprocessed. | |
* @return {Array<HTMLElement>} An array of nodes. | |
* | |
* FIXME: This only lets one node preprocessor modify the nodes; more | |
* generically we want to be able to have a nested node->nodes for each | |
* preprocessor, eventually flattening a tree-like result. | |
*/ | |
function preprocessNode(node, startingPreprocessorIndex) { | |
var newNodes; | |
for (var i = startingPreprocessorIndex || 0, j = this.nodePreProcessors.length; i < j; i++) { | |
newNodes = this.nodePreProcessors[i].call(this, node, this); | |
if (newNodes) { return newNodes; } | |
} | |
return; | |
} | |
// addProvider(provider instance) | |
// --- | |
// | |
// Other providers (such as ko.components) can be added with the `addProvider` | |
// call. Each provider is expected to have a `nodeHasBindings` and a | |
// `getBindingAccessors` function. | |
// | |
function addProvider(p) { this.otherProviders.push(p); } | |
function clearProviders() { this.otherProviders.length = 0; } | |
function addBindingPreprocessor(fn) { this.bindingPreProcessors.push(fn); } | |
function clearBindingPreprocessors() { this.bindingPreProcessors.length = 0; } | |
function addNodePreprocessor(fn) { this.nodePreProcessors.push(fn); } | |
function clearNodePreprocessors() { this.nodePreProcessors.length = 0; } | |
extend(Provider.prototype, { | |
nodeHasBindings: nodeHasBindings, | |
getBindingAccessors: getBindingAccessors, | |
getBindingsString: getBindingsString, | |
Parser: Parser, | |
preProcessBindings: preProcessBindings, | |
preprocessNode: preprocessNode, | |
addProvider: addProvider, | |
addBindingPreprocessor: addBindingPreprocessor, | |
clearProviders: clearProviders, | |
clearBindingPreprocessors: clearBindingPreprocessors, | |
addNodePreprocessor: addNodePreprocessor, | |
clearNodePreprocessors: clearNodePreprocessors, | |
}); | |
exports.parseObjectLiteral = parseObjectLiteral; | |
exports.Provider = Provider; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment