You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ko...@apache.org on 2014/05/12 13:27:59 UTC
[7/7] git commit: [OLINGO-238] new Build infrastruture in datajs
folder
[OLINGO-238] new Build infrastruture in datajs folder
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/commit/06560a6d
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/tree/06560a6d
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/diff/06560a6d
Branch: refs/heads/master
Commit: 06560a6db8deedcc9328fbd797f194951951730a
Parents: a84a42d
Author: Sven Kobler <sv...@sap.com>
Authored: Mon May 12 13:27:23 2014 +0200
Committer: Sven Kobler <sv...@sap.com>
Committed: Mon May 12 13:27:23 2014 +0200
----------------------------------------------------------------------
.gitignore | 4 +
datajs/.gitignore | 4 +
datajs/Gruntfile.js | 115 +++
datajs/package.json | 32 +
datajs/readme.md | 20 +
datajs/src/banner.txt | 12 +
datajs/src/index.js | 22 +
datajs/src/lib/cache.js | 1351 +++++++++++++++++++++++++++++
datajs/src/lib/cache/source.js | 182 ++++
datajs/src/lib/datajs.js | 95 +++
datajs/src/lib/datajs/deferred.js | 178 ++++
datajs/src/lib/datajs/utils.js | 487 +++++++++++
datajs/src/lib/datajs/xml.js | 818 ++++++++++++++++++
datajs/src/lib/odata.js | 175 ++++
datajs/src/lib/odata/atom.js | 1417 +++++++++++++++++++++++++++++++
datajs/src/lib/odata/batch.js | 390 +++++++++
datajs/src/lib/odata/gml.js | 833 ++++++++++++++++++
datajs/src/lib/odata/handler.js | 273 ++++++
datajs/src/lib/odata/json-light.js | 1384 ++++++++++++++++++++++++++++++
datajs/src/lib/odata/json.js | 374 ++++++++
datajs/src/lib/odata/metadata.js | 499 +++++++++++
datajs/src/lib/odata/net.js | 324 +++++++
datajs/src/lib/odata/utils.js | 1110 ++++++++++++++++++++++++
datajs/src/lib/odata/xml.js | 853 +++++++++++++++++++
datajs/src/lib/store.js | 59 ++
datajs/src/lib/store/dom.js | 316 +++++++
datajs/src/lib/store/indexeddb.js | 416 +++++++++
datajs/src/lib/store/memory.js | 230 +++++
28 files changed, 11973 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8ee90f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+JSLib/
+JSLib/JSLib.csproj
+JSLib/JSLib.sln
+JSLib/Web.config
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/.gitignore
----------------------------------------------------------------------
diff --git a/datajs/.gitignore b/datajs/.gitignore
new file mode 100644
index 0000000..a396154
--- /dev/null
+++ b/datajs/.gitignore
@@ -0,0 +1,4 @@
+demo/jscripts/
+demo/jscripts/*
+demo/jscripts/datajs-0.0.0.js
+node_modules/
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/Gruntfile.js
----------------------------------------------------------------------
diff --git a/datajs/Gruntfile.js b/datajs/Gruntfile.js
new file mode 100644
index 0000000..4c98506
--- /dev/null
+++ b/datajs/Gruntfile.js
@@ -0,0 +1,115 @@
+module.exports = function(grunt) {
+ 'use strict';
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ banner: grunt.file.read('src/banner.txt'),
+ filename : '<%= pkg.name %>-<%= pkg.version %>',
+
+ browserify: {
+ datajs: {
+ files: {
+ 'build/<%= filename %>.js': ['src/index.js'],
+ },
+ options: {
+ browserifyOptions: {
+ } ,
+ bundleOptions: {
+ debug: true
+ },
+ }
+ }
+ },
+ /*
+ uglify: {
+ options: {
+ banner: '<%= banner %>',
+ sourceMap : true,
+ sourceMapName : 'build/<%= filename %>.map',
+ sourceMapIncludeSources :true,
+ --sourceMapIn : 'build/<%= filename %>.split_map',--
+ },
+ build: {
+ src: 'build/<%= filename %>.js',
+ dest: 'build/<%= filename %>.min.js'
+ }
+ },*/
+ copy: {
+ saveOrig : {
+ files: [
+ // includes files within path
+ {expand: false, flatten: true,src: './build/<%= filename %>.js', dest: 'demo/jscripts/<%= filename %>.bu_js'},
+ ]
+ },
+ toDemo: {
+ files: [
+ // includes files within path
+ {expand: false, flatten: true,src: './build/<%= filename %>.js', dest: 'demo/jscripts/<%= filename %>.js'},
+ {expand: false, flatten: true,src: './build/<%= filename %>.min.js', dest: 'demo/jscripts/<%= filename %>.min.js'},
+ {expand: false, flatten: true,src: './build/<%= filename %>.map', dest: 'demo/jscripts/<%= filename %>.map'},
+ {expand: false, flatten: true,src: './build/<%= filename %>.split_map', dest: 'demo/jscripts/<%= filename %>.split_map'},
+ ]
+ }
+ },
+ connect: {
+ proxies: [{
+ context: "/sap/bc/ds/odata/v2/", // When the url contains this...
+ host: "ldcigmd.wdf.sap.corp",
+ changeOrigin: true,
+ https: true,
+ port: 44355,
+ rejectUnauthorized: false,
+ },{
+ context: "/tests/endpoints/", // When the url contains this...
+ host: "localhost",
+ changeOrigin: true,
+ https: false,
+ port: 10092,
+ rejectUnauthorized: false,
+ },{
+ context: "/tests/common/", // When the url contains this...
+ host: "localhost",
+ changeOrigin: true,
+ https: false,
+ port: 10092,
+ rejectUnauthorized: false,
+ }],
+ demo: {
+ options: {
+ port: 4001 ,
+ hostname: "localhost",
+ base: "demo",
+ keepalive : true,
+ middleware: function (connect, options) {
+ return [
+ require("grunt-connect-proxy/lib/utils").proxyRequest ,
+ connect.static(options.base), // static content
+ connect.directory(options.base) // browse directories
+ ];
+ },
+ },
+ },
+ },
+ open: {
+ demo: {
+ path: "http://<%= connect.demo.options.hostname %>:<%= connect.demo.options.port %>/demo.html",
+ options: {
+ delay : 500,
+ }
+ }
+ },
+ });
+
+ // These plugins provide necessary tasks.
+ grunt.loadNpmTasks('grunt-browserify');
+ //grunt.loadNpmTasks('grunt-contrib-uglify');
+ grunt.loadNpmTasks('grunt-contrib-copy');
+ grunt.loadNpmTasks("grunt-connect-proxy");
+ grunt.loadNpmTasks("grunt-contrib-connect");
+
+
+
+ // Default task.
+ grunt.registerTask('build', ['browserify:datajs', 'copy:toDemo']);
+ grunt.registerTask('run', ['configureProxies', 'connect:demo']);
+};
+
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/package.json
----------------------------------------------------------------------
diff --git a/datajs/package.json b/datajs/package.json
new file mode 100644
index 0000000..7b250e8
--- /dev/null
+++ b/datajs/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "datajs",
+ "version": "0.0.0",
+ "title": "datajs library",
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "devDependencies": {
+ "browserify": "^3.40.0",
+ "grunt": "^0.4.4",
+ "grunt-browserify": "^2.0.3",
+ "grunt-connect-proxy": "^0.1.10",
+ "grunt-contrib-connect": "^0.7.1",
+ "grunt-contrib-copy": "^0.5.0",
+ "grunt-contrib-uglify": "^0.4.0",
+ "grunt-exorcise": "^0.1.0",
+ "grunt-open": "^0.2.3",
+ "http-proxy": "^1.1.4",
+ "mocha": "^1.18.2",
+ "uglifyify": "^2.1.1"
+ },
+ "testling": {
+ "harness": "mocha-tdd",
+ "files": "test/sampletest_mocca.js",
+ "browsers": [
+ "ie/8"
+ ]
+ },
+ "scripts": {
+ "test": "mocha --ui tdd"
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/readme.md
----------------------------------------------------------------------
diff --git a/datajs/readme.md b/datajs/readme.md
new file mode 100644
index 0000000..bcd2560
--- /dev/null
+++ b/datajs/readme.md
@@ -0,0 +1,20 @@
+Development
+===========
+
+Preparation
+1. npm install -g grunt-cli
+
+Installation
+1. git clone https://git-wip-us.apache.org/repos/asf/olingo-odata4-js
+1. cd datajs
+1. npm install
+
+Build datajs-x.x.x.js
+1. grunt build
+* Output is copied into the directory ...
+
+Run demo
+1. grunt run
+
+Run tests
+1. grunt test
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/src/banner.txt
----------------------------------------------------------------------
diff --git a/datajs/src/banner.txt b/datajs/src/banner.txt
new file mode 100644
index 0000000..1aefb0f
--- /dev/null
+++ b/datajs/src/banner.txt
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/src/index.js
----------------------------------------------------------------------
diff --git a/datajs/src/index.js b/datajs/src/index.js
new file mode 100644
index 0000000..2079d66
--- /dev/null
+++ b/datajs/src/index.js
@@ -0,0 +1,22 @@
+
+
+window.dataJS = require('./lib/datajs.js');
+window.OData = require('./lib/odata.js');
+
+
+extend(window.OData, require('./lib/store.js'));
+extend(window.OData, 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;
+}
+
+
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/src/lib/cache.js
----------------------------------------------------------------------
diff --git a/datajs/src/lib/cache.js b/datajs/src/lib/cache.js
new file mode 100644
index 0000000..9f5d28f
--- /dev/null
+++ b/datajs/src/lib/cache.js
@@ -0,0 +1,1351 @@
+//SK name cache.js
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache.js
+
+
+exports.storeReq = require("./store.js");
+
+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;
+
+// CONTENT START
+
+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);
+ if (intersection) {
+ var start = intersection.i - page.i;
+ var end = start + (operation.c - operation.d.length);
+ operation.d = operation.d.concat(page.d.slice(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.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[i]);
+ }
+
+ if (data.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 page = { i: start, c: data.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 arr = [];
+ var canceled = false;
+ var pendingReadRange = null;
+
+ var readMore = function (readIndex, readCount) {
+ if (!canceled) {
+ if (count >= 0 && arr.length >= count) {
+ deferred.resolve(arr);
+ } else {
+ pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
+ for (var i = 0, length = data.length; i < length && (count < 0 || arr.length < count); i++) {
+ var dataIndex = backwards ? length - i - 1 : i;
+ var item = data[dataIndex];
+ if (predicate(item)) {
+ var element = {
+ index: readIndex + dataIndex,
+ item: item
+ };
+
+ backwards ? arr.unshift(element) : arr.push(element);
+ }
+ }
+
+ // Have we reached the end of the collection?
+ if ((!backwards && data.length < readCount) || (backwards && readIndex <= 0)) {
+ deferred.resolve(arr);
+ } 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 = operation.d.length;
+ // 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);
+};
+
+// DATAJS INTERNAL START
+//window.datajs.estimateSize = estimateSize;
+exports.estimateSize = estimateSize;
+// DATAJS INTERNAL END
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/src/lib/cache/source.js
----------------------------------------------------------------------
diff --git a/datajs/src/lib/cache/source.js b/datajs/src/lib/cache/source.js
new file mode 100644
index 0000000..bcb61de
--- /dev/null
+++ b/datajs/src/lib/cache/source.js
@@ -0,0 +1,182 @@
+//SK name /cache/cache-source.js
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache-source.js
+
+var utils = require("./../datajs.js").utils;
+var odataRequest = require("./../odata.js").request;
+
+var parseInt10 = utils.parseInt10;
+var normalizeURICase = utils.normalizeURICase;
+
+// CONTENT START
+
+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 = oRta_request.request(request, function (newData) {
+ var next = newData.__next;
+ var results = newData.results;
+
+ data = data.concat(results);
+
+ if (next) {
+ currentRequest = queryForDataInternal(next, 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;
+};
+
+// DATAJS INTERNAL START
+exports.ODataCacheSource = ODataCacheSource;
+//window.datajs.ODataCacheSource = ODataCacheSource;
+// DATAJS INTERNAL END
+
+// CONTENT END
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/src/lib/datajs.js
----------------------------------------------------------------------
diff --git a/datajs/src/lib/datajs.js b/datajs/src/lib/datajs.js
new file mode 100644
index 0000000..eef7a68
--- /dev/null
+++ b/datajs/src/lib/datajs.js
@@ -0,0 +1,95 @@
+//SK name datajs.js
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// datajs.js
+
+//var utils = require('./lib/utils.js');
+//var xml = require('./lib/xml.js');
+//var deferred = require('./lib/deferred.js');
+//var odata = require('./lib/odata.js');
+
+
+//expose all external usable functions via self.apiFunc = function
+exports.version = {
+ major: 0,
+ minor: 0,
+ build: 0
+};
+
+
+exports.deferred = require('./datajs/deferred.js');
+exports.utils = require('./datajs/utils.js');
+exports.xml = require('./datajs/xml.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;
+}
+*/
+
+
+/*
+(function (window, undefined) {
+
+ var datajs = window.datajs || {};
+ var odata = window.OData || {};
+
+ // AMD support
+ if (typeof define === 'function' && define.amd) {
+ define('datajs', datajs);
+ define('OData', odata);
+ } else {
+ window.datajs = datajs;
+ window.OData = odata;
+ }
+
+ datajs.version = {
+ major: 1,
+ minor: 1,
+ build: 1
+ };
+
+ // INCLUDE: utils.js
+ // INCLUDE: xml.js
+
+ // INCLUDE: deferred.js
+
+ // INCLUDE: odata-utils.js
+ // INCLUDE: odata-net.js
+ // INCLUDE: odata-handler.js
+ // INCLUDE: odata-gml.js
+ // INCLUDE: odata-xml.js
+ // INCLUDE: odata-atom.js
+ // INCLUDE: odata-metadata.js
+ // INCLUDE: odata-json-light.js
+ // INCLUDE: odata-json.js
+ // INCLUDE: odata-batch.js
+ // INCLUDE: odata.js
+
+ // INCLUDE: store-dom.js
+ // INCLUDE: store-indexeddb.js
+ // INCLUDE: store-memory.js
+ // INCLUDE: store.js
+
+ // INCLUDE: cache-source.js
+ // INCLUDE: cache.js
+
+})(this);*/
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/06560a6d/datajs/src/lib/datajs/deferred.js
----------------------------------------------------------------------
diff --git a/datajs/src/lib/datajs/deferred.js b/datajs/src/lib/datajs/deferred.js
new file mode 100644
index 0000000..e73b2f9
--- /dev/null
+++ b/datajs/src/lib/datajs/deferred.js
@@ -0,0 +1,178 @@
+//SK name deferred.js
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// deferred.js
+
+
+// CONTENT START
+
+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();
+ }
+};
+
+// DATAJS INTERNAL START
+//window.datajs.createDeferred = createDeferred;
+//window.datajs.DjsDeferred = DjsDeferred;
+exports.createDeferred = createDeferred;
+exports.DjsDeferred = DjsDeferred;
+// DATAJS INTERNAL END