javascripthtmlstimulusjs

Stimulus build html element from rest API


Hello I have a stimulus code into my symfony project. This code is calling a rest API which taking around 3 seconds to provide the response. This rest api return JSON.

This is my code :

import {Controller} from "@hotwired/stimulus";
import axios from "axios";


export default class extends Controller {
    static values = {
        url: String
    }

    connect() {
        axios.get(this.urlValue)
            .then((r) => {
                if (r.data !== null) {
                    let html
                    const tmp = JSON.parse(r.data)
                    if (tmp === null) {
                        html = document.createElement("div")
                        html.classList.add("alert", "alert-danger", "alert-dismissible", "fade", "show")
                        html.innerHTML += "Asset Number Not Valid";
                        html.innerHTML += "<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>"
                    } else {
                        html = document.createElement("ul")
                        html.classList.add("list-group")
                        for(let key in tmp) {
                            html.innerHTML += "<li class=\"list-group-item\">" + key + " : " + tmp[key] + "</li>";
                        }
                        html.innerHTML += "</ul>";
                    }
                    this.element.replaceWith(html);
                }
            })
    }
}

As you can see, it's building a list or display an error. This code is really simple and works well. I just don't like how html is build.

Do you have any other/cleaner way ?


Solution

  • Here is a different approach, basically leveraging the HTML Template element to move all of your HTML back into the HTML file.

    When you are building too much HTML in your Stimulus controller it can get messy, instead think about the power of targets as being able to target any element including templates and even things you add dynamically.

    Controller

    import { Controller } from '@hotwired/stimulus';
    
    export default class extends Controller {
      static targets = [
        'error',
        'errorTemplate',
        'item',
        'itemTemplate',
        'results',
      ];
    
      static values = {
        url: String,
      };
    
      connect() {
        this.clearError();
        this.clearResults();
    
        fetch(/* or axios */)
          .then(/* json parsing etc */)
          .then((items) => {
            items.forEach((value) => {
              this.addResultItem(value);
            });
          })
          .catch((error) => {
            // be sure to handle errors using nice promise like thing
            this.showError(error);
          });
      }
    
      clearError() {
        this.errorTarget && this.errorTarget.remove();
      }
    
      clearResults() {
        this.resultItemTargets.forEach((itemElement) => {
          itemElement.remove();
        });
      }
    
      showError() {
        const alert =
          this.errorTemplateTarget.content.firstElementChild.cloneNode(true);
        this.prepend(alert);
      }
    
      addResultItem(value) {
        const item =
          this.itemTemplateTarget.content.firstElementChild.cloneNode(true);
        item.innerText = value;
        this.resultsTarget.append(item);
      }
    }
    

    HTML

    <section class="container" data-controller="results"
      data-results-url-value="https://myapi">
      <template data-results-target="itemTemplate">
        <li class="list-group-item" data-results-target="item">__VALUE__</li>
      </template>
      <template data-results-target="errorTemplate">
        <div data-results-target="error" class="alert alert-danger
          alert-dismissible fade show" role="alert">
          <!-- remember accessibility - use proper titles & role=alert when putting things in DOM -->
          <button type="button" class="btn-close" data-bs-dismiss="alert"
            aria-label="Close"></button>
          <h3>Asset Number Not Valid</h3>
          <p>Details about the thing</p>
        </div>
      </template>
      <!-- use css to hide if :empty -->
      <ul class="list-group" data-results-target="results">
      </ul>
    </section>
    

    Helpful links