DEV Community

loading...
Cover image for YEW Tutorial: 03 Services and all

YEW Tutorial: 03 Services and all

davidedelpapa profile image Davide Del Papa ・6 min read

Good things come to those who wait... (PS: Photo by Kate Townsend on Unsplash)

We keep on extending... With services and more.

Part 1: Intro to dirty talking (to the console)

Code to follow this tutorial

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

git clone https://github.com/davidedelpapa/yew-tutorial.git
cd yew-tutorial
git checkout tags/v3p1a
Enter fullscreen mode Exit fullscreen mode

I know you have been following me so far. So apart from typos, and some missing dependencies, I hope you have done well so far. Your only concern so far has been to copy and understand the code. What if you had to do it from scratch yourself? I've been doing the old programmer's way: try and error.

So it's time to talk a little about debugging. Speaking of which, debugging WASM is not that easy, so as all lazy programmers I try first the easy way around: printing and logging!

In javascript it's easy:

console.log("Something's happening");
Enter fullscreen mode Exit fullscreen mode

We log straight to the console.
This is the equivalent of our trusty println! used for the same debug purposes, but on the terminal (really the stdout).
What about Yew?

We start by use-ing the appropriate module in app.rs

use rand::prelude::*;
use yew::prelude::*;
use yew::services::ConsoleService;
Enter fullscreen mode Exit fullscreen mode

Then we need to update our App struct:

pub struct App {
    items: Vec<i64>,
    link: ComponentLink<Self>,
    console: ConsoleService,
}
Enter fullscreen mode Exit fullscreen mode

Never forget to update our constructor, the create fn:

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        App {
            link,
            items: Vec::new(),
            console: ConsoleService::new(),
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now we can use our trusty console, just as we would in JS:


    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::AddOne => {
                let added: i64 = random();
                self.items.push(added);
                self.console.log(format!("Added {}", added).as_str());
            }
            Msg::RemoveOne => {
                let removed = self.items.pop();
                self.console
                    .log(format!("Removed {}", removed.unwrap_or_default()).as_str());
            }
        }
        true
    }
Enter fullscreen mode Exit fullscreen mode

There is not much to that code, but we just rearranged last tutorial's code to have the random element to add in a variable to show, and we saved the popped element to show as well (using unwrap_or_default to handle the option)

Upon running we should see the JavaScript console being logged.

Alt Text

As you can see it is bundle.js the one who is actually logging to the JS console, because bundle.js is where our WASM worker is called into play.

Coming up, another, better, way to log.

Code to follow this part

git checkout tags/v3p1b
Enter fullscreen mode Exit fullscreen mode

Raw logging to the console is a very primitive method. We love our wine, we love our luxuries, and we love our logging levels as well, don't we?
In fact, we can provide our WASM front-end and workers with the ability to log different levels of suspicious activities for debugging purposes

We can issue an info:

self.console.info("Added 1 elemet to the vec");
Enter fullscreen mode Exit fullscreen mode

We can issue a warning, or we can even issue a more severe error

let removed = self.items.pop();
match removed {
    Some(x) => self.console.warn(format!("Removed {}", x).as_str()),
    None => self.console.error("No more elements to remove!"),
};
Enter fullscreen mode Exit fullscreen mode

Alt Text

Of course we can delve a little deeper and discover some more interesting stuff, like set a counter (to see how many times a certain part of a loop has been called, for example), or set timers, to record elapsed time. You can also clear the console, group and un-group with a level of indentation, and, better still: trace the calls and make JavaScript asserts. Maybe one day I'll write a tutorial on the subject.

Reference documentation is found on the ConsoleService documentation.

Part 2: some more dirty talk

Code to follow this part

git checkout tags/v3p2a
Enter fullscreen mode Exit fullscreen mode

Lowering ourselves in the realm of services, we find a handy little service: a dialog.

This service corresponds really to 2 modal windows: alert, and confirm. Modal widows steal the focus out of the current page, so they must be used wisely.

Alert

The alert is just a small window with a message and an OK button. It just really shows to the user important stuff, stealing the focus from the page. Thus we will test it on a trivial case, just to bother our user.
Well, really just because right now there's nothing really important to communicate, it's just for the sake of showing the modal.

In our src/app.rs we need first to update the services' use line:

