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
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");
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;
Then we need to update our App struct:
pub struct App {
items: Vec<i64>,
link: ComponentLink<Self>,
console: ConsoleService,
}
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(),
}
}
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
}
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.
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
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");
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!"),
};
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
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};
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,
}
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
App {
link,
items: Vec::new(),
console: ConsoleService::new(),
dialog: DialogService::new(),
}
}
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,
}
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>
With our trusty useless modal now showing
How did we get that dialogue? With this one-line addition to the Msg processing logic
Msg::About => self.dialog.alert("Purposeless App"),
Just that! How cool is it?
Confirm
Code to follow this part
git checkout tags/v3p2b
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!",
);
}
}
};
And.. run!
(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());
}
};
As we can see if we select the tick to the don't bother me mode, we log false
s even though the dialog's not showing up!
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!
Top comments (2)
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:
You might wanna update this post to reflect that, because otherwise it is quite great, thanks for the effort : )
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.