// Knockout JavaScript library v1.2.1 // (c) Steven Sanderson - http://knockoutjs.com/ // License: MIT (http://www.opensource.org/licenses/mit-license.php) (function(window,undefined){ var ko = window["ko"] = {}; // Google Closure Compiler helpers (used only to make the minified file smaller) ko.exportSymbol = function(publicPath, object) { var tokens = publicPath.split("."); var target = window; for (var i = 0; i < tokens.length - 1; i++) target = target[tokens[i]]; target[tokens[tokens.length - 1]] = object; }; ko.exportProperty = function(owner, publicName, object) { owner[publicName] = object; }; ko.utils = new (function () { var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g; var isIe6 = /MSIE 6/i.test(navigator.userAgent); var isIe7 = /MSIE 7/i.test(navigator.userAgent); // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup) var knownEvents = {}, knownEventTypesByEventName = {}; var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents'; knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress']; knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave']; for (var eventType in knownEvents) { var knownEventsForType = knownEvents[eventType]; if (knownEventsForType.length) { for (var i = 0, j = knownEventsForType.length; i < j; i++) knownEventTypesByEventName[knownEventsForType[i]] = eventType; } } function isClickOnCheckableElement(element, eventType) { if ((element.tagName != "INPUT") || !element.type) return false; if (eventType.toLowerCase() != "click") return false; var inputType = element.type.toLowerCase(); return (inputType == "checkbox") || (inputType == "radio"); } return { fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/], arrayForEach: function (array, action) { for (var i = 0, j = array.length; i < j; i++) action(array[i]); }, arrayIndexOf: function (array, item) { if (typeof array.indexOf == "function") return array.indexOf(item); for (var i = 0, j = array.length; i < j; i++) if (array[i] === item) return i; return -1; }, arrayFirst: function (array, predicate, predicateOwner) { for (var i = 0, j = array.length; i < j; i++) if (predicate.call(predicateOwner, array[i])) return array[i]; return null; }, arrayRemoveItem: function (array, itemToRemove) { var index = ko.utils.arrayIndexOf(array, itemToRemove); if (index >= 0) array.splice(index, 1); }, arrayGetDistinctValues: function (array) { array = array || []; var result = []; for (var i = 0, j = array.length; i < j; i++) { if (ko.utils.arrayIndexOf(result, array[i]) < 0) result.push(array[i]); } return result; }, arrayMap: function (array, mapping) { array = array || []; var result = []; for (var i = 0, j = array.length; i < j; i++) result.push(mapping(array[i])); return result; }, arrayFilter: function (array, predicate) { array = array || []; var result = []; for (var i = 0, j = array.length; i < j; i++) if (predicate(array[i])) result.push(array[i]); return result; }, arrayPushAll: function (array, valuesToPush) { for (var i = 0, j = valuesToPush.length; i < j; i++) array.push(valuesToPush[i]); }, emptyDomNode: function (domNode) { while (domNode.firstChild) { ko.removeNode(domNode.firstChild); } }, setDomNodeChildren: function (domNode, childNodes) { ko.utils.emptyDomNode(domNode); if (childNodes) { ko.utils.arrayForEach(childNodes, function (childNode) { domNode.appendChild(childNode); }); } }, replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) { var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray; if (nodesToReplaceArray.length > 0) { var insertionPoint = nodesToReplaceArray[0]; var parent = insertionPoint.parentNode; for (var i = 0, j = newNodesArray.length; i < j; i++) parent.insertBefore(newNodesArray[i], insertionPoint); for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) { ko.removeNode(nodesToReplaceArray[i]); } } }, setOptionNodeSelectionState: function (optionNode, isSelected) { // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser. if (navigator.userAgent.indexOf("MSIE 6") >= 0) optionNode.setAttribute("selected", isSelected); else optionNode.selected = isSelected; }, getElementsHavingAttribute: function (rootNode, attributeName) { if ((!rootNode) || (rootNode.nodeType != 1)) return []; var results = []; if (rootNode.getAttribute(attributeName) !== null) results.push(rootNode); var descendants = rootNode.getElementsByTagName("*"); for (var i = 0, j = descendants.length; i < j; i++) if (descendants[i].getAttribute(attributeName) !== null) results.push(descendants[i]); return results; }, stringTrim: function (string) { return (string || "").replace(stringTrimRegex, ""); }, stringTokenize: function (string, delimiter) { var result = []; var tokens = (string || "").split(delimiter); for (var i = 0, j = tokens.length; i < j; i++) { var trimmed = ko.utils.stringTrim(tokens[i]); if (trimmed !== "") result.push(trimmed); } return result; }, stringStartsWith: function (string, startsWith) { string = string || ""; if (startsWith.length > string.length) return false; return string.substring(0, startsWith.length) === startsWith; }, evalWithinScope: function (expression, scope, node) { // Always do the evaling within a "new Function" to block access to parent scope if (scope === undefined) return (new Function("return " + expression))(); scope['skonode'] = node; // Ensure "expression" is flattened into a source code string *before* it runs, otherwise // the variable name "expression" itself will clash with a subproperty called "expression" // The model must available in the chain scope for arbritrary JS code to execute, but it // also must be reference by <> and [] URIs anc CURIES return (new Function("__SKO__sc", "with(__SKO__sc){ var innerNode=skonode; return (" + expression + ") }"))(scope); }, domNodeIsContainedBy: function (node, containedByNode) { if (containedByNode.compareDocumentPosition) return (containedByNode.compareDocumentPosition(node) & 16) == 16; while (node != null) { if (node == containedByNode) return true; node = node.parentNode; } return false; }, domNodeIsAttachedToDocument: function (node) { return ko.utils.domNodeIsContainedBy(node, document); }, registerEventHandler: function (element, eventType, handler) { if (typeof jQuery != "undefined") { if (isClickOnCheckableElement(element, eventType)) { // For click events on checkboxes, jQuery interferes with the event handling in an awkward way: // it toggles the element checked state *after* the click event handlers run, whereas native // click events toggle the checked state *before* the event handler. // Fix this by intecepting the handler and applying the correct checkedness before it runs. var originalHandler = handler; handler = function(event, eventData) { var jQuerySuppliedCheckedState = this.checked; if (eventData) this.checked = eventData.checkedStateBeforeEvent !== true; originalHandler.call(this, event); this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied }; } jQuery(element)['bind'](eventType, handler); } else if (typeof element.addEventListener == "function") element.addEventListener(eventType, handler, false); else if (typeof element.attachEvent != "undefined") element.attachEvent("on" + eventType, function (event) { handler.call(element, event); }); else throw new Error("Browser doesn't support addEventListener or attachEvent"); }, triggerEvent: function (element, eventType) { if (!(element && element.nodeType)) throw new Error("element must be a DOM node when calling triggerEvent"); if (typeof jQuery != "undefined") { var eventData = []; if (isClickOnCheckableElement(element, eventType)) { // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler eventData.push({ checkedStateBeforeEvent: element.checked }); } jQuery(element)['trigger'](eventType, eventData); } else if (typeof document.createEvent == "function") { if (typeof element.dispatchEvent == "function") { var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents"; var event = document.createEvent(eventCategory); event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element); element.dispatchEvent(event); } else throw new Error("The supplied element doesn't support dispatchEvent"); } else if (typeof element.fireEvent != "undefined") { // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event // so to make it consistent, we'll do it manually here if (eventType == "click") { if ((element.tagName == "INPUT") && ((element.type.toLowerCase() == "checkbox") || (element.type.toLowerCase() == "radio"))) element.checked = element.checked !== true; } element.fireEvent("on" + eventType); } else throw new Error("Browser doesn't support triggering events"); }, unwrapObservable: function (value) { return ko.isObservable(value) ? value() : value; }, domNodeHasCssClass: function (node, className) { var currentClassNames = (node.className || "").split(/\s+/); return ko.utils.arrayIndexOf(currentClassNames, className) >= 0; }, toggleDomNodeCssClass: function (node, className, shouldHaveClass) { var hasClass = ko.utils.domNodeHasCssClass(node, className); if (shouldHaveClass && !hasClass) { node.className = (node.className || "") + " " + className; } else if (hasClass && !shouldHaveClass) { var currentClassNames = (node.className || "").split(/\s+/); var newClassName = ""; for (var i = 0; i < currentClassNames.length; i++) if (currentClassNames[i] != className) newClassName += currentClassNames[i] + " "; node.className = ko.utils.stringTrim(newClassName); } }, range: function (min, max) { min = ko.utils.unwrapObservable(min); max = ko.utils.unwrapObservable(max); var result = []; for (var i = min; i <= max; i++) result.push(i); return result; }, makeArray: function(arrayLikeObject) { var result = []; for (var i = 0, j = arrayLikeObject.length; i < j; i++) { result.push(arrayLikeObject[i]); }; return result; }, isIe6 : isIe6, isIe7 : isIe7, getFormFields: function(form, fieldName) { var fields = ko.utils.makeArray(form.getElementsByTagName("INPUT")).concat(ko.utils.makeArray(form.getElementsByTagName("TEXTAREA"))); var isMatchingField = (typeof fieldName == 'string') ? function(field) { return field.name === fieldName } : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate var matches = []; for (var i = fields.length - 1; i >= 0; i--) { if (isMatchingField(fields[i])) matches.push(fields[i]); }; return matches; }, parseJson: function (jsonString) { if (typeof jsonString == "string") { jsonString = ko.utils.stringTrim(jsonString); if (jsonString) { if (window.JSON && window.JSON.parse) // Use native parsing where available return window.JSON.parse(jsonString); return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers } } return null; }, stringifyJson: function (data) { if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined")) throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js"); return JSON.stringify(ko.utils.unwrapObservable(data)); }, postJson: function (urlOrForm, data, options) { options = options || {}; var params = options['params'] || {}; var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost; var url = urlOrForm; // If we were given a form, use its 'action' URL and pick out any requested field values if((typeof urlOrForm == 'object') && (urlOrForm.tagName == "FORM")) { var originalForm = urlOrForm; url = originalForm.action; for (var i = includeFields.length - 1; i >= 0; i--) { var fields = ko.utils.getFormFields(originalForm, includeFields[i]); for (var j = fields.length - 1; j >= 0; j--) params[fields[j].name] = fields[j].value; } } data = ko.utils.unwrapObservable(data); var form = document.createElement("FORM"); form.style.display = "none"; form.action = url; form.method = "post"; for (var key in data) { var input = document.createElement("INPUT"); input.name = key; input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key])); form.appendChild(input); } for (var key in params) { var input = document.createElement("INPUT"); input.name = key; input.value = params[key]; form.appendChild(input); } document.body.appendChild(form); options['submitter'] ? options['submitter'](form) : form.submit(); setTimeout(function () { form.parentNode.removeChild(form); }, 0); } } })(); ko.exportSymbol('ko.utils', ko.utils); ko.exportSymbol('ko.utils.arrayForEach', ko.utils.arrayForEach); ko.exportSymbol('ko.utils.arrayFirst', ko.utils.arrayFirst); ko.exportSymbol('ko.utils.arrayFilter', ko.utils.arrayFilter); ko.exportSymbol('ko.utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues); ko.exportSymbol('ko.utils.arrayIndexOf', ko.utils.arrayIndexOf); ko.exportSymbol('ko.utils.arrayMap', ko.utils.arrayMap); ko.exportSymbol('ko.utils.arrayPushAll', ko.utils.arrayPushAll); ko.exportSymbol('ko.utils.arrayRemoveItem', ko.utils.arrayRemoveItem); ko.exportSymbol('ko.utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost); ko.exportSymbol('ko.utils.getElementsHavingAttribute', ko.utils.getElementsHavingAttribute); ko.exportSymbol('ko.utils.getFormFields', ko.utils.getFormFields); ko.exportSymbol('ko.utils.postJson', ko.utils.postJson); ko.exportSymbol('ko.utils.parseJson', ko.utils.parseJson); ko.exportSymbol('ko.utils.registerEventHandler', ko.utils.registerEventHandler); ko.exportSymbol('ko.utils.stringifyJson', ko.utils.stringifyJson); ko.exportSymbol('ko.utils.range', ko.utils.range); ko.exportSymbol('ko.utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass); ko.exportSymbol('ko.utils.triggerEvent', ko.utils.triggerEvent); ko.exportSymbol('ko.utils.unwrapObservable', ko.utils.unwrapObservable); if (!Function.prototype['bind']) { // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf) // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js Function.prototype['bind'] = function (object) { var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift(); return function () { return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments))); }; }; } ko.utils.domData = new (function () { var uniqueId = 0; var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime(); var dataStore = {}; return { get: function (node, key) { var allDataForNode = ko.utils.domData.getAll(node, false); return allDataForNode === undefined ? undefined : allDataForNode[key]; }, set: function (node, key, value) { if (value === undefined) { // Make sure we don't actually create a new domData key if we are actually deleting a value if (ko.utils.domData.getAll(node, false) === undefined) return; } var allDataForNode = ko.utils.domData.getAll(node, true); allDataForNode[key] = value; }, getAll: function (node, createIfNotFound) { var dataStoreKey = node[dataStoreKeyExpandoPropertyName]; if (!dataStoreKey) { if (!createIfNotFound) return undefined; dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++; dataStore[dataStoreKey] = {}; } return dataStore[dataStoreKey]; }, clear: function (node) { var dataStoreKey = node[dataStoreKeyExpandoPropertyName]; if (dataStoreKey) { delete dataStore[dataStoreKey]; node[dataStoreKeyExpandoPropertyName] = null; } } } })(); ko.utils.domNodeDisposal = new (function () { var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime(); function getDisposeCallbacksCollection(node, createIfNotFound) { var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey); if ((allDisposeCallbacks === undefined) && createIfNotFound) { allDisposeCallbacks = []; ko.utils.domData.set(node, domDataKey, allDisposeCallbacks); } return allDisposeCallbacks; } function destroyCallbacksCollection(node) { ko.utils.domData.set(node, domDataKey, undefined); } function cleanSingleNode(node) { // @modified // clean RDF observers sko.cleanNode(node) // Run all the dispose callbacks var callbacks = getDisposeCallbacksCollection(node, false); if (callbacks) { callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves) for (var i = 0; i < callbacks.length; i++) callbacks[i](node); } // Also erase the DOM data ko.utils.domData.clear(node); // 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. if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function")) jQuery['cleanData']([node]); } return { addDisposeCallback : function(node, callback) { if (typeof callback != "function") throw new Error("Callback must be a function"); getDisposeCallbacksCollection(node, true).push(callback); }, removeDisposeCallback : function(node, callback) { var callbacksCollection = getDisposeCallbacksCollection(node, false); if (callbacksCollection) { ko.utils.arrayRemoveItem(callbacksCollection, callback); if (callbacksCollection.length == 0) destroyCallbacksCollection(node); } }, cleanNode : function(node) { if ((node.nodeType != 1) && (node.nodeType != 9)) return; cleanSingleNode(node); // Clone the descendants list in case it changes during iteration var descendants = []; ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*")); for (var i = 0, j = descendants.length; i < j; i++) cleanSingleNode(descendants[i]); }, removeNode : function(node) { ko.cleanNode(node); if (node.parentNode) node.parentNode.removeChild(node); } } })(); ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience ko.exportSymbol('ko.cleanNode', ko.cleanNode); ko.exportSymbol('ko.removeNode', ko.removeNode); ko.exportSymbol('ko.utils.domNodeDisposal', ko.utils.domNodeDisposal); ko.exportSymbol('ko.utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback); ko.exportSymbol('ko.utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback); (function () { function simpleHtmlParse(html) { // Based on jQuery's "clean" function, but only accounting for table-related elements. // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly // Trim whitespace, otherwise indexOf won't work as expected var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div"); // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "", "
"] || !tags.indexOf("", ""] || (!tags.indexOf("", ""] || /* anything else */ [0, "", ""]; // Go to html and back, then peel off extra wrappers div.innerHTML = wrap[1] + html + wrap[2]; // Move to the right depth while (wrap[0]--) div = div.lastChild; return ko.utils.makeArray(div.childNodes); } ko.utils.parseHtmlFragment = function(html) { return typeof jQuery != 'undefined' ? jQuery['clean']([html]) // As below, benefit from jQuery's optimisations where possible : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases. }; ko.utils.setHtml = function(node, html) { ko.utils.emptyDomNode(node); if ((html !== null) && (html !== undefined)) { if (typeof html != 'string') html = html.toString(); // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments, // for example elements which are not normally allowed to exist on their own. // If you've referenced jQuery we'll use that rather than duplicating its code. if (typeof jQuery != 'undefined') { try { jQuery(node)['html'](html); } catch (x) { jQuery(node)['html'](html.replace("<","<").replace(">",">")); } } else { // ... otherwise, use KO's own parsing logic. var parsedNodes = ko.utils.parseHtmlFragment(html); for (var i = 0; i < parsedNodes.length; i++) node.appendChild(parsedNodes[i]); } } }; })(); ko.memoization = (function () { var memos = {}; function randomMax8HexChars() { return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); } function generateRandomId() { return randomMax8HexChars() + randomMax8HexChars(); } function findMemoNodes(rootNode, appendToArray) { if (!rootNode) return; if (rootNode.nodeType == 8) { var memoId = ko.memoization.parseMemoText(rootNode.nodeValue); if (memoId != null) appendToArray.push({ domNode: rootNode, memoId: memoId }); } else if (rootNode.nodeType == 1) { for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++) findMemoNodes(childNodes[i], appendToArray); } } return { memoize: function (callback) { if (typeof callback != "function") throw new Error("You can only pass a function to ko.memoization.memoize()"); var memoId = generateRandomId(); memos[memoId] = callback; return ""; }, unmemoize: function (memoId, callbackParams) { var callback = memos[memoId]; if (callback === undefined) throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized."); try { callback.apply(null, callbackParams || []); return true; } finally { delete memos[memoId]; } }, unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) { var memos = []; findMemoNodes(domNode, memos); for (var i = 0, j = memos.length; i < j; i++) { var node = memos[i].domNode; var combinedParams = [node]; if (extraCallbackParamsArray) ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray); var viewModel = extraCallbackParamsArray[0]; sko.traceResources(domNode, viewModel, function(){ sko.traceRelations(domNode, viewModel, function(){ try{ if(memos[i]) { ko.memoization.unmemoize(memos[i].memoId, combinedParams); node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again } if (node.parentNode) node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again) }catch(e) {} }); }); } }, parseMemoText: function (memoText) { var match = memoText.match(/^\[ko_memo\:(.*?)\]$/); return match ? match[1] : null; } }; })(); ko.exportSymbol('ko.memoization', ko.memoization); ko.exportSymbol('ko.memoization.memoize', ko.memoization.memoize); ko.exportSymbol('ko.memoization.unmemoize', ko.memoization.unmemoize); ko.exportSymbol('ko.memoization.parseMemoText', ko.memoization.parseMemoText); ko.exportSymbol('ko.memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants); ko.subscription = function (callback, disposeCallback) { this.callback = callback; this.dispose = function () { this.isDisposed = true; disposeCallback(); }['bind'](this); ko.exportProperty(this, 'dispose', this.dispose); }; ko.subscribable = function () { var _subscriptions = []; this.subscribe = function (callback, callbackTarget) { var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback; var subscription = new ko.subscription(boundCallback, function () { ko.utils.arrayRemoveItem(_subscriptions, subscription); }); _subscriptions.push(subscription); return subscription; }; this.notifySubscribers = function (valueToNotify) { ko.utils.arrayForEach(_subscriptions.slice(0), function (subscription) { // In case a subscription was disposed during the arrayForEach cycle, check // for isDisposed on each subscription before invoking its callback if (subscription && (subscription.isDisposed !== true)) subscription.callback(valueToNotify); }); }; this.getSubscriptionsCount = function () { return _subscriptions.length; }; ko.exportProperty(this, 'subscribe', this.subscribe); ko.exportProperty(this, 'notifySubscribers', this.notifySubscribers); ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount); } ko.isSubscribable = function (instance) { return typeof instance.subscribe == "function" && typeof instance.notifySubscribers == "function"; }; ko.exportSymbol('ko.subscribable', ko.subscribable); ko.exportSymbol('ko.isSubscribable', ko.isSubscribable); ko.dependencyDetection = (function () { var _detectedDependencies = []; return { begin: function () { _detectedDependencies.push([]); }, end: function () { return _detectedDependencies.pop(); }, registerDependency: function (subscribable) { if (!ko.isSubscribable(subscribable)) throw "Only subscribable things can act as dependencies"; if (_detectedDependencies.length > 0) { _detectedDependencies[_detectedDependencies.length - 1].push(subscribable); } } }; })();var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true }; function valuesArePrimitiveAndEqual(a, b) { var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes); return oldValueIsPrimitive ? (a === b) : false; } ko.observable = function (initialValue) { var _latestValue = initialValue; function observable() { if (arguments.length > 0) { // Write // Ignore writes if the value hasn't changed if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) { _latestValue = arguments[0]; observable.notifySubscribers(_latestValue); } return this; // Permits chained assignments } else { // Read ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation return _latestValue; } } observable.__ko_proto__ = ko.observable; observable.valueHasMutated = function () { observable.notifySubscribers(_latestValue); } observable['equalityComparer'] = valuesArePrimitiveAndEqual; ko.subscribable.call(observable); ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated); return observable; } ko.isObservable = function (instance) { if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) return false; if (instance.__ko_proto__ === ko.observable) return true; return ko.isObservable(instance.__ko_proto__); // Walk the prototype chain } ko.isWriteableObservable = function (instance) { // Observable if ((typeof instance == "function") && instance.__ko_proto__ === ko.observable) return true; // Writeable dependent observable if ((typeof instance == "function") && (instance.__ko_proto__ === ko.dependentObservable) && (instance.hasWriteFunction)) return true; // Anything else return false; } ko.exportSymbol('ko.observable', ko.observable); ko.exportSymbol('ko.isObservable', ko.isObservable); ko.exportSymbol('ko.isWriteableObservable', ko.isWriteableObservable); ko.observableArray = function (initialValues) { if (arguments.length == 0) { // Zero-parameter constructor initializes to empty array initialValues = []; } if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues)) throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined."); var result = new ko.observable(initialValues); ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) { result[methodName] = function () { var underlyingArray = result(); var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments); result.valueHasMutated(); return methodCallResult; }; }); ko.utils.arrayForEach(["slice"], function (methodName) { result[methodName] = function () { var underlyingArray = result(); return underlyingArray[methodName].apply(underlyingArray, arguments); }; }); result.remove = function (valueOrPredicate) { var underlyingArray = result(); var remainingValues = []; var removedValues = []; var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; for (var i = 0, j = underlyingArray.length; i < j; i++) { var value = underlyingArray[i]; if (!predicate(value)) remainingValues.push(value); else removedValues.push(value); } result(remainingValues); return removedValues; }; result.removeAll = function (arrayOfValues) { // If you passed zero args, we remove everything if (arrayOfValues === undefined) { var allValues = result(); result([]); return allValues; } // If you passed an arg, we interpret it as an array of entries to remove if (!arrayOfValues) return []; return result.remove(function (value) { return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0; }); }; result.destroy = function (valueOrPredicate) { var underlyingArray = result(); var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; }; for (var i = underlyingArray.length - 1; i >= 0; i--) { var value = underlyingArray[i]; if (predicate(value)) underlyingArray[i]["_destroy"] = true; } result.valueHasMutated(); }; result.destroyAll = function (arrayOfValues) { // If you passed zero args, we destroy everything if (arrayOfValues === undefined) return result.destroy(function() { return true }); // If you passed an arg, we interpret it as an array of entries to destroy if (!arrayOfValues) return []; return result.destroy(function (value) { return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0; }); }; result.indexOf = function (item) { var underlyingArray = result(); return ko.utils.arrayIndexOf(underlyingArray, item); }; result.replace = function(oldItem, newItem) { var index = result.indexOf(oldItem); if (index >= 0) { result()[index] = newItem; result.valueHasMutated(); } }; ko.exportProperty(result, "remove", result.remove); ko.exportProperty(result, "removeAll", result.removeAll); ko.exportProperty(result, "destroy", result.destroy); ko.exportProperty(result, "destroyAll", result.destroyAll); ko.exportProperty(result, "indexOf", result.indexOf); return result; } ko.exportSymbol('ko.observableArray', ko.observableArray); ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) { var _latestValue, _hasBeenEvaluated = false; if (evaluatorFunctionOrOptions && typeof evaluatorFunctionOrOptions == "object") { // Single-parameter syntax - everything is on this "options" param options = evaluatorFunctionOrOptions; } else { // Multi-parameter syntax - construct the options according to the params passed options = options || {}; options["read"] = evaluatorFunctionOrOptions || options["read"]; options["owner"] = evaluatorFunctionTarget || options["owner"]; } // By here, "options" is always non-null if (typeof options["read"] != "function") throw "Pass a function that returns the value of the dependentObservable"; // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(), // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.) var disposeWhenNodeIsRemoved = (typeof options["disposeWhenNodeIsRemoved"] == "object") ? options["disposeWhenNodeIsRemoved"] : null; var disposeWhenNodeIsRemovedCallback = null; if (disposeWhenNodeIsRemoved) { disposeWhenNodeIsRemovedCallback = function() { dependentObservable.dispose() }; ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback); var existingDisposeWhenFunction = options["disposeWhen"]; options["disposeWhen"] = function () { return (!ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved)) || ((typeof existingDisposeWhenFunction == "function") && existingDisposeWhenFunction()); } } var _subscriptionsToDependencies = []; function disposeAllSubscriptionsToDependencies() { ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) { subscription.dispose(); }); _subscriptionsToDependencies = []; } function replaceSubscriptionsToDependencies(newDependencies) { disposeAllSubscriptionsToDependencies(); ko.utils.arrayForEach(newDependencies, function (dependency) { _subscriptionsToDependencies.push(dependency.subscribe(evaluate)); }); }; function evaluate() { // Don't dispose on first evaluation, because the "disposeWhen" callback might // e.g., dispose when the associated DOM element isn't in the doc, and it's not // going to be in the doc until *after* the first evaluation if ((_hasBeenEvaluated) && typeof options["disposeWhen"] == "function") { if (options["disposeWhen"]()) { dependentObservable.dispose(); return; } } try { ko.dependencyDetection.begin(); _latestValue = options["owner"] ? options["read"].call(options["owner"]) : options["read"](); } finally { var distinctDependencies = ko.utils.arrayGetDistinctValues(ko.dependencyDetection.end()); replaceSubscriptionsToDependencies(distinctDependencies); } dependentObservable.notifySubscribers(_latestValue); _hasBeenEvaluated = true; } function dependentObservable() { if (arguments.length > 0) { if (typeof options["write"] === "function") { // Writing a value var valueToWrite = arguments[0]; options["owner"] ? options["write"].call(options["owner"], valueToWrite) : options["write"](valueToWrite); } else { throw "Cannot write a value to a dependentObservable unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters."; } } else { // Reading the value if (!_hasBeenEvaluated) evaluate(); ko.dependencyDetection.registerDependency(dependentObservable); return _latestValue; } } dependentObservable.__ko_proto__ = ko.dependentObservable; dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; } dependentObservable.hasWriteFunction = typeof options["write"] === "function"; dependentObservable.dispose = function () { if (disposeWhenNodeIsRemoved) ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, disposeWhenNodeIsRemovedCallback); disposeAllSubscriptionsToDependencies(); }; ko.subscribable.call(dependentObservable); if (options['deferEvaluation'] !== true) evaluate(); ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose); ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount); return dependentObservable; }; ko.dependentObservable.__ko_proto__ = ko.observable; ko.exportSymbol('ko.dependentObservable', ko.dependentObservable); (function() { var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle) ko.toJS = function(rootObject) { if (arguments.length == 0) throw new Error("When calling ko.toJS, pass the object you want to convert."); // We just unwrap everything at every level in the object graph return mapJsObjectGraph(rootObject, function(valueToMap) { // Loop because an observable's value might in turn be another observable wrapper for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++) valueToMap = valueToMap(); return valueToMap; }); }; ko.toJSON = function(rootObject) { var plainJavaScriptObject = ko.toJS(rootObject); return ko.utils.stringifyJson(plainJavaScriptObject); }; function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) { visitedObjects = visitedObjects || new objectLookup(); rootObject = mapInputCallback(rootObject); var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined); if (!canHaveProperties) return rootObject; var outputProperties = rootObject instanceof Array ? [] : {}; visitedObjects.save(rootObject, outputProperties); visitPropertiesOrArrayEntries(rootObject, function(indexer) { var propertyValue = mapInputCallback(rootObject[indexer]); switch (typeof propertyValue) { case "boolean": case "number": case "string": case "function": outputProperties[indexer] = propertyValue; break; case "object": case "undefined": var previouslyMappedValue = visitedObjects.get(propertyValue); outputProperties[indexer] = (previouslyMappedValue !== undefined) ? previouslyMappedValue : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects); break; } }); return outputProperties; } function visitPropertiesOrArrayEntries(rootObject, visitorCallback) { if (rootObject instanceof Array) { for (var i = 0; i < rootObject.length; i++) visitorCallback(i); } else { for (var propertyName in rootObject) visitorCallback(propertyName); } }; function objectLookup() { var keys = []; var values = []; this.save = function(key, value) { var existingIndex = ko.utils.arrayIndexOf(keys, key); if (existingIndex >= 0) values[existingIndex] = value; else { keys.push(key); values.push(value); } }; this.get = function(key) { var existingIndex = ko.utils.arrayIndexOf(keys, key); return (existingIndex >= 0) ? values[existingIndex] : undefined; }; }; })(); ko.exportSymbol('ko.toJS', ko.toJS); ko.exportSymbol('ko.toJSON', ko.toJSON);(function () { // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns. ko.selectExtensions = { readValue : function(element) { if (element.tagName == 'OPTION') { if (element['__ko__hasDomDataOptionValue__'] === true) return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey); return element.getAttribute("value"); } else if (element.tagName == 'SELECT') return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined; else return element.value; }, writeValue: function(element, value) { if (element.tagName == 'OPTION') { switch(typeof value) { case "string": case "number": ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined); if ('__ko__hasDomDataOptionValue__' in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node delete element['__ko__hasDomDataOptionValue__']; } element.value = value; break; default: // Store arbitrary object using DomData ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value); element['__ko__hasDomDataOptionValue__'] = true; element.value = ""; break; } } else if (element.tagName == 'SELECT') { for (var i = element.options.length - 1; i >= 0; i--) { if (ko.selectExtensions.readValue(element.options[i]) == value) { element.selectedIndex = i; break; } } } else { if ((value === null) || (value === undefined)) value = ""; element.value = value; } } }; })(); ko.exportSymbol('ko.selectExtensions', ko.selectExtensions); ko.exportSymbol('ko.selectExtensions.readValue', ko.selectExtensions.readValue); ko.exportSymbol('ko.selectExtensions.writeValue', ko.selectExtensions.writeValue); ko.jsonExpressionRewriting = (function () { var restoreCapturedTokensRegex = /\[ko_token_(\d+)\]/g; var javaScriptAssignmentTarget = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i; var javaScriptReservedWords = ["true", "false"]; function restoreTokens(string, tokens) { return string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) { return tokens[tokenIndex]; }); } function isWriteableValue(expression) { if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0) return false; if(expression[0]=="<" && expression[expression.length-1]==">") return true; return expression.match(javaScriptAssignmentTarget) !== null; } return { parseJson: function (jsonString) { jsonString = ko.utils.stringTrim(jsonString); if (jsonString.length < 3) return {}; //@modified // added counter of nested curly braces // We're going to split on commas, so first extract any blocks that may contain commas other than those at the top level var tokens = []; var tokenStart = null, tokenEndChar, tokenCounter; for (var position = jsonString.charAt(0) == "{" ? 1 : 0; position < jsonString.length; position++) { var c = jsonString.charAt(position); if (tokenStart === null) { switch (c) { case '"': case "'": case "/": tokenStart = position; tokenEndChar = c; break; case "<": tokenStart = position; tokenEndChar = ">"; break; case "{": tokenStart = position; tokenCounter = 1; tokenEndChar = "}"; break; case "[": tokenStart = position; tokenEndChar = "]"; break; } } else if(tokenEndChar == "}" && c == "{") { tokenCounter++; } else if(tokenEndChar == "}" && c == "}" && tokenCounter>1) { tokenCounter--; } else if (c == tokenEndChar) { var token = jsonString.substring(tokenStart, position + 1); tokens.push(token); var replacement = "[ko_token_" + (tokens.length - 1) + "]"; jsonString = jsonString.substring(0, tokenStart) + replacement + jsonString.substring(position + 1); position -= (token.length - replacement.length); tokenStart = null; } } // Now we can safely split on commas to get the key/value pairs var result = {}; var keyValuePairs = jsonString.split(","); for (var i = 0, j = keyValuePairs.length; i < j; i++) { var pair = keyValuePairs[i]; var colonPos = pair.indexOf(":"); if ((colonPos > 0) && (colonPos < pair.length - 1)) { var key = ko.utils.stringTrim(pair.substring(0, colonPos)); var value = ko.utils.stringTrim(pair.substring(colonPos + 1)); if (key.charAt(0) == "{") key = key.substring(1); if (value.charAt(value.length - 1) == "}") value = value.substring(0, value.length - 1); key = ko.utils.stringTrim(restoreTokens(key, tokens)); value = ko.utils.stringTrim(restoreTokens(value, tokens)); result[key] = value; } } return result; }, insertPropertyAccessorsIntoJson: function (jsonString) { var parsed = ko.jsonExpressionRewriting.parseJson(jsonString); var propertyAccessorTokens = []; for (var key in parsed) { var value = parsed[key]; if (isWriteableValue(value)) { if (propertyAccessorTokens.length > 0) propertyAccessorTokens.push(", "); propertyAccessorTokens.push(key + " : function(__ko_value) { " + value + " = __ko_value; }"); } } if (propertyAccessorTokens.length > 0) { var allPropertyAccessors = propertyAccessorTokens.join(""); jsonString = jsonString + ", '_ko_property_writers' : { " + allPropertyAccessors + " } "; } return jsonString; }, //@modified parseURIsInJSONString: function(jsonString) { var re = /["']?(<|\[)[a-z:\/.#?&%]+(\]|>)['"]?/g; var acum = ""; var found = re.exec(jsonString); while(found != null) { if((found[0][0] === "'" || found[0][0] === '"') && (found[0][found[0].length-1] === "'" || found[0][found[0].length-1] === '"')) { var parts = jsonString.split(found[0]); acum = acum + parts[0] + found[0]; jsonString = parts[1]; } else { var w = found[0]; var index = found.index; var pref = jsonString.substring(0,index); acum = pref+"sko.current().tryProperty('"+w+"')"; jsonString= jsonString.substring(index+w.length); } found = re.exec(jsonString); } return acum+jsonString; }, insertPropertyReaderWritersIntoJson: function (jsonString) { var parsed = ko.jsonExpressionRewriting.parseJson(jsonString); var propertyAccessorTokens = []; var readers = ""; var isFirst = true; for (var key in parsed) { var value = parsed[key]; value = this.parseURIsInJSONString(value); if (isWriteableValue(value)) { if (propertyAccessorTokens.length > 0) propertyAccessorTokens.push(", "); if(value[0]==="<" && value[value.length-1]===">" && key !== 'about' && key !== 'rel') { propertyAccessorTokens.push(key + " : function(__ko_value) { sko.current = function() { return sko.currentResource(innerNode); }; sko.current().tryProperty('" + value + "') = __ko_value; }"); } else if(value.match(/^\[[^,;"\]\}\{\[\.:]+:[^,;"\}\]\{\[\.:]+\]$/) != null && key !== 'about' && key !== 'rel') { propertyAccessorTokens.push(key + " : function(__ko_value) { sko.current = function() { return sko.currentResource(innerNode); }; sko.current().tryProperty('" + value + "') = __ko_value; }"); } else if(value[0]==="<" && value[value.length-1]===">" && (key === 'about' || key === 'rel')) { // nothing here } else if(value[0]==="[" && value[value.length-1]==="]" && (key === 'about' || key === 'rel')) { // nothing here } else { if(/tryProperty\([^)]+\)$/.test(value) || /prop\([^)]+\)$/.test(value)) { propertyAccessorTokens.push(key + " : function(__ko_value) { sko.current = function() { return sko.currentResource(innerNode); }; " + value + "(__ko_value); }"); } else { propertyAccessorTokens.push(key + " : function(__ko_value) { sko.current = function() { return sko.currentResource(innerNode); }; " + value + " = __ko_value; }"); } } } if(!isFirst) { readers = readers+", "; } else { isFirst = false; } if(value[0]==='<' && value[value.length-1]==='>' && key !== 'about' && key !== 'rel') { readers = readers+key+": (function(){ sko.current = function() { return sko.currentResource(innerNode); }; return sko.current().tryProperty('"+value+"') })()"; } else if(value.match(/^\[[^,;"\]\}\{\[\.:]+:[^,;"\}\]\{\[\.:]+\]$/) != null && key !== 'about' && key !== 'rel') { readers = readers+key+": (function(){ sko.current = function() { return sko.currentResource(innerNode); }; return sko.current().tryProperty('"+value+"') })()"; } else if(value[0]==="<" && value[value.length-1]===">" && (key === 'about' || key === 'rel')) { readers = readers+key+": '"+value.slice(1,value.length-1)+"'"; } else if(value.match(/^\[[^,;"\]\}\{\[\.:]+:[^,;"\}\]\{\[\.:]+\]$/) != null && (key === 'about' || key === 'rel')) { readers = readers+key+": sko.rdf.prefixes.resolve('"+value.slice(1,value.length-1)+"')"; } else { readers = readers+key+": (function(){ sko.current = function() { return sko.currentResource(innerNode); }; return "+value+" })()"; } } jsonString = readers; if (propertyAccessorTokens.length > 0) { var allPropertyAccessors = propertyAccessorTokens.join(""); jsonString = jsonString + ", '_ko_property_writers' : { " + allPropertyAccessors + " } "; } return jsonString; } }; })(); ko.exportSymbol('ko.jsonExpressionRewriting', ko.jsonExpressionRewriting); ko.exportSymbol('ko.jsonExpressionRewriting.parseJson', ko.jsonExpressionRewriting.parseJson); ko.exportSymbol('ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson); ko.exportSymbol('ko.jsonExpressionRewriting.insertPropertyReaderWritersIntoJson', ko.jsonExpressionRewriting.insertPropertyReaderWritersIntoJson); (function () { var defaultBindingAttributeName = "data-bind"; ko.bindingHandlers = {}; function parseBindingAttribute(attributeText, viewModel, node) { try { var json = " { " + ko.jsonExpressionRewriting.insertPropertyReaderWritersIntoJson(attributeText) + " } "; return ko.utils.evalWithinScope(json, viewModel === null ? window : viewModel, node); } catch (ex) { if(typeof(console) !== 'undefined') { console.log("!!! ERROR"); console.log(attributeText); console.log(ex); } //@modified //throw new Error("Unable to parse binding attribute.\nMessage: " + ex + ";\nAttribute value: " + attributeText); } }; function invokeBindingHandler(handler, element, dataValue, allBindings, viewModel) { handler(element, dataValue, allBindings, viewModel); } ko.applyBindingsToNode = function (node, bindings, viewModel, bindingAttributeName) { var isFirstEvaluation = true; bindingAttributeName = bindingAttributeName || defaultBindingAttributeName; // Each time the dependentObservable is evaluated (after data changes), // the binding attribute is reparsed so that it can pick out the correct // model properties in the context of the changed data. // DOM event callbacks need to be able to access this changed data, // so we need a single parsedBindings variable (shared by all callbacks // associated with this node's bindings) that all the closures can access. var parsedBindings; function makeValueAccessor(bindingKey) { return function () { return parsedBindings[bindingKey] } } function parsedBindingsAccessor() { return parsedBindings; } new ko.dependentObservable( function () { var evaluatedBindings, bindingsToBeEvaluated; if(typeof(bindings) == 'function') { viewModel['skonode'] = node; bindingsToBeEvaluated = bindings; with(viewModel){ evaluatedBindings = bindingsToBeEvaluated() }; } else { evaluatedBindings = bindings; } parsedBindings = evaluatedBindings || parseBindingAttribute(node.getAttribute(bindingAttributeName), viewModel, node); // First run all the inits, so bindings can register for notification on changes if (isFirstEvaluation) { for (var bindingKey in parsedBindings) { if (ko.bindingHandlers[bindingKey] && typeof ko.bindingHandlers[bindingKey]["init"] == "function") invokeBindingHandler(ko.bindingHandlers[bindingKey]["init"], node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel); } } // ... then run all the updates, which might trigger changes even on the first evaluation for (var bindingKey in parsedBindings) { if (ko.bindingHandlers[bindingKey] && typeof ko.bindingHandlers[bindingKey]["update"] == "function") invokeBindingHandler(ko.bindingHandlers[bindingKey]["update"], node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel); } }, null, { 'disposeWhenNodeIsRemoved' : node } ); isFirstEvaluation = false; }; ko.applyBindings = function (viewModel, rootNode) { if (rootNode && (rootNode.nodeType == undefined)) throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node (note: this is a breaking change since KO version 1.05)"); rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional var elemsWithBindingAttribute = ko.utils.getElementsHavingAttribute(rootNode, defaultBindingAttributeName); ko.utils.arrayForEach(elemsWithBindingAttribute, function (element) { ko.applyBindingsToNode(element, null, viewModel); }); }; ko.exportSymbol('ko.bindingHandlers', ko.bindingHandlers); ko.exportSymbol('ko.applyBindings', ko.applyBindings); ko.exportSymbol('ko.applyBindingsToNode', ko.applyBindingsToNode); })(); // For certain common events (currently just 'click'), allow a simplified data-binding syntax // e.g. click:handler instead of the usual full-length event:{click:handler} var eventHandlersWithShortcuts = ['click']; ko.utils.arrayForEach(eventHandlersWithShortcuts, function(eventName) { ko.bindingHandlers[eventName] = { 'init': function(element, valueAccessor, allBindingsAccessor, viewModel) { var newValueAccessor = function () { var result = {}; result[eventName] = valueAccessor(); return result; }; return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel); } } }); ko.bindingHandlers['event'] = { 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) { var eventsToHandle = valueAccessor() || {}; for(var eventNameOutsideClosure in eventsToHandle) { (function() { var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure if (typeof eventName == "string") { ko.utils.registerEventHandler(element, eventName, function (event) { var handlerReturnValue; var handlerFunction = valueAccessor()[eventName]; if (!handlerFunction) return; var allBindings = allBindingsAccessor(); try { handlerReturnValue = handlerFunction.apply(viewModel, arguments); } finally { if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. if (event.preventDefault) event.preventDefault(); else event.returnValue = false; } } var bubble = allBindings[eventName + 'Bubble'] !== false; if (!bubble) { event.cancelBubble = true; if (event.stopPropagation) event.stopPropagation(); } }); } })(); } } }; ko.bindingHandlers['submit'] = { 'init': function (element, valueAccessor, allBindingsAccessor, viewModel) { if (typeof valueAccessor() != "function") throw new Error("The value for a submit binding must be a function to invoke on submit"); ko.utils.registerEventHandler(element, "submit", function (event) { var handlerReturnValue; var value = valueAccessor(); try { handlerReturnValue = value.call(viewModel, element); } finally { if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true. if (event.preventDefault) event.preventDefault(); else event.returnValue = false; } } }); } }; ko.bindingHandlers['visible'] = { 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); var isCurrentlyVisible = !(element.style.display == "none"); if (value && !isCurrentlyVisible) element.style.display = ""; else if ((!value) && isCurrentlyVisible) element.style.display = "none"; } } ko.bindingHandlers['enable'] = { 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); if (value && element.disabled) element.removeAttribute("disabled"); else if ((!value) && (!element.disabled)) element.disabled = true; } }; ko.bindingHandlers['disable'] = { 'update': function (element, valueAccessor) { ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) }); } }; ko.bindingHandlers['value'] = { 'init': function (element, valueAccessor, allBindingsAccessor) { // Always catch "change" event; possibly other events too if asked var eventsToCatch = ["change"]; var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"]; if (requestedEventsToCatch) { if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names requestedEventsToCatch = [requestedEventsToCatch]; ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch); eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch); } ko.utils.arrayForEach(eventsToCatch, function(eventName) { // The syntax "after" means "run the handler asynchronously after the event" // This is useful, for example, to catch "keydown" events after the browser has updated the control // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event) var handleEventAsynchronously = false; if (ko.utils.stringStartsWith(eventName, "after")) { handleEventAsynchronously = true; eventName = eventName.substring("after".length); } var runEventHandler = handleEventAsynchronously ? function(handler) { setTimeout(handler, 0) } : function(handler) { handler() }; ko.utils.registerEventHandler(element, eventName, function () { runEventHandler(function() { var modelValue = valueAccessor(); var elementValue = ko.selectExtensions.readValue(element); if (ko.isWriteableObservable(modelValue)) modelValue(elementValue); else { var allBindings = allBindingsAccessor(); if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value']) allBindings['_ko_property_writers']['value'](elementValue); } }); }); }); }, 'update': function (element, valueAccessor) { var newValue = ko.utils.unwrapObservable(valueAccessor()); var elementValue = ko.selectExtensions.readValue(element); var valueHasChanged = (newValue != elementValue); // JavaScript's 0 == "" behavious is unfortunate here as it prevents writing 0 to an empty text box (loose equality suggests the values are the same). // We don't want to do a strict equality comparison as that is more confusing for developers in certain cases, so we specifically special case 0 != "" here. if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0")) valueHasChanged = true; if (valueHasChanged) { var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); }; applyValueAction(); // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread // to apply the value as well. var alsoApplyAsynchronously = element.tagName == "SELECT"; if (alsoApplyAsynchronously) setTimeout(applyValueAction, 0); } // For SELECT nodes, you're not allowed to have a model value that disagrees with the UI selection, so if there is a // difference, treat it as a change that should be written back to the model if (element.tagName == "SELECT") { elementValue = ko.selectExtensions.readValue(element); if(elementValue !== newValue) ko.utils.triggerEvent(element, "change"); } } }; ko.bindingHandlers['options'] = { 'update': function (element, valueAccessor, allBindingsAccessor) { if (element.tagName != "SELECT") throw new Error("options binding applies only to SELECT elements"); var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) { return node.tagName && node.tagName == "OPTION" && node.selected; }), function (node) { return ko.selectExtensions.readValue(node) || node.innerText || node.textContent; }); var previousScrollTop = element.scrollTop; var value = ko.utils.unwrapObservable(valueAccessor()); var selectedValue = element.value; ko.utils.emptyDomNode(element); if (value) { var allBindings = allBindingsAccessor(); if (typeof value.length != "number") value = [value]; if (allBindings['optionsCaption']) { var option = document.createElement("OPTION"); option.innerHTML = allBindings['optionsCaption']; ko.selectExtensions.writeValue(option, undefined); element.appendChild(option); } for (var i = 0, j = value.length; i < j; i++) { var option = document.createElement("OPTION"); // Apply a value to the option element var optionValue = typeof allBindings['optionsValue'] == "string" ? value[i][allBindings['optionsValue']] : value[i]; optionValue = ko.utils.unwrapObservable(optionValue); ko.selectExtensions.writeValue(option, optionValue); // Apply some text to the option element var optionsTextValue = allBindings['optionsText']; if (typeof optionsTextValue == "function") optionText = optionsTextValue(value[i]); // Given a function; run it against the data value else if (typeof optionsTextValue == "string") optionText = value[i][optionsTextValue]; // Given a string; treat it as a property name on the data value else optionText = optionValue; // Given no optionsText arg; use the data value itself if ((optionText === null) || (optionText === undefined)) optionText = ""; optionText = ko.utils.unwrapObservable(optionText).toString(); typeof option.innerText == "string" ? option.innerText = optionText : option.textContent = optionText; element.appendChild(option); } // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document. // That's why we first added them without selection. Now it's time to set the selection. var newOptions = element.getElementsByTagName("OPTION"); var countSelectionsRetained = 0; for (var i = 0, j = newOptions.length; i < j; i++) { if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) { ko.utils.setOptionNodeSelectionState(newOptions[i], true); countSelectionsRetained++; } } if (previousScrollTop) element.scrollTop = previousScrollTop; } } }; ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.bindingHandlers.options.optionValueDomData__'; ko.bindingHandlers['selectedOptions'] = { getSelectedValuesFromSelectNode: function (selectNode) { var result = []; var nodes = selectNode.childNodes; for (var i = 0, j = nodes.length; i < j; i++) { var node = nodes[i]; if ((node.tagName == "OPTION") && node.selected) result.push(ko.selectExtensions.readValue(node)); } return result; }, 'init': function (element, valueAccessor, allBindingsAccessor) { ko.utils.registerEventHandler(element, "change", function () { var value = valueAccessor(); if (ko.isWriteableObservable(value)) value(ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this)); else { var allBindings = allBindingsAccessor(); if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['value']) allBindings['_ko_property_writers']['value'](ko.bindingHandlers['selectedOptions'].getSelectedValuesFromSelectNode(this)); } }); }, 'update': function (element, valueAccessor) { if (element.tagName != "SELECT") throw new Error("values binding applies only to SELECT elements"); var newValue = ko.utils.unwrapObservable(valueAccessor()); if (newValue && typeof newValue.length == "number") { var nodes = element.childNodes; for (var i = 0, j = nodes.length; i < j; i++) { var node = nodes[i]; if (node.tagName == "OPTION") ko.utils.setOptionNodeSelectionState(node, ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0); } } } }; ko.bindingHandlers['text'] = { 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); if ((value === null) || (value === undefined)) value = ""; typeof element.innerText == "string" ? element.innerText = value : element.textContent = value; } }; ko.bindingHandlers['html'] = { 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); ko.utils.setHtml(element, value); } }; ko.bindingHandlers['css'] = { 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor() || {}); for (var className in value) { if (typeof className == "string") { var shouldHaveClass = ko.utils.unwrapObservable(value[className]); ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass); } } } }; ko.bindingHandlers['style'] = { 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor() || {}); for (var styleName in value) { if (typeof styleName == "string") { var styleValue = ko.utils.unwrapObservable(value[styleName]); element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect } } } }; ko.bindingHandlers['uniqueName'] = { 'init': function (element, valueAccessor) { if (valueAccessor()) { element.name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex); // Workaround IE 6 issue - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/ if (ko.utils.isIe6) element.mergeAttributes(document.createElement(""), false); } } }; ko.bindingHandlers['uniqueName'].currentIndex = 0; ko.bindingHandlers['checked'] = { 'init': function (element, valueAccessor, allBindingsAccessor) { var updateHandler = function() { var valueToWrite; if (element.type == "checkbox") { valueToWrite = element.checked; } else if ((element.type == "radio") && (element.checked)) { valueToWrite = element.value; } else { return; // "checked" binding only responds to checkboxes and selected radio buttons } var modelValue = valueAccessor(); if ((element.type == "checkbox") && (ko.utils.unwrapObservable(modelValue) instanceof Array)) { // For checkboxes bound to an array, we add/remove the checkbox value to that array // This works for both observable and non-observable arrays var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.unwrapObservable(modelValue), element.value); if (element.checked && (existingEntryIndex < 0)) modelValue.push(element.value); else if ((!element.checked) && (existingEntryIndex >= 0)) modelValue.splice(existingEntryIndex, 1); } else if (ko.isWriteableObservable(modelValue)) { if (modelValue() !== valueToWrite) { // Suppress repeated events when there's nothing new to notify (some browsers raise them) modelValue(valueToWrite); } } else { var allBindings = allBindingsAccessor(); if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['checked']) { allBindings['_ko_property_writers']['checked'](valueToWrite); } } }; ko.utils.registerEventHandler(element, "click", updateHandler); // IE 6 won't allow radio buttons to be selected unless they have a name if ((element.type == "radio") && !element.name) ko.bindingHandlers['uniqueName']['init'](element, function() { return true }); }, 'update': function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); if (element.type == "checkbox") { if (value instanceof Array) { // When bound to an array, the checkbox being checked represents its value being present in that array element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0; } else { // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish element.checked = value; } // Workaround for IE 6 bug - it fails to apply checked state to dynamically-created checkboxes if you merely say "element.checked = true" if (value && ko.utils.isIe6) element.mergeAttributes(document.createElement(""), false); } else if (element.type == "radio") { element.checked = (element.value == value); // Workaround for IE 6/7 bug - it fails to apply checked state to dynamically-created radio buttons if you merely say "element.checked = true" if ((element.value == value) && (ko.utils.isIe6 || ko.utils.isIe7)) element.mergeAttributes(document.createElement(""), false); } } }; ko.bindingHandlers['attr'] = { update: function(element, valueAccessor, allBindingsAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()) || {}; for (var attrName in value) { if (typeof attrName == "string") { var attrValue = ko.utils.unwrapObservable(value[attrName]); // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely // when someProp is a "no value"-like value (strictly null, false, or undefined) // (because the absence of the "checked" attr is how to mark an element as not checked, etc.) if ((attrValue === false) || (attrValue === null) || (attrValue === undefined)) element.removeAttribute(attrName); else // @modified var actualValue = attrValue.toString(); if(actualValue) { if(actualValue[0] === '<' && actualValue[actualValue.length-1] === '>') { actualValue = actualValue.substring(1,actualValue.length-1); } element.setAttribute(attrName, actualValue); } } } } }; ko.templateEngine = function () { this['renderTemplate'] = function (templateName, data, options) { throw "Override renderTemplate in your ko.templateEngine subclass"; }, this['isTemplateRewritten'] = function (templateName) { throw "Override isTemplateRewritten in your ko.templateEngine subclass"; }, this['rewriteTemplate'] = function (templateName, rewriterCallback) { throw "Override rewriteTemplate in your ko.templateEngine subclass"; }, this['createJavaScriptEvaluatorBlock'] = function (script) { throw "Override createJavaScriptEvaluatorBlock in your ko.templateEngine subclass"; } }; ko.exportSymbol('ko.templateEngine', ko.templateEngine); ko.templateRewriting = (function () { var memoizeBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi; return { ensureTemplateIsRewritten: function (template, templateEngine) { if (!templateEngine['isTemplateRewritten'](template)) templateEngine['rewriteTemplate'](template, function (htmlString) { return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine); }); }, memoizeBindingAttributeSyntax: function (htmlString, templateEngine) { return htmlString.replace(memoizeBindingAttributeSyntaxRegex, function () { var tagToRetain = arguments[1]; var dataBindAttributeValue = arguments[6]; // @modified // modified the rewritting function used //dataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyAccessorsIntoJson(dataBindAttributeValue); dataBindAttributeValue = ko.jsonExpressionRewriting.insertPropertyReaderWritersIntoJson(dataBindAttributeValue); // For no obvious reason, Opera fails to evaluate dataBindAttributeValue unless it's wrapped in an additional anonymous function, // even though Opera's built-in debugger can evaluate it anyway. No other browser requires this extra indirection. var applyBindingsToNextSiblingScript = "ko.templateRewriting.applyMemoizedBindingsToNextSibling(function() { \ return (function() { var innerNode=skonode; return { " + dataBindAttributeValue + " } })() \ })"; return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain; }); }, applyMemoizedBindingsToNextSibling: function (bindings) { return ko.memoization.memoize(function (domNode, viewModel) { if (domNode.nextSibling) { // @modified sko.traceResources(domNode.nextSibling, viewModel, function(){ sko.traceRelations(domNode.nextSibling, viewModel, function(){ ko.applyBindingsToNode(domNode.nextSibling, bindings, viewModel); }); }); } }); } } })(); ko.exportSymbol('ko.templateRewriting', ko.templateRewriting); ko.exportSymbol('ko.templateRewriting.applyMemoizedBindingsToNextSibling', ko.templateRewriting.applyMemoizedBindingsToNextSibling); // Exported only because it has to be referenced by string lookup from within rewritten template (function () { var _templateEngine; ko.setTemplateEngine = function (templateEngine) { if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine)) throw "templateEngine must inherit from ko.templateEngine"; _templateEngine = templateEngine; } function getFirstNodeFromPossibleArray(nodeOrNodeArray) { return nodeOrNodeArray.nodeType ? nodeOrNodeArray : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0] : null; } function executeTemplate(targetNodeOrNodeArray, renderMode, template, data, options) { var dataForTemplate = ko.utils.unwrapObservable(data); options = options || {}; var templateEngineToUse = (options['templateEngine'] || _templateEngine); ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse); var renderedNodesArray = templateEngineToUse['renderTemplate'](template, dataForTemplate, options); // Loosely check result is an array of DOM nodes if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number")) throw "Template engine must return an array of DOM nodes"; // @modified // Change the positoin of switch and if(render // so the rendered node is added to the DOM before being unmemoized switch (renderMode) { case "replaceChildren": ko.utils.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray); break; case "replaceNode": ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray); break; case "ignoreTargetNode": break; default: throw new Error("Unknown renderMode: " + renderMode); } if (renderedNodesArray) ko.utils.arrayForEach(renderedNodesArray, function (renderedNode) { ko.memoization.unmemoizeDomNodeAndDescendants(renderedNode, [data]); }); if (options['afterRender']) options['afterRender'](renderedNodesArray, data); return renderedNodesArray; } ko.renderTemplate = function (template, data, options, targetNodeOrNodeArray, renderMode) { options = options || {}; if ((options['templateEngine'] || _templateEngine) == undefined) throw "Set a template engine before calling renderTemplate"; renderMode = renderMode || "replaceChildren"; if (targetNodeOrNodeArray) { var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray); var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation) var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode; return new ko.dependentObservable( // So the DOM is automatically updated when any dependency changes function () { // Support selecting template as a function of the data being rendered var templateName = typeof(template) == 'function' ? template(data) : template; var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, data, options); if (renderMode == "replaceNode") { targetNodeOrNodeArray = renderedNodesArray; firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray); } }, null, { 'disposeWhen': whenToDispose, 'disposeWhenNodeIsRemoved': activelyDisposeWhenNodeIsRemoved } ); } else { // We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node return ko.memoization.memoize(function (domNode) { ko.renderTemplate(template, data, options, domNode, "replaceNode"); }); } }; ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode) { return new ko.dependentObservable(function () { var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || []; // @modified if (unwrappedArray.constructor != Array) // Coerce single value into array unwrappedArray = [unwrappedArray]; // @modified // wrapping automatically non objects for(var i=0; i 0) || (newIndex > 0)) { var me = editDistanceMatrix[newIndex][oldIndex]; var distanceViaAdd = (newIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex] : maxDistance + 1; var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1; var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1; if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1; if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1; if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1; if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) { editScript.push({ status: "added", value: newArray[newIndex - 1] }); newIndex--; } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) { editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] }); oldIndex--; } else { editScript.push({ status: "retained", value: oldArray[oldIndex - 1] }); newIndex--; oldIndex--; } } return editScript.reverse(); } ko.utils.compareArrays = function (oldArray, newArray, maxEditsToConsider) { if (maxEditsToConsider === undefined) { return ko.utils.compareArrays(oldArray, newArray, 1) // First consider likely case where there is at most one edit (very fast) || ko.utils.compareArrays(oldArray, newArray, 10) // If that fails, account for a fair number of changes while still being fast || ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time } else { oldArray = oldArray || []; newArray = newArray || []; var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider); return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray); } }; })(); ko.exportSymbol('ko.utils.compareArrays', ko.utils.compareArrays); (function () { // Objective: // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes, // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we // previously mapped - retain those nodes, and just insert/delete other ones function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap) { // Map this array value inside a dependentObservable so we re-map when any dependency changes var mappedNodes = []; var dependentObservable = ko.dependentObservable(function() { var newMappedNodes = mapping(valueToMap) || []; // On subsequent evaluations, just replace the previously-inserted DOM nodes if (mappedNodes.length > 0) ko.utils.replaceDomNodes(mappedNodes, newMappedNodes); // Replace the contents of the mappedNodes array, thereby updating the record // of which nodes would be deleted if valueToMap was itself later removed mappedNodes.splice(0, mappedNodes.length); ko.utils.arrayPushAll(mappedNodes, newMappedNodes); }, null, { 'disposeWhenNodeIsRemoved': containerNode, 'disposeWhen': function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } }); return { mappedNodes : mappedNodes, dependentObservable : dependentObservable }; } ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options) { // Compare the provided array against the previous one array = array || []; options = options || {}; var isFirstExecution = ko.utils.domData.get(domNode, "setDomNodeChildrenFromArrayMapping_lastMappingResult") === undefined; var lastMappingResult = ko.utils.domData.get(domNode, "setDomNodeChildrenFromArrayMapping_lastMappingResult") || []; var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; }); var editScript = ko.utils.compareArrays(lastArray, array); // Build the new mapping result var newMappingResult = []; var lastMappingResultIndex = 0; var nodesToDelete = []; var nodesAdded = []; var insertAfterNode = null; for (var i = 0, j = editScript.length; i < j; i++) { switch (editScript[i].status) { case "retained": // Just keep the information - don't touch the nodes var dataToRetain = lastMappingResult[lastMappingResultIndex]; newMappingResult.push(dataToRetain); if (dataToRetain.domNodes.length > 0) insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1]; lastMappingResultIndex++; break; case "deleted": // Stop tracking changes to the mapping for these nodes lastMappingResult[lastMappingResultIndex].dependentObservable.dispose(); // Queue these nodes for later removal ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) { nodesToDelete.push({ element: node, index: i, value: editScript[i].value }); insertAfterNode = node; }); lastMappingResultIndex++; break; case "added": var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, editScript[i].value); var mappedNodes = mapData.mappedNodes; // On the first evaluation, insert the nodes at the current insertion point newMappingResult.push({ arrayEntry: editScript[i].value, domNodes: mappedNodes, dependentObservable: mapData.dependentObservable }); for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) { var node = mappedNodes[nodeIndex]; nodesAdded.push({ element: node, index: i, value: editScript[i].value }); if (insertAfterNode == null) { // Insert at beginning if (domNode.firstChild) domNode.insertBefore(node, domNode.firstChild); else domNode.appendChild(node); } else { // Insert after insertion point if (insertAfterNode.nextSibling) domNode.insertBefore(node, insertAfterNode.nextSibling); else domNode.appendChild(node); } insertAfterNode = node; } break; } } ko.utils.arrayForEach(nodesToDelete, function (node) { ko.cleanNode(node.element) }); var invokedBeforeRemoveCallback = false; if (!isFirstExecution) { if (options['afterAdd']) { for (var i = 0; i < nodesAdded.length; i++) options['afterAdd'](nodesAdded[i].element, nodesAdded[i].index, nodesAdded[i].value); } if (options['beforeRemove']) { for (var i = 0; i < nodesToDelete.length; i++) options['beforeRemove'](nodesToDelete[i].element, nodesToDelete[i].index, nodesToDelete[i].value); invokedBeforeRemoveCallback = true; } } if (!invokedBeforeRemoveCallback) ko.utils.arrayForEach(nodesToDelete, function (node) { if (node.element.parentNode) node.element.parentNode.removeChild(node.element); }); // Store a copy of the array items we just considered so we can difference it next time ko.utils.domData.set(domNode, "setDomNodeChildrenFromArrayMapping_lastMappingResult", newMappingResult); } })(); ko.exportSymbol('ko.utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping); ko.jqueryTmplTemplateEngine = function () { // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl // doesn't expose a version number, so we have to infer it. this.jQueryTmplVersion = (function() { if ((typeof(jQuery) == "undefined") || !jQuery['tmpl']) return 0; // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves. if (jQuery['tmpl']['tag']) { if (jQuery['tmpl']['tag']['tmpl'] && jQuery['tmpl']['tag']['tmpl']['open']) { if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) { return 3; // Since 1.0.0pre, custom tags should append markup to an array called "__" } } return 2; // Prior to 1.0.0pre, custom tags should append markup to an array called "_" } return 1; // Very old version doesn't have an extensible tag system })(); this['getTemplateNode'] = function (template) { var templateNode = document.getElementById(template); if (templateNode == null) throw new Error("Cannot find template with ID=" + template); return templateNode; } // These two only needed for jquery-tmpl v1 var aposMarker = "__ko_apos__"; var aposRegex = new RegExp(aposMarker, "g"); this['renderTemplate'] = function (templateId, data, options) { options = options || {}; if (this.jQueryTmplVersion == 0) throw new Error("jquery.tmpl not detected.\nTo use KO's default template engine, reference jQuery and jquery.tmpl. See Knockout installation documentation for more details."); if (this.jQueryTmplVersion == 1) { // jquery.tmpl v1 doesn't like it if the template returns just text content or nothing - it only likes you to return DOM nodes. // To make things more flexible, we can wrap the whole template in a "; var renderedMarkupInWrapper = jQuery['tmpl'](templateTextInWrapper, data); var renderedMarkup = renderedMarkupInWrapper[0].text.replace(aposRegex, "'");; return jQuery['clean']([renderedMarkup], document); } // It's easier with jquery.tmpl v2 and later - it handles any DOM structure if (!(templateId in jQuery['template'])) { // Precache a precompiled version of this template (don't want to reparse on every render) var templateText = this['getTemplateNode'](templateId).text; jQuery['template'](templateId, templateText); } data = [data]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays var resultNodes = jQuery['tmpl'](templateId, data, options['templateOptions']); resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders return resultNodes; }, this['isTemplateRewritten'] = function (templateId) { // It must already be rewritten if we've already got a cached version of it // (this optimisation helps on IE < 9, because it greatly reduces the number of getElementById calls) if (templateId in jQuery['template']) return true; return this['getTemplateNode'](templateId).isRewritten === true; }, this['rewriteTemplate'] = function (template, rewriterCallback) { var templateNode = this['getTemplateNode'](template); var rewritten = rewriterCallback(templateNode.text); if (this.jQueryTmplVersion == 1) { // jquery.tmpl v1 falls over if you use single-quotes, so replace these with a temporary marker for template rendering, // and then replace back after the template was rendered. This is slightly complicated by the fact that we must not interfere // with any code blocks - only replace apos characters outside code blocks. rewritten = ko.utils.stringTrim(rewritten); rewritten = rewritten.replace(/([\s\S]*?)(\${[\s\S]*?}|{{[\=a-z][\s\S]*?}}|$)/g, function(match) { // Called for each non-code-block followed by a code block (or end of template) var nonCodeSnippet = arguments[1]; var codeSnippet = arguments[2]; return nonCodeSnippet.replace(/\'/g, aposMarker) + codeSnippet; }); } templateNode.text = rewritten; templateNode.isRewritten = true; }, this['createJavaScriptEvaluatorBlock'] = function (script) { var splitTemplate = function(dataBindCode) { var regexp1 = /<\$[^>]*>/g; if(dataBindCode.split(regexp1).length > 1) { var acum = "" var rem = null; dataBindCode.replace(regexp1,function( all, slash, type, fnargs, target, parens, args ){ if(rem === null) { rem = type; } var parts = rem.split(all); acum = acum + parts[0] + all.replace(//,"+'>"); parts.shift(); if(parts.length === 1) { acum = acum + parts[0]; } else { rem = parts.join(all); } }); return acum; } else { return dataBindCode; } }; var transformedTemplate = splitTemplate(script); // nothing to escape -> regular execution if (this.jQueryTmplVersion == 1) return "{{= " + transformedTemplate + "}}" // From v2, jquery-tmpl does some parameter parsing that fails on nontrivial expressions. // Prevent it from messing with the code by wrapping it in a further function. return "{{ko_code ((function() { return " + transformedTemplate + " })()) }}" }, this.addTemplate = function (templateName, templateMarkup) { document.write(""); } ko.exportProperty(this, 'addTemplate', this.addTemplate); if (this.jQueryTmplVersion > 1) { jQuery['tmpl']['tag']['ko_code'] = { open: (this.jQueryTmplVersion < 3 ? "_" : "__") + ".push($1 || '');" }; } }; ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine(); // Use this one by default ko.setTemplateEngine(new ko.jqueryTmplTemplateEngine()); ko.exportSymbol('ko.jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine); Utils = {}; Utils.stackCounterLimit = 1000; Utils.stackCounter = 0; Utils.recur = function(c){ if(Utils.stackCounter === Utils.stackCounterLimit) { Utils.stackCounter = 0; setTimeout(c, 0); } else { Utils.stackCounter++; c(); } }; Utils.repeat = function(c,max,floop,fend,env) { if(arguments.length===4) { env = {}; } if(c "+resource.valuesMap[sko.NTUri(propertyUri)]); } return resource.valuesMap[sko.NTUri(propertyUri)] != null } else { sko.log("CHECKIN CLASS URI: "+sko.NTUri(klass)); sko.log(resource); for(var p in resource.classes) { sko.log(" - "+p+" ---> "+resource.classes[p]); } return resource.classes[sko.NTUri(klass)] === true; } }; /** * Starts the library. Call this before anything else. */ sko.ready = function() { // reset state sko.aboutResourceMap = {}; sko.aboutResourceSubscriptionMap = {}; sko.Class.registry = {}; sko.aboutCounter = 0; sko.generatorId = 0; sko.generatorsMap = {}; var cb = null; if(arguments.length === 2) { sko.store = arguments[0]; cb = arguments[1]; sko.rdf = sko.store.rdf; cb(true); } else { cb = arguments[0]; rdfstore.create(function(store) { sko.store = store; sko.store.registerDefaultProfileNamespaces(); sko.rdf = sko.store.rdf; cb(true); }); } }; sko.registerPrefix = function(prefix, uri) { sko.store.registerDefaultNamespace(prefix, uri); }; // blank IDs counter sko._blankCounter = 0; sko.nextBlankLabel = function () { var blankLabel = "_:sko_" + sko._blankCounter; sko._blankCounter++; return blankLabel; }; // Collection of observable resources sko.aboutResourceMap = {}; sko.aboutResourceSubscriptionMap = {}; sko.aboutCounter = 0; sko.about = function(aboutValue, viewModel, cb) { var currentValue = sko.about[aboutValue]; if(currentValue != null) { // returning an observable that was already registered for this node cb(aboutValue); } else { // the about value hasn't been registered yet // identifier var nextId = ''+sko.aboutCounter; sko.aboutCounter++; // this is a blank node if(aboutValue == null) { aboutValue = sko.nextBlankLabel(); } if(typeof(aboutValue) === 'string') { // the value is aconstant URI var uri = aboutValue; // register the new observer and resource sko.store.node(sko.plainUri(uri), function(success, resource) { if(success) { sko.log("FOUND RESOURCE: "+uri); sko.log(resource); sko.log(" FOR "+sko.plainUri(uri)); // id -> Resource sko.log(" ------------> "+nextId+" : "+uri); sko.aboutResourceMap[nextId] = new sko.Resource(nextId, uri,resource); // id -> observer sko.about[nextId] = ko.observable(uri); // we observe changes in the about resource var subscription = sko.about[nextId].subscribe(function(nextUri) { sko.log("*** OBSERVING NODE ABOT ID:"+nextId+" new value -> "+nextUri); if(nextUri == null) { sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); nextUri = sko.nextBlankLabel(); } sko.store.node(sko.plainUri(nextUri), function(success, nextResource) { if(success) { var newUri = nextResource.toArray()[0].subject.valueOf(); sko.log(" ------------> "+nextId+" : "+newUri+" 2"); sko.aboutResourceMap[nextId].about(newUri); } else { // reset resource? sko.log("*** NO RESOURCE FOR URI:"+nextUri); sko.log(" ------------> "+nextId+" : "+nextUri+" 3"); sko.aboutResourceMap[nextId].about(nextUri); } }); }); sko.aboutResourceSubscriptionMap[nextId] = subscription; } else { // what here? } cb(nextId); }); } else { // The value is a function (maybe an observer) sko.about[nextId] = ko.dependentObservable({ read: function(){ var uri = aboutValue(); sko.log("*** OBSERVABLE READING DEPENDING NODE ABOT ID:"+nextId+" new value -> "+uri); if(uri == null) { sko.log(" ** URI IS BLANK -> GEN BLANK LABEL"); uri = sko.nextBlankLabel(); } return uri; }, write: function(value) { sko.log("*** OBSERVABLE WRITING DEPENDING NODE ABOT ID:"+nextId+" new value -> "+value); if(value == null) { sko.log(" ** URI IS BLANK -> GEN BLANK LABEL"); value = sko.nextBlankLabel(); } aboutValue(value); }, owner: viewModel }); // register the new observer and resource sko.store.node(sko.plainUri(sko.about[nextId]()), function(success, resource) { if(success) { // id -> Resource sko.log(" ------------> "+nextId+" : "+resource+" 4"); sko.aboutResourceMap[nextId] = new sko.Resource(nextId, sko.about[nextId](), resource); // we observe changes in the about resource var subscription = sko.about[nextId].subscribe(function(nextUri) { sko.log("*** OBSERVING NODE ABOT ID:"+nextId+" new value -> "+nextUri); if(nextUri != null) { sko.store.node(sko.plainUri(nextUri), function(success, nextResource) { if(success) { if(nextResource.toArray().length>0) { var newUri = nextResource.toArray()[0].subject.toNT(); sko.log(" ------------> "+nextId+" : "+newUri+" 5"); sko.aboutResourceMap[nextId].about(newUri); } else { // reset resource? sko.log("*** NO RESOURCE FOR URI:"+nextUri); sko.log(" ------------> "+nextId+" : "+nextUri+" 6"); sko.aboutResourceMap[nextId].about(nextUri); } } else { sko.log("Error updating 3 resource for URI:"+nextUri+" in SKO about node observer"); sko.log("*** NO RESOURCE FOR URI:"+nextUri + " NEXT ID:"+nextId); sko.log(" ------------> "+nextId+" : "+nextUri+" 7"); sko.aboutResourceMap[nextId].about(nextUri); } }); } else { sko.log("*** NO RESOURCE FOR URI:"+nextUri); sko.log(" ------------> "+nextId+" : NEW BLANK 8"); sko.aboutResourceMap[nextId].about(sko.nextBlankLabel()); } }); sko.aboutResourceSubscriptionMap[nextId] = subscription; } else { // what here? } cb(nextId); }); } } }; sko.rel = function(relValue, node, viewModel, cb) { var nextId = ''+sko.aboutCounter; var relValueUri = null; sko.aboutCounter++; if(typeof(relValue) === 'string') { var uri = sko.NTUri(relValue); relValueUri = uri; sko.about[nextId] = ko.dependentObservable({ read: function(){ sko.log("*** OBSERVABLE READING RELATED DEPENDING NODE ABOT ID:"+nextId); var resource = sko.currentResource(jQuery(node).parent().toArray()[0]); sko.log(resource); if(resource != null) { sko.log(" ** about:"+resource.about()); sko.log("*** Found parent resource: "+resource.about()); if(resource[uri]) { var relResourceUri = resource[uri](); if(relResourceUri != null && !sko.isSKOBlankNode(resource[uri]())) { relResourceUri = sko.NTUri(relResourceUri); if(sko.aboutResourceMap[nextId] == null || sko.aboutResourceMap[nextId].about() != relResourceUri) { sko.log("*** found related resource: "+relResourceUri); // register the new observer and resource sko.store.node(sko.plainUri(relResourceUri), function(success, resource) { sko.log("CREATED NODE FOR ID "+nextId+" AND URI: "+sko.plainUri(relResourceUri)); sko.log(resource); sko.log(" ------------> "+nextId+" : "+relResourceUri+" 8b"); sko.aboutResourceMap[nextId] = new sko.Resource(nextId, relResourceUri, resource); }); } else { sko.log("*** Related resource hasn't changed"); } return relResourceUri; } else { if(relResourceUri == null) { sko.log("*** related resource is null"); sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); var nextUri = sko.nextBlankLabel(); sko.log(" ** setting parent node related resource to "+nextUri); resource[uri](nextUri); } else { if(sko.aboutResourceMap[nextId]) { // @todo here sko.log("*** setting new value for related resource "+nextUri); sko.log(" ------------> "+nextId+" : "+nextUri+" 9"); sko.aboutResourceMap[nextId].about(nextUri); } else { // what here? sko.log("!! Should I create the new blank node?"); } } return nextUri; } } else { sko.log("!!! parent resource doest not link to the related resource"); } } else { sko.log("!!! impossible to find parent resource"); }}, // we setup the related object of the parent resource // this will trigger the observer that will update this model proxy write: function(uri) { if(uri == null) { sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); uri = sko.nextBlankLabel(); } uri = sko.NTUri(uri); sko.log("*** OBSERVABLE WRITING RELATED DEPENDING NODE ABOT ID:"+nextId+" URI -> "+uri); var resource = sko.currentResource(jQuery(node).parent().toArray()[0]); if(resource != null) { sko.log("*** Found parent resource: "+resource.about()); if(resource[relValueUri]) { sko.log("*** Setting new related resource in parent resource found: "+uri); resource[relValueUri](uri); } else { sko.log("!!! parent resource doest not link to the related resource"); } } else { sko.log("!!! impossible to find parent resource"); } }, owner: viewModel }); var subscription = sko.about[nextId].subscribe(function(nextUri) { sko.log("*** OBSERVING RELATED NODE ABOT ID:"+nextId+" new value -> "+nextUri); if(nextUri == null) { sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); nextUri = sko.nextBlankLabel(); } nextUri = sko.NTUri(nextUri); if(sko.about[nextId]() != null) { if(sko.plainUri(nextUri) !== sko.plainUri(sko.about[nextId]())) { sko.store.node(sko.plainUri(nextUri), function(success, nextResource) { if(success) { var newUri = nextResource.toArray()[0].subject.valueOf(); sko.log(" ------------> "+nextId+" : "+uri+" 10"); sko.aboutResourceMap[nextId].about(uri); } else { sko.log("Error updating 1 resource for URI:"+nextUri+" in SKO about related node observer"); } }); } } else { // @todo sko.log("!! this resource is now null, should be removed from list of resources?"); } }); sko.aboutResourceSubscriptionMap[nextId] = subscription; } else { sko.about[nextId] = ko.dependentObservable({ read: function(){ var uri; if(relValue != null) { uri = relValue(); uri = sko.NTUri(uri); sko.log("*** OBSERVABLE READING RELATED DEPENDING NODE ABOT ID:"+nextId+" URI -> "+uri); } else { sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); uri = sko.nextBlankLabel(); } var resource = sko.currentResource(jQuery(node).parent().toArray()[0]); if(resource != null) { sko.log("*** Found parent resource: "+resource.about()); if(resource[uri]) { var relResourceUri = resource[uri](); sko.log("*** found related resource: "+relResourceUri); // register the new observer and resource sko.store.node(sko.plainUri(relResourceUri), function(success, resource) { sko.log(" ------------> "+nextId+" : "+relResourceUri+" 11"); sko.aboutResourceMap[nextId] = new sko.Resource(nextId, relResourceUri, resource); }); return relResourceUri; } else { sko.log("!!! parent resource doest not link to the related resource"); } } else { sko.log("!!! impossible to find parent resource"); } }, // we setup the related object of the parent resource // this will trigger the observer that will update this model proxy write: function(uri) { uri = sko.NTUri(uri); var resource = sko.currentResource(jQuery(node).parent().toArray()[0]); sko.log("*** OBSERVABLE WRITING RELATED DEPENDING NODE ABOT ID:"+nextId+" URI -> "+uri); if(uri == null) { sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); uri = sko.nextBlankLabel(); } if(resource != null) { sko.log("*** Found parent resource: "+resource.about()); if(resource[relValue()]) { sko.log("*** Setting new related resource in parent resource found: "+uri); resource[relValue()](uri); relValue(uri); } else { sko.log("!!! parent resource doest not link to the related resource"); } } else { sko.log("!!! impossible to find parent resource"); } }, owner: viewModel }); var subscription = sko.about[nextId].subscribe(function(nextUri) { nextUri = sko.NTUri(nextUri); sko.log("*** OBSERVING RELATED NODE (F) ABOT ID:"+nextId+" new value -> "+nextUri); if(nextUri == null) { sko.log(" ** NEXT URI IS NULL, GEN BLANK LABEL"); nextUri = sko.nextBlankLabel(); } if(sko.plainUri(nextUri) != sko.plainUri(sko.about[nextId]())) { sko.store.node(sko.plainUri(nextUri), function(success, nextResource) { if(success) { var newUri = nextResource.toArray()[0].subject.valueOf(); sko.log(" ------------> "+nextId+" : "+newUri+" 12"); sko.aboutResourceMap[nextId].about(newUri); } else { sko.log("Error updating 2 resource for URI:"+nextUri+" in SKO about related node observer"); } }); } else { sko.log("*** Related about resource hasn't changed"); } }); sko.aboutResourceSubscriptionMap[nextId] = subscription; } cb(nextId); }; sko.plainUri = function(uri) { if(uri[0] === "<" && uri[uri.length-1] == ">") { return uri.slice(1,uri.length-1); } else if(uri.match(/\[[^,;"\]\}\{\[\.:]+:[^,;"\}\]\{\[\.:]+\]/) != null) { uri = uri.slice(1, uri.length-1); var resolved = sko.rdf.prefixes.resolve(uri); if(resolved == null) { throw("The CURIE "+uri+" cannot be resolved"); } else { return resolved; } } else { return uri; } }; sko.effectiveValue = function(term) { if(term == null) { return null; } else { if(term.interfaceName && term.interfaceName === 'Literal') { if(term.datatype == "http://www.w3.org/2001/XMLSchema#integer" || term.datatype == "http://www.w3.org/2001/XMLSchema#decimal" || term.datatype == "http://www.w3.org/2001/XMLSchema#double" || term.datatype == "http://www.w3.org/2001/XMLSchema#nonPositiveInteger" || term.datatype == "http://www.w3.org/2001/XMLSchema#negativeInteger" || term.datatype == "http://www.w3.org/2001/XMLSchema#long" || term.datatype == "http://www.w3.org/2001/XMLSchema#int" || term.datatype == "http://www.w3.org/2001/XMLSchema#short" || term.datatype == "http://www.w3.org/2001/XMLSchema#byte" || term.datatype == "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" || term.datatype == "http://www.w3.org/2001/XMLSchema#unsignedLong" || term.datatype == "http://www.w3.org/2001/XMLSchema#unsignedInt" || term.datatype == "http://www.w3.org/2001/XMLSchema#unsignedShort" || term.datatype == "http://www.w3.org/2001/XMLSchema#unsignedByte" || term.datatype == "http://www.w3.org/2001/XMLSchema#positiveInteger" ) { return parseFloat(term.valueOf()); } else if(term.type === "http://www.w3.org/2001/XMLSchema#boolean"){ return (term.valueOf() === 'true' || term.valueOf() === true || term.valueOf() === 'True'); } else if(term.type === "http://www.w3.org/2001/XMLSchema#string"){ return term.valueOf(); } else if(term.type === "http://www.w3.org/2001/XMLSchema#dateTime"){ return new Date(term.valueOf()); } else if(term.type == null) { return term.valueOf(); } } else { return term.valueOf(); } } }; sko.defaultLanguage = ko.observable(null); sko.Resource = function(resourceId, subject, node) { this.resourceId = resourceId; this.valuesMap = {}; this.subscriptions = []; this.literalLangs = {}; // classes this object is instance of this.classes = {}; var that = this; // default language for literals this.defaultLanguage = ko.dependentObservable(function(){ return sko.defaultLanguage(); }); subject = sko.NTUri(subject); node.forEach(function(triple){ sko.log(triple); if(triple.predicate.toNT() === "") { sko.log(" found resource class "+triple.object.toNT()); that.classes[triple.object.toNT()] = true; } if(triple.object.interfaceName === 'NamedNode') { that.valuesMap[triple.predicate.toNT()] = triple.object.toNT(); that[triple.predicate.toNT()] = ko.observable(triple.object.toNT()); } else if(triple.object.interfaceName === 'Literal') { if(that.defaultLanguage() != null) { if(that.valuesMap[triple.predicate.toNT()] == null || triple.object.language == that.defaultLanguage()) { var effectiveValue = sko.effectiveValue(triple.object.valueOf()); that.valuesMap[triple.predicate.toNT()] = effectiveValue; that[triple.predicate.toNT()] = ko.observable(effectiveValue); that[sko.plainUri(triple.predicate.toNT())] = that[triple.predicate.toNT()]; that.literalLangs[triple.predicate.toNT()] = triple.object.language; } } else { if(that.valuesMap[triple.predicate.toNT()] == null || triple.object.language == null) { var effectiveValue = sko.effectiveValue(triple.object.valueOf()); that.valuesMap[triple.predicate.toNT()] = effectiveValue; that[triple.predicate.toNT()] = ko.observable(effectiveValue); that[sko.plainUri(triple.predicate.toNT())] = that[triple.predicate.toNT()]; that.literalLangs[triple.predicate.toNT()] = triple.object.language; } } } else { that.valuesMap[triple.predicate.toNT()] = triple.object.valueOf(); that[triple.predicate.toNT()] = ko.observable(triple.object.valueOf()); that[sko.plainUri(triple.predicate.toNT())] = that[triple.predicate.toNT()]; } }); this.about = ko.observable(subject); this['@'] = this.about; this.storeObserverFn = sko.Resource.storeObserver(this); // setup classes sko.Class.check(this, true); // observe changes in the subject of this resource var that = this; var subscription = this.about.subscribe(function(newUri){ sko.log("SKO Resource new resource:"+newUri); sko.log("*** STOP OBSERVING NODE STORE"); sko.store.stopObservingNode(that.storeObserverFn); if(newUri != null && newUri.indexOf("_:sko")!=0) { sko.log("*** START OBSERVING NODE STORE FOR "+that.about()); sko.store.startObservingNode(sko.plainUri(newUri), that.storeObserverFn); } else { sko.log("*** nullifying resources"); // set properties to null for(var p in that.valuesMap) { that.valuesMap[p] = null; } for(var p in that.valuesMap) { that[p](null); } } }); this.subscriptions.push(subscription); // observe notifications from KO and the RDF store sko.Resource.koObserver(this); sko.store.startObservingNode(sko.plainUri(this.about()), that.storeObserverFn); }; sko.NTUri = function(uri) { if(uri[0]==="[" && uri[uri.length-1]==="]") { uri = uri.slice(1, uri.length-1); resolved = sko.rdf.prefixes.resolve(uri); if(uri == null) { throw("The CURIE "+uri+" cannot be resolved"); } else { uri = "<"+resolved+">"; } } return uri; }; /** * helper method for bound accessors */ sko.Resource.prototype.tryProperty = function(property) { property = sko.NTUri(property); if(this[property]!=null) { return this[property]; } else { this.valuesMap[property] = null; this[property] = ko.observable(null); var that = this; var observerFn = function(newValue){ that.notifyPropertyChange(property,newValue); }; var subscription = this[property].subscribe(observerFn); this.subscriptions.push(subscription); return this[property]; } }; sko.Resource.prototype.prop = function (property) { return this.tryProperty(property); }; sko.Resource.prototype.getProp = function (property) { return this.tryProperty(property)(); }; sko.Resource.prototype.setProp = function (property, newValue) { return this.tryProperty(property)(newValue); }; /** * Must update the value in the RDFstore */ sko.Resource.prototype.notifyPropertyChange = function(property, newValue) { sko.log("*** received KO notification for property "+property+" -> "+newValue); if(this.valuesMap[property] == null) { // property is not defined -> create // if it is a blank ID don't do anything this.valuesMap[property] = newValue; var isBlank = this.about().indexOf("_:sko") === 0; if(!isBlank) { if(newValue != null) { if(newValue.indexOf("http") === 0) { sko.store.execute('INSERT DATA { '+this.about()+' '+property+' <'+newValue+'> }', function(){}); } else if(newValue.indexOf("<") === 0) { sko.store.execute('INSERT DATA { '+this.about()+' '+property+' '+newValue+' }', function(){}); } else { sko.store.execute('INSERT DATA { '+this.about()+' '+property+' "'+newValue+'" }', function(){}); } } } } else { if(this.valuesMap[property] !== newValue && !sko.isSKOBlankNode(newValue)) { // property is already present and the value has changed -> update var oldValue = '"'+this.valuesMap[property]+'"'; newValue = '"'+newValue+'"'; if(this.literalLangs[property] != null) { oldValue = oldValue+"@"+this.literalLangs[property]; newValue = newValue+"@"+this.literalLangs[property]; } //@todo something must be done with datatypes and literals var query = "DELETE { "+this.about()+" "+property+" "+oldValue+" }"; query = query + " INSERT { "+this.about()+" "+property+" "+newValue+" }"; query = query + " WHERE { "+this.about()+" "+property+" "+oldValue+" }"; query = query.replace(/""/g,">"); this.valuesMap[property] = newValue; sko.log("*** updating STORE: \n "+query); sko.store.execute(query); } } }; sko.isSKOBlankNode = function (term) { return term.indexOf("_:sko") === 0; }; sko.Resource.prototype.disconnect = function() { sko.log(" ** DISCONNECTING"); sko.store.stopObservingNode(this.storeObserverFn); sko.log(" ** disconnected STORE"); for(var i=0; i "+node.toArray().length); node.forEach(function(triple){ if(triple.object.interfaceName === 'NamedNode') { sko.log(" "+triple.predicate.toNT()+" -> "+triple.object.toNT()); if(newValues[triple.predicate.toNT()] != null) { if(newValues[triple.predicate.toNT()].constructor === Array) { // more than one, added to array. // @todo what if named nodes and literals are mixed? newValues[triple.predicate.toNT()].push(triple.object.toNT()); newValues[triple.predicate.toNT()].sort(); } else { newValues[triple.predicate.toNT()] = [newValues[triple.predicate.toNT()], triple.object.toNT()]; } } else { newValues[triple.predicate.toNT()] = triple.object.toNT(); } } else { if(skoResource.defaultLanguage() != null) { if(newValues[triple.predicate.toNT()] == null || triple.object.language == skoResource.defaultLanguage()) { if(newValues[triple.predicate.toNT()] != null) { if(newValues[triple.predicate.toNT()].constructor === Array) { // more than one, added to array. // The value in the array cannot have a null lang // @todo what if named nodes and literals are mixed? newValues[triple.predicate.toNT()].push(triple.object.valueOf()); newValues[triple.predicate.toNT()].sort(); } else { if(newValuesLangs[triple.predicate.toNT()] != triple.object.language) { // replace old value (no lang) by a new value with lang newValues[triple.predicate.toNT()] = triple.object.valueOf(); newValuesLangs[triple.predicate.toNT()] = triple.object.language; } else { // last value was a single value with the correct lang -> now is an array newValues[triple.predicate.toNT()] = [newValues[triple.predicate.toNT()], triple.object.valueOf()]; } } } else { // set up a default value, with null or correct lang sko.log(" "+triple.predicate.toNT()+" -> "+triple.object.valueOf()); newValues[triple.predicate.toNT()] = triple.object.valueOf(); newValuesLangs[triple.predicate.toNT()] = triple.object.language; } } } else { if(newValues[triple.predicate.toNT()] == null || triple.object.language == null) { if(newValues[triple.predicate.toNT()] != null) { if(newValues[triple.predicate.toNT()].constructor === Array) { // more than one, added to array. // @todo what if named nodes and literals are mixed? newValues[triple.predicate.toNT()].push(triple.object.valueOf()); newValues[triple.predicate.toNT()].sort(); } else { if(newValuesLangs[triple.predicate.toNT()] != null) { // replace old value (with lang) by a new value with no lang newValues[triple.predicate.toNT()] = triple.object.valueOf(); newValuesLangs[triple.predicate.toNT()] = triple.object.language; } else { // last value was a single value with the correct lang -> now is an array newValues[triple.predicate.toNT()] = [newValues[triple.predicate.toNT()], triple.object.valueOf()]; } } } else { sko.log(" "+triple.predicate.toNT()+" -> "+triple.object.valueOf()); newValues[triple.predicate.toNT()] = triple.object.valueOf(); newValuesLangs[triple.predicate.toNT()] = triple.object.language; } } } } }); var newValueMap = {}; var toNullify = []; var toUpdate = []; var toCreate = []; // what has changed?, what need to be removed? for(var p in skoResource.valuesMap) { if(newValues[p] != null) { newValueMap[p] = newValues[p]; if(skoResource.valuesMap[p] && skoResource.valuesMap[p].constructor === Array && newValues[p].constructor === Array) { if(skoResource.valuesMap[p].length != newValues[p].length) { // @todo check also the individual URIS toUpdate.push(p); } } else { if(skoResource.valuesMap[p] !== newValues[p]) { toUpdate.push(p); if(newValuesLangs[p] != null || skoResource.literalLangs[p] != null) { skoResource.literalLangs[p] = newValuesLangs[p]; } } } } else { toNullify.push(p); delete skoResource.literalLangs[p]; } } // what is new? for(var p in newValues) { if(skoResource.valuesMap[p] == null) { toCreate.push(p); newValueMap[p] = newValues[p]; skoResource.literalLangs[p] = newValuesLangs[p]; } } // updateValues skoResource.valuesMap = newValueMap; for(var i=0; i NULL"); skoResource[toNullify[i]](null); } for(var i=0; i "+skoResource.valuesMap[toUpdate[i]]); skoResource[toUpdate[i]](skoResource.valuesMap[toUpdate[i]]); } for(var i=0; i "+skoResource.valuesMap[toCreate[i]]); if(skoResource[toCreate[i]]!=null){ skoResource[toCreate[i]](skoResource.valuesMap[toCreate[i]]); } else { skoResource[toCreate[i]] = ko.observable(skoResource.valuesMap[toCreate[i]]); } skoResource[sko.plainUri(toCreate[i])] = skoResource[toCreate[i]]; } // setup classes sko.Class.check(skoResource); sko.log("*** END MODIFICATION"); }; }; // custom bindings //ko.bindingHandlers.about = { // init: function(element, valueAccessor, allBindingsAccessor, viewModel) { // // This will be called when the binding is first applied to an element // // Set up any initial state, event handlers, etc. here // // var value = valueAccessor(); // $(element).attr("about", value); // }, // // update: function(element, valueAccessor, allBindingsAccessor, viewModel) { // // This will be called once when the binding is first applied to an element, // // and again whenever the associated observable changes value. // // Update the DOM element based on the supplied values here. // // var value = valueAccessor(); // $(element).attr("about", value); // } //}; // trace resources sko.traceResources = function(rootNode, model, cb) { sko.log("** TRACING:"); sko.log(rootNode); var nodes = []; if(jQuery(rootNode).attr("about") || jQuery(rootNode).attr("data-bind")) { nodes.push(rootNode); } var childNodes = jQuery(rootNode).find("*[about], *[data-bind]").toArray(); nodes = nodes.concat(childNodes); var registerFn = function(k,env){ var node = nodes[env._i]; var about = jQuery(node).attr("about"); var aboutId = jQuery(node).attr("aboutId"); if(aboutId == null) { var databind; if(about == null) { var dataBind = jQuery(node).attr("data-bind"); if(dataBind != null) { if(dataBind.indexOf("about:") != -1) { var re = new RegExp("\s*([^ ]+)\s*,?"); about = re.exec(dataBind.split("about:")[1])[0]; if(about[about.length-1] === ',') { about = about.slice(0,about.length-1); } } } } if(about != null && about != '') { if(typeof(about) === 'string' && about[0] !== '<' && about[about.length-1] !== '>' && about[0] !== '[' && about[about.length-1] !== ']') { about = model[about]; } sko.about(about, model, function(aboutId) { jQuery(node).attr('aboutId',aboutId); k(registerFn,env); }); } else { k(registerFn, env); } } else { k(registerFn, env); } }; Utils.repeat(0,nodes.length, registerFn, function(env) { cb(); }); }; /** * This function must be called *after* traceResources has been * invoked. */ sko.currentResource = function(node) { sko.log("in current resource"); sko.log(node); if(node == null) { sko.log("!!! top of DOM tree, About node not found"); return null; } var aboutId = jQuery(node).attr('aboutId'); sko.log("about id:"+aboutId); if(aboutId) { var uri = sko.about[aboutId](); sko.log("uri:"+uri); if(uri != null) { return sko.aboutResourceMap[aboutId]; } else { sko.log("!!! current resource is null: "+aboutId); } } else { sko.log("recurring"); return sko.currentResource(jQuery(node).parent().toArray()[0]); } }; sko.currentResourceNode = function(node) { sko.log("in current resource node"); sko.log(node); if(node == null) { sko.log("!!! top of DOM tree, About node not found"); return null; } var aboutId = jQuery(node).attr('aboutId'); sko.log("about id:"+aboutId); if(aboutId) { var uri = sko.about[aboutId](); sko.log("uri:"+uri); if(uri != null) { return node; } else { sko.log("!!! current resource is null: "+aboutId); return null; } } else { sko.log("recurring"); return sko.currentResourceNode(jQuery(node).parent().toArray()[0]); } }; sko.childrenResourceIds = function(node) { return jQuery(node).find("*[aboutId]").map(function(){ return jQuery(this).attr("aboutId") }); }; sko.traceRelations = function(rootNode, model, cb) { sko.log("*** TRACING RELATIONS:"); rootNode = (sko.currentResourceNode(rootNode) || rootNode); sko.log("*** FOUND ROOT NODE TO LOOK FOR RELATIONS:"); sko.log(rootNode); var nodes = []; if(jQuery(rootNode).attr("rel") || jQuery(rootNode).attr("data-bind")) { nodes.push(rootNode); } var childNodes = jQuery(rootNode).find("*[rel], *[data-bind]").toArray(); nodes = nodes.concat(childNodes); sko.log(" ** NODES TO LOOK FOR RELATED RESOURCES"); sko.log(nodes); var registerFn = function(k,env){ var node = nodes[env._i]; var rel = jQuery(node).attr("rel"); var databind; if(rel == null) { var dataBind = jQuery(node).attr("data-bind"); if(dataBind != null) { if(dataBind.indexOf("rel:") != -1) { var re = new RegExp("\s*([^ ]+)\s*,?"); rel = re.exec(dataBind.split("rel:")[1])[0]; if(rel[rel.length-1] === ',') { rel = rel.slice(0,rel.length-1); } } } } if(rel != null && rel != '') { if(typeof(rel) === 'string' && rel[0] !== '<' && rel[rel.length-1] !== '>' && rel[0] !== '[' && rel[rel.length-1] !== ']') { rel = model[rel]; } var nextId = jQuery(node).attr("aboutId"); if(nextId == null) { sko.log("*** CREATING RELATED NODE"); sko.rel(rel, node, model, function(aboutId) { jQuery(node).attr('aboutId',aboutId); k(registerFn,env); }); } else { sko.log("*** NODE IS ALREADY TRACED"); k(registerFn,env); } } else { k(registerFn, env); } }; Utils.repeat(0,nodes.length, registerFn, function(env) { cb(); }); }; sko.generatorId = 0; sko.generatorsMap = {}; sko.where = function (query) { query = "select ?subject where " + query; var nextId = '' + sko.generatorId; sko.generatorId++; sko.generatorsMap[nextId] = ko.observable([]); sko.log("*** WHERE QUERY: " + query); sko.store.startObservingQuery(query, function (bindingsList) { var acum = []; sko.log(" ** RESULTS!!!"); for (var i = 0; i < bindingsList.length; i++) { sko.log(" ** ADDING VALUE " + bindingsList[i].subject.value); acum.push("<" + bindingsList[i].subject.value + ">"); } sko.log("** SETTING VALUE"); sko.log(acum); sko.generatorsMap[nextId](acum) }); return sko.generatorsMap[nextId]; }; /** * Applies bindings and RDF nodes to the DOM tree */ sko.applyBindings = function(node, viewModel, cb) { if(typeof(node) === 'string') { node = jQuery(node)[0]; } sko.traceResources(node, viewModel, function(){ sko.traceRelations(node, viewModel, function(){ ko.applyBindings(viewModel, node); if(cb != null) { cb(); } }); }); }; /** * Retrieves the resource object active for a node */ sko.resource = function (jqueryPath) { var nodes = jQuery(jqueryPath).toArray(); if (nodes.length === 1) { return sko.currentResource(nodes[0]); } else { var acum = []; for (var i = 0; i < nodes.length; i++) { acum.push(sko.currentResource(nodes[i])); } return acum; } }; // place holder for the parser sko.owl = {}; })(window);