Coding a dynamic todo-list in pure Typescript (no frameworks).
Introducing the Axino Widget model, Channels and state handling.
Live demo of the app:
Why Axino Widgets?
Getting started with Axino:
Axino-related concepts introduced in this tutorial:
- Composition (Widgets: Composing the elementary components into reusable components or Widgets).
- Modularity (with Typescript modules).
- Channels (Golang-inspired async inter-widgets communication).
Coding a TODO-list with the Axino Lib
Step 1: A frame for the app:
A basic frame for the app consists of an index.html page to hold the app on the web and an app.ts file which holds the code of the app:
The standard index.html file for an Axino app is coded as follows:
It points to the app.ts file:
In order to display a list of tasks, we need :
- A widget which embodies a frame, a task list.
- A widget that represents a single task to appear in the tasks list.
In Axino, Widgets are implemented as Typescript classes inheriting from the Widget class. An elementary task can be coded as:
The file taks.ts represents an elementary task, coded as a div (itself in a wrapper div, for purely aesthetic reasons),
// Create inner Div:
this.taskDiv = new Div(text);
It is styled so as to be editable (like a textArea, for instance):
NB: The styling of the elements is done from Typescript rather than via a CSS, in agreement with the Axino approach, but a CSS can be used instead if the developer so chooses (in that case, the CSS file has to be linked to the app via the index.html page, as illustrated on the 1st example of this article).
A “check button” and a “task delete button” are further provided for the widget:
this.checkButton = new CheckBox(); this.checkButton.Margin("10px"); this.checkButton.appendTo(this.wrapperDiv);// Initially checked:
// Delete task button:
this.deleteButton = new Button("-"); this.deleteButton.Margin("10px"); this.deleteButton.appendTo(this.wrapperDiv);
This creates the following element of the app’s interface:
At startup of the demo app, two dummy tasks are created and inserted into the tasks list:
which results in:
The Channels objects appearing in the constructors of the Task objects enable the task widgets to communicate asynchronously with the other Widgets which make up the app (in a manner which we will illustrate below).
Step 3: the task list Widget
In a manner similar to the task Widget, the TaskList Widget is coded by extending the Widget Class, and adding the Button components, Div components, and the corresponding event-handling methods which make up the TaskList Widget:
In the examples, above we have illustrated the use of modularity and composition :
- Composition is the ability to group elementary components (buttons, Div, etc…) into reusable components, such as the Task Widget and the TaskList Widget with a scriptable style and behavior.
- Modularity is the ability to separate the code of the app into separate .ts modules which are each responsible for a different aspect of the apps (for instance each Widget can if deemed useful be coded into its own file thereby increasing the readability of the code).
- Further, if deemed useful, the behavior part of the code of a Widget can itself be stored in its own dedicated file.
- For illustration purposes, in the case of the TaskList Widget, style and behavior of the Widget are stored into one single short file (see example TakList.ts above).
- Alternatively, in the case of the Task Widget, the behavior of the Widget has been stored in a separate file: the task_action.ts file as an example of “separation of concerns”:
The TaskAction class extends the Tasks class and provides behavior to the elements of the Task Widget.
Step 4: app.ts: the main module of the app: Channels and State.
The global behavior of the app, the handling of the events and data held by the Widgets, and the handling of the resulting global state of the app is managed by the main module of the app, the app.ts module:
Channels are declared as follows:
//================================================= // Channels for transmitting events between widgets: //================================================== const channel_checked = new Channel("check"); const channel_delete = new Channel("delete"); const channel_add = new Channel("add");
They are used to listen to specific events occurring in the ancillary Widgets of the app (Task and TasKList) as follows :
The meaning of this code snippet is as follows:
When one clicks on the “delete” button element of a Task Widget, an event and the associated data is send down the delete Channel (TaksAction module), subsequently the channel_delete Channel in the app.ts module receives the corresponding event/data which is handled in the callback provided as an argument to the channel_delete.listen() method of the channel_delete Channel in the app.ts module.
Step 5: Global state of the app
Recap: So far we have seen how to:
- Build the code of our app in a modular fashion using Typescript modules.
- Build reusable components using the Widget class of the Axino Lib.
- Enable async communication to/from the Widgets in our app using Channels (the Channel class).
Where does the State of the app reside?
Since the app.ts module of our app manages all the data/events of all its components/Widgets (via Channels), it is up to the developer to decide which data (which set of variable(s) of the app.ts module) gives an accurate representation of the state of our task list (our TODO list).
In this case, the state of our list of tasks is held in the all_tasks object of the app (a Vector of Task objects).
// State :
let all_tasks = new Vector<TaskAction>();
let checked_tasks = new Vector<TaskAction>(); let todo_tasks = new Vector<TaskAction>();
Therefore, in this particular instance, the Vector of Tasks: all_tasks *is* the state of the app as illustrated below:
Code of the example:
The complete code of the app is available here: