| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| (function e(t, n, r) { |
| function s(o, u) { |
| if (!n[o]) { |
| if (!t[o]) { |
| var a = typeof require == "function" && require; |
| if (!u && a) return a(o, !0); |
| if (i) return i(o, !0); |
| throw new Error("Cannot find module '" + o + "'") |
| } |
| var f = n[o] = { |
| exports: {} |
| }; |
| t[o][0].call(f.exports, function(e) { |
| var n = t[o][1][e]; |
| return s(n ? n : e) |
| }, f, f.exports, e, t, n, r) |
| } |
| return n[o].exports |
| } |
| var i = typeof require == "function" && require; |
| for (var o = 0; o < r.length; o++) s(r[o]); |
| return s |
| })({ |
| 1: [ |
| function(require, module, exports) { |
| |
| |
| if (window.odatajs === undefined) { |
| window.odatajs = {}; |
| } |
| |
| window.odatajs = require('./lib/datajs.js'); |
| window.odatajs.oData = require('./lib/odata.js'); |
| |
| window.odatajs.store = require('./lib/store.js'); |
| window.odatajs.cache = require('./lib/cache.js'); |
| |
| /* |
| function extend(target) { |
| var sources = [].slice.call(arguments, 1); |
| sources.forEach(function (source) { |
| for (var prop in source) { |
| target[prop] = source[prop]; |
| } |
| }); |
| return target; |
| }*/ |
| |
| |
| |
| }, { |
| "./lib/cache.js": 2, |
| "./lib/datajs.js": 4, |
| "./lib/odata.js": 8, |
| "./lib/store.js": 15 |
| } |
| ], |
| 2: [ |
| function(require, module, exports) { |
| |
| |
| /** @module cache */ |
| |
| var utils = require('./datajs.js').utils; |
| |
| |
| var assigned = utils.assigned; |
| var delay = utils.delay; |
| var extend = utils.extend; |
| var djsassert = utils.djsassert; |
| var isArray = utils.isArray; |
| var normalizeURI = utils.normalizeURI; |
| var parseInt10 = utils.parseInt10; |
| var undefinedDefault = utils.undefinedDefault; |
| |
| var deferred = require('./datajs/deferred.js'); |
| |
| var createDeferred = deferred.createDeferred; |
| var DjsDeferred = deferred.DjsDeferred; |
| var ODataCacheSource = require('./cache/source').ODataCacheSource; |
| |
| var getJsonValueArraryLength = utils.getJsonValueArraryLength; |
| var sliceJsonValueArray = utils.sliceJsonValueArray; |
| var concatJsonValueArray = utils.concatJsonValueArray; |
| var storeReq = require('./datajs.js').store; |
| |
| |
| /** Appending a page appendPage */ |
| var appendPage = function(operation, page) { |
| /// <summary>Appends a page's data to the operation data.</summary> |
| /// <param name="operation" type="Object">Operation with (i)ndex, (c)ount and (d)ata.</param> |
| /// <param name="page" type="Object">Page with (i)ndex, (c)ount and (d)ata.</param> |
| |
| var intersection = intersectRanges(operation, page); |
| var start = 0; |
| var end = 0; |
| if (intersection) { |
| start = intersection.i - page.i; |
| end = start + (operation.c - getJsonValueArraryLength(operation.d)); |
| } |
| |
| operation.d = concatJsonValueArray(operation.d, sliceJsonValueArray(page.d, start, end)); |
| }; |
| |
| var intersectRanges = function(x, y) { |
| /// <summary>Returns the {(i)ndex, (c)ount} range for the intersection of x and y.</summary> |
| /// <param name="x" type="Object">Range with (i)ndex and (c)ount members.</param> |
| /// <param name="y" type="Object">Range with (i)ndex and (c)ount members.</param> |
| /// <returns type="Object">The intersection (i)ndex and (c)ount; undefined if there is no intersection.</returns> |
| |
| var xLast = x.i + x.c; |
| var yLast = y.i + y.c; |
| var resultIndex = (x.i > y.i) ? x.i : y.i; |
| var resultLast = (xLast < yLast) ? xLast : yLast; |
| var result; |
| if (resultLast >= resultIndex) { |
| result = { |
| i: resultIndex, |
| c: resultLast - resultIndex |
| }; |
| } |
| |
| return result; |
| }; |
| |
| var checkZeroGreater = function(val, name) { |
| /// <summary>Checks whether val is a defined number with value zero or greater.</summary> |
| /// <param name="val" type="Number">Value to check.</param> |
| /// <param name="name" type="String">Parameter name to use in exception.</param> |
| |
| if (val === undefined || typeof val !== "number") { |
| throw { |
| message: "'" + name + "' must be a number." |
| }; |
| } |
| |
| if (isNaN(val) || val < 0 || !isFinite(val)) { |
| throw { |
| message: "'" + name + "' must be greater than or equal to zero." |
| }; |
| } |
| }; |
| |
| var checkUndefinedGreaterThanZero = function(val, name) { |
| /// <summary>Checks whether val is undefined or a number with value greater than zero.</summary> |
| /// <param name="val" type="Number">Value to check.</param> |
| /// <param name="name" type="String">Parameter name to use in exception.</param> |
| |
| if (val !== undefined) { |
| if (typeof val !== "number") { |
| throw { |
| message: "'" + name + "' must be a number." |
| }; |
| } |
| |
| if (isNaN(val) || val <= 0 || !isFinite(val)) { |
| throw { |
| message: "'" + name + "' must be greater than zero." |
| }; |
| } |
| } |
| }; |
| |
| var checkUndefinedOrNumber = function(val, name) { |
| /// <summary>Checks whether val is undefined or a number</summary> |
| /// <param name="val" type="Number">Value to check.</param> |
| /// <param name="name" type="String">Parameter name to use in exception.</param> |
| if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) { |
| throw { |
| message: "'" + name + "' must be a number." |
| }; |
| } |
| }; |
| |
| var removeFromArray = function(arr, item) { |
| /// <summary>Performs a linear search on the specified array and removes the first instance of 'item'.</summary> |
| /// <param name="arr" type="Array">Array to search.</param> |
| /// <param name="item">Item being sought.</param> |
| /// <returns type="Boolean">Whether the item was removed.</returns> |
| |
| var i, len; |
| for (i = 0, len = arr.length; i < len; i++) { |
| if (arr[i] === item) { |
| arr.splice(i, 1); |
| return true; |
| } |
| } |
| |
| return false; |
| }; |
| |
| var estimateSize = function(obj) { |
| /// <summary>Estimates the size of an object in bytes.</summary> |
| /// <param name="obj" type="Object">Object to determine the size of.</param> |
| /// <returns type="Integer">Estimated size of the object in bytes.</returns> |
| var size = 0; |
| var type = typeof obj; |
| |
| if (type === "object" && obj) { |
| for (var name in obj) { |
| size += name.length * 2 + estimateSize(obj[name]); |
| } |
| } else if (type === "string") { |
| size = obj.length * 2; |
| } else { |
| size = 8; |
| } |
| return size; |
| }; |
| |
| var snapToPageBoundaries = function(lowIndex, highIndex, pageSize) { |
| /// <summary>Snaps low and high indices into page sizes and returns a range.</summary> |
| /// <param name="lowIndex" type="Number">Low index to snap to a lower value.</param> |
| /// <param name="highIndex" type="Number">High index to snap to a higher value.</param> |
| /// <param name="pageSize" type="Number">Page size to snap to.</param> |
| /// <returns type="Object">A range with (i)ndex and (c)ount of elements.</returns> |
| |
| lowIndex = Math.floor(lowIndex / pageSize) * pageSize; |
| highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize; |
| return { |
| i: lowIndex, |
| c: highIndex - lowIndex |
| }; |
| }; |
| |
| // The DataCache is implemented using state machines. The following constants are used to properly |
| // identify and label the states that these machines transition to. |
| |
| // DataCache state constants |
| |
| var CACHE_STATE_DESTROY = "destroy"; |
| var CACHE_STATE_IDLE = "idle"; |
| var CACHE_STATE_INIT = "init"; |
| var CACHE_STATE_READ = "read"; |
| var CACHE_STATE_PREFETCH = "prefetch"; |
| var CACHE_STATE_WRITE = "write"; |
| |
| // DataCacheOperation state machine states. |
| // Transitions on operations also depend on the cache current of the cache. |
| |
| var OPERATION_STATE_CANCEL = "cancel"; |
| var OPERATION_STATE_END = "end"; |
| var OPERATION_STATE_ERROR = "error"; |
| var OPERATION_STATE_START = "start"; |
| var OPERATION_STATE_WAIT = "wait"; |
| |
| // Destroy state machine states |
| |
| var DESTROY_STATE_CLEAR = "clear"; |
| |
| // Read / Prefetch state machine states |
| |
| var READ_STATE_DONE = "done"; |
| var READ_STATE_LOCAL = "local"; |
| var READ_STATE_SAVE = "save"; |
| var READ_STATE_SOURCE = "source"; |
| |
| var DataCacheOperation = function(stateMachine, promise, isCancelable, index, count, data, pending) { |
| /// <summary>Creates a new operation object.</summary> |
| /// <param name="stateMachine" type="Function">State machine that describes the specific behavior of the operation.</param> |
| /// <param name="promise" type ="DjsDeferred">Promise for requested values.</param> |
| /// <param name="isCancelable" type ="Boolean">Whether this operation can be canceled or not.</param> |
| /// <param name="index" type="Number">Index of first item requested.</param> |
| /// <param name="count" type="Number">Count of items requested.</param> |
| /// <param name="data" type="Array">Array with the items requested by the operation.</param> |
| /// <param name="pending" type="Number">Total number of pending prefetch records.</param> |
| /// <returns type="DataCacheOperation">A new data cache operation instance.</returns> |
| |
| /// <field name="p" type="DjsDeferred">Promise for requested values.</field> |
| /// <field name="i" type="Number">Index of first item requested.</field> |
| /// <field name="c" type="Number">Count of items requested.</field> |
| /// <field name="d" type="Array">Array with the items requested by the operation.</field> |
| /// <field name="s" type="Array">Current state of the operation.</field> |
| /// <field name="canceled" type="Boolean">Whether the operation has been canceled.</field> |
| /// <field name="pending" type="Number">Total number of pending prefetch records.</field> |
| /// <field name="oncomplete" type="Function">Callback executed when the operation reaches the end state.</field> |
| |
| var stateData; |
| var cacheState; |
| var that = this; |
| |
| that.p = promise; |
| that.i = index; |
| that.c = count; |
| that.d = data; |
| that.s = OPERATION_STATE_START; |
| |
| that.canceled = false; |
| that.pending = pending; |
| that.oncomplete = null; |
| |
| that.cancel = function() { |
| /// <summary>Transitions this operation to the cancel state and sets the canceled flag to true.</summary> |
| /// <remarks>The function is a no-op if the operation is non-cancelable.</summary> |
| |
| if (!isCancelable) { |
| return; |
| } |
| |
| var state = that.s; |
| if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) { |
| that.canceled = true; |
| transition(OPERATION_STATE_CANCEL, stateData); |
| } |
| }; |
| |
| that.complete = function() { |
| /// <summary>Transitions this operation to the end state.</summary> |
| |
| djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.complete() - operation is in the end state", that); |
| transition(OPERATION_STATE_END, stateData); |
| }; |
| |
| that.error = function(err) { |
| /// <summary>Transitions this operation to the error state.</summary> |
| if (!that.canceled) { |
| djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.error() - operation is in the end state", that); |
| djsassert(that.s !== OPERATION_STATE_ERROR, "DataCacheOperation.error() - operation is in the error state", that); |
| transition(OPERATION_STATE_ERROR, err); |
| } |
| }; |
| |
| that.run = function(state) { |
| /// <summary>Executes the operation's current state in the context of a new cache state.</summary> |
| /// <param name="state" type="Object">New cache state.</param> |
| |
| cacheState = state; |
| that.transition(that.s, stateData); |
| }; |
| |
| that.wait = function(data) { |
| /// <summary>Transitions this operation to the wait state.</summary> |
| |
| djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.wait() - operation is in the end state", that); |
| transition(OPERATION_STATE_WAIT, data); |
| }; |
| |
| var operationStateMachine = function(opTargetState, cacheState, data) { |
| /// <summary>State machine that describes all operations common behavior.</summary> |
| /// <param name="opTargetState" type="Object">Operation state to transition to.</param> |
| /// <param name="cacheState" type="Object">Current cache state.</param> |
| /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param> |
| |
| switch (opTargetState) { |
| case OPERATION_STATE_START: |
| // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized. |
| if (cacheState !== CACHE_STATE_INIT) { |
| stateMachine(that, opTargetState, cacheState, data); |
| } |
| break; |
| |
| case OPERATION_STATE_WAIT: |
| // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete. |
| stateMachine(that, opTargetState, cacheState, data); |
| break; |
| |
| case OPERATION_STATE_CANCEL: |
| // Cancel state. |
| stateMachine(that, opTargetState, cacheState, data); |
| that.fireCanceled(); |
| transition(OPERATION_STATE_END); |
| break; |
| |
| case OPERATION_STATE_ERROR: |
| // Error state. Data is expected to be an object detailing the error condition. |
| stateMachine(that, opTargetState, cacheState, data); |
| that.canceled = true; |
| that.fireRejected(data); |
| transition(OPERATION_STATE_END); |
| break; |
| |
| case OPERATION_STATE_END: |
| // Final state of the operation. |
| if (that.oncomplete) { |
| that.oncomplete(that); |
| } |
| if (!that.canceled) { |
| that.fireResolved(); |
| } |
| stateMachine(that, opTargetState, cacheState, data); |
| break; |
| |
| default: |
| // Any other state is passed down to the state machine describing the operation's specific behavior. |
| // DATAJS INTERNAL START |
| if (true) { |
| // Check that the state machine actually handled the sate. |
| var handled = stateMachine(that, opTargetState, cacheState, data); |
| djsassert(handled, "Bad operation state: " + opTargetState + " cacheState: " + cacheState, this); |
| } else { |
| // DATAJS INTERNAL END |
| stateMachine(that, opTargetState, cacheState, data); |
| // DATAJS INTERNAL START |
| } |
| // DATAJS INTERNAL END |
| break; |
| } |
| }; |
| |
| var transition = function(state, data) { |
| /// <summary>Transitions this operation to a new state.</summary> |
| /// <param name="state" type="Object">State to transition the operation to.</param> |
| /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param> |
| |
| that.s = state; |
| stateData = data; |
| operationStateMachine(state, cacheState, data); |
| }; |
| |
| that.transition = transition; |
| |
| return that; |
| }; |
| |
| DataCacheOperation.prototype.fireResolved = function() { |
| /// <summary>Fires a resolved notification as necessary.</summary> |
| |
| // Fire the resolve just once. |
| var p = this.p; |
| if (p) { |
| this.p = null; |
| p.resolve(this.d); |
| } |
| }; |
| |
| DataCacheOperation.prototype.fireRejected = function(reason) { |
| /// <summary>Fires a rejected notification as necessary.</summary> |
| |
| // Fire the rejection just once. |
| var p = this.p; |
| if (p) { |
| this.p = null; |
| p.reject(reason); |
| } |
| }; |
| |
| DataCacheOperation.prototype.fireCanceled = function() { |
| /// <summary>Fires a canceled notification as necessary.</summary> |
| |
| this.fireRejected({ |
| canceled: true, |
| message: "Operation canceled" |
| }); |
| }; |
| |
| |
| var DataCache = function(options) { |
| /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary> |
| /// <param name="options"> |
| /// Options for the data cache, including name, source, pageSize, |
| /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler. |
| /// </param> |
| /// <returns type="DataCache">A new data cache instance.</returns> |
| |
| var state = CACHE_STATE_INIT; |
| var stats = { |
| counts: 0, |
| netReads: 0, |
| prefetches: 0, |
| cacheReads: 0 |
| }; |
| |
| var clearOperations = []; |
| var readOperations = []; |
| var prefetchOperations = []; |
| |
| var actualCacheSize = 0; // Actual cache size in bytes. |
| var allDataLocal = false; // Whether all data is local. |
| var cacheSize = undefinedDefault(options.cacheSize, 1048576); // Requested cache size in bytes, default 1 MB. |
| var collectionCount = 0; // Number of elements in the server collection. |
| var highestSavedPage = 0; // Highest index of all the saved pages. |
| var highestSavedPageSize = 0; // Item count of the saved page with the highest index. |
| var overflowed = cacheSize === 0; // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0); |
| var pageSize = undefinedDefault(options.pageSize, 50); // Number of elements to store per page. |
| var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling. |
| var version = "1.0"; |
| var cacheFailure; |
| |
| var pendingOperations = 0; |
| |
| var source = options.source; |
| if (typeof source === "string") { |
| // Create a new cache source. |
| source = new ODataCacheSource(options); |
| } |
| source.options = options; |
| |
| // Create a cache local store. |
| var store = storeReq.createStore(options.name, options.mechanism); |
| |
| var that = this; |
| |
| that.onidle = options.idle; |
| that.stats = stats; |
| |
| that.count = function() { |
| /// <summary>Counts the number of items in the collection.</summary> |
| /// <returns type="Object">A promise with the number of items.</returns> |
| |
| if (cacheFailure) { |
| throw cacheFailure; |
| } |
| |
| var deferred = createDeferred(); |
| var canceled = false; |
| |
| if (allDataLocal) { |
| delay(function() { |
| deferred.resolve(collectionCount); |
| }); |
| |
| return deferred.promise(); |
| } |
| |
| // TODO: Consider returning the local data count instead once allDataLocal flag is set to true. |
| var request = source.count(function(count) { |
| request = null; |
| stats.counts++; |
| deferred.resolve(count); |
| }, function(err) { |
| request = null; |
| deferred.reject(extend(err, { |
| canceled: canceled |
| })); |
| }); |
| |
| return extend(deferred.promise(), { |
| cancel: function() { |
| /// <summary>Aborts the count operation.</summary> |
| if (request) { |
| canceled = true; |
| request.abort(); |
| request = null; |
| } |
| } |
| }); |
| }; |
| |
| that.clear = function() { |
| /// <summary>Cancels all running operations and clears all local data associated with this cache.</summary> |
| /// <remarks> |
| /// New read requests made while a clear operation is in progress will not be canceled. |
| /// Instead they will be queued for execution once the operation is completed. |
| /// </remarks> |
| /// <returns type="Object">A promise that has no value and can't be canceled.</returns> |
| |
| if (cacheFailure) { |
| throw cacheFailure; |
| } |
| |
| if (clearOperations.length === 0) { |
| var deferred = createDeferred(); |
| var op = new DataCacheOperation(destroyStateMachine, deferred, false); |
| queueAndStart(op, clearOperations); |
| return deferred.promise(); |
| } |
| return clearOperations[0].p; |
| }; |
| |
| that.filterForward = function(index, count, predicate) { |
| /// <summary>Filters the cache data based a predicate.</summary> |
| /// <param name="index" type="Number">The index of the item to start filtering forward from.</param> |
| /// <param name="count" type="Number">Maximum number of items to include in the result.</param> |
| /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param> |
| /// <remarks> |
| /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate. |
| /// </remarks> |
| /// <returns type="DjsDeferred">A promise for an array of results.</returns> |
| return filter(index, count, predicate, false); |
| }; |
| |
| that.filterBack = function(index, count, predicate) { |
| /// <summary>Filters the cache data based a predicate.</summary> |
| /// <param name="index" type="Number">The index of the item to start filtering backward from.</param> |
| /// <param name="count" type="Number">Maximum number of items to include in the result.</param> |
| /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param> |
| /// <remarks> |
| /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate. |
| /// </remarks> |
| /// <returns type="DjsDeferred">A promise for an array of results.</returns> |
| return filter(index, count, predicate, true); |
| }; |
| |
| that.readRange = function(index, count) { |
| /// <summary>Reads a range of adjacent records.</summary> |
| /// <param name="index" type="Number">Zero-based index of record range to read.</param> |
| /// <param name="count" type="Number">Number of records in the range.</param> |
| /// <remarks> |
| /// New read requests made while a clear operation is in progress will not be canceled. |
| /// Instead they will be queued for execution once the operation is completed. |
| /// </remarks> |
| /// <returns type="DjsDeferred"> |
| /// A promise for an array of records; less records may be returned if the |
| /// end of the collection is found. |
| /// </returns> |
| |
| checkZeroGreater(index, "index"); |
| checkZeroGreater(count, "count"); |
| |
| if (cacheFailure) { |
| throw cacheFailure; |
| } |
| |
| var deferred = createDeferred(); |
| |
| // Merging read operations would be a nice optimization here. |
| var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, {}, 0); |
| queueAndStart(op, readOperations); |
| |
| return extend(deferred.promise(), { |
| cancel: function() { |
| /// <summary>Aborts the readRange operation.</summary> |
| op.cancel(); |
| } |
| }); |
| }; |
| |
| that.ToObservable = that.toObservable = function() { |
| /// <summary>Creates an Observable object that enumerates all the cache contents.</summary> |
| /// <returns>A new Observable object that enumerates all the cache contents.</returns> |
| if (!window.Rx || !window.Rx.Observable) { |
| throw { |
| message: "Rx library not available - include rx.js" |
| }; |
| } |
| |
| if (cacheFailure) { |
| throw cacheFailure; |
| } |
| |
| return window.Rx.Observable.CreateWithDisposable(function(obs) { |
| var disposed = false; |
| var index = 0; |
| |
| var errorCallback = function(error) { |
| if (!disposed) { |
| obs.OnError(error); |
| } |
| }; |
| |
| var successCallback = function(data) { |
| if (!disposed) { |
| var i, len; |
| for (i = 0, len = data.value.length; i < len; i++) { |
| // The wrapper automatically checks for Dispose |
| // on the observer, so we don't need to check it here. |
| obs.OnNext(data.value[i]); |
| } |
| |
| if (data.value.length < pageSize) { |
| obs.OnCompleted(); |
| } else { |
| index += pageSize; |
| that.readRange(index, pageSize).then(successCallback, errorCallback); |
| } |
| } |
| }; |
| |
| that.readRange(index, pageSize).then(successCallback, errorCallback); |
| |
| return { |
| Dispose: function() { |
| disposed = true; |
| } |
| }; |
| }); |
| }; |
| |
| var cacheFailureCallback = function(message) { |
| /// <summary>Creates a function that handles a callback by setting the cache into failure mode.</summary> |
| /// <param name="message" type="String">Message text.</param> |
| /// <returns type="Function">Function to use as error callback.</returns> |
| /// <remarks> |
| /// This function will specifically handle problems with critical store resources |
| /// during cache initialization. |
| /// </remarks> |
| |
| return function(error) { |
| cacheFailure = { |
| message: message, |
| error: error |
| }; |
| |
| // Destroy any pending clear or read operations. |
| // At this point there should be no prefetch operations. |
| // Count operations will go through but are benign because they |
| // won't interact with the store. |
| djsassert(prefetchOperations.length === 0, "prefetchOperations.length === 0"); |
| var i, len; |
| for (i = 0, len = readOperations.length; i < len; i++) { |
| readOperations[i].fireRejected(cacheFailure); |
| } |
| for (i = 0, len = clearOperations.length; i < len; i++) { |
| clearOperations[i].fireRejected(cacheFailure); |
| } |
| |
| // Null out the operation arrays. |
| readOperations = clearOperations = null; |
| }; |
| }; |
| |
| var changeState = function(newState) { |
| /// <summary>Updates the cache's state and signals all pending operations of the change.</summary> |
| /// <param name="newState" type="Object">New cache state.</param> |
| /// <remarks>This method is a no-op if the cache's current state and the new state are the same.</remarks> |
| |
| if (newState !== state) { |
| state = newState; |
| var operations = clearOperations.concat(readOperations, prefetchOperations); |
| var i, len; |
| for (i = 0, len = operations.length; i < len; i++) { |
| operations[i].run(state); |
| } |
| } |
| }; |
| |
| var clearStore = function() { |
| /// <summary>Removes all the data stored in the cache.</summary> |
| /// <returns type="DjsDeferred">A promise with no value.</returns> |
| djsassert(state === CACHE_STATE_DESTROY || state === CACHE_STATE_INIT, "DataCache.clearStore() - cache is not on the destroy or initialize state, current sate = " + state); |
| |
| var deferred = new DjsDeferred(); |
| store.clear(function() { |
| |
| // Reset the cache settings. |
| actualCacheSize = 0; |
| allDataLocal = false; |
| collectionCount = 0; |
| highestSavedPage = 0; |
| highestSavedPageSize = 0; |
| overflowed = cacheSize === 0; |
| |
| // version is not reset, in case there is other state in eg V1.1 that is still around. |
| |
| // Reset the cache stats. |
| stats = { |
| counts: 0, |
| netReads: 0, |
| prefetches: 0, |
| cacheReads: 0 |
| }; |
| that.stats = stats; |
| |
| store.close(); |
| deferred.resolve(); |
| }, function(err) { |
| deferred.reject(err); |
| }); |
| return deferred; |
| }; |
| |
| var dequeueOperation = function(operation) { |
| /// <summary>Removes an operation from the caches queues and changes the cache state to idle.</summary> |
| /// <param name="operation" type="DataCacheOperation">Operation to dequeue.</param> |
| /// <remarks>This method is used as a handler for the operation's oncomplete event.</remarks> |
| |
| var removed = removeFromArray(clearOperations, operation); |
| if (!removed) { |
| removed = removeFromArray(readOperations, operation); |
| if (!removed) { |
| removeFromArray(prefetchOperations, operation); |
| } |
| } |
| |
| pendingOperations--; |
| changeState(CACHE_STATE_IDLE); |
| }; |
| |
| var fetchPage = function(start) { |
| /// <summary>Requests data from the cache source.</summary> |
| /// <param name="start" type="Number">Zero-based index of items to request.</param> |
| /// <returns type="DjsDeferred">A promise for a page object with (i)ndex, (c)ount, (d)ata.</returns> |
| |
| djsassert(state !== CACHE_STATE_DESTROY, "DataCache.fetchPage() - cache is on the destroy state"); |
| djsassert(state !== CACHE_STATE_IDLE, "DataCache.fetchPage() - cache is on the idle state"); |
| |
| var deferred = new DjsDeferred(); |
| var canceled = false; |
| |
| var request = source.read(start, pageSize, function(data) { |
| var length = getJsonValueArraryLength(data); |
| var page = { |
| i: start, |
| c: length, |
| d: data |
| }; |
| deferred.resolve(page); |
| }, function(err) { |
| deferred.reject(err); |
| }); |
| |
| return extend(deferred, { |
| cancel: function() { |
| if (request) { |
| request.abort(); |
| canceled = true; |
| request = null; |
| } |
| } |
| }); |
| }; |
| |
| var filter = function(index, count, predicate, backwards) { |
| /// <summary>Filters the cache data based a predicate.</summary> |
| /// <param name="index" type="Number">The index of the item to start filtering from.</param> |
| /// <param name="count" type="Number">Maximum number of items to include in the result.</param> |
| /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param> |
| /// <param name="backwards" type="Boolean">True if the filtering should move backward from the specified index, falsey otherwise.</param> |
| /// <remarks> |
| /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate. |
| /// </remarks> |
| /// <returns type="DjsDeferred">A promise for an array of results.</returns> |
| index = parseInt10(index); |
| count = parseInt10(count); |
| |
| if (isNaN(index)) { |
| throw { |
| message: "'index' must be a valid number.", |
| index: index |
| }; |
| } |
| if (isNaN(count)) { |
| throw { |
| message: "'count' must be a valid number.", |
| count: count |
| }; |
| } |
| |
| if (cacheFailure) { |
| throw cacheFailure; |
| } |
| |
| index = Math.max(index, 0); |
| |
| var deferred = createDeferred(); |
| var returnData = {}; |
| returnData.value = []; |
| var canceled = false; |
| var pendingReadRange = null; |
| |
| var readMore = function(readIndex, readCount) { |
| if (!canceled) { |
| if (count > 0 && returnData.value.length >= count) { |
| deferred.resolve(returnData); |
| } else { |
| pendingReadRange = that.readRange(readIndex, readCount).then(function(data) { |
| if (data["@odata.context"] && !returnData["@odata.context"]) { |
| returnData["@odata.context"] = data["@odata.context"]; |
| } |
| |
| for (var i = 0, length = data.value.length; i < length && (count < 0 || returnData.value.length < count); i++) { |
| var dataIndex = backwards ? length - i - 1 : i; |
| var item = data.value[dataIndex]; |
| if (predicate(item)) { |
| var element = { |
| index: readIndex + dataIndex, |
| item: item |
| }; |
| |
| backwards ? returnData.value.unshift(element) : returnData.value.push(element); |
| } |
| } |
| |
| // Have we reached the end of the collection? |
| if ((!backwards && data.value.length < readCount) || (backwards && readIndex <= 0)) { |
| deferred.resolve(returnData); |
| } else { |
| var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount; |
| readMore(nextIndex, pageSize); |
| } |
| }, function(err) { |
| deferred.reject(err); |
| }); |
| } |
| } |
| }; |
| |
| // Initially, we read from the given starting index to the next/previous page boundary |
| var initialPage = snapToPageBoundaries(index, index, pageSize); |
| var initialIndex = backwards ? initialPage.i : index; |
| var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index; |
| readMore(initialIndex, initialCount); |
| |
| return extend(deferred.promise(), { |
| cancel: function() { |
| /// <summary>Aborts the filter operation</summary> |
| if (pendingReadRange) { |
| pendingReadRange.cancel(); |
| } |
| canceled = true; |
| } |
| }); |
| }; |
| |
| var fireOnIdle = function() { |
| /// <summary>Fires an onidle event if any functions are assigned.</summary> |
| |
| if (that.onidle && pendingOperations === 0) { |
| that.onidle(); |
| } |
| }; |
| |
| var prefetch = function(start) { |
| /// <summary>Creates and starts a new prefetch operation.</summary> |
| /// <param name="start" type="Number">Zero-based index of the items to prefetch.</param> |
| /// <remarks> |
| /// This method is a no-op if any of the following conditions is true: |
| /// 1.- prefetchSize is 0 |
| /// 2.- All data has been read and stored locally in the cache. |
| /// 3.- There is already an all data prefetch operation queued. |
| /// 4.- The cache has run out of available space (overflowed). |
| /// <remarks> |
| |
| if (allDataLocal || prefetchSize === 0 || overflowed) { |
| return; |
| } |
| |
| djsassert(state === CACHE_STATE_READ, "DataCache.prefetch() - cache is not on the read state, current state: " + state); |
| |
| if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) { |
| // Merging prefetch operations would be a nice optimization here. |
| var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize); |
| queueAndStart(op, prefetchOperations); |
| } |
| }; |
| |
| var queueAndStart = function(op, queue) { |
| /// <summary>Queues an operation and runs it.</summary> |
| /// <param name="op" type="DataCacheOperation">Operation to queue.</param> |
| /// <param name="queue" type="Array">Array that will store the operation.</param> |
| |
| op.oncomplete = dequeueOperation; |
| queue.push(op); |
| pendingOperations++; |
| op.run(state); |
| }; |
| |
| var readPage = function(key) { |
| /// <summary>Requests a page from the cache local store.</summary> |
| /// <param name="key" type="Number">Zero-based index of the reuqested page.</param> |
| /// <returns type="DjsDeferred">A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.</returns> |
| |
| djsassert(state !== CACHE_STATE_DESTROY, "DataCache.readPage() - cache is on the destroy state"); |
| |
| var canceled = false; |
| var deferred = extend(new DjsDeferred(), { |
| cancel: function() { |
| /// <summary>Aborts the readPage operation.</summary> |
| canceled = true; |
| } |
| }); |
| |
| var error = storeFailureCallback(deferred, "Read page from store failure"); |
| |
| store.contains(key, function(contained) { |
| if (canceled) { |
| return; |
| } |
| if (contained) { |
| store.read(key, function(_, data) { |
| if (!canceled) { |
| deferred.resolve(data !== undefined, data); |
| } |
| }, error); |
| return; |
| } |
| deferred.resolve(false); |
| }, error); |
| return deferred; |
| }; |
| |
| var savePage = function(key, page) { |
| /// <summary>Saves a page to the cache local store.</summary> |
| /// <param name="key" type="Number">Zero-based index of the requested page.</param> |
| /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata, and (t)icks.</param> |
| /// <returns type="DjsDeferred">A promise with no value.</returns> |
| |
| djsassert(state !== CACHE_STATE_DESTROY, "DataCache.savePage() - cache is on the destroy state"); |
| djsassert(state !== CACHE_STATE_IDLE, "DataCache.savePage() - cache is on the idle state"); |
| |
| var canceled = false; |
| |
| var deferred = extend(new DjsDeferred(), { |
| cancel: function() { |
| /// <summary>Aborts the readPage operation.</summary> |
| canceled = true; |
| } |
| }); |
| |
| var error = storeFailureCallback(deferred, "Save page to store failure"); |
| |
| var resolve = function() { |
| deferred.resolve(true); |
| }; |
| |
| if (page.c > 0) { |
| var pageBytes = estimateSize(page); |
| overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes; |
| |
| if (!overflowed) { |
| store.addOrUpdate(key, page, function() { |
| updateSettings(page, pageBytes); |
| saveSettings(resolve, error); |
| }, error); |
| } else { |
| resolve(); |
| } |
| } else { |
| updateSettings(page, 0); |
| saveSettings(resolve, error); |
| } |
| return deferred; |
| }; |
| |
| var saveSettings = function(success, error) { |
| /// <summary>Saves the cache's current settings to the local store.</summary> |
| /// <param name="success" type="Function">Success callback.</param> |
| /// <param name="error" type="Function">Errror callback.</param> |
| |
| var settings = { |
| actualCacheSize: actualCacheSize, |
| allDataLocal: allDataLocal, |
| cacheSize: cacheSize, |
| collectionCount: collectionCount, |
| highestSavedPage: highestSavedPage, |
| highestSavedPageSize: highestSavedPageSize, |
| pageSize: pageSize, |
| sourceId: source.identifier, |
| version: version |
| }; |
| |
| store.addOrUpdate("__settings", settings, success, error); |
| }; |
| |
| var storeFailureCallback = function(deferred /*, message*/ ) { |
| /// <summary>Creates a function that handles a store error.</summary> |
| /// <param name="deferred" type="DjsDeferred">Deferred object to resolve.</param> |
| /// <param name="message" type="String">Message text.</param> |
| /// <returns type="Function">Function to use as error callback.</returns> |
| /// <remarks> |
| /// This function will specifically handle problems when interacting with the store. |
| /// </remarks> |
| |
| return function( /*error*/ ) { |
| // var console = window.console; |
| // if (console && console.log) { |
| // console.log(message); |
| // console.dir(error); |
| // } |
| deferred.resolve(false); |
| }; |
| }; |
| |
| var updateSettings = function(page, pageBytes) { |
| /// <summary>Updates the cache's settings based on a page object.</summary> |
| /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata.</param> |
| /// <param name="pageBytes" type="Number">Size of the page in bytes.</param> |
| |
| var pageCount = page.c; |
| var pageIndex = page.i; |
| |
| // Detect the collection size. |
| if (pageCount === 0) { |
| if (highestSavedPage === pageIndex - pageSize) { |
| collectionCount = highestSavedPage + highestSavedPageSize; |
| } |
| } else { |
| highestSavedPage = Math.max(highestSavedPage, pageIndex); |
| if (highestSavedPage === pageIndex) { |
| highestSavedPageSize = pageCount; |
| } |
| actualCacheSize += pageBytes; |
| if (pageCount < pageSize && !collectionCount) { |
| collectionCount = pageIndex + pageCount; |
| } |
| } |
| |
| // Detect the end of the collection. |
| if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) { |
| allDataLocal = true; |
| } |
| }; |
| |
| var cancelStateMachine = function(operation, opTargetState, cacheState, data) { |
| /// <summary>State machine describing the behavior for cancelling a read or prefetch operation.</summary> |
| /// <param name="operation" type="DataCacheOperation">Operation being run.</param> |
| /// <param name="opTargetState" type="Object">Operation state to transition to.</param> |
| /// <param name="cacheState" type="Object">Current cache state.</param> |
| /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param> |
| /// <remarks> |
| /// This state machine contains behavior common to read and prefetch operations. |
| /// </remarks> |
| |
| var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END; |
| if (canceled) { |
| if (opTargetState === OPERATION_STATE_CANCEL) { |
| // Cancel state. |
| // Data is expected to be any pending request made to the cache. |
| if (data && data.cancel) { |
| data.cancel(); |
| } |
| } |
| } |
| return canceled; |
| }; |
| |
| var destroyStateMachine = function(operation, opTargetState, cacheState) { |
| /// <summary>State machine describing the behavior of a clear operation.</summary> |
| /// <param name="operation" type="DataCacheOperation">Operation being run.</param> |
| /// <param name="opTargetState" type="Object">Operation state to transition to.</param> |
| /// <param name="cacheState" type="Object">Current cache state.</param> |
| /// <remarks> |
| /// Clear operations have the highest priority and can't be interrupted by other operations; however, |
| /// they will preempt any other operation currently executing. |
| /// </remarks> |
| |
| var transition = operation.transition; |
| |
| // Signal the cache that a clear operation is running. |
| if (cacheState !== CACHE_STATE_DESTROY) { |
| changeState(CACHE_STATE_DESTROY); |
| return true; |
| } |
| |
| switch (opTargetState) { |
| case OPERATION_STATE_START: |
| // Initial state of the operation. |
| transition(DESTROY_STATE_CLEAR); |
| break; |
| |
| case OPERATION_STATE_END: |
| // State that signals the operation is done. |
| fireOnIdle(); |
| break; |
| |
| case DESTROY_STATE_CLEAR: |
| // State that clears all the local data of the cache. |
| clearStore().then(function() { |
| // Terminate the operation once the local store has been cleared. |
| operation.complete(); |
| }); |
| // Wait until the clear request completes. |
| operation.wait(); |
| break; |
| |
| default: |
| return false; |
| } |
| return true; |
| }; |
| |
| var prefetchStateMachine = function(operation, opTargetState, cacheState, data) { |
| /// <summary>State machine describing the behavior of a prefetch operation.</summary> |
| /// <param name="operation" type="DataCacheOperation">Operation being run.</param> |
| /// <param name="opTargetState" type="Object">Operation state to transition to.</param> |
| /// <param name="cacheState" type="Object">Current cache state.</param> |
| /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param> |
| /// <remarks> |
| /// Prefetch operations have the lowest priority and will be interrupted by operations of |
| /// other kinds. A preempted prefetch operation will resume its execution only when the state |
| /// of the cache returns to idle. |
| /// |
| /// If a clear operation starts executing then all the prefetch operations are canceled, |
| /// even if they haven't started executing yet. |
| /// </remarks> |
| |
| // Handle cancelation |
| if (!cancelStateMachine(operation, opTargetState, cacheState, data)) { |
| |
| var transition = operation.transition; |
| |
| // Handle preemption |
| if (cacheState !== CACHE_STATE_PREFETCH) { |
| if (cacheState === CACHE_STATE_DESTROY) { |
| if (opTargetState !== OPERATION_STATE_CANCEL) { |
| operation.cancel(); |
| } |
| } else if (cacheState === CACHE_STATE_IDLE) { |
| // Signal the cache that a prefetch operation is running. |
| changeState(CACHE_STATE_PREFETCH); |
| } |
| return true; |
| } |
| |
| switch (opTargetState) { |
| case OPERATION_STATE_START: |
| // Initial state of the operation. |
| if (prefetchOperations[0] === operation) { |
| transition(READ_STATE_LOCAL, operation.i); |
| } |
| break; |
| |
| case READ_STATE_DONE: |
| // State that determines if the operation can be resolved or has to |
| // continue processing. |
| // Data is expected to be the read page. |
| var pending = operation.pending; |
| |
| if (pending > 0) { |
| pending -= Math.min(pending, data.c); |
| } |
| |
| // Are we done, or has all the data been stored? |
| if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) { |
| operation.complete(); |
| } else { |
| // Continue processing the operation. |
| operation.pending = pending; |
| transition(READ_STATE_LOCAL, data.i + pageSize); |
| } |
| break; |
| |
| default: |
| return readSaveStateMachine(operation, opTargetState, cacheState, data, true); |
| } |
| } |
| return true; |
| }; |
| |
| var readStateMachine = function(operation, opTargetState, cacheState, data) { |
| /// <summary>State machine describing the behavior of a read operation.</summary> |
| /// <param name="operation" type="DataCacheOperation">Operation being run.</param> |
| /// <param name="opTargetState" type="Object">Operation state to transition to.</param> |
| /// <param name="cacheState" type="Object">Current cache state.</param> |
| /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param> |
| /// <remarks> |
| /// Read operations have a higher priority than prefetch operations, but lower than |
| /// clear operations. They will preempt any prefetch operation currently running |
| /// but will be interrupted by a clear operation. |
| /// |
| /// If a clear operation starts executing then all the currently running |
| /// read operations are canceled. Read operations that haven't started yet will |
| /// wait in the start state until the destory operation finishes. |
| /// </remarks> |
| |
| // Handle cancelation |
| if (!cancelStateMachine(operation, opTargetState, cacheState, data)) { |
| |
| var transition = operation.transition; |
| |
| // Handle preemption |
| if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) { |
| if (cacheState === CACHE_STATE_DESTROY) { |
| if (opTargetState !== OPERATION_STATE_START) { |
| operation.cancel(); |
| } |
| } else if (cacheState !== CACHE_STATE_WRITE) { |
| // Signal the cache that a read operation is running. |
| djsassert(state == CACHE_STATE_IDLE || state === CACHE_STATE_PREFETCH, "DataCache.readStateMachine() - cache is not on the read or idle state."); |
| changeState(CACHE_STATE_READ); |
| } |
| |
| return true; |
| } |
| |
| switch (opTargetState) { |
| case OPERATION_STATE_START: |
| // Initial state of the operation. |
| // Wait until the cache is idle or prefetching. |
| if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) { |
| // Signal the cache that a read operation is running. |
| changeState(CACHE_STATE_READ); |
| if (operation.c >= 0) { |
| // Snap the requested range to a page boundary. |
| var range = snapToPageBoundaries(operation.i, operation.c, pageSize); |
| transition(READ_STATE_LOCAL, range.i); |
| } else { |
| transition(READ_STATE_DONE, operation); |
| } |
| } |
| break; |
| |
| case READ_STATE_DONE: |
| // State that determines if the operation can be resolved or has to |
| // continue processing. |
| // Data is expected to be the read page. |
| appendPage(operation, data); |
| var len = getJsonValueArraryLength(operation.d); |
| // Are we done? |
| if (operation.c === len || data.c < pageSize) { |
| // Update the stats, request for a prefetch operation. |
| stats.cacheReads++; |
| prefetch(data.i + data.c); |
| // Terminate the operation. |
| operation.complete(); |
| } else { |
| // Continue processing the operation. |
| transition(READ_STATE_LOCAL, data.i + pageSize); |
| } |
| break; |
| |
| default: |
| return readSaveStateMachine(operation, opTargetState, cacheState, data, false); |
| } |
| } |
| |
| return true; |
| }; |
| |
| var readSaveStateMachine = function(operation, opTargetState, cacheState, data, isPrefetch) { |
| /// <summary>State machine describing the behavior for reading and saving data into the cache.</summary> |
| /// <param name="operation" type="DataCacheOperation">Operation being run.</param> |
| /// <param name="opTargetState" type="Object">Operation state to transition to.</param> |
| /// <param name="cacheState" type="Object">Current cache state.</param> |
| /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param> |
| /// <param name="isPrefetch" type="Boolean">Flag indicating whether a read (false) or prefetch (true) operation is running. |
| /// <remarks> |
| /// This state machine contains behavior common to read and prefetch operations. |
| /// </remarks> |
| |
| var error = operation.error; |
| var transition = operation.transition; |
| var wait = operation.wait; |
| var request; |
| |
| switch (opTargetState) { |
| case OPERATION_STATE_END: |
| // State that signals the operation is done. |
| fireOnIdle(); |
| break; |
| |
| case READ_STATE_LOCAL: |
| // State that requests for a page from the local store. |
| // Data is expected to be the index of the page to request. |
| request = readPage(data).then(function(found, page) { |
| // Signal the cache that a read operation is running. |
| if (!operation.canceled) { |
| if (found) { |
| // The page is in the local store, check if the operation can be resolved. |
| transition(READ_STATE_DONE, page); |
| } else { |
| // The page is not in the local store, request it from the source. |
| transition(READ_STATE_SOURCE, data); |
| } |
| } |
| }); |
| break; |
| |
| case READ_STATE_SOURCE: |
| // State that requests for a page from the cache source. |
| // Data is expected to be the index of the page to request. |
| request = fetchPage(data).then(function(page) { |
| // Signal the cache that a read operation is running. |
| if (!operation.canceled) { |
| // Update the stats and save the page to the local store. |
| if (isPrefetch) { |
| stats.prefetches++; |
| } else { |
| stats.netReads++; |
| } |
| transition(READ_STATE_SAVE, page); |
| } |
| }, error); |
| break; |
| |
| case READ_STATE_SAVE: |
| // State that saves a page to the local store. |
| // Data is expected to be the page to save. |
| // Write access to the store is exclusive. |
| if (cacheState !== CACHE_STATE_WRITE) { |
| changeState(CACHE_STATE_WRITE); |
| request = savePage(data.i, data).then(function(saved) { |
| if (!operation.canceled) { |
| if (!saved && isPrefetch) { |
| operation.pending = 0; |
| } |
| // Check if the operation can be resolved. |
| transition(READ_STATE_DONE, data); |
| } |
| changeState(CACHE_STATE_IDLE); |
| }); |
| } |
| break; |
| |
| default: |
| // Unknown state that can't be handled by this state machine. |
| return false; |
| } |
| |
| if (request) { |
| // The operation might have been canceled between stack frames do to the async calls. |
| if (operation.canceled) { |
| request.cancel(); |
| } else if (operation.s === opTargetState) { |
| // Wait for the request to complete. |
| wait(request); |
| } |
| } |
| |
| return true; |
| }; |
| |
| // Initialize the cache. |
| store.read("__settings", function(_, settings) { |
| if (assigned(settings)) { |
| var settingsVersion = settings.version; |
| if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) { |
| cacheFailureCallback("Unsupported cache store version " + settingsVersion)(); |
| return; |
| } |
| |
| if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) { |
| // The shape or the source of the data was changed so invalidate the store. |
| clearStore().then(function() { |
| // Signal the cache is fully initialized. |
| changeState(CACHE_STATE_IDLE); |
| }, cacheFailureCallback("Unable to clear store during initialization")); |
| } else { |
| // Restore the saved settings. |
| actualCacheSize = settings.actualCacheSize; |
| allDataLocal = settings.allDataLocal; |
| cacheSize = settings.cacheSize; |
| collectionCount = settings.collectionCount; |
| highestSavedPage = settings.highestSavedPage; |
| highestSavedPageSize = settings.highestSavedPageSize; |
| version = settingsVersion; |
| |
| // Signal the cache is fully initialized. |
| changeState(CACHE_STATE_IDLE); |
| } |
| } else { |
| // This is a brand new cache. |
| saveSettings(function() { |
| // Signal the cache is fully initialized. |
| changeState(CACHE_STATE_IDLE); |
| }, cacheFailureCallback("Unable to write settings during initialization.")); |
| } |
| }, cacheFailureCallback("Unable to read settings from store.")); |
| |
| return that; |
| }; |
| |
| exports.createDataCache = function(options) { |
| /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary> |
| /// <param name="options"> |
| /// Options for the data cache, including name, source, pageSize, |
| /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler. |
| /// </param> |
| /// <returns type="DataCache">A new data cache instance.</returns> |
| checkUndefinedGreaterThanZero(options.pageSize, "pageSize"); |
| checkUndefinedOrNumber(options.cacheSize, "cacheSize"); |
| checkUndefinedOrNumber(options.prefetchSize, "prefetchSize"); |
| |
| if (!assigned(options.name)) { |
| throw { |
| message: "Undefined or null name", |
| options: options |
| }; |
| } |
| |
| if (!assigned(options.source)) { |
| throw { |
| message: "Undefined source", |
| options: options |
| }; |
| } |
| |
| return new DataCache(options); |
| }; |
| |
| exports.estimateSize = estimateSize; |
| |
| |
| }, { |
| "./cache/source": 3, |
| "./datajs.js": 4, |
| "./datajs/deferred.js": 5 |
| } |
| ], |
| 3: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var utils = require("./../datajs.js").utils; |
| var odataRequest = require("./../odata.js"); |
| |
| var parseInt10 = utils.parseInt10; |
| var normalizeURICase = utils.normalizeURICase; |
| |
| var appendQueryOption = function(uri, queryOption) { |
| /// <summary>Appends the specified escaped query option to the specified URI.</summary> |
| /// <param name="uri" type="String">URI to append option to.</param> |
| /// <param name="queryOption" type="String">Escaped query option to append.</param> |
| var separator = (uri.indexOf("?") >= 0) ? "&" : "?"; |
| return uri + separator + queryOption; |
| }; |
| |
| var appendSegment = function(uri, segment) { |
| /// <summary>Appends the specified segment to the given URI.</summary> |
| /// <param name="uri" type="String">URI to append a segment to.</param> |
| /// <param name="segment" type="String">Segment to append.</param> |
| /// <returns type="String">The original URI with a new segment appended.</returns> |
| |
| var index = uri.indexOf("?"); |
| var queryPortion = ""; |
| if (index >= 0) { |
| queryPortion = uri.substr(index); |
| uri = uri.substr(0, index); |
| } |
| |
| if (uri[uri.length - 1] !== "/") { |
| uri += "/"; |
| } |
| return uri + segment + queryPortion; |
| }; |
| |
| var buildODataRequest = function(uri, options) { |
| /// <summary>Builds a request object to GET the specified URI.</summary> |
| /// <param name="uri" type="String">URI for request.</param> |
| /// <param name="options" type="Object">Additional options.</param> |
| |
| return { |
| method: "GET", |
| requestUri: uri, |
| user: options.user, |
| password: options.password, |
| enableJsonpCallback: options.enableJsonpCallback, |
| callbackParameterName: options.callbackParameterName, |
| formatQueryString: options.formatQueryString |
| }; |
| }; |
| |
| var findQueryOptionStart = function(uri, name) { |
| /// <summary>Finds the index where the value of a query option starts.</summary> |
| /// <param name="uri" type="String">URI to search in.</param> |
| /// <param name="name" type="String">Name to look for.</param> |
| /// <returns type="Number">The index where the query option starts.</returns> |
| |
| var result = -1; |
| var queryIndex = uri.indexOf("?"); |
| if (queryIndex !== -1) { |
| var start = uri.indexOf("?" + name + "=", queryIndex); |
| if (start === -1) { |
| start = uri.indexOf("&" + name + "=", queryIndex); |
| } |
| if (start !== -1) { |
| result = start + name.length + 2; |
| } |
| } |
| return result; |
| }; |
| |
| var queryForData = function(uri, options, success, error) { |
| /// <summary>Gets data from an OData service.</summary> |
| /// <param name="uri" type="String">URI to the OData service.</param> |
| /// <param name="options" type="Object">Object with additional well-known request options.</param> |
| /// <param name="success" type="Function">Success callback.</param> |
| /// <param name="error" type="Function">Error callback.</param> |
| /// <returns type="Object">Object with an abort method.</returns> |
| |
| var request = queryForDataInternal(uri, options, {}, success, error); |
| return request; |
| }; |
| |
| var queryForDataInternal = function(uri, options, data, success, error) { |
| /// <summary>Gets data from an OData service taking into consideration server side paging.</summary> |
| /// <param name="uri" type="String">URI to the OData service.</param> |
| /// <param name="options" type="Object">Object with additional well-known request options.</param> |
| /// <param name="data" type="Array">Array that stores the data provided by the OData service.</param> |
| /// <param name="success" type="Function">Success callback.</param> |
| /// <param name="error" type="Function">Error callback.</param> |
| /// <returns type="Object">Object with an abort method.</returns> |
| |
| var request = buildODataRequest(uri, options); |
| var currentRequest = odataRequest.request(request, function(newData) { |
| var nextLink = newData["@odata.nextLink"]; |
| if (nextLink) { |
| var index = uri.indexOf(".svc/", 0); |
| if (index != -1) { |
| nextLink = uri.substring(0, index + 5) + nextLink; |
| } |
| } |
| |
| if (data.value && newData.value) { |
| data.value = data.value.concat(newData.value); |
| } else { |
| for (var property in newData) { |
| if (property != "@odata.nextLink") { |
| data[property] = newData[property]; |
| } |
| } |
| } |
| |
| if (nextLink) { |
| currentRequest = queryForDataInternal(nextLink, options, data, success, error); |
| } else { |
| success(data); |
| } |
| }, error, undefined, options.httpClient, options.metadata); |
| |
| return { |
| abort: function() { |
| currentRequest.abort(); |
| } |
| }; |
| }; |
| |
| var ODataCacheSource = function(options) { |
| /// <summary>Creates a data cache source object for requesting data from an OData service.</summary> |
| /// <param name="options">Options for the cache data source.</param> |
| /// <returns type="ODataCacheSource">A new data cache source instance.</returns> |
| |
| var that = this; |
| var uri = options.source; |
| |
| that.identifier = normalizeURICase(encodeURI(decodeURI(uri))); |
| that.options = options; |
| |
| that.count = function(success, error) { |
| /// <summary>Gets the number of items in the collection.</summary> |
| /// <param name="success" type="Function">Success callback with the item count.</param> |
| /// <param name="error" type="Function">Error callback.</param> |
| /// <returns type="Object">Request object with an abort method./<param> |
| |
| var options = that.options; |
| return odataRequest.request( |
| buildODataRequest(appendSegment(uri, "$count"), options), |
| function(data) { |
| var count = parseInt10(data.toString()); |
| if (isNaN(count)) { |
| error({ |
| message: "Count is NaN", |
| count: count |
| }); |
| } else { |
| success(count); |
| } |
| }, error, undefined, options.httpClient, options.metadata); |
| }; |
| |
| that.read = function(index, count, success, error) { |
| /// <summary>Gets a number of consecutive items from the collection.</summary> |
| /// <param name="index" type="Number">Zero-based index of the items to retrieve.</param> |
| /// <param name="count" type="Number">Number of items to retrieve.</param> |
| /// <param name="success" type="Function">Success callback with the requested items.</param> |
| /// <param name="error" type="Function">Error callback.</param> |
| /// <returns type="Object">Request object with an abort method./<param> |
| |
| var queryOptions = "$skip=" + index + "&$top=" + count; |
| return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error); |
| }; |
| |
| return that; |
| }; |
| |
| exports.ODataCacheSource = ODataCacheSource; |
| |
| }, { |
| "./../datajs.js": 4, |
| "./../odata.js": 8 |
| } |
| ], |
| 4: [ |
| function(require, module, exports) { |
| |
| |
| |
| //expose all external usable functions via self.apiFunc = function |
| exports.version = { |
| major: 1, |
| minor: 1, |
| build: 1 |
| }; |
| |
| |
| exports.deferred = require('./datajs/deferred.js'); |
| exports.utils = require('./datajs/utils.js'); |
| exports.xml = require('./datajs/xml.js'); |
| |
| |
| }, { |
| "./datajs/deferred.js": 5, |
| "./datajs/utils.js": 6, |
| "./datajs/xml.js": 7 |
| } |
| ], |
| 5: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var forwardCall = function(thisValue, name, returnValue) { |
| /// <summary>Creates a new function to forward a call.</summary> |
| /// <param name="thisValue" type="Object">Value to use as the 'this' object.</param> |
| /// <param name="name" type="String">Name of function to forward to.</param> |
| /// <param name="returnValue" type="Object">Return value for the forward call (helps keep identity when chaining calls).</param> |
| /// <returns type="Function">A new function that will forward a call.</returns> |
| |
| return function() { |
| thisValue[name].apply(thisValue, arguments); |
| return returnValue; |
| }; |
| }; |
| |
| var DjsDeferred = function() { |
| /// <summary>Initializes a new DjsDeferred object.</summary> |
| /// <remarks> |
| /// Compability Note A - Ordering of callbacks through chained 'then' invocations |
| /// |
| /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A |
| /// implies that .then() returns a distinct object. |
| //// |
| /// For compatibility with http://api.jquery.com/category/deferred-object/ |
| /// we return this same object. This affects ordering, as |
| /// the jQuery version will fire callbacks in registration |
| /// order regardless of whether they occur on the result |
| /// or the original object. |
| /// |
| /// Compability Note B - Fulfillment value |
| /// |
| /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A |
| /// implies that the result of a success callback is the |
| /// fulfillment value of the object and is received by |
| /// other success callbacks that are chained. |
| /// |
| /// For compatibility with http://api.jquery.com/category/deferred-object/ |
| /// we disregard this value instead. |
| /// </remarks> |
| |
| this._arguments = undefined; |
| this._done = undefined; |
| this._fail = undefined; |
| this._resolved = false; |
| this._rejected = false; |
| }; |
| |
| DjsDeferred.prototype = { |
| then: function(fulfilledHandler, errorHandler /*, progressHandler */ ) { |
| /// <summary>Adds success and error callbacks for this deferred object.</summary> |
| /// <param name="fulfilledHandler" type="Function" mayBeNull="true" optional="true">Success callback.</param> |
| /// <param name="errorHandler" type="Function" mayBeNull="true" optional="true">Error callback.</param> |
| /// <remarks>See Compatibility Note A.</remarks> |
| |
| if (fulfilledHandler) { |
| if (!this._done) { |
| this._done = [fulfilledHandler]; |
| } else { |
| this._done.push(fulfilledHandler); |
| } |
| } |
| |
| if (errorHandler) { |
| if (!this._fail) { |
| this._fail = [errorHandler]; |
| } else { |
| this._fail.push(errorHandler); |
| } |
| } |
| |
| //// See Compatibility Note A in the DjsDeferred constructor. |
| //// if (!this._next) { |
| //// this._next = createDeferred(); |
| //// } |
| //// return this._next.promise(); |
| |
| if (this._resolved) { |
| this.resolve.apply(this, this._arguments); |
| } else if (this._rejected) { |
| this.reject.apply(this, this._arguments); |
| } |
| |
| return this; |
| }, |
| |
| resolve: function( /* args */ ) { |
| /// <summary>Invokes success callbacks for this deferred object.</summary> |
| /// <remarks>All arguments are forwarded to success callbacks.</remarks> |
| |
| |
| if (this._done) { |
| var i, len; |
| for (i = 0, len = this._done.length; i < len; i++) { |
| //// See Compability Note B - Fulfillment value. |
| //// var nextValue = |
| this._done[i].apply(null, arguments); |
| } |
| |
| //// See Compatibility Note A in the DjsDeferred constructor. |
| //// this._next.resolve(nextValue); |
| //// delete this._next; |
| |
| this._done = undefined; |
| this._resolved = false; |
| this._arguments = undefined; |
| } else { |
| this._resolved = true; |
| this._arguments = arguments; |
| } |
| }, |
| |
| reject: function( /* args */ ) { |
| /// <summary>Invokes error callbacks for this deferred object.</summary> |
| /// <remarks>All arguments are forwarded to error callbacks.</remarks> |
| if (this._fail) { |
| var i, len; |
| for (i = 0, len = this._fail.length; i < len; i++) { |
| this._fail[i].apply(null, arguments); |
| } |
| |
| this._fail = undefined; |
| this._rejected = false; |
| this._arguments = undefined; |
| } else { |
| this._rejected = true; |
| this._arguments = arguments; |
| } |
| }, |
| |
| promise: function() { |
| /// <summary>Returns a version of this object that has only the read-only methods available.</summary> |
| /// <returns>An object with only the promise object.</returns> |
| |
| var result = {}; |
| result.then = forwardCall(this, "then", result); |
| return result; |
| } |
| }; |
| |
| var createDeferred = function() { |
| /// <summary>Creates a deferred object.</summary> |
| /// <returns type="DjsDeferred"> |
| /// A new deferred object. If jQuery is installed, then a jQuery |
| /// Deferred object is returned, which provides a superset of features. |
| /// </returns> |
| |
| if (window.jQuery && window.jQuery.Deferred) { |
| return new window.jQuery.Deferred(); |
| } else { |
| return new DjsDeferred(); |
| } |
| }; |
| |
| exports.createDeferred = createDeferred; |
| exports.DjsDeferred = DjsDeferred; |
| |
| |
| }, {} |
| ], |
| 6: [ |
| function(require, module, exports) { |
| |
| |
| var activeXObject = function(progId) { |
| /// <summary>Creates a new ActiveXObject from the given progId.</summary> |
| /// <param name="progId" type="String" mayBeNull="false" optional="false"> |
| /// ProgId string of the desired ActiveXObject. |
| /// </param> |
| /// <remarks> |
| /// This function throws whatever exception might occur during the creation |
| /// of the ActiveXObject. |
| /// </remarks> |
| /// <returns type="Object"> |
| /// The ActiveXObject instance. Null if ActiveX is not supported by the |
| /// browser. |
| /// </returns> |
| if (window.ActiveXObject) { |
| return new window.ActiveXObject(progId); |
| } |
| return null; |
| }; |
| |
| var assigned = function(value) { |
| /// <summary>Checks whether the specified value is different from null and undefined.</summary> |
| /// <param name="value" mayBeNull="true" optional="true">Value to check.</param> |
| /// <returns type="Boolean">true if the value is assigned; false otherwise.</returns> |
| return value !== null && value !== undefined; |
| }; |
| |
| var contains = function(arr, item) { |
| /// <summary>Checks whether the specified item is in the array.</summary> |
| /// <param name="arr" type="Array" optional="false" mayBeNull="false">Array to check in.</param> |
| /// <param name="item">Item to look for.</param> |
| /// <returns type="Boolean">true if the item is contained, false otherwise.</returns> |
| |
| var i, len; |
| for (i = 0, len = arr.length; i < len; i++) { |
| if (arr[i] === item) { |
| return true; |
| } |
| } |
| |
| return false; |
| }; |
| |
| var defined = function(a, b) { |
| /// <summary>Given two values, picks the first one that is not undefined.</summary> |
| /// <param name="a">First value.</param> |
| /// <param name="b">Second value.</param> |
| /// <returns>a if it's a defined value; else b.</returns> |
| return (a !== undefined) ? a : b; |
| }; |
| |
| var delay = function(callback) { |
| /// <summary>Delays the invocation of the specified function until execution unwinds.</summary> |
| /// <param name="callback" type="Function">Callback function.</param> |
| if (arguments.length === 1) { |
| window.setTimeout(callback, 0); |
| return; |
| } |
| |
| var args = Array.prototype.slice.call(arguments, 1); |
| window.setTimeout(function() { |
| callback.apply(this, args); |
| }, 0); |
| }; |
| |
| // DATAJS INTERNAL START |
| var djsassert = function(condition, message, data) { |
| /// <summary>Throws an exception in case that a condition evaluates to false.</summary> |
| /// <param name="condition" type="Boolean">Condition to evaluate.</param> |
| /// <param name="message" type="String">Message explaining the assertion.</param> |
| /// <param name="data" type="Object">Additional data to be included in the exception.</param> |
| |
| if (!condition) { |
| throw { |
| message: "Assert fired: " + message, |
| data: data |
| }; |
| }; |
| }; |
| // DATAJS INTERNAL END |
| |
| var extend = function(target, values) { |
| /// <summary>Extends the target with the specified values.</summary> |
| /// <param name="target" type="Object">Object to add properties to.</param> |
| /// <param name="values" type="Object">Object with properties to add into target.</param> |
| /// <returns type="Object">The target object.</returns> |
| |
| for (var name in values) { |
| target[name] = values[name]; |
| } |
| |
| return target; |
| }; |
| |
| var find = function(arr, callback) { |
| /// <summary>Returns the first item in the array that makes the callback function true.</summary> |
| /// <param name="arr" type="Array" optional="false" mayBeNull="true">Array to check in.</param> |
| /// <param name="callback" type="Function">Callback function to invoke once per item in the array.</param> |
| /// <returns>The first item that makes the callback return true; null otherwise or if the array is null.</returns> |
| |
| if (arr) { |
| var i, len; |
| for (i = 0, len = arr.length; i < len; i++) { |
| if (callback(arr[i])) { |
| return arr[i]; |
| } |
| } |
| } |
| return null; |
| }; |
| |
| var isArray = function(value) { |
| /// <summary>Checks whether the specified value is an array object.</summary> |
| /// <param name="value">Value to check.</param> |
| /// <returns type="Boolean">true if the value is an array object; false otherwise.</returns> |
| |
| return Object.prototype.toString.call(value) === "[object Array]"; |
| }; |
| |
| var isDate = function(value) { |
| /// <summary>Checks whether the specified value is a Date object.</summary> |
| /// <param name="value">Value to check.</param> |
| /// <returns type="Boolean">true if the value is a Date object; false otherwise.</returns> |
| |
| return Object.prototype.toString.call(value) === "[object Date]"; |
| }; |
| |
| var isObject = function(value) { |
| /// <summary>Tests whether a value is an object.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <remarks> |
| /// Per javascript rules, null and array values are objects and will cause this function to return true. |
| /// </remarks> |
| /// <returns type="Boolean">True is the value is an object; false otherwise.</returns> |
| |
| return typeof value === "object"; |
| }; |
| |
| var parseInt10 = function(value) { |
| /// <summary>Parses a value in base 10.</summary> |
| /// <param name="value" type="String">String value to parse.</param> |
| /// <returns type="Number">The parsed value, NaN if not a valid value.</returns> |
| |
| return parseInt(value, 10); |
| }; |
| |
| var renameProperty = function(obj, oldName, newName) { |
| /// <summary>Renames a property in an object.</summary> |
| /// <param name="obj" type="Object">Object in which the property will be renamed.</param> |
| /// <param name="oldName" type="String">Name of the property that will be renamed.</param> |
| /// <param name="newName" type="String">New name of the property.</param> |
| /// <remarks> |
| /// This function will not do anything if the object doesn't own a property with the specified old name. |
| /// </remarks> |
| |
| if (obj.hasOwnProperty(oldName)) { |
| obj[newName] = obj[oldName]; |
| delete obj[oldName]; |
| } |
| }; |
| |
| var throwErrorCallback = function(error) { |
| /// <summary>Default error handler.</summary> |
| /// <param name="error" type="Object">Error to handle.</param> |
| throw error; |
| }; |
| |
| var trimString = function(str) { |
| /// <summary>Removes leading and trailing whitespaces from a string.</summary> |
| /// <param name="str" type="String" optional="false" mayBeNull="false">String to trim</param> |
| /// <returns type="String">The string with no leading or trailing whitespace.</returns> |
| |
| if (str.trim) { |
| return str.trim(); |
| } |
| |
| return str.replace(/^\s+|\s+$/g, ''); |
| }; |
| |
| var undefinedDefault = function(value, defaultValue) { |
| /// <summary>Returns a default value in place of undefined.</summary> |
| /// <param name="value" mayBeNull="true" optional="true">Value to check.</param> |
| /// <param name="defaultValue">Value to return if value is undefined.</param> |
| /// <returns>value if it's defined; defaultValue otherwise.</returns> |
| /// <remarks> |
| /// This should only be used for cases where falsy values are valid; |
| /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'. |
| /// </remarks> |
| return (value !== undefined) ? value : defaultValue; |
| }; |
| |
| // Regular expression that splits a uri into its components: |
| // 0 - is the matched string. |
| // 1 - is the scheme. |
| // 2 - is the authority. |
| // 3 - is the path. |
| // 4 - is the query. |
| // 5 - is the fragment. |
| var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/; |
| var uriPartNames = ["scheme", "authority", "path", "query", "fragment"]; |
| |
| var getURIInfo = function(uri) { |
| /// <summary>Gets information about the components of the specified URI.</summary> |
| /// <param name="uri" type="String">URI to get information from.</param> |
| /// <returns type="Object"> |
| /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available. |
| /// </returns> |
| |
| var result = { |
| isAbsolute: false |
| }; |
| |
| if (uri) { |
| var matches = uriRegEx.exec(uri); |
| if (matches) { |
| var i, len; |
| for (i = 0, len = uriPartNames.length; i < len; i++) { |
| if (matches[i + 1]) { |
| result[uriPartNames[i]] = matches[i + 1]; |
| } |
| } |
| } |
| if (result.scheme) { |
| result.isAbsolute = true; |
| } |
| } |
| |
| return result; |
| }; |
| |
| var getURIFromInfo = function(uriInfo) { |
| /// <summary>Builds a URI string from its components.</summary> |
| /// <param name="uriInfo" type="Object"> An object with uri parts (scheme, authority, etc.).</param> |
| /// <returns type="String">URI string.</returns> |
| |
| return "".concat( |
| uriInfo.scheme || "", |
| uriInfo.authority || "", |
| uriInfo.path || "", |
| uriInfo.query || "", |
| uriInfo.fragment || ""); |
| }; |
| |
| // Regular expression that splits a uri authority into its subcomponents: |
| // 0 - is the matched string. |
| // 1 - is the userinfo subcomponent. |
| // 2 - is the host subcomponent. |
| // 3 - is the port component. |
| var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/; |
| |
| // Regular expression that matches percentage enconded octects (i.e %20 or %3A); |
| var pctEncodingRegEx = /%[0-9A-F]{2}/ig; |
| |
| var normalizeURICase = function(uri) { |
| /// <summary>Normalizes the casing of a URI.</summary> |
| /// <param name="uri" type="String">URI to normalize, absolute or relative.</param> |
| /// <returns type="String">The URI normalized to lower case.</returns> |
| |
| var uriInfo = getURIInfo(uri); |
| var scheme = uriInfo.scheme; |
| var authority = uriInfo.authority; |
| |
| if (scheme) { |
| uriInfo.scheme = scheme.toLowerCase(); |
| if (authority) { |
| var matches = uriAuthorityRegEx.exec(authority); |
| if (matches) { |
| uriInfo.authority = "//" + |
| (matches[1] ? matches[1] + "@" : "") + |
| (matches[2].toLowerCase()) + |
| (matches[3] ? ":" + matches[3] : ""); |
| } |
| } |
| } |
| |
| uri = getURIFromInfo(uriInfo); |
| |
| return uri.replace(pctEncodingRegEx, function(str) { |
| return str.toLowerCase(); |
| }); |
| }; |
| |
| var normalizeURI = function(uri, base) { |
| /// <summary>Normalizes a possibly relative URI with a base URI.</summary> |
| /// <param name="uri" type="String">URI to normalize, absolute or relative.</param> |
| /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param> |
| /// <returns type="String">The composed URI if relative; the original one if absolute.</returns> |
| |
| if (!base) { |
| return uri; |
| } |
| |
| var uriInfo = getURIInfo(uri); |
| if (uriInfo.isAbsolute) { |
| return uri; |
| } |
| |
| var baseInfo = getURIInfo(base); |
| var normInfo = {}; |
| var path; |
| |
| if (uriInfo.authority) { |
| normInfo.authority = uriInfo.authority; |
| path = uriInfo.path; |
| normInfo.query = uriInfo.query; |
| } else { |
| if (!uriInfo.path) { |
| path = baseInfo.path; |
| normInfo.query = uriInfo.query || baseInfo.query; |
| } else { |
| if (uriInfo.path.charAt(0) === '/') { |
| path = uriInfo.path; |
| } else { |
| path = mergeUriPathWithBase(uriInfo.path, baseInfo.path); |
| } |
| normInfo.query = uriInfo.query; |
| } |
| normInfo.authority = baseInfo.authority; |
| } |
| |
| normInfo.path = removeDotsFromPath(path); |
| |
| normInfo.scheme = baseInfo.scheme; |
| normInfo.fragment = uriInfo.fragment; |
| |
| return getURIFromInfo(normInfo); |
| }; |
| |
| var mergeUriPathWithBase = function(uriPath, basePath) { |
| /// <summary>Merges the path of a relative URI and a base URI.</summary> |
| /// <param name="uriPath" type="String>Relative URI path.</param> |
| /// <param name="basePath" type="String">Base URI path.</param> |
| /// <returns type="String">A string with the merged path.</returns> |
| |
| var path = "/"; |
| var end; |
| |
| if (basePath) { |
| end = basePath.lastIndexOf("/"); |
| path = basePath.substring(0, end); |
| |
| if (path.charAt(path.length - 1) !== "/") { |
| path = path + "/"; |
| } |
| } |
| |
| return path + uriPath; |
| }; |
| |
| var removeDotsFromPath = function(path) { |
| /// <summary>Removes the special folders . and .. from a URI's path.</summary> |
| /// <param name="path" type="string">URI path component.</param> |
| /// <returns type="String">Path without any . and .. folders.</returns> |
| |
| var result = ""; |
| var segment = ""; |
| var end; |
| |
| while (path) { |
| if (path.indexOf("..") === 0 || path.indexOf(".") === 0) { |
| path = path.replace(/^\.\.?\/?/g, ""); |
| } else if (path.indexOf("/..") === 0) { |
| path = path.replace(/^\/\..\/?/g, "/"); |
| end = result.lastIndexOf("/"); |
| if (end === -1) { |
| result = ""; |
| } else { |
| result = result.substring(0, end); |
| } |
| } else if (path.indexOf("/.") === 0) { |
| path = path.replace(/^\/\.\/?/g, "/"); |
| } else { |
| segment = path; |
| end = path.indexOf("/", 1); |
| if (end !== -1) { |
| segment = path.substring(0, end); |
| } |
| result = result + segment; |
| path = path.replace(segment, ""); |
| } |
| } |
| return result; |
| }; |
| |
| var convertByteArrayToHexString = function(str) { |
| var arr = []; |
| if (window.atob === undefined) { |
| arr = decodeBase64(str); |
| } else { |
| var binaryStr = window.atob(str); |
| for (var i = 0; i < binaryStr.length; i++) { |
| arr.push(binaryStr.charCodeAt(i)); |
| } |
| } |
| var hexValue = ""; |
| var hexValues = "0123456789ABCDEF"; |
| for (var j = 0; j < arr.length; j++) { |
| var t = arr[j]; |
| hexValue += hexValues[t >> 4]; |
| hexValue += hexValues[t & 0x0F]; |
| } |
| return hexValue; |
| }; |
| |
| var decodeBase64 = function(str) { |
| var binaryString = ""; |
| for (var i = 0; i < str.length; i++) { |
| var base65IndexValue = getBase64IndexValue(str[i]); |
| var binaryValue = ""; |
| if (base65IndexValue !== null) { |
| binaryValue = base65IndexValue.toString(2); |
| binaryString += addBase64Padding(binaryValue); |
| } |
| } |
| var byteArray = []; |
| var numberOfBytes = parseInt(binaryString.length / 8, 10); |
| for (i = 0; i < numberOfBytes; i++) { |
| var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2); |
| byteArray.push(intValue); |
| } |
| return byteArray; |
| }; |
| |
| var getBase64IndexValue = function(character) { |
| var asciiCode = character.charCodeAt(0); |
| var asciiOfA = 65; |
| var differenceBetweenZanda = 6; |
| if (asciiCode >= 65 && asciiCode <= 90) { // between "A" and "Z" inclusive |
| return asciiCode - asciiOfA; |
| } else if (asciiCode >= 97 && asciiCode <= 122) { // between 'a' and 'z' inclusive |
| return asciiCode - asciiOfA - differenceBetweenZanda; |
| } else if (asciiCode >= 48 && asciiCode <= 57) { // between '0' and '9' inclusive |
| return asciiCode + 4; |
| } else if (character == "+") { |
| return 62; |
| } else if (character == "/") { |
| return 63; |
| } else { |
| return null; |
| } |
| }; |
| |
| var addBase64Padding = function(binaryString) { |
| while (binaryString.length < 6) { |
| binaryString = "0" + binaryString; |
| } |
| return binaryString; |
| |
| }; |
| |
| var getJsonValueArraryLength = function(data) { |
| if (data && data.value) { |
| return data.value.length; |
| } |
| |
| return 0; |
| }; |
| |
| var sliceJsonValueArray = function(data, start, end) { |
| if (data == undefined || data.value == undefined) { |
| return data; |
| } |
| |
| if (start < 0) { |
| start = 0; |
| } |
| |
| var length = getJsonValueArraryLength(data); |
| if (length < end) { |
| end = length; |
| } |
| |
| var newdata = {}; |
| for (var property in data) { |
| if (property == "value") { |
| newdata[property] = data[property].slice(start, end); |
| } else { |
| newdata[property] = data[property]; |
| } |
| } |
| |
| return newdata; |
| }; |
| |
| var concatJsonValueArray = function(data, concatData) { |
| if (concatData == undefined || concatData.value == undefined) { |
| return data; |
| } |
| |
| if (data == undefined || Object.keys(data).length == 0) { |
| return concatData; |
| } |
| |
| if (data.value == undefined) { |
| data.value = concatData.value; |
| return data; |
| } |
| |
| data.value = data.value.concat(concatData.value); |
| |
| return data; |
| }; |
| |
| var endsWith = function(input, search) { |
| return input.indexOf(search, input.length - search.length) !== -1; |
| }; |
| |
| var startsWith = function(input, search) { |
| return input.indexOf(search) == 0; |
| }; |
| |
| var getFormatKind = function(format, defaultFormatKind) { |
| var formatKind = defaultFormatKind; |
| if (!assigned(format)) { |
| return formatKind; |
| } |
| |
| var normalizedFormat = format.toLowerCase(); |
| switch (normalizedFormat) { |
| case "none": |
| formatKind = 0; |
| break; |
| case "minimal": |
| formatKind = 1; |
| break; |
| case "full": |
| formatKind = 2; |
| break; |
| default: |
| break; |
| } |
| |
| return formatKind; |
| }; |
| |
| exports.activeXObject = activeXObject; |
| exports.assigned = assigned; |
| exports.contains = contains; |
| exports.defined = defined; |
| exports.delay = delay; |
| exports.djsassert = djsassert; |
| exports.extend = extend; |
| exports.find = find; |
| exports.getURIInfo = getURIInfo; |
| exports.isArray = isArray; |
| exports.isDate = isDate; |
| exports.isObject = isObject; |
| exports.normalizeURI = normalizeURI; |
| exports.normalizeURICase = normalizeURICase; |
| exports.parseInt10 = parseInt10; |
| exports.renameProperty = renameProperty; |
| exports.throwErrorCallback = throwErrorCallback; |
| exports.trimString = trimString; |
| exports.undefinedDefault = undefinedDefault; |
| exports.decodeBase64 = decodeBase64; |
| exports.convertByteArrayToHexString = convertByteArrayToHexString; |
| exports.getJsonValueArraryLength = getJsonValueArraryLength; |
| exports.sliceJsonValueArray = sliceJsonValueArray; |
| exports.concatJsonValueArray = concatJsonValueArray; |
| exports.startsWith = startsWith; |
| exports.endsWith = endsWith; |
| exports.getFormatKind = getFormatKind; |
| |
| }, {} |
| ], |
| 7: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var utils = require('./utils.js'); |
| |
| var activeXObject = utils.activeXObject; |
| var djsassert = utils.djsassert; |
| var extend = utils.extend; |
| var isArray = utils.isArray; |
| var normalizeURI = utils.normalizeURI; |
| |
| // URI prefixes to generate smaller code. |
| var http = "http://"; |
| var w3org = http + "www.w3.org/"; // http://www.w3.org/ |
| |
| var xhtmlNS = w3org + "1999/xhtml"; // http://www.w3.org/1999/xhtml |
| var xmlnsNS = w3org + "2000/xmlns/"; // http://www.w3.org/2000/xmlns/ |
| var xmlNS = w3org + "XML/1998/namespace"; // http://www.w3.org/XML/1998/namespace |
| |
| var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml"; |
| |
| var hasLeadingOrTrailingWhitespace = function(text) { |
| /// <summary>Checks whether the specified string has leading or trailing spaces.</summary> |
| /// <param name="text" type="String">String to check.</param> |
| /// <returns type="Boolean">true if text has any leading or trailing whitespace; false otherwise.</returns> |
| |
| var re = /(^\s)|(\s$)/; |
| return re.test(text); |
| }; |
| |
| var isWhitespace = function(text) { |
| /// <summary>Determines whether the specified text is empty or whitespace.</summary> |
| /// <param name="text" type="String">Value to inspect.</param> |
| /// <returns type="Boolean">true if the text value is empty or all whitespace; false otherwise.</returns> |
| |
| var ws = /^\s*$/; |
| return text === null || ws.test(text); |
| }; |
| |
| var isWhitespacePreserveContext = function(domElement) { |
| /// <summary>Determines whether the specified element has xml:space='preserve' applied.</summary> |
| /// <param name="domElement">Element to inspect.</param> |
| /// <returns type="Boolean">Whether xml:space='preserve' is in effect.</returns> |
| |
| while (domElement !== null && domElement.nodeType === 1) { |
| var val = xmlAttributeValue(domElement, "space", xmlNS); |
| if (val === "preserve") { |
| return true; |
| } else if (val === "default") { |
| break; |
| } else { |
| domElement = domElement.parentNode; |
| } |
| } |
| |
| return false; |
| }; |
| |
| var isXmlNSDeclaration = function(domAttribute) { |
| /// <summary>Determines whether the attribute is a XML namespace declaration.</summary> |
| /// <param name="domAttribute">Element to inspect.</param> |
| /// <returns type="Boolean"> |
| /// True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise. |
| /// </returns> |
| |
| var nodeName = domAttribute.nodeName; |
| return nodeName == "xmlns" || nodeName.indexOf("xmlns:") === 0; |
| }; |
| |
| var safeSetProperty = function(obj, name, value) { |
| /// <summary>Safely set as property in an object by invoking obj.setProperty.</summary> |
| /// <param name="obj">Object that exposes a setProperty method.</param> |
| /// <param name="name" type="String" mayBeNull="false">Property name.</param> |
| /// <param name="value">Property value.</param> |
| |
| try { |
| obj.setProperty(name, value); |
| } catch (_) {} |
| }; |
| |
| var msXmlDom3 = function() { |
| /// <summary>Creates an configures new MSXML 3.0 ActiveX object.</summary> |
| /// <remakrs> |
| /// This function throws any exception that occurs during the creation |
| /// of the MSXML 3.0 ActiveX object. |
| /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns> |
| |
| var msxml3 = activeXObject("Msxml2.DOMDocument.3.0"); |
| if (msxml3) { |
| safeSetProperty(msxml3, "ProhibitDTD", true); |
| safeSetProperty(msxml3, "MaxElementDepth", 256); |
| safeSetProperty(msxml3, "AllowDocumentFunction", false); |
| safeSetProperty(msxml3, "AllowXsltScript", false); |
| } |
| return msxml3; |
| }; |
| |
| var msXmlDom = function() { |
| /// <summary>Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object.</summary> |
| /// <remakrs> |
| /// This function will try to create a new MSXML 6.0 ActiveX object. If it fails then |
| /// it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that |
| /// happens during the creation of the MSXML 6.0 will be handled by the function while |
| /// the ones that happend during the creation of the MSXML 3.0 will be thrown. |
| /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns> |
| |
| try { |
| var msxml = activeXObject("Msxml2.DOMDocument.6.0"); |
| if (msxml) { |
| msxml.async = true; |
| } |
| return msxml; |
| } catch (_) { |
| return msXmlDom3(); |
| } |
| }; |
| |
| var msXmlParse = function(text) { |
| /// <summary>Parses an XML string using the MSXML DOM.</summary> |
| /// <remakrs> |
| /// This function throws any exception that occurs during the creation |
| /// of the MSXML ActiveX object. It also will throw an exception |
| /// in case of a parsing error. |
| /// <returns type="Object">New MSXML DOMDocument node representing the parsed XML string.</returns> |
| |
| var dom = msXmlDom(); |
| if (!dom) { |
| return null; |
| } |
| |
| dom.loadXML(text); |
| var parseError = dom.parseError; |
| if (parseError.errorCode !== 0) { |
| xmlThrowParserError(parseError.reason, parseError.srcText, text); |
| } |
| return dom; |
| }; |
| |
| var xmlThrowParserError = function(exceptionOrReason, srcText, errorXmlText) { |
| /// <summary>Throws a new exception containing XML parsing error information.</summary> |
| /// <param name="exceptionOrReason"> |
| /// String indicatin the reason of the parsing failure or |
| /// Object detailing the parsing error. |
| /// </param> |
| /// <param name="srcText" type="String"> |
| /// String indicating the part of the XML string that caused the parsing error. |
| /// </param> |
| /// <param name="errorXmlText" type="String">XML string for wich the parsing failed.</param> |
| |
| if (typeof exceptionOrReason === "string") { |
| exceptionOrReason = { |
| message: exceptionOrReason |
| }; |
| } |
| throw extend(exceptionOrReason, { |
| srcText: srcText || "", |
| errorXmlText: errorXmlText || "" |
| }); |
| }; |
| |
| var xmlParse = function(text) { |
| /// <summary>Returns an XML DOM document from the specified text.</summary> |
| /// <param name="text" type="String">Document text.</param> |
| /// <returns>XML DOM document.</returns> |
| /// <remarks>This function will throw an exception in case of a parse error.</remarks> |
| |
| var domParser = window.DOMParser && new window.DOMParser(); |
| var dom; |
| |
| if (!domParser) { |
| dom = msXmlParse(text); |
| if (!dom) { |
| xmlThrowParserError("XML DOM parser not supported"); |
| } |
| return dom; |
| } |
| |
| try { |
| dom = domParser.parseFromString(text, "text/xml"); |
| } catch (e) { |
| xmlThrowParserError(e, "", text); |
| } |
| |
| var element = dom.documentElement; |
| var nsURI = element.namespaceURI; |
| var localName = xmlLocalName(element); |
| |
| // Firefox reports errors by returing the DOM for an xml document describing the problem. |
| if (localName === "parsererror" && nsURI === mozillaParserErroNS) { |
| var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext"); |
| var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : ""; |
| xmlThrowParserError(xmlInnerText(element) || "", srcText, text); |
| } |
| |
| // Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message. |
| // The error may be localized, so instead we simply check for a header as the |
| // top element or descendant child of the document. |
| if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) { |
| var reason = ""; |
| var siblings = []; |
| var cursor = element.firstChild; |
| while (cursor) { |
| if (cursor.nodeType === 1) { |
| reason += xmlInnerText(cursor) || ""; |
| } |
| siblings.push(cursor.nextSibling); |
| cursor = cursor.firstChild || siblings.shift(); |
| } |
| reason += xmlInnerText(element) || ""; |
| xmlThrowParserError(reason, "", text); |
| } |
| |
| return dom; |
| }; |
| |
| var xmlQualifiedName = function(prefix, name) { |
| /// <summary>Builds a XML qualified name string in the form of "prefix:name".</summary> |
| /// <param name="prefix" type="String" maybeNull="true">Prefix string.</param> |
| /// <param name="name" type="String">Name string to qualify with the prefix.</param> |
| /// <returns type="String">Qualified name.</returns> |
| |
| return prefix ? prefix + ":" + name : name; |
| }; |
| |
| var xmlAppendText = function(domNode, textNode) { |
| /// <summary>Appends a text node into the specified DOM element node.</summary> |
| /// <param name="domNode">DOM node for the element.</param> |
| /// <param name="text" type="String" mayBeNull="false">Text to append as a child of element.</param> |
| if (hasLeadingOrTrailingWhitespace(textNode.data)) { |
| var attr = xmlAttributeNode(domNode, xmlNS, "space"); |
| if (!attr) { |
| attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space")); |
| xmlAppendChild(domNode, attr); |
| } |
| attr.value = "preserve"; |
| } |
| domNode.appendChild(textNode); |
| return domNode; |
| }; |
| |
| var xmlAttributes = function(element, onAttributeCallback) { |
| /// <summary>Iterates through the XML element's attributes and invokes the callback function for each one.</summary> |
| /// <param name="element">Wrapped element to iterate over.</param> |
| /// <param name="onAttributeCallback" type="Function">Callback function to invoke with wrapped attribute nodes.</param> |
| |
| var attributes = element.attributes; |
| var i, len; |
| for (i = 0, len = attributes.length; i < len; i++) { |
| onAttributeCallback(attributes.item(i)); |
| } |
| }; |
| |
| var xmlAttributeValue = function(domNode, localName, nsURI) { |
| /// <summary>Returns the value of a DOM element's attribute.</summary> |
| /// <param name="domNode">DOM node for the owning element.</param> |
| /// <param name="localName" type="String">Local name of the attribute.</param> |
| /// <param name="nsURI" type="String">Namespace URI of the attribute.</param> |
| /// <returns type="String" maybeNull="true">The attribute value, null if not found.</returns> |
| |
| var attribute = xmlAttributeNode(domNode, localName, nsURI); |
| return attribute ? xmlNodeValue(attribute) : null; |
| }; |
| |
| var xmlAttributeNode = function(domNode, localName, nsURI) { |
| /// <summary>Gets an attribute node from a DOM element.</summary> |
| /// <param name="domNode">DOM node for the owning element.</param> |
| /// <param name="localName" type="String">Local name of the attribute.</param> |
| /// <param name="nsURI" type="String">Namespace URI of the attribute.</param> |
| /// <returns>The attribute node, null if not found.</returns> |
| |
| var attributes = domNode.attributes; |
| if (attributes.getNamedItemNS) { |
| return attributes.getNamedItemNS(nsURI || null, localName); |
| } |
| |
| return attributes.getQualifiedItem(localName, nsURI) || null; |
| }; |
| |
| var xmlBaseURI = function(domNode, baseURI) { |
| /// <summary>Gets the value of the xml:base attribute on the specified element.</summary> |
| /// <param name="domNode">Element to get xml:base attribute value from.</param> |
| /// <param name="baseURI" mayBeNull="true" optional="true">Base URI used to normalize the value of the xml:base attribute.</param> |
| /// <returns type="String">Value of the xml:base attribute if found; the baseURI or null otherwise.</returns> |
| |
| var base = xmlAttributeNode(domNode, "base", xmlNS); |
| return (base ? normalizeURI(base.value, baseURI) : baseURI) || null; |
| }; |
| |
| |
| var xmlChildElements = function(domNode, onElementCallback) { |
| /// <summary>Iterates through the XML element's child DOM elements and invokes the callback function for each one.</summary> |
| /// <param name="element">DOM Node containing the DOM elements to iterate over.</param> |
| /// <param name="onElementCallback" type="Function">Callback function to invoke for each child DOM element.</param> |
| |
| xmlTraverse(domNode, /*recursive*/ false, function(child) { |
| if (child.nodeType === 1) { |
| onElementCallback(child); |
| } |
| // continue traversing. |
| return true; |
| }); |
| }; |
| |
| var xmlFindElementByPath = function(root, namespaceURI, path) { |
| /// <summary>Gets the descendant element under root that corresponds to the specified path and namespace URI.</summary> |
| /// <param name="root">DOM element node from which to get the descendant element.</param> |
| /// <param name="namespaceURI" type="String">The namespace URI of the element to match.</param> |
| /// <param name="path" type="String">Path to the desired descendant element.</param> |
| /// <returns>The element specified by path and namespace URI.</returns> |
| /// <remarks> |
| /// All the elements in the path are matched against namespaceURI. |
| /// The function will stop searching on the first element that doesn't match the namespace and the path. |
| /// </remarks> |
| |
| var parts = path.split("/"); |
| var i, len; |
| for (i = 0, len = parts.length; i < len; i++) { |
| root = root && xmlFirstChildElement(root, namespaceURI, parts[i]); |
| } |
| return root || null; |
| }; |
| |
| var xmlFindNodeByPath = function(root, namespaceURI, path) { |
| /// <summary>Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI.</summary> |
| /// <param name="root">DOM element node from which to get the descendant node.</param> |
| /// <param name="namespaceURI" type="String">The namespace URI of the node to match.</param> |
| /// <param name="path" type="String">Path to the desired descendant node.</param> |
| /// <returns>The node specified by path and namespace URI.</returns> |
| /// <remarks> |
| /// This function will traverse the path and match each node associated to a path segement against the namespace URI. |
| /// The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered. |
| /// |
| /// The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute. |
| /// </remarks> |
| |
| var lastSegmentStart = path.lastIndexOf("/"); |
| var nodePath = path.substring(lastSegmentStart + 1); |
| var parentPath = path.substring(0, lastSegmentStart); |
| |
| var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root; |
| if (node) { |
| if (nodePath.charAt(0) === "@") { |
| return xmlAttributeNode(node, nodePath.substring(1), namespaceURI); |
| } |
| return xmlFirstChildElement(node, namespaceURI, nodePath); |
| } |
| return null; |
| }; |
| |
| var xmlFirstChildElement = function(domNode, namespaceURI, localName) { |
| /// <summary>Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary> |
| /// <param name="domNode">DOM node from which the child DOM element is going to be retrieved.</param> |
| /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> |
| /// <param name="localName" type="String" optional="true">Name of the element to match.</param> |
| /// <returns>The node's first child DOM element that matches the specified namespace URI and local name; null otherwise.</returns> |
| |
| return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/ false); |
| }; |
| |
| var xmlFirstDescendantElement = function(domNode, namespaceURI, localName) { |
| /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary> |
| /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param> |
| /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> |
| /// <param name="localName" type="String" optional="true">Name of the element to match.</param> |
| /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns> |
| |
| if (domNode.getElementsByTagNameNS) { |
| var result = domNode.getElementsByTagNameNS(namespaceURI, localName); |
| return result.length > 0 ? result[0] : null; |
| } |
| return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/ true); |
| }; |
| |
| var xmlFirstElementMaybeRecursive = function(domNode, namespaceURI, localName, recursive) { |
| /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary> |
| /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param> |
| /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> |
| /// <param name="localName" type="String" optional="true">Name of the element to match.</param> |
| /// <param name="recursive" type="Boolean"> |
| /// True if the search should include all the descendants of the DOM node. |
| /// False if the search should be scoped only to the direct children of the DOM node. |
| /// </param> |
| /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns> |
| |
| var firstElement = null; |
| xmlTraverse(domNode, recursive, function(child) { |
| if (child.nodeType === 1) { |
| var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI; |
| var isExpectedNodeName = !localName || xmlLocalName(child) === localName; |
| |
| if (isExpectedNamespace && isExpectedNodeName) { |
| firstElement = child; |
| } |
| } |
| return firstElement === null; |
| }); |
| return firstElement; |
| }; |
| |
| var xmlInnerText = function(xmlElement) { |
| /// <summary>Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.</summary> |
| /// <param name="domElement">Element to get values for.</param> |
| /// <returns type="String">Text for all direct children.</returns> |
| |
| var result = null; |
| var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement; |
| var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false; |
| var whitespacePreserveContext; |
| |
| xmlTraverse(root, false, function(child) { |
| if (child.nodeType === 3 || child.nodeType === 4) { |
| // isElementContentWhitespace indicates that this is 'ignorable whitespace', |
| // but it's not defined by all browsers, and does not honor xml:space='preserve' |
| // in some implementations. |
| // |
| // If we can't tell either way, we walk up the tree to figure out whether |
| // xml:space is set to preserve; otherwise we discard pure-whitespace. |
| // |
| // For example <a> <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'. |
| var text = xmlNodeValue(child); |
| var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text); |
| if (!shouldInclude) { |
| // Walk up the tree to figure out whether we are in xml:space='preserve' context |
| // for the cursor (needs to happen only once). |
| if (whitespacePreserveContext === undefined) { |
| whitespacePreserveContext = isWhitespacePreserveContext(root); |
| } |
| |
| shouldInclude = whitespacePreserveContext; |
| } |
| |
| if (shouldInclude) { |
| if (!result) { |
| result = text; |
| } else { |
| result += text; |
| } |
| } |
| } |
| // Continue traversing? |
| return true; |
| }); |
| return result; |
| }; |
| |
| var xmlLocalName = function(domNode) { |
| /// <summary>Returns the localName of a XML node.</summary> |
| /// <param name="domNode">DOM node to get the value from.</param> |
| /// <returns type="String">localName of domNode.</returns> |
| |
| return domNode.localName || domNode.baseName; |
| }; |
| |
| var xmlNamespaceURI = function(domNode) { |
| /// <summary>Returns the namespace URI of a XML node.</summary> |
| /// <param name="node">DOM node to get the value from.</param> |
| /// <returns type="String">Namespace URI of domNode.</returns> |
| |
| return domNode.namespaceURI || null; |
| }; |
| |
| var xmlNodeValue = function(domNode) { |
| /// <summary>Returns the value or the inner text of a XML node.</summary> |
| /// <param name="node">DOM node to get the value from.</param> |
| /// <returns>Value of the domNode or the inner text if domNode represents a DOM element node.</returns> |
| |
| if (domNode.nodeType === 1) { |
| return xmlInnerText(domNode); |
| } |
| return domNode.nodeValue; |
| }; |
| |
| var xmlTraverse = function(domNode, recursive, onChildCallback) { |
| /// <summary>Walks through the descendants of the domNode and invokes a callback for each node.</summary> |
| /// <param name="domNode">DOM node whose descendants are going to be traversed.</param> |
| /// <param name="recursive" type="Boolean"> |
| /// True if the traversal should include all the descenants of the DOM node. |
| /// False if the traversal should be scoped only to the direct children of the DOM node. |
| /// </param> |
| /// <returns type="String">Namespace URI of node.</returns> |
| |
| var subtrees = []; |
| var child = domNode.firstChild; |
| var proceed = true; |
| while (child && proceed) { |
| proceed = onChildCallback(child); |
| if (proceed) { |
| if (recursive && child.firstChild) { |
| subtrees.push(child.firstChild); |
| } |
| child = child.nextSibling || subtrees.shift(); |
| } |
| } |
| }; |
| |
| var xmlSiblingElement = function(domNode, namespaceURI, localName) { |
| /// <summary>Returns the next sibling DOM element of the specified DOM node.</summary> |
| /// <param name="domNode">DOM node from which the next sibling is going to be retrieved.</param> |
| /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> |
| /// <param name="localName" type="String" optional="true">Name of the element to match.</param> |
| /// <returns>The node's next sibling DOM element, null if there is none.</returns> |
| |
| var sibling = domNode.nextSibling; |
| while (sibling) { |
| if (sibling.nodeType === 1) { |
| var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI; |
| var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName; |
| |
| if (isExpectedNamespace && isExpectedNodeName) { |
| return sibling; |
| } |
| } |
| sibling = sibling.nextSibling; |
| } |
| return null; |
| }; |
| |
| var xmlDom = function() { |
| /// <summary>Creates a new empty DOM document node.</summary> |
| /// <returns>New DOM document node.</returns> |
| /// <remarks> |
| /// This function will first try to create a native DOM document using |
| /// the browsers createDocument function. If the browser doesn't |
| /// support this but supports ActiveXObject, then an attempt to create |
| /// an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt |
| /// for creating an MXSML 3.0 DOM will be made. If this last attemp fails or |
| /// the browser doesn't support ActiveXObject then an exception will be thrown. |
| /// </remarks> |
| |
| var implementation = window.document.implementation; |
| return (implementation && implementation.createDocument) ? |
| implementation.createDocument(null, null, null) : |
| msXmlDom(); |
| }; |
| |
| var xmlAppendChildren = function(parent, children) { |
| /// <summary>Appends a collection of child nodes or string values to a parent DOM node.</summary> |
| /// <param name="parent">DOM node to which the children will be appended.</param> |
| /// <param name="children" type="Array">Array containing DOM nodes or string values that will be appended to the parent.</param> |
| /// <returns>The parent with the appended children or string values.</returns> |
| /// <remarks> |
| /// If a value in the children collection is a string, then a new DOM text node is going to be created |
| /// for it and then appended to the parent. |
| /// </remarks> |
| |
| if (!isArray(children)) { |
| return xmlAppendChild(parent, children); |
| } |
| |
| var i, len; |
| for (i = 0, len = children.length; i < len; i++) { |
| children[i] && xmlAppendChild(parent, children[i]); |
| } |
| return parent; |
| }; |
| |
| var xmlAppendChild = function(parent, child) { |
| /// <summary>Appends a child node or a string value to a parent DOM node.</summary> |
| /// <param name="parent">DOM node to which the child will be appended.</param> |
| /// <param name="child">Child DOM node or string value to append to the parent.</param> |
| /// <returns>The parent with the appended child or string value.</returns> |
| /// <remarks> |
| /// If child is a string value, then a new DOM text node is going to be created |
| /// for it and then appended to the parent. |
| /// </remarks> |
| |
| djsassert(parent !== child, "xmlAppendChild() - parent and child are one and the same!"); |
| if (child) { |
| if (typeof child === "string") { |
| return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child)); |
| } |
| if (child.nodeType === 2) { |
| parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child); |
| } else { |
| parent.appendChild(child); |
| } |
| } |
| return parent; |
| }; |
| |
| var xmlNewAttribute = function(dom, namespaceURI, qualifiedName, value) { |
| /// <summary>Creates a new DOM attribute node.</summary> |
| /// <param name="dom">DOM document used to create the attribute.</param> |
| /// <param name="prefix" type="String">Namespace prefix.</param> |
| /// <param name="namespaceURI" type="String">Namespace URI.</param> |
| /// <returns>DOM attribute node for the namespace declaration.</returns> |
| |
| var attribute = |
| dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) || |
| dom.createNode(2, qualifiedName, namespaceURI || undefined); |
| |
| attribute.value = value || ""; |
| return attribute; |
| }; |
| |
| var xmlNewElement = function(dom, nampespaceURI, qualifiedName, children) { |
| /// <summary>Creates a new DOM element node.</summary> |
| /// <param name="dom">DOM document used to create the DOM element.</param> |
| /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element.</param> |
| /// <param name="qualifiedName" type="String">Qualified name in the form of "prefix:name" of the new DOM element.</param> |
| /// <param name="children" type="Array" optional="true"> |
| /// Collection of child DOM nodes or string values that are going to be appended to the new DOM element. |
| /// </param> |
| /// <returns>New DOM element.</returns> |
| /// <remarks> |
| /// If a value in the children collection is a string, then a new DOM text node is going to be created |
| /// for it and then appended to the new DOM element. |
| /// </remarks> |
| |
| var element = |
| dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) || |
| dom.createNode(1, qualifiedName, nampespaceURI || undefined); |
| |
| return xmlAppendChildren(element, children || []); |
| }; |
| |
| var xmlNewNSDeclaration = function(dom, namespaceURI, prefix) { |
| /// <summary>Creates a namespace declaration attribute.</summary> |
| /// <param name="dom">DOM document used to create the attribute.</param> |
| /// <param name="namespaceURI" type="String">Namespace URI.</param> |
| /// <param name="prefix" type="String">Namespace prefix.</param> |
| /// <returns>DOM attribute node for the namespace declaration.</returns> |
| |
| return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI); |
| }; |
| |
| var xmlNewFragment = function(dom, text) { |
| /// <summary>Creates a new DOM document fragment node for the specified xml text.</summary> |
| /// <param name="dom">DOM document from which the fragment node is going to be created.</param> |
| /// <param name="text" type="String" mayBeNull="false">XML text to be represented by the XmlFragment.</param> |
| /// <returns>New DOM document fragment object.</returns> |
| |
| var value = "<c>" + text + "</c>"; |
| var tempDom = xmlParse(value); |
| var tempRoot = tempDom.documentElement; |
| var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot; |
| var fragment = dom.createDocumentFragment(); |
| |
| var importedChild = imported.firstChild; |
| while (importedChild) { |
| fragment.appendChild(importedChild); |
| importedChild = importedChild.nextSibling; |
| } |
| return fragment; |
| }; |
| |
| var xmlNewText = function(dom, text) { |
| /// <summary>Creates new DOM text node.</summary> |
| /// <param name="dom">DOM document used to create the text node.</param> |
| /// <param name="text" type="String">Text value for the DOM text node.</param> |
| /// <returns>DOM text node.</returns> |
| |
| return dom.createTextNode(text); |
| }; |
| |
| var xmlNewNodeByPath = function(dom, root, namespaceURI, prefix, path) { |
| /// <summary>Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root.</summary> |
| /// <param name="dom">DOM document used to create the new node.</param> |
| /// <param name="root">DOM element node used as root of the subtree on which the new nodes are going to be created.</param> |
| /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element or attribute.</param> |
| /// <param name="namespacePrefix" type="String">Prefix used to qualify the name of the new DOM element or attribute.</param> |
| /// <param name="Path" type="String">Path string describing the location of the new DOM element or attribute from the root element.</param> |
| /// <returns>DOM element or attribute node for the last segment of the path.</returns> |
| /// <remarks> |
| /// This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix |
| /// for each segment that doesn't have a matching element under root. |
| /// |
| /// The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node |
| /// will be created. |
| /// </remarks> |
| |
| var name = ""; |
| var parts = path.split("/"); |
| var xmlFindNode = xmlFirstChildElement; |
| var xmlNewNode = xmlNewElement; |
| var xmlNode = root; |
| |
| var i, len; |
| for (i = 0, len = parts.length; i < len; i++) { |
| name = parts[i]; |
| if (name.charAt(0) === "@") { |
| name = name.substring(1); |
| xmlFindNode = xmlAttributeNode; |
| xmlNewNode = xmlNewAttribute; |
| } |
| |
| var childNode = xmlFindNode(xmlNode, namespaceURI, name); |
| if (!childNode) { |
| childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name)); |
| xmlAppendChild(xmlNode, childNode); |
| } |
| xmlNode = childNode; |
| } |
| return xmlNode; |
| }; |
| |
| var xmlSerialize = function(domNode) { |
| /// <summary> |
| /// Returns the text representation of the document to which the specified node belongs. |
| /// </summary> |
| /// <param name="root">Wrapped element in the document to serialize.</param> |
| /// <returns type="String">Serialized document.</returns> |
| |
| var xmlSerializer = window.XMLSerializer; |
| if (xmlSerializer) { |
| var serializer = new xmlSerializer(); |
| return serializer.serializeToString(domNode); |
| } |
| |
| if (domNode.xml) { |
| return domNode.xml; |
| } |
| |
| throw { |
| message: "XML serialization unsupported" |
| }; |
| }; |
| |
| var xmlSerializeDescendants = function(domNode) { |
| /// <summary>Returns the XML representation of the all the descendants of the node.</summary> |
| /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param> |
| /// <returns type="String">The XML representation of all the descendants of the node.</returns> |
| |
| var children = domNode.childNodes; |
| var i, len = children.length; |
| if (len === 0) { |
| return ""; |
| } |
| |
| // Some implementations of the XMLSerializer don't deal very well with fragments that |
| // don't have a DOMElement as their first child. The work around is to wrap all the |
| // nodes in a dummy root node named "c", serialize it and then just extract the text between |
| // the <c> and the </c> substrings. |
| |
| var dom = domNode.ownerDocument; |
| var fragment = dom.createDocumentFragment(); |
| var fragmentRoot = dom.createElement("c"); |
| |
| fragment.appendChild(fragmentRoot); |
| // Move the children to the fragment tree. |
| for (i = 0; i < len; i++) { |
| fragmentRoot.appendChild(children[i]); |
| } |
| |
| var xml = xmlSerialize(fragment); |
| xml = xml.substr(3, xml.length - 7); |
| |
| // Move the children back to the original dom tree. |
| for (i = 0; i < len; i++) { |
| domNode.appendChild(fragmentRoot.childNodes[i]); |
| } |
| |
| return xml; |
| }; |
| |
| var xmlSerializeNode = function(domNode) { |
| /// <summary>Returns the XML representation of the node and all its descendants.</summary> |
| /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param> |
| /// <returns type="String">The XML representation of the node and all its descendants.</returns> |
| |
| var xml = domNode.xml; |
| if (xml !== undefined) { |
| return xml; |
| } |
| |
| if (window.XMLSerializer) { |
| var serializer = new window.XMLSerializer(); |
| return serializer.serializeToString(domNode); |
| } |
| |
| throw { |
| message: "XML serialization unsupported" |
| }; |
| }; |
| |
| exports.http = http; |
| exports.w3org = w3org; |
| exports.xmlNS = xmlNS; |
| exports.xmlnsNS = xmlnsNS; |
| |
| exports.hasLeadingOrTrailingWhitespace = hasLeadingOrTrailingWhitespace; |
| exports.isXmlNSDeclaration = isXmlNSDeclaration; |
| exports.xmlAppendChild = xmlAppendChild; |
| exports.xmlAppendChildren = xmlAppendChildren; |
| exports.xmlAttributeNode = xmlAttributeNode; |
| exports.xmlAttributes = xmlAttributes; |
| exports.xmlAttributeValue = xmlAttributeValue; |
| exports.xmlBaseURI = xmlBaseURI; |
| exports.xmlChildElements = xmlChildElements; |
| exports.xmlFindElementByPath = xmlFindElementByPath; |
| exports.xmlFindNodeByPath = xmlFindNodeByPath; |
| exports.xmlFirstChildElement = xmlFirstChildElement; |
| exports.xmlFirstDescendantElement = xmlFirstDescendantElement; |
| exports.xmlInnerText = xmlInnerText; |
| exports.xmlLocalName = xmlLocalName; |
| exports.xmlNamespaceURI = xmlNamespaceURI; |
| exports.xmlNodeValue = xmlNodeValue; |
| exports.xmlDom = xmlDom; |
| exports.xmlNewAttribute = xmlNewAttribute; |
| exports.xmlNewElement = xmlNewElement; |
| exports.xmlNewFragment = xmlNewFragment; |
| exports.xmlNewNodeByPath = xmlNewNodeByPath; |
| exports.xmlNewNSDeclaration = xmlNewNSDeclaration; |
| exports.xmlNewText = xmlNewText; |
| exports.xmlParse = xmlParse; |
| exports.xmlQualifiedName = xmlQualifiedName; |
| exports.xmlSerialize = xmlSerialize; |
| exports.xmlSerializeDescendants = xmlSerializeDescendants; |
| exports.xmlSiblingElement = xmlSiblingElement; |
| |
| }, { |
| "./utils.js": 6 |
| } |
| ], |
| 8: [ |
| function(require, module, exports) { |
| |
| |
| // Imports |
| var odataUtils = exports.utils = require('./odata/utils.js'); |
| var odataHandler = exports.handler = require('./odata/handler.js'); |
| var odataMetadata = exports.metadata = require('./odata/metadata.js'); |
| var odataNet = exports.net = require('./odata/net.js'); |
| var odataJson = exports.json = require('./odata/json.js'); |
| exports.batch = require('./odata/batch.js'); |
| |
| exports.metadataHandler = odataMetadata.metadataHandler; |
| |
| var utils = require('./datajs/utils.js'); |
| var assigned = utils.assigned; |
| |
| var defined = utils.defined; |
| var throwErrorCallback = utils.throwErrorCallback; |
| |
| var invokeRequest = odataUtils.invokeRequest; |
| var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION; |
| var prepareRequest = odataUtils.prepareRequest; |
| var metadataParser = odataMetadata.metadataParser; |
| |
| // CONTENT START |
| |
| var handlers = [odataJson.jsonHandler, odataHandler.textHandler]; |
| |
| var dispatchHandler = function(handlerMethod, requestOrResponse, context) { |
| /// <summary>Dispatches an operation to handlers.</summary> |
| /// <param name="handlerMethod" type="String">Name of handler method to invoke.</param> |
| /// <param name="requestOrResponse" type="Object">request/response argument for delegated call.</param> |
| /// <param name="context" type="Object">context argument for delegated call.</param> |
| |
| var i, len; |
| for (i = 0, len = handlers.length; i < len && !handlers[i][handlerMethod](requestOrResponse, context); i++) {} |
| |
| if (i === len) { |
| throw { |
| message: "no handler for data" |
| }; |
| } |
| }; |
| |
| exports.defaultSuccess = function(data) { |
| /// <summary>Default success handler for OData.</summary> |
| /// <param name="data">Data to process.</param> |
| |
| window.alert(window.JSON.stringify(data)); |
| }; |
| |
| exports.defaultError = throwErrorCallback; |
| |
| exports.defaultHandler = { |
| read: function(response, context) { |
| /// <summary>Reads the body of the specified response by delegating to JSON handlers.</summary> |
| /// <param name="response">Response object.</param> |
| /// <param name="context">Operation context.</param> |
| |
| if (response && assigned(response.body) && response.headers["Content-Type"]) { |
| dispatchHandler("read", response, context); |
| } |
| }, |
| |
| write: function(request, context) { |
| /// <summary>Write the body of the specified request by delegating to JSON handlers.</summary> |
| /// <param name="request">Reques tobject.</param> |
| /// <param name="context">Operation context.</param> |
| |
| dispatchHandler("write", request, context); |
| }, |
| |
| maxDataServiceVersion: MAX_DATA_SERVICE_VERSION, |
| accept: "application/json;q=0.9, */*;q=0.1" |
| }; |
| |
| exports.defaultMetadata = []; //TODO check why is the defaultMetadata an Array? and not an Object. |
| |
| exports.read = function(urlOrRequest, success, error, handler, httpClient, metadata) { |
| /// <summary>Reads data from the specified URL.</summary> |
| /// <param name="urlOrRequest">URL to read data from.</param> |
| /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param> |
| /// <param name="error" type="Function" optional="true">Callback for handling errors.</param> |
| /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param> |
| /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param> |
| /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param> |
| |
| var request; |
| if (urlOrRequest instanceof String || typeof urlOrRequest === "string") { |
| request = { |
| requestUri: urlOrRequest |
| }; |
| } else { |
| request = urlOrRequest; |
| } |
| |
| return exports.request(request, success, error, handler, httpClient, metadata); |
| }; |
| |
| exports.request = function(request, success, error, handler, httpClient, metadata) { |
| /// <summary>Sends a request containing OData payload to a server.</summary> |
| /// <param name="request" type="Object">Object that represents the request to be sent.</param> |
| /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param> |
| /// <param name="error" type="Function" optional="true">Callback for handling errors.</param> |
| /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param> |
| /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param> |
| /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param> |
| |
| success = success || exports.defaultSuccess; |
| error = error || exports.defaultError; |
| handler = handler || exports.defaultHandler; |
| httpClient = httpClient || odataNet.defaultHttpClient; |
| metadata = metadata || exports.defaultMetadata; |
| |
| // Augment the request with additional defaults. |
| request.recognizeDates = utils.defined(request.recognizeDates, odataJson.jsonHandler.recognizeDates); |
| request.callbackParameterName = utils.defined(request.callbackParameterName, odataNet.defaultHttpClient.callbackParameterName); |
| request.formatQueryString = utils.defined(request.formatQueryString, odataNet.defaultHttpClient.formatQueryString); |
| request.enableJsonpCallback = utils.defined(request.enableJsonpCallback, odataNet.defaultHttpClient.enableJsonpCallback); |
| |
| // Create the base context for read/write operations, also specifying complete settings. |
| var context = { |
| metadata: metadata, |
| recognizeDates: request.recognizeDates, |
| callbackParameterName: request.callbackParameterName, |
| formatQueryString: request.formatQueryString, |
| enableJsonpCallback: request.enableJsonpCallback |
| }; |
| |
| try { |
| odataUtils.prepareRequest(request, handler, context); |
| return odataUtils.invokeRequest(request, success, error, handler, httpClient, context); |
| } catch (err) { |
| // errors in success handler for sync requests are catched here and result in error handler calls. |
| // So here we fix this and throw that error further. |
| if (err.bIsSuccessHandlerError) { |
| throw err; |
| } else { |
| error(err); |
| } |
| } |
| |
| }; |
| |
| exports.parseMetadata = function(csdlMetadataDocument) { |
| /// <summary>Parses the csdl metadata to DataJS metatdata format. This method can be used when the metadata is retrieved using something other than DataJS</summary> |
| /// <param name="csdlMetadata" type="string">A string that represents the entire csdl metadata.</param> |
| /// <returns type="Object">An object that has the representation of the metadata in Datajs format.</returns> |
| |
| return metadataParser(null, csdlMetadataDocument); |
| }; |
| |
| // Configure the batch handler to use the default handler for the batch parts. |
| exports.batch.batchHandler.partHandler = exports.defaultHandler; |
| |
| }, { |
| "./datajs/utils.js": 6, |
| "./odata/batch.js": 9, |
| "./odata/handler.js": 10, |
| "./odata/json.js": 11, |
| "./odata/metadata.js": 12, |
| "./odata/net.js": 13, |
| "./odata/utils.js": 14 |
| } |
| ], |
| 9: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /* { |
| oldname:'odata-batch.js', |
| updated:'20140514 12:59' |
| }*/ |
| |
| var utils = require('./../datajs.js').utils; |
| var odataUtils = require('./utils.js'); |
| var odataHandler = require('./handler.js'); |
| |
| // Imports |
| |
| var extend = utils.extend; |
| var isArray = utils.isArray; |
| var trimString = utils.trimString; |
| |
| var contentType = odataHandler.contentType; |
| var handler = odataHandler.handler; |
| var isBatch = odataUtils.isBatch; |
| var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION; |
| var normalizeHeaders = odataUtils.normalizeHeaders; |
| //TODO var payloadTypeOf = odata.payloadTypeOf; |
| var prepareRequest = odataUtils.prepareRequest; |
| |
| // CONTENT START |
| var batchMediaType = "multipart/mixed"; |
| var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i; |
| var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/; |
| |
| var hex16 = function() { |
| /// <summary> |
| /// Calculates a random 16 bit number and returns it in hexadecimal format. |
| /// </summary> |
| /// <returns type="String">A 16-bit number in hex format.</returns> |
| |
| return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1); |
| }; |
| |
| var createBoundary = function(prefix) { |
| /// <summary> |
| /// Creates a string that can be used as a multipart request boundary. |
| /// </summary> |
| /// <param name="prefix" type="String" optional="true">String to use as the start of the boundary string</param> |
| /// <returns type="String">Boundary string of the format: <prefix><hex16>-<hex16>-<hex16></returns> |
| |
| return prefix + hex16() + "-" + hex16() + "-" + hex16(); |
| }; |
| |
| var partHandler = function(context) { |
| /// <summary> |
| /// Gets the handler for data serialization of individual requests / responses in a batch. |
| /// </summary> |
| /// <param name="context">Context used for data serialization.</param> |
| /// <returns>Handler object.</returns> |
| |
| return context.handler.partHandler; |
| }; |
| |
| var currentBoundary = function(context) { |
| /// <summary> |
| /// Gets the current boundary used for parsing the body of a multipart response. |
| /// </summary> |
| /// <param name="context">Context used for parsing a multipart response.</param> |
| /// <returns type="String">Boundary string.</returns> |
| |
| var boundaries = context.boundaries; |
| return boundaries[boundaries.length - 1]; |
| }; |
| |
| var batchParser = function(handler, text, context) { |
| /// <summary>Parses a batch response.</summary> |
| /// <param name="handler">This handler.</param> |
| /// <param name="text" type="String">Batch text.</param> |
| /// <param name="context" type="Object">Object with parsing context.</param> |
| /// <returns>An object representation of the batch.</returns> |
| |
| var boundary = context.contentType.properties["boundary"]; |
| return { |
| __batchResponses: readBatch(text, { |
| boundaries: [boundary], |
| handlerContext: context |
| }) |
| }; |
| }; |
| |
| var batchSerializer = function(handler, data, context) { |
| /// <summary>Serializes a batch object representation into text.</summary> |
| /// <param name="handler">This handler.</param> |
| /// <param name="data" type="Object">Representation of a batch.</param> |
| /// <param name="context" type="Object">Object with parsing context.</param> |
| /// <returns>An text representation of the batch object; undefined if not applicable.</returns> |
| |
| var cType = context.contentType = context.contentType || contentType(batchMediaType); |
| if (cType.mediaType === batchMediaType) { |
| return writeBatch(data, context); |
| } |
| }; |
| |
| var readBatch = function(text, context) { |
| /// <summary> |
| /// Parses a multipart/mixed response body from from the position defined by the context. |
| /// </summary> |
| /// <param name="text" type="String" optional="false">Body of the multipart/mixed response.</param> |
| /// <param name="context">Context used for parsing.</param> |
| /// <returns>Array of objects representing the individual responses.</returns> |
| |
| var delimiter = "--" + currentBoundary(context); |
| |
| // Move beyond the delimiter and read the complete batch |
| readTo(text, context, delimiter); |
| |
| // Ignore the incoming line |
| readLine(text, context); |
| |
| // Read the batch parts |
| var responses = []; |
| var partEnd; |
| |
| while (partEnd !== "--" && context.position < text.length) { |
| var partHeaders = readHeaders(text, context); |
| var partContentType = contentType(partHeaders["Content-Type"]); |
| |
| var changeResponses; |
| if (partContentType && partContentType.mediaType === batchMediaType) { |
| context.boundaries.push(partContentType.properties["boundary"]); |
| try { |
| changeResponses = readBatch(text, context); |
| } catch (e) { |
| e.response = readResponse(text, context, delimiter); |
| changeResponses = [e]; |
| } |
| responses.push({ |
| __changeResponses: changeResponses |
| }); |
| context.boundaries.pop(); |
| readTo(text, context, "--" + currentBoundary(context)); |
| } else { |
| if (!partContentType || partContentType.mediaType !== "application/http") { |
| throw { |
| message: "invalid MIME part type " |
| }; |
| } |
| // Skip empty line |
| readLine(text, context); |
| // Read the response |
| var response = readResponse(text, context, delimiter); |
| try { |
| if (response.statusCode >= 200 && response.statusCode <= 299) { |
| partHandler(context.handlerContext).read(response, context.handlerContext); |
| } else { |
| // Keep track of failed responses and continue processing the batch. |
| response = { |
| message: "HTTP request failed", |
| response: response |
| }; |
| } |
| } catch (e) { |
| response = e; |
| } |
| |
| responses.push(response); |
| } |
| |
| partEnd = text.substr(context.position, 2); |
| |
| // Ignore the incoming line. |
| readLine(text, context); |
| } |
| return responses; |
| }; |
| |
| var readHeaders = function(text, context) { |
| /// <summary> |
| /// Parses the http headers in the text from the position defined by the context. |
| /// </summary> |
| /// <param name="text" type="String" optional="false">Text containing an http response's headers</param> |
| /// <param name="context">Context used for parsing.</param> |
| /// <returns>Object containing the headers as key value pairs.</returns> |
| /// <remarks> |
| /// This function doesn't support split headers and it will stop reading when it hits two consecutive line breaks. |
| /// </remarks> |
| |
| var headers = {}; |
| var parts; |
| var line; |
| var pos; |
| |
| do { |
| pos = context.position; |
| line = readLine(text, context); |
| parts = responseHeaderRegex.exec(line); |
| if (parts !== null) { |
| headers[parts[1]] = parts[2]; |
| } else { |
| // Whatever was found is not a header, so reset the context position. |
| context.position = pos; |
| } |
| } while (line && parts); |
| |
| normalizeHeaders(headers); |
| |
| return headers; |
| }; |
| |
| var readResponse = function(text, context, delimiter) { |
| /// <summary> |
| /// Parses an HTTP response. |
| /// </summary> |
| /// <param name="text" type="String" optional="false">Text representing the http response.</param> |
| /// <param name="context" optional="false">Context used for parsing.</param> |
| /// <param name="delimiter" type="String" optional="false">String used as delimiter of the multipart response parts.</param> |
| /// <returns>Object representing the http response.</returns> |
| |
| // Read the status line. |
| var pos = context.position; |
| var match = responseStatusRegex.exec(readLine(text, context)); |
| |
| var statusCode; |
| var statusText; |
| var headers; |
| |
| if (match) { |
| statusCode = match[1]; |
| statusText = match[2]; |
| headers = readHeaders(text, context); |
| readLine(text, context); |
| } else { |
| context.position = pos; |
| } |
| |
| return { |
| statusCode: statusCode, |
| statusText: statusText, |
| headers: headers, |
| body: readTo(text, context, "\r\n" + delimiter) |
| }; |
| }; |
| |
| var readLine = function(text, context) { |
| /// <summary> |
| /// Returns a substring from the position defined by the context up to the next line break (CRLF). |
| /// </summary> |
| /// <param name="text" type="String" optional="false">Input string.</param> |
| /// <param name="context" optional="false">Context used for reading the input string.</param> |
| /// <returns type="String">Substring to the first ocurrence of a line break or null if none can be found. </returns> |
| |
| return readTo(text, context, "\r\n"); |
| }; |
| |
| var readTo = function(text, context, str) { |
| /// <summary> |
| /// Returns a substring from the position given by the context up to value defined by the str parameter and increments the position in the context. |
| /// </summary> |
| /// <param name="text" type="String" optional="false">Input string.</param> |
| /// <param name="context" type="Object" optional="false">Context used for reading the input string.</param> |
| /// <param name="str" type="String" optional="true">Substring to read up to.</param> |
| /// <returns type="String">Substring to the first ocurrence of str or the end of the input string if str is not specified. Null if the marker is not found.</returns> |
| |
| var start = context.position || 0; |
| var end = text.length; |
| if (str) { |
| end = text.indexOf(str, start); |
| if (end === -1) { |
| return null; |
| } |
| context.position = end + str.length; |
| } else { |
| context.position = end; |
| } |
| |
| return text.substring(start, end); |
| }; |
| |
| var writeBatch = function(data, context) { |
| /// <summary> |
| /// Serializes a batch request object to a string. |
| /// </summary> |
| /// <param name="data" optional="false">Batch request object in payload representation format</param> |
| /// <param name="context" optional="false">Context used for the serialization</param> |
| /// <returns type="String">String representing the batch request</returns> |
| |
| if (!isBatch(data)) { |
| throw { |
| message: "Data is not a batch object." |
| }; |
| } |
| |
| var batchBoundary = createBoundary("batch_"); |
| var batchParts = data.__batchRequests; |
| var batch = ""; |
| var i, len; |
| for (i = 0, len = batchParts.length; i < len; i++) { |
| batch += writeBatchPartDelimiter(batchBoundary, false) + |
| writeBatchPart(batchParts[i], context); |
| } |
| batch += writeBatchPartDelimiter(batchBoundary, true); |
| |
| // Register the boundary with the request content type. |
| var contentTypeProperties = context.contentType.properties; |
| contentTypeProperties.boundary = batchBoundary; |
| |
| return batch; |
| }; |
| |
| var writeBatchPartDelimiter = function(boundary, close) { |
| /// <summary> |
| /// Creates the delimiter that indicates that start or end of an individual request. |
| /// </summary> |
| /// <param name="boundary" type="String" optional="false">Boundary string used to indicate the start of the request</param> |
| /// <param name="close" type="Boolean">Flag indicating that a close delimiter string should be generated</param> |
| /// <returns type="String">Delimiter string</returns> |
| |
| var result = "\r\n--" + boundary; |
| if (close) { |
| result += "--"; |
| } |
| |
| return result + "\r\n"; |
| }; |
| |
| var writeBatchPart = function(part, context, nested) { |
| /// <summary> |
| /// Serializes a part of a batch request to a string. A part can be either a GET request or |
| /// a change set grouping several CUD (create, update, delete) requests. |
| /// </summary> |
| /// <param name="part" optional="false">Request or change set object in payload representation format</param> |
| /// <param name="context" optional="false">Object containing context information used for the serialization</param> |
| /// <param name="nested" type="boolean" optional="true">Flag indicating that the part is nested inside a change set</param> |
| /// <returns type="String">String representing the serialized part</returns> |
| /// <remarks> |
| /// A change set is an array of request objects and they cannot be nested inside other change sets. |
| /// </remarks> |
| |
| var changeSet = part.__changeRequests; |
| var result; |
| if (isArray(changeSet)) { |
| if (nested) { |
| throw { |
| message: "Not Supported: change set nested in other change set" |
| }; |
| } |
| |
| var changeSetBoundary = createBoundary("changeset_"); |
| result = "Content-Type: " + batchMediaType + "; boundary=" + changeSetBoundary + "\r\n"; |
| var i, len; |
| for (i = 0, len = changeSet.length; i < len; i++) { |
| result += writeBatchPartDelimiter(changeSetBoundary, false) + |
| writeBatchPart(changeSet[i], context, true); |
| } |
| |
| result += writeBatchPartDelimiter(changeSetBoundary, true); |
| } else { |
| result = "Content-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\n"; |
| var partContext = extend({}, context); |
| partContext.handler = handler; |
| partContext.request = part; |
| partContext.contentType = null; |
| |
| prepareRequest(part, partHandler(context), partContext); |
| result += writeRequest(part); |
| } |
| |
| return result; |
| }; |
| |
| var writeRequest = function(request) { |
| /// <summary> |
| /// Serializes a request object to a string. |
| /// </summary> |
| /// <param name="request" optional="false">Request object to serialize</param> |
| /// <returns type="String">String representing the serialized request</returns> |
| |
| var result = (request.method ? request.method : "GET") + " " + request.requestUri + " HTTP/1.1\r\n"; |
| for (var name in request.headers) { |
| if (request.headers[name]) { |
| result = result + name + ": " + request.headers[name] + "\r\n"; |
| } |
| } |
| |
| result += "\r\n"; |
| |
| if (request.body) { |
| result += request.body; |
| } |
| |
| return result; |
| }; |
| |
| exports.batchHandler = handler(batchParser, batchSerializer, batchMediaType, MAX_DATA_SERVICE_VERSION); |
| |
| // DATAJS INTERNAL START |
| exports.batchSerializer = batchSerializer; |
| exports.writeRequest = writeRequest; |
| // DATAJS INTERNAL END |
| |
| }, { |
| "./../datajs.js": 4, |
| "./handler.js": 10, |
| "./utils.js": 14 |
| } |
| ], |
| 10: [ |
| function(require, module, exports) { |
| |
| |
| var utils = require('./../datajs.js').utils; |
| var oDataUtils = require('./utils.js'); |
| |
| // Imports. |
| var assigned = utils.assigned; |
| var extend = utils.extend; |
| var trimString = utils.trimString; |
| var maxVersion = oDataUtils.maxVersion; |
| var MAX_DATA_SERVICE_VERSION = "4.0"; |
| |
| var contentType = function(str) { |
| /// <summary>Parses a string into an object with media type and properties.</summary> |
| /// <param name="str" type="String">String with media type to parse.</param> |
| /// <returns>null if the string is empty; an object with 'mediaType' and a 'properties' dictionary otherwise.</returns> |
| |
| if (!str) { |
| return null; |
| } |
| |
| var contentTypeParts = str.split(";"); |
| var properties = {}; |
| |
| var i, len; |
| for (i = 1, len = contentTypeParts.length; i < len; i++) { |
| var contentTypeParams = contentTypeParts[i].split("="); |
| properties[trimString(contentTypeParams[0])] = contentTypeParams[1]; |
| } |
| |
| return { |
| mediaType: trimString(contentTypeParts[0]), |
| properties: properties |
| }; |
| }; |
| |
| var contentTypeToString = function(contentType) { |
| /// <summary>Serializes an object with media type and properties dictionary into a string.</summary> |
| /// <param name="contentType">Object with media type and properties dictionary to serialize.</param> |
| /// <returns>String representation of the media type object; undefined if contentType is null or undefined.</returns> |
| |
| if (!contentType) { |
| return undefined; |
| } |
| |
| var result = contentType.mediaType; |
| var property; |
| for (property in contentType.properties) { |
| result += ";" + property + "=" + contentType.properties[property]; |
| } |
| return result; |
| }; |
| |
| var createReadWriteContext = function(contentType, dataServiceVersion, context, handler) { |
| /// <summary>Creates an object that is going to be used as the context for the handler's parser and serializer.</summary> |
| /// <param name="contentType">Object with media type and properties dictionary.</param> |
| /// <param name="dataServiceVersion" type="String">String indicating the version of the protocol to use.</param> |
| /// <param name="context">Operation context.</param> |
| /// <param name="handler">Handler object that is processing a resquest or response.</param> |
| /// <returns>Context object.</returns> |
| |
| var rwContext = {}; |
| extend(rwContext, context); |
| extend(rwContext, { |
| contentType: contentType, |
| dataServiceVersion: dataServiceVersion, |
| handler: handler |
| }); |
| |
| return rwContext; |
| }; |
| |
| var fixRequestHeader = function(request, name, value) { |
| /// <summary>Sets a request header's value. If the header has already a value other than undefined, null or empty string, then this method does nothing.</summary> |
| /// <param name="request">Request object on which the header will be set.</param> |
| /// <param name="name" type="String">Header name.</param> |
| /// <param name="value" type="String">Header value.</param> |
| if (!request) { |
| return; |
| } |
| |
| var headers = request.headers; |
| if (!headers[name]) { |
| headers[name] = value; |
| } |
| }; |
| |
| var fixDataServiceVersionHeader = function(request, version) { |
| /// <summary>Sets the DataServiceVersion header of the request if its value is not yet defined or of a lower version.</summary> |
| /// <param name="request">Request object on which the header will be set.</param> |
| /// <param name="version" type="String">Version value.</param> |
| /// <remarks> |
| /// If the request has already a version value higher than the one supplied the this function does nothing. |
| /// </remarks> |
| |
| if (request) { |
| var headers = request.headers; |
| var dsv = headers["OData-Version"]; |
| headers["OData-Version"] = dsv ? maxVersion(dsv, version) : version; |
| } |
| }; |
| |
| var getRequestOrResponseHeader = function(requestOrResponse, name) { |
| /// <summary>Gets the value of a request or response header.</summary> |
| /// <param name="requestOrResponse">Object representing a request or a response.</param> |
| /// <param name="name" type="String">Name of the header to retrieve.</param> |
| /// <returns type="String">String value of the header; undefined if the header cannot be found.</returns> |
| |
| var headers = requestOrResponse.headers; |
| return (headers && headers[name]) || undefined; |
| }; |
| |
| var getContentType = function(requestOrResponse) { |
| /// <summary>Gets the value of the Content-Type header from a request or response.</summary> |
| /// <param name="requestOrResponse">Object representing a request or a response.</param> |
| /// <returns type="Object">Object with 'mediaType' and a 'properties' dictionary; null in case that the header is not found or doesn't have a value.</returns> |
| |
| return contentType(getRequestOrResponseHeader(requestOrResponse, "Content-Type")); |
| }; |
| |
| var versionRE = /^\s?(\d+\.\d+);?.*$/; |
| var getDataServiceVersion = function(requestOrResponse) { |
| /// <summary>Gets the value of the DataServiceVersion header from a request or response.</summary> |
| /// <param name="requestOrResponse">Object representing a request or a response.</param> |
| /// <returns type="String">Data service version; undefined if the header cannot be found.</returns> |
| |
| var value = getRequestOrResponseHeader(requestOrResponse, "OData-Version"); |
| if (value) { |
| var matches = versionRE.exec(value); |
| if (matches && matches.length) { |
| return matches[1]; |
| } |
| } |
| |
| // Fall through and return undefined. |
| }; |
| |
| var handlerAccepts = function(handler, cType) { |
| /// <summary>Checks that a handler can process a particular mime type.</summary> |
| /// <param name="handler">Handler object that is processing a resquest or response.</param> |
| /// <param name="cType">Object with 'mediaType' and a 'properties' dictionary.</param> |
| /// <returns type="Boolean">True if the handler can process the mime type; false otherwise.</returns> |
| |
| // The following check isn't as strict because if cType.mediaType = application/; it will match an accept value of "application/xml"; |
| // however in practice we don't not expect to see such "suffixed" mimeTypes for the handlers. |
| return handler.accept.indexOf(cType.mediaType) >= 0; |
| }; |
| |
| var handlerRead = function(handler, parseCallback, response, context) { |
| /// <summary>Invokes the parser associated with a handler for reading the payload of a HTTP response.</summary> |
| /// <param name="handler">Handler object that is processing the response.</param> |
| /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param> |
| /// <param name="response">HTTP response whose payload is going to be processed.</param> |
| /// <param name="context">Object used as the context for processing the response.</param> |
| /// <returns type="Boolean">True if the handler processed the response payload and the response.data property was set; false otherwise.</returns> |
| |
| if (!response || !response.headers) { |
| return false; |
| } |
| |
| var cType = getContentType(response); |
| var version = getDataServiceVersion(response) || ""; |
| var body = response.body; |
| |
| if (!assigned(body)) { |
| return false; |
| } |
| |
| if (handlerAccepts(handler, cType)) { |
| var readContext = createReadWriteContext(cType, version, context, handler); |
| readContext.response = response; |
| response.data = parseCallback(handler, body, readContext); |
| return response.data !== undefined; |
| } |
| |
| return false; |
| }; |
| |
| var handlerWrite = function(handler, serializeCallback, request, context) { |
| /// <summary>Invokes the serializer associated with a handler for generating the payload of a HTTP request.</summary> |
| /// <param name="handler">Handler object that is processing the request.</param> |
| /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param> |
| /// <param name="response">HTTP request whose payload is going to be generated.</param> |
| /// <param name="context">Object used as the context for serializing the request.</param> |
| /// <returns type="Boolean">True if the handler serialized the request payload and the request.body property was set; false otherwise.</returns> |
| if (!request || !request.headers) { |
| return false; |
| } |
| |
| var cType = getContentType(request); |
| var version = getDataServiceVersion(request); |
| |
| if (!cType || handlerAccepts(handler, cType)) { |
| var writeContext = createReadWriteContext(cType, version, context, handler); |
| writeContext.request = request; |
| |
| request.body = serializeCallback(handler, request.data, writeContext); |
| |
| if (request.body !== undefined) { |
| fixDataServiceVersionHeader(request, writeContext.dataServiceVersion || "4.0"); |
| |
| fixRequestHeader(request, "Content-Type", contentTypeToString(writeContext.contentType)); |
| fixRequestHeader(request, "OData-MaxVersion", handler.maxDataServiceVersion); |
| return true; |
| } |
| } |
| |
| return false; |
| }; |
| |
| var handler = function(parseCallback, serializeCallback, accept, maxDataServiceVersion) { |
| /// <summary>Creates a handler object for processing HTTP requests and responses.</summary> |
| /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param> |
| /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param> |
| /// <param name="accept" type="String">String containing a comma separated list of the mime types that this handler can work with.</param> |
| /// <param name="maxDataServiceVersion" type="String">String indicating the highest version of the protocol that this handler can work with.</param> |
| /// <returns type="Object">Handler object.</returns> |
| |
| return { |
| accept: accept, |
| maxDataServiceVersion: maxDataServiceVersion, |
| |
| read: function(response, context) { |
| return handlerRead(this, parseCallback, response, context); |
| }, |
| |
| write: function(request, context) { |
| return handlerWrite(this, serializeCallback, request, context); |
| } |
| }; |
| }; |
| |
| var textParse = function(handler, body /*, context */ ) { |
| return body; |
| }; |
| |
| var textSerialize = function(handler, data /*, context */ ) { |
| if (assigned(data)) { |
| return data.toString(); |
| } else { |
| return undefined; |
| } |
| }; |
| |
| exports.textHandler = handler(textParse, textSerialize, "text/plain", MAX_DATA_SERVICE_VERSION); |
| |
| exports.contentType = contentType; |
| exports.contentTypeToString = contentTypeToString; |
| exports.handler = handler; |
| exports.createReadWriteContext = createReadWriteContext; |
| exports.fixRequestHeader = fixRequestHeader; |
| exports.getRequestOrResponseHeader = getRequestOrResponseHeader; |
| exports.getContentType = getContentType; |
| exports.getDataServiceVersion = getDataServiceVersion; |
| exports.MAX_DATA_SERVICE_VERSION = MAX_DATA_SERVICE_VERSION; |
| |
| }, { |
| "./../datajs.js": 4, |
| "./utils.js": 14 |
| } |
| ], |
| 11: [ |
| function(require, module, exports) { |
| |
| |
| var utils = require('./../datajs.js').utils; |
| var oDataUtils = require('./utils.js'); |
| var oDataHandler = require('./handler.js'); |
| |
| var odataNs = "odata"; |
| var odataAnnotationPrefix = odataNs + "."; |
| var contextUrlAnnotation = "@" + odataAnnotationPrefix + "context"; |
| |
| var assigned = utils.assigned; |
| var defined = utils.defined; |
| var isArray = utils.isArray; |
| //var isDate = utils.isDate; |
| var isObject = utils.isObject; |
| //var normalizeURI = utils.normalizeURI; |
| var parseInt10 = utils.parseInt10; |
| var getFormatKind = utils.getFormatKind; |
| |
| var formatDateTimeOffset = oDataUtils.formatDateTimeOffset; |
| var formatDuration = oDataUtils.formatDuration; |
| var formatNumberWidth = oDataUtils.formatNumberWidth; |
| var getCanonicalTimezone = oDataUtils.getCanonicalTimezone; |
| var handler = oDataUtils.handler; |
| var isComplex = oDataUtils.isComplex; |
| var isPrimitive = oDataUtils.isPrimitive; |
| var isCollectionType = oDataUtils.isCollectionType; |
| var lookupComplexType = oDataUtils.lookupComplexType; |
| var lookupEntityType = oDataUtils.lookupEntityType; |
| var lookupSingleton = oDataUtils.lookupSingleton; |
| var lookupEntitySet = oDataUtils.lookupEntitySet; |
| var lookupDefaultEntityContainer = oDataUtils.lookupDefaultEntityContainer; |
| var lookupProperty = oDataUtils.lookupProperty; |
| var MAX_DATA_SERVICE_VERSION = oDataUtils.MAX_DATA_SERVICE_VERSION; |
| var maxVersion = oDataUtils.maxVersion; |
| var XXXparseDateTime = oDataUtils.XXXparseDateTime; |
| |
| var isPrimitiveEdmType = oDataUtils.isPrimitiveEdmType; |
| var isGeographyEdmType = oDataUtils.isGeographyEdmType; |
| var isGeometryEdmType = oDataUtils.isGeometryEdmType; |
| //var parseDuration = oDataUtils.parseDuration; |
| //var parseTimezone = oDataUtils.parseTimezone; |
| //var payloadTypeOf = oDataUtils.payloadTypeOf; |
| //var traverse = oDataUtils.traverse; |
| |
| var PAYLOADTYPE_FEED = "f"; |
| var PAYLOADTYPE_ENTRY = "e"; |
| var PAYLOADTYPE_PROPERTY = "p"; |
| var PAYLOADTYPE_COLLECTION = "c"; |
| var PAYLOADTYPE_ENUMERATION_PROPERTY = "enum"; |
| var PAYLOADTYPE_SVCDOC = "s"; |
| var PAYLOADTYPE_ENTITY_REF_LINK = "erl"; |
| var PAYLOADTYPE_ENTITY_REF_LINKS = "erls"; |
| |
| var PAYLOADTYPE_VALUE = "v"; |
| |
| var PAYLOADTYPE_DELTA = "d"; |
| var DELTATYPE_FEED = "f"; |
| var DELTATYPE_DELETED_ENTRY = "de"; |
| var DELTATYPE_LINK = "l"; |
| var DELTATYPE_DELETED_LINK = "dl"; |
| |
| var jsonMediaType = "application/json"; |
| var jsonContentType = oDataHandler.contentType(jsonMediaType); |
| |
| |
| // The regular expression corresponds to something like this: |
| // /Date(123+60)/ |
| // |
| // This first number is date ticks, the + may be a - and is optional, |
| // with the second number indicating a timezone offset in minutes. |
| // |
| // On the wire, the leading and trailing forward slashes are |
| // escaped without being required to so the chance of collisions is reduced; |
| // however, by the time we see the objects, the characters already |
| // look like regular forward slashes. |
| var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/; |
| |
| var minutesToOffset = function(minutes) { |
| /// <summary>Formats the given minutes into (+/-)hh:mm format.</summary> |
| /// <param name="minutes" type="Number">Number of minutes to format.</param> |
| /// <returns type="String">The minutes in (+/-)hh:mm format.</returns> |
| |
| var sign; |
| if (minutes < 0) { |
| sign = "-"; |
| minutes = -minutes; |
| } else { |
| sign = "+"; |
| } |
| |
| var hours = Math.floor(minutes / 60); |
| minutes = minutes - (60 * hours); |
| |
| return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2); |
| }; |
| |
| var parseJsonDateString = function(value) { |
| /// <summary>Parses the JSON Date representation into a Date object.</summary> |
| /// <param name="value" type="String">String value.</param> |
| /// <returns type="Date">A Date object if the value matches one; falsy otherwise.</returns> |
| |
| var arr = value && jsonDateRE.exec(value); |
| if (arr) { |
| // 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes |
| var result = new Date(parseInt10(arr[1])); |
| if (arr[2]) { |
| var mins = parseInt10(arr[3]); |
| if (arr[2] === "-") { |
| mins = -mins; |
| } |
| |
| // The offset is reversed to get back the UTC date, which is |
| // what the API will eventually have. |
| var current = result.getUTCMinutes(); |
| result.setUTCMinutes(current - mins); |
| result.__edmType = "Edm.DateTimeOffset"; |
| result.__offset = minutesToOffset(mins); |
| } |
| if (!isNaN(result.valueOf())) { |
| return result; |
| } |
| } |
| |
| // Allow undefined to be returned. |
| }; |
| |
| // Some JSON implementations cannot produce the character sequence \/ |
| // which is needed to format DateTime and DateTimeOffset into the |
| // JSON string representation defined by the OData protocol. |
| // See the history of this file for a candidate implementation of |
| // a 'formatJsonDateString' function. |
| |
| var jsonParser = function(handler, text, context) { |
| /// <summary>Parses a JSON OData payload.</summary> |
| /// <param name="handler">This handler.</param> |
| /// <param name="text">Payload text (this parser also handles pre-parsed objects).</param> |
| /// <param name="context" type="Object">Object with parsing context.</param> |
| /// <returns>An object representation of the OData payload.</returns> |
| |
| var recognizeDates = defined(context.recognizeDates, handler.recognizeDates); |
| var model = context.metadata; |
| var json = (typeof text === "string") ? JSON.parse(text) : text; |
| var metadataContentType; |
| if (assigned(context.contentType) && assigned(context.contentType.properties)) { |
| metadataContentType = context.contentType.properties["odata.metadata"]; //TODO convert to lower before comparism |
| } |
| |
| var payloadFormat = getFormatKind(metadataContentType, 1); // none: 0, minimal: 1, full: 2 |
| |
| // No errors should be throw out if we could not parse the json payload, instead we should just return the original json object. |
| if (payloadFormat === 0) { |
| return json; |
| } else if (payloadFormat === 1) { |
| return readPayloadMinimal(json, model, recognizeDates); |
| } else if (payloadFormat === 2) { |
| // to do: using the EDM Model to get the type of each property instead of just guessing. |
| return readPayloadFull(json, model, recognizeDates); |
| } else { |
| return json; |
| } |
| }; |
| |
| |
| var addType = function(data, name, value) { |
| var fullName = name + '@odata.type'; |
| |
| if (data[fullName] === undefined) { |
| data[fullName] = value; |
| } |
| }; |
| |
| var addTypeNoEdm = function(data, name, value) { |
| var fullName = name + '@odata.type'; |
| |
| if (data[fullName] === undefined) { |
| if (value.substring(0, 4) === 'Edm.') { |
| data[fullName] = value.substring(4); |
| } else { |
| data[fullName] = value; |
| } |
| |
| } |
| }; |
| |
| var addTypeColNoEdm = function(data, name, value) { |
| var fullName = name + '@odata.type'; |
| |
| if (data[fullName] === undefined) { |
| if (value.substring(0, 4) === 'Edm.') { |
| data[fullName] = 'Collection(' + value.substring(4) + ')'; |
| } else { |
| data[fullName] = 'Collection(' + value + ')'; |
| } |
| } |
| }; |
| |
| |
| var readPayloadFull = function(data, model, recognizeDates) { |
| /// <summary>Adds typeinformation for String, Boolean and numerical EDM-types. |
| /// The type is determined from the odata-json-format-v4.0.doc specification |
| ///</summary> |
| /// <param name="data">Date which will be extendet</param> |
| /// <param name="recognizeDates" type="Boolean"> |
| /// True if strings formatted as datetime values should be treated as datetime values. False otherwise. |
| /// </param> |
| /// <returns>An object representation of the OData payload.</returns> |
| |
| if (utils.isObject(data)) { |
| for (var key in data) { |
| if (data.hasOwnProperty(key)) { |
| if (key.indexOf('@') === -1) { |
| if (utils.isArray(data[key])) { |
| for (var i = 0; i < data[key].length; ++i) { |
| readPayloadFull(data[key][i], model, recognizeDates); |
| } |
| } else if (utils.isObject(data[key])) { |
| if (data[key] !== null) { |
| //don't step into geo.. objects |
| var isGeo = false; |
| var type = data[key + '@odata.type']; |
| if (type && (isGeographyEdmType(type) || isGeometryEdmType(type))) { |
| // is gemometry type |
| } else { |
| readPayloadFull(data[key], model, recognizeDates); |
| } |
| } |
| } else { |
| var type = data[key + '@odata.type']; |
| |
| // On .Net OData library, some basic EDM type is omitted, e.g. Edm.String, Edm.Int, and etc. |
| // For the full metadata payload, we need to full fill the @data.type for each property if it is missing. |
| // We do this is to help the OlingoJS consumers to easily get the type of each property. |
| if (!assigned(type)) { |
| // Guessing the "type" from the type of the value is not the right way here. |
| // To do: we need to get the type from metadata instead of guessing. |
| var typeFromObject = typeof data[key]; |
| if (typeFromObject === 'string') { |
| addType(data, key, '#String'); |
| } else if (typeFromObject === 'boolean') { |
| addType(data, key, '#Boolean'); |
| } else if (typeFromObject === 'number') { |
| if (data[key] % 1 === 0) { // has fraction |
| addType(data, key, '#Int32'); // the biggst integer |
| } else { |
| addType(data, key, '#Decimal'); // the biggst float single,doulbe,decimal |
| } |
| } |
| } else { |
| if (recognizeDates) { |
| convertDatesNoEdm(data, key, type.substring(1)); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| return data; |
| }; |
| |
| var jsonSerializer = function(handler, data, context) { |
| /// <summary>Serializes the data by returning its string representation.</summary> |
| /// <param name="handler">This handler.</param> |
| /// <param name="data">Data to serialize.</param> |
| /// <param name="context" type="Object">Object with serialization context.</param> |
| /// <returns type="String">The string representation of data.</returns> |
| |
| var dataServiceVersion = context.dataServiceVersion || "4.0"; |
| var cType = context.contentType = context.contentType || jsonContentType; |
| |
| if (cType && cType.mediaType === jsonContentType.mediaType) { |
| context.dataServiceVersion = maxVersion(dataServiceVersion, "4.0"); |
| var newdata = formatJsonRequestPayload(data); |
| if (newdata) { |
| return JSON.stringify(newdata); |
| } |
| } |
| |
| return undefined; |
| }; |
| |
| var formatJsonRequestPayload = function(data) { |
| if (!data) { |
| return data; |
| } |
| |
| if (isPrimitive(data)) { |
| return data; |
| } |
| |
| if (isArray(data)) { |
| var newArrayData = []; |
| var i, len; |
| for (i = 0, len = data.length; i < len; i++) { |
| newArrayData[i] = formatJsonRequestPayload(data[i]); |
| } |
| |
| return newArrayData; |
| } |
| |
| var newdata = {}; |
| for (var property in data) { |
| if (isJsonSerializableProperty(property)) { |
| newdata[property] = formatJsonRequestPayload(data[property]); |
| } |
| } |
| |
| return newdata; |
| }; |
| var jsonReplacer = function(_, value) { |
| /// <summary>JSON replacer function for converting a value to its JSON representation.</summary> |
| /// <param value type="Object">Value to convert.</param> |
| /// <returns type="String">JSON representation of the input value.</returns> |
| /// <remarks> |
| /// This method is used during JSON serialization and invoked only by the JSON.stringify function. |
| /// It should never be called directly. |
| /// </remarks> |
| |
| if (value && value.__edmType === "Edm.Time") { |
| return formatDuration(value); |
| } else { |
| return value; |
| } |
| }; |
| |
| |
| var jsonMakePayloadInfo = function(kind, type) { |
| /// <summary>Creates an object containing information for the json payload.</summary> |
| /// <param name="kind" type="String">JSON payload kind, one of the PAYLOADTYPE_XXX constant values.</param> |
| /// <param name="typeName" type="String">Type name of the JSON payload.</param> |
| /// <returns type="Object">Object with kind and type fields.</returns> |
| |
| /// <field name="kind" type="String">Kind of the JSON payload. One of the PAYLOADTYPE_XXX constant values.</field> |
| /// <field name="type" type="String">Data type of the JSON payload.</field> |
| |
| return { |
| kind: kind, |
| type: type || null |
| }; |
| }; |
| |
| /// <summary>Creates an object containing information for the context</summary> |
| /// ... |
| /// <returns type="Object">Object with type information |
| /// attribute detectedPayloadKind(optional): see constants starting with PAYLOADTYPE_ |
| /// attribute deltaKind(optional): deltainformation, one of the following valus DELTATYPE_FEED | DELTATYPE_DELETED_ENTRY | DELTATYPE_LINK | DELTATYPE_DELETED_LINK |
| /// attribute typeName(optional): name of the type |
| /// attribute type(optional): object containing type information for entity- and complex-types ( null if a typeName is a primitive) |
| /// </returns> |
| var parseContextUriFragment = function(fragments, model) { |
| var ret = {}; |
| |
| if (fragments.indexOf('/') === -1) { |
| if (fragments.length === 0) { |
| // Capter 10.1 |
| ret.detectedPayloadKind = PAYLOADTYPE_SVCDOC; |
| return ret; |
| } else if (fragments === 'Edm.Null') { |
| // Capter 10.15 |
| ret.detectedPayloadKind = PAYLOADTYPE_VALUE; |
| ret.isNullProperty = true; |
| return ret; |
| } else if (fragments === 'Collection($ref)') { |
| // Capter 10.11 |
| ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINKS; |
| return ret; |
| } else if (fragments === '$ref') { |
| // Capter 10.12 |
| ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINK; |
| return ret; |
| } else { |
| //TODO check for navigation resource |
| } |
| } |
| |
| ret.type = undefined; |
| ret.typeName = undefined; |
| |
| var fragmentParts = fragments.split("/"); |
| var type; |
| |
| for (var i = 0; i < fragmentParts.length; ++i) { |
| var fragment = fragmentParts[i]; |
| if (ret.typeName === undefined) { |
| //preparation |
| if (fragment.indexOf('(') !== -1) { |
| //remove the query function, cut fragment to matching '(' |
| var index = fragment.length - 2; |
| for (var rCount = 1; rCount > 0 && index > 0; --index) { |
| if (fragment.charAt(index) == '(') { |
| rCount--; |
| } else if (fragment.charAt(index) == ')') { |
| rCount++; |
| } |
| } |
| |
| if (index === 0) { |
| //TODO throw error |
| } |
| |
| //remove the projected entity from the fragment; TODO decide if we want to store the projected entity |
| var inPharenthesis = fragment.substring(index + 2, fragment.length - 1); |
| fragment = fragment.substring(0, index + 1); |
| |
| if (utils.startsWith(fragment, 'Collection')) { |
| ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION; |
| // Capter 10.14 |
| ret.typeName = inPharenthesis; |
| |
| type = lookupEntityType(ret.typeName, model); |
| if (type !== null) { |
| ret.type = type; |
| continue; |
| } |
| type = lookupComplexType(ret.typeName, model); |
| if (type !== null) { |
| ret.type = type; |
| continue; |
| } |
| |
| ret.type = null; //in case of #Collection(Edm.String) only lastTypeName is filled |
| continue; |
| } else { |
| // projection: Capter 10.7, 10.8 and 10.9 |
| ret.projection = inPharenthesis; |
| } |
| } |
| |
| |
| if (jsonIsPrimitiveType(fragment)) { |
| ret.typeName = fragment; |
| ret.type = null; |
| ret.detectedPayloadKind = PAYLOADTYPE_VALUE; |
| continue; |
| } |
| |
| var container = lookupDefaultEntityContainer(model); |
| |
| //check for entity |
| var entitySet = lookupEntitySet(container.entitySet, fragment); |
| if (entitySet !== null) { |
| ret.typeName = entitySet.entityType; |
| ret.type = lookupEntityType(ret.typeName, model); |
| ret.name = fragment; |
| ret.detectedPayloadKind = PAYLOADTYPE_FEED; |
| // Capter 10.2 |
| continue; |
| } |
| |
| //check for singleton |
| var singleton = lookupSingleton(container.singleton, fragment); |
| if (singleton !== null) { |
| ret.typeName = singleton.entityType; |
| ret.type = lookupEntityType(ret.typeName, model); |
| ret.name = fragment; |
| ret.detectedPayloadKind = PAYLOADTYPE_ENTRY; |
| // Capter 10.4 |
| continue; |
| } |
| |
| |
| |
| //TODO throw ERROR |
| } else { |
| //check for $entity |
| if (utils.endsWith(fragment, '$entity') && (ret.detectedPayloadKind === PAYLOADTYPE_FEED)) { |
| //TODO ret.name = fragment; |
| ret.detectedPayloadKind = PAYLOADTYPE_ENTRY; |
| // Capter 10.3 and 10.6 |
| continue; |
| } |
| |
| //check for derived types |
| if (fragment.indexOf('.') !== -1) { |
| // Capter 10.6 |
| ret.typeName = fragment; |
| type = lookupEntityType(ret.typeName, model); |
| if (type !== null) { |
| ret.type = type; |
| continue; |
| } |
| type = lookupComplexType(ret.typeName, model); |
| if (type !== null) { |
| ret.type = type; |
| continue; |
| } |
| |
| //TODO throw ERROR invalid type |
| } |
| |
| //check for property value |
| if (ret.detectedPayloadKind === PAYLOADTYPE_FEED || ret.detectedPayloadKind === PAYLOADTYPE_ENTRY) { |
| var property = lookupProperty(ret.type.property, fragment); |
| if (property !== null) { |
| //PAYLOADTYPE_COLLECTION |
| ret.typeName = property.type; |
| |
| |
| if (utils.startsWith(property.type, 'Collection')) { |
| ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION; |
| var tmp12 = property.type.substring(10 + 1, property.type.length - 1); |
| ret.typeName = tmp12; |
| ret.type = lookupComplexType(tmp12, model); |
| ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION; |
| } else { |
| ret.type = lookupComplexType(property.type, model); |
| ret.detectedPayloadKind = PAYLOADTYPE_PROPERTY; |
| } |
| |
| ret.name = fragment; |
| // Capter 10.15 |
| } |
| continue; |
| } |
| |
| if (fragment === '$delta') { |
| ret.deltaKind = DELTATYPE_FEED; |
| continue; |
| } else if (utils.endsWith(fragment, '/$deletedEntity')) { |
| ret.deltaKind = DELTATYPE_DELETED_ENTRY; |
| continue; |
| } else if (utils.endsWith(fragment, '/$link')) { |
| ret.deltaKind = DELTATYPE_LINK; |
| continue; |
| } else if (utils.endsWith(fragment, '/$deletedLink')) { |
| ret.deltaKind = DELTATYPE_DELETED_LINK; |
| continue; |
| } |
| //TODO throw ERROr |
| } |
| } |
| |
| return ret; |
| }; |
| |
| var createPayloadInfo = function(data, model) { |
| /// <summary>Infers the information describing the JSON payload from its metadata annotation, structure, and data model.</summary> |
| /// <param name="data" type="Object">Json response payload object.</param> |
| /// <param name="model" type="Object">Object describing an OData conceptual schema.</param> |
| /// <remarks> |
| /// If the arguments passed to the function don't convey enough information about the payload to determine without doubt that the payload is a feed then it |
| /// will try to use the payload object structure instead. If the payload looks like a feed (has value property that is an array or non-primitive values) then |
| /// the function will report its kind as PAYLOADTYPE_FEED unless the inferFeedAsComplexType flag is set to true. This flag comes from the user request |
| /// and allows the user to control how the library behaves with an ambigous JSON payload. |
| /// </remarks> |
| /// <returns type="Object"> |
| /// Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained.. |
| /// </returns> |
| |
| var metadataUri = data[contextUrlAnnotation]; |
| if (!metadataUri || typeof metadataUri !== "string") { |
| return null; |
| } |
| |
| var fragmentStart = metadataUri.lastIndexOf("#"); |
| if (fragmentStart === -1) { |
| return jsonMakePayloadInfo(PAYLOADTYPE_SVCDOC); |
| } |
| |
| var fragment = metadataUri.substring(fragmentStart + 1); |
| return parseContextUriFragment(fragment, model); |
| }; |
| |
| var readPayloadMinimal = function(data, model, recognizeDates) { |
| /// <summary>Processe a JSON response payload with metadata-minimal</summary> |
| /// <param name="data" type="Object">Json response payload object</param> |
| /// <param name="model" type="Object">Object describing an OData conceptual schema</param> |
| /// <param name="recognizeDates" type="Boolean">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param> |
| /// <returns type="Object">Object in the library's representation.</returns> |
| |
| if (!assigned(model) || isArray(model)) { |
| return data; |
| } |
| |
| var baseURI = data[contextUrlAnnotation]; |
| var payloadInfo = createPayloadInfo(data, model); |
| |
| switch (payloadInfo.detectedPayloadKind) { |
| case PAYLOADTYPE_VALUE: |
| return readPayloadMinimalProperty(data, model, payloadInfo, baseURI, recognizeDates); |
| case PAYLOADTYPE_FEED: |
| return readPayloadMinimalFeed(data, model, payloadInfo, baseURI, recognizeDates); |
| case PAYLOADTYPE_ENTRY: |
| return readPayloadMinimalEntry(data, model, payloadInfo, baseURI, recognizeDates); |
| case PAYLOADTYPE_COLLECTION: |
| return readPayloadMinimalCollection(data, model, payloadInfo, baseURI, recognizeDates); |
| case PAYLOADTYPE_PROPERTY: |
| return readPayloadMinimalProperty(data, model, payloadInfo, baseURI, recognizeDates); |
| case PAYLOADTYPE_SVCDOC: |
| return data; |
| case PAYLOADTYPE_LINKS: |
| return data; |
| } |
| |
| return data; |
| }; |
| |
| var jsonGetEntryKey = function(data, entityModel) { |
| /// <summary>Gets the key of an entry.</summary> |
| /// <param name="data" type="Object">JSON entry.</param> |
| /// <paraFrom Subject Received Size Categories |
| /// <returns type="string">Entry instance key.</returns> |
| |
| var entityInstanceKey; |
| var entityKeys = entityModel.key[0].propertyRef; |
| var type; |
| entityInstanceKey = "("; |
| if (entityKeys.length == 1) { |
| type = lookupProperty(entityModel.property, entityKeys[0].name).type; |
| entityInstanceKey += formatLiteral(data[entityKeys[0].name], type); |
| } else { |
| var first = true; |
| for (var i = 0; i < entityKeys.length; i++) { |
| if (!first) { |
| entityInstanceKey += ","; |
| } else { |
| first = false; |
| } |
| type = lookupProperty(entityModel.property, entityKeys[i].name).type; |
| entityInstanceKey += entityKeys[i].name + "=" + formatLiteral(data[entityKeys[i].name], type); |
| } |
| } |
| entityInstanceKey += ")"; |
| return entityInstanceKey; |
| }; |
| |
| var readPayloadMinimalProperty = function(data, model, collectionInfo, baseURI, recognizeDates) { |
| if (collectionInfo.type !== null) { |
| readPayloadMinimalObject(data, collectionInfo, baseURI, model, recognizeDates); |
| } else { |
| addTypeNoEdm(data, 'value', collectionInfo.typeName); |
| //data['value@odata.type'] = '#'+collectionInfo.typeName; |
| } |
| return data; |
| }; |
| |
| var readPayloadMinimalCollection = function(data, model, collectionInfo, baseURI, recognizeDates) { |
| //data['@odata.type'] = '#Collection('+collectionInfo.typeName + ')'; |
| addTypeColNoEdm(data, '', collectionInfo.typeName); |
| |
| if (collectionInfo.type !== null) { |
| var entries = []; |
| |
| var items = data.value; |
| for (i = 0, len = items.length; i < len; i++) { |
| var item = items[i]; |
| if (defined(item['@odata.type'])) { // in case of mixed collections |
| var typeName = item['@odata.type'].substring(1); |
| var type = lookupEntityType(typeName, model); |
| var entryInfo = { |
| contentTypeOdata: collectionInfo.contentTypeOdata, |
| detectedPayloadKind: collectionInfo.detectedPayloadKind, |
| name: collectionInfo.name, |
| type: type, |
| typeName: typeName |
| }; |
| |
| entry = readPayloadMinimalObject(item, entryInfo, baseURI, model, recognizeDates); |
| } else { |
| entry = readPayloadMinimalObject(item, collectionInfo, baseURI, model, recognizeDates); |
| } |
| |
| entries.push(entry); |
| } |
| data.value = entries; |
| } |
| return data; |
| }; |
| var readPayloadMinimalFeed = function(data, model, feedInfo, baseURI, recognizeDates) { |
| var entries = []; |
| var items = data.value; |
| for (i = 0, len = items.length; i < len; i++) { |
| var item = items[i]; |
| if (defined(item['@odata.type'])) { // in case of mixed feeds |
| var typeName = item['@odata.type'].substring(1); |
| var type = lookupEntityType(typeName, model); |
| var entryInfo = { |
| contentTypeOdata: feedInfo.contentTypeOdata, |
| detectedPayloadKind: feedInfo.detectedPayloadKind, |
| name: feedInfo.name, |
| type: type, |
| typeName: typeName |
| }; |
| |
| entry = readPayloadMinimalObject(item, entryInfo, baseURI, model, recognizeDates); |
| } else { |
| entry = readPayloadMinimalObject(item, feedInfo, baseURI, model, recognizeDates); |
| } |
| |
| entries.push(entry); |
| } |
| data.value = entries; |
| return data; |
| }; |
| |
| var readPayloadMinimalEntry = function(data, model, entryInfo, baseURI, recognizeDates) { |
| return readPayloadMinimalObject(data, entryInfo, baseURI, model, recognizeDates); |
| }; |
| |
| var formatLiteral = function(value, type) { |
| /// <summary>Formats a value according to Uri literal format</summary> |
| /// <param name="value">Value to be formatted.</param> |
| /// <param name="type">Edm type of the value</param> |
| /// <returns type="string">Value after formatting</returns> |
| |
| value = "" + formatRowLiteral(value, type); |
| value = encodeURIComponent(value.replace("'", "''")); |
| switch ((type)) { |
| case "Edm.Binary": |
| return "X'" + value + "'"; |
| case "Edm.DateTime": |
| return "datetime" + "'" + value + "'"; |
| case "Edm.DateTimeOffset": |
| return "datetimeoffset" + "'" + value + "'"; |
| case "Edm.Decimal": |
| return value + "M"; |
| case "Edm.Guid": |
| return "guid" + "'" + value + "'"; |
| case "Edm.Int64": |
| return value + "L"; |
| case "Edm.Float": |
| return value + "f"; |
| case "Edm.Double": |
| return value + "D"; |
| case "Edm.Geography": |
| return "geography" + "'" + value + "'"; |
| case "Edm.Geometry": |
| return "geometry" + "'" + value + "'"; |
| case "Edm.Time": |
| return "time" + "'" + value + "'"; |
| case "Edm.String": |
| return "'" + value + "'"; |
| default: |
| return value; |
| } |
| }; |
| |
| var formatRowLiteral = function(value, type) { |
| switch (type) { |
| case "Edm.Binary": |
| return convertByteArrayToHexString(value); |
| default: |
| return value; |
| } |
| }; |
| |
| var convertDates = function(data, propertyName, type) { |
| if (type === 'Edm.Date') { |
| data[propertyName] = oDataUtils.parseDate(data[propertyName], true); |
| } else if (type === 'Edm.DateTimeOffset') { |
| data[propertyName] = oDataUtils.parseDateTimeOffset(data[propertyName], true); |
| } else if (type === 'Edm.Duration') { |
| data[propertyName] = oDataUtils.parseDuration(data[propertyName], true); |
| } else if (type === 'Edm.Time') { |
| data[propertyName] = oDataUtils.parseTime(data[propertyName], true); |
| } |
| }; |
| |
| var convertDatesNoEdm = function(data, propertyName, type) { |
| if (type === 'Date') { |
| data[propertyName] = oDataUtils.parseDate(data[propertyName], true); |
| } else if (type === 'DateTimeOffset') { |
| data[propertyName] = oDataUtils.parseDateTimeOffset(data[propertyName], true); |
| } else if (type === 'Duration') { |
| data[propertyName] = oDataUtils.parseDuration(data[propertyName], true); |
| } else if (type === 'Time') { |
| data[propertyName] = oDataUtils.parseTime(data[propertyName], true); |
| } |
| }; |
| |
| var checkProperties = function(data, objectInfoType, baseURI, model, recognizeDates) { |
| for (var name in data) { |
| if (name.indexOf("@") === -1) { |
| var curType = objectInfoType; |
| var propertyValue = data[name]; |
| var property = lookupProperty(curType.property, name); //TODO SK add check for parent type |
| |
| while ((property === null) && (curType.baseType !== undefined)) { |
| curType = lookupEntityType(curType.baseType, model); |
| property = lookupProperty(curType.property, name); |
| } |
| |
| if (isArray(propertyValue)) { |
| //data[name+'@odata.type'] = '#' + property.type; |
| if (isCollectionType(property.type)) { |
| addTypeColNoEdm(data, name, property.type.substring(11, property.type.length - 1)); |
| } else { |
| addTypeNoEdm(data, name, property.type); |
| } |
| |
| |
| for (var i = 0; i < propertyValue.length; i++) { |
| readPayloadMinimalComplexObject(propertyValue[i], property, baseURI, model, recognizeDates); |
| } |
| } else if (isObject(propertyValue) && (propertyValue !== null)) { |
| readPayloadMinimalComplexObject(propertyValue, property, baseURI, model, recognizeDates); |
| } else { |
| //data[name+'@odata.type'] = '#' + property.type; |
| addTypeNoEdm(data, name, property.type); |
| if (recognizeDates) { |
| convertDates(data, name, property.type); |
| } |
| } |
| } |
| } |
| }; |
| |
| var readPayloadMinimalComplexObject = function(data, property, baseURI, model, recognizeDates) { |
| var type = property.type; |
| if (isCollectionType(property.type)) { |
| type = property.type.substring(11, property.type.length - 1); |
| } |
| |
| //data['@odata.type'] = '#'+type; |
| addType(data, '', property.type); |
| |
| |
| var propertyType = lookupComplexType(type, model); |
| if (propertyType === null) { |
| return; //TODO check what to do if the type is not known e.g. type #GeometryCollection |
| } |
| |
| checkProperties(data, propertyType, baseURI, model, recognizeDates); |
| }; |
| |
| var readPayloadMinimalObject = function(data, objectInfo, baseURI, model, recognizeDates) { |
| //data['@odata.type'] = '#'+objectInfo.typeName; |
| addType(data, '', objectInfo.typeName); |
| |
| var keyType = objectInfo.type; |
| while ((defined(keyType)) && (keyType.key === undefined) && (keyType.baseType !== undefined)) { |
| keyType = lookupEntityType(keyType.baseType, model); |
| } |
| |
| //if ((keyType !== undefined) && (keyType.key !== undefined)) { |
| if (keyType.key !== undefined) { |
| var lastIdSegment = objectInfo.name + jsonGetEntryKey(data, keyType); |
| data['@odata.id'] = baseURI.substring(0, baseURI.lastIndexOf("$metadata")) + lastIdSegment; |
| data['@odata.editLink'] = lastIdSegment; |
| } |
| |
| var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata")); |
| //json ComputeUrisIfMissing(data, entryInfo, actualType, serviceURI, dataModel, baseTypeModel); |
| |
| checkProperties(data, objectInfo.type, baseURI, model, recognizeDates); |
| |
| return data; |
| }; |
| |
| var jsonSerializableMetadata = ["@odata.id", "@odata.type"]; |
| |
| var isJsonSerializableProperty = function(property) { |
| if (!property) { |
| return false; |
| } |
| |
| if (property.indexOf("@odata.") == -1) { |
| return true; |
| } |
| |
| var i, len; |
| for (i = 0, len = jsonSerializableMetadata.length; i < len; i++) { |
| var name = jsonSerializableMetadata[i]; |
| if (property.indexOf(name) != -1) { |
| return true; |
| } |
| } |
| |
| return false; |
| }; |
| |
| var jsonIsPrimitiveType = function(typeName) { |
| /// <summary>Determines whether a type name is a primitive type in a JSON payload.</summary> |
| /// <param name="typeName" type="String">Type name to test.</param> |
| /// <returns type="Boolean">True if the type name an EDM primitive type or an OData spatial type; false otherwise.</returns> |
| |
| return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || isGeometryEdmType(typeName); |
| }; |
| |
| |
| var jsonHandler = oDataHandler.handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION); |
| jsonHandler.recognizeDates = false; |
| |
| exports.jsonHandler = jsonHandler; |
| exports.jsonParser = jsonParser; |
| exports.jsonSerializer = jsonSerializer; |
| exports.parseJsonDateString = parseJsonDateString; |
| |
| }, { |
| "./../datajs.js": 4, |
| "./handler.js": 10, |
| "./utils.js": 14 |
| } |
| ], |
| 12: [ |
| function(require, module, exports) { |
| |
| |
| var utils = require('./../datajs.js').utils; |
| var oDSxml = require('./../datajs.js').xml; |
| var odataHandler = require('./handler.js'); |
| |
| // imports |
| var contains = utils.contains; |
| var normalizeURI = utils.normalizeURI; |
| var xmlAttributes = oDSxml.xmlAttributes; |
| var xmlChildElements = oDSxml.xmlChildElements; |
| var xmlFirstChildElement = oDSxml.xmlFirstChildElement; |
| var xmlInnerText = oDSxml.xmlInnerText; |
| var xmlLocalName = oDSxml.xmlLocalName; |
| var xmlNamespaceURI = oDSxml.xmlNamespaceURI; |
| var xmlNS = oDSxml.xmlNS; |
| var xmlnsNS = oDSxml.xmlnsNS; |
| var xmlParse = oDSxml.xmlParse; |
| |
| var ado = oDSxml.http + "docs.oasis-open.org/odata/"; // http://docs.oasis-open.org/odata/ |
| var adoDs = ado + "ns"; // http://docs.oasis-open.org/odata/ns |
| var edmxNs = adoDs + "/edmx"; // http://docs.oasis-open.org/odata/ns/edmx |
| var edmNs1 = adoDs + "/edm"; // http://docs.oasis-open.org/odata/ns/edm |
| var odataMetaXmlNs = adoDs + "/metadata"; // http://docs.oasis-open.org/odata/ns/metadata |
| var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION; |
| |
| var xmlMediaType = "application/xml"; |
| |
| var schemaElement = function(attributes, elements, text, ns) { |
| /// <summary>Creates an object that describes an element in an schema.</summary> |
| /// <param name="attributes" type="Array">List containing the names of the attributes allowed for this element.</param> |
| /// <param name="elements" type="Array">List containing the names of the child elements allowed for this element.</param> |
| /// <param name="text" type="Boolean">Flag indicating if the element's text value is of interest or not.</param> |
| /// <param name="ns" type="String">Namespace to which the element belongs to.</param> |
| /// <remarks> |
| /// If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times. |
| /// </remarks> |
| /// <returns type="Object">Object with attributes, elements, text, and ns fields.</returns> |
| |
| return { |
| attributes: attributes, |
| elements: elements, |
| text: text || false, |
| ns: ns |
| }; |
| }; |
| |
| // It's assumed that all elements may have Documentation children and Annotation elements. |
| // See http://docs.oasis-open.org/odata/odata/v4.0/cs01/part3-csdl/odata-v4.0-cs01-part3-csdl.html for a CSDL reference. |
| var schema = { |
| elements: { |
| Action: schemaElement( |
| /*attributes*/ |
| ["Name", "IsBound", "EntitySetPath"], |
| /*elements*/ |
| ["ReturnType", "Parameter*", "Annotation*"] |
| ), |
| ActionImport: schemaElement( |
| /*attributes*/ |
| ["Name", "Action", "EntitySet", "Annotation*"] |
| ), |
| Annotation: schemaElement( |
| /*attributes*/ |
| ["Term", "Qualifier", "Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "AnnotationPath", "NavigationPropertyPath", "Path", "PropertyPath", "UrlRef"], |
| /*elements*/ |
| ["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] |
| ), |
| AnnotationPath: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Annotations: schemaElement( |
| /*attributes*/ |
| ["Target", "Qualifier"], |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| Apply: schemaElement( |
| /*attributes*/ |
| ["Function"], |
| /*elements*/ |
| ["String*", "Path*", "LabeledElement*", "Annotation*"] |
| ), |
| And: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Or: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Not: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Eq: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Ne: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Gt: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Ge: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Lt: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Le: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Binary: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Bool: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Cast: schemaElement( |
| /*attributes*/ |
| ["Type"], |
| /*elements*/ |
| ["Path*", "Annotation*"] |
| ), |
| Collection: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| ["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*"] |
| ), |
| ComplexType: schemaElement( |
| /*attributes*/ |
| ["Name", "BaseType", "Abstract", "OpenType"], |
| /*elements*/ |
| ["Property*", "NavigationProperty*", "Annotation*"] |
| ), |
| Date: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| DateTimeOffset: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Decimal: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Duration: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| EntityContainer: schemaElement( |
| /*attributes*/ |
| ["Name", "Extends"], |
| /*elements*/ |
| ["EntitySet*", "Singleton*", "ActionImport*", "FunctionImport*", "Annotation*"] |
| ), |
| EntitySet: schemaElement( |
| /*attributes*/ |
| ["Name", "EntityType", "IncludeInServiceDocument"], |
| /*elements*/ |
| ["NavigationPropertyBinding*", "Annotation*"] |
| ), |
| EntityType: schemaElement( |
| /*attributes*/ |
| ["Name", "BaseType", "Abstract", "OpenType", "HasStream"], |
| /*elements*/ |
| ["Key*", "Property*", "NavigationProperty*", "Annotation*"] |
| ), |
| EnumMember: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| EnumType: schemaElement( |
| /*attributes*/ |
| ["Name", "UnderlyingType", "IsFlags"], |
| /*elements*/ |
| ["Member*"] |
| ), |
| Float: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Function: schemaElement( |
| /*attributes*/ |
| ["Name", "IsBound", "IsComposable", "EntitySetPath"], |
| /*elements*/ |
| ["ReturnType", "Parameter*", "Annotation*"] |
| ), |
| FunctionImport: schemaElement( |
| /*attributes*/ |
| ["Name", "Function", "EntitySet", "IncludeInServiceDocument", "Annotation*"] |
| ), |
| Guid: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| If: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| ["Path*", "String*", "Annotation*"] |
| ), |
| Int: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| IsOf: schemaElement( |
| /*attributes*/ |
| ["Type", "MaxLength", "Precision", "Scale", "Unicode", "SRID", "DefaultValue", "Annotation*"], |
| /*elements*/ |
| ["Path*"] |
| ), |
| Key: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| ["PropertyRef*"] |
| ), |
| LabeledElement: schemaElement( |
| /*attributes*/ |
| ["Name"], |
| /*elements*/ |
| ["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] |
| ), |
| LabeledElementReference: schemaElement( |
| /*attributes*/ |
| ["Term"], |
| /*elements*/ |
| ["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*"] |
| ), |
| Member: schemaElement( |
| /*attributes*/ |
| ["Name", "Value"], |
| /*element*/ |
| ["Annotation*"] |
| ), |
| NavigationProperty: schemaElement( |
| /*attributes*/ |
| ["Name", "Type", "Nullable", "Partner", "ContainsTarget"], |
| /*elements*/ |
| ["ReferentialConstraint*", "OnDelete*", "Annotation*"] |
| ), |
| NavigationPropertyBinding: schemaElement( |
| /*attributes*/ |
| ["Path", "Target"] |
| ), |
| NavigationPropertyPath: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Null: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| OnDelete: schemaElement( |
| /*attributes*/ |
| ["Action"], |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| Path: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Parameter: schemaElement( |
| /*attributes*/ |
| ["Name", "Type", "Nullable", "MaxLength", "Precision", "Scale", "SRID"], |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| Property: schemaElement( |
| /*attributes*/ |
| ["Name", "Type", "Nullable", "MaxLength", "Precision", "Scale", "Unicode", "SRID", "DefaultValue"], |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| PropertyPath: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| PropertyRef: schemaElement( |
| /*attributes*/ |
| ["Name", "Alias"] |
| ), |
| PropertyValue: schemaElement( |
| /*attributes*/ |
| ["Property", "Path"], |
| /*elements*/ |
| ["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] |
| ), |
| Record: schemaElement( |
| /*attributes*/ |
| null, |
| /*Elements*/ |
| ["PropertyValue*", "Property*", "Annotation*"] |
| ), |
| ReferentialConstraint: schemaElement( |
| /*attributes*/ |
| ["Property", "ReferencedProperty", "Annotation*"] |
| ), |
| ReturnType: schemaElement( |
| /*attributes*/ |
| ["Type", "Nullable", "MaxLength", "Precision", "Scale", "SRID"] |
| ), |
| String: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| Schema: schemaElement( |
| /*attributes*/ |
| ["Namespace", "Alias"], |
| /*elements*/ |
| ["Action*", "Annotations*", "Annotation*", "ComplexType*", "EntityContainer", "EntityType*", "EnumType*", "Function*", "Term*", "TypeDefinition*", "Annotation*"] |
| ), |
| Singleton: schemaElement( |
| /*attributes*/ |
| ["Name", "Type"], |
| /*elements*/ |
| ["NavigationPropertyBinding*", "Annotation*"] |
| ), |
| Term: schemaElement( |
| /*attributes*/ |
| ["Name", "Type", "BaseTerm", "DefaultValue ", "AppliesTo", "Nullable", "MaxLength", "Precision", "Scale", "SRID"], |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| TimeOfDay: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| null, |
| /*text*/ |
| true |
| ), |
| TypeDefinition: schemaElement( |
| /*attributes*/ |
| ["Name", "UnderlyingType", "MaxLength", "Unicode", "Precision", "Scale", "SRID"], |
| /*elements*/ |
| ["Annotation*"] |
| ), |
| UrlRef: schemaElement( |
| /*attributes*/ |
| null, |
| /*elements*/ |
| ["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] |
| ), |
| |
| // See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference. |
| Edmx: schemaElement( |
| /*attributes*/ |
| ["Version"], |
| /*elements*/ |
| ["DataServices", "Reference*"], |
| /*text*/ |
| false, |
| /*ns*/ |
| edmxNs |
| ), |
| DataServices: schemaElement( |
| /*attributes*/ |
| ["m:MaxDataServiceVersion", "m:DataServiceVersion"], |
| /*elements*/ |
| ["Schema*"], |
| /*text*/ |
| false, |
| /*ns*/ |
| edmxNs |
| ), |
| Reference: schemaElement( |
| /*attributes*/ |
| ["Uri"], |
| /*elements*/ |
| ["Include*", "IncludeAnnotations*", "Annotation*"] |
| ), |
| Include: schemaElement( |
| /*attributes*/ |
| ["Namespace", "Alias"] |
| ), |
| IncludeAnnotations: schemaElement( |
| /*attributes*/ |
| ["TermNamespace", "Qualifier", "TargetNamespace"] |
| ) |
| } |
| }; |
| |
| |
| var scriptCase = function(text) { |
| /// <summary>Converts a Pascal-case identifier into a camel-case identifier.</summary> |
| /// <param name="text" type="String">Text to convert.</param> |
| /// <returns type="String">Converted text.</returns> |
| /// <remarks>If the text starts with multiple uppercase characters, it is left as-is.</remarks> |
| |
| if (!text) { |
| return text; |
| } |
| |
| if (text.length > 1) { |
| var firstTwo = text.substr(0, 2); |
| if (firstTwo === firstTwo.toUpperCase()) { |
| return text; |
| } |
| |
| return text.charAt(0).toLowerCase() + text.substr(1); |
| } |
| |
| return text.charAt(0).toLowerCase(); |
| }; |
| |
| var getChildSchema = function(parentSchema, candidateName) { |
| /// <summary>Gets the schema node for the specified element.</summary> |
| /// <param name="parentSchema" type="Object">Schema of the parent XML node of 'element'.</param> |
| /// <param name="candidateName">XML element name to consider.</param> |
| /// <returns type="Object">The schema that describes the specified element; null if not found.</returns> |
| |
| var elements = parentSchema.elements; |
| if (!elements) { |
| return null; |
| } |
| |
| var i, len; |
| for (i = 0, len = elements.length; i < len; i++) { |
| var elementName = elements[i]; |
| var multipleElements = false; |
| if (elementName.charAt(elementName.length - 1) === "*") { |
| multipleElements = true; |
| elementName = elementName.substr(0, elementName.length - 1); |
| } |
| |
| if (candidateName === elementName) { |
| var propertyName = scriptCase(elementName); |
| return { |
| isArray: multipleElements, |
| propertyName: propertyName |
| }; |
| } |
| } |
| |
| return null; |
| }; |
| |
| var isEdmNamespace = function(nsURI) { |
| /// <summary>Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.</summary> |
| /// <param name="nsURI" type="String">Namespace URI to check.</param> |
| /// <returns type="Boolean">true if nsURI is a known CSDL namespace; false otherwise.</returns> |
| |
| return nsURI === edmNs1; |
| }; |
| |
| var parseConceptualModelElement = function(element) { |
| /// <summary>Parses a CSDL document.</summary> |
| /// <param name="element">DOM element to parse.</param> |
| /// <returns type="Object">An object describing the parsed element.</returns> |
| |
| var localName = xmlLocalName(element); |
| var nsURI = xmlNamespaceURI(element); |
| var elementSchema = schema.elements[localName]; |
| if (!elementSchema) { |
| return null; |
| } |
| |
| if (elementSchema.ns) { |
| if (nsURI !== elementSchema.ns) { |
| return null; |
| } |
| } else if (!isEdmNamespace(nsURI)) { |
| return null; |
| } |
| |
| var item = {}; |
| var attributes = elementSchema.attributes || []; |
| xmlAttributes(element, function(attribute) { |
| |
| var localName = xmlLocalName(attribute); |
| var nsURI = xmlNamespaceURI(attribute); |
| var value = attribute.value; |
| |
| // Don't do anything with xmlns attributes. |
| if (nsURI === xmlnsNS) { |
| return; |
| } |
| |
| // Currently, only m: for metadata is supported as a prefix in the internal schema table, |
| // un-prefixed element names imply one a CSDL element. |
| var schemaName = null; |
| var handled = false; |
| if (isEdmNamespace(nsURI) || nsURI === null) { |
| schemaName = ""; |
| } else if (nsURI === odataMetaXmlNs) { |
| schemaName = "m:"; |
| } |
| |
| if (schemaName !== null) { |
| schemaName += localName; |
| |
| if (contains(attributes, schemaName)) { |
| item[scriptCase(localName)] = value; |
| } |
| } |
| |
| }); |
| |
| xmlChildElements(element, function(child) { |
| var localName = xmlLocalName(child); |
| var childSchema = getChildSchema(elementSchema, localName); |
| if (childSchema) { |
| if (childSchema.isArray) { |
| var arr = item[childSchema.propertyName]; |
| if (!arr) { |
| arr = []; |
| item[childSchema.propertyName] = arr; |
| } |
| arr.push(parseConceptualModelElement(child)); |
| } else { |
| item[childSchema.propertyName] = parseConceptualModelElement(child); |
| } |
| } |
| }); |
| |
| if (elementSchema.text) { |
| item.text = xmlInnerText(element); |
| } |
| |
| return item; |
| }; |
| |
| var metadataParser = function(handler, text) { |
| /// <summary>Parses a metadata document.</summary> |
| /// <param name="handler">This handler.</param> |
| /// <param name="text" type="String">Metadata text.</param> |
| /// <returns>An object representation of the conceptual model.</returns> |
| |
| var doc = xmlParse(text); |
| var root = xmlFirstChildElement(doc); |
| return parseConceptualModelElement(root) || undefined; |
| }; |
| |
| exports.metadataHandler = odataHandler.handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION); |
| |
| exports.schema = schema; |
| exports.scriptCase = scriptCase; |
| exports.getChildSchema = getChildSchema; |
| exports.parseConceptualModelElement = parseConceptualModelElement; |
| exports.metadataParser = metadataParser; |
| |
| }, { |
| "./../datajs.js": 4, |
| "./handler.js": 10 |
| } |
| ], |
| 13: [ |
| function(require, module, exports) { |
| |
| |
| var utils = require('./../datajs.js').utils; |
| // Imports. |
| |
| var defined = utils.defined; |
| var delay = utils.delay; |
| |
| var ticks = 0; |
| |
| var canUseJSONP = function(request) { |
| /// <summary> |
| /// Checks whether the specified request can be satisfied with a JSONP request. |
| /// </summary> |
| /// <param name="request">Request object to check.</param> |
| /// <returns type="Boolean">true if the request can be satisfied; false otherwise.</returns> |
| |
| // Requests that 'degrade' without changing their meaning by going through JSONP |
| // are considered usable. |
| // |
| // We allow data to come in a different format, as the servers SHOULD honor the Accept |
| // request but may in practice return content with a different MIME type. |
| if (request.method && request.method !== "GET") { |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| var createIFrame = function(url) { |
| /// <summary>Creates an IFRAME tag for loading the JSONP script</summary> |
| /// <param name="url" type="String">The source URL of the script</param> |
| /// <returns type="HTMLElement">The IFRAME tag</returns> |
| var iframe = window.document.createElement("IFRAME"); |
| iframe.style.display = "none"; |
| |
| var attributeEncodedUrl = url.replace(/&/g, "&").replace(/"/g, """).replace(/\</g, "<"); |
| var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>"; |
| |
| var body = window.document.getElementsByTagName("BODY")[0]; |
| body.appendChild(iframe); |
| |
| writeHtmlToIFrame(iframe, html); |
| return iframe; |
| }; |
| |
| var createXmlHttpRequest = function() { |
| /// <summary>Creates a XmlHttpRequest object.</summary> |
| /// <returns type="XmlHttpRequest">XmlHttpRequest object.</returns> |
| if (window.XMLHttpRequest) { |
| return new window.XMLHttpRequest(); |
| } |
| var exception; |
| if (window.ActiveXObject) { |
| try { |
| return new window.ActiveXObject("Msxml2.XMLHTTP.6.0"); |
| } catch (_) { |
| try { |
| return new window.ActiveXObject("Msxml2.XMLHTTP.3.0"); |
| } catch (e) { |
| exception = e; |
| } |
| } |
| } else { |
| exception = { |
| message: "XMLHttpRequest not supported" |
| }; |
| } |
| throw exception; |
| }; |
| |
| var isAbsoluteUrl = function(url) { |
| /// <summary>Checks whether the specified URL is an absolute URL.</summary> |
| /// <param name="url" type="String">URL to check.</param> |
| /// <returns type="Boolean">true if the url is an absolute URL; false otherwise.</returns> |
| |
| return url.indexOf("http://") === 0 || |
| url.indexOf("https://") === 0 || |
| url.indexOf("file://") === 0; |
| }; |
| |
| var isLocalUrl = function(url) { |
| /// <summary>Checks whether the specified URL is local to the current context.</summary> |
| /// <param name="url" type="String">URL to check.</param> |
| /// <returns type="Boolean">true if the url is a local URL; false otherwise.</returns> |
| |
| if (!isAbsoluteUrl(url)) { |
| return true; |
| } |
| |
| // URL-embedded username and password will not be recognized as same-origin URLs. |
| var location = window.location; |
| var locationDomain = location.protocol + "//" + location.host + "/"; |
| return (url.indexOf(locationDomain) === 0); |
| }; |
| |
| var removeCallback = function(name, tick) { |
| /// <summary>Removes a callback used for a JSONP request.</summary> |
| /// <param name="name" type="String">Function name to remove.</param> |
| /// <param name="tick" type="Number">Tick count used on the callback.</param> |
| try { |
| delete window[name]; |
| } catch (err) { |
| window[name] = undefined; |
| if (tick === ticks - 1) { |
| ticks -= 1; |
| } |
| } |
| }; |
| |
| var removeIFrame = function(iframe) { |
| /// <summary>Removes an iframe.</summary> |
| /// <param name="iframe" type="Object">The iframe to remove.</param> |
| /// <returns type="Object">Null value to be assigned to iframe reference.</returns> |
| if (iframe) { |
| writeHtmlToIFrame(iframe, ""); |
| iframe.parentNode.removeChild(iframe); |
| } |
| |
| return null; |
| }; |
| |
| var readResponseHeaders = function(xhr, headers) { |
| /// <summary>Reads response headers into array.</summary> |
| /// <param name="xhr" type="XMLHttpRequest">HTTP request with response available.</param> |
| /// <param name="headers" type="Array">Target array to fill with name/value pairs.</param> |
| |
| var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/); |
| var i, len; |
| for (i = 0, len = responseHeaders.length; i < len; i++) { |
| if (responseHeaders[i]) { |
| var header = responseHeaders[i].split(": "); |
| headers[header[0]] = header[1]; |
| } |
| } |
| }; |
| |
| var writeHtmlToIFrame = function(iframe, html) { |
| /// <summary>Writes HTML to an IFRAME document.</summary> |
| /// <param name="iframe" type="HTMLElement">The IFRAME element to write to.</param> |
| /// <param name="html" type="String">The HTML to write.</param> |
| var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document; |
| frameDocument.open(); |
| frameDocument.write(html); |
| frameDocument.close(); |
| }; |
| |
| exports.defaultHttpClient = { |
| callbackParameterName: "$callback", |
| |
| formatQueryString: "$format=json", |
| |
| enableJsonpCallback: false, |
| |
| request: function(request, success, error) { |
| /// <summary>Performs a network request.</summary> |
| /// <param name="request" type="Object">Request description.</request> |
| /// <param name="success" type="Function">Success callback with the response object.</param> |
| /// <param name="error" type="Function">Error callback with an error object.</param> |
| /// <returns type="Object">Object with an 'abort' method for the operation.</returns> |
| |
| var result = {}; |
| var xhr = null; |
| var done = false; |
| var iframe; |
| |
| result.abort = function() { |
| iframe = removeIFrame(iframe); |
| if (done) { |
| return; |
| } |
| |
| done = true; |
| if (xhr) { |
| xhr.abort(); |
| xhr = null; |
| } |
| |
| error({ |
| message: "Request aborted" |
| }); |
| }; |
| |
| var handleTimeout = function() { |
| iframe = removeIFrame(iframe); |
| if (!done) { |
| done = true; |
| xhr = null; |
| error({ |
| message: "Request timed out" |
| }); |
| } |
| }; |
| |
| var name; |
| var url = request.requestUri; |
| var enableJsonpCallback = defined(request.enableJsonpCallback, this.enableJsonpCallback); |
| var callbackParameterName = defined(request.callbackParameterName, this.callbackParameterName); |
| var formatQueryString = defined(request.formatQueryString, this.formatQueryString); |
| if (!enableJsonpCallback || isLocalUrl(url)) { |
| |
| xhr = createXmlHttpRequest(); |
| xhr.onreadystatechange = function() { |
| if (done || xhr === null || xhr.readyState !== 4) { |
| return; |
| } |
| |
| // Workaround for XHR behavior on IE. |
| var statusText = xhr.statusText; |
| var statusCode = xhr.status; |
| if (statusCode === 1223) { |
| statusCode = 204; |
| statusText = "No Content"; |
| } |
| |
| var headers = []; |
| readResponseHeaders(xhr, headers); |
| |
| var response = { |
| requestUri: url, |
| statusCode: statusCode, |
| statusText: statusText, |
| headers: headers, |
| body: xhr.responseText |
| }; |
| |
| done = true; |
| xhr = null; |
| if (statusCode >= 200 && statusCode <= 299) { |
| success(response); |
| } else { |
| error({ |
| message: "HTTP request failed", |
| request: request, |
| response: response |
| }); |
| } |
| }; |
| |
| xhr.open(request.method || "GET", url, true, request.user, request.password); |
| |
| // Set the name/value pairs. |
| if (request.headers) { |
| for (name in request.headers) { |
| xhr.setRequestHeader(name, request.headers[name]); |
| } |
| } |
| |
| // Set the timeout if available. |
| if (request.timeoutMS) { |
| xhr.timeout = request.timeoutMS; |
| xhr.ontimeout = handleTimeout; |
| } |
| |
| xhr.send(request.body); |
| } else { |
| if (!canUseJSONP(request)) { |
| throw { |
| message: "Request is not local and cannot be done through JSONP." |
| }; |
| } |
| |
| var tick = ticks; |
| ticks += 1; |
| var tickText = tick.toString(); |
| var succeeded = false; |
| var timeoutId; |
| name = "handleJSONP_" + tickText; |
| window[name] = function(data) { |
| iframe = removeIFrame(iframe); |
| if (!done) { |
| succeeded = true; |
| window.clearTimeout(timeoutId); |
| removeCallback(name, tick); |
| |
| // Workaround for IE8 and IE10 below where trying to access data.constructor after the IFRAME has been removed |
| // throws an "unknown exception" |
| if (window.ActiveXObject) { |
| data = window.JSON.parse(window.JSON.stringify(data)); |
| } |
| |
| |
| var headers; |
| if (!formatQueryString || formatQueryString == "$format=json") { |
| headers = { |
| "Content-Type": "application/json;odata.metadata=minimal", |
| "OData-Version": "4.0" |
| }; |
| } else { |
| // the formatQueryString should be in the format of "$format=xxx", xxx should be one of the application/json;odata.metadata=minimal(none or full) |
| // set the content-type with the string xxx which stars from index 8. |
| headers = { |
| "Content-Type": formatQueryString.substring(8), |
| "OData-Version": "4.0" |
| }; |
| } |
| |
| // Call the success callback in the context of the parent window, instead of the IFRAME |
| delay(function() { |
| removeIFrame(iframe); |
| success({ |
| body: data, |
| statusCode: 200, |
| headers: headers |
| }); |
| }); |
| } |
| }; |
| |
| // Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000. |
| var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000; |
| timeoutId = window.setTimeout(handleTimeout, timeoutMS); |
| |
| var queryStringParams = callbackParameterName + "=parent." + name; |
| if (formatQueryString) { |
| queryStringParams += "&" + formatQueryString; |
| } |
| |
| var qIndex = url.indexOf("?"); |
| if (qIndex === -1) { |
| url = url + "?" + queryStringParams; |
| } else if (qIndex === url.length - 1) { |
| url = url + queryStringParams; |
| } else { |
| url = url + "&" + queryStringParams; |
| } |
| |
| iframe = createIFrame(url); |
| } |
| |
| return result; |
| } |
| }; |
| |
| exports.canUseJSONP = canUseJSONP; |
| exports.isAbsoluteUrl = isAbsoluteUrl; |
| exports.isLocalUrl = isLocalUrl; |
| |
| |
| }, { |
| "./../datajs.js": 4 |
| } |
| ], |
| 14: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| /** @module odata/utils */ |
| |
| var utils = require('./../datajs.js').utils; |
| |
| // Imports |
| var assigned = utils.assigned; |
| var contains = utils.contains; |
| var find = utils.find; |
| var isArray = utils.isArray; |
| var isDate = utils.isDate; |
| var isObject = utils.isObject; |
| var parseInt10 = utils.parseInt10; |
| |
| |
| /** Gets the type name of a data item value that belongs to a feed, an entry, a complex type property, or a collection property |
| * @param {string} value - Value of the data item from which the type name is going to be retrieved. |
| * @param {object} [metadata] - Object containing metadata about the data tiem. |
| * @returns {string} Data item type name; null if the type name cannot be found within the value or the metadata |
| * This function will first try to get the type name from the data item's value itself if it is an object with a __metadata property; otherwise |
| * it will try to recover it from the metadata. If both attempts fail, it will return null. |
| */ |
| var dataItemTypeName = function(value, metadata) { |
| var valueTypeName = ((value && value.__metadata) || {}).type; |
| return valueTypeName || (metadata ? metadata.type : null); |
| }; |
| |
| var EDM = "Edm."; |
| var EDM_BINARY = EDM + "Binary"; |
| var EDM_BOOLEAN = EDM + "Boolean"; |
| var EDM_BYTE = EDM + "Byte"; |
| var EDM_DATETIME = EDM + "DateTime"; |
| var EDM_DATETIMEOFFSET = EDM + "DateTimeOffset"; |
| var EDM_DECIMAL = EDM + "Decimal"; |
| var EDM_DOUBLE = EDM + "Double"; |
| var EDM_GUID = EDM + "Guid"; |
| var EDM_INT16 = EDM + "Int16"; |
| var EDM_INT32 = EDM + "Int32"; |
| var EDM_INT64 = EDM + "Int64"; |
| var EDM_SBYTE = EDM + "SByte"; |
| var EDM_SINGLE = EDM + "Single"; |
| var EDM_STRING = EDM + "String"; |
| var EDM_TIME = EDM + "Time"; |
| |
| var GEOGRAPHY = "Geography"; |
| var EDM_GEOGRAPHY = EDM + GEOGRAPHY; |
| var EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY + "Point"; |
| var EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY + "LineString"; |
| var EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY + "Polygon"; |
| var EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY + "Collection"; |
| var EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY + "MultiPolygon"; |
| var EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY + "MultiLineString"; |
| var EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY + "MultiPoint"; |
| |
| var GEOGRAPHY_POINT = GEOGRAPHY + "Point"; |
| var GEOGRAPHY_LINESTRING = GEOGRAPHY + "LineString"; |
| var GEOGRAPHY_POLYGON = GEOGRAPHY + "Polygon"; |
| var GEOGRAPHY_COLLECTION = GEOGRAPHY + "Collection"; |
| var GEOGRAPHY_MULTIPOLYGON = GEOGRAPHY + "MultiPolygon"; |
| var GEOGRAPHY_MULTILINESTRING = GEOGRAPHY + "MultiLineString"; |
| var GEOGRAPHY_MULTIPOINT = GEOGRAPHY + "MultiPoint"; |
| |
| var GEOMETRY = "Geometry"; |
| var EDM_GEOMETRY = EDM + GEOMETRY; |
| var EDM_GEOMETRY_POINT = EDM_GEOMETRY + "Point"; |
| var EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY + "LineString"; |
| var EDM_GEOMETRY_POLYGON = EDM_GEOMETRY + "Polygon"; |
| var EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY + "Collection"; |
| var EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY + "MultiPolygon"; |
| var EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY + "MultiLineString"; |
| var EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY + "MultiPoint"; |
| |
| var GEOMETRY_POINT = GEOMETRY + "Point"; |
| var GEOMETRY_LINESTRING = GEOMETRY + "LineString"; |
| var GEOMETRY_POLYGON = GEOMETRY + "Polygon"; |
| var GEOMETRY_COLLECTION = GEOMETRY + "Collection"; |
| var GEOMETRY_MULTIPOLYGON = GEOMETRY + "MultiPolygon"; |
| var GEOMETRY_MULTILINESTRING = GEOMETRY + "MultiLineString"; |
| var GEOMETRY_MULTIPOINT = GEOMETRY + "MultiPoint"; |
| |
| var GEOJSON_POINT = "Point"; |
| var GEOJSON_LINESTRING = "LineString"; |
| var GEOJSON_POLYGON = "Polygon"; |
| var GEOJSON_MULTIPOINT = "MultiPoint"; |
| var GEOJSON_MULTILINESTRING = "MultiLineString"; |
| var GEOJSON_MULTIPOLYGON = "MultiPolygon"; |
| var GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection"; |
| |
| var primitiveEdmTypes = [ |
| EDM_STRING, |
| EDM_INT32, |
| EDM_INT64, |
| EDM_BOOLEAN, |
| EDM_DOUBLE, |
| EDM_SINGLE, |
| EDM_DATETIME, |
| EDM_DATETIMEOFFSET, |
| EDM_TIME, |
| EDM_DECIMAL, |
| EDM_GUID, |
| EDM_BYTE, |
| EDM_INT16, |
| EDM_SBYTE, |
| EDM_BINARY |
| ]; |
| |
| var geometryEdmTypes = [ |
| EDM_GEOMETRY, |
| EDM_GEOMETRY_POINT, |
| EDM_GEOMETRY_LINESTRING, |
| EDM_GEOMETRY_POLYGON, |
| EDM_GEOMETRY_COLLECTION, |
| EDM_GEOMETRY_MULTIPOLYGON, |
| EDM_GEOMETRY_MULTILINESTRING, |
| EDM_GEOMETRY_MULTIPOINT |
| ]; |
| |
| var geometryTypes = [ |
| GEOMETRY, |
| GEOMETRY_POINT, |
| GEOMETRY_LINESTRING, |
| GEOMETRY_POLYGON, |
| GEOMETRY_COLLECTION, |
| GEOMETRY_MULTIPOLYGON, |
| GEOMETRY_MULTILINESTRING, |
| GEOMETRY_MULTIPOINT |
| ]; |
| |
| var geographyEdmTypes = [ |
| EDM_GEOGRAPHY, |
| EDM_GEOGRAPHY_POINT, |
| EDM_GEOGRAPHY_LINESTRING, |
| EDM_GEOGRAPHY_POLYGON, |
| EDM_GEOGRAPHY_COLLECTION, |
| EDM_GEOGRAPHY_MULTIPOLYGON, |
| EDM_GEOGRAPHY_MULTILINESTRING, |
| EDM_GEOGRAPHY_MULTIPOINT |
| ]; |
| |
| var geographyTypes = [ |
| GEOGRAPHY, |
| GEOGRAPHY_POINT, |
| GEOGRAPHY_LINESTRING, |
| GEOGRAPHY_POLYGON, |
| GEOGRAPHY_COLLECTION, |
| GEOGRAPHY_MULTIPOLYGON, |
| GEOGRAPHY_MULTILINESTRING, |
| GEOGRAPHY_MULTIPOINT |
| ]; |
| |
| var forEachSchema = function(metadata, callback) { |
| /// <summary>Invokes a function once per schema in metadata.</summary> |
| /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param> |
| /// <param name="callback" type="Function">Callback function to invoke once per schema.</param> |
| /// <returns> |
| /// The first truthy value to be returned from the callback; null or the last falsy value otherwise. |
| /// </returns> |
| |
| if (!metadata) { |
| return null; |
| } |
| |
| if (isArray(metadata)) { |
| var i, len, result; |
| for (i = 0, len = metadata.length; i < len; i++) { |
| result = forEachSchema(metadata[i], callback); |
| if (result) { |
| return result; |
| } |
| } |
| |
| return null; |
| } else { |
| if (metadata.dataServices) { |
| return forEachSchema(metadata.dataServices.schema, callback); |
| } |
| |
| return callback(metadata); |
| } |
| }; |
| |
| var formatMilliseconds = function(ms, ns) { |
| /// <summary>Formats a millisecond and a nanosecond value into a single string.</summary> |
| /// <param name="ms" type="Number" mayBeNull="false">Number of milliseconds to format.</param> |
| /// <param name="ns" type="Number" mayBeNull="false">Number of nanoseconds to format.</param> |
| /// <returns type="String">Formatted text.</returns> |
| /// <remarks>If the value is already as string it's returned as-is.</remarks> |
| |
| // Avoid generating milliseconds if not necessary. |
| if (ms === 0) { |
| ms = ""; |
| } else { |
| ms = "." + formatNumberWidth(ms.toString(), 3); |
| } |
| if (ns > 0) { |
| if (ms === "") { |
| ms = ".000"; |
| } |
| ms += formatNumberWidth(ns.toString(), 4); |
| } |
| return ms; |
| }; |
| |
| var formatDateTimeOffsetJSON = function(value) { |
| return "\/Date(" + value.getTime() + ")\/"; |
| }; |
| |
| var formatDateTimeOffset = function(value) { |
| /// <summary>Formats a DateTime or DateTimeOffset value a string.</summary> |
| /// <param name="value" type="Date" mayBeNull="false">Value to format.</param> |
| /// <returns type="String">Formatted text.</returns> |
| /// <remarks>If the value is already as string it's returned as-is.</remarks> |
| |
| if (typeof value === "string") { |
| return value; |
| } |
| |
| var hasOffset = isDateTimeOffset(value); |
| var offset = getCanonicalTimezone(value.__offset); |
| if (hasOffset && offset !== "Z") { |
| // We're about to change the value, so make a copy. |
| value = new Date(value.valueOf()); |
| |
| var timezone = parseTimezone(offset); |
| var hours = value.getUTCHours() + (timezone.d * timezone.h); |
| var minutes = value.getUTCMinutes() + (timezone.d * timezone.m); |
| |
| value.setUTCHours(hours, minutes); |
| } else if (!hasOffset) { |
| // Don't suffix a 'Z' for Edm.DateTime values. |
| offset = ""; |
| } |
| |
| var year = value.getUTCFullYear(); |
| var month = value.getUTCMonth() + 1; |
| var sign = ""; |
| if (year <= 0) { |
| year = -(year - 1); |
| sign = "-"; |
| } |
| |
| var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns); |
| |
| return sign + |
| formatNumberWidth(year, 4) + "-" + |
| formatNumberWidth(month, 2) + "-" + |
| formatNumberWidth(value.getUTCDate(), 2) + "T" + |
| formatNumberWidth(value.getUTCHours(), 2) + ":" + |
| formatNumberWidth(value.getUTCMinutes(), 2) + ":" + |
| formatNumberWidth(value.getUTCSeconds(), 2) + |
| ms + offset; |
| }; |
| |
| var formatDuration = function(value) { |
| /// <summary>Converts a duration to a string in xsd:duration format.</summary> |
| /// <param name="value" type="Object">Object with ms and __edmType properties.</param> |
| /// <returns type="String">String representation of the time object in xsd:duration format.</returns> |
| |
| var ms = value.ms; |
| |
| var sign = ""; |
| if (ms < 0) { |
| sign = "-"; |
| ms = -ms; |
| } |
| |
| var days = Math.floor(ms / 86400000); |
| ms -= 86400000 * days; |
| var hours = Math.floor(ms / 3600000); |
| ms -= 3600000 * hours; |
| var minutes = Math.floor(ms / 60000); |
| ms -= 60000 * minutes; |
| var seconds = Math.floor(ms / 1000); |
| ms -= seconds * 1000; |
| |
| return sign + "P" + |
| formatNumberWidth(days, 2) + "DT" + |
| formatNumberWidth(hours, 2) + "H" + |
| formatNumberWidth(minutes, 2) + "M" + |
| formatNumberWidth(seconds, 2) + |
| formatMilliseconds(ms, value.ns) + "S"; |
| }; |
| |
| var formatNumberWidth = function(value, width, append) { |
| /// <summary>Formats the specified value to the given width.</summary> |
| /// <param name="value" type="Number">Number to format (non-negative).</param> |
| /// <param name="width" type="Number">Minimum width for number.</param> |
| /// <param name="append" type="Boolean">Flag indicating if the value is padded at the beginning (false) or at the end (true).</param> |
| /// <returns type="String">Text representation.</returns> |
| var result = value.toString(10); |
| while (result.length < width) { |
| if (append) { |
| result += "0"; |
| } else { |
| result = "0" + result; |
| } |
| } |
| |
| return result; |
| }; |
| |
| var getCanonicalTimezone = function(timezone) { |
| /// <summary>Gets the canonical timezone representation.</summary> |
| /// <param name="timezone" type="String">Timezone representation.</param> |
| /// <returns type="String">An 'Z' string if the timezone is absent or 0; the timezone otherwise.</returns> |
| |
| return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone; |
| }; |
| |
| var getCollectionType = function(typeName) { |
| /// <summary>Gets the type of a collection type name.</summary> |
| /// <param name="typeName" type="String">Type name of the collection.</param> |
| /// <returns type="String">Type of the collection; null if the type name is not a collection type.</returns> |
| |
| if (typeof typeName === "string") { |
| var end = typeName.indexOf(")", 10); |
| if (typeName.indexOf("Collection(") === 0 && end > 0) { |
| return typeName.substring(11, end); |
| } |
| } |
| return null; |
| }; |
| |
| var invokeRequest = function(request, success, error, handler, httpClient, context) { |
| /// <summary>Sends a request containing OData payload to a server.</summary> |
| /// <param name="request">Object that represents the request to be sent..</param> |
| /// <param name="success">Callback for a successful read operation.</param> |
| /// <param name="error">Callback for handling errors.</param> |
| /// <param name="handler">Handler for data serialization.</param> |
| /// <param name="httpClient">HTTP client layer.</param> |
| /// <param name="context">Context used for processing the request</param> |
| |
| return httpClient.request(request, function(response) { |
| try { |
| if (response.headers) { |
| normalizeHeaders(response.headers); |
| } |
| |
| if (response.data === undefined && response.statusCode !== 204) { |
| handler.read(response, context); |
| } |
| } catch (err) { |
| if (err.request === undefined) { |
| err.request = request; |
| } |
| if (err.response === undefined) { |
| err.response = response; |
| } |
| error(err); |
| return; |
| } |
| // errors in success handler for sync requests result in error handler calls. So here we fix this. |
| try { |
| success(response.data, response); |
| } catch (err) { |
| err.bIsSuccessHandlerError = true; |
| throw err; |
| } |
| }, error); |
| }; |
| |
| var isBatch = function(value) { |
| /// <summary>Tests whether a value is a batch object in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <returns type="Boolean">True is the value is a batch object; false otherwise.</returns> |
| |
| return isComplex(value) && isArray(value.__batchRequests); |
| }; |
| |
| // Regular expression used for testing and parsing for a collection type. |
| var collectionTypeRE = /Collection\((.*)\)/; |
| |
| var isCollection = function(value, typeName) { |
| /// <summary>Tests whether a value is a collection value in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param> |
| /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns> |
| |
| var colData = value && value.results || value; |
| return !!colData && |
| (isCollectionType(typeName)) || |
| (!typeName && isArray(colData) && !isComplex(colData[0])); |
| }; |
| |
| var isCollectionType = function(typeName) { |
| /// <summary>Checks whether the specified type name is a collection type.</summary> |
| /// <param name="typeName" type="String">Name of type to check.</param> |
| /// <returns type="Boolean">True if the type is the name of a collection type; false otherwise.</returns> |
| return collectionTypeRE.test(typeName); |
| }; |
| |
| var isComplex = function(value) { |
| /// <summary>Tests whether a value is a complex type value in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <returns type="Boolean">True is the value is a complex type value; false otherwise.</returns> |
| |
| return !!value && |
| isObject(value) && |
| !isArray(value) && |
| !isDate(value); |
| }; |
| |
| var isDateTimeOffset = function(value) { |
| /// <summary>Checks whether a Date object is DateTimeOffset value</summary> |
| /// <param name="value" type="Date" mayBeNull="false">Value to check.</param> |
| /// <returns type="Boolean">true if the value is a DateTimeOffset, false otherwise.</returns> |
| return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset)); |
| }; |
| |
| var isDeferred = function(value) { |
| /// <summary>Tests whether a value is a deferred navigation property in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <returns type="Boolean">True is the value is a deferred navigation property; false otherwise.</returns> |
| |
| if (!value && !isComplex(value)) { |
| return false; |
| } |
| var metadata = value.__metadata || {}; |
| var deferred = value.__deferred || {}; |
| return !metadata.type && !!deferred.uri; |
| }; |
| |
| var isEntry = function(value) { |
| /// <summary>Tests whether a value is an entry object in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <returns type="Boolean">True is the value is an entry object; false otherwise.</returns> |
| |
| return isComplex(value) && value.__metadata && "uri" in value.__metadata; |
| }; |
| |
| var isFeed = function(value, typeName) { |
| /// <summary>Tests whether a value is a feed value in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param> |
| /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns> |
| |
| var feedData = value && value.results || value; |
| return isArray(feedData) && ( |
| (!isCollectionType(typeName)) && |
| (isComplex(feedData[0])) |
| ); |
| }; |
| |
| var isGeographyEdmType = function(typeName) { |
| /// <summary>Checks whether the specified type name is a geography EDM type.</summary> |
| /// <param name="typeName" type="String">Name of type to check.</param> |
| /// <returns type="Boolean">True if the type is a geography EDM type; false otherwise.</returns> |
| |
| //check with edm |
| var ret = contains(geographyEdmTypes, typeName) || |
| (typeName.indexOf('.') === -1 && contains(geographyTypes, typeName)); |
| return ret; |
| |
| }; |
| |
| var isGeometryEdmType = function(typeName) { |
| /// <summary>Checks whether the specified type name is a geometry EDM type.</summary> |
| /// <param name="typeName" type="String">Name of type to check.</param> |
| /// <returns type="Boolean">True if the type is a geometry EDM type; false otherwise.</returns> |
| |
| var ret = contains(geometryEdmTypes, typeName) || |
| (typeName.indexOf('.') === -1 && contains(geometryTypes, typeName)); |
| return ret; |
| }; |
| |
| var isNamedStream = function(value) { |
| /// <summary>Tests whether a value is a named stream value in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <returns type="Boolean">True is the value is a named stream; false otherwise.</returns> |
| |
| if (!value && !isComplex(value)) { |
| return false; |
| } |
| var metadata = value.__metadata; |
| var mediaResource = value.__mediaresource; |
| return !metadata && !!mediaResource && !!mediaResource.media_src; |
| }; |
| |
| var isPrimitive = function(value) { |
| /// <summary>Tests whether a value is a primitive type value in the library's internal representation.</summary> |
| /// <param name="value">Value to test.</param> |
| /// <remarks> |
| /// Date objects are considered primitive types by the library. |
| /// </remarks> |
| /// <returns type="Boolean">True is the value is a primitive type value.</returns> |
| |
| return isDate(value) || |
| typeof value === "string" || |
| typeof value === "number" || |
| typeof value === "boolean"; |
| }; |
| |
| var isPrimitiveEdmType = function(typeName) { |
| /// <summary>Checks whether the specified type name is a primitive EDM type.</summary> |
| /// <param name="typeName" type="String">Name of type to check.</param> |
| /// <returns type="Boolean">True if the type is a primitive EDM type; false otherwise.</returns> |
| |
| return contains(primitiveEdmTypes, typeName); |
| }; |
| |
| var navigationPropertyKind = function(value, propertyModel) { |
| /// <summary>Gets the kind of a navigation property value.</summary> |
| /// <param name="value">Value of the navigation property.</param> |
| /// <param name="propertyModel" type="Object" optional="true"> |
| /// Object that describes the navigation property in an OData conceptual schema. |
| /// </param> |
| /// <remarks> |
| /// The returned string is as follows |
| /// </remarks> |
| /// <returns type="String">String value describing the kind of the navigation property; null if the kind cannot be determined.</returns> |
| |
| if (isDeferred(value)) { |
| return "deferred"; |
| } |
| if (isEntry(value)) { |
| return "entry"; |
| } |
| if (isFeed(value)) { |
| return "feed"; |
| } |
| if (propertyModel && propertyModel.relationship) { |
| if (value === null || value === undefined || !isFeed(value)) { |
| return "entry"; |
| } |
| return "feed"; |
| } |
| return null; |
| }; |
| |
| var lookupProperty = function(properties, name) { |
| /// <summary>Looks up a property by name.</summary> |
| /// <param name="properties" type="Array" mayBeNull="true">Array of property objects as per EDM metadata.</param> |
| /// <param name="name" type="String">Name to look for.</param> |
| /// <returns type="Object">The property object; null if not found.</returns> |
| |
| return find(properties, function(property) { |
| return property.name === name; |
| }); |
| }; |
| |
| var lookupInMetadata = function(name, metadata, kind) { |
| /// <summary>Looks up a type object by name.</summary> |
| /// <param name="name" type="String">Name, possibly null or empty.</param> |
| /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param> |
| /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param> |
| /// <returns>An type description if the name is found; null otherwise.</returns> |
| |
| return (name) ? forEachSchema(metadata, function(schema) { |
| return lookupInSchema(name, schema, kind); |
| }) : null; |
| }; |
| |
| var lookupEntitySet = function(entitySets, name) { |
| /// <summary>Looks up a entity set by name.</summary> |
| /// <param name="properties" type="Array" mayBeNull="true">Array of entity set objects as per EDM metadata.</param> |
| /// <param name="name" type="String">Name to look for.</param> |
| /// <returns type="Object">The entity set object; null if not found.</returns> |
| |
| return find(entitySets, function(entitySet) { |
| return entitySet.name === name; |
| }); |
| }; |
| |
| var lookupSingleton = function(singletons, name) { |
| /// <summary>Looks up a entity set by name.</summary> |
| /// <param name="properties" type="Array" mayBeNull="true">Array of entity set objects as per EDM metadata.</param> |
| /// <param name="name" type="String">Name to look for.</param> |
| /// <returns type="Object">The entity set object; null if not found.</returns> |
| |
| return find(singletons, function(singleton) { |
| return singleton.name === name; |
| }); |
| }; |
| |
| var lookupComplexType = function(name, metadata) { |
| /// <summary>Looks up a complex type object by name.</summary> |
| /// <param name="name" type="String">Name, possibly null or empty.</param> |
| /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param> |
| /// <returns>A complex type description if the name is found; null otherwise.</returns> |
| |
| return lookupInMetadata(name, metadata, "complexType"); |
| }; |
| |
| var lookupEntityType = function(name, metadata) { |
| /// <summary>Looks up an entity type object by name.</summary> |
| /// <param name="name" type="String">Name, possibly null or empty.</param> |
| /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param> |
| /// <returns>An entity type description if the name is found; null otherwise.</returns> |
| |
| return lookupInMetadata(name, metadata, "entityType"); |
| }; |
| |
| |
| var lookupDefaultEntityContainer = function(metadata) { |
| /// <summary>Looks up an</summary> |
| /// <param name="name" type="String">Name, possibly null or empty.</param> |
| /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param> |
| /// <returns>An entity container description if the name is found; null otherwise.</returns> |
| |
| return forEachSchema(metadata, function(schema) { |
| if (isObject(schema.entityContainer)) { |
| return schema.entityContainer; |
| } |
| }); |
| }; |
| |
| var lookupEntityContainer = function(name, metadata) { |
| /// <summary>Looks up an entity container object by name.</summary> |
| /// <param name="name" type="String">Name, possibly null or empty.</param> |
| /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param> |
| /// <returns>An entity container description if the name is found; null otherwise.</returns> |
| |
| return lookupInMetadata(name, metadata, "entityContainer"); |
| }; |
| |
| var lookupFunctionImport = function(functionImports, name) { |
| /// <summary>Looks up a function import by name.</summary> |
| /// <param name="properties" type="Array" mayBeNull="true">Array of function import objects as per EDM metadata.</param> |
| /// <param name="name" type="String">Name to look for.</param> |
| /// <returns type="Object">The entity set object; null if not found.</returns> |
| |
| return find(functionImports, function(functionImport) { |
| return functionImport.name === name; |
| }); |
| }; |
| |
| var lookupNavigationPropertyType = function(navigationProperty, metadata) { |
| /// <summary>Looks up the target entity type for a navigation property.</summary> |
| /// <param name="navigationProperty" type="Object"></param> |
| /// <param name="metadata" type="Object"></param> |
| /// <returns type="String">The entity type name for the specified property, null if not found.</returns> |
| |
| var result = null; |
| if (navigationProperty) { |
| var rel = navigationProperty.relationship; |
| var association = forEachSchema(metadata, function(schema) { |
| // The name should be the namespace qualified name in 'ns'.'type' format. |
| var nameOnly = removeNamespace(schema["namespace"], rel); |
| var associations = schema.association; |
| if (nameOnly && associations) { |
| var i, len; |
| for (i = 0, len = associations.length; i < len; i++) { |
| if (associations[i].name === nameOnly) { |
| return associations[i]; |
| } |
| } |
| } |
| return null; |
| }); |
| |
| if (association) { |
| var end = association.end[0]; |
| if (end.role !== navigationProperty.toRole) { |
| end = association.end[1]; |
| // For metadata to be valid, end.role === navigationProperty.toRole now. |
| } |
| result = end.type; |
| } |
| } |
| return result; |
| }; |
| |
| var lookupNavigationPropertyEntitySet = function(navigationProperty, sourceEntitySetName, metadata) { |
| /// <summary>Looks up the target entityset name for a navigation property.</summary> |
| /// <param name="navigationProperty" type="Object"></param> |
| /// <param name="metadata" type="Object"></param> |
| /// <returns type="String">The entityset name for the specified property, null if not found.</returns> |
| |
| if (navigationProperty) { |
| var rel = navigationProperty.relationship; |
| var associationSet = forEachSchema(metadata, function(schema) { |
| var containers = schema.entityContainer; |
| for (var i = 0; i < containers.length; i++) { |
| var associationSets = containers[i].associationSet; |
| if (associationSets) { |
| for (var j = 0; j < associationSets.length; j++) { |
| if (associationSets[j].association == rel) { |
| return associationSets[j]; |
| } |
| } |
| } |
| } |
| return null; |
| }); |
| if (associationSet && associationSet.end[0] && associationSet.end[1]) { |
| return (associationSet.end[0].entitySet == sourceEntitySetName) ? associationSet.end[1].entitySet : associationSet.end[0].entitySet; |
| } |
| } |
| return null; |
| }; |
| |
| var getEntitySetInfo = function(entitySetName, metadata) { |
| /// <summary>Gets the entitySet info, container name and functionImports for an entitySet</summary> |
| /// <param name="navigationProperty" type="Object"></param> |
| /// <param name="metadata" type="Object"></param> |
| /// <returns type="Object">The info about the entitySet.</returns> |
| |
| var info = forEachSchema(metadata, function(schema) { |
| var container = schema.entityContainer; |
| var entitySets = container.entitySet; |
| if (entitySets) { |
| for (var j = 0; j < entitySets.length; j++) { |
| if (entitySets[j].name == entitySetName) { |
| return { |
| entitySet: entitySets[j], |
| containerName: container.name, |
| functionImport: container.functionImport |
| }; |
| } |
| } |
| } |
| return null; |
| }); |
| |
| return info; |
| }; |
| |
| var removeNamespace = function(ns, fullName) { |
| /// <summary>Given an expected namespace prefix, removes it from a full name.</summary> |
| /// <param name="ns" type="String">Expected namespace.</param> |
| /// <param name="fullName" type="String">Full name in 'ns'.'name' form.</param> |
| /// <returns type="String">The local name, null if it isn't found in the expected namespace.</returns> |
| |
| if (fullName.indexOf(ns) === 0 && fullName.charAt(ns.length) === ".") { |
| return fullName.substr(ns.length + 1); |
| } |
| |
| return null; |
| }; |
| |
| var lookupInSchema = function(name, schema, kind) { |
| /// <summary>Looks up a schema object by name.</summary> |
| /// <param name="name" type="String">Name (assigned).</param> |
| /// <param name="schema">Schema object as per EDM metadata.</param> |
| /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param> |
| /// <returns>An entity type description if the name is found; null otherwise.</returns> |
| |
| if (name && schema) { |
| // The name should be the namespace qualified name in 'ns'.'type' format. |
| var nameOnly = removeNamespace(schema["namespace"], name); |
| if (nameOnly) { |
| return find(schema[kind], function(item) { |
| return item.name === nameOnly; |
| }); |
| } |
| } |
| return null; |
| }; |
| |
| var maxVersion = function(left, right) { |
| /// <summary>Compares to version strings and returns the higher one.</summary> |
| /// <param name="left" type="String">Version string in the form "major.minor.rev"</param> |
| /// <param name="right" type="String">Version string in the form "major.minor.rev"</param> |
| /// <returns type="String">The higher version string.</returns> |
| |
| if (left === right) { |
| return left; |
| } |
| |
| var leftParts = left.split("."); |
| var rightParts = right.split("."); |
| |
| var len = (leftParts.length >= rightParts.length) ? |
| leftParts.length : |
| rightParts.length; |
| |
| for (var i = 0; i < len; i++) { |
| var leftVersion = leftParts[i] && parseInt10(leftParts[i]); |
| var rightVersion = rightParts[i] && parseInt10(rightParts[i]); |
| if (leftVersion > rightVersion) { |
| return left; |
| } |
| if (leftVersion < rightVersion) { |
| return right; |
| } |
| } |
| }; |
| |
| var normalHeaders = { |
| // Headers shared by request and response |
| "content-type": "Content-Type", |
| "content-encoding": "Content-Encoding", |
| "content-length": "Content-Length", |
| "odata-version": "OData-Version", |
| |
| // Headers used by request |
| "accept": "Accept", |
| "accept-charset": "Accept-Charset", |
| "if-match": "If-Match", |
| "if-none-match": "If-None-Match", |
| "odata-isolation": "OData-Isolation", |
| "odata-maxversion": "OData-MaxVersion", |
| "prefer": "Prefer", |
| "content-id": "Content-ID", |
| "content-transfer-encoding": "Content-Transfer-Encoding", |
| |
| // Headers used by response |
| "etag": "ETag", |
| "location": "Location", |
| "odata-entityid": "OData-EntityId", |
| "preference-applied": "Preference-Applied", |
| "retry-after": "Retry-After" |
| }; |
| |
| var normalizeHeaders = function(headers) { |
| /// <summary>Normalizes headers so they can be found with consistent casing.</summary> |
| /// <param name="headers" type="Object">Dictionary of name/value pairs.</param> |
| |
| for (var name in headers) { |
| var lowerName = name.toLowerCase(); |
| var normalName = normalHeaders[lowerName]; |
| if (normalName && name !== normalName) { |
| var val = headers[name]; |
| delete headers[name]; |
| headers[normalName] = val; |
| } |
| } |
| }; |
| |
| var parseBool = function(propertyValue) { |
| /// <summary>Parses a string into a boolean value.</summary> |
| /// <param name="propertyValue">Value to parse.</param> |
| /// <returns type="Boolean">true if the property value is 'true'; false otherwise.</returns> |
| |
| if (typeof propertyValue === "boolean") { |
| return propertyValue; |
| } |
| |
| return typeof propertyValue === "string" && propertyValue.toLowerCase() === "true"; |
| }; |
| |
| |
| // The captured indices for this expression are: |
| // 0 - complete input |
| // 1,2,3 - year with optional minus sign, month, day |
| // 4,5,6 - hours, minutes, seconds |
| // 7 - optional milliseconds |
| // 8 - everything else (presumably offset information) |
| var parseDateTimeRE = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?(.*)$/; |
| |
| var parseDateTimeMaybeOffset = function(value, withOffset, nullOnError) { |
| /// <summary>Parses a string into a DateTime value.</summary> |
| /// <param name="value" type="String">Value to parse.</param> |
| /// <param name="withOffset" type="Boolean">Whether offset is expected.</param> |
| /// <returns type="Date">The parsed value.</returns> |
| |
| // We cannot parse this in cases of failure to match or if offset information is specified. |
| var parts = parseDateTimeRE.exec(value); |
| var offset = (parts) ? getCanonicalTimezone(parts[8]) : null; |
| |
| if (!parts || (!withOffset && offset !== "Z")) { |
| if (nullOnError) { |
| return null; |
| } |
| throw { |
| message: "Invalid date/time value" |
| }; |
| } |
| |
| // Pre-parse years, account for year '0' being invalid in dateTime. |
| var year = parseInt10(parts[1]); |
| if (year <= 0) { |
| year++; |
| } |
| |
| // Pre-parse optional milliseconds, fill in default. Fail if value is too precise. |
| var ms = parts[7]; |
| var ns = 0; |
| if (!ms) { |
| ms = 0; |
| } else { |
| if (ms.length > 7) { |
| if (nullOnError) { |
| return null; |
| } |
| throw { |
| message: "Cannot parse date/time value to given precision." |
| }; |
| } |
| |
| ns = formatNumberWidth(ms.substring(3), 4, true); |
| ms = formatNumberWidth(ms.substring(0, 3), 3, true); |
| |
| ms = parseInt10(ms); |
| ns = parseInt10(ns); |
| } |
| |
| // Pre-parse other time components and offset them if necessary. |
| var hours = parseInt10(parts[4]); |
| var minutes = parseInt10(parts[5]); |
| var seconds = parseInt10(parts[6]) || 0; |
| if (offset !== "Z") { |
| // The offset is reversed to get back the UTC date, which is |
| // what the API will eventually have. |
| var timezone = parseTimezone(offset); |
| var direction = -(timezone.d); |
| hours += timezone.h * direction; |
| minutes += timezone.m * direction; |
| } |
| |
| // Set the date and time separately with setFullYear, so years 0-99 aren't biased like in Date.UTC. |
| var result = new Date(); |
| result.setUTCFullYear( |
| year, // Year. |
| parseInt10(parts[2]) - 1, // Month (zero-based for Date.UTC and setFullYear). |
| parseInt10(parts[3]) // Date. |
| ); |
| result.setUTCHours(hours, minutes, seconds, ms); |
| |
| if (isNaN(result.valueOf())) { |
| if (nullOnError) { |
| return null; |
| } |
| throw { |
| message: "Invalid date/time value" |
| }; |
| } |
| |
| if (withOffset) { |
| result.__edmType = "Edm.DateTimeOffset"; |
| result.__offset = offset; |
| } |
| |
| if (ns) { |
| result.__ns = ns; |
| } |
| |
| return result; |
| }; |
| |
| var parseDate = function(propertyValue, nullOnError) { |
| /// <summary>Parses a string into a Date object.</summary> |
| /// <param name="propertyValue" type="String">Value to parse.</param> |
| /// <returns type="Date">The parsed with year, month, day set, time values are set to 0</returns> |
| var parts = propertyValue.split('-'); |
| |
| if (parts.length != 3 && nullOnError) { |
| return null; |
| } |
| return new Date( |
| parseInt10(parts[0]), // Year. |
| parseInt10(parts[1]) - 1, // Month (zero-based for Date.UTC and setFullYear). |
| parseInt10(parts[2], |
| 0, 0, 0, 0) // Date. |
| ); |
| |
| }; |
| |
| var parseTimeOfDayRE = /^(\d+):(\d+)(:(\d+)(.(\d+))?)?$/; |
| |
| var parseTimeOfDay = function(propertyValue, nullOnError) { |
| var parts = parseTimeOfDayRE.exec(propertyValue); |
| |
| |
| return { |
| 'h': parseInt10(parts[1]), |
| 'm': parseInt10(parts[2]), |
| 's': parseInt10(parts[4]), |
| 'ms': parseInt10(parts[6]), |
| }; |
| }; |
| |
| var parseDateTimeOffset = function(propertyValue, nullOnError) { |
| /// <summary>Parses a string into a DateTimeOffset value.</summary> |
| /// <param name="propertyValue" type="String">Value to parse.</param> |
| /// <returns type="Date">The parsed value.</returns> |
| /// <remarks> |
| /// The resulting object is annotated with an __edmType property and |
| /// an __offset property reflecting the original intended offset of |
| /// the value. The time is adjusted for UTC time, as the current |
| /// timezone-aware Date APIs will only work with the local timezone. |
| /// </remarks> |
| |
| return parseDateTimeMaybeOffset(propertyValue, true, nullOnError); |
| }; |
| |
| // The captured indices for this expression are: |
| // 0 - complete input |
| // 1 - direction |
| // 2,3,4 - years, months, days |
| // 5,6,7,8 - hours, minutes, seconds, miliseconds |
| |
| var parseTimeRE = /^([+-])?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?/; |
| |
| var isEdmDurationValue = function(value) { |
| parseTimeRE.test(value); |
| }; |
| |
| var parseDuration = function(duration) { |
| /// <summary>Parses a string in xsd:duration format.</summary> |
| /// <param name="duration" type="String">Duration value.</param> |
| /// <remarks> |
| /// This method will throw an exception if the input string has a year or a month component. |
| /// </remarks> |
| /// <returns type="Object">Object representing the time</returns> |
| |
| var parts = parseTimeRE.exec(duration); |
| |
| if (parts === null) { |
| throw { |
| message: "Invalid duration value." |
| }; |
| } |
| |
| var years = parts[2] || "0"; |
| var months = parts[3] || "0"; |
| var days = parseInt10(parts[4] || 0); |
| var hours = parseInt10(parts[5] || 0); |
| var minutes = parseInt10(parts[6] || 0); |
| var seconds = parseFloat(parts[7] || 0); |
| |
| if (years !== "0" || months !== "0") { |
| throw { |
| message: "Unsupported duration value." |
| }; |
| } |
| |
| var ms = parts[8]; |
| var ns = 0; |
| if (!ms) { |
| ms = 0; |
| } else { |
| if (ms.length > 7) { |
| throw { |
| message: "Cannot parse duration value to given precision." |
| }; |
| } |
| |
| ns = formatNumberWidth(ms.substring(3), 4, true); |
| ms = formatNumberWidth(ms.substring(0, 3), 3, true); |
| |
| ms = parseInt10(ms); |
| ns = parseInt10(ns); |
| } |
| |
| ms += seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000; |
| |
| if (parts[1] === "-") { |
| ms = -ms; |
| } |
| |
| var result = { |
| ms: ms, |
| __edmType: "Edm.Time" |
| }; |
| |
| if (ns) { |
| result.ns = ns; |
| } |
| return result; |
| }; |
| |
| var parseTimezone = function(timezone) { |
| /// <summary>Parses a timezone description in (+|-)nn:nn format.</summary> |
| /// <param name="timezone" type="String">Timezone offset.</param> |
| /// <returns type="Object"> |
| /// An object with a (d)irection property of 1 for + and -1 for -, |
| /// offset (h)ours and offset (m)inutes. |
| /// </returns> |
| |
| var direction = timezone.substring(0, 1); |
| direction = (direction === "+") ? 1 : -1; |
| |
| var offsetHours = parseInt10(timezone.substring(1)); |
| var offsetMinutes = parseInt10(timezone.substring(timezone.indexOf(":") + 1)); |
| return { |
| d: direction, |
| h: offsetHours, |
| m: offsetMinutes |
| }; |
| }; |
| |
| var prepareRequest = function(request, handler, context) { |
| /// <summary>Prepares a request object so that it can be sent through the network.</summary> |
| /// <param name="request">Object that represents the request to be sent.</param> |
| /// <param name="handler">Handler for data serialization</param> |
| /// <param name="context">Context used for preparing the request</param> |
| |
| // Default to GET if no method has been specified. |
| if (!request.method) { |
| request.method = "GET"; |
| } |
| |
| if (!request.headers) { |
| request.headers = {}; |
| } else { |
| normalizeHeaders(request.headers); |
| } |
| |
| if (request.headers.Accept === undefined) { |
| request.headers.Accept = handler.accept; |
| } |
| |
| if (assigned(request.data) && request.body === undefined) { |
| handler.write(request, context); |
| } |
| |
| if (!assigned(request.headers["OData-MaxVersion"])) { |
| request.headers["OData-MaxVersion"] = handler.maxDataServiceVersion || "4.0"; |
| } |
| |
| if (request.async === undefined) { |
| request.async = true; |
| } |
| |
| }; |
| |
| var traverseInternal = function(item, owner, callback) { |
| /// <summary>Traverses a tree of objects invoking callback for every value.</summary> |
| /// <param name="item" type="Object">Object or array to traverse.</param> |
| /// <param name="callback" type="Function"> |
| /// Callback function with key and value, similar to JSON.parse reviver. |
| /// </param> |
| /// <returns type="Object">The object with traversed properties.</returns> |
| /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks> |
| |
| if (item && typeof item === "object") { |
| for (var name in item) { |
| var value = item[name]; |
| var result = traverseInternal(value, name, callback); |
| result = callback(name, result, owner); |
| if (result !== value) { |
| if (value === undefined) { |
| delete item[name]; |
| } else { |
| item[name] = result; |
| } |
| } |
| } |
| } |
| |
| return item; |
| }; |
| |
| var traverse = function(item, callback) { |
| /// <summary>Traverses a tree of objects invoking callback for every value.</summary> |
| /// <param name="item" type="Object">Object or array to traverse.</param> |
| /// <param name="callback" type="Function"> |
| /// Callback function with key and value, similar to JSON.parse reviver. |
| /// </param> |
| /// <returns type="Object">The traversed object.</returns> |
| /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks> |
| |
| return callback("", traverseInternal(item, "", callback)); |
| }; |
| |
| exports.dataItemTypeName = dataItemTypeName; |
| exports.EDM_BINARY = EDM_BINARY; |
| exports.EDM_BOOLEAN = EDM_BOOLEAN; |
| exports.EDM_BYTE = EDM_BYTE; |
| exports.EDM_DATETIME = EDM_DATETIME; |
| exports.EDM_DATETIMEOFFSET = EDM_DATETIMEOFFSET; |
| exports.EDM_DECIMAL = EDM_DECIMAL; |
| exports.EDM_DOUBLE = EDM_DOUBLE; |
| exports.EDM_GEOGRAPHY = EDM_GEOGRAPHY; |
| exports.EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY_POINT; |
| exports.EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY_LINESTRING; |
| exports.EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY_POLYGON; |
| exports.EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY_COLLECTION; |
| exports.EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY_MULTIPOLYGON; |
| exports.EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY_MULTILINESTRING; |
| exports.EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY_MULTIPOINT; |
| exports.EDM_GEOMETRY = EDM_GEOMETRY; |
| exports.EDM_GEOMETRY_POINT = EDM_GEOMETRY_POINT; |
| exports.EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY_LINESTRING; |
| exports.EDM_GEOMETRY_POLYGON = EDM_GEOMETRY_POLYGON; |
| exports.EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY_COLLECTION; |
| exports.EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY_MULTIPOLYGON; |
| exports.EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY_MULTILINESTRING; |
| exports.EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY_MULTIPOINT; |
| exports.EDM_GUID = EDM_GUID; |
| exports.EDM_INT16 = EDM_INT16; |
| exports.EDM_INT32 = EDM_INT32; |
| exports.EDM_INT64 = EDM_INT64; |
| exports.EDM_SBYTE = EDM_SBYTE; |
| exports.EDM_SINGLE = EDM_SINGLE; |
| exports.EDM_STRING = EDM_STRING; |
| exports.EDM_TIME = EDM_TIME; |
| exports.GEOJSON_POINT = GEOJSON_POINT; |
| exports.GEOJSON_LINESTRING = GEOJSON_LINESTRING; |
| exports.GEOJSON_POLYGON = GEOJSON_POLYGON; |
| exports.GEOJSON_MULTIPOINT = GEOJSON_MULTIPOINT; |
| exports.GEOJSON_MULTILINESTRING = GEOJSON_MULTILINESTRING; |
| exports.GEOJSON_MULTIPOLYGON = GEOJSON_MULTIPOLYGON; |
| exports.GEOJSON_GEOMETRYCOLLECTION = GEOJSON_GEOMETRYCOLLECTION; |
| exports.forEachSchema = forEachSchema; |
| exports.formatDateTimeOffset = formatDateTimeOffset; |
| exports.formatDateTimeOffsetJSON = formatDateTimeOffsetJSON; |
| exports.formatDuration = formatDuration; |
| exports.formatNumberWidth = formatNumberWidth; |
| exports.getCanonicalTimezone = getCanonicalTimezone; |
| exports.getCollectionType = getCollectionType; |
| exports.invokeRequest = invokeRequest; |
| exports.isBatch = isBatch; |
| exports.isCollection = isCollection; |
| exports.isCollectionType = isCollectionType; |
| exports.isComplex = isComplex; |
| exports.isDateTimeOffset = isDateTimeOffset; |
| exports.isDeferred = isDeferred; |
| exports.isEntry = isEntry; |
| exports.isFeed = isFeed; |
| exports.isGeographyEdmType = isGeographyEdmType; |
| exports.isGeometryEdmType = isGeometryEdmType; |
| exports.isNamedStream = isNamedStream; |
| exports.isPrimitive = isPrimitive; |
| exports.isPrimitiveEdmType = isPrimitiveEdmType; |
| exports.lookupComplexType = lookupComplexType; |
| exports.lookupDefaultEntityContainer = lookupDefaultEntityContainer; |
| exports.lookupEntityContainer = lookupEntityContainer; |
| exports.lookupEntitySet = lookupEntitySet; |
| exports.lookupSingleton = lookupSingleton; |
| exports.lookupEntityType = lookupEntityType; |
| exports.lookupFunctionImport = lookupFunctionImport; |
| exports.lookupNavigationPropertyType = lookupNavigationPropertyType; |
| exports.lookupNavigationPropertyEntitySet = lookupNavigationPropertyEntitySet; |
| exports.lookupInSchema = lookupInSchema; |
| exports.lookupProperty = lookupProperty; |
| exports.lookupInMetadata = lookupInMetadata; |
| exports.getEntitySetInfo = getEntitySetInfo; |
| exports.maxVersion = maxVersion; |
| exports.navigationPropertyKind = navigationPropertyKind; |
| exports.normalizeHeaders = normalizeHeaders; |
| exports.parseBool = parseBool; |
| |
| |
| exports.parseDate = parseDate; |
| exports.parseDateTimeOffset = parseDateTimeOffset; |
| exports.parseDuration = parseDuration; |
| exports.parseTimeOfDay = parseTimeOfDay; |
| |
| exports.parseInt10 = parseInt10; |
| exports.prepareRequest = prepareRequest; |
| exports.removeNamespace = removeNamespace; |
| exports.traverse = traverse; |
| |
| |
| }, { |
| "./../datajs.js": 4 |
| } |
| ], |
| 15: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| exports.DomStore = DomStore = require('./store/dom.js'); |
| exports.IndexedDBStore = IndexedDBStore = require('./store/indexeddb.js'); |
| exports.MemoryStore = MemoryStore = require('./store/memory.js'); |
| |
| var mechanisms = { |
| indexeddb: IndexedDBStore, |
| dom: DomStore, |
| memory: MemoryStore |
| }; |
| |
| exports.defaultStoreMechanism = "best"; |
| |
| exports.createStore = function(name, mechanism) { |
| /// <summary>Creates a new store object.</summary> |
| /// <param name="name" type="String">Store name.</param> |
| /// <param name="mechanism" type="String" optional="true">A specific mechanism to use (defaults to best, can be "best", "dom", "indexeddb", "webdb").</param> |
| /// <returns type="Object">Store object.</returns> |
| |
| if (!mechanism) { |
| mechanism = exports.defaultStoreMechanism; |
| } |
| |
| if (mechanism === "best") { |
| mechanism = (DomStore.isSupported()) ? "dom" : "memory"; |
| } |
| |
| var factory = mechanisms[mechanism]; |
| if (factory) { |
| return factory.create(name); |
| } |
| |
| throw { |
| message: "Failed to create store", |
| name: name, |
| mechanism: mechanism |
| }; |
| }; |
| |
| exports.mechanisms = mechanisms; |
| |
| |
| |
| }, { |
| "./store/dom.js": 16, |
| "./store/indexeddb.js": 17, |
| "./store/memory.js": 18 |
| } |
| ], |
| 16: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var utils = require('./../datajs.js').utils; |
| |
| // Imports. |
| var throwErrorCallback = utils.throwErrorCallback; |
| var delay = utils.delay; |
| |
| var localStorage = null; |
| |
| var domStoreDateToJSON = function() { |
| /// <summary>Converts a Date object into an object representation friendly to JSON serialization.</summary> |
| /// <returns type="Object">Object that represents the Date.</returns> |
| /// <remarks> |
| /// This method is used to override the Date.toJSON method and is called only by |
| /// JSON.stringify. It should never be called directly. |
| /// </remarks> |
| |
| var newValue = { |
| v: this.valueOf(), |
| t: "[object Date]" |
| }; |
| // Date objects might have extra properties on them so we save them. |
| for (var name in this) { |
| newValue[name] = this[name]; |
| } |
| return newValue; |
| }; |
| |
| var domStoreJSONToDate = function(_, value) { |
| /// <summary>JSON reviver function for converting an object representing a Date in a JSON stream to a Date object</summary> |
| /// <param value="Object">Object to convert.</param> |
| /// <returns type="Date">Date object.</returns> |
| /// <remarks> |
| /// This method is used during JSON parsing and invoked only by the reviver function. |
| /// It should never be called directly. |
| /// </remarks> |
| |
| if (value && value.t === "[object Date]") { |
| var newValue = new Date(value.v); |
| for (var name in value) { |
| if (name !== "t" && name !== "v") { |
| newValue[name] = value[name]; |
| } |
| } |
| value = newValue; |
| } |
| return value; |
| }; |
| |
| var qualifyDomStoreKey = function(store, key) { |
| /// <summary>Qualifies the key with the name of the store.</summary> |
| /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param> |
| /// <param name="key" type="String">Key string.</param> |
| /// <returns type="String">Fully qualified key string.</returns> |
| |
| return store.name + "#!#" + key; |
| }; |
| |
| var unqualifyDomStoreKey = function(store, key) { |
| /// <summary>Gets the key part of a fully qualified key string.</summary> |
| /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param> |
| /// <param name="key" type="String">Fully qualified key string.</param> |
| /// <returns type="String">Key part string</returns> |
| |
| return key.replace(store.name + "#!#", ""); |
| }; |
| |
| var DomStore = function(name) { |
| /// <summary>Constructor for store objects that use DOM storage as the underlying mechanism.</summary> |
| /// <param name="name" type="String">Store name.</param> |
| this.name = name; |
| }; |
| |
| DomStore.create = function(name) { |
| /// <summary>Creates a store object that uses DOM Storage as its underlying mechanism.</summary> |
| /// <param name="name" type="String">Store name.</param> |
| /// <returns type="Object">Store object.</returns> |
| |
| if (DomStore.isSupported()) { |
| localStorage = localStorage || window.localStorage; |
| return new DomStore(name); |
| } |
| |
| throw { |
| message: "Web Storage not supported by the browser" |
| }; |
| }; |
| |
| DomStore.isSupported = function() { |
| /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary> |
| /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</summary> |
| return !!window.localStorage; |
| }; |
| |
| DomStore.prototype.add = function(key, value, success, error) { |
| /// <summary>Adds a new value identified by a key to the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="value">Value that is going to be added to the store.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// This method errors out if the store already contains the specified key. |
| /// </remarks> |
| |
| error = error || this.defaultError; |
| var store = this; |
| this.contains(key, function(contained) { |
| if (!contained) { |
| store.addOrUpdate(key, value, success, error); |
| } else { |
| delay(error, { |
| message: "key already exists", |
| key: key |
| }); |
| } |
| }, error); |
| }; |
| |
| DomStore.prototype.addOrUpdate = function(key, value, success, error) { |
| /// <summary>Adds or updates a value identified by a key to the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="value">Value that is going to be added or updated to the store.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value. |
| /// </remarks> |
| |
| error = error || this.defaultError; |
| |
| if (key instanceof Array) { |
| error({ |
| message: "Array of keys not supported" |
| }); |
| } else { |
| var fullKey = qualifyDomStoreKey(this, key); |
| var oldDateToJSON = Date.prototype.toJSON; |
| try { |
| var storedValue = value; |
| if (storedValue !== undefined) { |
| // Dehydrate using json |
| Date.prototype.toJSON = domStoreDateToJSON; |
| storedValue = window.JSON.stringify(value); |
| } |
| // Save the json string. |
| localStorage.setItem(fullKey, storedValue); |
| delay(success, key, value); |
| } catch (e) { |
| if (e.code === 22 || e.number === 0x8007000E) { |
| delay(error, { |
| name: "QUOTA_EXCEEDED_ERR", |
| error: e |
| }); |
| } else { |
| delay(error, e); |
| } |
| } finally { |
| Date.prototype.toJSON = oldDateToJSON; |
| } |
| } |
| }; |
| |
| DomStore.prototype.clear = function(success, error) { |
| /// <summary>Removes all the data associated with this store object.</summary> |
| /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// In case of an error, this method will not restore any keys that might have been deleted at that point. |
| /// </remarks> |
| |
| error = error || this.defaultError; |
| try { |
| var i = 0, |
| len = localStorage.length; |
| while (len > 0 && i < len) { |
| var fullKey = localStorage.key(i); |
| var key = unqualifyDomStoreKey(this, fullKey); |
| if (fullKey !== key) { |
| localStorage.removeItem(fullKey); |
| len = localStorage.length; |
| } else { |
| i++; |
| } |
| } |
| delay(success); |
| } catch (e) { |
| delay(error, e); |
| } |
| }; |
| |
| DomStore.prototype.close = function() { |
| /// <summary>This function does nothing in DomStore as it does not have a connection model</summary> |
| }; |
| |
| DomStore.prototype.contains = function(key, success, error) { |
| /// <summary>Checks whether a key exists in the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| error = error || this.defaultError; |
| try { |
| var fullKey = qualifyDomStoreKey(this, key); |
| var value = localStorage.getItem(fullKey); |
| delay(success, value !== null); |
| } catch (e) { |
| delay(error, e); |
| } |
| }; |
| |
| DomStore.prototype.defaultError = throwErrorCallback; |
| |
| DomStore.prototype.getAllKeys = function(success, error) { |
| /// <summary>Gets all the keys that exist in the store.</summary> |
| /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| |
| error = error || this.defaultError; |
| |
| var results = []; |
| var i, len; |
| |
| try { |
| for (i = 0, len = localStorage.length; i < len; i++) { |
| var fullKey = localStorage.key(i); |
| var key = unqualifyDomStoreKey(this, fullKey); |
| if (fullKey !== key) { |
| results.push(key); |
| } |
| } |
| delay(success, results); |
| } catch (e) { |
| delay(error, e); |
| } |
| }; |
| |
| /// <summary>Identifies the underlying mechanism used by the store.</summary> |
| DomStore.prototype.mechanism = "dom"; |
| |
| DomStore.prototype.read = function(key, success, error) { |
| /// <summary>Reads the value associated to a key in the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| error = error || this.defaultError; |
| |
| if (key instanceof Array) { |
| error({ |
| message: "Array of keys not supported" |
| }); |
| } else { |
| try { |
| var fullKey = qualifyDomStoreKey(this, key); |
| var value = localStorage.getItem(fullKey); |
| if (value !== null && value !== "undefined") { |
| // Hydrate using json |
| value = window.JSON.parse(value, domStoreJSONToDate); |
| } else { |
| value = undefined; |
| } |
| delay(success, key, value); |
| } catch (e) { |
| delay(error, e); |
| } |
| } |
| }; |
| |
| DomStore.prototype.remove = function(key, success, error) { |
| /// <summary>Removes a key and its value from the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| error = error || this.defaultError; |
| |
| if (key instanceof Array) { |
| error({ |
| message: "Batches not supported" |
| }); |
| } else { |
| try { |
| var fullKey = qualifyDomStoreKey(this, key); |
| localStorage.removeItem(fullKey); |
| delay(success); |
| } catch (e) { |
| delay(error, e); |
| } |
| } |
| }; |
| |
| DomStore.prototype.update = function(key, value, success, error) { |
| /// <summary>Updates the value associated to a key in the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="value">New value.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// This method errors out if the specified key is not found in the store. |
| /// </remarks> |
| |
| error = error || this.defaultError; |
| var store = this; |
| this.contains(key, function(contained) { |
| if (contained) { |
| store.addOrUpdate(key, value, success, error); |
| } else { |
| delay(error, { |
| message: "key not found", |
| key: key |
| }); |
| } |
| }, error); |
| }; |
| |
| module.exports = DomStore; |
| |
| }, { |
| "./../datajs.js": 4 |
| } |
| ], |
| 17: [ |
| function(require, module, exports) { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var utils = require('./../datajs.js').utils; |
| |
| |
| // Imports. |
| var throwErrorCallback = utils.throwErrorCallback; |
| var delay = utils.delay; |
| |
| var indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB; |
| var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange; |
| var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {}; |
| |
| var IDBT_READ_ONLY = IDBTransaction.READ_ONLY || "readonly"; |
| var IDBT_READ_WRITE = IDBTransaction.READ_WRITE || "readwrite"; |
| |
| var getError = function(error, defaultError) { |
| /// <summary>Returns either a specific error handler or the default error handler</summary> |
| /// <param name="error" type="Function">The specific error handler</param> |
| /// <param name="defaultError" type="Function">The default error handler</param> |
| /// <returns type="Function">The error callback</returns> |
| |
| return function(e) { |
| var errorFunc = error || defaultError; |
| if (!errorFunc) { |
| return; |
| } |
| |
| // Old api quota exceeded error support. |
| if (Object.prototype.toString.call(e) === "[object IDBDatabaseException]") { |
| if (e.code === 11 /* IndexedDb disk quota exceeded */ ) { |
| errorFunc({ |
| name: "QuotaExceededError", |
| error: e |
| }); |
| return; |
| } |
| errorFunc(e); |
| return; |
| } |
| |
| var errName; |
| try { |
| var errObj = e.target.error || e; |
| errName = errObj.name; |
| } catch (ex) { |
| errName = (e.type === "blocked") ? "IndexedDBBlocked" : "UnknownError"; |
| } |
| errorFunc({ |
| name: errName, |
| error: e |
| }); |
| }; |
| }; |
| |
| var openStoreDb = function(store, success, error) { |
| /// <summary>Opens the store object's indexed db database.</summary> |
| /// <param name="store" type="IndexedDBStore">The store object</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| |
| var storeName = store.name; |
| var dbName = "_datajs_" + storeName; |
| |
| var request = indexedDB.open(dbName); |
| request.onblocked = error; |
| request.onerror = error; |
| |
| request.onupgradeneeded = function() { |
| var db = request.result; |
| if (!db.objectStoreNames.contains(storeName)) { |
| db.createObjectStore(storeName); |
| } |
| }; |
| |
| request.onsuccess = function(event) { |
| var db = request.result; |
| if (!db.objectStoreNames.contains(storeName)) { |
| // Should we use the old style api to define the database schema? |
| if ("setVersion" in db) { |
| var versionRequest = db.setVersion("1.0"); |
| versionRequest.onsuccess = function() { |
| var transaction = versionRequest.transaction; |
| transaction.oncomplete = function() { |
| success(db); |
| }; |
| db.createObjectStore(storeName, null, false); |
| }; |
| versionRequest.onerror = error; |
| versionRequest.onblocked = error; |
| return; |
| } |
| |
| // The database doesn't have the expected store. |
| // Fabricate an error object for the event for the schema mismatch |
| // and error out. |
| event.target.error = { |
| name: "DBSchemaMismatch" |
| }; |
| error(event); |
| return; |
| } |
| |
| db.onversionchange = function(event) { |
| event.target.close(); |
| }; |
| success(db); |
| }; |
| }; |
| |
| var openTransaction = function(store, mode, success, error) { |
| /// <summary>Opens a new transaction to the store</summary> |
| /// <param name="store" type="IndexedDBStore">The store object</param> |
| /// <param name="mode" type="Short">The read/write mode of the transaction (constants from IDBTransaction)</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| |
| var storeName = store.name; |
| var storeDb = store.db; |
| var errorCallback = getError(error, store.defaultError); |
| |
| if (storeDb) { |
| success(storeDb.transaction(storeName, mode)); |
| return; |
| } |
| |
| openStoreDb(store, function(db) { |
| store.db = db; |
| success(db.transaction(storeName, mode)); |
| }, errorCallback); |
| }; |
| |
| var IndexedDBStore = function(name) { |
| /// <summary>Creates a new IndexedDBStore.</summary> |
| /// <param name="name" type="String">The name of the store.</param> |
| /// <returns type="Object">The new IndexedDBStore.</returns> |
| this.name = name; |
| }; |
| |
| IndexedDBStore.create = function(name) { |
| /// <summary>Creates a new IndexedDBStore.</summary> |
| /// <param name="name" type="String">The name of the store.</param> |
| /// <returns type="Object">The new IndexedDBStore.</returns> |
| if (IndexedDBStore.isSupported()) { |
| return new IndexedDBStore(name); |
| } |
| |
| throw { |
| message: "IndexedDB is not supported on this browser" |
| }; |
| }; |
| |
| IndexedDBStore.isSupported = function() { |
| /// <summary>Returns whether IndexedDB is supported.</summary> |
| /// <returns type="Boolean">True if IndexedDB is supported, false otherwise.</returns> |
| return !!indexedDB; |
| }; |
| |
| IndexedDBStore.prototype.add = function(key, value, success, error) { |
| /// <summary>Adds a key/value pair to the store</summary> |
| /// <param name="key" type="String">The key</param> |
| /// <param name="value" type="Object">The value</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| var keys = []; |
| var values = []; |
| |
| if (key instanceof Array) { |
| keys = key; |
| values = value; |
| } else { |
| keys = [key]; |
| values = [value]; |
| } |
| |
| openTransaction(this, IDBT_READ_WRITE, function(transaction) { |
| transaction.onabort = getError(error, defaultError, key, "add"); |
| transaction.oncomplete = function() { |
| if (key instanceof Array) { |
| success(keys, values); |
| } else { |
| success(key, value); |
| } |
| }; |
| |
| for (var i = 0; i < keys.length && i < values.length; i++) { |
| transaction.objectStore(name).add({ |
| v: values[i] |
| }, keys[i]); |
| } |
| }, error); |
| }; |
| |
| IndexedDBStore.prototype.addOrUpdate = function(key, value, success, error) { |
| /// <summary>Adds or updates a key/value pair in the store</summary> |
| /// <param name="key" type="String">The key</param> |
| /// <param name="value" type="Object">The value</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| var keys = []; |
| var values = []; |
| |
| if (key instanceof Array) { |
| keys = key; |
| values = value; |
| } else { |
| keys = [key]; |
| values = [value]; |
| } |
| |
| openTransaction(this, IDBT_READ_WRITE, function(transaction) { |
| transaction.onabort = getError(error, defaultError); |
| transaction.oncomplete = function() { |
| if (key instanceof Array) { |
| success(keys, values); |
| } else { |
| success(key, value); |
| } |
| }; |
| |
| for (var i = 0; i < keys.length && i < values.length; i++) { |
| var record = { |
| v: values[i] |
| }; |
| transaction.objectStore(name).put(record, keys[i]); |
| } |
| }, error); |
| }; |
| |
| IndexedDBStore.prototype.clear = function(success, error) { |
| /// <summary>Clears the store</summary> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| openTransaction(this, IDBT_READ_WRITE, function(transaction) { |
| transaction.onerror = getError(error, defaultError); |
| transaction.oncomplete = function() { |
| success(); |
| }; |
| |
| transaction.objectStore(name).clear(); |
| }, error); |
| }; |
| |
| IndexedDBStore.prototype.close = function() { |
| /// <summary>Closes the connection to the database</summary> |
| if (this.db) { |
| this.db.close(); |
| this.db = null; |
| } |
| }; |
| |
| IndexedDBStore.prototype.contains = function(key, success, error) { |
| /// <summary>Returns whether the store contains a key</summary> |
| /// <param name="key" type="String">The key</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| openTransaction(this, IDBT_READ_ONLY, function(transaction) { |
| var objectStore = transaction.objectStore(name); |
| var request = objectStore["get"](key); |
| |
| transaction.oncomplete = function() { |
| success(!!request.result); |
| }; |
| transaction.onerror = getError(error, defaultError); |
| }, error); |
| }; |
| |
| IndexedDBStore.prototype.defaultError = throwErrorCallback; |
| |
| IndexedDBStore.prototype.getAllKeys = function(success, error) { |
| /// <summary>Gets all the keys from the store</summary> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| openTransaction(this, IDBT_READ_WRITE, function(transaction) { |
| var results = []; |
| |
| transaction.oncomplete = function() { |
| success(results); |
| }; |
| |
| var request = transaction.objectStore(name).openCursor(); |
| |
| request.onerror = getError(error, defaultError); |
| request.onsuccess = function(event) { |
| var cursor = event.target.result; |
| if (cursor) { |
| results.push(cursor.key); |
| // Some tools have issues because continue is a javascript reserved word. |
| cursor["continue"].call(cursor); |
| } |
| }; |
| }, error); |
| }; |
| |
| /// <summary>Identifies the underlying mechanism used by the store.</summary> |
| IndexedDBStore.prototype.mechanism = "indexeddb"; |
| |
| IndexedDBStore.prototype.read = function(key, success, error) { |
| /// <summary>Reads the value for the specified key</summary> |
| /// <param name="key" type="String">The key</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| /// <remarks>If the key does not exist, the success handler will be called with value = undefined</remarks> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| var keys = (key instanceof Array) ? key : [key]; |
| |
| openTransaction(this, IDBT_READ_ONLY, function(transaction) { |
| var values = []; |
| |
| transaction.onerror = getError(error, defaultError, key, "read"); |
| transaction.oncomplete = function() { |
| if (key instanceof Array) { |
| success(keys, values); |
| } else { |
| success(keys[0], values[0]); |
| } |
| }; |
| |
| for (var i = 0; i < keys.length; i++) { |
| // Some tools have issues because get is a javascript reserved word. |
| var objectStore = transaction.objectStore(name); |
| var request = objectStore["get"].call(objectStore, keys[i]); |
| request.onsuccess = function(event) { |
| var record = event.target.result; |
| values.push(record ? record.v : undefined); |
| }; |
| } |
| }, error); |
| }; |
| |
| IndexedDBStore.prototype.remove = function(key, success, error) { |
| /// <summary>Removes the specified key from the store</summary> |
| /// <param name="key" type="String">The key</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| var keys = (key instanceof Array) ? key : [key]; |
| |
| openTransaction(this, IDBT_READ_WRITE, function(transaction) { |
| transaction.onerror = getError(error, defaultError); |
| transaction.oncomplete = function() { |
| success(); |
| }; |
| |
| for (var i = 0; i < keys.length; i++) { |
| // Some tools have issues because continue is a javascript reserved word. |
| var objectStore = transaction.objectStore(name); |
| objectStore["delete"].call(objectStore, keys[i]); |
| } |
| }, error); |
| }; |
| |
| IndexedDBStore.prototype.update = function(key, value, success, error) { |
| /// <summary>Updates a key/value pair in the store</summary> |
| /// <param name="key" type="String">The key</param> |
| /// <param name="value" type="Object">The value</param> |
| /// <param name="success" type="Function">The success callback</param> |
| /// <param name="error" type="Function">The error callback</param> |
| var name = this.name; |
| var defaultError = this.defaultError; |
| var keys = []; |
| var values = []; |
| |
| if (key instanceof Array) { |
| keys = key; |
| values = value; |
| } else { |
| keys = [key]; |
| values = [value]; |
| } |
| |
| openTransaction(this, IDBT_READ_WRITE, function(transaction) { |
| transaction.onabort = getError(error, defaultError); |
| transaction.oncomplete = function() { |
| if (key instanceof Array) { |
| success(keys, values); |
| } else { |
| success(key, value); |
| } |
| }; |
| |
| for (var i = 0; i < keys.length && i < values.length; i++) { |
| var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i])); |
| var record = { |
| v: values[i] |
| }; |
| request.pair = { |
| key: keys[i], |
| value: record |
| }; |
| request.onsuccess = function(event) { |
| var cursor = event.target.result; |
| if (cursor) { |
| cursor.update(event.target.pair.value); |
| } else { |
| transaction.abort(); |
| } |
| }; |
| } |
| }, error); |
| }; |
| |
| module.exports = IndexedDBStore; |
| |
| }, { |
| "./../datajs.js": 4 |
| } |
| ], |
| 18: [ |
| function(require, module, exports) { |
| |
| |
| var utils = require('./../datajs.js').utils; |
| |
| |
| // Imports. |
| var throwErrorCallback = utils.throwErrorCallback; |
| var delay = utils.delay; |
| |
| var MemoryStore = function(name) { |
| /// <summary>Constructor for store objects that use a sorted array as the underlying mechanism.</summary> |
| /// <param name="name" type="String">Store name.</param> |
| |
| var holes = []; |
| var items = []; |
| var keys = {}; |
| |
| this.name = name; |
| |
| var getErrorCallback = function(error) { |
| return error || this.defaultError; |
| }; |
| |
| var validateKeyInput = function(key, error) { |
| /// <summary>Validates that the specified key is not undefined, not null, and not an array</summary> |
| /// <param name="key">Key value.</param> |
| /// <param name="error" type="Function">Error callback.</param> |
| /// <returns type="Boolean">True if the key is valid. False if the key is invalid and the error callback has been queued for execution.</returns> |
| |
| var messageString; |
| |
| if (key instanceof Array) { |
| messageString = "Array of keys not supported"; |
| } |
| |
| if (key === undefined || key === null) { |
| messageString = "Invalid key"; |
| } |
| |
| if (messageString) { |
| delay(error, { |
| message: messageString |
| }); |
| return false; |
| } |
| return true; |
| }; |
| |
| this.add = function(key, value, success, error) { |
| /// <summary>Adds a new value identified by a key to the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="value">Value that is going to be added to the store.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// This method errors out if the store already contains the specified key. |
| /// </remarks> |
| |
| error = getErrorCallback(error); |
| |
| if (validateKeyInput(key, error)) { |
| if (!keys.hasOwnProperty(key)) { |
| this.addOrUpdate(key, value, success, error); |
| } else { |
| error({ |
| message: "key already exists", |
| key: key |
| }); |
| } |
| } |
| }; |
| |
| this.addOrUpdate = function(key, value, success, error) { |
| /// <summary>Adds or updates a value identified by a key to the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="value">Value that is going to be added or updated to the store.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value. |
| /// </remarks> |
| |
| error = getErrorCallback(error); |
| |
| if (validateKeyInput(key, error)) { |
| var index = keys[key]; |
| if (index === undefined) { |
| if (holes.length > 0) { |
| index = holes.splice(0, 1); |
| } else { |
| index = items.length; |
| } |
| } |
| items[index] = value; |
| keys[key] = index; |
| delay(success, key, value); |
| } |
| }; |
| |
| this.clear = function(success) { |
| /// <summary>Removes all the data associated with this store object.</summary> |
| /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param> |
| |
| items = []; |
| keys = {}; |
| holes = []; |
| |
| delay(success); |
| }; |
| |
| this.contains = function(key, success) { |
| /// <summary>Checks whether a key exists in the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param> |
| |
| var contained = keys.hasOwnProperty(key); |
| delay(success, contained); |
| }; |
| |
| this.getAllKeys = function(success) { |
| /// <summary>Gets all the keys that exist in the store.</summary> |
| /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param> |
| |
| var results = []; |
| for (var name in keys) { |
| results.push(name); |
| } |
| delay(success, results); |
| }; |
| |
| this.read = function(key, success, error) { |
| /// <summary>Reads the value associated to a key in the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| error = getErrorCallback(error); |
| |
| if (validateKeyInput(key, error)) { |
| var index = keys[key]; |
| delay(success, key, items[index]); |
| } |
| }; |
| |
| this.remove = function(key, success, error) { |
| /// <summary>Removes a key and its value from the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| error = getErrorCallback(error); |
| |
| if (validateKeyInput(key, error)) { |
| var index = keys[key]; |
| if (index !== undefined) { |
| if (index === items.length - 1) { |
| items.pop(); |
| } else { |
| items[index] = undefined; |
| holes.push(index); |
| } |
| delete keys[key]; |
| |
| // The last item was removed, no need to keep track of any holes in the array. |
| if (items.length === 0) { |
| holes = []; |
| } |
| } |
| |
| delay(success); |
| } |
| }; |
| |
| this.update = function(key, value, success, error) { |
| /// <summary>Updates the value associated to a key in the store.</summary> |
| /// <param name="key" type="String">Key string.</param> |
| /// <param name="value">New value.</param> |
| /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param> |
| /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param> |
| /// <remarks> |
| /// This method errors out if the specified key is not found in the store. |
| /// </remarks> |
| |
| error = getErrorCallback(error); |
| if (validateKeyInput(key, error)) { |
| if (keys.hasOwnProperty(key)) { |
| this.addOrUpdate(key, value, success, error); |
| } else { |
| error({ |
| message: "key not found", |
| key: key |
| }); |
| } |
| } |
| }; |
| }; |
| |
| MemoryStore.create = function(name) { |
| /// <summary>Creates a store object that uses memory storage as its underlying mechanism.</summary> |
| /// <param name="name" type="String">Store name.</param> |
| /// <returns type="Object">Store object.</returns> |
| return new MemoryStore(name); |
| }; |
| |
| MemoryStore.isSupported = function() { |
| /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary> |
| /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</returns> |
| return true; |
| }; |
| |
| MemoryStore.prototype.close = function() { |
| /// <summary>This function does nothing in MemoryStore as it does not have a connection model.</summary> |
| }; |
| |
| MemoryStore.prototype.defaultError = throwErrorCallback; |
| |
| /// <summary>Identifies the underlying mechanism used by the store.</summary> |
| MemoryStore.prototype.mechanism = "memory"; |
| |
| module.exports = MemoryStore; |
| |
| }, { |
| "./../datajs.js": 4 |
| } |
| ] |
| }, {}, [1]); |