Search code examples
javascripthtmlcordovaonsen-ui

Referencing Templated HTML Elements in Cordova/Onsen


This may not be specific to Cordova/Onsen, but I'm new to Javascript so I'm just providing the information that I think it relevant. I'm wondering what the correct way to reference templated HTML elements is. Here's what I'm doing currently (snippets):

HTML:

    <ons-navigator title="myApp" var="myNavigator">
        <ons-page id="mainPage">
            <ons-toolbar>
                <div class="center">Title</div>
            </ons-toolbar>

            <ons-list>
                <ons-list-item id="button" modifier="chevron">
                    Button Text
                </ons-list-item>
            </ons-list>
        </ons-page>
    </ons-navigator>

    <ons-template id="newPage.html">
        <ons-page id="newPage">
            <ons-toolbar>
                <div class="left">
                    <ons-back-button>Back</ons-back-button>
                </div>
                <div class="center">Page Title</div>
            </ons-toolbar>
            <button id="submit-btn" class="button button--large">Submit</button><br>
        </ons-page>
    </ons-template>

Javascript:

(function () {
"use strict";

/* Event Registration */
document.addEventListener('deviceready', onDeviceReady.bind(this), false);

// Listening for pageinit event to grab submit button.
document.addEventListener("pageinit", function (event) {
    console.log("init Fired!");
    if (event.target.id == "newPage") {
        initPage();
    }
}, false);

function initPage() {
    $('#submit-btn').on('click', doSomethingUseful);
}

function onDeviceReady() {
    // Handle the Cordova pause and resume events
    document.addEventListener( 'pause', onPause.bind( this ), false );
    document.addEventListener('resume', onResume.bind(this), false);

    // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
    $('#button').on('click', function () {
        myNavigator.pushPage('newPage.html', { animation: 'slide' })
    });
};
} )();

Essentially, I'm waiting for Onsen to signal the pageinit event before grabbing the button from the templated page. The pageinit signal gets sent once the page is loaded, i.e. when "button" is pressed.

Even though this works, it feels like there should be a better way to do this. Rather than having to wait for a page to be loaded before setting up button handlers and other things, it seems like I should be able to set them up ahead of time. However, if I try to access the templated element before the page is loaded, it doesn't work (the jQuery selector returns null).

FYI, I'm not using Angular. (most of the Onsen examples are in Angular, but I'm learning enough new things at the moment and didn't want another thing to add confusion at this point.)


Solution

  • As @Munsterlander mentioned - you cannot attach events to elements which have not been added to the DOM yet. Currently what you are doing is sort of considered as the best way of doing things. So I would just suggest trying to simplify the existing code without actually changing it's general idea. For example: inline-ing some functions and using ons.ready instead of deviceready. An equal and simpler version of this code can be:

    document.addEventListener("pageinit", function (event) {
        console.log("init Fired!");
        if (event.target.id == "newPage") {
            $('#submit-btn').on('click', doSomethingUseful);
        }
    });
    
    ons.ready(function () {
        document.addEventListener( 'pause', onPause.bind( this ), false );
        document.addEventListener('resume', onResume.bind(this), false);
    
        // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
        $('#button').on('click', function () {
            myNavigator.pushPage('newPage.html', { animation: 'slide' })
        });
    });
    

    Usually I would also suggest making something like an object with the ids of the pages. Something like:

    var handlers = {
        newPage: function(e) { $('#submit-btn').click(doSomethingUseful); },
        page2: function(e) { $('#submit-btn2').click(doSomethingUseful2); }
    };
    

    and

    document.addEventListener('pageinit', function(e) {
        if (handlers.hasOwnProperty(e.target.id)) {
          handlers[e.target.id](e);
        }
    });
    

    And I guess now is the time for the real answer which you wanted. Since you cannot listen to elements before they are added to the DOM then if you want you could listen to events and filter their targets the same way which you're doing with the pageinit event. With jQuery there is a relatively easy way to do this:

    $(document).on(eventName, selector, handler);
    

    so you can just do this at any point

    $(document).on('click', '#submit-btn', doSomethingUseful);
    

    And it will work since it's attaching the handler to the document instead of the element. So this will work for all #submit-btns which you add in the future to the DOM. :) (Even though this is not very recommended to do if you want you can still do it.)