Event listener for custom element

I’ve been getting to know custom elements and was able to integrate a 3rd party app API using a “click” addEventListener for the custom element with this code:

I now want to be able to trigger the custom element code immediately when the page loads. I tried this but it didn’t seem to work:

Can anyone point me in the right direction?

I’m not sure if you can use something like:

windows.addEventListener('load')

Which will be fired when the page loads;


Passing data from page to custom element in onReady

But what you can do I think is you can pass an empty data or anything from your page code to custom element with custom element APIs (setAttribute) inside the $w.onReady in this way you can fire any code when custom element recieve the attirbute value say pageLoaded.

Example:

// Wix Page Code:

$w.onReady(function () {
  $w('#customElemenet').setAttribute("pageLoaded", true);
})
// Inside Custom Element

// Attributes keys that's listened for changes
static get observedAttributes() {
        return ["pageLoaded"];
}

attributeChangedCallback(name, oldValue, newValue) {
        if (name === "pageLoaded") {
            // fire anything you want here
        }
 }

connectedCallback (better)

Another way which is more simple and better I think; you can use built-in connectedCallback event inside custom elements.

connectedCallback() {
    // fire any code you want here (this event will be fired when custom elements loaded)
  };

I hope it helps. If you can solve the problem mark this as solution so thread will be marked as solved.

That’s very helpful thank you.

I was using the connectedCallback() event but thought that I had to use an addEventListener under that.

Now I see that I can just put the code I want under it without the event listener and it fires when the custom element loads.

FYI I tried the window.addEventListener(‘load’) and couldn’t even get a console log message to fire. Not sure why.


Edit: I spoke too soon. Running the code immediately below the connectedCallback() event does nicely trigger a console log, but doesn’t seem to let me use dispatchEvent to define the event to use in the page code.

I would love to be able to use the window.addEventListener(‘load’) option under connectedCallback(), but it’s not working!

I would love to be able to use the window.addEventListener(‘load’) option under connectedCallback(), but it’s not working!

This won’t work even if the listener attached because page is already loaded. And you also don’t need this. If you want to run some code when page loads/custom element loads you can run that code directly under connectedCallback()

OK so I put the code under connectedCallback():

connectedCallback() {  
    this.appendChild(createStyle());
    let container = createContainer();

    getMsReadyPromise().then(({ memberInfo }) => {
      let MSId = String(memberInfo.id)
      let MSName = memberInfo.name
      let MSEmail = memberInfo.email
      let MSImage = memberInfo.profileImageUrl

      this.dispatchEvent(new CustomEvent('pullMSId', { detail: { data: MSId } }));
      this.dispatchEvent(new CustomEvent('pullMSName', { detail: { data: MSName } }));
      this.dispatchEvent(new CustomEvent('pullMSEmail', { detail: { data: MSEmail } }));
      this.dispatchEvent(new CustomEvent('pullMSImage', { detail: { data: MSImage } }));

      });
    ...
}

with this on the page code:

$w.onReady(function () { 
	$w('#customElement').on('pullMSId',  ({ detail: { data } }) => {console.log('pullMSId triggered ' + data)})
...
})

…but it doesn’t work this way (maybe because the timing clashes with onReady?).
It DOES work if I simply use a “click” event listener (same page code):

