How does a web component call functions define in other JS

I would like to integrate a web component jexcel-spreadsheet into my application following the guidance of the document.

  1. Download the template PaperSlider project and modify the souce code
package com.example.webcomponent;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;

@Tag("jexcel-spreadsheet")
@JsModule("jexcel/dist/jexcel.js")
@JsModule("jexcel/dist/jexcel.webcomponent.js")
@NpmPackage(value = "jexcel", version = "4.2.0")
@CssImport(value = "jexcel/dist/jexcel.css")

public class PaperSlider extends Component {

    public PaperSlider() {
    }

}
  1. Integrate the web component into a demo project
package com.example.ui;

import org.springframework.beans.factory.annotation.Autowired;

import com.codegene.webcomponent.PaperSlider;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route("jexcel")
@PageTitle("JExcel")
public class MainView extends VerticalLayout {

    private PaperSlider excel;

    @Autowired
    public MainView() {

        setSizeFull();
        setPadding(false);
        setSpacing(false);
        
        excel = new PaperSlider() ;
        add(new Label("Excel:"), excel);
    }
}

  1. Opne Chrome to access http://localhost:8080/jexcel, an error is displayed:
    Script error. (:0)

  2. Trace the the error and find it is caused by Line 30: var config = JSON.parse(o.innerHTML) at jexcel\dist\jexcel.webcomponent.js, as ‘o’ is web component jexcel-spreadsheet and there is no JSON string defined for this element right now:

class Jexcel extends HTMLElement {
    constructor() {
        super();
    }

    init(o) {
        // Shadow root
        const shadowRoot = this.attachShadow({mode: 'open'});

        // Style
        const cssJexcel = document.createElement('link');
        cssJexcel.rel = 'stylesheet';
        cssJexcel.type = 'text/css'
        cssJexcel.href = 'jexcel.css';
        shadowRoot.appendChild(cssJexcel);

        const cssJsuites = document.createElement('link');
        cssJsuites.rel = 'stylesheet';
        cssJsuites.type = 'text/css'
        cssJsuites.href = 'jsuites.css';
        shadowRoot.appendChild(cssJsuites);

        // Jexcel container
        var container = document.createElement('div'); 
        shadowRoot.appendChild(container);

        // Garantee all elements are rendered
        setTimeout(function() {
            // Parse JSON
            var config = JSON.parse(o.innerHTML);       // <--Line 30
            // Root
            config.root = shadowRoot;
            // Reset container
            o.innerHTML = '';
            // Create jexcel element
            jexcel(container, config);
        }, 0);
    }

    connectedCallback() {
        this.init(this);
    }

    disconnectedCallback() {
    }

    attributeChangedCallback() {
    }
}

window.customElements.define('jexcel-spreadsheet', Jexcel);
  1. Following the tips in \node_modules\jexcel\webcomponent-spreadsheet.md to replace jexcel.webcomponent.js
        // Garantee all elements are rendered
        setTimeout(function() {
            // Parse JSON
            var config = JSON.parse(o.innerHTML);
            // Root
            config.root = shadowRoot;
            // Reset container
            o.innerHTML = '';
            // Create jexcel element
            jexcel(container, config);
        }, 0);

with

        // Create jexcel element
        this.el = jexcel(container, {
            root: shadowRoot,
            minDimensions: [10,10]

        });
  1. Access http://localhost:8080/jexcel again, the error changes:
    ReferenceError: jexcel is not defined

  2. Function jexcel(…) is defined in File jexcel/dist/jexcel.js, which is imported by @JsModule(“jexcel/dist/jexcel.js”), and check file /target/frontend/generated-flow-imports.js, jexcel.js is also imported

