How to use DukeScript with OnsenUI

Onsen UI is a popular framework for creating hybrid mobile web apps. It has native look and feels for iOS and Android. In the past we used ChocolateChipUI for some projects, but nowadays OnsenUI seems more active and popular. here are some hints in case you want to use it in your own project.

ons-navigator

Fortunately OnsenUI is framework agnostic, so it should be easy to use it in DukeScript. For simple demos with a single ons-page it works out of the box. But typically you use a ons-navigator to navigate between pages.

The ons-navigator provides page stack management and navigation. Stack navigation is the most common navigation pattern for mobile apps. When a page is pushed on top of the stack it is displayed with a transition animation. When the user returns to the previous page the top page will be popped from the top of the stack and hidden with an opposite transition animation. To display the pushed page the navigator reads the page from a ons-template and adds it to the dom. Integrating that with DukeScript requires a bit of JavaScript.

ons-page lifecycle

The ons-page throws an “init” event during it’s lifecycle and we can listen on the document for these events. All we need to do is take the relevant view model and call applybindings for the new page. You can have your own management for binding viewmodels on demand. For the sake of simplicity, we’ll simply bind the root view model and pick the relevant submodel in the ons-page. This way you can simply add the bindings as if the template would already be part of the page.

Let’s have a look at an example with some bindings:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Onsen UI App</title>
        <link href="css/onsenui.css" rel="stylesheet" type="text/css"/>
        <link href="css/onsen-css-components.min.css" rel="stylesheet" type="text/css"/>
        <script src="js/onsenui.min.js" type="text/javascript"></script>
    </head>
    <body>
    <ons-navigator page="tabbar.html"></ons-navigator>

    <ons-template id="tabbar.html">
        <ons-page id="tabbar-page">
            <ons-toolbar>
                <div class="center">KO List </div>
            </ons-toolbar>
            <ons-tabbar position="auto"> 
                <ons-tab page="list.html" label="List" active></ons-tab>
                <ons-tab page="settings.html" label="Settings"></ons-tab>
            </ons-tabbar>
        </ons-page>
    </ons-template>

    <ons-template id="list.html">
        <ons-page id="list-page">
            <!-- ko with: itemList -->
            <ons-list data-bind="foreach: items">
                <ons-list-item tappable>
                    <div class="center" data-bind="text: $data, click: $parent.detailsItem"></div>
                    <div class="right" data-bind="click: $parent.removeItem">
                        <ons-icon icon="ion-ios-trash-outline, material:md-delete"></ons-icon>
                    </div>
                </ons-list-item>
            </ons-list>

            <ons-if platform="android">
                <ons-fab position="bottom right" data-bind="click: addItem">+</ons-fab>
            </ons-if>
            <ons-if platform="ios other">
                <p><ons-button modifier="large--quiet" data-bind="click: addItem">Add new item</ons-button></p>
            </ons-if>
            <!-- /ko -->
        </ons-page>
    </ons-template>

    <ons-template id="details.html">
        <ons-page id="details-page">
            <!-- ko with: itemList -->
            <ons-toolbar>
                <div class="left"><ons-back-button>KO List</ons-back-button></div>
                <div class="center">Items details</div>
            </ons-toolbar>
            <ons-row style="margin-top: 100px; text-align: center;">
                <ons-col>
                    <p style="color: #666; font-size: 18px;">You clicked on: <span style="color: #999" data-bind="text: item"></span></p>
                </ons-col>
            </ons-row>
            <!-- /ko -->
        </ons-page>
    </ons-template>

    <ons-template id="settings.html">
        <ons-page id="settings-page">
            <p style="text-align: center" data-bind="text: info"></p>
        </ons-page>
    </ons-template>
</body>
</html>

And here’s the view model we’re binding to:

@Model(className = "SettingsViewModel", targetId = "", instance = true, properties = {
    @Property(name="info", type = String.class),
    @Property(name="itemList",type = ListViewModel.class)
})
final class DataModel {
    static void onPageLoad() {
        SettingsViewModel ui = new SettingsViewModel();     
        ListViewModel listViewModel = new ListViewModel("Item 1", "Item 2", "Item 3");
        ui.setItemList(listViewModel);
        ui.setInfo("hello");
        ui.applyBindings();
        OnsenUITemplates.registerListener();
    }
 
    @Model( className = "ListViewModel", properties = {
        @Property(name = "items", type = String.class, array = true)
        ,@Property(name = "item", type = String.class)
    }
    )
    public static class ListViewModelVMD{
        @Function
        public static void addItem(ListViewModel model){
           model.getItems().add("Item "+model.getItems().size());
        }
        
        @Function
        public static void removeItem(ListViewModel model, String data){
           model.getItems().remove(data);
        }
        
        @Function
        public static void detailsItem(ListViewModel model, String data){
           model.setItem(data);
           OnsenUITemplates.switchPage("details.html");
        }
    }
}

The interesting part is the call to “OnsenUITemplates.registerListener()” and “OnsenUITemplates.switchPage(“details.html”)”.

The “registerListener” methods job is to wait for an ons-page to be loaded and apply the bindings. The method “switchPage” is used, when the user wants to swith to a view via a Function in the Java viewmodel (see method “detailsItem”). It simply asks the Navigator to load a certain ons-page.

As soon as the page is loaded an “init” event is thrown and the bindings are applied.

public class OnsenUITemplates {

    @JavaScriptBody(args = {"name"}, body
            = "document.querySelector('ons-navigator')\n"
            + "      .pushPage(name);")
    public static native void switchPage(String name);

    @JavaScriptBody(args = {}, body
            = "document.addEventListener('init', function(event) {\n"
            + "  var page = event.target;"
            + "    ko.applyBindings(ko.dataFor(page), page);\n"
            + "  }); ")
    public static native void registerListener();
}

That’s it. With these little changes you can use the beautiful OnsenUI for your DukeScript mobile apps.