// 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; }));