connectedCallback() {  
    this.appendChild(createStyle());
    let container = createContainer();

    container.addEventListener('click', () => {
      getMsReadyPromise().then(({ memberInfo }) => {
      let MSId = String(memberInfo.id)
      let MSName = memberInfo.name
      let MSEmail = memberInfo.email
      let MSImage = memberInfo.profileImageUrl

      this.dispatchEvent(new CustomEvent('pullMSId', { detail: { data: MSId } }));
      this.dispatchEvent(new CustomEvent('pullMSName', { detail: { data: MSName } }));
      this.dispatchEvent(new CustomEvent('pullMSEmail', { detail: { data: MSEmail } }));
      this.dispatchEvent(new CustomEvent('pullMSImage', { detail: { data: MSImage } }));

      });
    })

But I want to be able to pull the Memberspace ID automatically rather than with a click.
FYI getMsReadyPromise() is from the Memberspace API as follows:

const getMsReadyPromise = () =>
  new Promise(resolve => {
    if (MemberSpace.ready) {
      // Ready event is already fired, so let's not wait for it, it will not be fired again
      resolve(window.MemberSpace.getMemberInfo());
      } 
    else {
      // MS widget is not yet ready, let's subscribe for the event
      const handleReady = ({ detail }) => {
        resolve(detail);
        // Unsubscribe ourselves, this allows GC to collect all related memory
        document.removeEventListener('MemberSpace.ready', handleReady);
        };
      // Listen to ready event
      document.addEventListener('MemberSpace.ready', handleReady);
      }
  });

It would be enough if I could just store the data in the memory. Please let me know if you have any suggestions!

Try to add a timeout and increase the timeout ms by 50ms each time.

setTimeout(() => {
   // your logic here
}, 50)

If it’s clashes with onReady this should fix it.

Thanks so much 50 ms worked!

I previously tried setTimeout but I put in a long duration >350ms thinking that the more I wait the better. But it seems 50 ms is catching it at just the right moment.

I wonder if this is going to be reliable, or might there be ebbs and flows depending on whether there’s a lag on either side?

Edit: For some pages, the mobile version isn’t working, for example. Is it possible to do a container.addEventListener for “onViewportEnter”?

You can try to check docs about elements for event listeners if onViewportEnter supported. I think 50ms will be ok but you can test on different devices/browsers to check it.

I’m doing some research but couldn’t find a list of event listeners. I’ll keep looking.

I thought your idea about using setAttribute was a great one - I tried it alone and with an onViewportEnter but for some reason it seems to not be hearing the signal on the custom element side.

The latest version I have is this:

class MemberspaceUp extends HTMLElement {
    constructor() {super();}
    static get observedAttributes() {
        return ["pageLoaded"];
        }
    attributeChangedCallback(name) {
        if (name === "pageLoaded") {console.log("page loaded")}
        }
    }
customElements.define('ms-up', MemberspaceUp);

with this page code:

$w.onReady(function () { 
	$w('#customElement').setAttribute("pageLoaded", false);console.log('pageLoaded false')
	$w("#msID").onClick(() => {
		$w('#customElement').setAttribute("pageLoaded", true);
		console.log('pageLoaded true')
		})
...

I get the console messages for the page code but not from the custom element side. It’s such a simple code that I’m surprised it’s not working.

I think the root problem is “timing” the execution of both logic, onReady() and the Custom Element.
When onReady() starts it’s logic the Custom Element is already rendered. It’s there for you to reference it but it’s still a HTMLElement (or whichever class you are extending).
Only after upgrade (throught define) the Custom Element will have your custom logic and respond to your choice of observedAttributes.
I had the same problem and found out it still a general problem with Custom Elements: to find when its upgrade will happen.
So sometimes onReady() happens before connectedCallback() other times after. You can see it from logging and reloading a page.
Timeout can solve it but demands fine tuning and I find it risky.
Solution I found:
Attributes changed before the upgrade stick after the upgrade.

  1. Begin onReady() setting some flag atribute:
	$w('#customElement').setAttribute( 'onreadystate', 'onreadyhasstarted' );
  1. In the Custom Element connectedCallback() I check for this atribute:
		if (this.getAttribute('onreadystate')=='onreadyhasstarted') 
			// I know onReady() has already started
		else 
			// I know this is happening before onReady()

Pay attention: any $w(‘#customElement’).on(‘event’) which is processed before the customElements.define() will not catch because it is referencing a #customElement which has not yet been upgraded to the Class you designed.

Thanks very much for this iSamurai.

I wanted to clarify, under the “else” statement, would you then put a setTimeout (although it seems this may defeat the purpose), or how would you deal with the fact that onReady hasn’t happened yet?

Maybe take your logic out of connectedCallback() into a doSomething() which will be triggered (attributeChangedCallback()) by the change in the attribute (in onReady()).

Thanks for the reply.

So I first tested the if/else you mentioned above:

connectedCallback() {  
    if (this.getAttribute('pageLoaded')== "pageLoaded") {
      getMsReadyPromise().then(({ memberInfo }) => {console.log("done");
      let MSId = String(memberInfo.id)
      let MSName = memberInfo.name
      let MSEmail = memberInfo.email
      let MSImage = memberInfo.profileImageUrl

      this.dispatchEvent(new CustomEvent('pullMSId', { detail: { data: MSId } }))
      this.dispatchEvent(new CustomEvent('pullMSName', { detail: { data: MSName } }));
      this.dispatchEvent(new CustomEvent('pullMSEmail', { detail: { data: MSEmail } }));
      this.dispatchEvent(new CustomEvent('pullMSImage', { detail: { data: MSImage } }));
        }); 
      }
    else {console.log ('no go')}
    }

…and encountered this problem: even though the “if” statement was able to catch the getAttribute every time, it wouldn’t fire my logic (the dispatchEvent doesn’t seem to work). If I remove the if/else and just have connectedCallback with a 50ms setTimeout, the logic fires.

I wonder if there’s another factor (other than onReady) that is affecting the timing issue?

Maybe the $w(‘#customElement’).on() has been processed before the upgrade of the Custom Element (define) thus being lost and not firing.
That is the worst situation. I am still looking for a solution to in onReady() verify if the Custom Element has upgraded because only then I can make use of $w(‘#customElement’).on().

Thanks for the input. For now, I think I’m going to deal with it using setTimeout but I look forward to possibly optimizing it.