Forward Thinking
Looking to the destination you always had in mind
From the command line of the to-do app you’ve been issuing commands which take things from a blank initial state
// ./main.js
const $state = $.atom({
view: "all",
next: 1,
todo: []
});
to something like this after a few actions:
{
view: "all",
next: 4,
todo: [
{id: 1, title: "Call mom", status: "completed"},
{id: 2, title: "Buy milk", status: "active"},
{id: 3, title: "Host movie night", status: "active"}
]
}
Once the core logic is fully developed, you’ll want to replace the command line interface with a full-featured graphical user interface, but before getting into that let’s talk about how you mentally map the state to the GUI and vice versa. This discussion involves HTML and your imagination.
One data model, many potential GUIs (and vice versa)
Looking at the state, what can we inferred?
The view
property implies multiple views. Seeing “all” implies unrestricted filtering. Looking to the statuses in the list of to-dos, we see “active” and “completed” are the other options.
How will the app facilitate view selection?
Maybe with a dropdown.
<select name="view">
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
Or a list of radio buttons.
<fieldset>
<legend>View:</legend>
<label><input type="radio" name="all" value="all" checked /> All</label>
<label><input type="radio" name="active" value="active" /> Active</label>
<label><input type="radio" name="all" value="all" /> Completed</label>
</fieldset>
Or something altogether custom.
Since this app has already been implemented, go ahead and interact with it a minute. Add some to-dos. Complete some. Then switch views.
Note how the app handles view selection. I listed 2 options, but the app demonstrates another. There are many ways this one facet of the interface might be implemented. You’re only limited by what you can imagine.
The next
property serves a purpose, but not a visible one. It’s a counter. It keeps the id
which will be assigned to the next to-do created.
The todo
property keeps an array of to-dos, the app’s most essential content.
A mere 3 properties provide the basis for the basic app you just demoed. But that interface is one implementation among many wildly different possibilities. Conversely, for any given GUI, there are different ways its data might be modeled.
Let’s briefly detour to see how an app gets onto the page.
Mounting components on a page
It starts which designating an element on the page to host the app. This is its root element.
ℹ️ Root element: It’s the element in the DOM which hosts an app. Everything the app is, is fully contained by it. It’s query selected and dynamically activated. In theory, multiple apps/components can be hosted on a single page.
Here that element is body
.
<!-- ./index.html -->
<body id='todo-app'>
</body>
Normally, some attribute or class is assigned to it to aid query selection. In this instance id
marks the element, but as there’s only one body
anyway it’s more for consistency than necessity.
// ./main.js
const el = document.getElementById("todo-app");
The reference to the root element in the main.js
module is the basis for wiring up the one-way data flow. When you’re writing an app, this simple approach works. But, at some point, you may wish to package the app as a component, making it more modular, reusable. Just don’t start here. Start simple, as above.
Apps are components
When an app will be hosted potentially multiple times on a page, or coexist with others, it’s better to treat the app as what it really is: a component. Relocate its guts from main.js
to another module which exports a mount
routine.
export function mount(el){
//activate the component using guts from `main.js`
}
It’s usually wise to accept an optional configuration object to permit fine tuning:
export function mount(el, config = {}){
//activate the component using guts from `main.js`
}
Once a component, main.js
imports it, finds its host element(s), and activates it (them). The change is minimal. Upon relocating its guts all main.js
had to do is orchestrate.
// ./main.js
import * as tc from "./todo-component.js";
import * as cc from "./calendar-component.js";
$.each(tc.mount, document.querySelectorAll("[data-component='todo']"));
$.each(cc.mount, document.querySelectorAll("[data-component='calendar']"));
Divide components into managable parts
Let’s take a closer look at the host element. Seeing this one is empty leads one to conclude the component must render the complete GUI.
<!-- ./index.html -->
<body id='todo-app'>
</body>
While this can work, non-trivial components are better divided into fixed parts in a predefined structure.
<!-- ./index.html -->
<body id='todo-app'>
<input class='title' type='text'/>
<fieldset>
<legend>View:</legend>
<label><input type="radio" name="all" value="all" checked /> All</label>
<label><input type="radio" name="active" value="active" /> Active</label>
<label><input type="radio" name="all" value="all" /> Completed</label>
</fieldset>
<div class='active'>
<h1>Active to-dos</h1>
<ul>
</ul>
</div>
<div class='completed'>
<h1>Completed to-dos</h1>
<ul>
</ul>
<button class='clear'>Clear All</button>
</div>
</body>
To facilitate this, it can be useful—in addition to keeping a reference to the root element—to also keep a reference to the fixtures.
// ./main.js
const fixtures = {
title: el.querySelector("input[type='text'].title"),
views: el.querySelectorAll("input[type='radio']"),
active: el.querySelector(".active ul"),
completed: el.querySelector(".completed ul"),
clear: el.querySelector(".clear")
}
The root element is never completely replaced; rather, the parts are independently updated. Having references to the fixtures aids this.
An alternate GUI for the same data model
Take a closer look at the HTML. You’ll notice a page structure whose GUI is different from the one demoed. In this one, there is a text entry field, top of screen, and a list on the left of active to-dos and a list on the right of completed to-dos, those placements assumedly handled by CSS.
Imagine it’s use.
You enter text into the title field, hit enter, and a to-do appears at the top of the active list. You click on the to-do in the active list and it’s bounced to the completed list. Click on the to-do in the completed list and it’s returned to the active list. This accounts for mistakes and corrections. That leaves the “Clear All” button to drop completed items from the list on the right.
If you toggle the views, “All” shows both lists, one on the left, one on the right and “Active” and “Completed” respectively show just the one list, hiding the other.
Now, this alternate vision relies on the same data model we started with:
// ./main.js
const $state = $.atom({
view: "all",
next: 1,
todo: []
});
This illustrates how that one model (and the commands which manipulate it) could be mapped to an altogether different GUI implementation.
It also reveals how certain aspects of the GUI required a piece of information from the model. For example, to toggle between views a view
property provides the necessary bookkeeping.
How the user interface looks and acts depends on the state kept in the data model. Realizing certain interfaces and behaviors depends on certain things being kept there. You have to envision what enables the activation logic to do its job. How that results in a GUI, how the user interacts with it, and how that translates to commands which transform the data model which, in turn, update the GUI. Develop the mental muscle to do this with small components and it’ll become less challenging with larger ones.
Knowing where you’re going before leaving the driveway
This all provides some perspective on what’s needed to start an app. The user interface, with the ultimate destination being a working app, correlates to a data model and the commands which reduce over it.
You were told to build a functional core, but apart from seeing the destination in your mind’s eye, you’re going to have difficulty getting there.
To build that effectively you have to be contemplating:
- how the GUI looks and acts
- the data the GUI depends on
- the commands which correlate to actions taken in the GUI
Yes, you start with simulation to flesh out a working core before standing up a GUI. But it depends on the working app and GUI being well contemplated.
ML