YEW Tutorial: 04 ...and services for all!
Tags: rust, yew, webassembly
Published: 2020-05-23 13:25:11

Never break a promise (Photo by LexScope on Unsplash)

In this fourth part we are going first to do some "minor" improvements,
and hopefully show a little more the potential of the [Yew framework](https://yew.rs)

In this article we will be tinkering around,as usual; after which we'll start to see the light and usefulness at the end of the tunnel (still, the exit is far away).

### Code to follow this tutorial

The code has been tagged with the relative tutorial and part.

```Bash
git clone https://github.com/davidedelpapa/yew-tutorial.git
cd yew-tutorial
git checkout tags/v4p0
```

## Part 0: Cleanup

First of all, let's remove some clutter from our _src/app.rs_

```Rust
use yew::prelude::*;

pub enum Msg {
    AddOne,
    RemoveOne,
}

pub struct App {
    items: Vec<i64>,
    link: ComponentLink<Self>,
}

impl Component for App {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        App {
            link,
            items: Vec::new(),
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::AddOne => {
                self.items.push(1);
            }
            Msg::RemoveOne => {
                let _ = self.items.pop();
            }
        }
        true
    }

    fn view(&self) -> Html {
        let render_item = |item| {
            html! {
                <>
                    <tr><td>{ item }</td></tr>
                </>
            }
        };
        html! {
            <div class="main">
              <div class="flex three">
                <div class="card">
                    <header>
                        <h2>{"Items: "}</h2>
                    </header>
                    <div class="card-body">
                        <table class="primary">
                            { for self.items.iter().map(render_item) }
                        </table>
                    </div>
                    <footer>
                        <button onclick=self.link.callback(|_| Msg::AddOne)>{ "Add 1" }</button> {" "}
                        <button onclick=self.link.callback(|_| Msg::RemoveOne)>{ "Remove 1" }</button>
                    </footer>
                </div>
              </div>
            </div>
        }
    }
}
```

Away with consoles and dialogs, and even random numbers. We are just adding `1` and removing `1`, literally.

## Part 1: Being persistent

#### Code to follow this part

```Bash
git checkout tags/v4p1
```

After cleaning up we can now bring back a service, the [storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage).

```Rust
use yew::services::storage;
```

What is the storage, and how does it work? Well, first of all MDN has got a [better explanation](https://developer.mozilla.org/en-US/docs/Web/API/Storage) than what I could ever provide. Let's say that we are using a persistent Key-Value store that any modern browser provides for us, in order to save data. There are two kinds really, [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage), and [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). We are using the local storage, that is saved across browser sessions.

Since it is a Key-Value store, we can either save all our data as a key-Value pair, or set a key and dump all data in it. The lazy bum in me always prefers the dump and forget, obviously..

We need therefore to establish a main key under which all our data will live.

```Rust
const KEY: &'static str = "yew.tut.database";
```

Now we need also to create a virtual database with a custom data structure.
We will use [serde](https://serde.rs/), one of the most mature crates in all Rust's ecosystem, to come to our rescue. Don't forget to update the _Cargo.toml_ with:

```Toml
serde = "1.0"
```

### Setup

This is all the `use` area of the _src/app.rs_ with our KEY right after it.

```Rust
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use yew::format::Json;
use yew::prelude::*;
use yew::services::storage::Area;
use yew::services::StorageService;

const KEY: &'static str = "yew.tut.database";
```

It is a lot more than just serde! In order:

- we have serde's _serialize_ to format the data, and _deserialize_ to dump it
- we have wasm_bindgen to go lower in the stack. We're getting more hardcore here. Explanations later on.
- from Yew we import _Json_ so we can create and dump data using JSON, because if we are lazy why not be also classy while doing it?
- of course we need still Yew's prelude
- then we import also yew's mod to relate to the storage area onto which we will save data (the local or session area we were discussing before) and the storage service itself

(If you look at the code you see i have implemented right away the hardcore stuff needed with wasm_bindgen, but we'll talk about it later on)

### Custom Data Structures

Now we can start to build our custom data structures!

```Rust
#[derive(Serialize, Deserialize)]
pub struct Database {
    tasks: Vec<Task>,
}
```

Our virtual database is a vec of tasks. Sometimes trying to be as simple as possible is just right.
I like to _impl_ my structs right away, just to keep together code that belong together, and that I might want to refactor out later on:

```Rust
impl Database {
    pub fn new() -> Self {
        Database { tasks: Vec::new() }
    }
}
```

Nothing too fancy, just a _new_ constructor.

Following this, we can declare our _Task_:

```Rust
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Task {
    title: String,
    description: String,
}
```

The `Debug` derive is there also if you want to call back the console and resume logging...
Go ahead, I won't judge you.

Instead, notice the `Clone` derive: if we want to copy a task and bring it out of any function or callback, or if we want to just save a new task inside our database, most probably we will need to clone a task somewhere. Otherwise be prepared to face the compiler's wrath because you will end up borrowing mutably some immutable Task at some point, or making a Task survive its scope!

And now a beautiful impl for our _Task_:

```Rust
impl Task {
    pub fn new() -> Self {
        Task {
            title: "".to_string(),
            description: "".to_string(),
        }
    }
    pub fn is_filledin(&self) -> bool {
        (self.title != "") && (self.description != "")
    }
}
```

- We have here an empty _new_ constructor
- We have also a check to know if the Task is filled-in or not, that we'll end up using later on

### Msg and App

We need to update our _Msg_ to manage our _Task_:

```Rust
pub enum Msg {
    AddTask,
    RemoveTask(usize),
    SetTitle(String),
    SetDescription(String),
}
```

We add and remove tasks, however we will not add them all at once: since we will have a form with a field for the _title_ and a field for the _description_ we need a way to collect those two separately.
We therefore have a _SetTitle_ message, containing the title in a string, and a _SetDescription_ message, containing the description.
The _AddTask_ will be shot by the submit button: when the message arrives we will collect title and description and save them in a new task. More on this mechanism later on.

Now, in our app, we can take away the _items_ and add _storage_ and _database_

```Rust
pub struct App {
    link: ComponentLink<Self>,
    storage: StorageService,
    database: Database,
    temp_task: Task,
}
```

We added a temporary task as well, to hold information on the task being filled in the form, before it will be stored in the database.

Let's move on to the 3 functions in the impl of our _App_

### fn create

```Rust
    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let storage = StorageService::new(Area::Local);
        let Json(database) = storage.restore(KEY);
        let database = database.unwrap_or_else(|_| Database::new());
        App {
            link,
            storage,
            database,
            temp_task: Task::new(),
        }
    }
```

The `let` lines are those of concern here:

- First we need to create a new storage object, and we are referencing the local, not the session storage (specified passing `Area::Local` to the constructor)
- We make a JSON out of the database, and we create it using the restore function. That is, we restore the database loading it as JSON from the storage connected to the KEY (if it exists).
- Of course the first time around the store area will be empty: in this case the _restore_ will return an error, that is why we unpack it in the next line with a _unwrap_or_else_: if there is something to restore we reassign the unwrapped content to the database, otherwise we create a new database by calling its constructor inside the closure of the _unwrap_or_else_.

### fn update

The _update_ function is where most of the magic usually happens, so we'll examine it carefully:

```Rust
    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::AddTask => {
                if self.temp_task.is_filledin() {
                    self.database.tasks.push(self.temp_task.clone());
                    self.storage.store(KEY, Json(&self.database));
                    self.temp_task = Task::new();
                    refreshform("taskform");
                }
            }
            Msg::RemoveTask(pos) => {
                let _ = self.database.tasks.remove(pos);
                self.storage.store(KEY, Json(&self.database));
            }
            Msg::SetTitle(title) => {
                self.temp_task.title = title;
            }
            Msg::SetDescription(description) => {
                self.temp_task.description = description;
            }
        }
        true
    }
```

We go message by message. Remember that we have a temporary _Task_ that we are using as buffer in order to insert title and description separately with two different from input fields.

`Msg::AddTask`: What we are doing here is simple. After the user has filled the form containing a field for the title, and a field for the description, the user will click on the submit button (that we will call _Add task_) However, before adding this temporary Task permanently on our storage, we need to check that both title and description have been inserted. We do it by calling the _is_filledin()_ function we implemented for the _Task_ struct earlier.

After this simple integrity check on the temporary _Task_, we are going to push it into the virtual database. Here we need to _clone()_ it, that is, we are saving a copy of the temporary task inside our virtual DB.

We update the storage by re-dumping the whole content of the virtual DB inside the value corresponding to our key: that is the purpose of the `self.storage.store(KEY, Json(&self.database))`; otherwise the virtual database and the storage of the page will be out of sync. Admittedly it is a lazy workaround, but the other option, that is, to have a proper serialize-deserialize method implemented in order to save each task as a pair of Key-Value takes much more coding effort. Of course, for a serious project, that should be the way, but for a tutorial's purpose (and small quick projects) I think this is sufficient.

Next we call the mysterious function `refreshform("taskform")`. This is the function we created with the `wasm-bindgen`. Let's analyze its purpose first and its implementation next.

**Purpose**: When the user fills out a form in a page and clicks on submit, usually the page sends information to another page, whicih is loaded if the form `action` field is set, or it refreshes the same page with updated information.

What happens here instead is that when we fire the _Add Task_ button the message is sent to the WASM app we are writing. This app has to refresh the form in some way, if we want to reset the _title_ and _description_ inputs. As of today there is not a straightforward way of doing that with Yew. However, we are using Yew on top of the _web_sys_ stack.
That means that we can use it to accomplish this task.

We can use `#[wasm-bindgen]` to bind a function created with javascript to Rust. It is just [FFI](https://doc.rust-lang.org/1.9.0/book/ffi.html) in action.
The _wasm-bindgen_ proposes us two ways of doing this: we can either write a `.js` file with a function that does this, and bind it to a Rust function interface, or we can write an inline javascript function right away. Of course, for a simple function it is OK to write inline javascript, while for a complex one we will need to write a separate file and bundle it in this project.

```Rust
#[wasm_bindgen(
    inline_js = "export function refreshform(form) { document.getElementById(form).reset(); }"
)]
extern "C" {
    fn refreshform(form: &str);
}
```

**Implementation**: What we are doing here is to define the `inline_js` function first, and its Rust interface next; the interface is simple `fn refreshform(form: &str)` it takes a string and does not have a return type.

The inline JavaScript function is simple as well:

```javascript
export function refreshform(form) {
  document.getElementById(form).reset();
}
```

We pass the form name to `document.getElementById` and we use the `reset` method on it in order to reset the fields of the form.
Of course you are allowed and encouraged to reuse this snippet in your own code.

After this brief detour into the `wasm_bindgen` lowlands, we can analyze the rest of the messages.

`Msg::RemoveTask`: we use the position inside the vector in order to pop that element out of the database's _tasks_ vector with `self.database.tasks.remove(pos)`; after this we dump again the database in the storage, in order to update it.

Wait a moment, how do we get the position inside the vector using a message from the `view` of the element? Well, we will see later on how to implement this trick, and I let you judge if indeed it is a really a trick or not.

`Msg::SetTitle` and `Msg::SetDescription`: both messages are self explanatory, we just set the `self.temp_task` _title_ and _description_ respectively, no big deal.

### fn view

Finally, we can analyze the `view`.

```Rust
    fn view(&self) -> Html {
        let render_item = |(idx, task): (usize, &Task)| {
            html! {
                <>
                    <div class="card">
                        <header><label>{ &task.title }</label></header>
                        <div class="card-body"><label>{ &task.description }</label></div>
                        <footer><button onclick=self.link.callback(move |_| Msg::RemoveTask(idx))>{ "Remove" }</button></footer>
                    </div>
                </>
            }
        };
        html! {
            <div>
                <h2>{"Tasks: "}</h2>
                { for self.database.tasks.iter().enumerate().map(render_item) }
                <div class="card">
                    <form id="taskform">
                        <label class="stack"><input placeholder="Title" oninput=self.link.callback(|e: InputData|  Msg::SetTitle(e.value)) /></label>
                        <label class="stack"><textarea rows=2 placeholder="Description" oninput=self.link.callback(|e: InputData|  Msg::SetDescription(e.value))></textarea></label>
                        <button class="stack icon-paper-plane" onclick=self.link.callback(|_|  Msg::AddTask)>{ "Add task" }</button>
                    </form>
                </div>
            </div>
        }
    }
```

The `render_item` closure has changed a little, however we left the original name just to keep the structure.

We are not creating rows of a _table_ as before, but creating new [picnic card](https://picnicss.com/documentation#card) items.

We also have to annotate the variables we are capturing with our closure, because it's getting more complicated for the compiler to keep track of types. We are passing to it a `usize` as index, called _idx_ and a `Task`

We are using the _task.title_ in the card's _header_, the _task.description in the card's _body_ (watch out for it, it is a custom style we created inside _index.html_ ), but the real magic is now in the code of the _footer_. In it we are creating a button to remove the task, with a callback that will fire the `Msg::RemoveTask` passing to it the `usize` _idx_ where it is stored the index of the task, index given by the position inside the _tasks_ vector. This index is used in order to remove the task from the database. But where did we get that index from? Again: good things will come to those who are patient.

The `html!` macro is simple, however, it is a little bit cluttered with many `<div>` and `class` for presentation purposes.

The structure is simple enough, once the clutter is removed: there is really only the list of the tasks, and the submission form to enter a new task.

We use the `for` syntactic sugar to call the _render_item_ closure. The thing to notice here is that before the `map()` we call the `enumerate()`. This trick is what allows us to know the index that each task has inside the _tasks_ vector!

Beware that `enumerate()` creates a tuple in the form of `(index, iter() content)`, so that same tuple we are passing to `map()`. This is the reason we had to annotate our closure as a `(usize, &Task)` tuple.

The `form` field is straightforward:

- we have an input with an `oninput` callback to update the temporary Task with the message `Msg::SetTitle`
- likewise, we have a textarea with an `oninput` callback, used to update the temporary Task's description
- the button _Add task_ completes the form, firing the message `Msg::AddTask`.

What is really of notice is the `<form id="taskform">`: here we are setting the id of the form, that we pass to the inline javascript function inside the `Msg::AddTask` handling logic.
Now we can understand the purpose of the `refreshform("taskform")`.

Let's give it a run, and a try.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ebp890haio0wdrl72v9v.jpg)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/etlit022uc1xo6i6lp3h.jpg)

Not bad at all. Now if you are playing with it, you can notice that the task list is persistent even if you refresh the page.
It stays the same also when you close the server and restart it (easy, it's not a server side storage)

We can inspect the storage with the browser tools. Here some images with Firefox and Chrome.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5xz3zi1ptncg06976xd6.jpg)

In firefox is under the tab **storage** of the dev tools, in the local storage.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6coexicb3ckb01fyvqpu.jpg)

You can inspect the JSON stored in the value.

Likewise you can inspect the JSON with Chrome's dev tools:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/55c44sk75y4aammnxc6s.jpg)

However, here the Local Storage is found under the _Application_ tab.

## Part 2: Being practical

You didn't think I would ever get to this point, did you?

Well, checkout my [yew-todolist](https://github.com/davidedelpapa/yew-todolist).
Live at [yew-todolist.surge.sh](http://yew-todolist.surge.sh).
It has got a _manifest.json_ to make it real PWA. Well not really, because to make a PWA we need `https`, but that is easy to get on premises.

#### Code to follow this part

It is another repository:

```Bash
git clone https://github.com/davidedelpapa/yew-todolist.git
cd yew-todolist
```

In order to make the manifest, I used this [firebase app](https://app-manifest.firebaseapp.com/), then you just link back to the _index.html_

```html
<link rel="manifest" href="manifest.json" />
```

While the rest is needed for ios compatibility.

Speaking of the things you should really notice is that I made a separate _deploy/_ directory in order to be able to run [surge](https://surge.sh/), and deploy there. Of course you can't get real PWA without **https** support, and surge with https cost$ (quite so).
Maybe with some other provider?

An interesting thing is that I refactored the code to separate the virtualdb out of the _app.rs_ in its own _database.rs_.

Moreover, I implemented a different status system for each todo, `Active`, `Completed`, `Archived`. For now there is no turning back from completed to active (if someone completed it by error), and the archived are removed from the interface but sit on the database. In the future I should make filters for views (active, completed, archived), ways to restore, adding the beginning date-completion date. Ways to completely remove old archived tasks. An export function could be useful too. Some of the these things we could implement with the knowledge so far gained, some other we really have to learn more concepts in ordr to implement; so stay tuned for more!

PS: If yu want to thinker with it, pass it to some other provider that guarantees https, try out implementing new functions... That repo is there as a study/starting point. Take it as a homework source, play with it, and let me know how it goes.

Stay tuned for next istallment, _Drive through libraries_, where we will see how to interface with online API's, NPM libraries, and more...