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!"));
        }
    });
})();