This tutorial is inspired by a wonderful video by Fireship where the same todo application is built using ten different frameworks.
Check out the working code for this example on the live WASL Components Demo website.
Before we get started with more Components, we'll need to determine how the information of our application should flow.
In general, we will input our todo item into an input
tag. When we're ready to submit, we'll press a button
that send this new item to the UI list
to be displayed and the LocalStorage API to store
as "todos" for later page reloads. When the page is reloaded, then, the LocalStorage API should load
the previous "todos" into the list
.
Translated into a WASL file, our application looks something like this:
{
"components": {
"input": {
"src": "./components/ui/input.js"
},
"button": {
"attributes": {
"innerHTML": "Add Todo",
"type": "submit"
},
"src": "./components/ui/button.js"
},
"list": {
"src": "./components/ui/ul.js"
},
"store": {
"src": "./components/storage/local/set.js"
},
"load": {
"arguments": {
"key": "todos"
},
"src": "./components/storage/local/get.js"
}
},
"listeners": {
"input": {
"button": true
},
"button": {
"list": true
},
"list": {
"store": true
},
"load": {
"list": true
},
}
}
We can use the button
Component we'd specified in the previous tutorial—but otherwise we'll need to define more Components from scratch!
The input
Component is going to be relatively similar to the button
component—although external inputs are going to reset the current input value before being passed on.
export const tagName = 'input' // Specify the HTML Element
export const attributes = {
oninput: function (ev) {
this.run({value: ev.target.value, _internal: true}) // Activate with new inputs
}
}
export default function (input){
if (input?._internal) return input.value // Pass input to linked Components
else {
this.element.value = input // Set the value of the input
return input // Pass to linked Components
}
}
The list
Component, on the other hand, is going to accumulate inputs, both on the DOM and inside an Array that is passed to linked Components.
export const tagName = 'ul' // Specify the HTML Element
export const items = [] // Maintain a list of items
export default function (...args) {
const inputs = args.flat() // Treat all arguments as single list items
inputs.forEach(input => {
if (typeof input === 'string'){ // Only allow strings
const li = document.createElement('li')
li.innerText = input
this.element.appendChild(li) // Add list item to the DOM
items.push(input) // Record new list item in the Array
}
})
return [items]
}
The store
Component will require a basic call set the item in storage using the LocalStorage API.
export default (value, key='todos') => {
const string = JSON.stringify(value) // Parse input into a string.
localStorage.setItem(key, string) // Store string in Local Storage.
}
Similarly, the load
Component will get the item from storage and parse it into a JavaScript object.
export default (key='todos') => {
let item = localStorage.getItem(key) // Get key from local storage.
item = JSON.parse(item) // Parse the string into a JavaScript Object.
return (item === null) ? undefined : item // Return values that are not null
}
Now that you've completed all of your Components, you should be able to run the application and create your own todo lists. Huzzah!
In our next tutorial, we'll see how what you've learned will generalize to real-time signals (coming soon…).