Vanilla patterns
This website demonstrates some of the common patterns for developing web applications using vanilla JavaScript. The term 'vanilla' is used to describe a plain version of something without adornments and additions. In the context of front end development 'vanilla JavaScript' refers to frameworkless development. The way 'vanilla' is defined by its practitioners ranges from using abstractions that replace frameworks to a more strict definition of not using non-application-specific abstractions. This website shows patterns that are more along the lines of the latter.
Specifically this means you will not find replacements for data binding, component libraries, template engines, and similar. You will instead find various ways to take advantage of features that the browsers offer, such as the template tag, ES modules, custom elements, constraints validation, form API...
If you would like to contribute to this collection or request some pattern, please feel free to file an issue on GitHub.
KISS—Keep it simple, stupid
Although this website showcases patterns, it is not intended to be a definitive guide on how to develop with vanilla JavaScript. The spirit of vanilla JavaScript development is about embracing the available APIs, and doing the simplest thing that works. Whether some approach described in this website is the simplest or not is technically not important. Carefully considering options, developing a feel for unnecessary complexity, and regularly pruning it—these are the guiding principles of the 'vanilla way'.
List of examples
- Using existing nodes
- Creating nodes
- Pre-rendered list
- Paginated list
- Sorted list
- Filtered list
- Form validation
- Click outside
- Carousel
- Drag order
- Button group
- Toggle switch
- Toasts
How to read the examples
Each example consists of three parts: HTML, CSS and JavaScript. These technologies are separate by design, and as vanillistas, we embrace this separation. Therefore, do not look at just the JavaScript. Look at the HTML and CSS as well. Some of the examples will use CSS where most developers resort to JavaScript, or rely on and augment the default HTML behaviors, and this is part of embracing the tools at our disposal.
The code base follows certain conventions that are repeated in all examples. To avoid repeatedly explaining them in each example, I will provide the explanations here:
-
Use the developer tools
You will find the developer tools a great help when viewing the examples. Because the code does not undergo any transformation whatsoever, you will have the complete unadulterated source code available to you in the dev tools. There is no need to open the code in your editor to see how it's actually written.
File names
The HTML file's name is used as the base for naming other files.
The CSS files use the same base name as the HTML file, and use a
.[media].css
double extension. The media is normally "screen" for web applications so forindex.html
, the CSS would be namedindex.screen.css
, but a printable web application, we might have anindex.print.css
. When linking these files, we use themedia
attribute on the<link>
tag, which must match the media in the file extension.JavaScript files normally use a
.client.js
double extension when there is just one file for the entire application. This is to differentiate it from a potential.server.js
file that we might have in a full-stack web application. JavaScript are linked to the HTML page using the standard<script>
tag, either using adefer
attribute (without a value) ortype="module"
. -
Placement of the
<script>
tagsThe
<script>
tags is placed in the<head>
tag, not the bottom of the<body>
. When using thedefer
ortype="module"
attribute, the JavaScript files are downloaded in parallel with parsing of the HTML. Therefore, we want to allow the browser to find them as soon as possible. The scripts are not executed until the document is parsed and DOM tree populated, so there is no need to place the scripts below page content.When using a build tool to bundle our sources, we should ensure that the tool outputs the
<script>
as described here. -
Use of ES modules
Since ES modules are supported in all major browsers, we use them for all JavaScript code. We do this even in cases where we are not importing or exporting any module members because a module also prevents variables from the module from becoming global.
An alternative to our use of modules would be to use the
defer
attribute on the<script>
tag, but then wrap the entire contents of the script file in a block:{ let someVariable = 'not going to leak into global scope' }
Here, we do not use the second method because it causes the code within the block to be indented in most editors, and those leading tabs/spaces increase the payload.
-
Variables starting with the
$
characterSome variables start with the dollar (
$
) character. These are variables that contain references to DOM nodes. Sometimes, a variable may be just a single dollar character, and that an "anonymous" node, usually used when iterating over a collection of nodes.Some variables start with two dollar characters (
$$
). This is not a reference to Vash the Stampede. We use this convention to denote variables that contain references to collections or arrays of DOM nodes. -
Mismatch between the
id
attribute and variable nameIn most cases, there is a mismatch between what the variable is named in the HTML (its
id
attribute) and how it is referred to in the code. For instance, an element on the page may have anid="clock"
, but the variable name may be$output
. We do this because HTMLid
attribute describes what the element is in the content structure, while the JavaScript variable name describes its role in the code. In JavaScript, the clock is an output of the code that calculates and formats the current time. This is a form of loose coupling: changing the role of the element on the page does not require us to update the variable name, and vice versa. -
Indent with tabs
Indenting with spaces uses more characters for indentation than tabs. Since we are not using any build tools, using tabs for indentation saves us characters.
-
Entry point at the top
In all examples, the entry point–the function which sets up the application–is called
start()
, and it is placed at the top. It is invoked as arequestIdleCallback()
callback.Entry point is placed at the top of the file because it makes it easier for us to orient ourselves when we first visit the code base. The entry point is followed by variables holding references to DOM nodes, and then by other functions ordered by distance from the entry point–those directly used in the entry point are closer to the entry point. We do not follow this rule literally, however. It's mostly about balance. If two functions are strongly related, they should be closer to each other.
License
This code is released under the terms of the WTFPL2 license. See the LICENSE file for details. You are free to copy any portion of the code and modify it as you wish for any purpose.
Copyright© 2023. Hajime Yamasaki Vukelic. Some rights reserved.