Can't touch this? Hammertime!

I’ve recently been asked how to deal with touch events in DukeScript. One option is to use hammer.js, a JavaScript Library that supports the most important number of events and gestures. You could create an API for that and wrap the events in Java Events, but it’s much more elegant to stay in the knockout world.

We don’t need to pollute our viewmodels with the notion of events, we can still use declarative bindings by simply registering knockout bindings for the hammer events. I used knockout-hammer as starting point, but I had to modify it a bit to get it to work:

(function () {

    ['hold', 'tap', 'doubletap', 'drag', 'dragstart', 'dragend', 'dragup', 'dragdown', 'dragleft', 'dragright', 'swipe', 'swipeup', 'swipedown', 'swipeleft', 'swiperight', 'transform', 'transformstart', 'transformend', 'rotate', 'pinch', 'pinchin', 'pinchout', 'touch', 'release'].forEach(function (gesture) {
        return ko.bindingHandlers["hm" + (gesture[0].toUpperCase()) + (gesture.slice(1).toLowerCase())] = {
            init: function (element, valueAccessor, allBindingsAccessor, data) {
                var handler, options, _ref, wrapper;
                wrapper = function (ev) {
                    handler(data, ev);
                };
                if (!valueAccessor()) {
                    return false;
                }
                options = allBindingsAccessor().hmOptions || {};
                handler = valueAccessor().bind(data);
                if ((_ref = data.hammer) == null) {
                    data.hammer = Hammer(element, options);
                }
                data.hammer.on(gesture,wrapper);
                ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                    return data.hammer.off(gesture, wrapper);
                });
                return true;
            }
        };
    });

}).call(this);

Now all we need to do is to make sure the two javascript libraries are loaded in the correct order. I created two classes for that. These classes are in the javascript libraries module of my DukeScript project (because the POM of this module has some special settings for handling JavaScript). Both classes use the @JavaScriptRessource Annotation to register the JavaScript libraries:

@JavaScriptResource("hammer.min.js")
public class HammerLibrary {
    
    @JavaScriptBody(args = {},body="")
    public static native void init();
    
}
@JavaScriptResource("knockout-hammer.js")
public class KnockoutHammerLibrary {
    
    
    public static void init(){
       HammerLibrary.init();
       init_impl();
    }

    @JavaScriptBody(args = {},body="")
    public static native void init_impl();
    
}

When calling KnockoutHammer.init(), HammerLibrary.init() is called first, so this JavaScript file is loaded before the bindings. Now we can use our library in a project:

In index.html:

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <style>  #myElement {
    background: silver;
    height: 300px;
    text-align: center;
    font: 30px/300px Helvetica, Arial, sans-serif;
  }</style>
    </head>
    <body>
        <div id="myElement" data-bind="hmTap: onHmTap, hmOptions: {interval:500}, text: text"></div>
    </body>
</html>

The actual event is prefixed with “hm”, so it’s “hmTap”. The name of the handler function is “onHmTap”. We can also pass some constraints. In our case we collect all the taps that happen in a 500ms interval. Now let’s have a look at the function that is called on our viewmodel:

@Model(className = "Data", targetId = "", properties = {
    @Property(name = "text", type = String.class)
})
final class DataModel {

    private static Data ui;

    /**
     * Called when the page is ready.
     */
    static void onPageLoad() throws Exception {
        ui = new Data("tap here", 400);
        Models.toRaw(ui);
        KnockoutHammerLibrary.init();
        ui.applyBindings();
    }

    @Function
    public static void onHmTap(Data data, int tapCount) {
        data.setText("Tapped "+tapCount);
    }
}

DukeScript recognizes the “tapCount” property of the method signature and automatically extracts the matching value from the event. Now launch the application on your Android or iOS device and check the result. If successful I suggest doing a little victory dance to this tune.

You can find the sample project on github.