Stimulus Discourse

Difference between webpack based setup and standalone js file


#1

I found a todoList app based on Stimulus at CodePen, but the problem is that it’s not the way typically Simulus apps are configured (the stimulus-starter blank slate). In this codepen the author has included the stimulus JavaScript file from the cdn and that’s why he enclosed the whole code in an IIFE like the Stimulus guide suggests.
What will I need to change in the code to make it work with the WebPack based setup?

I tried as follows (but failed).

1. todo.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>ToDo Example</title>
</head>
<body>
  <h2>Todo List with Stimulus</h2>
  <div data-controller="todoList">
    <div>Remaining : <span data-target="todoList.remaining"></span> / 
      <span data-target="todoList.todolength"></span></div>
    [ <a href="" data-action="todoList#archive">archive</a> ]
    <ul class="unstyled" data-target="todoList.dataList">
      <li data-done="true">Learn StimulusJS</li>
      <li data-done="false">Build a StimulusJS app</li>
    </ul>
    <form data-action="submit->todoList#addTodo">
      <input type="text" data-target="todoList.todoText" size="30"
         placeholder="add new todo here" >
      <input type="submit" class="btn-primary" value="Add Todo" />
    </form>
  </div>
</body>
</html>

2. todoList_controller.js

import { Controller } from "stimulus";

export default class extends Controller {
  static targets = ["todoText", "dataList", "remaining", "todolength"];

  calc_remaining() {
    var tmp_list = this.dataListTarget.children;
    var nb_remaining = 0;
    for (let i = 0, imax = tmp_list.length; i < imax; i++) {
      if (tmp_list[i].dataset["done"] === "false") {
        nb_remaining++;
      }
    }
    this.remainingTarget.textContent = nb_remaining;
    this.todolengthTarget.textContent = tmp_list.length;
  }

  archive(evt) {
    evt.preventDefault();
    var tmp_list = this.dataListTarget.children;
    // lecture à l'envers pour gérer les suppressions d'éléments
    // sans perte d'indice (children = HTMLCollection = NodeList "live")
    for (let i = tmp_list.length - 1; i >= 0; i--) {
      if (tmp_list[i].dataset["done"] === "true") {
        let parent = tmp_list[i].parentNode;
        parent.removeChild(tmp_list[i]);
      }
    }
    this.calc_remaining();
  }

  addTodo(evt) {
    evt.preventDefault();
    var input = this.todoTextTarget;
    if (input && input.value !== "") {
      var li = document.createElement("li");
      li.dataset["done"] = "false";
      li.innerHTML = this.getLiTemplate(input.value);
      this.dataListTarget.appendChild(li);
      this.todoTextTarget.value = "";
      this.calc_remaining();
    }
  }

  getLiTemplate(value, done = "false") {
    var checked = "";
    if (done === "true") {
      checked = "checked";
    }
    return `<label class="checkbox">
          <input type="checkbox" 
              data-action="todoList#execToggleCheck" ${checked}>
          <span>${value}</span>
        </label>`;
  }

  execToggleCheck(evt) {
    let nb_done = parseInt(this.remainingTarget.textContent);
    let parent = evt.target.parentNode.parentNode;
    if (parent.dataset["done"] === "false") {
      parent.dataset["done"] = "true";
      nb_done--;
    } else {
      parent.dataset["done"] = "false";
      nb_done++;
    }
    this.remainingTarget.textContent = nb_done;
  }

  addDynamicsOnLiItems() {
    var tmp_list = this.dataListTarget.children;
    for (let i = 0, imax = tmp_list.length; i < imax; i++) {
      let item = tmp_list[i];
      let done = item.dataset["done"];
      item.innerHTML = this.getLiTemplate(item.innerHTML, done);
    }
  }

  connect() {
    this.addDynamicsOnLiItems();
    this.todoTextTarget.focus();
    this.calc_remaining();
  }
}

#2

Your attempt was correct, you just forgot to include <script src="bundle.js" async></script> in the head.