Jonas: So using the workout classes
that we just implemented,
let's now finally implement the feature
of creating a new workout from our user interface.
And to start, let's take a look at our flowchart here.
And so basically, this here is the action
that we're interested in now.
So when the user submits a new workout,
and so we already added this new workout method here
as an event handler function for that event.
And so what we want to happen now is
to basically render the workout on the map,
render the workout on the list,
and then this part here, we're gonna leave for later.
So this one is the high level overview
of what needs to happen,
but actually, there's a lot more things involved,
than just these immediate actions
that we see on the user interface.
So actually, we're gonna leave the rendering here
for the next video because as I just said,
there's actually a lot of other stuff
that we have to do, all right?
So as you see, we are back here at looking at the flowchart
because now we are back to being interested
what we actually need to implement,
and not how to implement it, right?
So the architecture here, again,
is about how we implement it.
And so it contains all of these different methods
and these low-level details,
while the flow chart here is really more
of a high-level overview of what we want to implement.
And so for now, we don't need the architecture anymore
because we already put everything into the code
that we had here.
Now, if you're really curious
and want to spoil the experience basically until the end
of the section, then you can already take a look
at the final architecture here.
But I'm not gonna do that.
So I just want to now keep building the rest
of the application as we go, okay?
But anyway, since this is so high level here,
let's actually go to the working demo application
and see what exactly happens.
So let's reload here.
And so let's click just somewhere,
and then you'll see, as we already know,
the form opens, then we can input some data here.
And then our application needs to get this data,
it needs to check if the data actually makes sense,
and then we need to create a new object for this workout
and render it right here,
and also on the map, of course.
And then finally, after all that,
the form is also hidden.
So everything I just said is gonna happen
whenever we hit Enter here.
So you see, of course, the marker was created,
it was rendered here, all the input data had to be checked,
and a new running object was created.
So for example, if we add another one,
but without any data, then we are gonna get this alert.
Or the same, if we input any data,
that doesn't make much sense.
For example, a negative number for the cadence,
which is, of course, impossible.
And so again, we get this error here.
So input validation is an important part
of creating any application that works with user inputs.
And so let's now actually work on the function
that is responsible for doing all of this.
And for starters, I will basically document everything
that we need to do here using comments.
So first, we need to get data from the form.
Then we need to check if data is valid,
then if it's running.
So if the activity running, then create running object.
And if the activity is cycling,
then, of course, create a cycling object.
Then add the new object to the workout array.
And so yeah, instead of activity,
let's actually call it workout here.
Then we want to render workout on map as a marker.
And finally, we want to render the new workout on the list.
And finally, also, we want to then hide the form
and clear the input fields.
So you see, there's gonna be a lot of work.
Let's put this one for now, at least here,
even though it's not gonna really work like this,
but okay, so with this,
we now have all the steps lined out.
And so let's get started.
And we're gonna start by getting the data from the form
and starting with the type,
and type is already stored at the variable input type.
So that's all the way up here.
Let's bring that down actually.
And we could even add these elements here basically
as fields also inside of the application.
But in this case, I prefer not to do that
because then every time that we wanted
to reference one of these elements,
then we would always have to use like this .inputDistance
or duration, and so on and so forth.
And I just don't really like that.
But anyway, let's not take the values of input type,
distance, duration, cadence, and elevation.
And these two here are not that straightforward,
but let's see that in a minute.
So inputType.value,
and so input type is actually a select element,
but still we get the value using the value property.
Let me just show that to you.
And so yeah, here it is.
And so each option has this value.
And so for running, the value is running with the r,
and for cycling, the same, okay?
And so again, the value will be whatever
of these two is selected, and then we get this value here.
And so that will be important in a minute.
But anyway, let's not take the distance,
so that's inputDistance.value.
And remember that this always comes as strings,
and so let's convert that immediately to a number.
And then the duration,
let's do the same here, inputDuration.value.
Now, about the cadence and the elevation gain,
we actually don't want to get them here right
in the beginning, but the cadence,
we only want to get if it is a running workout,
and the elevation gain, we only want to get
if it is a cycling workout.
Because that will then make it easier for us
to validate the data, but more about that in a second.
So for now, let's actually check what kind of workout
we are dealing with.
And so all we have to do is to check the type.
And if it's running, then we want to get the cadence.
So let's call that cadence as well,
and convert inputCadence.value to a number.
But if we are dealing with cycling,
then we want the elevation gain.
So let's call this one elevation,
and here, it's also called inputElevation.
Now, okay, and so now let's check if the data is valid.
And so we will now do that inside of each
of these if statements.
Now, you might have noticed
that I actually created two separate if statements here,
instead of an if else statement.
And that's just something that you will see more and more
in modern JavaScript.
So the if else is not really that much used anymore,
and I think that actually, it looks a lot cleaner like this.
So to simply have two if statements.
So anyway, let's not check if the data is valid,
and what does it mean for the data to be valid?
Well, first of all,
each of them should be a number, right?
And so let's do that.
And here again, we will actually use a guard clause.
So again, what a guard clause means is
that we will basically check
for the opposite of what we are originally interested in.
And if that opposite is true,
then we simply return the function immediately.
And so once again, this is a trait
of more modern JavaScript.
So kind of a trend that you will see in modern JavaScript.
But anyway, let me show you what I mean here.
So if the distance is not a number,
and remember that for that, we use Number.isFinite.
So if the distance here is not a number,
then we want to return immediately.
So we want to return,
and then we can also do some other code here.
So let's create an alert.
Inputs have to be positive numbers.
So for now, we check for numbers,
but we didn't yet check for positive,
but let's leave that for a second.
So we should already be able to test out
if this if here is gonna work.
So let's just see if our map appears here,
let's try to reload here.
And for some reason,
sometimes this takes a really long time.
So sometimes while I was developing this application,
I waited up to like two or three minutes,
and I don't know why that happens.
But yeah, I hope that never happens for you.
But in case you see long waiting times,
that might be normal, all right?
But now here it is,
and so let's input some invalid distance,
so like just any letter.
And then we get inputs have to be positive numbers.
So that is already working.
And now we just need to do the same with the other inputs.
So we want the same for the distance.
So Number.isFinite,
so actually for duration,
and then the same thing for Number.isFinite, cadence, okay?
Now, this is actually not correct
because we don't want the and,
but instead, we want or.
So this one here should happen,
whenever one of these three here is not a number
and not when all of them are invalid numbers, right?
And so that's exactly what or means.
So if this one is an invalid number or this one or this one,
then we can get this alert.
And so the reason why we did this checking here inside
of this if block is because if we did the same out here,
then we would also want to check for the elevation, right?
But as we know already,
only one of the cadence and elevation can be defined
at the same time.
They cannot both be defined at the same time.
And so performing this check,
would then be a little bit more difficult.
So now we can go ahead and do the same thing down here.
So in the cycling part,
but let's actually refactor this code here already,
and basically create a small function
which takes a number of arguments,
and we'll then validate
if all of them are numbers or not, okay?
So basically, we want it to work like this,
but we want to write this
in a more clear and more beautiful,
so to say, way, all right?
So let's create a very simple helper function here,
and let me call it validInputs.
So a very simple arrow function,
and this can take an arbitrary number of inputs.
And remember that when we use rest parameters like this,
then we get an array.
So inputs is an array,
and we now want to loop over this array,
and basically check if all of them are positive.
Now, there's actually already a predefined method
in JavaScript that is very helpful for that.
So remember that we have a nice little method called every,
and so let's use it here,
and then I will quickly explain what it does.
So the current input and then Number.isFinite,
the current input.
So basically, this will loop over the array,
and in each of them, it'll check
whether the number is finite or not.
And then in the end, the every method will only return true
if this value here was true for all of them,
so for all elements in the array.
But if only one of these values here was not finite,
so if the result here was false
for one of the elements of the array,
then every will return false.
And so that will then be the return value
of this arrow function, right?
And so then here, we can replace all of this.
And let's actually just commented out,
we can now say if not valid inputs
and then pass in the three that we're interested in,
so distance, duration, and cadence.
And again, here we are inverting the condition, right?
So if all of these are numbers,
then this year will become true, right?
But whenever this is not true,
then that's the case where we want to return
from the function, and show this alert we know.
All right, so let's copy this,
and do the same here for the elevation like this.
And here, I will get rid of the comments.
And so this is the first check,
but in fact, we also want to test
if the numbers are positive,
because otherwise, they don't make much sense.
So let's reload here again.
So right now, if I put a letter in any of these,
then it's not gonna work, right?
But if I put in a negative number of steps,
then this is perfectly acceptable,
and we already get the marker here printed to the map.
But in fact, we don't want this to be acceptable.
So negative values should not be allowed
for any of the three,
but in the cycling, it should be allowed
for the elevation gain.
So here, we can of course have a negative number
which happens when we go basically down a mountain, right?
So that's simple as well.
All we have to do is to check
if these three values here are greater than zero,
and the same for these two,
but not for the elevation, okay?
And so actually, to make this really cool,
I will create another small helper function like this one,
and it might be a good habit for you as well
to get used to these small helper functions.
So they're especially nice in these situations
for testing complex conditions.
So instead of writing all of this
and plus checking now for positive numbers,
we can simply export that into one or two functions
which then do the job for us.
So here, I'm gonna create a function called allPositive,
and this one is gonna be almost the same
as the previous one.
So it takes in an arbitrary amount of inputs,
and then we will only return through
if all of them are greater than zero.
And so here, the every method is very useful,
again, so input.
And then here we need some condition
that returns true or false.
And so that's input greater than zero.
And so let's now use that here.
And so we want basically, if all of the inputs are not valid
or if there is basically any number that is not positive,
so if not allPositive, distance, duration,
and cadence, all right?
So in this case, we then want to get this alert.
And now here, let's do the same for the cycling,
but here, we are only interested in distance and duration,
because again, the elevation might very well be negative.
So this is a little bit complex validation,
but that's no problem at all.
That's why I spent some time here
on this validation without any problem.
Now, it would've been a little bit easier
if we didn't have these different values of elevation
and cadence for the two different workouts,
but we do have it.
And yeah, so we have to solve that problem.
And so I think it works really nice like this.
Let me just check it out here one more time now
with these negative values.
So if we have a negative distance,
then now indeed, we get the (indistinct) two,
except for cycling.
So let's remove that one,
and minus two, but that still works.
And so clearly, our validation logic here is just fine.
Great, so next up,
let's see what we have to do.
So actually, we still have to finish these ones.
So if the workout is running, then create a running object,
and otherwise, create a cycling object now, right?
And then afterwards, push that object to the workout array.
So add it to the array,
and for that, we're gonna use the push method as always.
So let's start with this one here,
right after this guard clause, now, right?
And so here, let's say workout should be a new running.
And now let's see what we need for running,
or actually, we can probably just hover this.
And indeed here we have the coordinates,
distance, duration, and cadence.
All right, so where do we get the coordinates from?
Well, we already know from the marker
that they are right here.
So in this .mapEvent.latlng,
so we this because we already used this data before
to render a marker on the map.
So let's get this one and paste it here
as the first argument.
But actually, this year is now gonna be an object.
So we know that because here we use these structuring
to get the latitude and longitude out of that object.
But up here, we said,
so in the class definition
that indeed we want an array, right?
So let's simply get this code from here to up there.
So here, and so basically create the latitude
and longitude variables here based on this object.
And so then here, we can define an array with that data,
so lat and lng.
So we already have our array
of the coordinates just as expected,
and then all we need is distance, duration, and cadence.
And then we can take this.activities,
and I think we didn't even create that one yet.
So that's up here,
and now again, we could do it right here like this.
So basically, the more classical way.
So this.workouts would be an empty array.
But again, in this project,
I chose to already use the class fields specification.
And so this will become part
of standard JavaScript very soon.
And so therefore, I can create a class field like this,
and I can even make it private just like the other two.
But this time, I actually already initialized it here
to a new empty array.
And so now we can take that.
So here in new workout,
we can say this.#workouts.push this new workout.
Now, we are gonna do basically,
the exact same thing down here.
And so this here is something that always needs to happen.
So in both cases, and so let's not repeat that,
and instead, put it down here.
But then we have the problem
that this workout variable is defined here
in this block scope, right?
And so because of the scope chain, as we already know,
this workout variable will then not be available outside
of this block, right?
But we want it outside of the block, so right here.
And so therefore, we put it right here outside.
So let's work out.
And then here, we simply redefine that,
and then let's simply take this and put it here again.
But this time, of course, with a cycling,
and then still the coordinates distance duration,
but here, the elevation, all right?
The next step here is to actually render the workout
on the map, all right?
But before we do that, let's actually take a quick look
at the console, and see our out there.
And so with that, we can already check
if all of this that we have up here,
is already working, right?
Okay, so let's wait for the map.
And now, let's click here and add a running.
So five and 25 with 170.
And indeed, here we get our running object.
So it has the coordinates, and so this worked,
and of course, it has all the other data as well.
This idea is now corrected as you see.
And also, the pace is calculated here,
based on this input data, right?
And so indeed, the most complicated part
of adding a new workout is already completed.
So that's beautiful.
Let's just try the same with cycling.
Let's say 10 kilometers, and 10 minutes with 30 meters.
No, and I didn't even click anywhere on the map, okay?
And so that clearly means that we are still missing a step,
which is to hide the form.
But anyway, it simply took the coordinates from earlier,
and then it added all the correct data here as well.
So indeed it is now a cycling object,
while before, it was a running object.
Let's just do that again here, but this time with clicking
so that I can show you something.
All right, so it still says workout,
and we're gonna fix that in the next video.
But for now, what I want is the cycling actually
to have this orange border.
So just like this one, all right?
So orange is for cycling, and green for running.
And so let's quickly do that.
So in our CSS, I think, I showed you earlier,
that we have running-popup and cycling-popup.
And these two values here are essentially simply the value
of the input type.
So this one here.
And so here we can simply do a template literal,
and then plug in the type here.
And now actually, let's also export this here
to its own method to make this add workout method here,
a little bit cleaner, all right?
So let's add down here, renderWorkoutMarker.
Let's grab this code here.
And so now, all we need to do is
to call this.renderWorkoutMarker.
And here, we need to pass in the workout object
because otherwise, it's not gonna work.
I mean, right now,
we don't even need to workout yet, but later we will.
So we want to display some kind of data about the workout,
and so let's pass it in.
So workout there and here,
let's actually call it workout as well.
And so here, instead of displaying this,
let's simply display workout.distance.
Okay, just for now,
just so we are actually using the workout here
in this method, okay?
You see that we are also using the this keyword
in this method.
But actually, that's no problem in this case
because here, we are actually calling this one here
as a method of the this keyword.
And besides that, we are also calling it ourselves.
So it's not a callback function
of any other function in JavaScript.
And so therefore, the this keyword in this method here,
will still be the current object.
And so no need to using bind in this case.
And so finally, the last step is
to actually render the workout on the list.
And this one, I'm gonna leave for the next lecture,
because this one is gonna take a little bit of time
in order to format all the HTML correctly.
So let's give this one a save.
And so besides that, this one is basically complete.
And so let's quickly check it out one more time just
to see the cycling-popup.
So let's see right there,
cycling 10, 10, 10, values don't matter.
Oh, and here, we do actually have an error,
which says that, "Latitude is not defined."
And so indeed, here it is not defined.
So instead these coordinates,
should, of course, come from the workout itself.
And so that itself is already a reason enough
to pass in the workout argument here into this function.
So now, it becomes very important for the first time
to have some data in the actual workout object,
because here we do need that in order to tell JavaScript,
or actually in order to tell leaflet
where to display this marker, okay?
So it's the data that's coming directly from the workout.
And so now, this should work,
or maybe it shouldn't.
So I'm looking at this variable here,
and right now, the type is,
of course, also no longer defined, right?
So we get the type here from this one,
but of course, this type variable,
is not gonna be available right here.
So, how do we fix that?
Well, ideally we should get the information
whether the current workout is of running
or of cycling, right, from the workout object.
And actually, that's a pretty good idea.
And so let's add that value to the cycling
and to the running.
However, it doesn't make much sense to always pass that in.
So we already know that running is always gonna be running,
and cycling is always gonna be cycling.
And so we can simply add basically that type right here.
And so let's do that.
So again, we are defining a field here, so cycling.
And so this value type will now be a property
that's gonna be available on all the instances, okay?
So again, this would be the same thing
as doing this.type equals cycling, like this, okay?
And now, the same for running like this, all right?
And so now, we can use that here,
and we will actually also need that in some other places.
And so this is indeed a good thing to have.
So workout.type, and so now,
this will weather be running or cycling.
So let's test that now with cycling.
So cycling and then,
and we get another error, okay?
So it failed to execute appendChild on Node for some reason,
but let's go back to that in a second.
For now, I just want to see if actually the type is there,
and indeed it is.
So that's great already.
Now, let's just go back and see
why this error here is happening.
Well, there is no appendChild method anywhere here.
Let's just set this here to something else,
but that's probably not the problem anyway.
So probably, I shouldn't have changed anything because now,
the error message here will be gone.
And then I cannot read the stack trace there,
which is what we call to that list of errors.
Oh, but now, it actually worked.
All right, so the problem there was
that we were actually probably passing in some value
that was not a string, and so therefore,
for some reason, leaflet created some problem there.
But anyway, we will take care of this string here
in the next video.
For now, I just want to create just a running one here.
And so this time, it turned out to be green,
and so indeed, here the type is now running.
All right, so this was a very long video,
and so let's finish it here.
And as I already said, in the next one,
we will then finally render the workout also here
on this sidebar.