How to execute a script when the custom element is upgraded
Asked Answered
T

3

16

I have defined a custom element and I want to execute a script only when the custom element is upgraded to its registered type. The use case is that I must call a custom method.

My main html file looks like this:

<project-list></project-list>
<script>
    var project_list = document.getElementsByTagName("project-list")[0]
    project_list.custom_method("some_data");
</script>

The custom element is registered in a HTML import like this:

<script>
  "use strict";
  var currentScript = document._currentScript || document.currentScript;

  class ProjectList extends HTMLElement {

    createdCallback(){
      console.log("created");
    }

    custom_method(data) {
      console.log("custom_method() OK");
      console.log(data);

      this.innerHTML = data;
    }

  }

  document.registerElement("project-list", ProjectList);
</script>

My question is simple: how to make sure the script in the main html file is called only after the custom element gains its custom_method method?

I'm looking for an elegant solution. Something that the spec authors would have thought of. I don't mind changing the architecture quite a bit (for example by moving the javascript from the main file into another custom element if that is necessary).

Turnip answered 4/3, 2016 at 20:13 Comment(4)
on browsers that implement the spec, creating the element and appending it will be sync. in terms of hard-coded tags though, afaik, there is no "allComponentsReady"-like DOM event. one way is to raise a custom event on document from within the component, and use that event like an onload() event for the page's code that needs the custom methods. you might also look into something like redux to wire up many disparate web components and app methods in a way that doesn't suck.Navigable
@dandavis: This is what I do. But I also set a flag. If the flag is set, then the component is already set up and the custom method can be executed immediately. Otherwise, it is scheduled for when the event fires. I have posted an answer with the code I use.Panchito
@dandavis: To wire up many disparate web components: If some webcomponent depends on others, then it must wait until ALL these other components are ready, before it set/dispatches its own flags/events.Panchito
of course, you can wait on about anything with a self-defer pattern: (function wait(){if(! project_list.custom_method) return setTimeout(wait, 50); project_list.custom_method("some_data");}()); you can white-list more pre-reqs in there if you need several depends.Navigable
S
5

Sync HTML Import

As suggested by @dandavis, because of the sync default behaviour of the <link> and <script> elements, you just have to put your tags in the right order: registration before method call.

Or instead you can call your custom method when the DOMContentLoaded or the window.onload event is fired, as below:

window.onload = function() 
{
    var project_list = document.getElementsByTagName("project-list")[0]
    project_list.custom_method("some_data")     
}
<project-list></project-list>

<script>
  "use strict";
  var currentScript = document._currentScript || document.currentScript;

  class ProjectList extends HTMLElement {

    createdCallback(){
      console.log("created");
    }

    custom_method(data) {
      console.log("custom_method() OK");
      console.log(data);

      this.innerHTML = data;
    }

  }

  document.registerElement("project-list", ProjectList);
</script>

Async HTML Import

If for some reasons you want to load your HTML Import file asynchronousely, you can wait for the link.onload event. At this time the |<script> inside the import has been already exectued, the custom element registered and created.

<html>
<head>
  <title></title>
    <meta charset="utf-8" />
    <link rel="import" href="projectList.html" id="projectList" async>
    <script>
    projectList.onload = function ()
    {
      console.log( "import {loaded}" )
      var project_list = document.getElementsByTagName( "project-list" )[0]
      project_list.custom_method("some_data")
    } 
    </script>
</head>
<body>
    <project-list id="pl"></project-list>
    <script>
        console.warn( "custom_method is " + pl.custom_method ) //undefined
    </script>
</body>
</html>

With WebComponents.js polyfill

In this situation the polyfill won't instantiate the created object immediately after the import is loaded. Instead you should listen to the WebComponentsReady event:

document.addEventListener( "WebComponentsReady", function ()
{
    console.log( "WebComponentsReady" )
    var project_list = document.getElementsByTagName( "project-list" )[0]
    project_list.custom_method( "some_data" )
} )

It works with Firefox, IE 11, and also with Chrome.

Steverson answered 5/3, 2016 at 0:9 Comment(5)
This would work on Chrome but on Firefox with the Polymer polyfill, it won't work. It is always asynchronousTurnip
I think this is related to this bug: github.com/webcomponents/webcomponentsjs/issues/431Turnip
For a cross-browser solution I've updated my answer.Steverson
@user6020072: That bug says it should NOT block on async. HTML imports, as per spec, may be sync or async. Read here: webcomponents.org/articles/introduction-to-html-importsPanchito
This also works: HTMLImports.whenReady(function(){ ... });Turnip
A
0

Load the dependency via an import in your custom element module. For example, a custom element module (e.g.something.mjs) which depends on Highcharts:

import '/node_modules/highcharts/highcharts.js'

class Something extends HTMLElement {}

customElements.define('x-something', Something)
Agony answered 7/8, 2020 at 14:50 Comment(0)
R
0

I was having an identical issue where I could not call functions on custom elements before they were upgraded. In this particular scenario, I did not have very much control over when the scripts were executing, so I leveraged a different approach using CustomElementRegistry:whenDefined().

The resulting function takes the tag names for the custom elements and returns a promise which executes when the custom elements are defined:

/**
 * Waits for the given components to be defined before executing.
 * @param {string[]} components the component tag names.
 * @returns {Promise<CustomElementConstructor[]>} a Promise that resolves when all the components have been defined.
 */
export function waitForComponents(components) {
    if (components == null)
        return;

    return Promise.all(components.map(n => customElements.whenDefined(n)));
}

// ...

waitForComponents('project-list').then(() => {
    let project_list = document.getElementsByTagName("project-list")[0];
    project_list.custom_method("some_data");
});
Rataplan answered 15/8 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.