...
import '@vaadin/vaadin-upload/theme/lumo/vaadin-upload.js';
import 'jexcel/dist/jexcel.js';
import 'jexcel/dist/jexcel.webcomponent.js';
import '@vaadin/flow-frontend/comboBoxConnector.js';
...
  1. Does anyone know what should be modified to let Function jexcel(…) available in jexcel.webcomponent.js?
  2. What’s the url for file jexcel.css (at node_modules/jexcel/dist) or jsuites.css (at node_modules/jsuites/dist), as they are both failed to load, or with file full path, e.g. ‘jsuites/dist/jsuites.css’:
        // Style
        const cssJexcel = document.createElement('link');
        cssJexcel.rel = 'stylesheet';
        cssJexcel.type = 'text/css'
        cssJexcel.href = 'jexcel.css';
        shadowRoot.appendChild(cssJexcel);

        const cssJsuites = document.createElement('link');
        cssJsuites.rel = 'stylesheet';
        cssJsuites.type = 'text/css'
        cssJsuites.href = 'jsuites.css';
        shadowRoot.appendChild(cssJsuites);

Hi,

I don’t know how the webcomponent is working but the configuration is read from the innerHTML, that’s why you have this error at the beginning. (Cannot parse json because the innerHtml is empty).

You can solve it by adding a configuration:

@Tag("jexcel-spreadsheet")
@JsModule("jexcel/dist/jexcel.js")
@JsModule("./src/jexcel.webcomponent.js")
@NpmPackage(value = "jexcel", version = "4.2.0")
@CssImport(value = "jexcel/dist/jexcel.css")
public class JExcel extends Div
{

    public JExcel() {
        setText("{\n" +
                "    \"minDimensions\": [10,10]
,\n" +
                "    \"columns\": [\n" +
                "        {\n" +
                "            \"width\": \"200px\",\n" +
                "            \"title\": \"Name\"\n" +
                "        }\n" +
                "    ]\n" +
                "}");
    }
}

I also changed the annotation JSModule(./src/jexcel.webcomponent.js") to read a copy of jexcel.webcomponent.js. I copied it in the frontend/src folder of the project and update few things:

  • the jexcel module is imported in the webcomponent (so it’s not unknown)
  • the css is now imported of jexcel and jsuites and added in the shadow root
import jexcel from "jexcel";
import $jexcelcss from "jexcel/dist/jexcel.css";
import $cssJsuites from 'jsuites/dist/jsuites.css';

class Jexcel extends HTMLElement {
    constructor() {
        super();
    }

    init(o) {
        // Shadow root
        const shadowRoot = this.attachShadow({mode: 'open'});

        const cssJexcel = document.createElement('style');
        cssJexcel.innerHTML = $jexcelcss;

        shadowRoot.appendChild(cssJexcel);

        const cssJsuites = document.createElement('style');
        cssJsuites.innerHTML = $cssJsuites;

        // Jexcel container
        var container = document.createElement('div'); 
        shadowRoot.appendChild(container);

        // Garantee all elements are rendered
        setTimeout(function() {
            // Parse JSON
            var config = JSON.parse(o.innerHTML);
            // Root
            config.root = shadowRoot;
            // Reset container
            o.innerHTML = '';
            // Create jexcel element
            jexcel(container, config);
        }, 0);
    }

    connectedCallback() {
        this.init(this);
    }

    disconnectedCallback() {
    }

    attributeChangedCallback() {
    }
}

window.customElements.define('jexcel-spreadsheet', Jexcel);

I didn’t try to update the component but it requires a better way to manage the configuration, as a property for example.

I hope this will help you.

I didn’t explain why jexcel was not defined even if it was imported in an other JavaScript file.

it’s because every JavaScript file is a jsmodule. if you import one element in one jsmodule, it won’t be imported in another one. you can find some explanation here: https://javascript.info/modules-intro

Great help indeed, Thank you Jean-Christophe! It is possible for me to continue next steps: Setting and Reading Properties and Listening to Events

In your case, you will have to modify the webcomponent to set/read some properties and listening some events because the jexcel web component does not have properties or events.

You can find some documentation to update/create a web component:

I don’t think we have some documentation for HTMLElement.