Introduction to the Reactive Extensions for JavaScript – MooTools Integration

In the previous post, I covered a bit about how we’ve integrated the Dojo Toolkit into the Reactive Extensions for JavaScript (RxJS) where we can leverage Dojo’s eventing and AJAX programming model.  Following onto that post, I wanted to move onto a different framework and show how we integrated it into RxJS.  This time, it’s MooTools up to the plate.

Now the question comes, why then if the given framework works as well as it does, why would we care to integrate it into RxJS?  What does it buy us?  What if we want to combine two mouse moves together, one offset from the other so that we get a delta from the previous?  Or how about throttling the values coming from our events, either the first value only or a delay between each value?  Between all the callbacks, setTimeouts and the global state that you need to track, it becomes very hand to manage.  That’s the reason why we’ve created this layer where we can go seamlessly from MooTools and other JavaScript frameworks to RxJS.  We can either hop out of the world of our framework and into RxJS, or integrate it underneath the covers to manage the composable nature of your framework.  Both are great options…

Before we get started, let’s get caught up to where we are today:

Diving Into MooTools Events

Once again the first part of our journey is to cover the eventing model of MooTools.  MooTools has the ability through any number of classes to be able to listen to events.  We’ll start with the Element class method addEvent, which attaches an event listener to a given DOM element. 

someElement.addEvent(
    type, 
    fn);

Each parameter is described below:

  • type (string) – the name of the event to monitor such as click, mousemove, etc.
  • fn (function) – the callback function to execute when the event is fired.

The fn callback as noted above gives us a Event which has any number of properties including page and client position, keys used, the target and related targets as well as some methods to stop propagation. 

Conversely to our addEvent, we can remove our handler from the Element by using the removeEvent method.

someElement.removeEvent(
    type, 
    fn);

Each parameter is described below:

  • type (string) – the name of the event to remove
  • fn (function) – the callback function to remove

We can then listen to events in MooTools such as a keyup event on a given input element.

$("someInput").addEvent("keyup", 
    function(event) {
        $("whatkey").set("html", key);
    });

We can wrap these events by using the Rx.Observable.Create function which hands us an observer to use and then we return a disposable which allows us to remove our listener.  Inside the Create function, we’ll create a handler which takes the event object from MooTools and passes it to the observer.OnNext.  After we’ve defined the handler, we call addEvent on our MooTools Element with the event type and we’ll pass in the handler as the method to invoke.  Finally, we return a function which is invoked when we call Dispose on our subscription.  Below is a wrapped version called Rx.Observable.FromMooToolsEvent.

Rx.Observable.FromMooToolsEvent = function(obj, eventType) {
    return observable.Create(function(observer) {
    
        // Create handler for OnNext
        var handler = function(eventObject) {
            observer.OnNext(eventObject);
        };
        
        // Subscribe to event
        obj.addEvent(eventType, handler);
        
        // Return function used to remove event handler
        return function() {
            obj.removeEvent(eventType, handler);
        };
    });
};

What this allows is for us to use a MooTools element to attach an event much like the following:

var element = $("someElement");

Rx.Observable.FromMooToolsEvent(element, "mousemove")
    .Where(
        function(event) {
            return event.client.x === event.client.y;
        })
    .Subscribe(
        function(event) {
            $("mousePosX").set("html", event.client.x);
        });

That’s all well and good, but I think it’s better to live and breathe within MooTools itself.  In order to do so, we need to extend a few objects that expose adding and removing event handlers, such as Element, Elements, Window, Document as well as any other class which inherits from the Event class.  To do that, we’ll use the Class method implement in which we pass in properties which alters the base class so that we can modify existing classes.  For example, I can extend the Element and all other classes mentioned above by doing the following:

var fromMooToolsEvent = function(type) {
    return fromMooToolsEvent(this, type);
}

Window.implement({
    addEventAsObservable : fromMooToolsEvent
});

Document.implement({
    addEventAsObservable : fromMooToolsEvent
});

Element.implement({
    addEventAsObservable : fromMooToolsEvent
});

Elements.implement({
    addEventAsObservable : fromMooToolsEvent
});    

Events.implement({
    addEventAsObservable : fromMooToolsEvent
});  

By doing this, we can take the above example of hooking a mouse move listener to report the x position relative to the viewport when both the x and y values are the same:

$("someElement").addEventAsObservable("mousemove")
    .Where(
        function(event) {
            return event.client.x === event.client.y;
        })
    .Subscribe(
        function(event) {
            $("mousePosX").set("html", event.client.x);
        });

Now that we have the basics down here, what about the AJAX APIs?

Diving into the Request

Another area where the Reactive Extensions for JavaScript can integrate well is with AJAX APIs.  MooTools exposes three such classes in the Core library, Request, Request.HTML and Request.JSON.  Each of those do as you suspect with one handling general requests such as text, whereas the HTML and JSON classes handle their respective data types.  But the one I’m going to focus on is in the More MooTools library called Request.JSONP, which allows me to make cross-domain JSON calls.

new Request.JSONP(options);

