583 lines
18 KiB
JavaScript
583 lines
18 KiB
JavaScript
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
;(function (factory) {
|
|
var objectTypes = {
|
|
'boolean': false,
|
|
'function': true,
|
|
'object': true,
|
|
'number': false,
|
|
'string': false,
|
|
'undefined': false
|
|
};
|
|
|
|
var root = (objectTypes[typeof window] && window) || this,
|
|
freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports,
|
|
freeModule = objectTypes[typeof module] && module && !module.nodeType && module,
|
|
moduleExports = freeModule && freeModule.exports === freeExports && freeExports,
|
|
freeGlobal = objectTypes[typeof global] && global;
|
|
|
|
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
|
|
root = freeGlobal;
|
|
}
|
|
|
|
// Because of build optimizers
|
|
if (typeof define === 'function' && define.amd) {
|
|
define(['rx.binding', 'exports'], function (Rx, exports) {
|
|
root.Rx = factory(root, exports, Rx);
|
|
return root.Rx;
|
|
});
|
|
} else if (typeof module === 'object' && module && module.exports === freeExports) {
|
|
module.exports = factory(root, module.exports, require('./rx'));
|
|
} else {
|
|
root.Rx = factory(root, {}, root.Rx);
|
|
}
|
|
}.call(this, function (root, exp, Rx, undefined) {
|
|
|
|
// Aliases
|
|
var Observable = Rx.Observable,
|
|
observableProto = Observable.prototype,
|
|
observableFromPromise = Observable.fromPromise,
|
|
observableThrow = Observable.throwException,
|
|
AnonymousObservable = Rx.AnonymousObservable,
|
|
AsyncSubject = Rx.AsyncSubject,
|
|
disposableCreate = Rx.Disposable.create,
|
|
CompositeDisposable = Rx.CompositeDisposable,
|
|
immediateScheduler = Rx.Scheduler.immediate,
|
|
timeoutScheduler = Rx.Scheduler.timeout,
|
|
isScheduler = Rx.helpers.isScheduler,
|
|
slice = Array.prototype.slice;
|
|
|
|
var fnString = 'function',
|
|
throwString = 'throw',
|
|
isObject = Rx.internals.isObject;
|
|
|
|
function toThunk(obj, ctx) {
|
|
if (Array.isArray(obj)) { return objectToThunk.call(ctx, obj); }
|
|
if (isGeneratorFunction(obj)) { return observableSpawn(obj.call(ctx)); }
|
|
if (isGenerator(obj)) { return observableSpawn(obj); }
|
|
if (isObservable(obj)) { return observableToThunk(obj); }
|
|
if (isPromise(obj)) { return promiseToThunk(obj); }
|
|
if (typeof obj === fnString) { return obj; }
|
|
if (isObject(obj) || Array.isArray(obj)) { return objectToThunk.call(ctx, obj); }
|
|
|
|
return obj;
|
|
}
|
|
|
|
function objectToThunk(obj) {
|
|
var ctx = this;
|
|
|
|
return function (done) {
|
|
var keys = Object.keys(obj),
|
|
pending = keys.length,
|
|
results = new obj.constructor(),
|
|
finished;
|
|
|
|
if (!pending) {
|
|
timeoutScheduler.schedule(function () { done(null, results); });
|
|
return;
|
|
}
|
|
|
|
for (var i = 0, len = keys.length; i < len; i++) {
|
|
run(obj[keys[i]], keys[i]);
|
|
}
|
|
|
|
function run(fn, key) {
|
|
if (finished) { return; }
|
|
try {
|
|
fn = toThunk(fn, ctx);
|
|
|
|
if (typeof fn !== fnString) {
|
|
results[key] = fn;
|
|
return --pending || done(null, results);
|
|
}
|
|
|
|
fn.call(ctx, function(err, res) {
|
|
if (finished) { return; }
|
|
|
|
if (err) {
|
|
finished = true;
|
|
return done(err);
|
|
}
|
|
|
|
results[key] = res;
|
|
--pending || done(null, results);
|
|
});
|
|
} catch (e) {
|
|
finished = true;
|
|
done(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function observableToThunk(observable) {
|
|
return function (fn) {
|
|
var value, hasValue = false;
|
|
observable.subscribe(
|
|
function (v) {
|
|
value = v;
|
|
hasValue = true;
|
|
},
|
|
fn,
|
|
function () {
|
|
hasValue && fn(null, value);
|
|
});
|
|
}
|
|
}
|
|
|
|
function promiseToThunk(promise) {
|
|
return function(fn) {
|
|
promise.then(function(res) {
|
|
fn(null, res);
|
|
}, fn);
|
|
}
|
|
}
|
|
|
|
function isObservable(obj) {
|
|
return obj && typeof obj.subscribe === fnString;
|
|
}
|
|
|
|
function isGeneratorFunction(obj) {
|
|
return obj && obj.constructor && obj.constructor.name === 'GeneratorFunction';
|
|
}
|
|
|
|
function isGenerator(obj) {
|
|
return obj && typeof obj.next === fnString && typeof obj[throwString] === fnString;
|
|
}
|
|
|
|
/*
|
|
* Spawns a generator function which allows for Promises, Observable sequences, Arrays, Objects, Generators and functions.
|
|
* @param {Function} The spawning function.
|
|
* @returns {Function} a function which has a done continuation.
|
|
*/
|
|
var observableSpawn = Rx.spawn = function (fn) {
|
|
var isGenFun = isGeneratorFunction(fn);
|
|
|
|
return function (done) {
|
|
var ctx = this,
|
|
gen = fn;
|
|
|
|
if (isGenFun) {
|
|
var args = slice.call(arguments),
|
|
len = args.length,
|
|
hasCallback = len && typeof args[len - 1] === fnString;
|
|
|
|
done = hasCallback ? args.pop() : handleError;
|
|
gen = fn.apply(this, args);
|
|
} else {
|
|
done = done || handleError;
|
|
}
|
|
|
|
next();
|
|
|
|
function exit(err, res) {
|
|
timeoutScheduler.schedule(done.bind(ctx, err, res));
|
|
}
|
|
|
|
function next(err, res) {
|
|
var ret;
|
|
|
|
// multiple args
|
|
if (arguments.length > 2) {
|
|
res = slice.call(arguments, 1);
|
|
}
|
|
|
|
if (err) {
|
|
try {
|
|
ret = gen[throwString](err);
|
|
} catch (e) {
|
|
return exit(e);
|
|
}
|
|
}
|
|
|
|
if (!err) {
|
|
try {
|
|
ret = gen.next(res);
|
|
} catch (e) {
|
|
return exit(e);
|
|
}
|
|
}
|
|
|
|
if (ret.done) {
|
|
return exit(null, ret.value);
|
|
}
|
|
|
|
ret.value = toThunk(ret.value, ctx);
|
|
|
|
if (typeof ret.value === fnString) {
|
|
var called = false;
|
|
try {
|
|
ret.value.call(ctx, function() {
|
|
if (called) {
|
|
return;
|
|
}
|
|
|
|
called = true;
|
|
next.apply(ctx, arguments);
|
|
});
|
|
} catch (e) {
|
|
timeoutScheduler.schedule(function () {
|
|
if (called) {
|
|
return;
|
|
}
|
|
|
|
called = true;
|
|
next.call(ctx, e);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Not supported
|
|
next(new TypeError('Rx.spawn only supports a function, Promise, Observable, Object or Array.'));
|
|
}
|
|
}
|
|
};
|
|
|
|
function handleError(err) {
|
|
if (!err) { return; }
|
|
timeoutScheduler.schedule(function() {
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence.
|
|
*
|
|
* @example
|
|
* var res = Rx.Observable.start(function () { console.log('hello'); });
|
|
* var res = Rx.Observable.start(function () { console.log('hello'); }, Rx.Scheduler.timeout);
|
|
* var res = Rx.Observable.start(function () { this.log('hello'); }, Rx.Scheduler.timeout, console);
|
|
*
|
|
* @param {Function} func Function to run asynchronously.
|
|
* @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout.
|
|
* @param [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
|
|
* @returns {Observable} An observable sequence exposing the function's result value, or an exception.
|
|
*
|
|
* Remarks
|
|
* * The function is called immediately, not during the subscription of the resulting sequence.
|
|
* * Multiple subscriptions to the resulting sequence can observe the function's result.
|
|
*/
|
|
Observable.start = function (func, context, scheduler) {
|
|
return observableToAsync(func, context, scheduler)();
|
|
};
|
|
|
|
/**
|
|
* Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler.
|
|
* @param {Function} function Function to convert to an asynchronous function.
|
|
* @param {Scheduler} [scheduler] Scheduler to run the function on. If not specified, defaults to Scheduler.timeout.
|
|
* @param {Mixed} [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
|
|
* @returns {Function} Asynchronous function.
|
|
*/
|
|
var observableToAsync = Observable.toAsync = function (func, context, scheduler) {
|
|
isScheduler(scheduler) || (scheduler = timeoutScheduler);
|
|
return function () {
|
|
var args = arguments,
|
|
subject = new AsyncSubject();
|
|
|
|
scheduler.schedule(function () {
|
|
var result;
|
|
try {
|
|
result = func.apply(context, args);
|
|
} catch (e) {
|
|
subject.onError(e);
|
|
return;
|
|
}
|
|
subject.onNext(result);
|
|
subject.onCompleted();
|
|
});
|
|
return subject.asObservable();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Converts a callback function to an observable sequence.
|
|
*
|
|
* @param {Function} function Function with a callback as the last parameter to convert to an Observable sequence.
|
|
* @param {Mixed} [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
|
|
* @param {Function} [selector] A selector which takes the arguments from the callback to produce a single item to yield on next.
|
|
* @returns {Function} A function, when executed with the required parameters minus the callback, produces an Observable sequence with a single value of the arguments to the callback as an array.
|
|
*/
|
|
Observable.fromCallback = function (func, context, selector) {
|
|
return function () {
|
|
var args = slice.call(arguments, 0);
|
|
|
|
return new AnonymousObservable(function (observer) {
|
|
function handler() {
|
|
var results = arguments;
|
|
|
|
if (selector) {
|
|
try {
|
|
results = selector(results);
|
|
} catch (err) {
|
|
observer.onError(err);
|
|
return;
|
|
}
|
|
|
|
observer.onNext(results);
|
|
} else {
|
|
if (results.length <= 1) {
|
|
observer.onNext.apply(observer, results);
|
|
} else {
|
|
observer.onNext(results);
|
|
}
|
|
}
|
|
|
|
observer.onCompleted();
|
|
}
|
|
|
|
args.push(handler);
|
|
func.apply(context, args);
|
|
}).publishLast().refCount();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Converts a Node.js callback style function to an observable sequence. This must be in function (err, ...) format.
|
|
* @param {Function} func The function to call
|
|
* @param {Mixed} [context] The context for the func parameter to be executed. If not specified, defaults to undefined.
|
|
* @param {Function} [selector] A selector which takes the arguments from the callback minus the error to produce a single item to yield on next.
|
|
* @returns {Function} An async function which when applied, returns an observable sequence with the callback arguments as an array.
|
|
*/
|
|
Observable.fromNodeCallback = function (func, context, selector) {
|
|
return function () {
|
|
var args = slice.call(arguments, 0);
|
|
|
|
return new AnonymousObservable(function (observer) {
|
|
function handler(err) {
|
|
if (err) {
|
|
observer.onError(err);
|
|
return;
|
|
}
|
|
|
|
var results = slice.call(arguments, 1);
|
|
|
|
if (selector) {
|
|
try {
|
|
results = selector(results);
|
|
} catch (e) {
|
|
observer.onError(e);
|
|
return;
|
|
}
|
|
observer.onNext(results);
|
|
} else {
|
|
if (results.length <= 1) {
|
|
observer.onNext.apply(observer, results);
|
|
} else {
|
|
observer.onNext(results);
|
|
}
|
|
}
|
|
|
|
observer.onCompleted();
|
|
}
|
|
|
|
args.push(handler);
|
|
func.apply(context, args);
|
|
}).publishLast().refCount();
|
|
};
|
|
};
|
|
|
|
function fixEvent(event) {
|
|
var stopPropagation = function () {
|
|
this.cancelBubble = true;
|
|
};
|
|
|
|
var preventDefault = function () {
|
|
this.bubbledKeyCode = this.keyCode;
|
|
if (this.ctrlKey) {
|
|
try {
|
|
this.keyCode = 0;
|
|
} catch (e) { }
|
|
}
|
|
this.defaultPrevented = true;
|
|
this.returnValue = false;
|
|
this.modified = true;
|
|
};
|
|
|
|
event || (event = root.event);
|
|
if (!event.target) {
|
|
event.target = event.target || event.srcElement;
|
|
|
|
if (event.type == 'mouseover') {
|
|
event.relatedTarget = event.fromElement;
|
|
}
|
|
if (event.type == 'mouseout') {
|
|
event.relatedTarget = event.toElement;
|
|
}
|
|
// Adding stopPropogation and preventDefault to IE
|
|
if (!event.stopPropagation) {
|
|
event.stopPropagation = stopPropagation;
|
|
event.preventDefault = preventDefault;
|
|
}
|
|
// Normalize key events
|
|
switch (event.type) {
|
|
case 'keypress':
|
|
var c = ('charCode' in event ? event.charCode : event.keyCode);
|
|
if (c == 10) {
|
|
c = 0;
|
|
event.keyCode = 13;
|
|
} else if (c == 13 || c == 27) {
|
|
c = 0;
|
|
} else if (c == 3) {
|
|
c = 99;
|
|
}
|
|
event.charCode = c;
|
|
event.keyChar = event.charCode ? String.fromCharCode(event.charCode) : '';
|
|
break;
|
|
}
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
function createListener (element, name, handler) {
|
|
// Standards compliant
|
|
if (element.addEventListener) {
|
|
element.addEventListener(name, handler, false);
|
|
return disposableCreate(function () {
|
|
element.removeEventListener(name, handler, false);
|
|
});
|
|
}
|
|
if (element.attachEvent) {
|
|
// IE Specific
|
|
var innerHandler = function (event) {
|
|
handler(fixEvent(event));
|
|
};
|
|
element.attachEvent('on' + name, innerHandler);
|
|
return disposableCreate(function () {
|
|
element.detachEvent('on' + name, innerHandler);
|
|
});
|
|
}
|
|
// Level 1 DOM Events
|
|
element['on' + name] = handler;
|
|
return disposableCreate(function () {
|
|
element['on' + name] = null;
|
|
});
|
|
}
|
|
|
|
function createEventListener (el, eventName, handler) {
|
|
var disposables = new CompositeDisposable();
|
|
|
|
// Asume NodeList
|
|
if (Object.prototype.toString.call(el) === '[object NodeList]') {
|
|
for (var i = 0, len = el.length; i < len; i++) {
|
|
disposables.add(createEventListener(el.item(i), eventName, handler));
|
|
}
|
|
} else if (el) {
|
|
disposables.add(createListener(el, eventName, handler));
|
|
}
|
|
|
|
return disposables;
|
|
}
|
|
|
|
/**
|
|
* Configuration option to determine whether to use native events only
|
|
*/
|
|
Rx.config.useNativeEvents = false;
|
|
|
|
/**
|
|
* Creates an observable sequence by adding an event listener to the matching DOMElement or each item in the NodeList.
|
|
*
|
|
* @example
|
|
* var source = Rx.Observable.fromEvent(element, 'mouseup');
|
|
*
|
|
* @param {Object} element The DOMElement or NodeList to attach a listener.
|
|
* @param {String} eventName The event name to attach the observable sequence.
|
|
* @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next.
|
|
* @returns {Observable} An observable sequence of events from the specified element and the specified event.
|
|
*/
|
|
Observable.fromEvent = function (element, eventName, selector) {
|
|
// Node.js specific
|
|
if (element.addListener) {
|
|
return fromEventPattern(
|
|
function (h) { element.addListener(eventName, h); },
|
|
function (h) { element.removeListener(eventName, h); },
|
|
selector);
|
|
}
|
|
|
|
// Use only if non-native events are allowed
|
|
if (!Rx.config.useNativeEvents) {
|
|
// Handles jq, Angular.js, Zepto, Marionette
|
|
if (typeof element.on === 'function' && typeof element.off === 'function') {
|
|
return fromEventPattern(
|
|
function (h) { element.on(eventName, h); },
|
|
function (h) { element.off(eventName, h); },
|
|
selector);
|
|
}
|
|
if (!!root.Ember && typeof root.Ember.addListener === 'function') {
|
|
return fromEventPattern(
|
|
function (h) { Ember.addListener(element, eventName, h); },
|
|
function (h) { Ember.removeListener(element, eventName, h); },
|
|
selector);
|
|
}
|
|
}
|
|
return new AnonymousObservable(function (observer) {
|
|
return createEventListener(
|
|
element,
|
|
eventName,
|
|
function handler (e) {
|
|
var results = e;
|
|
|
|
if (selector) {
|
|
try {
|
|
results = selector(arguments);
|
|
} catch (err) {
|
|
observer.onError(err);
|
|
return
|
|
}
|
|
}
|
|
|
|
observer.onNext(results);
|
|
});
|
|
}).publish().refCount();
|
|
};
|
|
|
|
/**
|
|
* Creates an observable sequence from an event emitter via an addHandler/removeHandler pair.
|
|
* @param {Function} addHandler The function to add a handler to the emitter.
|
|
* @param {Function} [removeHandler] The optional function to remove a handler from an emitter.
|
|
* @param {Function} [selector] A selector which takes the arguments from the event handler to produce a single item to yield on next.
|
|
* @returns {Observable} An observable sequence which wraps an event from an event emitter
|
|
*/
|
|
var fromEventPattern = Observable.fromEventPattern = function (addHandler, removeHandler, selector) {
|
|
return new AnonymousObservable(function (observer) {
|
|
function innerHandler (e) {
|
|
var result = e;
|
|
if (selector) {
|
|
try {
|
|
result = selector(arguments);
|
|
} catch (err) {
|
|
observer.onError(err);
|
|
return;
|
|
}
|
|
}
|
|
observer.onNext(result);
|
|
}
|
|
|
|
var returnValue = addHandler(innerHandler);
|
|
return disposableCreate(function () {
|
|
if (removeHandler) {
|
|
removeHandler(innerHandler, returnValue);
|
|
}
|
|
});
|
|
}).publish().refCount();
|
|
};
|
|
|
|
/**
|
|
* Invokes the asynchronous function, surfacing the result through an observable sequence.
|
|
* @param {Function} functionAsync Asynchronous function which returns a Promise to run.
|
|
* @returns {Observable} An observable sequence exposing the function's result value, or an exception.
|
|
*/
|
|
Observable.startAsync = function (functionAsync) {
|
|
var promise;
|
|
try {
|
|
promise = functionAsync();
|
|
} catch (e) {
|
|
return observableThrow(e);
|
|
}
|
|
return observableFromPromise(promise);
|
|
}
|
|
|
|
return Rx;
|
|
}));
|