Django ChatGPT Tutorial Series:
- Introduction
- Create Django Project with Modern Frontend Tooling
- Create Chat App
- Partial Form Submission With Turbo Frame
- Use Turbo Stream To Manipulate DOM Elements
- Send Turbo Stream Over Websocket
- Using OpenAI Streaming API With Celery
- Use Stimulus to Better Organize Javascript Code in Django
- Use Stimulus to Render Markdown and Highlight Code Block
- Use Stimulus to Improve UX of Message Form
- 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:
- Use the classic dev frameworks such as Django, Rails to render HTML.
- 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;
}
}
}
- Stimulus controller is a ES6 class, we export it as default.
- 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:
- We import
websocketController
and thenregister
it usingStimulus.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.
- Stimulus detects elements which have
data-controller
attribute. - Then it creates a new instance of the controller class (
websocket
controller in this case), and connect it to the DOM element. - The controller's
connect
method will run, just like the__init__
method of a Python class. this.element
is reference to the connected DOM element,this.element.dataset.url
can let us read the data propertyurl
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
clipboard_controller.js -> clipboard
hello_world_controller.js -> hello-world
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:
- Introduction
- Create Django Project with Modern Frontend Tooling
- Create Chat App
- Partial Form Submission With Turbo Frame
- Use Turbo Stream To Manipulate DOM Elements
- Send Turbo Stream Over Websocket
- Using OpenAI Streaming API With Celery
- Use Stimulus to Better Organize Javascript Code in Django
- Use Stimulus to Render Markdown and Highlight Code Block
- Use Stimulus to Improve UX of Message Form
- Source Code chatgpt-django-project