The documentation for options is as follows:

  • url - (url) the url to get the JSON data
  • callbackKey - (string) the key in the url that the server uses to wrap the JSON results. So, for example, if you used callbackKey: 'callback' then the server is expecting something like http://..../?q=search+term&callback=myFunction; defaults to "callback". This must be defined correctly.
  • data - (object) additional key/value data to append to the url
  • retries - (integer; defaults to zero) if this value is a positive integer, the JSONP request will abort after the duration specified in the timeout option and fire again until the number of retries has been exhausted.
  • timeout - (integer; defaults to zero) the duration to wait before aborting a request or retrying.
  • injectScript - (mixed; defaults to document head) where to inject the script elements used for the calls
  • log - (boolean) if true, sends logging messages to Log (to console if it's defined). Defaults to false.

For example, should we want to search on Twitter once again, we can pass in our URL, the data for the query string, the callback and a function used for the callback.  We can then create the Request.JSONP instance with our options and send our request.  

var options = {
    url: 'http://search.twitter.com/search.json',
    data: { rpp: "100", q: "4sq.com" }
    callbackKey: "callback" 
    onComplete: 
        function(data) {
            $("results").set("html", data.results[0].text);
        }
};
var req = new Request.JSONP(options);
req.send();

We could also cancel our request as well should we need to by using the cancel function.  Once again, the issue arises of composability as well as error handling.  How could we integrate with key up events and throttle our request?  Sooner or later when we start composing things together via callbacks, our code could walk right off the screen.  Instead, RxJS could allow us some flexibility to compose events and AJAX calls together.  To make this happen, we can wrap the API once again like we did with Dojo and jQuery, but this time we’ll take a different approach.

As before, we’ll create an AsyncSubject, which is used for asynchronous communication that happens once and then caches the value.  Next, we need to handle both the success and error conditions by adding the functions load and error respectively to the options object.  The load function simply passes the data where we call the subject’s OnNext with the data and then mark it as complete with the OnCompleted call.  The error function simply is given an error object so that we can see what went wrong and act accordingly.  Now what’s different between the two approaches is that we’re supporting a cancel option.  If we should call Dispose on our subscription before the request is complete, we want to have the ability to cancel our request.  To make this happen we’ll create a RefCountDisposable which allows us to cancel our request should we need to.  And finally we’ll return a CompositeDisposable which takes the Disposable from the AsyncSubject and from the cancel together so that if one ends before the other, then we can continue with the second.

observable.MooToolsJSONPRequest = function(options) {

    var subject = new root.AsyncSubject();
    var request = null;
    
    try {
        options.onSuccess = function(html) {
            subject.OnNext(html);
            subject.OnCompleted();
        };
    
        options.onFailure = function(xhr) {
            subject.OnError({ kind: "failure", xhr: xhr });
        };
    
        options.onException = function(headerName, value) {
            subject.OnError({ kind: "exception", headerName: headerName, value: value });
        };
        
        request = new Request.JSONP(options);
        request.send();
    }
    catch(err) {
        subject.OnError(err);
    }
    
    var refCount = new root.RefCountDisposable(root.Disposable.Create(function() {
        if(request) {
            request.cancel();
        }
    }));
    
    return observable.CreateWithDisposable(function(subscriber) {
        return new root.CompositeDisposable(subject.Subscribe(subscriber), refCount.GetDisposable());
    });    
}    

We can also extend the Request.JSONP class (and thanks for the assistance from Sebastian Markbåge) by using the implement function once again.  We’ll create a toObservable function to implement this same functionality.  There is a difference though that the options are private to the class.  So, we can either use the setOptions function to set the options or since the Request.JSONP class inherits from the Events class, we can call addEvents directly to add properties for success, failure and exception cases.

Request.JSONP.implement({
   
    toObservable: function () {
        var subject = new root.AsyncSubject();
        var request = this;
        try {
            
            request.addEvents({
    
                success: function(data) {
                    subject.OnNext(data);
                    subject.OnCompleted();
                },
    
                failure: function(xhr) {
                    subject.OnError({ kind: "failure", xhr: xhr });
                },
    
                exception: function(headerName, value) {
                    subject.OnError({ kind: "exception", headerName: headerName, value: value });
                }
    
            });
            
            request.send();
        }
        catch (err) {
            subject.OnError(err);
        }
    
        var refCount = new root.RefCountDisposable(root.Disposable.Create(function () {
            request.cancel();
        }));
    
        return observable.CreateWithDisposable(function (subscriber) {
            return new root.CompositeDisposable(subject.Subscribe(subscriber), refCount.GetDisposable());
        });
    }        
}); 

Once we’ve implemented this, we’re able to redo example where we query Twitter for those who are posting to FourSquare and has geolocation turned on and get the maximum user ID and then print their text.

window.addEvent("domready", function() {
    var options = {
        url: "http://search.twitter.com/search.json",
        data: { rpp : "100", q : "4sq.com" },
        callbackKey: "callback"
    };

    new Request.JSONP(options)
        .toObservable()
        .SelectMany(
            function(data) {
                return Rx.Observable.FromArray(data.results);
            })
        .Where(
            function(result) {
                return result.geo != null;
            })
        .Max(
            function(result) {
                return result.from_user_id;
            })
        .Subscribe(
            function(result) {
                alert(result.text);
            },
            function(error) {
                alert("An error!" + error.description);
            });
});

You can find the complete MooTools integration here and will be available in the next release of RxJS.

Instead of this example where we go from MooTools to RxJS, we could also extend a number of classes to create an API totally in MooTools with RxJS underneath the covers.  With this, we could implement behavior similar to that of Towel.js, an extension to MooTools for reactive programming.

Conclusion

Dealing with asynchronous programming has been in the forefront of many minds in the JavaScript community.  At JSConf, there were several examples of frameworks trying to get around the idea of callbacks and instead lean more towards composition.  By utilizing the Reactive Extensions for JavaScript, we’re able to compose together asynchronous and event-based operations together and transform the results in interesting ways. 

One important aspect of the Reactive Extensions for JavaScript is how well we play with other frameworks.  By giving out of the box extensions points, this allows for you to still use your framework of choice along with the Reactive Extensions to take care of the event-based and asynchronous behavior.

So with that, download it, and give the team feedback!

No Comments