use yew::services::{ConsoleService, DialogService};
Enter fullscreen mode Exit fullscreen mode

Then we need to update our App struct, and our constructor, as usual:

pub struct App {
    items: Vec<i64>,
    link: ComponentLink<Self>,
    console: ConsoleService,
    dialog: DialogService,
}
Enter fullscreen mode Exit fullscreen mode
    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        App {
            link,
            items: Vec::new(),
            console: ConsoleService::new(),
            dialog: DialogService::new(),
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now, just to be less frivolous than expected (I know you were thinking of updating the error log with a dialog prompting the f*ing user to stop deleting stuff when the list is empty; however, we are nice towards our users, aren't we?)

pub enum Msg {
    AddOne,
    RemoveOne,
    About,
}
Enter fullscreen mode Exit fullscreen mode

Now you do have a feeling of where I'm headed with this, don't ya?

<header>
    <h2>{"Items: "}</h2>
    <button onclick=self.link.callback(|_| Msg::About)>{ "About" }</button>
</header>
Enter fullscreen mode Exit fullscreen mode

With our trusty useless modal now showing

Alt Text

How did we get that dialogue? With this one-line addition to the Msg processing logic

Msg::About => self.dialog.alert("Purposeless App"),
Enter fullscreen mode Exit fullscreen mode

Just that! How cool is it?

Confirm

Code to follow this part

git checkout tags/v3p2b
Enter fullscreen mode Exit fullscreen mode

The Confirm dialogue is actually the less useless of the two modals, since it returns something: it is a dialogue that steals the focus from the page, and shows a message with an OK and a CANCEL button. It returns a bool, with true meaning OK, false all other cases.

I know you are thinking: there are two cases, really, so what's wrong with saying false for CANCEl? well, if the browser is set to ignore in-page dialogs, then cofirm returns also false, without asking any question.

Well, let's add an action, in the most useless place...

match removed {
    Some(x) => self.console.warn(format!("Removed {}", x).as_str()),
    None => {
        self.console.error("No more elements to remove!");
        let user_is_a_monkey = self
            .dialog
            .confirm("Are you dum? There are no more elements to remove!");
        if user_is_a_monkey {
            self.dialog.alert("I kenw it!");
        } else {
            self.dialog.alert(
                "Maybe it was an error, there are no more elements to remove!",
            );
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

And.. run!

Alt Text

(PS: dum is not a word, or is it?)

As I was saying, after the first dialogue, Firefox shows a tick box in the modal.
If you mark it, the page will not show any more modals, thus the value of the confirm will always be false even if not showing.

We can confirm this behavior of confirm, by logging the value of user_is_a_monkey

match removed {
    Some(x) => self.console.warn(format!("Removed {}", x).as_str()),
    None => {
        self.console.error("No more elements to remove!");
        let user_is_a_monkey = self
            .dialog
            .confirm("Are you dum? There are no more elements to remove!");
        self.console
            .debug(format!("Confirm value: {}", user_is_a_monkey).as_str());
    }
};
Enter fullscreen mode Exit fullscreen mode

As we can see if we select the tick to the don't bother me mode, we log falses even though the dialog's not showing up!

Alt Text

To get this on the console, I chose not to be bothered, and kept pushing remove: no dialogue appeared, yet it logged a false!

We must then remember this possible behavior and plan ahead.

For today it's all, we will continue in the next installment of this tutorial talking about more services. I promise you, they will be more useful than these Dialog modals, so stay tuned in!

Discussion (2)

pic
Editor guide
Collapse
atoav profile image
David Huss

As for yew 17.3 (maybe even before, I didn't check) Console got a little easier.

You don't need to add a console: ConsoleService field to the App struct anymore. In fact that aproach won't work anymore, which means your example won't compile.

The good thing is, it got simpler. Just use this anywhere in the code:

ConsoleService::log("something");

You might wanna update this post to reflect that, because otherwise it is quite great, thanks for the effort : )

Collapse
davidedelpapa profile image
Davide Del Papa Author

Hi. Yes, it's something I take care of much later in the tutorials. You know at the time of writing this, the stable Yew version I was using was 0.12.
Up to 0.16 the interfaces were pretty stable, but from 0.16 on many changes were introduced.