Developing a comoponent with SolidTemplateElement

What is SolidTemplateElement?

It is a class that can extend your component in order to purify your code of all useless complexity so that you can concentrate on the essential: your functionality.

Warning

To start this tutorial, you should have followed this first part.

What are we going to do?

For the example, we are gonna make a simple FAQ component. It just displays questions and answers in an accordion and allows the user to submit a new question.

Something like this:

A FAQ component as example

Let’s suppose we want to make the recipient’s email customizable. To obtain this rendering, it would be enough to implement in our html page a component that looked like this:

<solid-faq
    data-src="https://api.startinblox.com/faqs/"
    recipient-email="alice@startinblox.com"
></solid-faq>

Note

Remember the ``data-src`` attribute ? This attribute hosts the data source you want to interact with in this component.

Note

Want to learn more about web components ? We recommend this introduction.

Let’s start :)

1. Set the base of you component in a solid-faq.js file

Create a component that extends SolidTemplateElement. Here is the minimum code your component must contain that extends SolidTemplateElement.

/**
* solid-faq.js
*/
// Import SolidTemplateElement
import SolidTemplateElement from "https://unpkg.com/@startinblox/core@0.10/dist/solid-template-element.js";

// Name your component and extend SolidTemplateElement
export class SolidFAQ extends SolidTemplateElement {

        constructor() {
        super();
    }

    // Define the attributes you want
    static get propsDefinition() {
        return {
        dataSrc: "data-src",
        recipientEmail: "recipient-email",
        };
    }

    // Pass your attributes to your template
    template({ dataSrc, recipientEmail }) {
        // If we have no data sources, we display nothing
        if (!dataSrc) return "";
        let tmpl = `
            <solid-display
                data-src="${dataSrc}"
                fields="question, answer"
                id="faq"
            ></solid-display>
            `;
        // Otherwise, set the possibility to submit a question
        if (recipientEmail) {
        tmpl += `
            <a href='mailto:${recipientEmail}?subject=A%20new%20question%20for%20the%20FAQ&body=Hi!'>
                Your question question not here ?
            </a>
        `;
        }
        return tmpl;
    }
}

customElements.define("solid-faq", SolidFAQ);

2. Pay attention to propsDefinition method

You are going to set your attribute in this method. recipientEmail is the parameter where we are going to fill in the email of our recipient.

static get propsDefinition() {
    return {
        dataSrc: 'data-src',
        recipientEmail: 'recipient-email'
    }
}

Note

Note the syntaxe convention => recipientEmail: ‘recipient-email’

3. Let’s focus on the template

The template contains the HTML you want to render. Pass your attributes to your template and write it. To display the questions and answers, we are going to use solid-display. The attributes fields is used to define which datas you want to display from your data source.

We add a conditional rendering: if no email is filled in, the possibility to submit a question is not displayed.

// Pass your attributes to your template
template({ dataSrc, recipientEmail }) {
    // If we have no data sources, we display nothing
    if (!dataSrc) return "";
    let tmpl = `
        <solid-display
            data-src="${dataSrc}"
            fields="question, answer"
            id="faq"
        ></solid-display>
        `;
    // Otherwise, set the possibility to submit a question
    if (recipientEmail) {
    tmpl += `
        <a href='mailto:${recipientEmail}?subject=A%20new%20question%20for%20the%20FAQ&body=Hi!'>
            Your question question not here ?
        </a>
    `;
    }
    return tmpl;
}

4. Set fake datas

Creating data sources is quite another matter. For the example, we will use static data in JSON-LD.

Create a file named data-faq.jsonld at the root of your project and put these datas:

{
    "@id": "",
    "@type": "ldp:Container",
    "ldp:contains": [
        {
            "question": "What is Startin'blox ?",
            "answer": "A cooperative and a technology to build the web of our dreams",
            "@id": "questions-1",
            "permissions": []
        },
        {
            "question": "What is the SOLID project ?",
            "answer": "A set of standards that allows web applications to all speak the same language and become interoperable.",
            "@id": "questions-2",
            "permissions": []
        }
    ],
    "permissions": [],
    "@context": "https://cdn.happy-dev.fr/owl/hdcontext.jsonld"
}

Note

If you want to know more about how our API looks like, have a look to our SOLID introduction.

5. Implement your component

Import the script of your component and set the component in your index.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!--Import a custom font-->
    <link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet">
    <!--Import the framework-->
    <script type="module" src="https://unpkg.com/@startinblox/core"></script>
    <!--Import the component-->
    <script type="module" src="/solid-faq.js"></script>
    <title>Solid FAQ Demo</title>
</head>
<body>
    <h1>Solid FAQ Demo</h1>
    <!--Import the component-->
    <solid-faq
        data-src="data-faq.jsonld"
        recipient-email="alice@startinblox.com"
    ></solid-faq>
