Use Stimulus to Better Organize Javascript Code in Django

Table of Contents

Django ChatGPT Tutorial Series:

  1. Introduction
  2. Create Django Project with Modern Frontend Tooling
  3. Create Chat App
  4. Partial Form Submission With Turbo Frame
  5. Use Turbo Stream To Manipulate DOM Elements
  6. Send Turbo Stream Over Websocket
  7. Using OpenAI Streaming API With Celery
  8. Use Stimulus to Better Organize Javascript Code in Django
  9. Use Stimulus to Render Markdown and Highlight Code Block
  10. Use Stimulus to Improve UX of Message Form
  11. Source Code chatgpt-django-project

In this article, we will learn how to use Stimulus to organize Javascript code in Django project.

Introduction

Like Turbo, Stimulus is also part of the Hotwire project, and it works very well with Turbo.

Stimulus is a JavaScript framework with modest ambitions. Unlike other front-end frameworks, Stimulus is designed to enhance static or server-rendered HTML—the “HTML you already have”—by connecting JavaScript objects to elements on the page using simple annotations.

We can:

  1. Use the classic dev frameworks such as Django, Rails to render HTML.
  2. Use Stimulus to attach JS to the DOM elements (handle events, do DOM manipulation).

Install Stimulus

$ npm install --save-exact @hotwired/[email protected]

Here we install @hotwired/[email protected] (which is the latest version when I write this chapter), we use --save-exact to pin the version here to make the readers easy to troubleshoot in some cases.

If we check package.json

"dependencies": {
    "@hotwired/stimulus": "3.2.2",
}

First Controller

In the previous chapter, we write some Javascript code to setup Websocket as source for Turbo Stream, let's rebuild this feature with Stimulus.

We will put all Stimulus controllers under the controllers directory.

Create frontend/src/controllers/websocket_controller.js

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

export default class extends Controller {

  connect() {
    const url = this.element.dataset.url;
    this.source = new WebSocket(
      (location.protocol === "https:" ? "wss" : "ws") +
        "://" +
        window.location.host +
        url
    );
    window.Turbo.connectStreamSource(this.source);
  }

  disconnect() {
    if (this.source) {
      window.Turbo.disconnectStreamSource(this.source);
      this.source.close();
      this.source = null;
    }
  }
}
  1. Stimulus controller is a ES6 class, we export it as default.
  2. We will talk more about this Stimulus controller in the next section.

Register Controller

Update frontend/src/application/app.js

// This is the scss entry file
import "../styles/index.scss";

import { Application } from "@hotwired/stimulus";
import websocketController from "../controllers/websocket_controller";

window.Stimulus = Application.start();
window.Stimulus.register("websocket", websocketController);

Notes:

  1. We import websocketController and then register it using Stimulus.register method.

Template

Update chatgpt_django_app/templates/message_list_page.html, remove the previous Websocket javascript code, and use below code

<div data-controller="websocket"
     data-url="/ws/turbo_stream/chat_{{ view.kwargs.chat_pk }}/"
></div>

Notes:

Stimulus continuously monitors the page waiting for HTML data-controller attributes to appear. For each attribute, Stimulus looks at the attribute’s value to find a corresponding controller class, creates a new instance of that class, and connects it to the element.

  1. Stimulus detects elements which have data-controller attribute.
  2. Then it creates a new instance of the controller class (websocket controller in this case), and connect it to the DOM element.
  3. The controller's connect method will run, just like the __init__ method of a Python class.
  4. this.element is reference to the connected DOM element, this.element.dataset.url can let us read the data property url of the DOM element.

Now if we test the page, we will see the websocket is still working.

Putting Javascript code in Stimulus controller can make it easy to reuse in other places, and we can even build a custom Django templatetag for it.

This make things more organized, compared with writing some Javascript code in the Django template file.

AutoLoading Controllers

If we have dozens of Stimulus controllers, we need to register them one by one in app.js, it is tedious, let's solve this problem.

$ npm install @hotwired/stimulus-webpack-helpers

Update frontend/src/application/app.js

// This is the scss entry file
import "../styles/index.scss";

import { Application } from "@hotwired/stimulus";
import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers";

window.Stimulus = Application.start();
const context = require.context("../controllers", true, /\.js$/);
window.Stimulus.load(definitionsFromContext(context));

With require.context (brought by Webpack), we pass the root directory of the controllers, and definitionsFromContext helps us generate controller names from the path and register them.

With stimulus-webpack-helpers, we do not need to manually register the controllers anymore, just put them under the controllers directory, and they will be registered automatically.

The identifier of the controller JS file will determine the controller file name, and below are some examples

  1. clipboard_controller.js -> clipboard
  2. hello_world_controller.js -> hello-world
  3. users/list_item_controller.js -> users--list-item

Turbo

In the previous chapter, we import Turbo by using <script src="https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/dist/turbo.es2017-umd.js"></script>, let's change it.

Next, we will install Turbo via the npm command.

$ npm install --save-exact @hotwired/[email protected]

Update frontend/src/application/app.js to add import "@hotwired/turbo";

// This is the scss entry file
import "../styles/index.scss";

import "@hotwired/turbo";             // new
import { Application } from "@hotwired/stimulus";
import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers";

window.Stimulus = Application.start();
const context = require.context("../controllers", true, /\.js$/);
window.Stimulus.load(definitionsFromContext(context));

Then remove the Turbo CDN link from the chatgpt_django_app/templates/base.html

Conclusion

In this article, we have used Stimulus to create a Websocket controller for us, the controller is responsible for connecting to the websocket and setup Turbo Stream as the source.

We can put Javascript logic code in Stimulus controllers and reuse them in other places, this make things more organized.

And we learned to import Turbo via npm package, instead of using CDN links in templates, I will talk about the benefits in later chapter.

Django ChatGPT Tutorial Series:

  1. Introduction
  2. Create Django Project with Modern Frontend Tooling
  3. Create Chat App
  4. Partial Form Submission With Turbo Frame
  5. Use Turbo Stream To Manipulate DOM Elements
  6. Send Turbo Stream Over Websocket
  7. Using OpenAI Streaming API With Celery
  8. Use Stimulus to Better Organize Javascript Code in Django
  9. Use Stimulus to Render Markdown and Highlight Code Block
  10. Use Stimulus to Improve UX of Message Form
  11. Source Code chatgpt-django-project
Launch Products Faster with Django

Unlock the power of Django combined with Hotwire through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer who loves writing code, tutorials about Django, and modern frontend techs.

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer