javascriptdom-eventshtml-imports

Event Listener not firing from JS script within HTML Import


I am importing form.html into index.html with the following function:

function importHTML() {
    let link = document.createElement('link');
    link.rel = 'import';
    link.href = 'form.html';
    link.onload = (e) => {
        console.log('Successfully loaded import: ' + e.target.href);
        importContent();
    }
    link.onerror = (e) => {
        console.log('Error loading import: ' + e.target.href);
    }
    document.head.appendChild(link);

    let importContent = () => {
        let importContent = document.querySelector('link[rel="import"]').import;
        if (importContent != null) {
            let el = importContent.querySelector('#formContainer');
            let container = document.body.querySelector('main');
            container.appendChild(el.cloneNode(true));
        }
    }
}

This works to creates a new link rel="import" tag, appending it to the head of index.html. When the link has completed loading, the content from form.html is appended to the main body container.

Inside form.html I have a script that gets a handle to a pagination element to attach an event handler:

<section id="formContainer">

    <form>
        ...
    </form>

    <!-- NOTE: pagination controls -->
    <div class="pagination">
        <span id="pageBack"><i>&lt;</i></span>
        <span id="pageForward"><i>&gt;</i></span>
    </div>

    <script>

        let importDoc = document.currentScript.ownerDocument;
        let pageForward = importDoc.querySelector('#pageForward');
        let pageBack = importDoc.querySelector('#pageBack');

        // these elements are present in the console at runtime
        console.log(pageForward, pageBack);

        pageForward.addEventListener('click', (e) => {
            console.log('click event heard on pageBack');
        });

        pageBack.addEventListener('click', (e) => {
            console.log('click event heard on pageBack');
        });

    </script>

</section>

The issue I'm having is that the Event Listeners are not firing despite the console showing no errors.

I thought this might have something to do with load order and experimented around with this a little bit, making sure that the import loads before the script is parsed though I'm not 100% on whether or not this is working as expected.

I've found it works to move my acting script into the main document by dynamically loading it after the importContent() function but I'd prefer to keep the form's associated script encapsulated within the Import.

Thoughts?


Solution

  • The Event Listeners are attached to the wrong element. In your example, they are set on the <span> elements in the imported document.

    But these elements are cloned and the <span> elements that are clicked are the cloned elements with no set Event Listeners.

    To make the code work, you should query the elements form the <body> instead of querying the imported document.

    In form.html:

    <script>
    let importDoc = document.currentScript.ownerDocument
    let el = importDoc.querySelector( '#formContainer' )
    let container = document.body.querySelector( 'main ' )
    container.appendChild( el.cloneNode( true ) )
    
    let pageForward = container.querySelector( '#pageForward' )
    let pageBack = container.querySelector( '#pageBack')
    
    // these elements are present in the console at runtime
    console.log(pageForward, pageBack);
    pageForward.addEventListener('click', e => 
        console.log( 'click event heard on pageBack' )
    )
    
    pageBack.addEventListener('click', e => 
        console.log( 'click event heard on pageBack' )
    )
    </script>
    

    NB : the in the imported document is executed as soon as the document is imported. No need to wait for a onload event and call a callback from the main document.

    If you want to defer the execustion of the script, you'll need to put it in a <template> element.