</body>
</html>

6. Test your component

npm run serve

You should be able to display your data but at the moment it’s a bit ugly. Let’s add some style.

4. Implement JS and CSS in your component

Create a js file, like /js/main.js.

Add the JS you need to make your accordion work, like this:

/**
* js/main.js
*/
// Select the component
var component = document.getElementById("faq");

// We use populate event to detect when the component is generated
component.addEventListener("populate", (event) => {

    // Seclect each question
    var acc = document.querySelectorAll("solid-display-value[name='question']");
    var i;

    for (i = 0; i < acc.length; i++) {
        //For each question, if we click..
        acc[i].addEventListener("click", function () {
        // Add or remove the "active" class
        this.classList.toggle("active");

        // Select the answer just below the question
        var panel = this.nextElementSibling;
        // If the answer is opened, then close it
        if (panel.style.maxHeight) {
            panel.style.maxHeight = null;
        // Otherwise, open it.
        } else {
            panel.style.maxHeight = panel.scrollHeight + "px";
        }
        });
    }
})

Note

Did you notice the populate event? It’s an event that allows you to trigger javascript only after the component you want to manipulate has been generated. See the Event documentation for more explanation.

Here is the CSS used for the demo:

/**
* css/main.css
*/

body {
    font-family: 'Montserrat', sans-serif;
    background-color: #4475B8;
}
h1{
    color : white;
}

solid-faq {
    max-width : 700px;
}

solid-display-value[name='question'] {
    cursor: pointer;
    padding: 18px;
    text-align: left;
    border: none;
    outline: none;
    transition: 0.4s;
    display : block;
    background-color: white;
    color : #4475B8;
}

solid-display-value[name='answer'] {
    padding: 0 30px;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease-out;
    display : block;
    background-color : #ECECEC;
    color : #414141;
    border : 1px #FDD17A solid;
    line-height: 30px;
}

solid-display-value[name='question']:after {
    content: '\02795';
    font-size: 13px;
    float: right;
    margin-left: 5px;
}

solid-display-value[name='question'].active:after {
    content: "\2796";
}

solid-faq solid-display+a{
    color : white;
    line-height : 50px ;
}

Use Helper functions

SiB framework provides you helpers functions to add JS and CSS in your component.

Add at the begin of your solid-faq.js, import your JS and CSS with those functions:

...

// Import Helper functions
import {
importCSS,
importJS,
} from "https://unpkg.com/@startinblox/core@0.10/dist/libs/helpers.js";

// Use the Helpers functions
importJS(`./js/main.js`);
importCSS(`/css/main.css`);

export class SolidFAQ extends SolidTemplateElement {

...

It should be better now, no ?

A FAQ component as example

7. Translate your component

Warning

This part is not working and need improvement. You can jump to the step 8 :)

To translate the static strings of your components, follow these steps:

  • In your component, create a folder which contains all the translation files. You can name it locales for example. Inside, create one file per language, with the structure [code_language].json, for example: fr.json.

  • In each file, add one line per string to translate. Your file should look like this:

{
    "label.question": "Your question is not here ?"
}
  • In the constructor of your component, define the path of your folder:

// For the demo
const base_url = "./";
// The production url of your component =>
//const base_url = "https://path/to/your/component";

export class SolidFAQ extends SolidTemplateElement {

    constructor() {
        ...

        this.setTranslationsPath(`${base_url}/locales`);
    }

...

}
  • Use the localize method to show the translated strings in your template:

const base_url = "https://unpkg.com/@startinblox/solid-faq"; // url of your component

export class SolidFAQ extends SolidTemplateElement {

    ...

    template( { dataSrc, recipientEmail } ) {
        if (!dataSrc) return '';
        let tmpl = `
            ...
            <a href='mailto:p.${recipientEmail}?subject=A%20new%20question%20for%20the%20FAQ&body=Hi!'>
                ${this.localize('label.question')}
            </a>
        `;
        return tmpl
    }
}

As a developer who uses a component, you can also add you own translation files by targeting you translation folder. Like for the component, this folder should contain one file per language:

<solid-conversation
    data-src="./data/conversations.jsonld"
    extra-translations-path="http://my_app/locales"
></solid-conversation>

8. Does it work well?

A FAQ component as example

If you get any trouble (or any idea to improve!), please mail me :)

Note

Document your component!

Each time you create a component, remember to document it with a README.md, especially the version of the core with which it was developed. If you create a component by default you will be considered as the maintainer. This means that you are responsible for its future updates but also that you could get support contracts on this component in the future :)

Releases are posted here. Subscribe to the newsletter not to miss the next ones!

Warning

This tutorial could be improved by adding accessibility.

Go Deeper

Discover other features of the framework playing with the demo on the website.