From 8f8927009ca6bb3966a6a90629d793c3cbd69c39 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Tue, 24 Mar 2015 16:17:41 +0200 Subject: [PATCH] Everlive libever --- apps/TelerikNEXT/lib/everlive.js | 5682 ++++++++++++++++++++++++++++++ 1 file changed, 5682 insertions(+) create mode 100644 apps/TelerikNEXT/lib/everlive.js diff --git a/apps/TelerikNEXT/lib/everlive.js b/apps/TelerikNEXT/lib/everlive.js new file mode 100644 index 000000000..68882ff57 --- /dev/null +++ b/apps/TelerikNEXT/lib/everlive.js @@ -0,0 +1,5682 @@ +// This script will be executed in the context of a native script module +var window = window || {}; +var isNativeScriptApplication = ((typeof android !== 'undefined' && android && android.widget && android.widget.Button) + || (typeof UIButton !== 'undefined' && UIButton)); +/*! + * Reqwest! A general purpose XHR connection manager + * (c) Dustin Diaz 2013 + * https://github.com/ded/reqwest + * license MIT + */ +!function (name, context, definition) { + if (typeof module != 'undefined' && module.exports) module.exports = definition() + else if (typeof define == 'function' && define.amd) define(definition) + else context[name] = definition() +}('reqwest', this, function () { + + // if we're in a nativescript application don't initialize this library + if (typeof (isNativeScriptApplication) !== 'undefined' && isNativeScriptApplication) { + return; + } + + var win = window + , doc = document + , twoHundo = /^20\d$/ + , byTag = 'getElementsByTagName' + , readyState = 'readyState' + , contentType = 'Content-Type' + , requestedWith = 'X-Requested-With' + , head = doc[byTag]('head')[0] + , uniqid = 0 + , callbackPrefix = 'reqwest_' + (+new Date()) + , lastValue // data stored by the most recent JSONP callback + , xmlHttpRequest = 'XMLHttpRequest' + , noop = function () { } + + , isArray = typeof Array.isArray == 'function' + ? Array.isArray + : function (a) { + return a instanceof Array + } + + , defaultHeaders = { + contentType: 'application/x-www-form-urlencoded' + , requestedWith: xmlHttpRequest + , accept: { + '*': 'text/javascript, text/html, application/xml, text/xml, */*' + , xml: 'application/xml, text/xml' + , html: 'text/html' + , text: 'text/plain' + , json: 'application/json, text/javascript' + , js: 'application/javascript, text/javascript' + } + } + + , xhr = win[xmlHttpRequest] + ? function () { + return new XMLHttpRequest() + } + : function () { + return new ActiveXObject('Microsoft.XMLHTTP') + } + , globalSetupOptions = { + dataFilter: function (data) { + return data + } + } + + function handleReadyState(r, success, error) { + return function () { + // use _aborted to mitigate against IE err c00c023f + // (can't read props on aborted request objects) + if (r._aborted) return error(r.request) + if (r.request && r.request[readyState] == 4) { + r.request.onreadystatechange = noop + if (twoHundo.test(r.request.status)) + success(r.request) + else + error(r.request) + } + } + } + + function setHeaders(http, o) { + var headers = o.headers || {} + , h + + headers.Accept = headers.Accept + || defaultHeaders.accept[o.type] + || defaultHeaders.accept['*'] + + // breaks cross-origin requests with legacy browsers + if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith + if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType + for (h in headers) + headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h]) + } + + function setCredentials(http, o) { + if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') { + http.withCredentials = !!o.withCredentials + } + } + + function generalCallback(data) { + lastValue = data + } + + function urlappend(url, s) { + return url + (/\?/.test(url) ? '&' : '?') + s + } + + function handleJsonp(o, fn, err, url) { + var reqId = uniqid++ + , cbkey = o.jsonpCallback || 'callback' // the 'callback' key + , cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId) + // , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value + , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') + , match = url.match(cbreg) + , script = doc.createElement('script') + , loaded = 0 + , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1 + + if (match) { + if (match[3] === '?') { + url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name + } else { + cbval = match[3] // provided callback func name + } + } else { + url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em + } + + win[cbval] = generalCallback + + script.type = 'text/javascript' + script.src = url + script.async = true + if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { + // need this for IE due to out-of-order onreadystatechange(), binding script + // execution to an event listener gives us control over when the script + // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html + // + // if this hack is used in IE10 jsonp callback are never called + script.event = 'onclick' + script.htmlFor = script.id = '_reqwest_' + reqId + } + + script.onload = script.onreadystatechange = function () { + if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { + return false + } + script.onload = script.onreadystatechange = null + script.onclick && script.onclick() + // Call the user callback with the last value stored and clean up values and scripts. + o.success && o.success(lastValue) + lastValue = undefined + head.removeChild(script) + loaded = 1 + } + + // Add the script to the DOM head + head.appendChild(script) + + // Enable JSONP timeout + return { + abort: function () { + script.onload = script.onreadystatechange = null + o.error && o.error({}, 'Request is aborted: timeout', {}) + lastValue = undefined + head.removeChild(script) + loaded = 1 + } + } + } + + function getRequest(fn, err) { + var o = this.o + , method = (o.method || 'GET').toUpperCase() + , url = typeof o === 'string' ? o : o.url + // convert non-string objects to query-string form unless o.processData is false + , data = (o.processData !== false && o.data && typeof o.data !== 'string') + ? reqwest.toQueryString(o.data) + : (o.data || null) + , http + + // if we're working on a GET request and we have data then we should append + // query string to end of URL and not post data + if ((o.type == 'jsonp' || method == 'GET') && data) { + url = urlappend(url, data) + data = null + } + + if (o.type == 'jsonp') return handleJsonp(o, fn, err, url) + + http = xhr() + http.open(method, url, true) + setHeaders(http, o) + setCredentials(http, o) + http.onreadystatechange = handleReadyState(this, fn, err) + o.before && o.before(http) + http.send(data) + return http + } + + function Reqwest(o, fn) { + this.o = o + this.fn = fn + + init.apply(this, arguments) + } + + function setType(url) { + var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/) + return m ? m[1] : 'js' + } + + function init(o, fn) { + + this.url = typeof o == 'string' ? o : o.url + this.timeout = null + + // whether request has been fulfilled for purpose + // of tracking the Promises + this._fulfilled = false + // success handlers + this._fulfillmentHandlers = [] + // error handlers + this._errorHandlers = [] + // complete (both success and fail) handlers + this._completeHandlers = [] + this._erred = false + this._responseArgs = {} + + var self = this + , type = o.type || setType(this.url) + + fn = fn || function () { } + + if (o.timeout) { + this.timeout = setTimeout(function () { + self.abort() + }, o.timeout) + } + + if (o.success) { + this._fulfillmentHandlers.push(function () { + o.success.apply(o, arguments) + }) + } + + if (o.error) { + this._errorHandlers.push(function () { + o.error.apply(o, arguments) + }) + } + + if (o.complete) { + this._completeHandlers.push(function () { + o.complete.apply(o, arguments) + }) + } + + function complete(resp) { + o.timeout && clearTimeout(self.timeout) + self.timeout = null + while (self._completeHandlers.length > 0) { + self._completeHandlers.shift()(resp) + } + } + + function success(resp) { + // use global data filter on response text + var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) + , r = resp.responseText = filteredResponse + if (r) { + switch (type) { + case 'json': + try { + resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')') + } catch (err) { + return error(resp, 'Could not parse JSON in response', err) + } + break + case 'js': + resp = eval(r) + break + case 'html': + resp = r + break + case 'xml': + resp = resp.responseXML + && resp.responseXML.parseError // IE trololo + && resp.responseXML.parseError.errorCode + && resp.responseXML.parseError.reason + ? null + : resp.responseXML + break + } + } + + self._responseArgs.resp = resp + self._fulfilled = true + fn(resp) + while (self._fulfillmentHandlers.length > 0) { + self._fulfillmentHandlers.shift()(resp) + } + + complete(resp) + } + + function error(resp, msg, t) { + self._responseArgs.resp = resp + self._responseArgs.msg = msg + self._responseArgs.t = t + self._erred = true + while (self._errorHandlers.length > 0) { + self._errorHandlers.shift()(resp, msg, t) + } + complete(resp) + } + + this.request = getRequest.call(this, success, error) + } + + Reqwest.prototype = { + abort: function () { + this._aborted = true + this.request.abort() + } + + , retry: function () { + init.call(this, this.o, this.fn) + } + + /** + * Small deviation from the Promises A CommonJs specification + * http://wiki.commonjs.org/wiki/Promises/A + */ + + /** + * `then` will execute upon successful requests + */ + , then: function (success, fail) { + if (this._fulfilled) { + success(this._responseArgs.resp) + } else if (this._erred) { + fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) + } else { + this._fulfillmentHandlers.push(success) + this._errorHandlers.push(fail) + } + return this + } + + /** + * `always` will execute whether the request succeeds or fails + */ + , always: function (fn) { + if (this._fulfilled || this._erred) { + fn(this._responseArgs.resp) + } else { + this._completeHandlers.push(fn) + } + return this + } + + /** + * `fail` will execute when the request fails + */ + , fail: function (fn) { + if (this._erred) { + fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) + } else { + this._errorHandlers.push(fn) + } + return this + } + } + + function reqwest(o, fn) { + return new Reqwest(o, fn) + } + + // normalize newline variants according to spec -> CRLF + function normalize(s) { + return s ? s.replace(/\r?\n/g, '\r\n') : '' + } + + function serial(el, cb) { + var n = el.name + , t = el.tagName.toLowerCase() + , optCb = function (o) { + // IE gives value="" even where there is no value attribute + // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 + if (o && !o.disabled) + cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text)) + } + , ch, ra, val, i + + // don't serialize elements that are disabled or without a name + if (el.disabled || !n) return + + switch (t) { + case 'input': + if (!/reset|button|image|file/i.test(el.type)) { + ch = /checkbox/i.test(el.type) + ra = /radio/i.test(el.type) + val = el.value + // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here + ; (!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) + } + break + case 'textarea': + cb(n, normalize(el.value)) + break + case 'select': + if (el.type.toLowerCase() === 'select-one') { + optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) + } else { + for (i = 0; el.length && i < el.length; i++) { + el.options[i].selected && optCb(el.options[i]) + } + } + break + } + } + + // collect up all form elements found from the passed argument elements all + // the way down to child elements; pass a '
' or form fields. + // called with 'this'=callback to use for serial() on each element + function eachFormElement() { + var cb = this + , e, i + , serializeSubtags = function (e, tags) { + var i, j, fa + for (i = 0; i < tags.length; i++) { + fa = e[byTag](tags[i]) + for (j = 0; j < fa.length; j++) serial(fa[j], cb) + } + } + + for (i = 0; i < arguments.length; i++) { + e = arguments[i] + if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) + serializeSubtags(e, ['input', 'select', 'textarea']) + } + } + + // standard query string style serialization + function serializeQueryString() { + return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) + } + + // { 'name': 'value', ... } style serialization + function serializeHash() { + var hash = {} + eachFormElement.apply(function (name, value) { + if (name in hash) { + hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) + hash[name].push(value) + } else hash[name] = value + }, arguments) + return hash + } + + // [ { name: 'name', value: 'value' }, ... ] style serialization + reqwest.serializeArray = function () { + var arr = [] + eachFormElement.apply(function (name, value) { + arr.push({ name: name, value: value }) + }, arguments) + return arr + } + + reqwest.serialize = function () { + if (arguments.length === 0) return '' + var opt, fn + , args = Array.prototype.slice.call(arguments, 0) + + opt = args.pop() + opt && opt.nodeType && args.push(opt) && (opt = null) + opt && (opt = opt.type) + + if (opt == 'map') fn = serializeHash + else if (opt == 'array') fn = reqwest.serializeArray + else fn = serializeQueryString + + return fn.apply(null, args) + } + + reqwest.toQueryString = function (o) { + var qs = '', i + , enc = encodeURIComponent + , push = function (k, v) { + qs += enc(k) + '=' + enc(v) + '&' + } + , k, v + + if (isArray(o)) { + for (i = 0; o && i < o.length; i++) push(o[i].name, o[i].value) + } else { + for (k in o) { + if (!Object.hasOwnProperty.call(o, k)) continue + v = o[k] + if (isArray(v)) { + for (i = 0; i < v.length; i++) push(k, v[i]) + } else push(k, o[k]) + } + } + + // spaces should be + according to spec + return qs.replace(/&$/, '').replace(/%20/g, '+') + } + + reqwest.getcallbackPrefix = function () { + return callbackPrefix + } + + // jQuery and Zepto compatibility, differences can be remapped here so you can call + // .ajax.compat(options, callback) + reqwest.compat = function (o, fn) { + if (o) { + o.type && (o.method = o.type) && delete o.type + o.dataType && (o.type = o.dataType) + o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback + o.jsonp && (o.jsonpCallback = o.jsonp) + } + return new Reqwest(o, fn) + } + + reqwest.ajaxSetup = function (options) { + options = options || {} + for (var k in options) { + globalSetupOptions[k] = options[k] + } + } + + return reqwest +}); +// RSVP.js provides simple tools for organizing asynchronous code. +// https://github.com/tildeio/rsvp.js +// Copyright (c) 2013 Yehuda Katz, Tom Dale, and contributors +(function () { + + // if we're in a nativescript application don't initialize this library + if (typeof (isNativeScriptApplication) !== 'undefined' && isNativeScriptApplication) { + return; + } + + var define, requireModule; + + (function () { + var registry = {}, seen = {}; + + define = function (name, deps, callback) { + registry[name] = { deps: deps, callback: callback }; + }; + + requireModule = function (name) { + if (seen[name]) { return seen[name]; } + seen[name] = {}; + + var mod = registry[name], + deps = mod.deps, + callback = mod.callback, + reified = [], + exports; + + for (var i = 0, l = deps.length; i < l; i++) { + if (deps[i] === 'exports') { + reified.push(exports = {}); + } else { + reified.push(requireModule(deps[i])); + } + } + + var value = callback.apply(this, reified); + return seen[name] = exports || value; + }; + })(); + + define("rsvp/all", + ["rsvp/defer", "exports"], + function (__dependency1__, __exports__) { + "use strict"; + var defer = __dependency1__.defer; + + function all(promises) { + var results = [], deferred = defer(), remaining = promises.length; + + if (remaining === 0) { + deferred.resolve([]); + } + + var resolver = function (index) { + return function (value) { + resolveAll(index, value); + }; + }; + + var resolveAll = function (index, value) { + results[index] = value; + if (--remaining === 0) { + deferred.resolve(results); + } + }; + + var rejectAll = function (error) { + deferred.reject(error); + }; + + for (var i = 0; i < promises.length; i++) { + if (promises[i] && typeof promises[i].then === 'function') { + promises[i].then(resolver(i), rejectAll); + } else { + resolveAll(i, promises[i]); + } + } + return deferred.promise; + } + + __exports__.all = all; + }); + + define("rsvp/async", + ["exports"], + function (__exports__) { + "use strict"; + var browserGlobal = (typeof window !== 'undefined') ? window : {}; + + var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; + var async; + + if (typeof process !== 'undefined' && + {}.toString.call(process) === '[object process]') { + async = function (callback, binding) { + process.nextTick(function () { + callback.call(binding); + }); + }; + } else if (BrowserMutationObserver) { + var queue = []; + + var observer = new BrowserMutationObserver(function () { + var toProcess = queue.slice(); + queue = []; + + toProcess.forEach(function (tuple) { + var callback = tuple[0], binding = tuple[1]; + callback.call(binding); + }); + }); + + var element = document.createElement('div'); + observer.observe(element, { attributes: true }); + + // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661 + window.addEventListener('unload', function () { + observer.disconnect(); + observer = null; + }); + + async = function (callback, binding) { + queue.push([callback, binding]); + element.setAttribute('drainQueue', 'drainQueue'); + }; + } else { + async = function (callback, binding) { + setTimeout(function () { + callback.call(binding); + }, 1); + }; + } + + + __exports__.async = async; + }); + + define("rsvp/config", + ["rsvp/async", "exports"], + function (__dependency1__, __exports__) { + "use strict"; + var async = __dependency1__.async; + + var config = {}; + config.async = async; + + __exports__.config = config; + }); + + define("rsvp/defer", + ["rsvp/promise", "exports"], + function (__dependency1__, __exports__) { + "use strict"; + var Promise = __dependency1__.Promise; + + function defer() { + var deferred = {}; + + var promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + + deferred.promise = promise; + return deferred; + } + + __exports__.defer = defer; + }); + + define("rsvp/events", + ["exports"], + function (__exports__) { + "use strict"; + var Event = function (type, options) { + this.type = type; + + for (var option in options) { + if (!options.hasOwnProperty(option)) { continue; } + + this[option] = options[option]; + } + }; + + var indexOf = function (callbacks, callback) { + for (var i = 0, l = callbacks.length; i < l; i++) { + if (callbacks[i][0] === callback) { return i; } + } + + return -1; + }; + + var callbacksFor = function (object) { + var callbacks = object._promiseCallbacks; + + if (!callbacks) { + callbacks = object._promiseCallbacks = {}; + } + + return callbacks; + }; + + var EventTarget = { + mixin: function (object) { + object.on = this.on; + object.off = this.off; + object.trigger = this.trigger; + return object; + }, + + on: function (eventNames, callback, binding) { + var allCallbacks = callbacksFor(this), callbacks, eventName; + eventNames = eventNames.split(/\s+/); + binding = binding || this; + + while (eventName = eventNames.shift()) { + callbacks = allCallbacks[eventName]; + + if (!callbacks) { + callbacks = allCallbacks[eventName] = []; + } + + if (indexOf(callbacks, callback) === -1) { + callbacks.push([callback, binding]); + } + } + }, + + off: function (eventNames, callback) { + var allCallbacks = callbacksFor(this), callbacks, eventName, index; + eventNames = eventNames.split(/\s+/); + + while (eventName = eventNames.shift()) { + if (!callback) { + allCallbacks[eventName] = []; + continue; + } + + callbacks = allCallbacks[eventName]; + + index = indexOf(callbacks, callback); + + if (index !== -1) { callbacks.splice(index, 1); } + } + }, + + trigger: function (eventName, options) { + var allCallbacks = callbacksFor(this), + callbacks, callbackTuple, callback, binding, event; + + if (callbacks = allCallbacks[eventName]) { + // Don't cache the callbacks.length since it may grow + for (var i = 0; i < callbacks.length; i++) { + callbackTuple = callbacks[i]; + callback = callbackTuple[0]; + binding = callbackTuple[1]; + + if (typeof options !== 'object') { + options = { detail: options }; + } + + event = new Event(eventName, options); + callback.call(binding, event); + } + } + } + }; + + + __exports__.EventTarget = EventTarget; + }); + + define("rsvp/hash", + ["rsvp/defer", "exports"], + function (__dependency1__, __exports__) { + "use strict"; + var defer = __dependency1__.defer; + + function size(object) { + var size = 0; + + for (var prop in object) { + size++; + } + + return size; + } + + function hash(promises) { + var results = {}, deferred = defer(), remaining = size(promises); + + if (remaining === 0) { + deferred.resolve({}); + } + + var resolver = function (prop) { + return function (value) { + resolveAll(prop, value); + }; + }; + + var resolveAll = function (prop, value) { + results[prop] = value; + if (--remaining === 0) { + deferred.resolve(results); + } + }; + + var rejectAll = function (error) { + deferred.reject(error); + }; + + for (var prop in promises) { + if (promises[prop] && typeof promises[prop].then === 'function') { + promises[prop].then(resolver(prop), rejectAll); + } else { + resolveAll(prop, promises[prop]); + } + } + + return deferred.promise; + } + + __exports__.hash = hash; + }); + + define("rsvp/node", + ["rsvp/promise", "rsvp/all", "exports"], + function (__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Promise = __dependency1__.Promise; + var all = __dependency2__.all; + + function makeNodeCallbackFor(resolve, reject) { + return function (error, value) { + if (error) { + reject(error); + } else if (arguments.length > 2) { + resolve(Array.prototype.slice.call(arguments, 1)); + } else { + resolve(value); + } + }; + } + + function denodeify(nodeFunc) { + return function () { + var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; + + var promise = new Promise(function (nodeResolve, nodeReject) { + resolve = nodeResolve; + reject = nodeReject; + }); + + all(nodeArgs).then(function (nodeArgs) { + nodeArgs.push(makeNodeCallbackFor(resolve, reject)); + + try { + nodeFunc.apply(this, nodeArgs); + } catch (e) { + reject(e); + } + }); + + return promise; + }; + } + + __exports__.denodeify = denodeify; + }); + + define("rsvp/promise", + ["rsvp/config", "rsvp/events", "exports"], + function (__dependency1__, __dependency2__, __exports__) { + "use strict"; + var config = __dependency1__.config; + var EventTarget = __dependency2__.EventTarget; + + function objectOrFunction(x) { + return isFunction(x) || (typeof x === "object" && x !== null); + } + + function isFunction(x) { + return typeof x === "function"; + } + + var Promise = function (resolver) { + var promise = this, + resolved = false; + + if (typeof resolver !== 'function') { + throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); + } + + if (!(promise instanceof Promise)) { + return new Promise(resolver); + } + + var resolvePromise = function (value) { + if (resolved) { return; } + resolved = true; + resolve(promise, value); + }; + + var rejectPromise = function (value) { + if (resolved) { return; } + resolved = true; + reject(promise, value); + }; + + this.on('promise:resolved', function (event) { + this.trigger('success', { detail: event.detail }); + }, this); + + this.on('promise:failed', function (event) { + this.trigger('error', { detail: event.detail }); + }, this); + + resolver(resolvePromise, rejectPromise); + }; + + var invokeCallback = function (type, promise, callback, event) { + var hasCallback = isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + try { + value = callback(event.detail); + succeeded = true; + } catch (e) { + failed = true; + error = e; + } + } else { + value = event.detail; + succeeded = true; + } + + if (objectOrFunction(value) && isFunction(value.then)) { + value.then(function (value) { + resolve(promise, value); + }, function (error) { + reject(promise, error); + }); + } else if (hasCallback && succeeded) { + resolve(promise, value); + } else if (failed) { + reject(promise, error); + } else if (type === 'resolve') { + resolve(promise, value); + } else if (type === 'reject') { + reject(promise, value); + } + }; + + Promise.prototype = { + constructor: Promise, + + then: function (done, fail) { + var thenPromise = new Promise(function () { }); + + if (this.isFulfilled) { + config.async(function () { + invokeCallback('resolve', thenPromise, done, { detail: this.fulfillmentValue }); + }, this); + } + + if (this.isRejected) { + config.async(function () { + invokeCallback('reject', thenPromise, fail, { detail: this.rejectedReason }); + }, this); + } + + this.on('promise:resolved', function (event) { + invokeCallback('resolve', thenPromise, done, event); + }); + + this.on('promise:failed', function (event) { + invokeCallback('reject', thenPromise, fail, event); + }); + + return thenPromise; + } + }; + + EventTarget.mixin(Promise.prototype); + + function resolve(promise, value) { + + if (promise === value) { + fulfill(promise, value); + } else if (objectOrFunction(value) && isFunction(value.then)) { + value.then(function (val) { + if (value !== val) { + resolve(promise, val); + } else { + fulfill(promise, val); + } + }, function (val) { + reject(promise, val); + }); + } else { + fulfill(promise, value); + } + } + + function fulfill(promise, value) { + config.async(function () { + promise.trigger('promise:resolved', { detail: value }); + promise.isFulfilled = true; + promise.fulfillmentValue = value; + }); + } + + function reject(promise, value) { + config.async(function () { + promise.trigger('promise:failed', { detail: value }); + promise.isRejected = true; + promise.rejectedReason = value; + }); + } + + + __exports__.Promise = Promise; + }); + + define("rsvp/resolve", + ["rsvp/promise", "exports"], + function (__dependency1__, __exports__) { + "use strict"; + var Promise = __dependency1__.Promise; + + + function objectOrFunction(x) { + return typeof x === "function" || (typeof x === "object" && x !== null); + } + + function resolve(thenable) { + var promise = new Promise(function (resolve, reject) { + var then; + + try { + if (objectOrFunction(thenable)) { + then = thenable.then; + + if (typeof then === "function") { + then.call(thenable, resolve, reject); + } else { + resolve(thenable); + } + + } else { + resolve(thenable); + } + + } catch (error) { + reject(error); + } + }); + + return promise; + } + + + __exports__.resolve = resolve; + }); + + define("rsvp", + ["rsvp/events", "rsvp/promise", "rsvp/node", "rsvp/all", "rsvp/hash", "rsvp/defer", "rsvp/config", "rsvp/resolve", "exports"], + function (__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + var EventTarget = __dependency1__.EventTarget; + var Promise = __dependency2__.Promise; + var denodeify = __dependency3__.denodeify; + var all = __dependency4__.all; + var hash = __dependency5__.hash; + var defer = __dependency6__.defer; + var config = __dependency7__.config; + var resolve = __dependency8__.resolve; + + function configure(name, value) { + config[name] = value; + } + + + __exports__.Promise = Promise; + __exports__.EventTarget = EventTarget; + __exports__.all = all; + __exports__.hash = hash; + __exports__.defer = defer; + __exports__.denodeify = denodeify; + __exports__.configure = configure; + __exports__.resolve = resolve; + }); + + window.RSVP = requireModule('rsvp'); +})(); +// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function () { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var + push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function (obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.4'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function (obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function (obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function (value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function (obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function (value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function (obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function (value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function (obj, iterator, context) { + var result; + any(obj, function (value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function (obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function (value, index, list) { + if (iterator.call(context, value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function (obj, iterator, context) { + return _.filter(obj, function (value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function (obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function (value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function (obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function (value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function (obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function (value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function (obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function (value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function (obj, key) { + return _.map(obj, function (value) { return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function (obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? void 0 : []; + return _[first ? 'find' : 'filter'](obj, function (value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function (obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) + _.max = function (obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = { computed: -Infinity, value: -Infinity }; + each(obj, function (value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = { value: value, computed: computed }); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function (obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = { computed: Infinity, value: Infinity }; + each(obj, function (value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = { value: value, computed: computed }); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function (obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function (value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function (value) { + return _.isFunction(value) ? value : function (obj) { return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function (obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function (value, index, list) { + return { + value: value, + index: index, + criteria: iterator.call(context, value, index, list) + }; + }).sort(function (left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function (obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value == null ? _.identity : value); + each(obj, function (value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function (obj, value, context) { + return group(obj, value, context, function (result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function (obj, value, context) { + return group(obj, value, context, function (result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function (array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function (obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function (obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function (array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function (array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function (array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function (array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function (array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function (input, shallow, output) { + each(input, function (value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function (array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function (array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function (array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function (value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function () { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function (array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function (item) { + return _.every(rest, function (other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function (array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function (value) { return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function () { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // The inverse operation to `_.zip`. If given an array of pairs it + // returns an array of the paired elements split into two left and + // right element arrays, if given an array of triples it returns a + // three element array and so on. For example, `_.unzip` given + // `[['a',1],['b',2],['c',3]]` returns the array + // [['a','b','c'],[1,2,3]]. + _.unzip = function (tuples) { + var maxLen = _.max(_.pluck(tuples, "length")) + return _.times(maxLen, _.partial(_.pluck, tuples)); + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function (list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function (array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function (array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function (start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while (idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Reusable constructor function for prototype setting. + var ctor = function () { }; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function (func, context) { + var args, bound; + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function () { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function (func) { + var args = slice.call(arguments, 1); + return function () { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function (obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) throw new Error("bindAll must be passed function names"); + each(funcs, function (f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function (func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function () { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function (func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function () { return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function (func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function (func, wait, immediate) { + var context, args, timeout, result; + var previous = 0; + var later = function () { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function () { + var now = new Date; + if (!previous && immediate === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function (func, wait, immediate) { + var timeout, result; + return function () { + var context = this, args = arguments; + var later = function () { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function (func) { + var ran = false, memo; + return function () { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function (func, wrapper) { + return function () { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function () { + var funcs = arguments; + return function () { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function (times, func) { + if (times <= 0) return func(); + return function () { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function (obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function (obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function (obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function (obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function (obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function (obj) { + each(slice.call(arguments, 1), function (source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function (obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function (key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function (obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function (obj) { + each(slice.call(arguments, 1), function (source) { + if (source) { + for (var prop in source) { + if (obj[prop] === void 0) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function (obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function (obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function (a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function (a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function (obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function (obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function (obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function (obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function (name) { + _['is' + name] = function (obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function (obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function (obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function (obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function (obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function (obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function (obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function (obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function (obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function () { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function (value) { + return value; + }; + + // Run a function **n** times. + _.times = function (n, iterator, context) { + var accum = Array(Math.max(0, n)); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function (min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function (method) { + _[method] = function (string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function (match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function (object, property) { + if (object == null) return void 0; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function (obj) { + each(_.functions(obj), function (name) { + var func = _[name] = obj[name]; + _.prototype[name] = function () { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function (prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function (text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function (match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function (match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function (data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function (obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function (obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function (name) { + var method = ArrayProto[name]; + _.prototype[name] = function () { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function (name) { + var method = ArrayProto[name]; + _.prototype[name] = function () { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function () { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function () { + return this._wrapped; + } + + }); + +}).call(this); +/** + * This script gives you the zone info key representing your device's time zone setting. + * + * @name jsTimezoneDetect + * @version 1.0.5 + * @author Jon Nylander + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://pellepim.bitbucket.org/jstz/ + * + * Copyright (c) Jon Nylander + */ + +/*jslint undef: true */ +/*global console, exports*/ + +(function (root) { + /** + * Namespace to hold all the code for timezone detection. + */ + var jstz = (function () { + 'use strict'; + var HEMISPHERE_SOUTH = 's', + + /** + * Gets the offset in minutes from UTC for a certain date. + * @param {Date} date + * @returns {Number} + */ + get_date_offset = function (date) { + var offset = -date.getTimezoneOffset(); + return (offset !== null ? offset : 0); + }, + + get_date = function (year, month, date) { + var d = new Date(); + if (year !== undefined) { + d.setFullYear(year); + } + d.setMonth(month); + d.setDate(date); + return d; + }, + + get_january_offset = function (year) { + return get_date_offset(get_date(year, 0, 2)); + }, + + get_june_offset = function (year) { + return get_date_offset(get_date(year, 5, 2)); + }, + + /** + * Private method. + * Checks whether a given date is in daylight saving time. + * If the date supplied is after august, we assume that we're checking + * for southern hemisphere DST. + * @param {Date} date + * @returns {Boolean} + */ + date_is_dst = function (date) { + var is_southern = date.getMonth() > 7, + base_offset = is_southern ? get_june_offset(date.getFullYear()) : + get_january_offset(date.getFullYear()), + date_offset = get_date_offset(date), + is_west = base_offset < 0, + dst_offset = base_offset - date_offset; + + if (!is_west && !is_southern) { + return dst_offset < 0; + } + + return dst_offset !== 0; + }, + + /** + * This function does some basic calculations to create information about + * the user's timezone. It uses REFERENCE_YEAR as a solid year for which + * the script has been tested rather than depend on the year set by the + * client device. + * + * Returns a key that can be used to do lookups in jstz.olson.timezones. + * eg: "720,1,2". + * + * @returns {String} + */ + + lookup_key = function () { + var january_offset = get_january_offset(), + june_offset = get_june_offset(), + diff = january_offset - june_offset; + + if (diff < 0) { + return january_offset + ",1"; + } else if (diff > 0) { + return june_offset + ",1," + HEMISPHERE_SOUTH; + } + + return january_offset + ",0"; + }, + + /** + * Uses get_timezone_info() to formulate a key to use in the olson.timezones dictionary. + * + * Returns a primitive object on the format: + * {'timezone': TimeZone, 'key' : 'the key used to find the TimeZone object'} + * + * @returns Object + */ + determine = function () { + var key = lookup_key(); + return new jstz.TimeZone(jstz.olson.timezones[key]); + }, + + /** + * This object contains information on when daylight savings starts for + * different timezones. + * + * The list is short for a reason. Often we do not have to be very specific + * to single out the correct timezone. But when we do, this list comes in + * handy. + * + * Each value is a date denoting when daylight savings starts for that timezone. + */ + dst_start_for = function (tz_name) { + + var ru_pre_dst_change = new Date(2010, 6, 15, 1, 0, 0, 0), // In 2010 Russia had DST, this allows us to detect Russia :) + dst_starts = { + 'America/Denver': new Date(2011, 2, 13, 3, 0, 0, 0), + 'America/Mazatlan': new Date(2011, 3, 3, 3, 0, 0, 0), + 'America/Chicago': new Date(2011, 2, 13, 3, 0, 0, 0), + 'America/Mexico_City': new Date(2011, 3, 3, 3, 0, 0, 0), + 'America/Asuncion': new Date(2012, 9, 7, 3, 0, 0, 0), + 'America/Santiago': new Date(2012, 9, 3, 3, 0, 0, 0), + 'America/Campo_Grande': new Date(2012, 9, 21, 5, 0, 0, 0), + 'America/Montevideo': new Date(2011, 9, 2, 3, 0, 0, 0), + 'America/Sao_Paulo': new Date(2011, 9, 16, 5, 0, 0, 0), + 'America/Los_Angeles': new Date(2011, 2, 13, 8, 0, 0, 0), + 'America/Santa_Isabel': new Date(2011, 3, 5, 8, 0, 0, 0), + 'America/Havana': new Date(2012, 2, 10, 2, 0, 0, 0), + 'America/New_York': new Date(2012, 2, 10, 7, 0, 0, 0), + 'Europe/Helsinki': new Date(2013, 2, 31, 5, 0, 0, 0), + 'Pacific/Auckland': new Date(2011, 8, 26, 7, 0, 0, 0), + 'America/Halifax': new Date(2011, 2, 13, 6, 0, 0, 0), + 'America/Goose_Bay': new Date(2011, 2, 13, 2, 1, 0, 0), + 'America/Miquelon': new Date(2011, 2, 13, 5, 0, 0, 0), + 'America/Godthab': new Date(2011, 2, 27, 1, 0, 0, 0), + 'Europe/Moscow': ru_pre_dst_change, + 'Asia/Amman': new Date(2013, 2, 29, 1, 0, 0, 0), + 'Asia/Beirut': new Date(2013, 2, 31, 2, 0, 0, 0), + 'Asia/Damascus': new Date(2013, 3, 6, 2, 0, 0, 0), + 'Asia/Jerusalem': new Date(2013, 2, 29, 5, 0, 0, 0), + 'Asia/Yekaterinburg': ru_pre_dst_change, + 'Asia/Omsk': ru_pre_dst_change, + 'Asia/Krasnoyarsk': ru_pre_dst_change, + 'Asia/Irkutsk': ru_pre_dst_change, + 'Asia/Yakutsk': ru_pre_dst_change, + 'Asia/Vladivostok': ru_pre_dst_change, + 'Asia/Baku': new Date(2013, 2, 31, 4, 0, 0), + 'Asia/Yerevan': new Date(2013, 2, 31, 3, 0, 0), + 'Asia/Kamchatka': ru_pre_dst_change, + 'Asia/Gaza': new Date(2010, 2, 27, 4, 0, 0), + 'Africa/Cairo': new Date(2010, 4, 1, 3, 0, 0), + 'Europe/Minsk': ru_pre_dst_change, + 'Pacific/Apia': new Date(2010, 10, 1, 1, 0, 0, 0), + 'Pacific/Fiji': new Date(2010, 11, 1, 0, 0, 0), + 'Australia/Perth': new Date(2008, 10, 1, 1, 0, 0, 0) + }; + + return dst_starts[tz_name]; + }; + + return { + determine: determine, + date_is_dst: date_is_dst, + dst_start_for: dst_start_for + }; + }()); + + /** + * Simple object to perform ambiguity check and to return name of time zone. + */ + jstz.TimeZone = function (tz_name) { + 'use strict'; + /** + * The keys in this object are timezones that we know may be ambiguous after + * a preliminary scan through the olson_tz object. + * + * The array of timezones to compare must be in the order that daylight savings + * starts for the regions. + */ + var AMBIGUITIES = { + 'America/Denver': ['America/Denver', 'America/Mazatlan'], + 'America/Chicago': ['America/Chicago', 'America/Mexico_City'], + 'America/Santiago': ['America/Santiago', 'America/Asuncion', 'America/Campo_Grande'], + 'America/Montevideo': ['America/Montevideo', 'America/Sao_Paulo'], + 'Asia/Beirut': ['Asia/Amman', 'Asia/Jerusalem', 'Asia/Beirut', 'Europe/Helsinki', 'Asia/Damascus'], + 'Pacific/Auckland': ['Pacific/Auckland', 'Pacific/Fiji'], + 'America/Los_Angeles': ['America/Los_Angeles', 'America/Santa_Isabel'], + 'America/New_York': ['America/Havana', 'America/New_York'], + 'America/Halifax': ['America/Goose_Bay', 'America/Halifax'], + 'America/Godthab': ['America/Miquelon', 'America/Godthab'], + 'Asia/Dubai': ['Europe/Moscow'], + 'Asia/Dhaka': ['Asia/Yekaterinburg'], + 'Asia/Jakarta': ['Asia/Omsk'], + 'Asia/Shanghai': ['Asia/Krasnoyarsk', 'Australia/Perth'], + 'Asia/Tokyo': ['Asia/Irkutsk'], + 'Australia/Brisbane': ['Asia/Yakutsk'], + 'Pacific/Noumea': ['Asia/Vladivostok'], + 'Pacific/Tarawa': ['Asia/Kamchatka', 'Pacific/Fiji'], + 'Pacific/Tongatapu': ['Pacific/Apia'], + 'Asia/Baghdad': ['Europe/Minsk'], + 'Asia/Baku': ['Asia/Yerevan', 'Asia/Baku'], + 'Africa/Johannesburg': ['Asia/Gaza', 'Africa/Cairo'] + }, + + timezone_name = tz_name, + + /** + * Checks if a timezone has possible ambiguities. I.e timezones that are similar. + * + * For example, if the preliminary scan determines that we're in America/Denver. + * We double check here that we're really there and not in America/Mazatlan. + * + * This is done by checking known dates for when daylight savings start for different + * timezones during 2010 and 2011. + */ + ambiguity_check = function () { + var ambiguity_list = AMBIGUITIES[timezone_name], + length = ambiguity_list.length, + i = 0, + tz = ambiguity_list[0]; + + for (; i < length; i += 1) { + tz = ambiguity_list[i]; + + if (jstz.date_is_dst(jstz.dst_start_for(tz))) { + timezone_name = tz; + return; + } + } + }, + + /** + * Checks if it is possible that the timezone is ambiguous. + */ + is_ambiguous = function () { + return typeof (AMBIGUITIES[timezone_name]) !== 'undefined'; + }; + + if (is_ambiguous()) { + ambiguity_check(); + } + + return { + name: function () { + return timezone_name; + } + }; + }; + + jstz.olson = {}; + + /* + * The keys in this dictionary are comma separated as such: + * + * First the offset compared to UTC time in minutes. + * + * Then a flag which is 0 if the timezone does not take daylight savings into account and 1 if it + * does. + * + * Thirdly an optional 's' signifies that the timezone is in the southern hemisphere, + * only interesting for timezones with DST. + * + * The mapped arrays is used for constructing the jstz.TimeZone object from within + * jstz.determine_timezone(); + */ + jstz.olson.timezones = { + '-720,0': 'Pacific/Majuro', + '-660,0': 'Pacific/Pago_Pago', + '-600,1': 'America/Adak', + '-600,0': 'Pacific/Honolulu', + '-570,0': 'Pacific/Marquesas', + '-540,0': 'Pacific/Gambier', + '-540,1': 'America/Anchorage', + '-480,1': 'America/Los_Angeles', + '-480,0': 'Pacific/Pitcairn', + '-420,0': 'America/Phoenix', + '-420,1': 'America/Denver', + '-360,0': 'America/Guatemala', + '-360,1': 'America/Chicago', + '-360,1,s': 'Pacific/Easter', + '-300,0': 'America/Bogota', + '-300,1': 'America/New_York', + '-270,0': 'America/Caracas', + '-240,1': 'America/Halifax', + '-240,0': 'America/Santo_Domingo', + '-240,1,s': 'America/Santiago', + '-210,1': 'America/St_Johns', + '-180,1': 'America/Godthab', + '-180,0': 'America/Argentina/Buenos_Aires', + '-180,1,s': 'America/Montevideo', + '-120,0': 'America/Noronha', + '-120,1': 'America/Noronha', + '-60,1': 'Atlantic/Azores', + '-60,0': 'Atlantic/Cape_Verde', + '0,0': 'UTC', + '0,1': 'Europe/London', + '60,1': 'Europe/Berlin', + '60,0': 'Africa/Lagos', + '60,1,s': 'Africa/Windhoek', + '120,1': 'Asia/Beirut', + '120,0': 'Africa/Johannesburg', + '180,0': 'Asia/Baghdad', + '180,1': 'Europe/Moscow', + '210,1': 'Asia/Tehran', + '240,0': 'Asia/Dubai', + '240,1': 'Asia/Baku', + '270,0': 'Asia/Kabul', + '300,1': 'Asia/Yekaterinburg', + '300,0': 'Asia/Karachi', + '330,0': 'Asia/Kolkata', + '345,0': 'Asia/Kathmandu', + '360,0': 'Asia/Dhaka', + '360,1': 'Asia/Omsk', + '390,0': 'Asia/Rangoon', + '420,1': 'Asia/Krasnoyarsk', + '420,0': 'Asia/Jakarta', + '480,0': 'Asia/Shanghai', + '480,1': 'Asia/Irkutsk', + '525,0': 'Australia/Eucla', + '525,1,s': 'Australia/Eucla', + '540,1': 'Asia/Yakutsk', + '540,0': 'Asia/Tokyo', + '570,0': 'Australia/Darwin', + '570,1,s': 'Australia/Adelaide', + '600,0': 'Australia/Brisbane', + '600,1': 'Asia/Vladivostok', + '600,1,s': 'Australia/Sydney', + '630,1,s': 'Australia/Lord_Howe', + '660,1': 'Asia/Kamchatka', + '660,0': 'Pacific/Noumea', + '690,0': 'Pacific/Norfolk', + '720,1,s': 'Pacific/Auckland', + '720,0': 'Pacific/Tarawa', + '765,1,s': 'Pacific/Chatham', + '780,0': 'Pacific/Tongatapu', + '780,1,s': 'Pacific/Apia', + '840,0': 'Pacific/Kiritimati' + }; + + if (typeof exports !== 'undefined') { + exports.jstz = jstz; + } else { + root.jstz = jstz; + } +})(this); +/*! + The MIT License (MIT) + + Copyright (c) 2013 Telerik AD + + 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.y distributed under the MIT license. + */ +/*! + Everlive SDK + Version 1.2.9 + */ +/*global device, define, window, navigator*/ +(function (root, factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // with require.js + define(['underscore', 'rsvp', 'reqwest', 'jstz'], function (_, rsvp, reqwest, jstz) { + return (root.Everlive = factory(_, rsvp, reqwest, jstz)); + }); + } else if (typeof (isNativeScriptApplication) !== 'undefined' && isNativeScriptApplication) { + // native script + + // wrap the native script Promise in a RSVP object + var rsvp = { + Promise: Promise + }; + root.RSVP = rsvp; + + var http = require('http'); + var reqwest = function (options) { + var httpRequestOptions = { + url: options.url, + method: options.method, + headers: options.headers || {} + }; + + if (options.data) { + httpRequestOptions.content = options.data; // NOTE: If we pass null/undefined, it will raise an exception in the http module. + } + + httpRequestOptions.headers['Accept'] = 'application/json'; + httpRequestOptions.headers['Content-Type'] = 'application/json'; + + var noop = function () { }; + var success = options.success || noop; + var error = options.error || noop; + http.request(httpRequestOptions).then( + function (response) { + var contentString = response.content.toString(); + if (response.statusCode < 400) { + // Success callback calls a custom parse function + success(contentString); + } else { + // Error callback relies on a JSON Object with ResponseText inside + error({ + responseText: contentString + }); + } + }, + function (err) { + // error: function(jqXHR, textStatus, errorThrown) + // when timeouting for example (i.e. no internet connectivity), we get an err with content { message: "timeout...", stack: null } + error({ + responseText: err + }); + }); + }; + root.reqwest = reqwest; + + module.exports = factory(exports._, root.RSVP, root.reqwest, exports.jstz); + } else if (typeof exports === 'object') { + // node js + module.exports = factory(require('underscore'), require('rsvp')); + } else { + // web browser + root.Everlive = factory(root._, root.RSVP, root.reqwest, root.jstz); + } +}(this, function (_, rsvp, reqwest, jstz) { + 'use strict'; + var slice = Array.prototype.slice; + var everliveUrl = '//api.everlive.com/v1/'; + var idField = 'Id'; + + function guardUnset(value, name, message) { + if (!message) { + message = 'The ' + name + ' is required'; + } + if (typeof value === 'undefined' || value === null) { + throw new EverliveError(message); + } + } + + // An object that keeps information about an Everlive connection + function Setup(options) { + this.url = everliveUrl; + this.apiKey = null; + this.masterKey = null; + this.token = null; + this.tokenType = null; + this.scheme = 'http'; // http or https + this._emulatorMode = options.emulatorMode; + this.parseOnlyCompleteDateTimeObjects = false; + if (typeof options === 'string') { + this.apiKey = options; + } else { + _.extend(this, options); + } + } + + // An array keeping initialization functions called by the Everlive constructor. + // These functions will be used to extend the functionality of an Everlive instance. + var initializations = []; + // The constructor of Everlive instances. + // The entry point for the SDK. + function Everlive(options) { + var self = this; + this.setup = new Setup(options); + _.each(initializations, function (init) { + init.func.call(self, options); + }); + if (Everlive.$ === null) { + Everlive.$ = self; + } + } + + // A reference to the current Everlive instance + Everlive.$ = null; + Everlive.idField = idField; + Everlive.initializations = initializations; + // Creates a new Everlive instance and set it as the current one + Everlive.init = function (options) { + Everlive.$ = null; + return new Everlive(options); + }; + Everlive.buildUrl = function (setup) { + var url = ''; + if (typeof setup.scheme === 'string') { + url += setup.scheme + ':'; + } + url += setup.url; + if (setup.apiKey) { + url += setup.apiKey + '/'; + } + return url; + }; + Everlive.prototype.data = function (collectionName) { + return new Data(this.setup, collectionName); + }; + Everlive.prototype.buildUrl = function () { + return Everlive.buildUrl(this.setup); + }; + var buildAuthHeader = function (setup, options) { + var authHeaderValue = null; + if (options && options.authHeaders === false) { + return authHeaderValue; + } + if (setup.token) { + authHeaderValue = (setup.tokenType || 'bearer') + ' ' + setup.token; + } + else if (setup.masterKey) { + authHeaderValue = 'masterkey ' + setup.masterKey; + } + if (authHeaderValue) { + return { Authorization: authHeaderValue }; + } else { + return null; + } + }; + Everlive.prototype.buildAuthHeader = function () { + return buildAuthHeader(this.setup); + }; + + // Everlive queries + (function () { + var OperatorType = { + query: 1, + + where: 100, + filter: 101, + + and: 110, + or: 111, + not: 112, + + equal: 120, + not_equal: 121, + lt: 122, + lte: 123, + gt: 124, + gte: 125, + isin: 126, + notin: 127, + all: 128, + size: 129, + regex: 130, + contains: 131, + startsWith: 132, + endsWith: 133, + + nearShpere: 140, + withinBox: 141, + withinPolygon: 142, + withinShpere: 143, + + select: 200, + exclude: 201, + + order: 300, + order_desc: 301, + + skip: 400, + take: 401, + expand: 402 + }; + + function Expression(operator, operands) { + this.operator = operator; + this.operands = operands || []; + } + + Expression.prototype = { + addOperand: function (operand) { + this.operands.push(operand); + } + }; + + function Query(filter, fields, sort, skip, take, expand) { + this.filter = filter; + this.fields = fields; + this.sort = sort; + this.toskip = skip; + this.totake = take; + this.expandExpression = expand; + this.expr = new Expression(OperatorType.query); + } + + Query.prototype = { + where: function (filter) { + if (filter) { + return this._simple(OperatorType.filter, [filter]); + } + else { + return new WhereQuery(this); + } + }, + select: function () { + return this._simple(OperatorType.select, arguments); + }, + // TODO + //exclude: function () { + // return this._simple(OperatorType.exclude, arguments); + //}, + order: function (field) { + return this._simple(OperatorType.order, [field]); + }, + orderDesc: function (field) { + return this._simple(OperatorType.order_desc, [field]); + }, + skip: function (value) { + return this._simple(OperatorType.skip, [value]); + }, + take: function (value) { + return this._simple(OperatorType.take, [value]); + }, + expand: function (expandExpression) { + return this._simple(OperatorType.expand, [expandExpression]); + }, + build: function () { + return new QueryBuilder(this).build(); + }, + _simple: function (op, oprs) { + var args = slice.call(oprs); + this.expr.addOperand(new Expression(op, args)); + return this; + } + }; + + function WhereQuery(parentQuery, exprOp, singleOperand) { + this.parent = parentQuery; + this.single = singleOperand; + this.expr = new Expression(exprOp || OperatorType.where); + this.parent.expr.addOperand(this.expr); + } + + WhereQuery.prototype = { + and: function () { + return new WhereQuery(this, OperatorType.and); + }, + or: function () { + return new WhereQuery(this, OperatorType.or); + }, + not: function () { + return new WhereQuery(this, OperatorType.not, true); + }, + _simple: function (operator) { + var args = slice.call(arguments, 1); + this.expr.addOperand(new Expression(operator, args)); + return this._done(); + }, + eq: function (field, value) { + return this._simple(OperatorType.equal, field, value); + }, + ne: function (field, value) { + return this._simple(OperatorType.not_equal, field, value); + }, + gt: function (field, value) { + return this._simple(OperatorType.gt, field, value); + }, + gte: function (field, value) { + return this._simple(OperatorType.gte, field, value); + }, + lt: function (field, value) { + return this._simple(OperatorType.lt, field, value); + }, + lte: function (field, value) { + return this._simple(OperatorType.lte, field, value); + }, + isin: function (field, value) { + return this._simple(OperatorType.isin, field, value); + }, + notin: function (field, value) { + return this._simple(OperatorType.notin, field, value); + }, + all: function (field, value) { + return this._simple(OperatorType.all, field, value); + }, + size: function (field, value) { + return this._simple(OperatorType.size, field, value); + }, + regex: function (field, value, flags) { + return this._simple(OperatorType.regex, field, value, flags); + }, + startsWith: function (field, value, flags) { + return this._simple(OperatorType.startsWith, field, value, flags); + }, + endsWith: function (field, value, flags) { + return this._simple(OperatorType.endsWith, field, value, flags); + }, + nearSphere: function (field, point, distance, metrics) { + return this._simple(OperatorType.nearShpere, field, point, distance, metrics); + }, + withinBox: function (field, pointBottomLeft, pointUpperRight) { + return this._simple(OperatorType.withinBox, field, pointBottomLeft, pointUpperRight); + }, + withinPolygon: function (field, points) { + return this._simple(OperatorType.withinPolygon, field, points); + }, + withinCenterSphere: function (field, center, radius, metrics) { + return this._simple(OperatorType.withinShpere, field, center, radius, metrics); + }, + done: function () { + if (this.parent instanceof WhereQuery) { + return this.parent._done(); + } else { + return this.parent; + } + }, + _done: function () { + if (this.single) { + return this.parent; + } else { + return this; + } + } + }; + WhereQuery.prototype.equal = WhereQuery.prototype.eq; + WhereQuery.prototype.notEqual = WhereQuery.prototype.ne; + WhereQuery.prototype.greaterThan = WhereQuery.prototype.gt; + WhereQuery.prototype.greaterThanEqual = WhereQuery.prototype.gte; + WhereQuery.prototype.lessThan = WhereQuery.prototype.lt; + WhereQuery.prototype.lessThanEqual = WhereQuery.prototype.lte; + + function QueryBuilder(query) { + this.query = query; + this.expr = query.expr; + } + + var maxDistanceConsts = { 'radians': '$maxDistance', 'km': '$maxDistanceInKilometers', 'miles': '$maxDistanceInMiles' }; + var radiusConsts = { 'radians': 'radius', 'km': 'radiusInKilometers', 'miles': 'radiusInMiles' }; + QueryBuilder.prototype = { + // TODO merge the two objects before returning them + build: function () { + var query = this.query; + if (query.filter || query.fields || query.sort || query.toskip || query.totake || query.expandExpression) { + return { + $where: query.filter || null, + $select: query.fields || null, + $sort: query.sort || null, + $skip: query.toskip || null, + $take: query.totake || null, + $expand: query.expandExpression || null + }; + } + return { + $where: this._buildWhere(), + $select: this._buildSelect(), + $sort: this._buildSort(), + $skip: this._getSkip(), + $take: this._getTake(), + $expand: this._getExpand() + }; + }, + _getSkip: function () { + var skipExpression = _.find(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.skip; + }); + return skipExpression ? skipExpression.operands[0] : null; + }, + _getTake: function () { + var takeExpression = _.find(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.take; + }); + return takeExpression ? takeExpression.operands[0] : null; + }, + _getExpand: function () { + var expandExpression = _.find(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.expand; + }); + return expandExpression ? expandExpression.operands[0] : null; + }, + _buildSelect: function () { + var selectExpression = _.find(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.select; + }); + var result = {}; + if (selectExpression) { + _.reduce(selectExpression.operands, function (memo, value) { + memo[value] = 1; + return memo; + }, result); + return result; + } + else { + return null; + } + }, + _buildSort: function () { + var sortExpressions = _.filter(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.order || value.operator === OperatorType.order_desc; + }); + var result = {}; + if (sortExpressions.length > 0) { + _.reduce(sortExpressions, function (memo, value) { + memo[value.operands[0]] = value.operator === OperatorType.order ? 1 : -1; + return memo; + }, result); + return result; + } + else { + return null; + } + }, + _buildWhere: function () { + var whereExpression = _.find(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.where; + }); + if (whereExpression) { + return this._build(new Expression(OperatorType.and, whereExpression.operands)); + } + else { + var filterExpression = _.find(this.expr.operands, function (value, index, list) { + return value.operator === OperatorType.filter; + }); + if (filterExpression) { + return filterExpression.operands[0]; + } + return null; + } + }, + _build: function (expr) { + if (this._isSimple(expr)) { + return this._simple(expr); + } + else if (this._isRegex(expr)) { + return this._regex(expr); + } + else if (this._isGeo(expr)) { + return this._geo(expr); + } + else if (this._isAnd(expr)) { + return this._and(expr); + } + else if (this._isOr(expr)) { + return this._or(expr); + } + else if (this._isNot(expr)) { + return this._not(expr); + } + }, + _isSimple: function (expr) { + return expr.operator >= OperatorType.equal && expr.operator <= OperatorType.size; + }, + _simple: function (expr) { + var term = {}, fieldTerm = {}; + var operands = expr.operands; + var operator = this._translateoperator(expr.operator); + if (operator) { + term[operator] = operands[1]; + } + else { + term = operands[1]; + } + fieldTerm[operands[0]] = term; + return fieldTerm; + }, + _isRegex: function (expr) { + return expr.operator >= OperatorType.regex && expr.operator <= OperatorType.endsWith; + }, + _regex: function (expr) { + var fieldTerm = {}; + var regex = this._getRegex(expr); + var regexValue = this._getRegexValue(regex); + var operands = expr.operands; + fieldTerm[operands[0]] = regexValue; + return fieldTerm; + }, + _getRegex: function (expr) { + var pattern = expr.operands[1]; + var flags = expr.operands[2] ? expr.operands[2] : ''; + switch (expr.operator) { + case OperatorType.regex: + return pattern instanceof RegExp ? pattern : new RegExp(pattern, flags); + case OperatorType.startsWith: + return new RegExp("^" + pattern, flags); + case OperatorType.endsWith: + return new RegExp(pattern + "$", flags); + default: + throw new EverliveError('Unknown operator type.'); + } + }, + _getRegexValue: function (regex) { + var options = ''; + if (regex.global) { + options += 'g'; + } + if (regex.multiline) { + options += 'm'; + } + if (regex.ignoreCase) { + options += 'i'; + } + return { $regex: regex.source, $options: options }; + }, + _isGeo: function (expr) { + return expr.operator >= OperatorType.nearShpere && expr.operator <= OperatorType.withinShpere; + }, + _geo: function (expr) { + var fieldTerm = {}; + var operands = expr.operands; + fieldTerm[operands[0]] = this._getGeoTerm(expr); + return fieldTerm; + }, + _getGeoTerm: function (expr) { + switch (expr.operator) { + case OperatorType.nearShpere: + return this._getNearSphereTerm(expr); + case OperatorType.withinBox: + return this._getWithinBox(expr); + case OperatorType.withinPolygon: + return this._getWithinPolygon(expr); + case OperatorType.withinShpere: + return this._getWithinCenterSphere(expr); + default: + throw new EverliveError('Unknown operator type.'); + } + }, + _getNearSphereTerm: function (expr) { + var operands = expr.operands; + var center = this._getGeoPoint(operands[1]); + var maxDistance = operands[2]; + var metrics = operands[3]; + var maxDistanceConst; + var term = { + '$nearSphere': center + }; + if (typeof maxDistance !== 'undefined') { + maxDistanceConst = maxDistanceConsts[metrics] || maxDistanceConsts.radians; + term[maxDistanceConst] = maxDistance; + } + return term; + }, + _getWithinBox: function (expr) { + var operands = expr.operands; + var bottomLeft = this._getGeoPoint(operands[1]); + var upperRight = this._getGeoPoint(operands[2]); + return { + '$within': { + '$box': [bottomLeft, upperRight] + } + }; + }, + _getWithinPolygon: function (expr) { + var operands = expr.operands; + var points = this._getGeoPoints(operands[1]); + return { + '$within': { + '$polygon': points + } + }; + }, + _getWithinCenterSphere: function (expr) { + var operands = expr.operands; + var center = this._getGeoPoint(operands[1]); + var radius = operands[2]; + var metrics = operands[3]; + var radiusConst = radiusConsts[metrics] || radiusConsts.radians; + var sphereInfo = { + 'center': center + }; + sphereInfo[radiusConst] = radius; + return { + '$within': { + '$centerSphere': sphereInfo + } + }; + }, + _getGeoPoint: function (point) { + if (_.isArray(point)) { + return new GeoPoint(point[0], point[1]); + } + return point; + }, + _getGeoPoints: function (points) { + var self = this; + return _.map(points, function (point) { + return self._getGeoPoint(point); + }); + }, + _isAnd: function (expr) { + return expr.operator === OperatorType.and; + }, + _and: function (expr) { + var i, l, term, result = {}; + var operands = expr.operands; + for (i = 0, l = operands.length; i < l; i++) { + term = this._build(operands[i]); + result = this._andAppend(result, term); + } + return result; + }, + _andAppend: function (andObj, newObj) { + var i, l, key, value, newValue; + var keys = _.keys(newObj); + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i]; + value = andObj[key]; + if (typeof value === 'undefined') { + andObj[key] = newObj[key]; + } + else { + newValue = newObj[key]; + if (typeof value === 'object' && typeof newValue === 'object') { + value = _.extend(value, newValue); + } else { + value = newValue; + } + andObj[key] = value; + } + } + return andObj; + }, + _isOr: function (expr) { + return expr.operator === OperatorType.or; + }, + _or: function (expr) { + var i, l, term, result = []; + var operands = expr.operands; + for (i = 0, l = operands.length; i < l; i++) { + term = this._build(operands[i]); + result.push(term); + } + return { $or: result }; + }, + _isNot: function (expr) { + return expr.operator === OperatorType.not; + }, + _not: function (expr) { + return { $not: this._build(expr.operands[0]) }; + }, + _translateoperator: function (operator) { + switch (operator) { + case OperatorType.equal: + return null; + case OperatorType.not_equal: + return '$ne'; + case OperatorType.gt: + return '$gt'; + case OperatorType.lt: + return '$lt'; + case OperatorType.gte: + return '$gte'; + case OperatorType.lte: + return '$lte'; + case OperatorType.isin: + return '$in'; + case OperatorType.notin: + return '$nin'; + case OperatorType.all: + return '$all'; + case OperatorType.size: + return '$size'; + } + throw new EverliveError('Unknown operator type.'); + } + }; + + Everlive.Query = Query; + Everlive.QueryBuilder = QueryBuilder; + }()); + + // Everlive requests + var Request = (function () { + // The headers used by the Everlive services + var Headers = { + filter: 'X-Everlive-Filter', + select: 'X-Everlive-Fields', + sort: 'X-Everlive-Sort', + skip: 'X-Everlive-Skip', + take: 'X-Everlive-Take', + expand: 'X-Everlive-Expand' + }; + + var _self = null; + + // The Request type is an abstraction over Ajax libraries + // A Request object needs information about the Everlive connection and initialization options + function Request(setup, options) { + guardUnset(setup, 'setup'); + guardUnset(options, 'options'); + this.setup = setup; + this.method = null; + this.endpoint = null; + this.data = null; + this.headers = {}; + // TODO success and error callbacks should be uniformed for all ajax libs + this.success = null; + this.error = null; + this.parse = Request.parsers.simple; + _.extend(this, options); + this._init(options); + _self = this; + } + + Request.prototype = { + // Calls the underlying Ajax library + send: function () { + Everlive.sendRequest(this); + }, + // Returns an authorization header used by the request. + // If there is a logged in user for the Everlive instance then her/his authentication will be used. + buildAuthHeader: buildAuthHeader, + // Builds the URL of the target Everlive service + buildUrl: function buildUrl(setup) { + return Everlive.buildUrl(setup); + }, + // Processes the given query to return appropriate headers to be used by the request + buildQueryHeaders: function buildQueryHeaders(query) { + if (query) { + if (query instanceof Everlive.Query) { + return Request.prototype._buildQueryHeaders(query); + } + else { + return Request.prototype._buildFilterHeader(query); + } + } + else { + return {}; + } + }, + // Initialize the Request object by using the passed options + _init: function (options) { + _.extend(this.headers, this.buildAuthHeader(this.setup, options), this.buildQueryHeaders(options.filter), options.headers); + }, + // Translates an Everlive.Query to request headers + _buildQueryHeaders: function (query) { + query = query.build(); + var headers = {}; + if (query.$where !== null) { + headers[Headers.filter] = JSON.stringify(query.$where); + } + if (query.$select !== null) { + headers[Headers.select] = JSON.stringify(query.$select); + } + if (query.$sort !== null) { + headers[Headers.sort] = JSON.stringify(query.$sort); + } + if (query.$skip !== null) { + headers[Headers.skip] = query.$skip; + } + if (query.$take !== null) { + headers[Headers.take] = query.$take; + } + if (query.$expand !== null) { + headers[Headers.expand] = JSON.stringify(query.$expand); + } + return headers; + }, + // Creates a header from a simple filter + _buildFilterHeader: function (filter) { + var headers = {}; + headers[Headers.filter] = JSON.stringify(filter); + return headers; + } + }; + // Exposes the Request constructor + Everlive.Request = Request; + // A utility method for creating requests for the current Everlive instance + Everlive.prototype.request = function (attrs) { + return new Request(this.setup, attrs); + }; + function parseIsoDateString(string) { + if (_self && _self.setup && _self.setup.parseOnlyCompleteDateTimeObjects) { + if (/^\d{4}-\d{2}-\d{2}$/.test(string)) { + // Date + return null; + } + + if (/^(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|((\+|-)(\d{2}):(\d{2})))?$/.test(string)) { + // Time + return null; + } + } + + var match; + if (match = string.match(/^(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|((\+|-)(\d{2}):(\d{2}))))?))$/)) { + // DateTime + var secondParts = match[12]; + if (secondParts) { + if (secondParts.length > 3) { + secondParts = Math.round(Number(secondParts.substr(0, 3) + '.' + secondParts.substr(3))); + } + else if (secondParts.length < 3) { + // if the secondParts are one or two characters then two or one zeros should be appended + // in order to have the correct number for milliseconds ('.67' means 670ms not 67ms) + secondParts += secondParts.length === 2 ? '0' : '00'; + } + } + var date = new Date( + Date.UTC( + Number(match[1]), // year + (Number(match[3]) - 1) || 0, // month + Number(match[5]) || 0, // day + Number(match[7]) || 0, // hour + Number(match[8]) || 0, // minute + Number(match[10]) || 0, // second + Number(secondParts) || 0 + ) + ); + + if (match[13] && match[13] !== "Z") { + var h = Number(match[16]) || 0, + m = Number(match[17]) || 0; + + h *= 3600000; + m *= 60000; + + var offset = h + m; + if (match[15] === "+") + offset = -offset; + + date = new Date(date.valueOf() + offset); + } + + return date; + } else { + return null; + } + }; + + function jsonDateReviver(key, value) { + if (typeof value === 'string') { + var date = parseIsoDateString(value); + if (date) { + value = date; + } + } + return value; + } + + function traverse(obj, func) { + var key, value, newValue; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + value = obj[key]; + newValue = func(key, value); + obj[key] = newValue; + if (value === newValue && typeof value === 'object') { + traverse(value, func); + } + } + } + } + + function traverseAndRevive(data) { + traverse(data, jsonDateReviver); + } + + Everlive._traverseAndRevive = traverseAndRevive; + function parseResult(data) { + if (typeof data === 'string' && data.length > 0) { + data = JSON.parse(data, jsonDateReviver); + } + else if (typeof data === 'object') { + traverseAndRevive(data); + } + if (data) { + return { result: data.Result, count: data.Count }; + } + else { + return data; + } + } + + function parseError(error) { + if (typeof error === 'string' && error.length > 0) { + try { + error = JSON.parse(error); + return { message: error.message, code: error.errorCode }; + } + catch (e) { + return error; + } + } + else { + return error; + } + } + + function parseSingleResult(data) { + if (typeof data === 'string' && data.length > 0) { + data = JSON.parse(data, jsonDateReviver); + } + else if (typeof data === 'object') { + traverseAndRevive(data); + } + if (data) { + return { result: data.Result }; + } + else { + return data; + } + } + + function parseUpdateResult(data) { + if (typeof data === 'string' && data.length > 0) { + data = JSON.parse(data, jsonDateReviver); + } + else if (typeof data === 'object') { + traverseAndRevive(data); + } + if (data) { + return { result: data.Result, ModifiedAt: data.ModifiedAt }; + } + else { + return data; + } + } + + Request.parsers = { + simple: { + result: parseResult, + error: parseError + }, + single: { + result: parseSingleResult, + error: parseError + }, + update: { + result: parseUpdateResult, + error: parseError + } + }; + Everlive.disableRequestCache = function (url, method) { + if (method === 'GET') { + var timestamp = (new Date()).getTime(); + var separator = url.indexOf('?') > -1 ? '&' : '?'; + url += separator + '_el=' + timestamp; + } + return url; + }; + // TODO built for reqwest + if (typeof Everlive.sendRequest === 'undefined') { + Everlive.sendRequest = function (request) { + var url = request.buildUrl(request.setup) + request.endpoint; + url = Everlive.disableRequestCache(url, request.method); + //var data = request.method === 'GET' ? request.data : JSON.stringify(request.data); + var data = (request.method === 'GET' || request.method === 'DELETE') ? request.data : JSON.stringify(request.data); + + //$.ajax(url, { + reqwest({ + url: url, + method: request.method, + data: data, + headers: request.headers, + type: 'json', + contentType: 'application/json', + crossOrigin: true, + //processData: request.method === "GET", + success: function (data, textStatus, jqXHR) { + request.success.call(request, request.parse.result(data)); + }, + error: function (jqXHR, textStatus, errorThrown) { + request.error.call(request, request.parse.error(jqXHR.responseText)); + } + }); + }; + } + return Request; + }()); + + // rsvp promises + Everlive.getCallbacks = function (success, error) { + var promise; + if (typeof success !== 'function' && typeof error !== 'function') { + promise = new rsvp.Promise(function (resolve, reject) { + success = function (data) { + resolve(data); + }; + error = function (error) { + reject(error); + }; + }); + } + return { promise: promise, success: success, error: error }; + }; + // whenjs promises + //Everlive.getCallbacks = function (success, error) { + // var promise; + // if (typeof success !== "function" && typeof error !== "function") { + // promise = when.defer(); + // success = function (data) { + // promise.resolve(data); + // }; + // error = function (error) { + // promise.reject(error); + // }; + // } + // return { promise: promise.promise, success: success, error: error }; + //}; + function buildPromise(operation, success, error) { + var callbacks = Everlive.getCallbacks(success, error); + operation(callbacks.success, callbacks.error); + return callbacks.promise; + } + + function mergeResultData(data, success) { + return function (res, response) { + var attrs = res.result; + // support for kendo observable array + if (_.isArray(data) || typeof data.length === 'number') { + _.each(data, function (item, index) { + _.extend(item, attrs[index]); + }); + } + else { + _.extend(data, attrs); + } + success(res, response); + }; + } + + function mergeUpdateResultData(data, success) { + return function (res) { + var modifiedAt = res.ModifiedAt; + data.ModifiedAt = modifiedAt; + success(res); + }; + } + + function Data(setup, collectionName) { + this.setup = setup; + this.collectionName = collectionName; + this.options = null; + } + + // Everlive base CRUD functions + Data.prototype = { + withHeaders: function (headers) { + var options = this.options || {}; + options.headers = _.extend(options.headers || {}, headers); + this.options = options; + return this; + }, + expand: function (expandExpression) { + var expandHeader = { + 'X-Everlive-Expand': JSON.stringify(expandExpression) + }; + return this.withHeaders(expandHeader); + }, + _createRequest: function (options) { + _.extend(options, this.options); + this.options = null; + return new Request(this.setup, options); + }, + // TODO implement options: { requestSettings: { executeServerCode: false } }. power fields queries could be added to that options argument + get: function (filter, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = self._createRequest({ + method: 'GET', + endpoint: self.collectionName, + filter: filter, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + // TODO handle options + // TODO think to pass the id as a filter + getById: function (id, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = self._createRequest({ + method: 'GET', + endpoint: self.collectionName + '/' + id, + parse: Request.parsers.single, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + count: function (filter, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = self._createRequest({ + method: 'GET', + endpoint: self.collectionName + '/_count', + filter: filter, + parse: Request.parsers.single, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + create: function (data, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = self._createRequest({ + method: 'POST', + endpoint: self.collectionName, + data: data, + parse: Request.parsers.single, + success: mergeResultData(data, success), + error: error + }); + request.send(); + }, success, error); + }, + rawUpdate: function (attrs, filter, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = self.collectionName; + var ofilter = null; // request options filter + // if filter is string than will update a single item using the filter as an identifier + if (typeof filter === 'string') { + endpoint += '/' + filter; + } + // else if it is an object than we will use it as a query filter + else if (typeof filter === 'object') { + ofilter = filter; + } + var request = self._createRequest({ + method: 'PUT', + endpoint: endpoint, + data: attrs, + filter: ofilter, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + _update: function (attrs, filter, single, replace, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = self.collectionName; + var requestSuccess = success; + if (single) { + endpoint += '/' + attrs[idField]; + requestSuccess = mergeUpdateResultData(attrs, success); + } + var data = {}; + data[replace ? '$replace' : '$set'] = attrs; + var request = self._createRequest({ + method: 'PUT', + endpoint: endpoint, + parse: Request.parsers.update, + data: data, + filter: filter, + success: requestSuccess, + error: error + }); + request.send(); + }, success, error); + }, + updateSingle: function (model, success, error) { + return this._update(model, null, true, false, success, error); + }, + update: function (model, filter, success, error) { + return this._update(model, filter, false, false, success, error); + }, + //replaceSingle: function (model, success, error) { + // return this._update(model, null, true, true, success, error); + //}, + _destroy: function (attrs, filter, single, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = self.collectionName; + if (single) { + endpoint += '/' + attrs[idField]; + } + var request = self._createRequest({ + method: 'DELETE', + endpoint: endpoint, + filter: filter, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + destroySingle: function (model, success, error) { + return this._destroy(model, null, true, success, error); + }, + destroy: function (filter, success, error) { + return this._destroy(null, filter, false, success, error); + }, + setAcl: function (acl, filter, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = self.collectionName; + + if (typeof filter === 'string') { // if filter is string than will update a single item using the filter as an identifier + endpoint += '/' + filter; + } + else if (typeof filter === 'object') { // else if it is an object than we will use it's id property + endpoint += '/' + filter[idField]; + } + endpoint += '/_acl'; + var method, data; + if (acl === null) { + method = 'DELETE'; + } else { + method = 'PUT'; + data = acl; + } + + var request = self._createRequest({ + method: method, + endpoint: endpoint, + data: data, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + setOwner: function (ownerId, filter, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = self.collectionName; + + if (typeof filter === 'string') { // if filter is string than will update a single item using the filter as an identifier + endpoint += '/' + filter; + } + else if (typeof filter === 'object') { // else if it is an object than we will use it's id property + endpoint += '/' + filter[idField]; + } + endpoint += '/_owner'; + + var request = self._createRequest({ + method: 'PUT', + endpoint: endpoint, + data: { + Owner: ownerId + }, + success: success, + error: error + }); + request.send(); + }, success, error); + }, + save: function (model, success, error) { + var self = this; + var isNew = this.isNew(model); + return buildPromise(function (success, error) { + function saveSuccess(res) { + res.type = isNew ? 'create' : 'update'; + success(res); + } + + function saveError(err) { + err.type = isNew ? 'create' : 'update'; + error(err); + } + + if (isNew) { + return self.create(model, saveSuccess, saveError); + } + else { + return self.updateSingle(model, saveSuccess, saveError); + } + }, success, error); + }, + isNew: function (model) { + return typeof model[idField] === 'undefined'; + } + }; + Everlive.Data = Data; + + //TODO add a function for calculating the distances in geospatial queries + + function GeoPoint(longitude, latitude) { + this.longitude = longitude || 0; + this.latitude = latitude || 0; + } + + Everlive.GeoPoint = GeoPoint; + + var AuthStatus = { + unauthenticated: 'unauthenticated', + masterKey: 'masterKey', + invalidAuthentication: 'invalidAuthentication', + authenticated: 'authenticated' + }; + Everlive.AuthStatus = AuthStatus; + function getAuthInfo(setup, getUser, success, error) { + if (setup.masterKey) { + return buildPromise(function (success, error) { + success({ status: AuthStatus.masterKey }); + }, success, error); + } + if (!setup.token) { + return buildPromise(function (success, error) { + success({ status: AuthStatus.unauthenticated }); + }, success, error); + } + + var errorcb; + if (success) { + errorcb = function (err) { + if (err && err.code === 601) { // invalid request, i.e. the access token is invalid or missing + success({ status: AuthStatus.invalidAuthentication }); + } + else { + error(err); + } + }; + } + var promise = getUser(success, errorcb); + if (promise) { + promise = promise.then(function (res) { + return { status: AuthStatus.authenticated, user: res.result }; + }, function (err) { + if (err && err.code === 601) { // invalid request, i.e. the access token is invalid or missing + return { status: AuthStatus.invalidAuthentication }; + } + else { + throw err; + } + }); + } + return promise; + } + + Everlive.prototype.authInfo = function (success, error) { + return getAuthInfo(this.setup, _.bind(this.Users.getById, this.Users, 'me'), success, error); + }; + + var addUsersFunctions = function (ns) { + ns._loginSuccess = function (data) { + var result = data.result; + var setup = this.setup; + setup.token = result.access_token; + setup.tokenType = result.token_type; + }; + ns._logoutSuccess = function () { + var setup = this.setup; + setup.token = null; + setup.tokenType = null; + }; + ns.register = function (username, password, attrs, success, error) { + guardUnset(username, 'username'); + guardUnset(password, 'password'); + + var user = { + Username: username, + Password: password + }; + _.extend(user, attrs); + return this.create(user, success, error); + }; + ns.login = function (username, password, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = new Request(self.setup, { + method: 'POST', + endpoint: 'oauth/token', + data: { + username: username, + password: password, + grant_type: 'password' + }, + authHeaders: false, + parse: Request.parsers.single, + success: function () { + self._loginSuccess.apply(self, arguments); + success.apply(null, arguments); + }, + error: error + }); + request.send(); + }, success, error); + }; + ns.currentUser = function (success, error) { + var self = this; + return buildPromise(function (success, error) { + getAuthInfo(self.setup, _.bind(self.getById, self, 'me')) + .then(function (res) { + if (typeof res.user !== 'undefined') { + success({ result: res.user }); + } + else { + success({ result: null }); + } + }, function (err) { + error(err); + }); + }, success, error); + }; + ns.changePassword = function (username, password, newPassword, keepTokens, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = 'Users/changepassword'; + if (keepTokens) { + endpoint += '?keepTokens=true'; + } + var request = new Request(self.setup, { + method: 'POST', + endpoint: endpoint, + data: { + Username: username, + Password: password, + NewPassword: newPassword + }, + authHeaders: false, + parse: Request.parsers.single, + success: success, + error: error + }); + request.send(); + }, success, error); + }; + ns.logout = function (success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = new Request(self.setup, { + method: 'GET', + endpoint: 'oauth/logout', + success: function () { + self._logoutSuccess.apply(self, arguments); + success.apply(null, arguments); + }, + error: error + }); + request.send(); + }, success, error); + }; + + ns._loginWithProvider = function (identity, success, error) { + var user = { + Identity: identity + }; + var self = this; + return buildPromise(function (success, error) { + var request = new Request(self.setup, { + method: 'POST', + endpoint: 'Users', + data: user, + authHeaders: false, + parse: Request.parsers.single, + success: function () { + self._loginSuccess.apply(self, arguments); + success.apply(null, arguments); + }, + error: error + }); + request.send(); + }, success, error); + }; + ns._linkWithProvider = function (identity, userId, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = new Request(self.setup, { + method: 'POST', + endpoint: 'Users/' + userId + '/link', + data: identity, + parse: Request.parsers.single, + success: success, + error: error + }); + request.send(); + }, success, error); + }; + ns._unlinkFromProvider = function (providerName, userId, success, error) { + var identity = { + Provider: providerName + }; + var self = this; + return buildPromise(function (success, error) { + var request = new Request(self.setup, { + method: 'POST', + endpoint: 'Users/' + userId + '/unlink', + data: identity, + parse: Request.parsers.single, + success: success, + error: error + }); + request.send(); + }, success, error); + }; + + ns.loginWithFacebook = function (accessToken, success, error) { + var identity = { + Provider: 'Facebook', + Token: accessToken + }; + return ns._loginWithProvider(identity, success, error); + }; + + ns.linkWithFacebook = function (userId, accessToken, success, error) { + var identity = { + Provider: 'Facebook', + Token: accessToken + }; + return ns._linkWithProvider(identity, userId, success, error); + }; + + ns.unlinkFromFacebook = function (userId, success, error) { + return ns._unlinkFromProvider('Facebook', userId, success, error); + }; + + ns.loginWithADFS = function (accessToken, success, error) { + var identity = { + Provider: 'ADFS', + Token: accessToken + }; + return ns._loginWithProvider(identity, success, error); + }; + + ns.linkWithADFS = function (userId, accessToken, success, error) { + var identity = { + Provider: 'ADFS', + Token: accessToken + }; + return ns._linkWithProvider(identity, userId, success, error); + }; + + ns.unlinkFromADFS = function (userId, success, error) { + return ns._unlinkFromProvider('ADFS', userId, success, error); + }; + + ns.loginWithLiveID = function (accessToken, success, error) { + var identity = { + Provider: 'LiveID', + Token: accessToken + }; + return ns._loginWithProvider(identity, success, error); + }; + + ns.linkWithLiveID = function (userId, accessToken, success, error) { + var identity = { + Provider: 'LiveID', + Token: accessToken + }; + return ns._linkWithProvider(identity, userId, success, error); + }; + + ns.unlinkFromLiveID = function (userId, success, error) { + return ns._unlinkFromProvider('LiveID', userId, success, error); + }; + + ns.loginWithGoogle = function (accessToken, success, error) { + var identity = { + Provider: 'Google', + Token: accessToken + }; + + return ns._loginWithProvider(identity, success, error); + }; + + ns.linkWithGoogle = function (userId, accessToken, success, error) { + var identity = { + Provider: 'Google', + Token: accessToken + }; + + return ns._linkWithProvider(identity, userId, success, error); + }; + + ns.unlinkFromGoogle = function (userId, success, error) { + return ns._unlinkFromProvider('Google', userId, success, error); + }; + + ns.loginWithTwitter = function (token, tokenSecret, success, error) { + var identity = { + Provider: 'Twitter', + Token: token, + TokenSecret: tokenSecret + }; + + return ns._loginWithProvider(identity, success, error); + }; + + ns.linkWithTwitter = function (userId, token, tokenSecret, success, error) { + var identity = { + Provider: 'Twitter', + Token: token, + TokenSecret: tokenSecret + }; + + return ns._linkWithProvider(identity, userId, success, error); + }; + + ns.unlinkFromTwitter = function (userId, success, error) { + return ns._unlinkFromProvider('Twitter', userId, success, error); + }; + }; + + var addFilesFunctions = function (ns) { + ns.getUploadUrl = function () { + return Everlive.buildUrl(this.setup) + this.collectionName; + }; + ns.getDownloadUrl = function (fileId) { + return Everlive.buildUrl(this.setup) + this.collectionName + '/' + fileId + '/Download'; + }; + ns._getUpdateUrl = function (fileId) { + return this.collectionName + '/' + fileId + '/Content'; + }; + ns.getUpdateUrl = function (fileId) { + return Everlive.buildUrl(this.setup) + this._getUpdateUrl(fileId); + }; + ns.updateContent = function (fileId, file, success, error) { + var self = this; + return buildPromise(function (success, error) { + var endpoint = self._getUpdateUrl(fileId); + // the passed file content is base64 encoded + var request = self._createRequest({ + method: 'PUT', + endpoint: endpoint, + data: file, + success: success, + error: error + }); + request.send(); + }, success, error); + }; + ns.getDownloadUrlById = function (fileId, success, error) { + var self = this; + return buildPromise(function (success, error) { + var request = self._createRequest({ + method: 'GET', + endpoint: self.collectionName + '/' + fileId, + parse: Request.parsers.single, + success: function (data) { + success(data.result.Uri); + }, + error: error + }); + request.send(); + }, success, error); + }; + }; + + //#region Push + + //Constants for different platforms in Everlive + var Platform = { + WindowsPhone: 1, + Windows: 2, + Android: 3, + iOS: 4, + OSX: 5, + Blackberry: 6, + Nokia: 7, + Unknown: 100 + }; + + //Global event handlers for push notification events. Required by the cordova PushNotifications plugin that we use. + Everlive.PushCallbacks = {}; + + var Push = function (el) { + this._el = el; + this.notifications = el.data('Push/Notifications'); + this.devices = el.data('Push/Devices'); + }; + Push.prototype = { + /** + * Ensures that the Push Plugin has been loaded and is ready to use. + */ + ensurePushIsAvailable: function () { + var isPushNotificationPluginAvailable = (typeof window !== 'undefined' && window.plugins && window.plugins.pushNotification); + + if (!isPushNotificationPluginAvailable) { + throw new EverliveError("The push notification plugin is not available. Ensure that the pushNotification plugin is included " + + "and use after `\deviceready\` event has been fired."); + } + }, + + /** + * @deprecated since version 1.2.7 + * Returns the current device for sending push + * @param [emulatorMode] {Boolean} + * A flag whether the application is in emulator mode + * @returns {CurrentDevice} + * Return an instance of the CurrentDevice + */ + currentDevice: function (emulatorMode) { + this.ensurePushIsAvailable(); + + if (arguments.length === 0) { + emulatorMode = this._el.setup._emulatorMode; + } + + if (!window.cordova) { + throw new EverliveError('Error: currentDevice() can only be called from within a hybrid mobile app, after \'deviceready\' event has been fired.'); + } + + if (!this._currentDevice) { + this._currentDevice = new CurrentDevice(this); + } + + this._currentDevice.emulatorMode = emulatorMode; + + return this._currentDevice; + }, + + /** + * Enables the notifications on the device and registers it for push with Telerik Backend Services if it hasn't + * already been registered. If it was registered the registration details are updated. + * @param settings {Object} + * Settings for the registration. Can include custom parameters to be saved in backend services. + * @param success + * Callback to invoke on success. + * @param error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if succes/error are supplied. + */ + register: function (settings, success, error) { + this.ensurePushIsAvailable(); + + var currentDevice = this.currentDevice(); + if (settings && settings.android) { + settings.android.senderID = settings.android.projectNumber || settings.android.senderID; + } + + var successCallback = function (token, callback) { + return function () { + var result = new DeviceRegistrationResult(token); + callback(result); + }; + }; + + var everliveErrorCallback = function (err, callback) { + var registrationError = DeviceRegistrationError.fromEverliveError(err); + callback(registrationError); + }; + + return buildPromise(function (successCb, errorCb) { + currentDevice.enableNotifications(settings, function (response) { + var token = response.token; + var customParameters = settings ? settings.customParameters : undefined; + currentDevice.getRegistration() + .then(function () { + currentDevice.updateRegistration(customParameters, successCallback(token, successCb), function (err) { + everliveErrorCallback(err, errorCb); + }); + }, function (err) { + if (err.code === 801) { + currentDevice.register(customParameters, successCallback(token, successCb), errorCb); + } else { + everliveErrorCallback(err, errorCb); + } + }); + }, function (err) { + var deviceRegistrationError = DeviceRegistrationError.fromPluginError(err); + errorCb(deviceRegistrationError); + }); + }, success, error); + }, + + /** + * Disables push notifications for the current device. This method invalidates any push tokens + * that were obtained for the device from the current application. The device will also be unregistered from + * Telerik Backend Services. + * + * @param onSuccess + * Callback to invoke on success. + * @param onError + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if succes/error are supplied. + */ + unregister: function (onSuccess, onError) { + this.ensurePushIsAvailable(); + + var currentDevice = this.currentDevice(); + return currentDevice.disableNotifications.apply(currentDevice, arguments); + }, + + /** + * Updates the registration for the current device. + * + * @param {Object} customParameters + * Custom parameters for the registration. If undefined, customParameters are not updated. + * @param {Function} onSuccess + * Callback to invoke on success. + * @param {Function} onError + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + updateRegistration: function (customParameters, onSuccess, onError) { + this.ensurePushIsAvailable(); + + var currentDevice = this.currentDevice(); + return currentDevice.updateRegistration.apply(currentDevice, arguments); + }, + + /** + * Sets the badge number on the server + * + * @param {(number|string)} badge + * The number to be set as a badge. + * @param {Function} onSuccess + * Callback to invoke on success. + * @param {Function} onError + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + setBadgeNumber: function (badge, onSuccess, onError) { + this.ensurePushIsAvailable(); + + badge = Number(badge); + if (isNaN(badge)) { + return buildPromise(function (success, error) { + error(new EverliveError('The badge must have a numeric value')); + }, onSuccess, onError); + } + + var deviceRegistration = {}; + var currentDevice = this.currentDevice(); + var deviceId = currentDevice._getDeviceId(); + deviceRegistration.Id = 'HardwareId/' + encodeURIComponent(deviceId); + deviceRegistration.BadgeCounter = badge; + return buildPromise(function (successCb, errorCb) { + currentDevice._pushHandler.devices.updateSingle(deviceRegistration).then( + function () { + if (window.plugins && window.plugins.pushNotification) { + return window.plugins.pushNotification.setApplicationIconBadgeNumber(successCb, errorCb, badge); + } else { + return successCb(); + } + }, errorCb) + }, onSuccess, onError); + }, + + /** + * Clears the badge number on the server by setting it to 0 + * + * @param {Function} onSuccess + * Callback to invoke on success. + * @param {Function} onError + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + clearBadgeNumber: function (onSuccess, onError) { + this.ensurePushIsAvailable(); + + return this.setBadgeNumber(0, onSuccess, onError); + }, + + /** + * Returns the push registration for the current device. + * + * @param {Function} onSuccess + * Callback to invoke on success. + * @param {Function} onError + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + getRegistration: function (onSuccess, onError) { + this.ensurePushIsAvailable(); + + var currentDevice = this.currentDevice(); + return currentDevice.getRegistration.apply(currentDevice, arguments); + }, + + /** + * Sends a push message + * @param notification {Object} + * The push notification object + * @param onSuccess + * Callback to invoke on success. + * @param onError + * Callback to invoke on error. + */ + send: function (notification, onSuccess, onError) { + this.ensurePushIsAvailable(); + + return this.notifications.create.apply(this.notifications, arguments); + }, + + /** + * iOS: Checks if the Notifications are enabled for this Application in the Device's Notification Center + * Windows Phone: Checks if the Application has an active opened Channel for communication with the Notification Service. Not relying on the device notification settings. + * Android: Checks if the Application has established connection with the Notification Service. Not relying on the device notification settings. + * @param onSuccess + * Callback to invoke on successful check - passes one boolean value - true or false + * @param onError + * Callback to invoke when an error in the push plugin has occured. + * @returns {*} + * A promise for the operation, or void if success/error are supplied. + */ + areNotificationsEnabled: function (options, onSuccess, onError) { + this.ensurePushIsAvailable(); + + options = options || {}; + var pushNotification = window.plugins.pushNotification; + + return buildPromise(function (successCb, errorCb) { + pushNotification.areNotificationsEnabled(successCb, errorCb, options); + }, onSuccess, onError); + } + }; + + var PushSettings = { + iOS: { + badge: true, + sound: true, + alert: true + }, + android: { + senderID: null + }, + notificationCallbackAndroid: null, + notificationCallbackIOS: null + }; + + var CurrentDevice = function (pushHandler) { + this._pushHandler = pushHandler; + this._initSuccessCallback = null; + this._initErrorCallback = null; + + //Suffix for the global callback functions + this._globalFunctionSuffix = null; + + this.pushSettings = null; + this.pushToken = null; + this.isInitialized = false; + this.isInitializing = false; + + this.emulatorMode = false; + }; + + CurrentDevice.prototype = { + + /** + * Initializes the current device for push notifications. This method requests a push token + * from the device vendor and enables the push notification functionality on the device. + * Once this is done, you can register the device in Everlive using the register() method. + * + * @param {PushSettings} pushSettings + * An object specifying various settings for the initialization. + * @param {Function} success + * Callback to invoke on success. + * @param {Function} error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + enableNotifications: function (pushSettings, success, error) { + this.pushSettings = pushSettings; + return buildPromise(_.bind(this._initialize, this), success, error); + }, + + /** + * Disables push notifications for the current device. This method invalidates any push tokens + * that were obtained for the device from the current application. + * + * @param {Function} success + * Callback to invoke on success. + * @param {Function} error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + disableNotifications: function (success, error) { + var self = this; + + return this.unregister().then( + function () { + return buildPromise( + function (success, error) { + if (self.emulatorMode) { + success(); + } else { + var pushNotification = window.plugins.pushNotification; + var unregisterOptions; + var platformType = self._getPlatformType(device.platform); + if (platformType === Platform.WindowsPhone) { + unregisterOptions = { 'channelName': self.pushSettings.wp8.channelName }; + } + pushNotification.unregister( + function () { + self.isInitialized = false; + success(); + }, + error, + unregisterOptions + ); + } + }, + success, + error + ); + }, + error + ); + }, + + /** + * Returns the push registration for the current device. + * + * @param {Function} success + * Callback to invoke on success. + * @param {Function} error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + getRegistration: function (success, error) { + var deviceId = encodeURIComponent(this._getDeviceId()); + return this._pushHandler.devices.getById('HardwareId/' + deviceId, success, error); + }, + + /** + * Registers the current device for push notifications in Everlive. This method can be called + * only after enableNotifications() has completed successfully. + * + * @param {Object} customParameters + * Custom parameters for the registration. + * @param {Function} success + * Callback to invoke on success. + * @param {Function} error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + register: function (customParameters, success, error) { + var self = this; + + var deviceRegistration = {}; + if (customParameters !== undefined) { + deviceRegistration.Parameters = customParameters; + } + + return this._populateRegistrationObject(deviceRegistration).then( + function () { + return self._pushHandler.devices.create(deviceRegistration, success, error); + }, + error + ); + }, + + /** + * Unregisters the current device from push notifications in Everlive. After this call completes + * successfully, Everlive will no longer send notifications to this device. Note that this does + * not prevent the device from receiving notifications and does not invalidate push tokens. + * + * @param {Function} success + * Callback to invoke on success. + * @param {Function} error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + unregister: function (success, error) { + var deviceId = encodeURIComponent(device.uuid); + return this._pushHandler.devices.destroySingle({ Id: 'HardwareId/' + deviceId }, success, error); + }, + + /** + * Updates the registration for the current device. + * + * @param {Object} customParameters + * Custom parameters for the registration. If undefined, customParameters are not updated. + * @param {Function} success + * Callback to invoke on success. + * @param {Function} error + * Callback to invoke on error. + * @returns {Object} + * A promise for the operation, or void if success/error are supplied. + */ + updateRegistration: function (customParameters, success, error) { + var self = this; + + var deviceRegistration = {}; + if (customParameters !== undefined) { + deviceRegistration.Parameters = customParameters; + } + + return this._populateRegistrationObject(deviceRegistration).then( + function () { + deviceRegistration.Id = 'HardwareId/' + encodeURIComponent(deviceRegistration.HardwareId); + return self._pushHandler.devices.updateSingle(deviceRegistration, success, error); + }, + error + ); + }, + + //Initializes the push functionality on the device. + _initialize: function (success, error) { + var self = this; + + if (this.isInitializing) { + error(new EverliveError('Push notifications are currently initializing.')); + return; + } + + if (!this.emulatorMode && (!window.navigator || !window.navigator.globalization)) { + error(new EverliveError('The globalization plugin is not initialized.')); + return; + } + + if (!this.emulatorMode && (!window.plugins || !window.plugins.pushNotification)) { + error(new EverliveError('The push notifications plugin is not initialized.')); + return; + } + + this._initSuccessCallback = success; + this._initErrorCallback = error; + + if (this.isInitialized) { + this._deviceRegistrationSuccess(this.pushToken); + return; + } + + if (this.emulatorMode) { + setTimeout( + function () { + self._deviceRegistrationSuccess('fake_push_token'); + }, + 1000 + ); + return; + } + + this.isInitializing = true; + + var suffix = this._globalFunctionSuffix; + if (!suffix) { + suffix = Date.now().toString(); + this._globalFunctionSuffix = suffix; + } + + var pushNotification = window.plugins.pushNotification; + + var platformType = this._getPlatformType(device.platform); + if (platformType === Platform.iOS) { + //Initialize global APN callback + var apnCallbackName = 'apnCallback_' + suffix; + Everlive.PushCallbacks[apnCallbackName] = _.bind(this._onNotificationAPN, this); + + //Construct registration options object and validate iOS settings + var apnRegistrationOptions = this.pushSettings.iOS; + this._validateIOSSettings(apnRegistrationOptions); + apnRegistrationOptions.ecb = 'Everlive.PushCallbacks.' + apnCallbackName; + + //Register for APN + pushNotification.register( + _.bind(this._successfulRegistrationAPN, this), + _.bind(this._failedRegistrationAPN, this), + apnRegistrationOptions + ); + } else if (platformType === Platform.Android) { + //Initialize global GCM callback + var gcmCallbackName = 'gcmCallback_' + suffix; + Everlive.PushCallbacks[gcmCallbackName] = _.bind(this._onNotificationGCM, this); + + //Construct registration options object and validate the Android settings + var gcmRegistrationOptions = this.pushSettings.android; + this._validateAndroidSettings(gcmRegistrationOptions); + gcmRegistrationOptions.ecb = 'Everlive.PushCallbacks.' + gcmCallbackName; + + //Register for GCM + pushNotification.register( + _.bind(this._successSentRegistrationGCM, this), + _.bind(this._errorSentRegistrationGCM, this), + gcmRegistrationOptions + ); + } else if (platformType === Platform.WindowsPhone) { + //Initialize global WP8 callbacks. + var wp8CallbackName = 'wp8Callback_' + suffix; + var wp8RegistrationSuccessCallbackName = 'wp8RegistrationSuccessCallback_' + suffix; + var wp8RegistrationErrorCallbackName = 'wp8RegistrationErrorCallback_' + suffix; + + Everlive.PushCallbacks[wp8CallbackName] = _.bind(this._onNotificationWP8, this); + Everlive.PushCallbacks[wp8RegistrationSuccessCallbackName] = _.bind(this._deviceRegistrationSuccessWP, this); + Everlive.PushCallbacks[wp8RegistrationErrorCallbackName] = _.bind(this._deviceRegistrationFailed, this); + + //Construct registration options object and validate the WP8 settings + var wp8RegistrationOptions = this.pushSettings.wp8; + this._validateWP8Settings(wp8RegistrationOptions); + wp8RegistrationOptions.ecb = 'Everlive.PushCallbacks.' + wp8CallbackName; + wp8RegistrationOptions.uccb = 'Everlive.PushCallbacks.' + wp8RegistrationSuccessCallbackName; + wp8RegistrationOptions.errcb = 'Everlive.PushCallbacks.' + wp8RegistrationErrorCallbackName; + + + pushNotification.register( + _.bind(this._successSentRegistrationWP8, this), + _.bind(this._errorSentRegistrationWP8, this), + wp8RegistrationOptions + ); + + } else { + throw new EverliveError('The current platform is not supported: ' + device.platform); + } + }, + + _deviceRegistrationSuccessWP: function (result) { + this._deviceRegistrationSuccess(result.uri); + }, + + _validateAndroidSettings: function (androidSettings) { + if (!androidSettings.senderID) { + throw new EverliveError('Sender ID (project number) is not set in the android settings.'); + } + }, + _validateWP8Settings: function (settings) { + if (!settings.channelName) { + throw new EverliveError('channelName is not set in the WP8 settings.'); + } + }, + + _validateIOSSettings: function (iOSSettings) { + + }, + + _populateRegistrationObject: function (deviceRegistration, success, error) { + var self = this; + + return buildPromise( + function (success, error) { + if (!self.pushToken) { + throw new EverliveError('Push token is not available.'); + } + + self._getLocaleName( + function (locale) { + var deviceId = self._getDeviceId(); + var hardwareModel = device.model; + var platformType = self._getPlatformType(device.platform); + var timeZone = jstz.determine().name(); + var pushToken = self.pushToken; + var language = locale.value; + var platformVersion = device.version; + + deviceRegistration.HardwareId = deviceId; + deviceRegistration.HardwareModel = hardwareModel; + deviceRegistration.PlatformType = platformType; + deviceRegistration.PlatformVersion = platformVersion; + deviceRegistration.TimeZone = timeZone; + deviceRegistration.PushToken = pushToken; + deviceRegistration.Locale = language; + + success(); + }, + error + ); + }, + success, + error + ); + }, + + _getLocaleName: function (success, error) { + if (this.emulatorMode) { + success({ value: 'en_US' }); + } else { + navigator.globalization.getLocaleName( + function (locale) { + success(locale); + }, + error + ); + navigator.globalization.getLocaleName( + function (locale) { + }, + error + ); + } + }, + + _getDeviceId: function () { + return device.uuid; + }, + + //Returns the Everlive device platform constant given a value aquired from cordova's device.platform. + _getPlatformType: function (platformString) { + var psLower = platformString.toLowerCase(); + switch (psLower) { + case 'ios': + case 'iphone': + case 'ipad': + return Platform.iOS; + case 'android': + return Platform.Android; + case 'wince': + return Platform.WindowsPhone; + case 'win32nt': // real wp8 devices return this string as platform identifier. + return Platform.WindowsPhone; + default: + return Platform.Unknown; + } + }, + + _deviceRegistrationFailed: function (error) { + this.pushToken = null; + this.isInitializing = false; + this.isInitialized = false; + + if (this._initErrorCallback) { + this._initErrorCallback({ error: error }); + } + }, + + _deviceRegistrationSuccess: function (token) { + this.pushToken = token; + this.isInitializing = false; + this.isInitialized = true; + + if (this._initSuccessCallback) { + this._initSuccessCallback({ token: token }); + } + }, + + //Occurs when the device registration in APN succeeds + _successfulRegistrationAPN: function (token) { + this._deviceRegistrationSuccess(token); + }, + + //Occurs if the device registration in APN fails + _failedRegistrationAPN: function (error) { + this._deviceRegistrationFailed(error); + }, + + //Occurs when device registration has been successfully sent to GCM + _successSentRegistrationGCM: function (id) { + //console.log("Successfully sent request for registering with GCM."); + }, + //Occurs when device registration has been successfully sent for WP8 + _successSentRegistrationWP8: function (id) { + //console.log("Successfully sent request for registering WP8 ."); + }, + //Occurs when an error occured when sending registration request for WP8 + _errorSentRegistrationWP8: function (error) { + this._deviceRegistrationFailed(error); + }, + + //Occurs when an error occured when sending registration request to GCM + _errorSentRegistrationGCM: function (error) { + this._deviceRegistrationFailed(error); + }, + + //This function receives all notification events from APN + _onNotificationAPN: function (e) { + this._raiseNotificationEventIOS(e); + }, + //This function receives all notification events for WP8 + _onNotificationWP8: function (e) { + this._raiseNotificationEventWP8(e); + }, + + //This function receives all notification events from GCM + _onNotificationGCM: function onNotificationGCM(e) { + switch (e.event) { + case 'registered': + if (e.regid.length > 0) { + this._deviceRegistrationSuccess(e.regid); + } + break; + case 'message': + this._raiseNotificationEventAndroid(e); + break; + case 'error': + if (!this.pushToken) { + this._deviceRegistrationFailed(e); + } else { + this._raiseNotificationEventAndroid(e); + } + break; + default: + this._raiseNotificationEventAndroid(e); + break; + } + }, + + _raiseNotificationEventAndroid: function (e) { + if (this.pushSettings.notificationCallbackAndroid) { + this.pushSettings.notificationCallbackAndroid(e); + } + }, + _raiseNotificationEventIOS: function (e) { + if (this.pushSettings.notificationCallbackIOS) { + this.pushSettings.notificationCallbackIOS(e); + } + }, + _raiseNotificationEventWP8: function (e) { + if (this.pushSettings.notificationCallbackWP8) { + this.pushSettings.notificationCallbackWP8(e); + } + } + }; + + function EverliveError() { + var tmp = Error.apply(this, arguments); + + tmp.name = this.name = 'EverliveError'; + + this.message = tmp.message; + + Object.defineProperty(this, 'stack', { + get: function () { + return tmp.stack + } + }); + + return this; + } + + EverliveError.prototype = Object.create(Error.prototype); + EverliveError.prototype.toJSON = function () { + return { + name: this.name, + message: this.message, + stack: this.stack + }; + }; + + var DeviceRegistrationError = function (errorType, message, additionalInformation) { + EverliveError.call(this, message); + this.errorType = errorType; + this.message = message; + if (additionalInformation !== undefined) { + this.additionalInformation = additionalInformation; + } + }; + + DeviceRegistrationError.prototype = Object.create(EverliveError.prototype); + + DeviceRegistrationError.fromEverliveError = function (everliveError) { + var deviceRegistrationError = new DeviceRegistrationError(DeviceRegistrationErrorTypes.EverliveError, everliveError.message, everliveError); + return deviceRegistrationError; + }; + + DeviceRegistrationError.fromPluginError = function (errorObj) { + var message = 'A plugin error occurred'; + if (errorObj) { + if (typeof errorObj.error === 'string') { + message = errorObj.error; + } else if (typeof errorObj.message === 'string') { + message = errorObj.message; + } + } + + var deviceRegistrationError = new DeviceRegistrationError(DeviceRegistrationErrorTypes.PluginError, message, errorObj); + return deviceRegistrationError; + }; + + var DeviceRegistrationErrorTypes = { + EverliveError: 1, + PluginError: 2 + }; + + var DeviceRegistrationResult = function (token) { + this.token = token; + }; + + //#endregion + + var initDefault = function () { + this.Users = this.data('Users'); + addUsersFunctions(this.Users); + this.Files = this.data('Files'); + addFilesFunctions(this.Files); + + this.push = new Push(this); + }; + + initializations.push({ name: 'default', func: initDefault }); + + return Everlive; +})); + +(function (root, factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + define(['Everlive'], function (Everlive) { + factory(Everlive); + }); + } + else { + factory(root.Everlive); + } +}(this, function (Everlive) { + var $ = window.jQuery, + kendo = window.kendo; + if ($ === undefined || kendo === undefined) + return; + var extend = $.extend; + + extend(true, kendo.data, { + schemas: { + everlive: { + type: "json", + data: function (data) { + // update + if (typeof data.ModifiedAt !== 'undefined') { + return { ModifiedAt: data.ModifiedAt }; + } + else { + return data.Result || data; + } + }, + total: "Count", + model: { + id: "Id" + } + } + }, + transports: { + everlive: { + read: { + dataType: "json", + type: "GET", + cache: false + }, + update: { + dataType: "json", + contentType: "application/json", + type: "PUT", + cache: false + }, + create: { + dataType: "json", + contentType: "application/json", + type: "POST", + cache: false + }, + destroy: { + dataType: "json", + type: "DELETE", + cache: false + }, + parameterMap: function (data, operation) { + if (operation === "destroy") { + return {}; + } + + if (operation === "create" || operation === "update") { + return JSON.stringify(data); + } + + if (operation === "read") { + return null; + } + } + } + } + }); + + function translateKendoQuery(data) { + var result = {}; + if (data) { + if (data.skip) { + result.$skip = data.skip; + delete data.skip; + } + if (data.take) { + result.$take = data.take; + delete data.take; + } + if (data.sort) { + var sortExpressions = data.sort; + var sort = {} + if (!$.isArray(sortExpressions)) { + sortExpressions = [sortExpressions]; + } + $.each(sortExpressions, function (idx, value) { + sort[value.field] = value.dir === 'asc' ? 1 : -1; + }); + result.$sort = sort; + delete data.sort; + } + if (data.filter) { + var filter = new FilterBuilder().build(data.filter); + result.$where = filter; + delete data.filter; + } + } + return result; + } + + var regexOperations = ['startswith', 'startsWith', 'endswith', 'endsWith', 'contains']; + function FilterBuilder() { + } + FilterBuilder.prototype = { + build: function (filter) { + return this._build(filter); + }, + _build: function (filter) { + if (this._isRaw(filter)) { + return this._raw(filter); + } + else if (this._isSimple(filter)) { + return this._simple(filter); + } + else if (this._isRegex(filter)) { + return this._regex(filter); + } + else if (this._isAnd(filter)) { + return this._and(filter); + } + else if (this._isOr(filter)) { + return this._or(filter); + } + }, + _isRaw: function (filter) { + return filter.operator === '_raw'; + }, + _raw: function (filter) { + var fieldTerm = {}; + fieldTerm[filter.field] = filter.value; + return fieldTerm; + }, + _isSimple: function (filter) { + return typeof filter.logic === 'undefined' && !this._isRegex(filter); + }, + _simple: function (filter) { + var term = {}, fieldTerm = {}; + var operator = this._translateoperator(filter.operator); + if (operator) { + term[operator] = filter.value; + } + else { + term = filter.value; + } + fieldTerm[filter.field] = term; + return fieldTerm; + }, + _isRegex: function (filter) { + return $.inArray(filter.operator, regexOperations) !== -1; + }, + _regex: function (filter) { + var fieldTerm = {}; + var regex = this._getRegex(filter); + var regexValue = this._getRegexValue(regex); + fieldTerm[filter.field] = regexValue; + return fieldTerm; + }, + _getRegex: function (filter) { + var pattern = filter.value; + var filterOperator = filter.operator; + switch (filterOperator) { + case 'contains': + return new RegExp(".*" + pattern + ".*", "i"); + case 'startsWith': // removing the camel case operators will be a breaking change + case 'startswith': // the Kendo UI operators are in lower case + return new RegExp("^" + pattern, "i"); + case 'endsWith': + case 'endswith': + return new RegExp(pattern + "$", "i"); + } + throw new Error("Unknown operator type."); + }, + _getRegexValue: function (regex) { + return Everlive.QueryBuilder.prototype._getRegexValue.call(this, regex); + }, + _isAnd: function (filter) { + return filter.logic === 'and'; + }, + _and: function (filter) { + var i, l, term, result = {}; + var operands = filter.filters; + for (i = 0, l = operands.length; i < l; i++) { + term = this._build(operands[i]); + result = this._andAppend(result, term); + } + return result; + }, + _andAppend: function (andObj, newObj) { + return Everlive.QueryBuilder.prototype._andAppend.call(this, andObj, newObj); + }, + _isOr: function (filter) { + return filter.logic === 'or'; + }, + _or: function (filter) { + var i, l, term, result = []; + var operands = filter.filters; + for (i = 0, l = operands.length; i < l; i++) { + term = this._build(operands[i]); + result.push(term); + } + return { $or: result }; + }, + _translateoperator: function (operator) { + switch (operator) { + case 'eq': + return null; + case 'neq': + return "$ne"; + case 'gt': + return "$gt"; + case 'lt': + return "$lt"; + case 'gte': + return "$gte"; + case 'lte': + return "$lte"; + } + throw new Error("Unknown operator type."); + } + }; + + function createEverliveQuery(query) { + return new Everlive.Query(query.$where, null, query.$sort, query.$skip, query.$take); + } + + // replace the setup method of RemoteTransport in order to inject options + // the setup method is called on all crud operations + var RemoteTransport_setup = kendo.data.RemoteTransport.prototype.setup; + kendo.data.RemoteTransport.prototype.setup = function (options, type) { + if (!options.url && !this.options[type].url && this.options.typeName) { + if (!Everlive.$) { + throw new Error("You should either specify a url for this transport method, or instantiate an Everlive instance."); + } + options.url = Everlive.Request.prototype.buildUrl(Everlive.$.setup) + this.options.typeName; + if (type === 'update' || type === 'destroy') + options.url += '/' + options.data[Everlive.idField]; + } + if (Everlive.$) { + options.headers = Everlive.Request.prototype.buildAuthHeader(Everlive.$.setup); + } + if (type === 'read' && options.data) { + var query = translateKendoQuery(options.data); + var everliveQuery = createEverliveQuery(query); + options.headers = $.extend(options.headers, Everlive.Request.prototype.buildQueryHeaders(everliveQuery)); + } + if (type === 'create' || type === 'read' || type === 'update') { + var success = options.success; + options.success = function (result) { + // convert date strings into dates + Everlive._traverseAndRevive(result); + if (success) + success(result); + }; + } + return RemoteTransport_setup.call(this, options, type); + }; + + // kendo merges the rest service result with the original data + // but Everlive does not return the whole objects on update and create + // so replace the accept method of Model in order to merge the response + // from the request for creating a new item to the client model item + var createRequestFields = [Everlive.idField, 'CreatedAt']; + var Model_accept = kendo.data.Model.prototype.accept; + kendo.data.Model.prototype.accept = function (data) { + var that = this, field, value; + Everlive._traverseAndRevive(data); + // create + if (data && that.isNew() && data[Everlive.idField]) { + for (field in that.fields) { + if ($.inArray(field, createRequestFields) === -1) { + value = that.get(field); + data[field] = value; + } + } + } + // update + else if (data && typeof data.ModifiedAt !== 'undefined') { + for (field in that.fields) { + if (field !== 'ModifiedAt') { + value = that.get(field); + data[field] = value; + } + } + } + Model_accept.call(this, data); + }; + + var getUrlGeneratorForNode = function (baseUrl, expandArray) { + var expandField = getRelationFieldForExpandNode(expandArray[expandArray.length - 1]); + var pathArray = expandArray.slice(0, expandArray.length - 1); + var pathUrl = '/_expand'; + for (var i = 0; i < pathArray.length; i++) { + pathUrl += '/' + getRelationFieldForExpandNode(pathArray[i]); + } + return (function (pathUrl, expandField) { + return function (options) { + var url = baseUrl + ''; + if (options.Id && expandField) {//if we are expanding + url += pathUrl + '/' + options.Id + '/' + expandField; + } + return url; + } + }(pathUrl, expandField)); + } + + var getHeadersForExpandNode = function (expandNode) { + if (typeof expandNode === "string") { + return {}; + } else { + return { + 'X-Everlive-Filter': JSON.stringify(expandNode.filter), + 'X-Everlive-Sort': JSON.stringify(expandNode.sort), + 'X-Everlive-Single-Field': expandNode.singleField, + 'X-Everlive-Skip': expandNode.skip, + 'X-Everlive-Take': expandNode.take, + 'X-Everlive-Fields': JSON.stringify(expandNode.fields), + } + } + } + + var getRelationFieldForExpandNode = function (expandNode) { + if (typeof expandNode === "string") { + return expandNode; + } else { + if (expandNode.relation) { + return expandNode.relation; + } else { + throw new Error("You need to specify a 'relation' for an expand node when using the object notation"); + } + } + } + + Everlive.createHierarchicalDataSource = function (options) { + options = options || {}; + var expand = options.expand; + var typeName = options.typeName; + delete options.expand; + delete options.typeName; + var baseUrl; + if (options.url) { + baseUrl = options.url; + } else if (Everlive.$ && typeName) { + baseUrl = Everlive.Request.prototype.buildUrl(Everlive.$.setup) + typeName; + } else { + if (!Everlive.$) { + throw new Error("You need to instantiate an Everlive instance in order to create a kendo HierarchicalDataSource."); + } + if (!typeName) { + throw new Error("You need to specify a 'typeName' in order to create a kendo HierarchicalDataSource."); + } + } + + var expandSchema; + if (expand) { + for (var i = expand.length - 1; i >= 0; i--) { + var expandSchema = { + model: { + hasChildren: getRelationFieldForExpandNode(expand[i]), + children: { + type: "everlive", + transport: { + read: { + url: getUrlGeneratorForNode(baseUrl, expand.slice(0, i + 1)), + headers: getHeadersForExpandNode(expand[i]) + } + }, + schema: expandSchema + } + } + } + } + } + var dataSourceOptions = {}; + dataSourceOptions.type = 'everlive'; + dataSourceOptions.transport = { + typeName: typeName + }, + dataSourceOptions.schema = expandSchema; + extend(true, dataSourceOptions, options); + return new kendo.data.HierarchicalDataSource(dataSourceOptions); + } + + Everlive.createDataSource = function (options) { + options = options || {}; + var typeName = options.typeName; + if (!typeName) { + throw new Error("You need to specify a 'typeName' in order to create a kendo DataSource."); + } + delete options.typeName; + + var dataSourceOptions = {}; + dataSourceOptions.type = 'everlive'; + dataSourceOptions.transport = { + typeName: typeName + }, + extend(true, dataSourceOptions, options); + return new kendo.data.DataSource(dataSourceOptions); + } +})); \ No newline at end of file