mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
5682 lines
207 KiB
JavaScript
5682 lines
207 KiB
JavaScript
// 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 '<form>' 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);
|
|
}
|
|
})); |