angularzonejsgrapesjssplidejs

Integrate Splidejs into Grapesjs: failing instanceof HTMLElement


I am trying to integrate Splidejs into a Grapesjs editor. When mounting splides, I get the Uncaught Error: [splide] A track/list element is missing.

After debugging, I realise that Splide does not find the required track or list HTML Element for mounting properly. However, they are present in the HTML of the Grapes component.

...
<div class="splide__track">
<ul class="splide__list">
...

The reason why Splide doesn't find them seems to be related to different HTMLElement base types, leading to my elements not to be recognized.

The test below (Splide 3.6.9) returns false

enter image description here

When investigating in Chrome dev tools console, the __proto__ chain evaluated by instanceof looks correct at first glance. However a closer look shows that subject has additionnal __zone_symbol__onxxx properties.

> subject.__proto__.__proto__
    HTMLElement {…}
        ...
        __zone_symbol__ononabortpatched: true
        __zone_symbol__ononanimationendpatched: true
        __zone_symbol__ononanimationiterationpatched: true
        ...
> HTMLElement.prototype
    HTMLElement {…}
        ...
        none of the __zone_symbol __onxxx present
        ...
> subject.__proto__.__proto__ == HTMLElement.prototype
    false

This could be explained by those two references:

(1) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof#instanceof_and_multiple_context_e.g._frames_or_windows

instanceof and multiple context (e.g. frames or windows)

Different scopes have different execution environments. This means that they have different built-ins (different global object, different constructors, etc.). This may result in unexpected results. For instance, [] instanceof window.frames[0].Array will return false,

(2) https://grapesjs.com/docs/modules/Components-js.html#important-caveat

Keep in mind that all component scripts are executed inside the iframe of the canvas (isolated, just like your final template), and therefore are NOT part of the current document.

I am suspecting that my Splide content gets enhanced by zone.js. Therefore I started creating the Grapes component outside of the Angular zone

this.zone.runOutsideAngular(() => {
      this.initGrapesEditor()
    })

but the error remains.

Do you have any hints on how I could fix this issue?


Solution

  • I have also reported the issue on grapesjs' github https://github.com/artf/grapesjs/discussions/4062

    and added two workarounds

    1. patching splidejs as follows (did not submit a PR). This allowed splidejs to be properly loaded by the script of my new component type.

    as in

    /**
     * Tests to see if the given TypeName appears anywhere
     * in the prototype chain of the given subject.
     * This is a lose version of the instanceof operator
     * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)
     * required when checking the type of elements that cross window and iframe bouderies.
     * 
     * @param subject 
     * @param typeName 
     * @returns `true` if 
     */
    function isInstanceOf(subject: any, typeName: string) {
      if (subject === null) {
        return false;
      }
      let p = subject.__proto__;
      while (p !== null) {
        if (p.constructor.name === typeName) {
          return true;
        }
        p = p.__proto__;
      }
      return false;
    }
    
    export function isHTMLElement( subject: unknown ): subject is HTMLElement {
      return isInstanceOf( subject, 'HTMLElement' );
    }
    
    export function isHTMLButtonElement( subject: unknown ): subject is HTMLButtonElement {
      return isInstanceOf( subject, 'HTMLButtonElement' );
    }
    
    1. replace Splide with Swiper. Swiper does not rely on instanceof HTMLElement out of the box, which simplifies my development.