Friday, January 11, 2013

WinJS Event Binding with Parameters

This is actually a JavaScript topic but let's just exam it inside WinJS context. Suppose we have a simple home.html with two divs:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>homePage</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

    <link href="/css/default.css" rel="stylesheet" />
    <link href="/pages/home/home.css" rel="stylesheet" />
    <script src="/pages/home/home.js"></script>
</head>
<body>
    <!-- The content that will be loaded and displayed. -->
    <div class="fragment homepage">
        <header aria-label="Header content" role="banner">
            <button class="win-backbutton" aria-label="Back" disabled type="button"></button>
            <h1 class="titlearea win-type-ellipsis">
                <span class="pagetitle">Welcome to Windows Store App!</span>
            </h1>
        </header>
        <section aria-label="Main content" role="main">
            <div id="book">This is book.</div>
            <div id="cd">This a CD.</div>
        </section>
    </div>
</body>
</html>
We want to show some message when those two divs are clicked and the showMessage is defined in home.js:
    function showMessage(value) {
        var msg = "You have clicked " + this.id + ". " + value;
        var popup = new Windows.UI.Popups.MessageDialog(msg);
        popup.showAsync();
    }
We can't bind the click handler directly to the method name because the the parameter will be missing and the execution context is wrong. In order to passing the parameter to the binding function and set the right "this" context, we could wrap the function call inside another function:
(function () {
    "use strict";

    function showMessage(value) {
        var msg = "You have clicked " + this.id + ". " + value;
        var popup = new Windows.UI.Popups.MessageDialog(msg);
        popup.showAsync();
    }

    WinJS.UI.Pages.define("/pages/home/home.html", {
        ready: function (element, options) {
            book.addEventListener("click", function () {
                showMessage.call(this, "Enjoy reading the book!");
            });
            cd.addEventListener("click", function () {
                showMessage.call(this, "Enjoy the music!");
            });
        }
    });
})();
Simple and it works. From the popup we can see the passed-in parameter and the right "this" object are referenced:



There's also other alternative to resolve the problem. John Resig posted an interesting article back in 2008 and later people refer it as JavaScript curry concept. It's basically to pre-fill arguments to a JavaScript function before it executed. Using John Resig's technique we can rewrite above binding and make it more generic and elegant:
(function () {
    "use strict";

    function showMessage(value) {
        var msg = "You have clicked " + this.id + ". " + value;
        var popup = new Windows.UI.Popups.MessageDialog(msg);
        popup.showAsync();
    }

    // Pre-fill arguments to a function. Example:
    //   function sum(a, b, c) { return a + b + c; }
    //   var sum_1_2 = curry(sum, this, 1, 2);
    //   var result = sum_1_2(3); // result = 1 + 2 + 3 = 6
    function curry(fn, self) {
        var self = self || window;
        var args = Array.prototype.slice.call(arguments, 2);
        return function () {
            fn.apply(self, args.concat(Array.prototype.slice.call(arguments)));
        };
    }

    WinJS.UI.Pages.define("/pages/home/home.html", {
        ready: function (element, options) {
            book.addEventListener("click", curry(showMessage, book, "Enjoy reading the book!"));
            cd.addEventListener("click", curry(showMessage, cd, "Enjoy the music!"));
        }
    });
})();

Friday, January 04, 2013

WinJS Promises Run in Sequence

Sometime we need to invoke a series of asynchronous calls (WinJS Promise objects) in sequence, i.e. wait for the first promise to complete and then start the second promise and so on. One way of doing that is by recursive calls as following demo code:

(function () {
    "use strict";
    
    // Download a link asychronously
    function downloadAsync(url) {
        console.log(url + " starting...");
        return WinJS.xhr({ url: url}).then(function () {
            console.log(url + " completed");
        }, function (err) {
            console.log(url + " error: " + err);
        });
    }
    
    // Recersivly download links asychronously
    function downloadRecursiveAsync(urls) {
        if (urls && urls.length > 0) {
            var url = urls[0];
            var remainUrls = urls.slice(1);
            return downloadAsync(url).then(function () {
                return downloadAsyncRecursive(remainUrls);
            });
        } else {
            return WinJS.Promise.as();
        }
    }

    WinJS.UI.Pages.define("/pages/home/home.html", {
        ready: function (element, options) {
            var testUrls = ["http://aa.com", "http://bb.com", "http://cc.com"];
            var promises = [];

            console.log("Start multiple downloads asynchronously without sequence.");
            for (var i = 0; i < testUrls.length; i++) {
                promises.push(downloadAsync(testUrls[i]));
            }

            WinJS.Promise.join(promises).then(function () {
                console.log("All asynchronous downloads completed without sequence.");
            }).then(function () {
                console.log("Start multiple download asynchronously in sequence.");
                downloadRecursiveAsync(testUrls).then(function () {
                    console.log("All asynchronous downloads completed in sequence.");
                });
            });
        }
    });
})();

Console log:

Start multiple downloads asynchronously without sequence.
http://aa.com starting...
http://bb.com starting...
http://cc.com starting...
http://bb.com completed
http://cc.com completed
http://aa.com completed
All asynchronous downloads completed without sequence.
Start multiple download asynchronously in sequence.
http://aa.com starting...
http://aa.com completed
http://bb.com starting...
http://bb.com completed
http://cc.com starting...
http://cc.com completed
All asynchronous downloads completed in sequence.