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 ?
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.
template
targets for our HTML that we will use as a base to copy when inject.error
and item` target name for being able to easily remove any error or items that get added.results
target will house the results (item templates) that get injected.innerText
instead of innerHtml
for safety.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);
}
}
template
elements that let us set up any HTML we want for the error
and item
parts.template
elements have the target names that end in template
ul
always in the DOM but you can use css :empty
to just hide it by default if there are no items in it.<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>