Valaa Tutorial: Simple To-Do
Collaborative task manager with 63 lines of code.
This is the second Valaa tutorial post in. I go quite a bit faster in this, so I recommend checking out the first Hello, World! tutorial before continuing.
Valaa is designed to make the development of real-time and multi-user applications as smooth as possible. In this tutorial we will create a simple to-do application that supports multiple users and allows them to share tasks with each other.
Application Structure
We again start by using the Valaa IDE to visually map out our project structure.
Project root
In our project root we create two Entities: client and tasks. We will later instance the client to the user’s local partition to achieve independent UI state. For that we also need two more Medias: partition.vsx with a LENS property pointing to it and a createLocalPartition.vs with an accessor property.
Client
The client Entity should have two properties, user set to null
for the username and hideCompleted set to false
. Here we need to add two more Medias: For the user interface we create ui.vsx with a LENS property pointing to it. For styles we create styles.css with an accessor property. A property tasks should be created and set to point to the tasks Entity in our project root.
Tasks
Tasks will take advantage of Valaa’s powerful instancing mechanism. We will also use Relations to store our tasks, but won’t assign targets for them. This is occasionally faster and simpler than creating separate Entities to store data and then referencing them with Relations.
We start by creating a Relation called Task with an accessor property as the prototype for actual tasks and assign following properties to it:
- user set to
null
- isPrivate set to
true
- text set to
null
- done set to
false
- date set to
null
Finally, we need a piece of ValaaScript for creating new instances of the Task Relation. For that we create a new Media called newTask.vs and add an accessor property for it.
Local Instancing
In the previous tutorial all users shared the same UI state. This is not an issue for personal applications or when the shared UI is intended behavior. For most multi-user applications additional effort is needed to allow users to interact with the application without interfering with other users.
Local instancing is an easy method for achieving independent UI state for different users. Local instances are instances of Valaa Entities that are stored in the browser using the IndexedDB API instead of the Valaa cloud. They still reflect prototype modifications from the cloud, so users don’t have to clear their browser’s cache to see updates to our app.
For this we use Valaa’s custom <ValaaScope> element. It renders an Entity passed to it through the focus property. You can specify what Lens to use from that Entity with the lensName. The focus can also be returned from a function— a local instance of our client Entity in this case. Let’s write the following to our partition.vsx in the project root:
The function below is the createLocalPartition.vs used above and located in the project root. It will first check if the user already has a local instance and if yes, will return it. This prevents the UI state from being lost between sessions. If this is the first time vising the app, the function will create a new local instance and return that instead.
I will not go through the function in more detail, as we will include a utility function for this task in Valaa’s standard library in the near future.
Creating New Tasks
Our users want to create new tasks. For that we are going to instance the Task Relation we created earlier. The newTask.vs inside tasks Entity should look like this:
Let’s look the function line by line:
(text, user) => {}
Our function takes two parameters, text for the content of the task and the name of the user who created it.
const date = new Date();
We create a instance of the standard JavaScript Date object to be used later.
new Task({
name: "task",
owner: this,
properties: {
text: text,
user: user,
date: date.getTime()
}
})
We instance the Task Relation like we would instance an object from a class: new Task()
. The “Task” refers to the accessor property we created for our prototype Relation earlier. The constructor takes an object as a parameter, which we can use to override built-in fields and assign or override custom properties. We give it a name “task” in lower case to denote that it is an instance, not a prototype intended for instantiation. We set the owner to the function context’s this
, which is the tasks Entity. Finally we set the properties text and user to the given parameters and fetch the time from the Date object we created earlier.
The User Interface
The ui.vsx inside the client Entity is where we describe our entire user interface. It is slightly more complex than our Hello, World! example, but I will break it down for you below. As we won’t be addressing CSS styling in this tutorial, you can download the styles.css used in the tutorial from here.
Let’s look at this in chunks:
<div className={VSS(focus.styles, "todo")}>
<h1>Todo List</h1>
We again start by wrapping our user interface into a <DIV> element and apply styling. We also hard-code a heading for our application.
<If test={!focus.user}>...</If>
Here we use another feature from our implementation of the JSX-Control-Statements: the <If> conditional. We can specify a test that will only render it’s contents if the test returns true
. In the first <If> block we present the user a login form only if the user is null
. Because our client Entity has the user set to null
before we do the local instantiation, the first-timers will always be prompted with a login form. Inside this block we have the following for the actual login:
<input type="text" placeholder="Sign in with your nickname"
onKeyPress={(e) => {
if(e.which === 13) { focus.user = e.target.value }
}}/>
Every time the user writes something to the text field, the onKeyPress
event listener will check if Enter (keycode 13) was pressed. If so, it will set the Property user to the value currently in the input field. Like in the Meteor example, we have left the password out.
<If test={focus.user}
context={{
user: focus.user,
tasks: focus.tasks[Relatable.getRelations]("task")
}}>...</If>
The next <If> block renders only when focus.user
is finally truthy and contains the rest of our user interface. We can set a context to an <If> block as well. It allows us to define values we want to remain constant during the rendering of the block. We shorten the focus.user
to be accessible inside the block with just user
and fetch all tasks to the tasks
variable from the tasks Entity via our pointer using the familiar syntax focus.tasks[Relatable.getRelations](“task”)
. Note, that we write the “task” with a lowercase in order to list the instances created by our newTask.vs function, but not the prototype named “Task”.
Because <If> blocks support only one element as their child, we wrap the rest of the user interface to another <DIV> element.
<div className="app"></div>
<span className="username">{user} </span>
<span className="signOut" onClick={() => focus.user = null}>
Sign out
</span>
Here we display the username and provide a link for signing out. In our implementation all we really do is set the current user to null
. This causes both <If> blocks testing the focus.user
value to refresh, the first now once more rendering the login screen and the second one no longer refreshing.
<label className="hideDone">
<input type="checkbox" checked={focus.hideCompleted}
onClick={(e) => focus.hideCompleted = e.target.checked} />
Hide completed tasks
</label>
Here we provide a checkbox for the hideCompleted Property. We can make sure the checkbox is correctly checked by providing the value to the checked
property.
<input type="text" placeholder="Add new task"
onKeyPress={(e) => {
if(e.which === 13) {
focus.tasks.newTask(e.target.value, user);
e.target.value = null;
}
}}/>
With this input field our users can add new tasks. We do the same check for onKeyPress
as with the login form, listening for the keycode 13. When Enter is pressed, we create a new task by traversing to the tasks Entity via the pointer and calling the newTask.vs function with the value of the input field and our current user. This will instance the Task Relation and create a new task. Finally we clear the input field with e.target.value = null;
.
<ul>
<ForEach focus={tasks.sort( (a, b) => {return b.date - a.date})}
context={{origin: focus}}> ... </ForEach>
</ul>
We use a list items to print our tasks. Inside the <UL> element we use the familiar <ForEach> utility to iterate through the tasks we set earlier in the context of our <If> block. We use a sort function to order the tasks from new to old.
<If test={
(!focus.isPrivate || user === focus.user)
&& (!focus.done || !origin.hideCompleted)
}>...</If>
This <If> block is used to determine if we show the iterated task at all. It checks if a) the task is not private or the user who created it is the user looking at it and b) the task is not done or the hideCompleted Property is false
.
<li className={focus.done ? "completed" : null}>
Here we again use a ternary operator to give our list item a class completed
based on done Property of the task.
<input type="checkbox" checked={focus.done}
onClick={(e) => focus.done = e.target.checked} />
Next we have another simple checkbox for toggling the done Property of the task.
<If test={user === focus.user}>...</If>
This <If> block is used to only allow the creator of the task to toggle the isPrivate Property of it. Here we create simple true/false
toggle function for the isPrivate Property and render either “Private” or “Public” in the button using the ternary operator:
<button onClick={() => focus.isPrivate = !focus.isPrivate}>
{focus.isPrivate ? "Private" : "Public"}
</button>
<span className="text">
<strong>{focus.user}</strong> - {focus.text}
</span>
Here we render the creator and the content of each task.
<If test={user === focus.user}>
<button onClick={() => Valaa.Resource.destroy(focus)}>X</button
</If>
And finally provide only the creator of the task a button to delete a task. Valaa.Resource.destroy()
is a built-in function in ValaaScript for destroying any Resource. Quite permanently.
And that’s it.
This tutorial was partially made as a benchmark to see how Valaa matches with Meteor, a great JavaScript framework for building multi-user applications. Like Valaa, Meteor ships with pretty much everything you need to get an application up, running and delivered to the users with minimal amount of code.
I chose their excellent to-do application tutorial and see if I could achieve similar results with Valaa.
As a disclaimer — Meteor is a mature and widely used framework while Valaa is not (yet). We still lack some of the built-in features of Meteor, such as user-defined tests, user management and authentication. Our version looks and feels similar, but it is not secure.