4372 lines
109 KiB
JavaScript
4372 lines
109 KiB
JavaScript
/*!
|
||
* @overview RSVP - a tiny implementation of Promises/A+.
|
||
* @copyright Copyright (c) 2016 Yehuda Katz, Tom Dale, Stefan Penner and contributors
|
||
* @license Licensed under MIT license
|
||
* See https://raw.githubusercontent.com/tildeio/rsvp.js/master/LICENSE
|
||
* @version 3.6.2
|
||
*/
|
||
|
||
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||
(factory((global.RSVP = global.RSVP || {})));
|
||
}(this, (function (exports) { 'use strict';
|
||
|
||
function indexOf(callbacks, callback) {
|
||
for (var i = 0, l = callbacks.length; i < l; i++) {
|
||
if (callbacks[i] === callback) {
|
||
return i;
|
||
}
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
function callbacksFor(object) {
|
||
var callbacks = object._promiseCallbacks;
|
||
|
||
if (!callbacks) {
|
||
callbacks = object._promiseCallbacks = {};
|
||
}
|
||
|
||
return callbacks;
|
||
}
|
||
|
||
/**
|
||
@class RSVP.EventTarget
|
||
*/
|
||
var EventTarget = {
|
||
|
||
/**
|
||
`RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
|
||
Example:
|
||
```javascript
|
||
let object = {};
|
||
RSVP.EventTarget.mixin(object);
|
||
object.on('finished', function(event) {
|
||
// handle event
|
||
});
|
||
object.trigger('finished', { detail: value });
|
||
```
|
||
`EventTarget.mixin` also works with prototypes:
|
||
```javascript
|
||
let Person = function() {};
|
||
RSVP.EventTarget.mixin(Person.prototype);
|
||
let yehuda = new Person();
|
||
let tom = new Person();
|
||
yehuda.on('poke', function(event) {
|
||
console.log('Yehuda says OW');
|
||
});
|
||
tom.on('poke', function(event) {
|
||
console.log('Tom says OW');
|
||
});
|
||
yehuda.trigger('poke');
|
||
tom.trigger('poke');
|
||
```
|
||
@method mixin
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {Object} object object to extend with EventTarget methods
|
||
*/
|
||
mixin: function (object) {
|
||
object['on'] = this['on'];
|
||
object['off'] = this['off'];
|
||
object['trigger'] = this['trigger'];
|
||
object._promiseCallbacks = undefined;
|
||
return object;
|
||
},
|
||
|
||
|
||
/**
|
||
Registers a callback to be executed when `eventName` is triggered
|
||
```javascript
|
||
object.on('event', function(eventInfo){
|
||
// handle the event
|
||
});
|
||
object.trigger('event');
|
||
```
|
||
@method on
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {String} eventName name of the event to listen for
|
||
@param {Function} callback function to be called when the event is triggered.
|
||
*/
|
||
on: function (eventName, callback) {
|
||
if (typeof callback !== 'function') {
|
||
throw new TypeError('Callback must be a function');
|
||
}
|
||
|
||
var allCallbacks = callbacksFor(this),
|
||
callbacks = void 0;
|
||
|
||
callbacks = allCallbacks[eventName];
|
||
|
||
if (!callbacks) {
|
||
callbacks = allCallbacks[eventName] = [];
|
||
}
|
||
|
||
if (indexOf(callbacks, callback) === -1) {
|
||
callbacks.push(callback);
|
||
}
|
||
},
|
||
|
||
|
||
/**
|
||
You can use `off` to stop firing a particular callback for an event:
|
||
```javascript
|
||
function doStuff() { // do stuff! }
|
||
object.on('stuff', doStuff);
|
||
object.trigger('stuff'); // doStuff will be called
|
||
// Unregister ONLY the doStuff callback
|
||
object.off('stuff', doStuff);
|
||
object.trigger('stuff'); // doStuff will NOT be called
|
||
```
|
||
If you don't pass a `callback` argument to `off`, ALL callbacks for the
|
||
event will not be executed when the event fires. For example:
|
||
```javascript
|
||
let callback1 = function(){};
|
||
let callback2 = function(){};
|
||
object.on('stuff', callback1);
|
||
object.on('stuff', callback2);
|
||
object.trigger('stuff'); // callback1 and callback2 will be executed.
|
||
object.off('stuff');
|
||
object.trigger('stuff'); // callback1 and callback2 will not be executed!
|
||
```
|
||
@method off
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {String} eventName event to stop listening to
|
||
@param {Function} callback optional argument. If given, only the function
|
||
given will be removed from the event's callback queue. If no `callback`
|
||
argument is given, all callbacks will be removed from the event's callback
|
||
queue.
|
||
*/
|
||
off: function (eventName, callback) {
|
||
var allCallbacks = callbacksFor(this),
|
||
callbacks = void 0,
|
||
index = void 0;
|
||
|
||
if (!callback) {
|
||
allCallbacks[eventName] = [];
|
||
return;
|
||
}
|
||
|
||
callbacks = allCallbacks[eventName];
|
||
|
||
index = indexOf(callbacks, callback);
|
||
|
||
if (index !== -1) {
|
||
callbacks.splice(index, 1);
|
||
}
|
||
},
|
||
|
||
|
||
/**
|
||
Use `trigger` to fire custom events. For example:
|
||
```javascript
|
||
object.on('foo', function(){
|
||
console.log('foo event happened!');
|
||
});
|
||
object.trigger('foo');
|
||
// 'foo event happened!' logged to the console
|
||
```
|
||
You can also pass a value as a second argument to `trigger` that will be
|
||
passed as an argument to all event listeners for the event:
|
||
```javascript
|
||
object.on('foo', function(value){
|
||
console.log(value.name);
|
||
});
|
||
object.trigger('foo', { name: 'bar' });
|
||
// 'bar' logged to the console
|
||
```
|
||
@method trigger
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {String} eventName name of the event to be triggered
|
||
@param {*} options optional value to be passed to any event handlers for
|
||
the given `eventName`
|
||
*/
|
||
trigger: function (eventName, options, label) {
|
||
var allCallbacks = callbacksFor(this),
|
||
callbacks = void 0,
|
||
callback = void 0;
|
||
|
||
if (callbacks = allCallbacks[eventName]) {
|
||
// Don't cache the callbacks.length since it may grow
|
||
for (var i = 0; i < callbacks.length; i++) {
|
||
callback = callbacks[i];
|
||
|
||
callback(options, label);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
var config = {
|
||
instrument: false
|
||
};
|
||
|
||
EventTarget['mixin'](config);
|
||
|
||
function configure(name, value) {
|
||
if (arguments.length === 2) {
|
||
config[name] = value;
|
||
} else {
|
||
return config[name];
|
||
}
|
||
}
|
||
|
||
function objectOrFunction(x) {
|
||
var type = typeof x;
|
||
return x !== null && (type === 'object' || type === 'function');
|
||
}
|
||
|
||
function isFunction(x) {
|
||
return typeof x === 'function';
|
||
}
|
||
|
||
function isObject(x) {
|
||
return x !== null && typeof x === 'object';
|
||
}
|
||
|
||
function isMaybeThenable(x) {
|
||
return x !== null && typeof x === 'object';
|
||
}
|
||
|
||
var _isArray = void 0;
|
||
if (Array.isArray) {
|
||
_isArray = Array.isArray;
|
||
} else {
|
||
_isArray = function (x) {
|
||
return Object.prototype.toString.call(x) === '[object Array]';
|
||
};
|
||
}
|
||
|
||
var isArray = _isArray;
|
||
|
||
// Date.now is not available in browsers < IE9
|
||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
|
||
var now = Date.now || function () {
|
||
return new Date().getTime();
|
||
};
|
||
|
||
var queue = [];
|
||
|
||
function scheduleFlush() {
|
||
setTimeout(function () {
|
||
for (var i = 0; i < queue.length; i++) {
|
||
var entry = queue[i];
|
||
|
||
var payload = entry.payload;
|
||
|
||
payload.guid = payload.key + payload.id;
|
||
payload.childGuid = payload.key + payload.childId;
|
||
if (payload.error) {
|
||
payload.stack = payload.error.stack;
|
||
}
|
||
|
||
config['trigger'](entry.name, entry.payload);
|
||
}
|
||
queue.length = 0;
|
||
}, 50);
|
||
}
|
||
|
||
function instrument(eventName, promise, child) {
|
||
if (1 === queue.push({
|
||
name: eventName,
|
||
payload: {
|
||
key: promise._guidKey,
|
||
id: promise._id,
|
||
eventName: eventName,
|
||
detail: promise._result,
|
||
childId: child && child._id,
|
||
label: promise._label,
|
||
timeStamp: now(),
|
||
error: config["instrument-with-stack"] ? new Error(promise._label) : null
|
||
} })) {
|
||
scheduleFlush();
|
||
}
|
||
}
|
||
|
||
/**
|
||
`RSVP.Promise.resolve` returns a promise that will become resolved with the
|
||
passed `value`. It is shorthand for the following:
|
||
|
||
```javascript
|
||
let promise = new RSVP.Promise(function(resolve, reject){
|
||
resolve(1);
|
||
});
|
||
|
||
promise.then(function(value){
|
||
// value === 1
|
||
});
|
||
```
|
||
|
||
Instead of writing the above, your code now simply becomes the following:
|
||
|
||
```javascript
|
||
let promise = RSVP.Promise.resolve(1);
|
||
|
||
promise.then(function(value){
|
||
// value === 1
|
||
});
|
||
```
|
||
|
||
@method resolve
|
||
@static
|
||
@param {*} object value that the returned promise will be resolved with
|
||
@param {String} label optional string for identifying the returned promise.
|
||
Useful for tooling.
|
||
@return {Promise} a promise that will become fulfilled with the given
|
||
`value`
|
||
*/
|
||
function resolve$1(object, label) {
|
||
/*jshint validthis:true */
|
||
var Constructor = this;
|
||
|
||
if (object && typeof object === 'object' && object.constructor === Constructor) {
|
||
return object;
|
||
}
|
||
|
||
var promise = new Constructor(noop, label);
|
||
resolve(promise, object);
|
||
return promise;
|
||
}
|
||
|
||
function withOwnPromise() {
|
||
return new TypeError('A promises callback cannot return that same promise.');
|
||
}
|
||
|
||
function noop() {}
|
||
|
||
var PENDING = void 0;
|
||
var FULFILLED = 1;
|
||
var REJECTED = 2;
|
||
|
||
var GET_THEN_ERROR = new ErrorObject();
|
||
|
||
function getThen(promise) {
|
||
try {
|
||
return promise.then;
|
||
} catch (error) {
|
||
GET_THEN_ERROR.error = error;
|
||
return GET_THEN_ERROR;
|
||
}
|
||
}
|
||
|
||
function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
|
||
try {
|
||
then$$1.call(value, fulfillmentHandler, rejectionHandler);
|
||
} catch (e) {
|
||
return e;
|
||
}
|
||
}
|
||
|
||
function handleForeignThenable(promise, thenable, then$$1) {
|
||
config.async(function (promise) {
|
||
var sealed = false;
|
||
var error = tryThen(then$$1, thenable, function (value) {
|
||
if (sealed) {
|
||
return;
|
||
}
|
||
sealed = true;
|
||
if (thenable !== value) {
|
||
resolve(promise, value, undefined);
|
||
} else {
|
||
fulfill(promise, value);
|
||
}
|
||
}, function (reason) {
|
||
if (sealed) {
|
||
return;
|
||
}
|
||
sealed = true;
|
||
|
||
reject(promise, reason);
|
||
}, 'Settle: ' + (promise._label || ' unknown promise'));
|
||
|
||
if (!sealed && error) {
|
||
sealed = true;
|
||
reject(promise, error);
|
||
}
|
||
}, promise);
|
||
}
|
||
|
||
function handleOwnThenable(promise, thenable) {
|
||
if (thenable._state === FULFILLED) {
|
||
fulfill(promise, thenable._result);
|
||
} else if (thenable._state === REJECTED) {
|
||
thenable._onError = null;
|
||
reject(promise, thenable._result);
|
||
} else {
|
||
subscribe(thenable, undefined, function (value) {
|
||
if (thenable !== value) {
|
||
resolve(promise, value, undefined);
|
||
} else {
|
||
fulfill(promise, value);
|
||
}
|
||
}, function (reason) {
|
||
return reject(promise, reason);
|
||
});
|
||
}
|
||
}
|
||
|
||
function handleMaybeThenable(promise, maybeThenable, then$$1) {
|
||
var isOwnThenable = maybeThenable.constructor === promise.constructor && then$$1 === then && promise.constructor.resolve === resolve$1;
|
||
|
||
if (isOwnThenable) {
|
||
handleOwnThenable(promise, maybeThenable);
|
||
} else if (then$$1 === GET_THEN_ERROR) {
|
||
reject(promise, GET_THEN_ERROR.error);
|
||
GET_THEN_ERROR.error = null;
|
||
} else if (isFunction(then$$1)) {
|
||
handleForeignThenable(promise, maybeThenable, then$$1);
|
||
} else {
|
||
fulfill(promise, maybeThenable);
|
||
}
|
||
}
|
||
|
||
function resolve(promise, value) {
|
||
if (promise === value) {
|
||
fulfill(promise, value);
|
||
} else if (objectOrFunction(value)) {
|
||
handleMaybeThenable(promise, value, getThen(value));
|
||
} else {
|
||
fulfill(promise, value);
|
||
}
|
||
}
|
||
|
||
function publishRejection(promise) {
|
||
if (promise._onError) {
|
||
promise._onError(promise._result);
|
||
}
|
||
|
||
publish(promise);
|
||
}
|
||
|
||
function fulfill(promise, value) {
|
||
if (promise._state !== PENDING) {
|
||
return;
|
||
}
|
||
|
||
promise._result = value;
|
||
promise._state = FULFILLED;
|
||
|
||
if (promise._subscribers.length === 0) {
|
||
if (config.instrument) {
|
||
instrument('fulfilled', promise);
|
||
}
|
||
} else {
|
||
config.async(publish, promise);
|
||
}
|
||
}
|
||
|
||
function reject(promise, reason) {
|
||
if (promise._state !== PENDING) {
|
||
return;
|
||
}
|
||
promise._state = REJECTED;
|
||
promise._result = reason;
|
||
config.async(publishRejection, promise);
|
||
}
|
||
|
||
function subscribe(parent, child, onFulfillment, onRejection) {
|
||
var subscribers = parent._subscribers;
|
||
var length = subscribers.length;
|
||
|
||
parent._onError = null;
|
||
|
||
subscribers[length] = child;
|
||
subscribers[length + FULFILLED] = onFulfillment;
|
||
subscribers[length + REJECTED] = onRejection;
|
||
|
||
if (length === 0 && parent._state) {
|
||
config.async(publish, parent);
|
||
}
|
||
}
|
||
|
||
function publish(promise) {
|
||
var subscribers = promise._subscribers;
|
||
var settled = promise._state;
|
||
|
||
if (config.instrument) {
|
||
instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
|
||
}
|
||
|
||
if (subscribers.length === 0) {
|
||
return;
|
||
}
|
||
|
||
var child = void 0,
|
||
callback = void 0,
|
||
result = promise._result;
|
||
|
||
for (var i = 0; i < subscribers.length; i += 3) {
|
||
child = subscribers[i];
|
||
callback = subscribers[i + settled];
|
||
|
||
if (child) {
|
||
invokeCallback(settled, child, callback, result);
|
||
} else {
|
||
callback(result);
|
||
}
|
||
}
|
||
|
||
promise._subscribers.length = 0;
|
||
}
|
||
|
||
function ErrorObject() {
|
||
this.error = null;
|
||
}
|
||
|
||
var TRY_CATCH_ERROR = new ErrorObject();
|
||
|
||
function tryCatch(callback, result) {
|
||
try {
|
||
return callback(result);
|
||
} catch (e) {
|
||
TRY_CATCH_ERROR.error = e;
|
||
return TRY_CATCH_ERROR;
|
||
}
|
||
}
|
||
|
||
function invokeCallback(state, promise, callback, result) {
|
||
var hasCallback = isFunction(callback);
|
||
var value = void 0,
|
||
error = void 0;
|
||
|
||
if (hasCallback) {
|
||
value = tryCatch(callback, result);
|
||
|
||
if (value === TRY_CATCH_ERROR) {
|
||
error = value.error;
|
||
value.error = null; // release
|
||
} else if (value === promise) {
|
||
reject(promise, withOwnPromise());
|
||
return;
|
||
}
|
||
} else {
|
||
value = result;
|
||
}
|
||
|
||
if (promise._state !== PENDING) {
|
||
// noop
|
||
} else if (hasCallback && error === undefined) {
|
||
resolve(promise, value);
|
||
} else if (error !== undefined) {
|
||
reject(promise, error);
|
||
} else if (state === FULFILLED) {
|
||
fulfill(promise, value);
|
||
} else if (state === REJECTED) {
|
||
reject(promise, value);
|
||
}
|
||
}
|
||
|
||
function initializePromise(promise, resolver) {
|
||
var resolved = false;
|
||
try {
|
||
resolver(function (value) {
|
||
if (resolved) {
|
||
return;
|
||
}
|
||
resolved = true;
|
||
resolve(promise, value);
|
||
}, function (reason) {
|
||
if (resolved) {
|
||
return;
|
||
}
|
||
resolved = true;
|
||
reject(promise, reason);
|
||
});
|
||
} catch (e) {
|
||
reject(promise, e);
|
||
}
|
||
}
|
||
|
||
function then(onFulfillment, onRejection, label) {
|
||
var parent = this;
|
||
var state = parent._state;
|
||
|
||
if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
|
||
config.instrument && instrument('chained', parent, parent);
|
||
return parent;
|
||
}
|
||
|
||
parent._onError = null;
|
||
|
||
var child = new parent.constructor(noop, label);
|
||
var result = parent._result;
|
||
|
||
config.instrument && instrument('chained', parent, child);
|
||
|
||
if (state === PENDING) {
|
||
subscribe(parent, child, onFulfillment, onRejection);
|
||
} else {
|
||
var callback = state === FULFILLED ? onFulfillment : onRejection;
|
||
config.async(function () {
|
||
return invokeCallback(state, child, callback, result);
|
||
});
|
||
}
|
||
|
||
return child;
|
||
}
|
||
|
||
var Enumerator = function () {
|
||
function Enumerator(Constructor, input, abortOnReject, label) {
|
||
this._instanceConstructor = Constructor;
|
||
this.promise = new Constructor(noop, label);
|
||
this._abortOnReject = abortOnReject;
|
||
|
||
this._init.apply(this, arguments);
|
||
}
|
||
|
||
Enumerator.prototype._init = function _init(Constructor, input) {
|
||
var len = input.length || 0;
|
||
this.length = len;
|
||
this._remaining = len;
|
||
this._result = new Array(len);
|
||
|
||
this._enumerate(input);
|
||
if (this._remaining === 0) {
|
||
fulfill(this.promise, this._result);
|
||
}
|
||
};
|
||
|
||
Enumerator.prototype._enumerate = function _enumerate(input) {
|
||
var length = this.length;
|
||
var promise = this.promise;
|
||
|
||
for (var i = 0; promise._state === PENDING && i < length; i++) {
|
||
this._eachEntry(input[i], i);
|
||
}
|
||
};
|
||
|
||
Enumerator.prototype._settleMaybeThenable = function _settleMaybeThenable(entry, i) {
|
||
var c = this._instanceConstructor;
|
||
var resolve$$1 = c.resolve;
|
||
|
||
if (resolve$$1 === resolve$1) {
|
||
var then$$1 = getThen(entry);
|
||
|
||
if (then$$1 === then && entry._state !== PENDING) {
|
||
entry._onError = null;
|
||
this._settledAt(entry._state, i, entry._result);
|
||
} else if (typeof then$$1 !== 'function') {
|
||
this._remaining--;
|
||
this._result[i] = this._makeResult(FULFILLED, i, entry);
|
||
} else if (c === Promise) {
|
||
var promise = new c(noop);
|
||
handleMaybeThenable(promise, entry, then$$1);
|
||
this._willSettleAt(promise, i);
|
||
} else {
|
||
this._willSettleAt(new c(function (resolve$$1) {
|
||
return resolve$$1(entry);
|
||
}), i);
|
||
}
|
||
} else {
|
||
this._willSettleAt(resolve$$1(entry), i);
|
||
}
|
||
};
|
||
|
||
Enumerator.prototype._eachEntry = function _eachEntry(entry, i) {
|
||
if (isMaybeThenable(entry)) {
|
||
this._settleMaybeThenable(entry, i);
|
||
} else {
|
||
this._remaining--;
|
||
this._result[i] = this._makeResult(FULFILLED, i, entry);
|
||
}
|
||
};
|
||
|
||
Enumerator.prototype._settledAt = function _settledAt(state, i, value) {
|
||
var promise = this.promise;
|
||
|
||
if (promise._state === PENDING) {
|
||
if (this._abortOnReject && state === REJECTED) {
|
||
reject(promise, value);
|
||
} else {
|
||
this._remaining--;
|
||
this._result[i] = this._makeResult(state, i, value);
|
||
if (this._remaining === 0) {
|
||
fulfill(promise, this._result);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
Enumerator.prototype._makeResult = function _makeResult(state, i, value) {
|
||
return value;
|
||
};
|
||
|
||
Enumerator.prototype._willSettleAt = function _willSettleAt(promise, i) {
|
||
var enumerator = this;
|
||
|
||
subscribe(promise, undefined, function (value) {
|
||
return enumerator._settledAt(FULFILLED, i, value);
|
||
}, function (reason) {
|
||
return enumerator._settledAt(REJECTED, i, reason);
|
||
});
|
||
};
|
||
|
||
return Enumerator;
|
||
}();
|
||
|
||
function makeSettledResult(state, position, value) {
|
||
if (state === FULFILLED) {
|
||
return {
|
||
state: 'fulfilled',
|
||
value: value
|
||
};
|
||
} else {
|
||
return {
|
||
state: 'rejected',
|
||
reason: value
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
`RSVP.Promise.all` accepts an array of promises, and returns a new promise which
|
||
is fulfilled with an array of fulfillment values for the passed promises, or
|
||
rejected with the reason of the first passed promise to be rejected. It casts all
|
||
elements of the passed iterable to promises as it runs this algorithm.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promise1 = RSVP.resolve(1);
|
||
let promise2 = RSVP.resolve(2);
|
||
let promise3 = RSVP.resolve(3);
|
||
let promises = [ promise1, promise2, promise3 ];
|
||
|
||
RSVP.Promise.all(promises).then(function(array){
|
||
// The array here would be [ 1, 2, 3 ];
|
||
});
|
||
```
|
||
|
||
If any of the `promises` given to `RSVP.all` are rejected, the first promise
|
||
that is rejected will be given as an argument to the returned promises's
|
||
rejection handler. For example:
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promise1 = RSVP.resolve(1);
|
||
let promise2 = RSVP.reject(new Error("2"));
|
||
let promise3 = RSVP.reject(new Error("3"));
|
||
let promises = [ promise1, promise2, promise3 ];
|
||
|
||
RSVP.Promise.all(promises).then(function(array){
|
||
// Code here never runs because there are rejected promises!
|
||
}, function(error) {
|
||
// error.message === "2"
|
||
});
|
||
```
|
||
|
||
@method all
|
||
@static
|
||
@param {Array} entries array of promises
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise} promise that is fulfilled when all `promises` have been
|
||
fulfilled, or rejected if any of them become rejected.
|
||
@static
|
||
*/
|
||
function all(entries, label) {
|
||
if (!isArray(entries)) {
|
||
return this.reject(new TypeError("Promise.all must be called with an array"), label);
|
||
}
|
||
return new Enumerator(this, entries, true /* abort on reject */, label).promise;
|
||
}
|
||
|
||
/**
|
||
`RSVP.Promise.race` returns a new promise which is settled in the same way as the
|
||
first passed promise to settle.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promise1 = new RSVP.Promise(function(resolve, reject){
|
||
setTimeout(function(){
|
||
resolve('promise 1');
|
||
}, 200);
|
||
});
|
||
|
||
let promise2 = new RSVP.Promise(function(resolve, reject){
|
||
setTimeout(function(){
|
||
resolve('promise 2');
|
||
}, 100);
|
||
});
|
||
|
||
RSVP.Promise.race([promise1, promise2]).then(function(result){
|
||
// result === 'promise 2' because it was resolved before promise1
|
||
// was resolved.
|
||
});
|
||
```
|
||
|
||
`RSVP.Promise.race` is deterministic in that only the state of the first
|
||
settled promise matters. For example, even if other promises given to the
|
||
`promises` array argument are resolved, but the first settled promise has
|
||
become rejected before the other promises became fulfilled, the returned
|
||
promise will become rejected:
|
||
|
||
```javascript
|
||
let promise1 = new RSVP.Promise(function(resolve, reject){
|
||
setTimeout(function(){
|
||
resolve('promise 1');
|
||
}, 200);
|
||
});
|
||
|
||
let promise2 = new RSVP.Promise(function(resolve, reject){
|
||
setTimeout(function(){
|
||
reject(new Error('promise 2'));
|
||
}, 100);
|
||
});
|
||
|
||
RSVP.Promise.race([promise1, promise2]).then(function(result){
|
||
// Code here never runs
|
||
}, function(reason){
|
||
// reason.message === 'promise 2' because promise 2 became rejected before
|
||
// promise 1 became fulfilled
|
||
});
|
||
```
|
||
|
||
An example real-world use case is implementing timeouts:
|
||
|
||
```javascript
|
||
RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
|
||
```
|
||
|
||
@method race
|
||
@static
|
||
@param {Array} entries array of promises to observe
|
||
@param {String} label optional string for describing the promise returned.
|
||
Useful for tooling.
|
||
@return {Promise} a promise which settles in the same way as the first passed
|
||
promise to settle.
|
||
*/
|
||
function race(entries, label) {
|
||
/*jshint validthis:true */
|
||
var Constructor = this;
|
||
|
||
var promise = new Constructor(noop, label);
|
||
|
||
if (!isArray(entries)) {
|
||
reject(promise, new TypeError('Promise.race must be called with an array'));
|
||
return promise;
|
||
}
|
||
|
||
for (var i = 0; promise._state === PENDING && i < entries.length; i++) {
|
||
subscribe(Constructor.resolve(entries[i]), undefined, function (value) {
|
||
return resolve(promise, value);
|
||
}, function (reason) {
|
||
return reject(promise, reason);
|
||
});
|
||
}
|
||
|
||
return promise;
|
||
}
|
||
|
||
/**
|
||
`RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
|
||
It is shorthand for the following:
|
||
|
||
```javascript
|
||
let promise = new RSVP.Promise(function(resolve, reject){
|
||
reject(new Error('WHOOPS'));
|
||
});
|
||
|
||
promise.then(function(value){
|
||
// Code here doesn't run because the promise is rejected!
|
||
}, function(reason){
|
||
// reason.message === 'WHOOPS'
|
||
});
|
||
```
|
||
|
||
Instead of writing the above, your code now simply becomes the following:
|
||
|
||
```javascript
|
||
let promise = RSVP.Promise.reject(new Error('WHOOPS'));
|
||
|
||
promise.then(function(value){
|
||
// Code here doesn't run because the promise is rejected!
|
||
}, function(reason){
|
||
// reason.message === 'WHOOPS'
|
||
});
|
||
```
|
||
|
||
@method reject
|
||
@static
|
||
@param {*} reason value that the returned promise will be rejected with.
|
||
@param {String} label optional string for identifying the returned promise.
|
||
Useful for tooling.
|
||
@return {Promise} a promise rejected with the given `reason`.
|
||
*/
|
||
function reject$1(reason, label) {
|
||
/*jshint validthis:true */
|
||
var Constructor = this;
|
||
var promise = new Constructor(noop, label);
|
||
reject(promise, reason);
|
||
return promise;
|
||
}
|
||
|
||
var guidKey = 'rsvp_' + now() + '-';
|
||
var counter = 0;
|
||
|
||
function needsResolver() {
|
||
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
|
||
}
|
||
|
||
function needsNew() {
|
||
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
|
||
}
|
||
|
||
/**
|
||
Promise objects represent the eventual result of an asynchronous operation. The
|
||
primary way of interacting with a promise is through its `then` method, which
|
||
registers callbacks to receive either a promise’s eventual value or the reason
|
||
why the promise cannot be fulfilled.
|
||
|
||
Terminology
|
||
-----------
|
||
|
||
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
|
||
- `thenable` is an object or function that defines a `then` method.
|
||
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
|
||
- `exception` is a value that is thrown using the throw statement.
|
||
- `reason` is a value that indicates why a promise was rejected.
|
||
- `settled` the final resting state of a promise, fulfilled or rejected.
|
||
|
||
A promise can be in one of three states: pending, fulfilled, or rejected.
|
||
|
||
Promises that are fulfilled have a fulfillment value and are in the fulfilled
|
||
state. Promises that are rejected have a rejection reason and are in the
|
||
rejected state. A fulfillment value is never a thenable.
|
||
|
||
Promises can also be said to *resolve* a value. If this value is also a
|
||
promise, then the original promise's settled state will match the value's
|
||
settled state. So a promise that *resolves* a promise that rejects will
|
||
itself reject, and a promise that *resolves* a promise that fulfills will
|
||
itself fulfill.
|
||
|
||
|
||
Basic Usage:
|
||
------------
|
||
|
||
```js
|
||
let promise = new Promise(function(resolve, reject) {
|
||
// on success
|
||
resolve(value);
|
||
|
||
// on failure
|
||
reject(reason);
|
||
});
|
||
|
||
promise.then(function(value) {
|
||
// on fulfillment
|
||
}, function(reason) {
|
||
// on rejection
|
||
});
|
||
```
|
||
|
||
Advanced Usage:
|
||
---------------
|
||
|
||
Promises shine when abstracting away asynchronous interactions such as
|
||
`XMLHttpRequest`s.
|
||
|
||
```js
|
||
function getJSON(url) {
|
||
return new Promise(function(resolve, reject){
|
||
let xhr = new XMLHttpRequest();
|
||
|
||
xhr.open('GET', url);
|
||
xhr.onreadystatechange = handler;
|
||
xhr.responseType = 'json';
|
||
xhr.setRequestHeader('Accept', 'application/json');
|
||
xhr.send();
|
||
|
||
function handler() {
|
||
if (this.readyState === this.DONE) {
|
||
if (this.status === 200) {
|
||
resolve(this.response);
|
||
} else {
|
||
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
|
||
}
|
||
}
|
||
};
|
||
});
|
||
}
|
||
|
||
getJSON('/posts.json').then(function(json) {
|
||
// on fulfillment
|
||
}, function(reason) {
|
||
// on rejection
|
||
});
|
||
```
|
||
|
||
Unlike callbacks, promises are great composable primitives.
|
||
|
||
```js
|
||
Promise.all([
|
||
getJSON('/posts'),
|
||
getJSON('/comments')
|
||
]).then(function(values){
|
||
values[0] // => postsJSON
|
||
values[1] // => commentsJSON
|
||
|
||
return values;
|
||
});
|
||
```
|
||
|
||
@class RSVP.Promise
|
||
@param {function} resolver
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@constructor
|
||
*/
|
||
|
||
var Promise = function () {
|
||
function Promise(resolver, label) {
|
||
this._id = counter++;
|
||
this._label = label;
|
||
this._state = undefined;
|
||
this._result = undefined;
|
||
this._subscribers = [];
|
||
|
||
config.instrument && instrument('created', this);
|
||
|
||
if (noop !== resolver) {
|
||
typeof resolver !== 'function' && needsResolver();
|
||
this instanceof Promise ? initializePromise(this, resolver) : needsNew();
|
||
}
|
||
}
|
||
|
||
Promise.prototype._onError = function _onError(reason) {
|
||
var _this = this;
|
||
|
||
config.after(function () {
|
||
if (_this._onError) {
|
||
config.trigger('error', reason, _this._label);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
|
||
as the catch block of a try/catch statement.
|
||
|
||
```js
|
||
function findAuthor(){
|
||
throw new Error('couldn\'t find that author');
|
||
}
|
||
|
||
// synchronous
|
||
try {
|
||
findAuthor();
|
||
} catch(reason) {
|
||
// something went wrong
|
||
}
|
||
|
||
// async with promises
|
||
findAuthor().catch(function(reason){
|
||
// something went wrong
|
||
});
|
||
```
|
||
|
||
@method catch
|
||
@param {Function} onRejection
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise}
|
||
*/
|
||
|
||
|
||
Promise.prototype.catch = function _catch(onRejection, label) {
|
||
return this.then(undefined, onRejection, label);
|
||
};
|
||
|
||
/**
|
||
`finally` will be invoked regardless of the promise's fate just as native
|
||
try/catch/finally behaves
|
||
|
||
Synchronous example:
|
||
|
||
```js
|
||
findAuthor() {
|
||
if (Math.random() > 0.5) {
|
||
throw new Error();
|
||
}
|
||
return new Author();
|
||
}
|
||
|
||
try {
|
||
return findAuthor(); // succeed or fail
|
||
} catch(error) {
|
||
return findOtherAuthor();
|
||
} finally {
|
||
// always runs
|
||
// doesn't affect the return value
|
||
}
|
||
```
|
||
|
||
Asynchronous example:
|
||
|
||
```js
|
||
findAuthor().catch(function(reason){
|
||
return findOtherAuthor();
|
||
}).finally(function(){
|
||
// author was either found, or not
|
||
});
|
||
```
|
||
|
||
@method finally
|
||
@param {Function} callback
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise}
|
||
*/
|
||
|
||
|
||
Promise.prototype.finally = function _finally(callback, label) {
|
||
var promise = this;
|
||
var constructor = promise.constructor;
|
||
|
||
return promise.then(function (value) {
|
||
return constructor.resolve(callback()).then(function () {
|
||
return value;
|
||
});
|
||
}, function (reason) {
|
||
return constructor.resolve(callback()).then(function () {
|
||
throw reason;
|
||
});
|
||
}, label);
|
||
};
|
||
|
||
return Promise;
|
||
}();
|
||
|
||
|
||
|
||
Promise.cast = resolve$1; // deprecated
|
||
Promise.all = all;
|
||
Promise.race = race;
|
||
Promise.resolve = resolve$1;
|
||
Promise.reject = reject$1;
|
||
|
||
Promise.prototype._guidKey = guidKey;
|
||
|
||
/**
|
||
The primary way of interacting with a promise is through its `then` method,
|
||
which registers callbacks to receive either a promise's eventual value or the
|
||
reason why the promise cannot be fulfilled.
|
||
|
||
```js
|
||
findUser().then(function(user){
|
||
// user is available
|
||
}, function(reason){
|
||
// user is unavailable, and you are given the reason why
|
||
});
|
||
```
|
||
|
||
Chaining
|
||
--------
|
||
|
||
The return value of `then` is itself a promise. This second, 'downstream'
|
||
promise is resolved with the return value of the first promise's fulfillment
|
||
or rejection handler, or rejected if the handler throws an exception.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
return user.name;
|
||
}, function (reason) {
|
||
return 'default name';
|
||
}).then(function (userName) {
|
||
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
|
||
// will be `'default name'`
|
||
});
|
||
|
||
findUser().then(function (user) {
|
||
throw new Error('Found user, but still unhappy');
|
||
}, function (reason) {
|
||
throw new Error('`findUser` rejected and we\'re unhappy');
|
||
}).then(function (value) {
|
||
// never reached
|
||
}, function (reason) {
|
||
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
|
||
// If `findUser` rejected, `reason` will be '`findUser` rejected and we\'re unhappy'.
|
||
});
|
||
```
|
||
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
throw new PedagogicalException('Upstream error');
|
||
}).then(function (value) {
|
||
// never reached
|
||
}).then(function (value) {
|
||
// never reached
|
||
}, function (reason) {
|
||
// The `PedgagocialException` is propagated all the way down to here
|
||
});
|
||
```
|
||
|
||
Assimilation
|
||
------------
|
||
|
||
Sometimes the value you want to propagate to a downstream promise can only be
|
||
retrieved asynchronously. This can be achieved by returning a promise in the
|
||
fulfillment or rejection handler. The downstream promise will then be pending
|
||
until the returned promise is settled. This is called *assimilation*.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
return findCommentsByAuthor(user);
|
||
}).then(function (comments) {
|
||
// The user's comments are now available
|
||
});
|
||
```
|
||
|
||
If the assimliated promise rejects, then the downstream promise will also reject.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
return findCommentsByAuthor(user);
|
||
}).then(function (comments) {
|
||
// If `findCommentsByAuthor` fulfills, we'll have the value here
|
||
}, function (reason) {
|
||
// If `findCommentsByAuthor` rejects, we'll have the reason here
|
||
});
|
||
```
|
||
|
||
Simple Example
|
||
--------------
|
||
|
||
Synchronous Example
|
||
|
||
```javascript
|
||
let result;
|
||
|
||
try {
|
||
result = findResult();
|
||
// success
|
||
} catch(reason) {
|
||
// failure
|
||
}
|
||
```
|
||
|
||
Errback Example
|
||
|
||
```js
|
||
findResult(function(result, err){
|
||
if (err) {
|
||
// failure
|
||
} else {
|
||
// success
|
||
}
|
||
});
|
||
```
|
||
|
||
Promise Example;
|
||
|
||
```javascript
|
||
findResult().then(function(result){
|
||
// success
|
||
}, function(reason){
|
||
// failure
|
||
});
|
||
```
|
||
|
||
Advanced Example
|
||
--------------
|
||
|
||
Synchronous Example
|
||
|
||
```javascript
|
||
let author, books;
|
||
|
||
try {
|
||
author = findAuthor();
|
||
books = findBooksByAuthor(author);
|
||
// success
|
||
} catch(reason) {
|
||
// failure
|
||
}
|
||
```
|
||
|
||
Errback Example
|
||
|
||
```js
|
||
|
||
function foundBooks(books) {
|
||
|
||
}
|
||
|
||
function failure(reason) {
|
||
|
||
}
|
||
|
||
findAuthor(function(author, err){
|
||
if (err) {
|
||
failure(err);
|
||
// failure
|
||
} else {
|
||
try {
|
||
findBoooksByAuthor(author, function(books, err) {
|
||
if (err) {
|
||
failure(err);
|
||
} else {
|
||
try {
|
||
foundBooks(books);
|
||
} catch(reason) {
|
||
failure(reason);
|
||
}
|
||
}
|
||
});
|
||
} catch(error) {
|
||
failure(err);
|
||
}
|
||
// success
|
||
}
|
||
});
|
||
```
|
||
|
||
Promise Example;
|
||
|
||
```javascript
|
||
findAuthor().
|
||
then(findBooksByAuthor).
|
||
then(function(books){
|
||
// found books
|
||
}).catch(function(reason){
|
||
// something went wrong
|
||
});
|
||
```
|
||
|
||
@method then
|
||
@param {Function} onFulfillment
|
||
@param {Function} onRejection
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise}
|
||
*/
|
||
Promise.prototype.then = then;
|
||
|
||
function Result() {
|
||
this.value = undefined;
|
||
}
|
||
|
||
var ERROR = new Result();
|
||
var GET_THEN_ERROR$1 = new Result();
|
||
|
||
function getThen$1(obj) {
|
||
try {
|
||
return obj.then;
|
||
} catch (error) {
|
||
ERROR.value = error;
|
||
return ERROR;
|
||
}
|
||
}
|
||
|
||
function tryApply(f, s, a) {
|
||
try {
|
||
f.apply(s, a);
|
||
} catch (error) {
|
||
ERROR.value = error;
|
||
return ERROR;
|
||
}
|
||
}
|
||
|
||
function makeObject(_, argumentNames) {
|
||
var obj = {};
|
||
var length = _.length;
|
||
var args = new Array(length);
|
||
|
||
for (var x = 0; x < length; x++) {
|
||
args[x] = _[x];
|
||
}
|
||
|
||
for (var i = 0; i < argumentNames.length; i++) {
|
||
var name = argumentNames[i];
|
||
obj[name] = args[i + 1];
|
||
}
|
||
|
||
return obj;
|
||
}
|
||
|
||
function arrayResult(_) {
|
||
var length = _.length;
|
||
var args = new Array(length - 1);
|
||
|
||
for (var i = 1; i < length; i++) {
|
||
args[i - 1] = _[i];
|
||
}
|
||
|
||
return args;
|
||
}
|
||
|
||
function wrapThenable(then, promise) {
|
||
return {
|
||
then: function (onFulFillment, onRejection) {
|
||
return then.call(promise, onFulFillment, onRejection);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
`RSVP.denodeify` takes a 'node-style' function and returns a function that
|
||
will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
|
||
browser when you'd prefer to use promises over using callbacks. For example,
|
||
`denodeify` transforms the following:
|
||
|
||
```javascript
|
||
let fs = require('fs');
|
||
|
||
fs.readFile('myfile.txt', function(err, data){
|
||
if (err) return handleError(err);
|
||
handleData(data);
|
||
});
|
||
```
|
||
|
||
into:
|
||
|
||
```javascript
|
||
let fs = require('fs');
|
||
let readFile = RSVP.denodeify(fs.readFile);
|
||
|
||
readFile('myfile.txt').then(handleData, handleError);
|
||
```
|
||
|
||
If the node function has multiple success parameters, then `denodeify`
|
||
just returns the first one:
|
||
|
||
```javascript
|
||
let request = RSVP.denodeify(require('request'));
|
||
|
||
request('http://example.com').then(function(res) {
|
||
// ...
|
||
});
|
||
```
|
||
|
||
However, if you need all success parameters, setting `denodeify`'s
|
||
second parameter to `true` causes it to return all success parameters
|
||
as an array:
|
||
|
||
```javascript
|
||
let request = RSVP.denodeify(require('request'), true);
|
||
|
||
request('http://example.com').then(function(result) {
|
||
// result[0] -> res
|
||
// result[1] -> body
|
||
});
|
||
```
|
||
|
||
Or if you pass it an array with names it returns the parameters as a hash:
|
||
|
||
```javascript
|
||
let request = RSVP.denodeify(require('request'), ['res', 'body']);
|
||
|
||
request('http://example.com').then(function(result) {
|
||
// result.res
|
||
// result.body
|
||
});
|
||
```
|
||
|
||
Sometimes you need to retain the `this`:
|
||
|
||
```javascript
|
||
let app = require('express')();
|
||
let render = RSVP.denodeify(app.render.bind(app));
|
||
```
|
||
|
||
The denodified function inherits from the original function. It works in all
|
||
environments, except IE 10 and below. Consequently all properties of the original
|
||
function are available to you. However, any properties you change on the
|
||
denodeified function won't be changed on the original function. Example:
|
||
|
||
```javascript
|
||
let request = RSVP.denodeify(require('request')),
|
||
cookieJar = request.jar(); // <- Inheritance is used here
|
||
|
||
request('http://example.com', {jar: cookieJar}).then(function(res) {
|
||
// cookieJar.cookies holds now the cookies returned by example.com
|
||
});
|
||
```
|
||
|
||
Using `denodeify` makes it easier to compose asynchronous operations instead
|
||
of using callbacks. For example, instead of:
|
||
|
||
```javascript
|
||
let fs = require('fs');
|
||
|
||
fs.readFile('myfile.txt', function(err, data){
|
||
if (err) { ... } // Handle error
|
||
fs.writeFile('myfile2.txt', data, function(err){
|
||
if (err) { ... } // Handle error
|
||
console.log('done')
|
||
});
|
||
});
|
||
```
|
||
|
||
you can chain the operations together using `then` from the returned promise:
|
||
|
||
```javascript
|
||
let fs = require('fs');
|
||
let readFile = RSVP.denodeify(fs.readFile);
|
||
let writeFile = RSVP.denodeify(fs.writeFile);
|
||
|
||
readFile('myfile.txt').then(function(data){
|
||
return writeFile('myfile2.txt', data);
|
||
}).then(function(){
|
||
console.log('done')
|
||
}).catch(function(error){
|
||
// Handle error
|
||
});
|
||
```
|
||
|
||
@method denodeify
|
||
@static
|
||
@for RSVP
|
||
@param {Function} nodeFunc a 'node-style' function that takes a callback as
|
||
its last argument. The callback expects an error to be passed as its first
|
||
argument (if an error occurred, otherwise null), and the value from the
|
||
operation as its second argument ('function(err, value){ }').
|
||
@param {Boolean|Array} [options] An optional paramter that if set
|
||
to `true` causes the promise to fulfill with the callback's success arguments
|
||
as an array. This is useful if the node function has multiple success
|
||
paramters. If you set this paramter to an array with names, the promise will
|
||
fulfill with a hash with these names as keys and the success parameters as
|
||
values.
|
||
@return {Function} a function that wraps `nodeFunc` to return an
|
||
`RSVP.Promise`
|
||
@static
|
||
*/
|
||
function denodeify(nodeFunc, options) {
|
||
var fn = function () {
|
||
var self = this;
|
||
var l = arguments.length;
|
||
var args = new Array(l + 1);
|
||
var promiseInput = false;
|
||
|
||
for (var i = 0; i < l; ++i) {
|
||
var arg = arguments[i];
|
||
|
||
if (!promiseInput) {
|
||
// TODO: clean this up
|
||
promiseInput = needsPromiseInput(arg);
|
||
if (promiseInput === GET_THEN_ERROR$1) {
|
||
var p = new Promise(noop);
|
||
reject(p, GET_THEN_ERROR$1.value);
|
||
return p;
|
||
} else if (promiseInput && promiseInput !== true) {
|
||
arg = wrapThenable(promiseInput, arg);
|
||
}
|
||
}
|
||
args[i] = arg;
|
||
}
|
||
|
||
var promise = new Promise(noop);
|
||
|
||
args[l] = function (err, val) {
|
||
if (err) reject(promise, err);else if (options === undefined) resolve(promise, val);else if (options === true) resolve(promise, arrayResult(arguments));else if (isArray(options)) resolve(promise, makeObject(arguments, options));else resolve(promise, val);
|
||
};
|
||
|
||
if (promiseInput) {
|
||
return handlePromiseInput(promise, args, nodeFunc, self);
|
||
} else {
|
||
return handleValueInput(promise, args, nodeFunc, self);
|
||
}
|
||
};
|
||
|
||
fn.__proto__ = nodeFunc;
|
||
|
||
return fn;
|
||
}
|
||
|
||
function handleValueInput(promise, args, nodeFunc, self) {
|
||
var result = tryApply(nodeFunc, self, args);
|
||
if (result === ERROR) {
|
||
reject(promise, result.value);
|
||
}
|
||
return promise;
|
||
}
|
||
|
||
function handlePromiseInput(promise, args, nodeFunc, self) {
|
||
return Promise.all(args).then(function (args) {
|
||
var result = tryApply(nodeFunc, self, args);
|
||
if (result === ERROR) {
|
||
reject(promise, result.value);
|
||
}
|
||
return promise;
|
||
});
|
||
}
|
||
|
||
function needsPromiseInput(arg) {
|
||
if (arg && typeof arg === 'object') {
|
||
if (arg.constructor === Promise) {
|
||
return true;
|
||
} else {
|
||
return getThen$1(arg);
|
||
}
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
This is a convenient alias for `RSVP.Promise.all`.
|
||
|
||
@method all
|
||
@static
|
||
@for RSVP
|
||
@param {Array} array Array of promises.
|
||
@param {String} label An optional label. This is useful
|
||
for tooling.
|
||
*/
|
||
function all$1(array, label) {
|
||
return Promise.all(array, label);
|
||
}
|
||
|
||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||
|
||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
||
|
||
var AllSettled = function (_Enumerator) {
|
||
_inherits(AllSettled, _Enumerator);
|
||
|
||
function AllSettled(Constructor, entries, label) {
|
||
return _possibleConstructorReturn(this, _Enumerator.call(this, Constructor, entries, false /* don't abort on reject */, label));
|
||
}
|
||
|
||
return AllSettled;
|
||
}(Enumerator);
|
||
|
||
AllSettled.prototype._makeResult = makeSettledResult;
|
||
|
||
/**
|
||
`RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
|
||
a fail-fast method, it waits until all the promises have returned and
|
||
shows you all the results. This is useful if you want to handle multiple
|
||
promises' failure states together as a set.
|
||
Returns a promise that is fulfilled when all the given promises have been
|
||
settled. The return promise is fulfilled with an array of the states of
|
||
the promises passed into the `promises` array argument.
|
||
Each state object will either indicate fulfillment or rejection, and
|
||
provide the corresponding value or reason. The states will take one of
|
||
the following formats:
|
||
```javascript
|
||
{ state: 'fulfilled', value: value }
|
||
or
|
||
{ state: 'rejected', reason: reason }
|
||
```
|
||
Example:
|
||
```javascript
|
||
let promise1 = RSVP.Promise.resolve(1);
|
||
let promise2 = RSVP.Promise.reject(new Error('2'));
|
||
let promise3 = RSVP.Promise.reject(new Error('3'));
|
||
let promises = [ promise1, promise2, promise3 ];
|
||
RSVP.allSettled(promises).then(function(array){
|
||
// array == [
|
||
// { state: 'fulfilled', value: 1 },
|
||
// { state: 'rejected', reason: Error },
|
||
// { state: 'rejected', reason: Error }
|
||
// ]
|
||
// Note that for the second item, reason.message will be '2', and for the
|
||
// third item, reason.message will be '3'.
|
||
}, function(error) {
|
||
// Not run. (This block would only be called if allSettled had failed,
|
||
// for instance if passed an incorrect argument type.)
|
||
});
|
||
```
|
||
@method allSettled
|
||
@static
|
||
@for RSVP
|
||
@param {Array} entries
|
||
@param {String} label - optional string that describes the promise.
|
||
Useful for tooling.
|
||
@return {Promise} promise that is fulfilled with an array of the settled
|
||
states of the constituent promises.
|
||
*/
|
||
|
||
function allSettled(entries, label) {
|
||
if (!isArray(entries)) {
|
||
return Promise.reject(new TypeError("Promise.allSettled must be called with an array"), label);
|
||
}
|
||
|
||
return new AllSettled(Promise, entries, label).promise;
|
||
}
|
||
|
||
/**
|
||
This is a convenient alias for `RSVP.Promise.race`.
|
||
|
||
@method race
|
||
@static
|
||
@for RSVP
|
||
@param {Array} array Array of promises.
|
||
@param {String} label An optional label. This is useful
|
||
for tooling.
|
||
*/
|
||
function race$1(array, label) {
|
||
return Promise.race(array, label);
|
||
}
|
||
|
||
function _possibleConstructorReturn$1(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||
|
||
function _inherits$1(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
||
|
||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||
|
||
var PromiseHash = function (_Enumerator) {
|
||
_inherits$1(PromiseHash, _Enumerator);
|
||
|
||
function PromiseHash(Constructor, object) {
|
||
var abortOnReject = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
||
var label = arguments[3];
|
||
return _possibleConstructorReturn$1(this, _Enumerator.call(this, Constructor, object, abortOnReject, label));
|
||
}
|
||
|
||
PromiseHash.prototype._init = function _init(Constructor, object) {
|
||
this._result = {};
|
||
|
||
this._enumerate(object);
|
||
if (this._remaining === 0) {
|
||
fulfill(this.promise, this._result);
|
||
}
|
||
};
|
||
|
||
PromiseHash.prototype._enumerate = function _enumerate(input) {
|
||
var promise = this.promise;
|
||
var results = [];
|
||
|
||
for (var key in input) {
|
||
if (hasOwnProperty.call(input, key)) {
|
||
results.push({
|
||
position: key,
|
||
entry: input[key]
|
||
});
|
||
}
|
||
}
|
||
|
||
var length = results.length;
|
||
this._remaining = length;
|
||
var result = void 0;
|
||
|
||
for (var i = 0; promise._state === PENDING && i < length; i++) {
|
||
result = results[i];
|
||
this._eachEntry(result.entry, result.position);
|
||
}
|
||
};
|
||
|
||
return PromiseHash;
|
||
}(Enumerator);
|
||
|
||
/**
|
||
`RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
|
||
for its `promises` argument.
|
||
|
||
Returns a promise that is fulfilled when all the given promises have been
|
||
fulfilled, or rejected if any of them become rejected. The returned promise
|
||
is fulfilled with a hash that has the same key names as the `promises` object
|
||
argument. If any of the values in the object are not promises, they will
|
||
simply be copied over to the fulfilled object.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promises = {
|
||
myPromise: RSVP.resolve(1),
|
||
yourPromise: RSVP.resolve(2),
|
||
theirPromise: RSVP.resolve(3),
|
||
notAPromise: 4
|
||
};
|
||
|
||
RSVP.hash(promises).then(function(hash){
|
||
// hash here is an object that looks like:
|
||
// {
|
||
// myPromise: 1,
|
||
// yourPromise: 2,
|
||
// theirPromise: 3,
|
||
// notAPromise: 4
|
||
// }
|
||
});
|
||
````
|
||
|
||
If any of the `promises` given to `RSVP.hash` are rejected, the first promise
|
||
that is rejected will be given as the reason to the rejection handler.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promises = {
|
||
myPromise: RSVP.resolve(1),
|
||
rejectedPromise: RSVP.reject(new Error('rejectedPromise')),
|
||
anotherRejectedPromise: RSVP.reject(new Error('anotherRejectedPromise')),
|
||
};
|
||
|
||
RSVP.hash(promises).then(function(hash){
|
||
// Code here never runs because there are rejected promises!
|
||
}, function(reason) {
|
||
// reason.message === 'rejectedPromise'
|
||
});
|
||
```
|
||
|
||
An important note: `RSVP.hash` is intended for plain JavaScript objects that
|
||
are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
|
||
chains.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
function MyConstructor(){
|
||
this.example = RSVP.resolve('Example');
|
||
}
|
||
|
||
MyConstructor.prototype = {
|
||
protoProperty: RSVP.resolve('Proto Property')
|
||
};
|
||
|
||
let myObject = new MyConstructor();
|
||
|
||
RSVP.hash(myObject).then(function(hash){
|
||
// protoProperty will not be present, instead you will just have an
|
||
// object that looks like:
|
||
// {
|
||
// example: 'Example'
|
||
// }
|
||
//
|
||
// hash.hasOwnProperty('protoProperty'); // false
|
||
// 'undefined' === typeof hash.protoProperty
|
||
});
|
||
```
|
||
|
||
@method hash
|
||
@static
|
||
@for RSVP
|
||
@param {Object} object
|
||
@param {String} label optional string that describes the promise.
|
||
Useful for tooling.
|
||
@return {Promise} promise that is fulfilled when all properties of `promises`
|
||
have been fulfilled, or rejected if any of them become rejected.
|
||
*/
|
||
function hash(object, label) {
|
||
if (!isObject(object)) {
|
||
return Promise.reject(new TypeError("Promise.hash must be called with an object"), label);
|
||
}
|
||
|
||
return new PromiseHash(Promise, object, label).promise;
|
||
}
|
||
|
||
function _possibleConstructorReturn$2(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||
|
||
function _inherits$2(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
||
|
||
var HashSettled = function (_PromiseHash) {
|
||
_inherits$2(HashSettled, _PromiseHash);
|
||
|
||
function HashSettled(Constructor, object, label) {
|
||
return _possibleConstructorReturn$2(this, _PromiseHash.call(this, Constructor, object, false, label));
|
||
}
|
||
|
||
return HashSettled;
|
||
}(PromiseHash);
|
||
|
||
HashSettled.prototype._makeResult = makeSettledResult;
|
||
|
||
/**
|
||
`RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object
|
||
instead of an array for its `promises` argument.
|
||
|
||
Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method,
|
||
but like `RSVP.allSettled`, `hashSettled` waits until all the
|
||
constituent promises have returned and then shows you all the results
|
||
with their states and values/reasons. This is useful if you want to
|
||
handle multiple promises' failure states together as a set.
|
||
|
||
Returns a promise that is fulfilled when all the given promises have been
|
||
settled, or rejected if the passed parameters are invalid.
|
||
|
||
The returned promise is fulfilled with a hash that has the same key names as
|
||
the `promises` object argument. If any of the values in the object are not
|
||
promises, they will be copied over to the fulfilled object and marked with state
|
||
'fulfilled'.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promises = {
|
||
myPromise: RSVP.Promise.resolve(1),
|
||
yourPromise: RSVP.Promise.resolve(2),
|
||
theirPromise: RSVP.Promise.resolve(3),
|
||
notAPromise: 4
|
||
};
|
||
|
||
RSVP.hashSettled(promises).then(function(hash){
|
||
// hash here is an object that looks like:
|
||
// {
|
||
// myPromise: { state: 'fulfilled', value: 1 },
|
||
// yourPromise: { state: 'fulfilled', value: 2 },
|
||
// theirPromise: { state: 'fulfilled', value: 3 },
|
||
// notAPromise: { state: 'fulfilled', value: 4 }
|
||
// }
|
||
});
|
||
```
|
||
|
||
If any of the `promises` given to `RSVP.hash` are rejected, the state will
|
||
be set to 'rejected' and the reason for rejection provided.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let promises = {
|
||
myPromise: RSVP.Promise.resolve(1),
|
||
rejectedPromise: RSVP.Promise.reject(new Error('rejection')),
|
||
anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')),
|
||
};
|
||
|
||
RSVP.hashSettled(promises).then(function(hash){
|
||
// hash here is an object that looks like:
|
||
// {
|
||
// myPromise: { state: 'fulfilled', value: 1 },
|
||
// rejectedPromise: { state: 'rejected', reason: Error },
|
||
// anotherRejectedPromise: { state: 'rejected', reason: Error },
|
||
// }
|
||
// Note that for rejectedPromise, reason.message == 'rejection',
|
||
// and for anotherRejectedPromise, reason.message == 'more rejection'.
|
||
});
|
||
```
|
||
|
||
An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that
|
||
are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype
|
||
chains.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
function MyConstructor(){
|
||
this.example = RSVP.Promise.resolve('Example');
|
||
}
|
||
|
||
MyConstructor.prototype = {
|
||
protoProperty: RSVP.Promise.resolve('Proto Property')
|
||
};
|
||
|
||
let myObject = new MyConstructor();
|
||
|
||
RSVP.hashSettled(myObject).then(function(hash){
|
||
// protoProperty will not be present, instead you will just have an
|
||
// object that looks like:
|
||
// {
|
||
// example: { state: 'fulfilled', value: 'Example' }
|
||
// }
|
||
//
|
||
// hash.hasOwnProperty('protoProperty'); // false
|
||
// 'undefined' === typeof hash.protoProperty
|
||
});
|
||
```
|
||
|
||
@method hashSettled
|
||
@for RSVP
|
||
@param {Object} object
|
||
@param {String} label optional string that describes the promise.
|
||
Useful for tooling.
|
||
@return {Promise} promise that is fulfilled when when all properties of `promises`
|
||
have been settled.
|
||
@static
|
||
*/
|
||
|
||
function hashSettled(object, label) {
|
||
if (!isObject(object)) {
|
||
return Promise.reject(new TypeError("RSVP.hashSettled must be called with an object"), label);
|
||
}
|
||
|
||
return new HashSettled(Promise, object, false, label).promise;
|
||
}
|
||
|
||
/**
|
||
`RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
|
||
loop in order to aid debugging.
|
||
|
||
Promises A+ specifies that any exceptions that occur with a promise must be
|
||
caught by the promises implementation and bubbled to the last handler. For
|
||
this reason, it is recommended that you always specify a second rejection
|
||
handler function to `then`. However, `RSVP.rethrow` will throw the exception
|
||
outside of the promise, so it bubbles up to your console if in the browser,
|
||
or domain/cause uncaught exception in Node. `rethrow` will also throw the
|
||
error again so the error can be handled by the promise per the spec.
|
||
|
||
```javascript
|
||
function throws(){
|
||
throw new Error('Whoops!');
|
||
}
|
||
|
||
let promise = new RSVP.Promise(function(resolve, reject){
|
||
throws();
|
||
});
|
||
|
||
promise.catch(RSVP.rethrow).then(function(){
|
||
// Code here doesn't run because the promise became rejected due to an
|
||
// error!
|
||
}, function (err){
|
||
// handle the error here
|
||
});
|
||
```
|
||
|
||
The 'Whoops' error will be thrown on the next turn of the event loop
|
||
and you can watch for it in your console. You can also handle it using a
|
||
rejection handler given to `.then` or `.catch` on the returned promise.
|
||
|
||
@method rethrow
|
||
@static
|
||
@for RSVP
|
||
@param {Error} reason reason the promise became rejected.
|
||
@throws Error
|
||
@static
|
||
*/
|
||
function rethrow(reason) {
|
||
setTimeout(function () {
|
||
throw reason;
|
||
});
|
||
throw reason;
|
||
}
|
||
|
||
/**
|
||
`RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
|
||
`RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
|
||
interface. New code should use the `RSVP.Promise` constructor instead.
|
||
|
||
The object returned from `RSVP.defer` is a plain object with three properties:
|
||
|
||
* promise - an `RSVP.Promise`.
|
||
* reject - a function that causes the `promise` property on this object to
|
||
become rejected
|
||
* resolve - a function that causes the `promise` property on this object to
|
||
become fulfilled.
|
||
|
||
Example:
|
||
|
||
```javascript
|
||
let deferred = RSVP.defer();
|
||
|
||
deferred.resolve("Success!");
|
||
|
||
deferred.promise.then(function(value){
|
||
// value here is "Success!"
|
||
});
|
||
```
|
||
|
||
@method defer
|
||
@static
|
||
@for RSVP
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Object}
|
||
*/
|
||
|
||
function defer(label) {
|
||
var deferred = { resolve: undefined, reject: undefined };
|
||
|
||
deferred.promise = new Promise(function (resolve, reject) {
|
||
deferred.resolve = resolve;
|
||
deferred.reject = reject;
|
||
}, label);
|
||
|
||
return deferred;
|
||
}
|
||
|
||
/**
|
||
`RSVP.map` is similar to JavaScript's native `map` method, except that it
|
||
waits for all promises to become fulfilled before running the `mapFn` on
|
||
each item in given to `promises`. `RSVP.map` returns a promise that will
|
||
become fulfilled with the result of running `mapFn` on the values the promises
|
||
become fulfilled with.
|
||
|
||
For example:
|
||
|
||
```javascript
|
||
|
||
let promise1 = RSVP.resolve(1);
|
||
let promise2 = RSVP.resolve(2);
|
||
let promise3 = RSVP.resolve(3);
|
||
let promises = [ promise1, promise2, promise3 ];
|
||
|
||
let mapFn = function(item){
|
||
return item + 1;
|
||
};
|
||
|
||
RSVP.map(promises, mapFn).then(function(result){
|
||
// result is [ 2, 3, 4 ]
|
||
});
|
||
```
|
||
|
||
If any of the `promises` given to `RSVP.map` are rejected, the first promise
|
||
that is rejected will be given as an argument to the returned promise's
|
||
rejection handler. For example:
|
||
|
||
```javascript
|
||
let promise1 = RSVP.resolve(1);
|
||
let promise2 = RSVP.reject(new Error('2'));
|
||
let promise3 = RSVP.reject(new Error('3'));
|
||
let promises = [ promise1, promise2, promise3 ];
|
||
|
||
let mapFn = function(item){
|
||
return item + 1;
|
||
};
|
||
|
||
RSVP.map(promises, mapFn).then(function(array){
|
||
// Code here never runs because there are rejected promises!
|
||
}, function(reason) {
|
||
// reason.message === '2'
|
||
});
|
||
```
|
||
|
||
`RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
|
||
say you want to get all comments from a set of blog posts, but you need
|
||
the blog posts first because they contain a url to those comments.
|
||
|
||
```javscript
|
||
|
||
let mapFn = function(blogPost){
|
||
// getComments does some ajax and returns an RSVP.Promise that is fulfilled
|
||
// with some comments data
|
||
return getComments(blogPost.comments_url);
|
||
};
|
||
|
||
// getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
|
||
// with some blog post data
|
||
RSVP.map(getBlogPosts(), mapFn).then(function(comments){
|
||
// comments is the result of asking the server for the comments
|
||
// of all blog posts returned from getBlogPosts()
|
||
});
|
||
```
|
||
|
||
@method map
|
||
@static
|
||
@for RSVP
|
||
@param {Array} promises
|
||
@param {Function} mapFn function to be called on each fulfilled promise.
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise} promise that is fulfilled with the result of calling
|
||
`mapFn` on each fulfilled promise or value when they become fulfilled.
|
||
The promise will be rejected if any of the given `promises` become rejected.
|
||
@static
|
||
*/
|
||
function map(promises, mapFn, label) {
|
||
if (!isArray(promises)) {
|
||
return Promise.reject(new TypeError("RSVP.map must be called with an array"), label);
|
||
}
|
||
|
||
if (!isFunction(mapFn)) {
|
||
return Promise.reject(new TypeError("RSVP.map expects a function as a second argument"), label);
|
||
}
|
||
|
||
return Promise.all(promises, label).then(function (values) {
|
||
var length = values.length;
|
||
var results = new Array(length);
|
||
|
||
for (var i = 0; i < length; i++) {
|
||
results[i] = mapFn(values[i]);
|
||
}
|
||
|
||
return Promise.all(results, label);
|
||
});
|
||
}
|
||
|
||
/**
|
||
This is a convenient alias for `RSVP.Promise.resolve`.
|
||
|
||
@method resolve
|
||
@static
|
||
@for RSVP
|
||
@param {*} value value that the returned promise will be resolved with
|
||
@param {String} label optional string for identifying the returned promise.
|
||
Useful for tooling.
|
||
@return {Promise} a promise that will become fulfilled with the given
|
||
`value`
|
||
*/
|
||
function resolve$2(value, label) {
|
||
return Promise.resolve(value, label);
|
||
}
|
||
|
||
/**
|
||
This is a convenient alias for `RSVP.Promise.reject`.
|
||
|
||
@method reject
|
||
@static
|
||
@for RSVP
|
||
@param {*} reason value that the returned promise will be rejected with.
|
||
@param {String} label optional string for identifying the returned promise.
|
||
Useful for tooling.
|
||
@return {Promise} a promise rejected with the given `reason`.
|
||
*/
|
||
function reject$2(reason, label) {
|
||
return Promise.reject(reason, label);
|
||
}
|
||
|
||
/**
|
||
`RSVP.filter` is similar to JavaScript's native `filter` method, except that it
|
||
waits for all promises to become fulfilled before running the `filterFn` on
|
||
each item in given to `promises`. `RSVP.filter` returns a promise that will
|
||
become fulfilled with the result of running `filterFn` on the values the
|
||
promises become fulfilled with.
|
||
|
||
For example:
|
||
|
||
```javascript
|
||
|
||
let promise1 = RSVP.resolve(1);
|
||
let promise2 = RSVP.resolve(2);
|
||
let promise3 = RSVP.resolve(3);
|
||
|
||
let promises = [promise1, promise2, promise3];
|
||
|
||
let filterFn = function(item){
|
||
return item > 1;
|
||
};
|
||
|
||
RSVP.filter(promises, filterFn).then(function(result){
|
||
// result is [ 2, 3 ]
|
||
});
|
||
```
|
||
|
||
If any of the `promises` given to `RSVP.filter` are rejected, the first promise
|
||
that is rejected will be given as an argument to the returned promise's
|
||
rejection handler. For example:
|
||
|
||
```javascript
|
||
let promise1 = RSVP.resolve(1);
|
||
let promise2 = RSVP.reject(new Error('2'));
|
||
let promise3 = RSVP.reject(new Error('3'));
|
||
let promises = [ promise1, promise2, promise3 ];
|
||
|
||
let filterFn = function(item){
|
||
return item > 1;
|
||
};
|
||
|
||
RSVP.filter(promises, filterFn).then(function(array){
|
||
// Code here never runs because there are rejected promises!
|
||
}, function(reason) {
|
||
// reason.message === '2'
|
||
});
|
||
```
|
||
|
||
`RSVP.filter` will also wait for any promises returned from `filterFn`.
|
||
For instance, you may want to fetch a list of users then return a subset
|
||
of those users based on some asynchronous operation:
|
||
|
||
```javascript
|
||
|
||
let alice = { name: 'alice' };
|
||
let bob = { name: 'bob' };
|
||
let users = [ alice, bob ];
|
||
|
||
let promises = users.map(function(user){
|
||
return RSVP.resolve(user);
|
||
});
|
||
|
||
let filterFn = function(user){
|
||
// Here, Alice has permissions to create a blog post, but Bob does not.
|
||
return getPrivilegesForUser(user).then(function(privs){
|
||
return privs.can_create_blog_post === true;
|
||
});
|
||
};
|
||
RSVP.filter(promises, filterFn).then(function(users){
|
||
// true, because the server told us only Alice can create a blog post.
|
||
users.length === 1;
|
||
// false, because Alice is the only user present in `users`
|
||
users[0] === bob;
|
||
});
|
||
```
|
||
|
||
@method filter
|
||
@static
|
||
@for RSVP
|
||
@param {Array} promises
|
||
@param {Function} filterFn - function to be called on each resolved value to
|
||
filter the final results.
|
||
@param {String} label optional string describing the promise. Useful for
|
||
tooling.
|
||
@return {Promise}
|
||
*/
|
||
|
||
function resolveAll(promises, label) {
|
||
return Promise.all(promises, label);
|
||
}
|
||
|
||
function resolveSingle(promise, label) {
|
||
return Promise.resolve(promise, label).then(function (promises) {
|
||
return resolveAll(promises, label);
|
||
});
|
||
}
|
||
|
||
function filter(promises, filterFn, label) {
|
||
if (!isArray(promises) && !(isObject(promises) && promises.then !== undefined)) {
|
||
return Promise.reject(new TypeError("RSVP.filter must be called with an array or promise"), label);
|
||
}
|
||
|
||
if (!isFunction(filterFn)) {
|
||
return Promise.reject(new TypeError("RSVP.filter expects function as a second argument"), label);
|
||
}
|
||
|
||
var promise = isArray(promises) ? resolveAll(promises, label) : resolveSingle(promises, label);
|
||
return promise.then(function (values) {
|
||
var length = values.length;
|
||
var filtered = new Array(length);
|
||
|
||
for (var i = 0; i < length; i++) {
|
||
filtered[i] = filterFn(values[i]);
|
||
}
|
||
|
||
return resolveAll(filtered, label).then(function (filtered) {
|
||
var results = new Array(length);
|
||
var newLength = 0;
|
||
|
||
for (var _i = 0; _i < length; _i++) {
|
||
if (filtered[_i]) {
|
||
results[newLength] = values[_i];
|
||
newLength++;
|
||
}
|
||
}
|
||
|
||
results.length = newLength;
|
||
|
||
return results;
|
||
});
|
||
});
|
||
}
|
||
|
||
var len = 0;
|
||
var vertxNext = void 0;
|
||
function asap(callback, arg) {
|
||
queue$1[len] = callback;
|
||
queue$1[len + 1] = arg;
|
||
len += 2;
|
||
if (len === 2) {
|
||
// If len is 1, that means that we need to schedule an async flush.
|
||
// If additional callbacks are queued before the queue is flushed, they
|
||
// will be processed by this flush that we are scheduling.
|
||
scheduleFlush$1();
|
||
}
|
||
}
|
||
|
||
var browserWindow = typeof window !== 'undefined' ? window : undefined;
|
||
var browserGlobal = browserWindow || {};
|
||
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
|
||
var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
|
||
|
||
// test for web worker but not in IE10
|
||
var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
|
||
|
||
// node
|
||
function useNextTick() {
|
||
var nextTick = process.nextTick;
|
||
// node version 0.10.x displays a deprecation warning when nextTick is used recursively
|
||
// setImmediate should be used instead instead
|
||
var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
|
||
if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
|
||
nextTick = setImmediate;
|
||
}
|
||
return function () {
|
||
return nextTick(flush);
|
||
};
|
||
}
|
||
|
||
// vertx
|
||
function useVertxTimer() {
|
||
if (typeof vertxNext !== 'undefined') {
|
||
return function () {
|
||
vertxNext(flush);
|
||
};
|
||
}
|
||
return useSetTimeout();
|
||
}
|
||
|
||
function useMutationObserver() {
|
||
var iterations = 0;
|
||
var observer = new BrowserMutationObserver(flush);
|
||
var node = document.createTextNode('');
|
||
observer.observe(node, { characterData: true });
|
||
|
||
return function () {
|
||
return node.data = iterations = ++iterations % 2;
|
||
};
|
||
}
|
||
|
||
// web worker
|
||
function useMessageChannel() {
|
||
var channel = new MessageChannel();
|
||
channel.port1.onmessage = flush;
|
||
return function () {
|
||
return channel.port2.postMessage(0);
|
||
};
|
||
}
|
||
|
||
function useSetTimeout() {
|
||
return function () {
|
||
return setTimeout(flush, 1);
|
||
};
|
||
}
|
||
|
||
var queue$1 = new Array(1000);
|
||
|
||
function flush() {
|
||
for (var i = 0; i < len; i += 2) {
|
||
var callback = queue$1[i];
|
||
var arg = queue$1[i + 1];
|
||
|
||
callback(arg);
|
||
|
||
queue$1[i] = undefined;
|
||
queue$1[i + 1] = undefined;
|
||
}
|
||
|
||
len = 0;
|
||
}
|
||
|
||
function attemptVertex() {
|
||
try {
|
||
var r = require;
|
||
var vertx = r('vertx');
|
||
vertxNext = vertx.runOnLoop || vertx.runOnContext;
|
||
return useVertxTimer();
|
||
} catch (e) {
|
||
return useSetTimeout();
|
||
}
|
||
}
|
||
|
||
var scheduleFlush$1 = void 0;
|
||
// Decide what async method to use to triggering processing of queued callbacks:
|
||
if (isNode) {
|
||
scheduleFlush$1 = useNextTick();
|
||
} else if (BrowserMutationObserver) {
|
||
scheduleFlush$1 = useMutationObserver();
|
||
} else if (isWorker) {
|
||
scheduleFlush$1 = useMessageChannel();
|
||
} else if (browserWindow === undefined && typeof require === 'function') {
|
||
scheduleFlush$1 = attemptVertex();
|
||
} else {
|
||
scheduleFlush$1 = useSetTimeout();
|
||
}
|
||
|
||
var platform = void 0;
|
||
|
||
/* global self */
|
||
if (typeof self === 'object') {
|
||
platform = self;
|
||
|
||
/* global global */
|
||
} else if (typeof global === 'object') {
|
||
platform = global;
|
||
} else {
|
||
throw new Error('no global: `self` or `global` found');
|
||
}
|
||
|
||
var _asap$cast$Promise$Ev;
|
||
|
||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||
|
||
// defaults
|
||
config.async = asap;
|
||
config.after = function (cb) {
|
||
return setTimeout(cb, 0);
|
||
};
|
||
var cast = resolve$2;
|
||
|
||
var async = function (callback, arg) {
|
||
return config.async(callback, arg);
|
||
};
|
||
|
||
function on() {
|
||
config['on'].apply(config, arguments);
|
||
}
|
||
|
||
function off() {
|
||
config['off'].apply(config, arguments);
|
||
}
|
||
|
||
// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
|
||
if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') {
|
||
var callbacks = window['__PROMISE_INSTRUMENTATION__'];
|
||
configure('instrument', true);
|
||
for (var eventName in callbacks) {
|
||
if (callbacks.hasOwnProperty(eventName)) {
|
||
on(eventName, callbacks[eventName]);
|
||
}
|
||
}
|
||
}
|
||
|
||
// the default export here is for backwards compat:
|
||
// https://github.com/tildeio/rsvp.js/issues/434
|
||
var rsvp = (_asap$cast$Promise$Ev = {
|
||
asap: asap,
|
||
cast: cast,
|
||
Promise: Promise,
|
||
EventTarget: EventTarget,
|
||
all: all$1,
|
||
allSettled: allSettled,
|
||
race: race$1,
|
||
hash: hash,
|
||
hashSettled: hashSettled,
|
||
rethrow: rethrow,
|
||
defer: defer,
|
||
denodeify: denodeify,
|
||
configure: configure,
|
||
on: on,
|
||
off: off,
|
||
resolve: resolve$2,
|
||
reject: reject$2,
|
||
map: map
|
||
}, _defineProperty(_asap$cast$Promise$Ev, 'async', async), _defineProperty(_asap$cast$Promise$Ev, 'filter', filter), _asap$cast$Promise$Ev);
|
||
|
||
exports['default'] = rsvp;
|
||
exports.asap = asap;
|
||
exports.cast = cast;
|
||
exports.Promise = Promise;
|
||
exports.EventTarget = EventTarget;
|
||
exports.all = all$1;
|
||
exports.allSettled = allSettled;
|
||
exports.race = race$1;
|
||
exports.hash = hash;
|
||
exports.hashSettled = hashSettled;
|
||
exports.rethrow = rethrow;
|
||
exports.defer = defer;
|
||
exports.denodeify = denodeify;
|
||
exports.configure = configure;
|
||
exports.on = on;
|
||
exports.off = off;
|
||
exports.resolve = resolve$2;
|
||
exports.reject = reject$2;
|
||
exports.map = map;
|
||
exports.async = async;
|
||
exports.filter = filter;
|
||
|
||
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
||
})));
|
||
|
||
//
|
||
|
||
var EPUBJS = EPUBJS || {};
|
||
EPUBJS.core = {};
|
||
|
||
var ELEMENT_NODE = 1;
|
||
var TEXT_NODE = 3;
|
||
var COMMENT_NODE = 8;
|
||
var DOCUMENT_NODE = 9;
|
||
|
||
//-- Get a element for an id
|
||
EPUBJS.core.getEl = function(elem) {
|
||
return document.getElementById(elem);
|
||
};
|
||
|
||
//-- Get all elements for a class
|
||
EPUBJS.core.getEls = function(classes) {
|
||
return document.getElementsByClassName(classes);
|
||
};
|
||
|
||
EPUBJS.core.request = function(url, type, withCredentials) {
|
||
var supportsURL = window.URL;
|
||
var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
|
||
var deferred = new RSVP.defer();
|
||
var xhr = new XMLHttpRequest();
|
||
var uri;
|
||
|
||
//-- Check from PDF.js:
|
||
// https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
|
||
var xhrPrototype = XMLHttpRequest.prototype;
|
||
|
||
var handler = function() {
|
||
var r;
|
||
|
||
if (this.readyState != this.DONE) return;
|
||
|
||
if ((this.status === 200 || this.status === 0) && this.response) { // Android & Firefox reporting 0 for local & blob urls
|
||
if (type == 'xml'){
|
||
// If this.responseXML wasn't set, try to parse using a DOMParser from text
|
||
if(!this.responseXML) {
|
||
r = new DOMParser().parseFromString(this.response, "application/xml");
|
||
} else {
|
||
r = this.responseXML;
|
||
}
|
||
} else if (type == 'xhtml') {
|
||
if (!this.responseXML){
|
||
r = new DOMParser().parseFromString(this.response, "application/xhtml+xml");
|
||
} else {
|
||
r = this.responseXML;
|
||
}
|
||
} else if (type == 'html') {
|
||
if (!this.responseXML){
|
||
r = new DOMParser().parseFromString(this.response, "text/html");
|
||
} else {
|
||
r = this.responseXML;
|
||
}
|
||
} else if (type == 'json') {
|
||
r = JSON.parse(this.response);
|
||
} else if (type == 'blob') {
|
||
if (supportsURL) {
|
||
r = this.response;
|
||
} else {
|
||
//-- Safari doesn't support responseType blob, so create a blob from arraybuffer
|
||
r = new Blob([this.response]);
|
||
}
|
||
} else {
|
||
r = this.response;
|
||
}
|
||
|
||
deferred.resolve(r);
|
||
} else {
|
||
deferred.reject({
|
||
message : this.response,
|
||
stack : new Error().stack
|
||
});
|
||
}
|
||
};
|
||
|
||
if (!('overrideMimeType' in xhrPrototype)) {
|
||
// IE10 might have response, but not overrideMimeType
|
||
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||
});
|
||
}
|
||
|
||
xhr.onreadystatechange = handler;
|
||
xhr.open("GET", url, true);
|
||
|
||
if(withCredentials) {
|
||
xhr.withCredentials = true;
|
||
}
|
||
|
||
// If type isn't set, determine it from the file extension
|
||
if(!type) {
|
||
uri = EPUBJS.core.uri(url);
|
||
type = uri.extension;
|
||
type = {
|
||
'htm': 'html'
|
||
}[type] || type;
|
||
}
|
||
|
||
if(type == 'blob'){
|
||
xhr.responseType = BLOB_RESPONSE;
|
||
}
|
||
|
||
if(type == "json") {
|
||
xhr.setRequestHeader("Accept", "application/json");
|
||
}
|
||
|
||
if(type == 'xml') {
|
||
xhr.responseType = "document";
|
||
xhr.overrideMimeType('text/xml'); // for OPF parsing
|
||
}
|
||
|
||
if(type == 'xhtml') {
|
||
xhr.responseType = "document";
|
||
}
|
||
|
||
if(type == 'html') {
|
||
xhr.responseType = "document";
|
||
}
|
||
|
||
if(type == "binary") {
|
||
xhr.responseType = "arraybuffer";
|
||
}
|
||
|
||
xhr.send();
|
||
|
||
return deferred.promise;
|
||
};
|
||
|
||
EPUBJS.core.toArray = function(obj) {
|
||
var arr = [];
|
||
|
||
for (var member in obj) {
|
||
var newitm;
|
||
if ( obj.hasOwnProperty(member) ) {
|
||
newitm = obj[member];
|
||
newitm.ident = member;
|
||
arr.push(newitm);
|
||
}
|
||
}
|
||
|
||
return arr;
|
||
};
|
||
|
||
//-- Parse the different parts of a url, returning a object
|
||
EPUBJS.core.uri = function(url){
|
||
var uri = {
|
||
protocol : '',
|
||
host : '',
|
||
path : '',
|
||
origin : '',
|
||
directory : '',
|
||
base : '',
|
||
filename : '',
|
||
extension : '',
|
||
fragment : '',
|
||
href : url
|
||
},
|
||
blob = url.indexOf('blob:'),
|
||
doubleSlash = url.indexOf('://'),
|
||
search = url.indexOf('?'),
|
||
fragment = url.indexOf("#"),
|
||
withoutProtocol,
|
||
dot,
|
||
firstSlash;
|
||
|
||
if(blob === 0) {
|
||
uri.protocol = "blob";
|
||
uri.base = url.indexOf(0, fragment);
|
||
return uri;
|
||
}
|
||
|
||
if(fragment != -1) {
|
||
uri.fragment = url.slice(fragment + 1);
|
||
url = url.slice(0, fragment);
|
||
}
|
||
|
||
if(search != -1) {
|
||
uri.search = url.slice(search + 1);
|
||
url = url.slice(0, search);
|
||
href = uri.href;
|
||
}
|
||
|
||
if(doubleSlash != -1) {
|
||
uri.protocol = url.slice(0, doubleSlash);
|
||
withoutProtocol = url.slice(doubleSlash+3);
|
||
firstSlash = withoutProtocol.indexOf('/');
|
||
|
||
if(firstSlash === -1) {
|
||
uri.host = uri.path;
|
||
uri.path = "";
|
||
} else {
|
||
uri.host = withoutProtocol.slice(0, firstSlash);
|
||
uri.path = withoutProtocol.slice(firstSlash);
|
||
}
|
||
|
||
|
||
uri.origin = uri.protocol + "://" + uri.host;
|
||
|
||
uri.directory = EPUBJS.core.folder(uri.path);
|
||
|
||
uri.base = uri.origin + uri.directory;
|
||
// return origin;
|
||
} else {
|
||
uri.path = url;
|
||
uri.directory = EPUBJS.core.folder(url);
|
||
uri.base = uri.directory;
|
||
}
|
||
|
||
//-- Filename
|
||
uri.filename = url.replace(uri.base, '');
|
||
dot = uri.filename.lastIndexOf('.');
|
||
if(dot != -1) {
|
||
uri.extension = uri.filename.slice(dot+1);
|
||
}
|
||
return uri;
|
||
};
|
||
|
||
//-- Parse out the folder, will return everything before the last slash
|
||
|
||
EPUBJS.core.folder = function(url){
|
||
|
||
var lastSlash = url.lastIndexOf('/');
|
||
|
||
if(lastSlash == -1) var folder = '';
|
||
|
||
folder = url.slice(0, lastSlash + 1);
|
||
|
||
return folder;
|
||
|
||
};
|
||
|
||
//-- https://github.com/ebidel/filer.js/blob/master/src/filer.js#L128
|
||
EPUBJS.core.dataURLToBlob = function(dataURL) {
|
||
var BASE64_MARKER = ';base64,',
|
||
parts, contentType, raw, rawLength, uInt8Array;
|
||
|
||
if (dataURL.indexOf(BASE64_MARKER) == -1) {
|
||
parts = dataURL.split(',');
|
||
contentType = parts[0].split(':')[1];
|
||
raw = parts[1];
|
||
|
||
return new Blob([raw], {type: contentType});
|
||
}
|
||
|
||
parts = dataURL.split(BASE64_MARKER);
|
||
contentType = parts[0].split(':')[1];
|
||
raw = window.atob(parts[1]);
|
||
rawLength = raw.length;
|
||
|
||
uInt8Array = new Uint8Array(rawLength);
|
||
|
||
for (var i = 0; i < rawLength; ++i) {
|
||
uInt8Array[i] = raw.charCodeAt(i);
|
||
}
|
||
|
||
return new Blob([uInt8Array], {type: contentType});
|
||
};
|
||
|
||
//-- Load scripts async: http://stackoverflow.com/questions/7718935/load-scripts-asynchronously
|
||
EPUBJS.core.addScript = function(src, callback, target) {
|
||
var s, r;
|
||
r = false;
|
||
s = document.createElement('script');
|
||
s.type = 'text/javascript';
|
||
s.async = false;
|
||
s.src = src;
|
||
s.onload = s.onreadystatechange = function() {
|
||
if ( !r && (!this.readyState || this.readyState == 'complete') ) {
|
||
r = true;
|
||
if(callback) callback();
|
||
}
|
||
};
|
||
target = target || document.body;
|
||
target.appendChild(s);
|
||
};
|
||
|
||
EPUBJS.core.addScripts = function(srcArr, callback, target) {
|
||
var total = srcArr.length,
|
||
curr = 0,
|
||
cb = function(){
|
||
curr++;
|
||
if(total == curr){
|
||
if(callback) callback();
|
||
}else{
|
||
EPUBJS.core.addScript(srcArr[curr], cb, target);
|
||
}
|
||
};
|
||
|
||
EPUBJS.core.addScript(srcArr[curr], cb, target);
|
||
};
|
||
|
||
EPUBJS.core.addCss = function(src, callback, target) {
|
||
var s, r;
|
||
r = false;
|
||
s = document.createElement('link');
|
||
s.type = 'text/css';
|
||
s.rel = "stylesheet";
|
||
s.href = src;
|
||
s.onload = s.onreadystatechange = function() {
|
||
if ( !r && (!this.readyState || this.readyState == 'complete') ) {
|
||
r = true;
|
||
if(callback) callback();
|
||
}
|
||
};
|
||
target = target || document.body;
|
||
target.appendChild(s);
|
||
};
|
||
|
||
EPUBJS.core.prefixed = function(unprefixed) {
|
||
var vendors = ["Webkit", "Moz", "O", "ms" ],
|
||
prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'],
|
||
upper = unprefixed[0].toUpperCase() + unprefixed.slice(1),
|
||
length = vendors.length;
|
||
|
||
if (typeof(document.documentElement.style[unprefixed]) != 'undefined') {
|
||
return unprefixed;
|
||
}
|
||
|
||
for ( var i=0; i < length; i++ ) {
|
||
if (typeof(document.documentElement.style[vendors[i] + upper]) != 'undefined') {
|
||
return vendors[i] + upper;
|
||
}
|
||
}
|
||
|
||
return unprefixed;
|
||
};
|
||
|
||
EPUBJS.core.resolveUrl = function(base, path) {
|
||
var url,
|
||
segments = [],
|
||
uri = EPUBJS.core.uri(path),
|
||
folders = base.split("/"),
|
||
paths;
|
||
|
||
if(uri.host) {
|
||
return path;
|
||
}
|
||
|
||
folders.pop();
|
||
|
||
paths = path.split("/");
|
||
paths.forEach(function(p){
|
||
if(p === ".."){
|
||
folders.pop();
|
||
}else{
|
||
segments.push(p);
|
||
}
|
||
});
|
||
|
||
url = folders.concat(segments);
|
||
|
||
return url.join("/");
|
||
};
|
||
|
||
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
||
EPUBJS.core.uuid = function() {
|
||
var d = new Date().getTime();
|
||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||
var r = (d + Math.random()*16)%16 | 0;
|
||
d = Math.floor(d/16);
|
||
return (c=='x' ? r : (r&0x7|0x8)).toString(16);
|
||
});
|
||
return uuid;
|
||
};
|
||
|
||
// Fast quicksort insert for sorted array -- based on:
|
||
// http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
|
||
EPUBJS.core.insert = function(item, array, compareFunction) {
|
||
var location = EPUBJS.core.locationOf(item, array, compareFunction);
|
||
array.splice(location, 0, item);
|
||
|
||
return location;
|
||
};
|
||
|
||
EPUBJS.core.locationOf = function(item, array, compareFunction, _start, _end) {
|
||
var start = _start || 0;
|
||
var end = _end || array.length;
|
||
var pivot = parseInt(start + (end - start) / 2);
|
||
var compared;
|
||
if(!compareFunction){
|
||
compareFunction = function(a, b) {
|
||
if(a > b) return 1;
|
||
if(a < b) return -1;
|
||
if(a = b) return 0;
|
||
};
|
||
}
|
||
if(end-start <= 0) {
|
||
return pivot;
|
||
}
|
||
|
||
compared = compareFunction(array[pivot], item);
|
||
if(end-start === 1) {
|
||
return compared > 0 ? pivot : pivot + 1;
|
||
}
|
||
|
||
if(compared === 0) {
|
||
return pivot;
|
||
}
|
||
if(compared === -1) {
|
||
return EPUBJS.core.locationOf(item, array, compareFunction, pivot, end);
|
||
} else{
|
||
return EPUBJS.core.locationOf(item, array, compareFunction, start, pivot);
|
||
}
|
||
};
|
||
|
||
EPUBJS.core.indexOfSorted = function(item, array, compareFunction, _start, _end) {
|
||
var start = _start || 0;
|
||
var end = _end || array.length;
|
||
var pivot = parseInt(start + (end - start) / 2);
|
||
var compared;
|
||
if(!compareFunction){
|
||
compareFunction = function(a, b) {
|
||
if(a > b) return 1;
|
||
if(a < b) return -1;
|
||
if(a = b) return 0;
|
||
};
|
||
}
|
||
if(end-start <= 0) {
|
||
return -1; // Not found
|
||
}
|
||
|
||
compared = compareFunction(array[pivot], item);
|
||
if(end-start === 1) {
|
||
return compared === 0 ? pivot : -1;
|
||
}
|
||
if(compared === 0) {
|
||
return pivot; // Found
|
||
}
|
||
if(compared === -1) {
|
||
return EPUBJS.core.indexOfSorted(item, array, compareFunction, pivot, end);
|
||
} else{
|
||
return EPUBJS.core.indexOfSorted(item, array, compareFunction, start, pivot);
|
||
}
|
||
};
|
||
|
||
|
||
EPUBJS.core.queue = function(_scope){
|
||
var _q = [];
|
||
var scope = _scope;
|
||
// Add an item to the queue
|
||
var enqueue = function(funcName, args, context) {
|
||
_q.push({
|
||
"funcName" : funcName,
|
||
"args" : args,
|
||
"context" : context
|
||
});
|
||
return _q;
|
||
};
|
||
// Run one item
|
||
var dequeue = function(){
|
||
var inwait;
|
||
if(_q.length) {
|
||
inwait = _q.shift();
|
||
// Defer to any current tasks
|
||
// setTimeout(function(){
|
||
scope[inwait.funcName].apply(inwait.context || scope, inwait.args);
|
||
// }, 0);
|
||
}
|
||
};
|
||
|
||
// Run All
|
||
var flush = function(){
|
||
while(_q.length) {
|
||
dequeue();
|
||
}
|
||
};
|
||
// Clear all items in wait
|
||
var clear = function(){
|
||
_q = [];
|
||
};
|
||
|
||
var length = function(){
|
||
return _q.length;
|
||
};
|
||
|
||
return {
|
||
"enqueue" : enqueue,
|
||
"dequeue" : dequeue,
|
||
"flush" : flush,
|
||
"clear" : clear,
|
||
"length" : length
|
||
};
|
||
};
|
||
|
||
// From: https://code.google.com/p/fbug/source/browse/branches/firebug1.10/content/firebug/lib/xpath.js
|
||
/**
|
||
* Gets an XPath for an element which describes its hierarchical location.
|
||
*/
|
||
EPUBJS.core.getElementXPath = function(element) {
|
||
if (element && element.id) {
|
||
return '//*[@id="' + element.id + '"]';
|
||
} else {
|
||
return EPUBJS.core.getElementTreeXPath(element);
|
||
}
|
||
};
|
||
|
||
EPUBJS.core.getElementTreeXPath = function(element) {
|
||
var paths = [];
|
||
var isXhtml = (element.ownerDocument.documentElement.getAttribute('xmlns') === "http://www.w3.org/1999/xhtml");
|
||
var index, nodeName, tagName, pathIndex;
|
||
|
||
if(element.nodeType === Node.TEXT_NODE){
|
||
// index = Array.prototype.indexOf.call(element.parentNode.childNodes, element) + 1;
|
||
index = EPUBJS.core.indexOfTextNode(element) + 1;
|
||
|
||
paths.push("text()["+index+"]");
|
||
element = element.parentNode;
|
||
}
|
||
|
||
// Use nodeName (instead of localName) so namespace prefix is included (if any).
|
||
for (; element && element.nodeType == 1; element = element.parentNode)
|
||
{
|
||
index = 0;
|
||
for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling)
|
||
{
|
||
// Ignore document type declaration.
|
||
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE) {
|
||
continue;
|
||
}
|
||
if (sibling.nodeName == element.nodeName) {
|
||
++index;
|
||
}
|
||
}
|
||
nodeName = element.nodeName.toLowerCase();
|
||
tagName = (isXhtml ? "xhtml:" + nodeName : nodeName);
|
||
pathIndex = (index ? "[" + (index+1) + "]" : "");
|
||
paths.splice(0, 0, tagName + pathIndex);
|
||
}
|
||
|
||
return paths.length ? "./" + paths.join("/") : null;
|
||
};
|
||
|
||
EPUBJS.core.nsResolver = function(prefix) {
|
||
var ns = {
|
||
'xhtml' : 'http://www.w3.org/1999/xhtml',
|
||
'epub': 'http://www.idpf.org/2007/ops'
|
||
};
|
||
return ns[prefix] || null;
|
||
};
|
||
|
||
//https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
|
||
EPUBJS.core.cleanStringForXpath = function(str) {
|
||
var parts = str.match(/[^'"]+|['"]/g);
|
||
parts = parts.map(function(part){
|
||
if (part === "'") {
|
||
return '\"\'\"'; // output "'"
|
||
}
|
||
|
||
if (part === '"') {
|
||
return "\'\"\'"; // output '"'
|
||
}
|
||
return "\'" + part + "\'";
|
||
});
|
||
return "concat(\'\'," + parts.join(",") + ")";
|
||
};
|
||
|
||
EPUBJS.core.indexOfTextNode = function(textNode){
|
||
var parent = textNode.parentNode;
|
||
var children = parent.childNodes;
|
||
var sib;
|
||
var index = -1;
|
||
for (var i = 0; i < children.length; i++) {
|
||
sib = children[i];
|
||
if(sib.nodeType === Node.TEXT_NODE){
|
||
index++;
|
||
}
|
||
if(sib == textNode) break;
|
||
}
|
||
|
||
return index;
|
||
};
|
||
|
||
// Underscore
|
||
EPUBJS.core.defaults = function(obj) {
|
||
for (var i = 1, length = arguments.length; i < length; i++) {
|
||
var source = arguments[i];
|
||
for (var prop in source) {
|
||
if (obj[prop] === void 0) obj[prop] = source[prop];
|
||
}
|
||
}
|
||
return obj;
|
||
};
|
||
|
||
EPUBJS.core.extend = function(target) {
|
||
var sources = [].slice.call(arguments, 1);
|
||
sources.forEach(function (source) {
|
||
if(!source) return;
|
||
Object.getOwnPropertyNames(source).forEach(function(propName) {
|
||
Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
|
||
});
|
||
});
|
||
return target;
|
||
};
|
||
|
||
EPUBJS.core.clone = function(obj) {
|
||
return EPUBJS.core.isArray(obj) ? obj.slice() : EPUBJS.core.extend({}, obj);
|
||
};
|
||
|
||
EPUBJS.core.isElement = function(obj) {
|
||
return !!(obj && obj.nodeType == 1);
|
||
};
|
||
|
||
EPUBJS.core.isNumber = function(n) {
|
||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||
};
|
||
|
||
EPUBJS.core.isString = function(str) {
|
||
return (typeof str === 'string' || str instanceof String);
|
||
};
|
||
|
||
EPUBJS.core.isArray = Array.isArray || function(obj) {
|
||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||
};
|
||
|
||
// Lodash
|
||
EPUBJS.core.values = function(object) {
|
||
var index = -1;
|
||
var props, length, result;
|
||
|
||
if(!object) return [];
|
||
|
||
props = Object.keys(object);
|
||
length = props.length;
|
||
result = Array(length);
|
||
|
||
while (++index < length) {
|
||
result[index] = object[props[index]];
|
||
}
|
||
return result;
|
||
};
|
||
|
||
EPUBJS.core.indexOfNode = function(node, typeId) {
|
||
var parent = node.parentNode;
|
||
var children = parent.childNodes;
|
||
var sib;
|
||
var index = -1;
|
||
for (var i = 0; i < children.length; i++) {
|
||
sib = children[i];
|
||
if (sib.nodeType === typeId) {
|
||
index++;
|
||
}
|
||
if (sib == node) break;
|
||
}
|
||
|
||
return index;
|
||
}
|
||
|
||
EPUBJS.core.indexOfTextNode = function(textNode) {
|
||
return EPUBJS.core.indexOfNode(textNode, TEXT_NODE);
|
||
}
|
||
|
||
EPUBJS.core.indexOfElementNode = function(elementNode) {
|
||
return EPUBJS.core.indexOfNode(elementNode, ELEMENT_NODE);
|
||
}
|
||
|
||
var EPUBJS = EPUBJS || {};
|
||
EPUBJS.reader = {};
|
||
EPUBJS.reader.plugins = {}; //-- Attach extra Controllers as plugins (like search?)
|
||
|
||
(function(root, $) {
|
||
|
||
var previousReader = root.ePubReader || {};
|
||
|
||
var ePubReader = root.ePubReader = function(path, options) {
|
||
return new EPUBJS.Reader(path, options);
|
||
};
|
||
|
||
//exports to multiple environments
|
||
if (typeof define === 'function' && define.amd) {
|
||
//AMD
|
||
define(function(){ return Reader; });
|
||
} else if (typeof module != "undefined" && module.exports) {
|
||
//Node
|
||
module.exports = ePubReader;
|
||
}
|
||
|
||
})(window, jQuery);
|
||
|
||
EPUBJS.Reader = function(bookPath, _options) {
|
||
var reader = this;
|
||
var book;
|
||
var plugin;
|
||
var $viewer = $("#viewer");
|
||
var search = window.location.search;
|
||
var parameters;
|
||
|
||
this.settings = EPUBJS.core.defaults(_options || {}, {
|
||
bookPath : bookPath,
|
||
restore : false,
|
||
reload : false,
|
||
bookmarks : undefined,
|
||
annotations : undefined,
|
||
contained : undefined,
|
||
bookKey : undefined,
|
||
styles : undefined,
|
||
sidebarReflow: false,
|
||
generatePagination: false,
|
||
history: true
|
||
});
|
||
|
||
// Overide options with search parameters
|
||
if(search) {
|
||
parameters = search.slice(1).split("&");
|
||
parameters.forEach(function(p){
|
||
var split = p.split("=");
|
||
var name = split[0];
|
||
var value = split[1] || '';
|
||
reader.settings[name] = decodeURIComponent(value);
|
||
});
|
||
}
|
||
|
||
this.setBookKey(this.settings.bookPath); //-- This could be username + path or any unique string
|
||
|
||
if(this.settings.restore && this.isSaved()) {
|
||
this.applySavedSettings();
|
||
}
|
||
|
||
this.settings.styles = this.settings.styles || {
|
||
fontSize : "100%"
|
||
};
|
||
|
||
this.book = book = new ePub(this.settings.bookPath, this.settings);
|
||
|
||
this.offline = false;
|
||
this.sidebarOpen = false;
|
||
if(!this.settings.bookmarks) {
|
||
this.settings.bookmarks = [];
|
||
}
|
||
|
||
if(!this.settings.annotations) {
|
||
this.settings.annotations = [];
|
||
}
|
||
|
||
if(this.settings.generatePagination) {
|
||
book.generatePagination($viewer.width(), $viewer.height());
|
||
}
|
||
|
||
this.rendition = book.renderTo("viewer", {
|
||
ignoreClass: "annotator-hl",
|
||
width: "100%",
|
||
height: "100%"
|
||
});
|
||
|
||
if(this.settings.previousLocationCfi) {
|
||
this.displayed = this.rendition.display(this.settings.previousLocationCfi);
|
||
} else {
|
||
this.displayed = this.rendition.display();
|
||
}
|
||
|
||
book.ready.then(function () {
|
||
reader.ReaderController = EPUBJS.reader.ReaderController.call(reader, book);
|
||
reader.SettingsController = EPUBJS.reader.SettingsController.call(reader, book);
|
||
reader.ControlsController = EPUBJS.reader.ControlsController.call(reader, book);
|
||
reader.SidebarController = EPUBJS.reader.SidebarController.call(reader, book);
|
||
reader.BookmarksController = EPUBJS.reader.BookmarksController.call(reader, book);
|
||
reader.NotesController = EPUBJS.reader.NotesController.call(reader, book);
|
||
|
||
window.addEventListener("hashchange", this.hashChanged.bind(this), false);
|
||
|
||
document.addEventListener('keydown', this.adjustFontSize.bind(this), false);
|
||
|
||
this.rendition.on("keydown", this.adjustFontSize.bind(this));
|
||
this.rendition.on("keydown", reader.ReaderController.arrowKeys.bind(this));
|
||
|
||
this.rendition.on("selected", this.selectedRange.bind(this));
|
||
}.bind(this)).then(function() {
|
||
reader.ReaderController.hideLoader();
|
||
}.bind(this));
|
||
|
||
// Call Plugins
|
||
for(plugin in EPUBJS.reader.plugins) {
|
||
if(EPUBJS.reader.plugins.hasOwnProperty(plugin)) {
|
||
reader[plugin] = EPUBJS.reader.plugins[plugin].call(reader, book);
|
||
}
|
||
}
|
||
|
||
book.loaded.metadata.then(function(meta) {
|
||
reader.MetaController = EPUBJS.reader.MetaController.call(reader, meta);
|
||
});
|
||
|
||
book.loaded.navigation.then(function(navigation) {
|
||
reader.TocController = EPUBJS.reader.TocController.call(reader, navigation);
|
||
});
|
||
|
||
window.addEventListener("beforeunload", this.unload.bind(this), false);
|
||
|
||
return this;
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.adjustFontSize = function(e) {
|
||
var fontSize;
|
||
var interval = 2;
|
||
var PLUS = 187;
|
||
var MINUS = 189;
|
||
var ZERO = 48;
|
||
var MOD = (e.ctrlKey || e.metaKey );
|
||
|
||
if(!this.settings.styles) return;
|
||
|
||
/*
|
||
if(!this.settings.styles.fontSize) {
|
||
this.settings.styles.fontSize = "100%";
|
||
}
|
||
|
||
fontSize = parseInt(this.settings.styles.fontSize.slice(0, -1));
|
||
|
||
if(MOD && e.keyCode == PLUS) {
|
||
e.preventDefault();
|
||
this.book.setStyle("fontSize", (fontSize + interval) + "%");
|
||
|
||
}
|
||
|
||
if(MOD && e.keyCode == MINUS){
|
||
|
||
e.preventDefault();
|
||
this.book.setStyle("fontSize", (fontSize - interval) + "%");
|
||
}
|
||
|
||
if(MOD && e.keyCode == ZERO){
|
||
e.preventDefault();
|
||
this.book.setStyle("fontSize", "100%");
|
||
}
|
||
*/
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.addBookmark = function(cfi) {
|
||
var present = this.isBookmarked(cfi);
|
||
if(present > -1 ) return;
|
||
|
||
this.settings.bookmarks.push(cfi);
|
||
|
||
this.trigger("reader:bookmarked", cfi);
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.removeBookmark = function(cfi) {
|
||
var bookmark = this.isBookmarked(cfi);
|
||
if( bookmark === -1 ) return;
|
||
|
||
this.settings.bookmarks.splice(bookmark, 1);
|
||
|
||
this.trigger("reader:unbookmarked", bookmark);
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.isBookmarked = function(cfi) {
|
||
var bookmarks = this.settings.bookmarks;
|
||
|
||
return bookmarks.indexOf(cfi);
|
||
};
|
||
|
||
/*
|
||
EPUBJS.Reader.prototype.searchBookmarked = function(cfi) {
|
||
var bookmarks = this.settings.bookmarks,
|
||
len = bookmarks.length,
|
||
i;
|
||
|
||
for(i = 0; i < len; i++) {
|
||
if (bookmarks[i]['cfi'] === cfi) return i;
|
||
}
|
||
return -1;
|
||
};
|
||
*/
|
||
|
||
EPUBJS.Reader.prototype.clearBookmarks = function() {
|
||
this.settings.bookmarks = [];
|
||
};
|
||
|
||
//-- Notes
|
||
EPUBJS.Reader.prototype.addNote = function(note) {
|
||
this.settings.annotations.push(note);
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.removeNote = function(note) {
|
||
var index = this.settings.annotations.indexOf(note);
|
||
if( index === -1 ) return;
|
||
|
||
delete this.settings.annotations[index];
|
||
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.clearNotes = function() {
|
||
this.settings.annotations = [];
|
||
};
|
||
|
||
//-- Settings
|
||
EPUBJS.Reader.prototype.setBookKey = function(identifier){
|
||
if(!this.settings.bookKey) {
|
||
this.settings.bookKey = "epubjsreader:" + EPUBJS.VERSION + ":" + window.location.host + ":" + identifier;
|
||
}
|
||
return this.settings.bookKey;
|
||
};
|
||
|
||
//-- Checks if the book setting can be retrieved from localStorage
|
||
EPUBJS.Reader.prototype.isSaved = function(bookPath) {
|
||
var storedSettings;
|
||
|
||
if(!localStorage) {
|
||
return false;
|
||
}
|
||
|
||
storedSettings = localStorage.getItem(this.settings.bookKey);
|
||
|
||
if(storedSettings === null) {
|
||
return false;
|
||
} else {
|
||
return true;
|
||
}
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.removeSavedSettings = function() {
|
||
if(!localStorage) {
|
||
return false;
|
||
}
|
||
|
||
localStorage.removeItem(this.settings.bookKey);
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.applySavedSettings = function() {
|
||
var stored;
|
||
|
||
if(!localStorage) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
stored = JSON.parse(localStorage.getItem(this.settings.bookKey));
|
||
} catch (e) { // parsing error of localStorage
|
||
return false;
|
||
}
|
||
|
||
if(stored) {
|
||
// Merge styles
|
||
if(stored.styles) {
|
||
this.settings.styles = EPUBJS.core.defaults(this.settings.styles || {}, stored.styles);
|
||
}
|
||
// Merge the rest
|
||
this.settings = EPUBJS.core.defaults(this.settings, stored);
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.saveSettings = function(){
|
||
if(this.book) {
|
||
this.settings.previousLocationCfi = this.rendition.currentLocation().start.cfi;
|
||
}
|
||
|
||
if(!localStorage) {
|
||
return false;
|
||
}
|
||
|
||
localStorage.setItem(this.settings.bookKey, JSON.stringify(this.settings));
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.unload = function(){
|
||
if(this.settings.restore && localStorage) {
|
||
this.saveSettings();
|
||
}
|
||
};
|
||
|
||
|
||
EPUBJS.Reader.prototype.hashChanged = function(){
|
||
var hash = window.location.hash.slice(1);
|
||
this.rendition.display(hash);
|
||
};
|
||
|
||
EPUBJS.Reader.prototype.selectedRange = function(cfiRange){
|
||
var cfiFragment = "#"+cfiRange;
|
||
|
||
// Update the History Location
|
||
if(this.settings.history &&
|
||
window.location.hash != cfiFragment) {
|
||
// Add CFI fragment to the history
|
||
history.pushState({}, '', cfiFragment);
|
||
this.currentLocationCfi = cfiRange;
|
||
}
|
||
};
|
||
|
||
//-- Enable binding events to reader
|
||
RSVP.EventTarget.mixin(EPUBJS.Reader.prototype);
|
||
|
||
EPUBJS.reader.BookmarksController = function() {
|
||
var reader = this;
|
||
var book = this.book;
|
||
var rendition = this.rendition;
|
||
|
||
var $bookmarks = $("#bookmarksView"),
|
||
$list = $bookmarks.find("#bookmarks");
|
||
|
||
var docfrag = document.createDocumentFragment();
|
||
|
||
var show = function() {
|
||
$bookmarks.show();
|
||
};
|
||
|
||
var hide = function() {
|
||
$bookmarks.hide();
|
||
};
|
||
|
||
var counter = 0;
|
||
|
||
var createBookmarkItem = function(cfi) {
|
||
var listitem = document.createElement("li"),
|
||
link = document.createElement("a");
|
||
|
||
listitem.id = "bookmark-"+counter;
|
||
listitem.classList.add('list_item');
|
||
|
||
var spineItem = book.spine.get(cfi);
|
||
var tocItem;
|
||
if (spineItem.index in book.navigation.toc) {
|
||
tocItem = book.navigation.toc[spineItem.index];
|
||
link.textContent = tocItem.label;
|
||
} else {
|
||
link.textContent = cfi;
|
||
}
|
||
|
||
link.href = cfi;
|
||
|
||
link.classList.add('bookmark_link');
|
||
|
||
link.addEventListener("click", function(event){
|
||
var cfi = this.getAttribute('href');
|
||
rendition.display(cfi);
|
||
event.preventDefault();
|
||
}, false);
|
||
|
||
listitem.appendChild(link);
|
||
|
||
counter++;
|
||
|
||
return listitem;
|
||
};
|
||
|
||
this.settings.bookmarks.forEach(function(cfi) {
|
||
var bookmark = createBookmarkItem(cfi);
|
||
docfrag.appendChild(bookmark);
|
||
});
|
||
|
||
$list.append(docfrag);
|
||
|
||
this.on("reader:bookmarked", function(cfi) {
|
||
var item = createBookmarkItem(cfi);
|
||
$list.append(item);
|
||
});
|
||
|
||
this.on("reader:unbookmarked", function(index) {
|
||
var $item = $("#bookmark-"+index);
|
||
$item.remove();
|
||
});
|
||
|
||
return {
|
||
"show" : show,
|
||
"hide" : hide
|
||
};
|
||
};
|
||
|
||
EPUBJS.reader.ControlsController = function(book) {
|
||
var reader = this;
|
||
var rendition = this.rendition;
|
||
|
||
var $store = $("#store"),
|
||
$fullscreen = $("#fullscreen"),
|
||
$fullscreenicon = $("#fullscreenicon"),
|
||
$cancelfullscreenicon = $("#cancelfullscreenicon"),
|
||
$slider = $("#slider"),
|
||
$main = $("#main"),
|
||
$sidebar = $("#sidebar"),
|
||
$settings = $("#setting"),
|
||
$bookmark = $("#bookmark");
|
||
/*
|
||
var goOnline = function() {
|
||
reader.offline = false;
|
||
// $store.attr("src", $icon.data("save"));
|
||
};
|
||
|
||
var goOffline = function() {
|
||
reader.offline = true;
|
||
// $store.attr("src", $icon.data("saved"));
|
||
};
|
||
|
||
var fullscreen = false;
|
||
|
||
book.on("book:online", goOnline);
|
||
book.on("book:offline", goOffline);
|
||
*/
|
||
$slider.on("click", function () {
|
||
if(reader.sidebarOpen) {
|
||
reader.SidebarController.hide();
|
||
$slider.addClass("icon-menu");
|
||
$slider.removeClass("icon-right");
|
||
} else {
|
||
reader.SidebarController.show();
|
||
$slider.addClass("icon-right");
|
||
$slider.removeClass("icon-menu");
|
||
}
|
||
});
|
||
|
||
if(typeof screenfull !== 'undefined') {
|
||
$fullscreen.on("click", function() {
|
||
screenfull.toggle($('#container')[0]);
|
||
});
|
||
if(screenfull.raw) {
|
||
document.addEventListener(screenfull.raw.fullscreenchange, function() {
|
||
fullscreen = screenfull.isFullscreen;
|
||
if(fullscreen) {
|
||
$fullscreen
|
||
.addClass("icon-resize-small")
|
||
.removeClass("icon-resize-full");
|
||
} else {
|
||
$fullscreen
|
||
.addClass("icon-resize-full")
|
||
.removeClass("icon-resize-small");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
$settings.on("click", function() {
|
||
reader.SettingsController.show();
|
||
});
|
||
|
||
$bookmark.on("click", function() {
|
||
var cfi = reader.rendition.currentLocation().start.cfi;
|
||
var bookmarked = reader.isBookmarked(cfi);
|
||
|
||
if(bookmarked === -1) { //-- Add bookmark
|
||
reader.addBookmark(cfi);
|
||
$bookmark
|
||
.addClass("icon-bookmark")
|
||
.removeClass("icon-bookmark-empty");
|
||
} else { //-- Remove Bookmark
|
||
reader.removeBookmark(cfi);
|
||
$bookmark
|
||
.removeClass("icon-bookmark")
|
||
.addClass("icon-bookmark-empty");
|
||
}
|
||
|
||
});
|
||
|
||
rendition.on('relocated', function(location){
|
||
var cfi = location.start.cfi;
|
||
var cfiFragment = "#" + cfi;
|
||
//-- Check if bookmarked
|
||
var bookmarked = reader.isBookmarked(cfi);
|
||
if(bookmarked === -1) { //-- Not bookmarked
|
||
$bookmark
|
||
.removeClass("icon-bookmark")
|
||
.addClass("icon-bookmark-empty");
|
||
} else { //-- Bookmarked
|
||
$bookmark
|
||
.addClass("icon-bookmark")
|
||
.removeClass("icon-bookmark-empty");
|
||
}
|
||
|
||
reader.currentLocationCfi = cfi;
|
||
|
||
// Update the History Location
|
||
if(reader.settings.history &&
|
||
window.location.hash != cfiFragment) {
|
||
// Add CFI fragment to the history
|
||
history.pushState({}, '', cfiFragment);
|
||
}
|
||
});
|
||
|
||
return {
|
||
|
||
};
|
||
};
|
||
|
||
EPUBJS.reader.MetaController = function(meta) {
|
||
var title = meta.title,
|
||
author = meta.creator;
|
||
|
||
var $title = $("#book-title"),
|
||
$author = $("#chapter-title"),
|
||
$dash = $("#title-seperator");
|
||
|
||
document.title = title+" – "+author;
|
||
|
||
$title.html(title);
|
||
$author.html(author);
|
||
$dash.show();
|
||
};
|
||
|
||
EPUBJS.reader.NotesController = function() {
|
||
var book = this.book;
|
||
var rendition = this.rendition;
|
||
var reader = this;
|
||
var $notesView = $("#notesView");
|
||
var $notes = $("#notes");
|
||
var $text = $("#note-text");
|
||
var $anchor = $("#note-anchor");
|
||
var annotations = reader.settings.annotations;
|
||
var renderer = book.renderer;
|
||
var popups = [];
|
||
var epubcfi = new ePub.CFI();
|
||
|
||
var show = function() {
|
||
$notesView.show();
|
||
};
|
||
|
||
var hide = function() {
|
||
$notesView.hide();
|
||
}
|
||
|
||
var insertAtPoint = function(e) {
|
||
var range;
|
||
var textNode;
|
||
var offset;
|
||
var doc = book.renderer.doc;
|
||
var cfi;
|
||
var annotation;
|
||
|
||
// standard
|
||
if (doc.caretPositionFromPoint) {
|
||
range = doc.caretPositionFromPoint(e.clientX, e.clientY);
|
||
textNode = range.offsetNode;
|
||
offset = range.offset;
|
||
// WebKit
|
||
} else if (doc.caretRangeFromPoint) {
|
||
range = doc.caretRangeFromPoint(e.clientX, e.clientY);
|
||
textNode = range.startContainer;
|
||
offset = range.startOffset;
|
||
}
|
||
|
||
if (textNode.nodeType !== 3) {
|
||
for (var i=0; i < textNode.childNodes.length; i++) {
|
||
if (textNode.childNodes[i].nodeType == 3) {
|
||
textNode = textNode.childNodes[i];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Find the end of the sentance
|
||
offset = textNode.textContent.indexOf(".", offset);
|
||
if(offset === -1){
|
||
offset = textNode.length; // Last item
|
||
} else {
|
||
offset += 1; // After the period
|
||
}
|
||
|
||
cfi = epubcfi.generateCfiFromTextNode(textNode, offset, book.renderer.currentChapter.cfiBase);
|
||
|
||
annotation = {
|
||
annotatedAt: new Date(),
|
||
anchor: cfi,
|
||
body: $text.val()
|
||
}
|
||
|
||
// add to list
|
||
reader.addNote(annotation);
|
||
|
||
// attach
|
||
addAnnotation(annotation);
|
||
placeMarker(annotation);
|
||
|
||
// clear
|
||
$text.val('');
|
||
$anchor.text("Attach");
|
||
$text.prop("disabled", false);
|
||
|
||
rendition.off("click", insertAtPoint);
|
||
|
||
};
|
||
|
||
var addAnnotation = function(annotation){
|
||
var note = document.createElement("li");
|
||
var link = document.createElement("a");
|
||
|
||
note.innerHTML = annotation.body;
|
||
// note.setAttribute("ref", annotation.anchor);
|
||
link.innerHTML = " context »";
|
||
link.href = "#"+annotation.anchor;
|
||
link.onclick = function(){
|
||
rendition.display(annotation.anchor);
|
||
return false;
|
||
};
|
||
|
||
note.appendChild(link);
|
||
$notes.append(note);
|
||
|
||
};
|
||
|
||
var placeMarker = function(annotation){
|
||
var doc = book.renderer.doc;
|
||
var marker = document.createElement("span");
|
||
var mark = document.createElement("a");
|
||
marker.classList.add("footnotesuperscript", "reader_generated");
|
||
|
||
marker.style.verticalAlign = "super";
|
||
marker.style.fontSize = ".75em";
|
||
// marker.style.position = "relative";
|
||
marker.style.lineHeight = "1em";
|
||
|
||
// mark.style.display = "inline-block";
|
||
mark.style.padding = "2px";
|
||
mark.style.backgroundColor = "#fffa96";
|
||
mark.style.borderRadius = "5px";
|
||
mark.style.cursor = "pointer";
|
||
|
||
marker.id = "note-"+EPUBJS.core.uuid();
|
||
mark.innerHTML = annotations.indexOf(annotation) + 1 + "[Reader]";
|
||
|
||
marker.appendChild(mark);
|
||
epubcfi.addMarker(annotation.anchor, doc, marker);
|
||
|
||
markerEvents(marker, annotation.body);
|
||
}
|
||
|
||
var markerEvents = function(item, txt){
|
||
var id = item.id;
|
||
|
||
var showPop = function(){
|
||
var poppos,
|
||
iheight = renderer.height,
|
||
iwidth = renderer.width,
|
||
tip,
|
||
pop,
|
||
maxHeight = 225,
|
||
itemRect,
|
||
left,
|
||
top,
|
||
pos;
|
||
|
||
|
||
//-- create a popup with endnote inside of it
|
||
if(!popups[id]) {
|
||
popups[id] = document.createElement("div");
|
||
popups[id].setAttribute("class", "popup");
|
||
|
||
pop_content = document.createElement("div");
|
||
|
||
popups[id].appendChild(pop_content);
|
||
|
||
pop_content.innerHTML = txt;
|
||
pop_content.setAttribute("class", "pop_content");
|
||
|
||
renderer.render.document.body.appendChild(popups[id]);
|
||
|
||
//-- TODO: will these leak memory? - Fred
|
||
popups[id].addEventListener("mouseover", onPop, false);
|
||
popups[id].addEventListener("mouseout", offPop, false);
|
||
|
||
//-- Add hide on page change
|
||
rendition.on("locationChanged", hidePop, this);
|
||
rendition.on("locationChanged", offPop, this);
|
||
// chapter.book.on("renderer:chapterDestroy", hidePop, this);
|
||
}
|
||
|
||
pop = popups[id];
|
||
|
||
|
||
//-- get location of item
|
||
itemRect = item.getBoundingClientRect();
|
||
left = itemRect.left;
|
||
top = itemRect.top;
|
||
|
||
//-- show the popup
|
||
pop.classList.add("show");
|
||
|
||
//-- locations of popup
|
||
popRect = pop.getBoundingClientRect();
|
||
|
||
//-- position the popup
|
||
pop.style.left = left - popRect.width / 2 + "px";
|
||
pop.style.top = top + "px";
|
||
|
||
|
||
//-- Adjust max height
|
||
if(maxHeight > iheight / 2.5) {
|
||
maxHeight = iheight / 2.5;
|
||
pop_content.style.maxHeight = maxHeight + "px";
|
||
}
|
||
|
||
//-- switch above / below
|
||
if(popRect.height + top >= iheight - 25) {
|
||
pop.style.top = top - popRect.height + "px";
|
||
pop.classList.add("above");
|
||
}else{
|
||
pop.classList.remove("above");
|
||
}
|
||
|
||
//-- switch left
|
||
if(left - popRect.width <= 0) {
|
||
pop.style.left = left + "px";
|
||
pop.classList.add("left");
|
||
}else{
|
||
pop.classList.remove("left");
|
||
}
|
||
|
||
//-- switch right
|
||
if(left + popRect.width / 2 >= iwidth) {
|
||
//-- TEMP MOVE: 300
|
||
pop.style.left = left - 300 + "px";
|
||
|
||
popRect = pop.getBoundingClientRect();
|
||
pop.style.left = left - popRect.width + "px";
|
||
//-- switch above / below again
|
||
if(popRect.height + top >= iheight - 25) {
|
||
pop.style.top = top - popRect.height + "px";
|
||
pop.classList.add("above");
|
||
}else{
|
||
pop.classList.remove("above");
|
||
}
|
||
|
||
pop.classList.add("right");
|
||
}else{
|
||
pop.classList.remove("right");
|
||
}
|
||
|
||
}
|
||
|
||
var onPop = function(){
|
||
popups[id].classList.add("on");
|
||
}
|
||
|
||
var offPop = function(){
|
||
popups[id].classList.remove("on");
|
||
}
|
||
|
||
var hidePop = function(){
|
||
setTimeout(function(){
|
||
popups[id].classList.remove("show");
|
||
}, 100);
|
||
}
|
||
|
||
var openSidebar = function(){
|
||
reader.ReaderController.slideOut();
|
||
show();
|
||
};
|
||
|
||
item.addEventListener("mouseover", showPop, false);
|
||
item.addEventListener("mouseout", hidePop, false);
|
||
item.addEventListener("click", openSidebar, false);
|
||
|
||
}
|
||
$anchor.on("click", function(e){
|
||
|
||
$anchor.text("Cancel");
|
||
$text.prop("disabled", "true");
|
||
// listen for selection
|
||
rendition.on("click", insertAtPoint);
|
||
|
||
});
|
||
|
||
annotations.forEach(function(note) {
|
||
addAnnotation(note);
|
||
});
|
||
|
||
/*
|
||
renderer.registerHook("beforeChapterDisplay", function(callback, renderer){
|
||
var chapter = renderer.currentChapter;
|
||
annotations.forEach(function(note) {
|
||
var cfi = epubcfi.parse(note.anchor);
|
||
if(cfi.spinePos === chapter.spinePos) {
|
||
try {
|
||
placeMarker(note);
|
||
} catch(e) {
|
||
console.log("anchoring failed", note.anchor);
|
||
}
|
||
}
|
||
});
|
||
callback();
|
||
}, true);
|
||
*/
|
||
|
||
return {
|
||
"show" : show,
|
||
"hide" : hide
|
||
};
|
||
};
|
||
|
||
EPUBJS.reader.ReaderController = function(book) {
|
||
var $main = $("#main"),
|
||
$divider = $("#divider"),
|
||
$loader = $("#loader"),
|
||
$next = $("#next"),
|
||
$prev = $("#prev");
|
||
var reader = this;
|
||
var book = this.book;
|
||
var rendition = this.rendition;
|
||
var slideIn = function() {
|
||
var currentPosition = rendition.currentLocation().start.cfi;
|
||
if (reader.settings.sidebarReflow){
|
||
$main.removeClass('single');
|
||
$main.one("transitionend", function(){
|
||
rendition.resize();
|
||
});
|
||
} else {
|
||
$main.removeClass("closed");
|
||
}
|
||
};
|
||
|
||
var slideOut = function() {
|
||
var location = rendition.currentLocation();
|
||
if (!location) {
|
||
return;
|
||
}
|
||
var currentPosition = location.start.cfi;
|
||
if (reader.settings.sidebarReflow){
|
||
$main.addClass('single');
|
||
$main.one("transitionend", function(){
|
||
rendition.resize();
|
||
});
|
||
} else {
|
||
$main.addClass("closed");
|
||
}
|
||
};
|
||
|
||
var showLoader = function() {
|
||
$loader.show();
|
||
hideDivider();
|
||
};
|
||
|
||
var hideLoader = function() {
|
||
$loader.hide();
|
||
|
||
//-- If the book is using spreads, show the divider
|
||
// if(book.settings.spreads) {
|
||
// showDivider();
|
||
// }
|
||
};
|
||
|
||
var showDivider = function() {
|
||
$divider.addClass("show");
|
||
};
|
||
|
||
var hideDivider = function() {
|
||
$divider.removeClass("show");
|
||
};
|
||
|
||
var keylock = false;
|
||
|
||
var arrowKeys = function(e) {
|
||
if(e.keyCode == 37) {
|
||
|
||
if(book.package.metadata.direction === "rtl") {
|
||
rendition.next();
|
||
} else {
|
||
rendition.prev();
|
||
}
|
||
|
||
$prev.addClass("active");
|
||
|
||
keylock = true;
|
||
setTimeout(function(){
|
||
keylock = false;
|
||
$prev.removeClass("active");
|
||
}, 100);
|
||
|
||
e.preventDefault();
|
||
}
|
||
if(e.keyCode == 39) {
|
||
|
||
if(book.package.metadata.direction === "rtl") {
|
||
rendition.prev();
|
||
} else {
|
||
rendition.next();
|
||
}
|
||
|
||
$next.addClass("active");
|
||
|
||
keylock = true;
|
||
setTimeout(function(){
|
||
keylock = false;
|
||
$next.removeClass("active");
|
||
}, 100);
|
||
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
|
||
document.addEventListener('keydown', arrowKeys, false);
|
||
|
||
$next.on("click", function(e){
|
||
|
||
if(book.package.metadata.direction === "rtl") {
|
||
rendition.prev();
|
||
} else {
|
||
rendition.next();
|
||
}
|
||
|
||
e.preventDefault();
|
||
});
|
||
|
||
$prev.on("click", function(e){
|
||
|
||
if(book.package.metadata.direction === "rtl") {
|
||
rendition.next();
|
||
} else {
|
||
rendition.prev();
|
||
}
|
||
|
||
e.preventDefault();
|
||
});
|
||
|
||
rendition.on("layout", function(props){
|
||
if(props.spread === true) {
|
||
showDivider();
|
||
} else {
|
||
hideDivider();
|
||
}
|
||
});
|
||
|
||
rendition.on('relocated', function(location){
|
||
if (location.atStart) {
|
||
$prev.addClass("disabled");
|
||
}
|
||
if (location.atEnd) {
|
||
$next.addClass("disabled");
|
||
}
|
||
});
|
||
|
||
return {
|
||
"slideOut" : slideOut,
|
||
"slideIn" : slideIn,
|
||
"showLoader" : showLoader,
|
||
"hideLoader" : hideLoader,
|
||
"showDivider" : showDivider,
|
||
"hideDivider" : hideDivider,
|
||
"arrowKeys" : arrowKeys
|
||
};
|
||
};
|
||
|
||
EPUBJS.reader.SettingsController = function() {
|
||
var book = this.book;
|
||
var reader = this;
|
||
var $settings = $("#settings-modal"),
|
||
$overlay = $(".overlay");
|
||
|
||
var show = function() {
|
||
$settings.addClass("md-show");
|
||
};
|
||
|
||
var hide = function() {
|
||
$settings.removeClass("md-show");
|
||
};
|
||
|
||
var $sidebarReflowSetting = $('#sidebarReflow');
|
||
|
||
$sidebarReflowSetting.on('click', function() {
|
||
reader.settings.sidebarReflow = !reader.settings.sidebarReflow;
|
||
});
|
||
|
||
$settings.find(".closer").on("click", function() {
|
||
hide();
|
||
});
|
||
|
||
$overlay.on("click", function() {
|
||
hide();
|
||
});
|
||
|
||
return {
|
||
"show" : show,
|
||
"hide" : hide
|
||
};
|
||
};
|
||
EPUBJS.reader.SidebarController = function(book) {
|
||
var reader = this;
|
||
|
||
var $sidebar = $("#sidebar"),
|
||
$panels = $("#panels");
|
||
|
||
var activePanel = "Toc";
|
||
|
||
var changePanelTo = function(viewName) {
|
||
var controllerName = viewName + "Controller";
|
||
|
||
if(activePanel == viewName || typeof reader[controllerName] === 'undefined' ) return;
|
||
reader[activePanel+ "Controller"].hide();
|
||
reader[controllerName].show();
|
||
activePanel = viewName;
|
||
|
||
$panels.find('.active').removeClass("active");
|
||
$panels.find("#show-" + viewName ).addClass("active");
|
||
};
|
||
|
||
var getActivePanel = function() {
|
||
return activePanel;
|
||
};
|
||
|
||
var show = function() {
|
||
reader.sidebarOpen = true;
|
||
reader.ReaderController.slideOut();
|
||
$sidebar.addClass("open");
|
||
}
|
||
|
||
var hide = function() {
|
||
reader.sidebarOpen = false;
|
||
reader.ReaderController.slideIn();
|
||
$sidebar.removeClass("open");
|
||
}
|
||
|
||
$panels.find(".show_view").on("click", function(event) {
|
||
var view = $(this).data("view");
|
||
|
||
changePanelTo(view);
|
||
event.preventDefault();
|
||
});
|
||
|
||
return {
|
||
'show' : show,
|
||
'hide' : hide,
|
||
'getActivePanel' : getActivePanel,
|
||
'changePanelTo' : changePanelTo
|
||
};
|
||
};
|
||
EPUBJS.reader.TocController = function(toc) {
|
||
var book = this.book;
|
||
var rendition = this.rendition;
|
||
|
||
var $list = $("#tocView"),
|
||
docfrag = document.createDocumentFragment();
|
||
|
||
var currentChapter = false;
|
||
|
||
var generateTocItems = function(toc, level) {
|
||
var container = document.createElement("ul");
|
||
|
||
if(!level) level = 1;
|
||
|
||
toc.forEach(function(chapter) {
|
||
var listitem = document.createElement("li"),
|
||
link = document.createElement("a");
|
||
toggle = document.createElement("a");
|
||
|
||
var subitems;
|
||
|
||
listitem.id = "toc-"+chapter.id;
|
||
listitem.classList.add('list_item');
|
||
|
||
link.textContent = chapter.label;
|
||
link.href = chapter.href;
|
||
|
||
link.classList.add('toc_link');
|
||
|
||
listitem.appendChild(link);
|
||
|
||
if(chapter.subitems && chapter.subitems.length > 0) {
|
||
level++;
|
||
subitems = generateTocItems(chapter.subitems, level);
|
||
toggle.classList.add('toc_toggle');
|
||
|
||
listitem.insertBefore(toggle, link);
|
||
listitem.appendChild(subitems);
|
||
}
|
||
|
||
|
||
container.appendChild(listitem);
|
||
|
||
});
|
||
|
||
return container;
|
||
};
|
||
|
||
var onShow = function() {
|
||
$list.show();
|
||
};
|
||
|
||
var onHide = function() {
|
||
$list.hide();
|
||
};
|
||
|
||
var chapterChange = function(e) {
|
||
var id = e.id,
|
||
$item = $list.find("#toc-"+id),
|
||
$current = $list.find(".currentChapter"),
|
||
$open = $list.find('.openChapter');
|
||
|
||
if($item.length){
|
||
|
||
if($item != $current && $item.has(currentChapter).length > 0) {
|
||
$current.removeClass("currentChapter");
|
||
}
|
||
|
||
$item.addClass("currentChapter");
|
||
|
||
// $open.removeClass("openChapter");
|
||
$item.parents('li').addClass("openChapter");
|
||
}
|
||
};
|
||
|
||
rendition.on('renderered', chapterChange);
|
||
|
||
var tocitems = generateTocItems(toc);
|
||
|
||
docfrag.appendChild(tocitems);
|
||
|
||
$list.append(docfrag);
|
||
$list.find(".toc_link").on("click", function(event){
|
||
var url = this.getAttribute('href');
|
||
|
||
event.preventDefault();
|
||
|
||
//-- Provide the Book with the url to show
|
||
// The Url must be found in the books manifest
|
||
rendition.display(url);
|
||
|
||
$list.find(".currentChapter")
|
||
.addClass("openChapter")
|
||
.removeClass("currentChapter");
|
||
|
||
$(this).parent('li').addClass("currentChapter");
|
||
|
||
});
|
||
|
||
$list.find(".toc_toggle").on("click", function(event){
|
||
var $el = $(this).parent('li'),
|
||
open = $el.hasClass("openChapter");
|
||
|
||
event.preventDefault();
|
||
if(open){
|
||
$el.removeClass("openChapter");
|
||
} else {
|
||
$el.addClass("openChapter");
|
||
}
|
||
});
|
||
|
||
return {
|
||
"show" : onShow,
|
||
"hide" : onHide
|
||
};
|
||
};
|