تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Using JS confirm0:01

So, what's the most basic way of adding confirmation before you take a destructive action? Probably by using JavaScript's confirm function. So, let's come to our show page here. We'll scroll down to where we have our deleteComment method. I can add braces to this. And then before we delete, perhaps we could say, well, if you've said that you don't want to confirm, and we'll say, are you sure you want to delete this comment, then we'll return early. In other words, if you don't want to delete the comment,

then we'll return early. In other words, if you don't want to delete the comment, don't delete the comment. And this should work pretty well. Let's come back to our browser. We can add a new comment. I'll go with the classic hello world. And let's go for delete. And the browser pops open a message saying, are you sure you want to delete this comment?

Problems with browser alerts0:51

And the browser pops open a message saying, are you sure you want to delete this Comment? If we click OK, well, the Comment gets deleted. But if we go ahead and cancel that instead of clicking OK, then the Comment stays in place. So, literally within 30 seconds, you can have a working implementation like that. But in my opinion, there are a few issues with this approach. First of all, note this checkbox on the alert. Don't allow this page to display.

First of all, note this checkbox on the alert. Don't allow this page to display. Most browsers ship with this little functionality. And I understand why. You see, alerts are used by so many sites to spam the user. It can be a bad experience, especially on malicious sites where they spam the user with alerts to make them feel like something has gone wrong so that they download some virus or malware or something along those lines.

so that they download some virus or malware or something along those lines. So, yeah, browsers have this feature for good reason. But we really don't want the user stopping us from being able to ask them whether they want to delete a Comment or not. That could destroy the functionality of our site. It also doesn't fit in with the UI of the rest of the site, right? It sticks out like a sore thumb. And different browsers will style this alert differently. Some stick it right at the top. It looks so ugly.

And different browsers will style this alert differently. Some stick it right at the top. It looks so ugly. Some stick it here in the middle as Arc does. I think it would be nicer just to have our own implementation that we control. But if I'm being honest, I would like to stick as close as humanly possible to the syntax we have here. Because this is such a glorious, simple syntax, it would be a shame to overcomplicate it. Let's see how close we can get. It's probably no surprise at this point,

Building modal wrapper2:35

Let's see how close we can get. It's probably no surprise at this point, but Jetstream actually has a confirmation modal component built right in, which is wonderful news because modals are notoriously difficult to build out. If you're not using Jetstream, why don't you just go and steal the confirmation modal component, save yourself some time, and feel free to make all the edits you want. So here's the component itself, but I don't want to have this inserted into the DOM at runtime. I want it always present.

but I don't want to have this inserted into the DOM at runtime. I want it always present. I want it to be sat at the bottom of the DOM, ready to be opened at a moment's notice. So I'm actually going to create a new component, and we'll call this the ConfirmationModalWrapper. We'll create our little template, and we'll have our script here, which is going to be a setup script. And then inside here, we're going to have our ConfirmationModal. Now, the way that the ConfirmationModal works is through slots.

And then inside here, we're going to have our confirmation modal. Now, the way that the confirmation modal works is through slots. So we'll have a template for, for example, the title. For now, let's say, are you sure? And then we'll have the content, which again is template. So template, and it's #content. And then we can say, do you want to perform this action? And then we have another template, and inside this template, we can have our actions, which is the footer, right?

and inside this template, we can have our actions, which is the footer, right? So in the footer, we could have a primary button, and that primary button is going to say confirm. And then above that, maybe we could have a secondary button, and that secondary button is our cancel button. And then we'll add a little class to this to add some spacing, maybe margin-left-3, like so. Now, you can add a show property to the confirmation modal to decide whether you want to hide or show it in the DOM.

Now, you can add a show property to the confirmation modal to decide whether you want to hide or show it in the DOM. Just for testing purposes, I'm going to set that to true. Then let's come into our app layout, and right at the bottom of our template down here, before the closing div, I'm going to add that confirmation modal wrapper. Okay, so if everything's working, when we come back onto our page and refresh, there is our modal.

when we come back onto our page and refresh, there is our modal. Are you sure do you want to perform this action? And we have cancel and confirm. So that is just perfect. We have a nice-looking modal. It sits above the content. The z-index is correct, and it has a clear call to action. So now, how do we take this piece of DOM, this view component that sits right at the bottom of our application,

Creating useConfirm state5:05

So now, how do we take this piece of DOM, this view component that sits right at the bottom of our application, and somehow wire its state up to something else in a completely different place of our view application? Let's have a think about that. We're going to have to create some form of global state here that's able to be tracked across different components. There are many ways to do this, but I'm going to keep it nice and simple. Let's create a new directory under Utilities called Composables. And inside here, let's create a new one called useConfirm.

Let's create a new directory under Utilities called Composables. And inside here, let's create a new one called UseConfirm. So if you've not worked with composables before, that's a term in the View Composition API. It basically means something that understands how view components work and even interacts with the state of those view components, but it isn't a view component itself. It's separated. It's reusable across different components. And usually they're prefixed with the word Use. So in this case, UseConfirm.

And usually they're prefixed with the word Use. So in this case, UseConfirm. I'm going to keep track of all of the state for this modal in a constant called GlobalState, which is actually going to be a Reactive object. Again, Reactive is part of the View API. It allows us to keep track of multiple properties inside a standard JavaScript object. So for example, are we going to show the modal? That's going to be a Boolean.

So for example, are we going to show the modal? That's going to be a Boolean. What will the modal's title be? What will be the body of the modal? And because it's Reactive, well, anytime these properties change, the DOM will also be updated. We now need a way to interact with this GlobalState object, and we do that by providing a composable to be used in other files. So in this case, let's export a function called useConfirm, and useConfirm is going to return an object.

So in this case, let's export a function called useConfirm, and useConfirm is going to return an object which will contain the various properties and methods that other components can make use of. Now, in this case, we're going to need to be able to access the values of show, title, and message, but I don't want you to be able to update these properties randomly outside of this file. So I'm going to return a read-only instance of GlobalState. So we'll say that the state is equal to,

So I'm going to return a read-only instance of GlobalState. So we'll say that the state is equal to, and readOnly is a method available inside View, and then we'll pass the GlobalState down. So now you can grab the state by making use of UseConfirm, but if you attempt to update it manually, it will fail. It will cause a JavaScript error. Now, let's make use of this right off the bat. Inside our ConfirmationModalWrapper, we can say const state, so I'm destructuring the object that will be returned to us,

Inside our ConfirmationModalWrapper, we can say const state, so I'm destructuring the object that will be returned to us, equals UseConfirm, and obviously, we'll have to import UseConfirm from our new composable. Now we can make use of that state object inside our ConfirmationModalWrapper. So where we have the hard-coded true here, well, that becomes state.show. Where we have our title, well, that becomes state.title, and where we have our hard-coded message,

Where we have our title, well, that becomes state.title, and where we have our hard-coded message, that's going to become state.message. So fairly straightforward, but let me demonstrate what happens when we change that state from anywhere in our application. So in UseConfirm, just to demonstrate what's taking place here, let's set an interval, and in that interval, we're going to alter the global state. So we'll say globalState.show equals not globalState.show, and we'll toggle that every second.

So we'll say globalState.show equals not globalState.show, and we'll toggle that every second. So every second, if this works correctly, our modal should show and then hide, and then show and then hide. Let's see this work. And you can see, yeah, that's working exactly as we'd expect. That's the power of being able to combine global state with reactive view properties. We no longer track any of that data inside the SFC,

with reactive view properties. We no longer track any of that data inside the SFC, inside the single file component that contains this modal, but we're still able to listen for changes across the entire application and then react in the front end. All right, while the flashing modal is cool and it illustrates a point, it's not what we're looking for. Let's go ahead and write a real implementation that we can actually make use of. So let's get rid of this setInterval,

that we can actually make use of. So let's get rid of this setInterval, and if we go into the show page of posts, we could start thinking about our ideal syntax here. So perhaps we'd have const, and then we'll destructure any methods that we want from useConfirm, and maybe, well, what if the method was called confirmation? That's quite close to the confirm function built in, but obviously slightly different so that we're not conflicting in any way.

but obviously slightly different so that we're not conflicting in any way or causing confusion for developers. So confirmation, and then down here we'd call confirmation, and sure, we could pass the body that we want to display inside that modal to the confirmation method. So let's come back here, and inside our useConfirm object, well, we'd need to provide a method called confirmation where you can pass in the message that you want to display. So how can we make this work?

where you can pass in the message that you want to display. So how can we make this work? Well, first of all, we could update the globalState, right? So globalState.show equals true. We want to show the modal, but also globalState.message equals message, and let's set the title at the same time, globalState.title equals 'please confirm'. I think that would actually be enough to display the modal. Let's see if it works.

I think that would actually be enough to display the modal. Let's see if it works. So I'm going to come back to the browser. We don't see the modal, but if I come down here and hit delete, yeah, you can see please confirm appears, and we have the cancel and confirm buttons. Not that the cancel and confirm buttons do anything, but yeah, we get the idea. We are now able to, from anywhere in our application,

Promise-based confirmation flow10:50

but yeah, we get the idea. We are now able to, from anywhere in our application, pop up a modal to ask for confirmation. Now comes the tricky part. See, in JavaScript, the confirm function has an advantage over our own implementation because it can just freeze the JavaScript execution. We can't do that. At least it would be a very bad idea to do that.

We can't do that. At least it would be a very bad idea to do that. So we need to asynchronously check for the resolution of our modal. In other words, we need some form of promise that when you click confirm or cancel, well, we will execute the rest of the code. In other words, in the case of our show page, we will execute router.delete based on the button you click inside that modal.

we will execute router.delete based on the button you click inside that modal. I use the word promise for good reason. That's because we're going to turn to JavaScript promises for this implementation. If you've not done any work with promises in JavaScript, don't worry. Hopefully it will become quite clear in just a moment. The first thing I'm going to do is update our global state object,

The first thing I'm going to do is update our global state object, and I'm going to add a resolver, which by default will be null. But when we call this confirmation method, what we're actually going to do is return a new Promise. So here that promise keyword comes in. And Promise is built into JavaScript. It receives a resolver, and this is a closure that is promised to be called.

It receives a resolver, and this is a closure that is promised to be called or executed at some point in the future. And all we're going to do is set the global state.resolver to that callback. So again, we don't know when this resolver is going to be called. It's asynchronous. But we promise to call it at some point in the future. Now, the fact that I have added the callback

But we promise to call it at some point in the future. Now, the fact that I have added the callback to the global state should tell you what I'm planning here. Let's create a couple more methods on our useConfirm object. Perhaps we could have one called confirm. And confirm is quite simple. It's going to resolve the global state resolver. So we'll say if globalState.resolver exists,

It's going to resolve the globalState.resolver. So we'll say if globalState.resolver exists, if it's not null, then let's call globalState.resolver, and let's pass true to it because you confirmed the action. On the other side of things, if you canceled, then again we'll check if there's a globalState.resolver. And if there is, we'll execute it again.

then again we'll check if there's a global state.resolver. And if there is, we'll execute it again. We'll fulfill our promise. But now we're going to pass false because you canceled instead of confirmed. We can now wire those two methods up to our confirmation modal wrapper. So where we have our useConfirm object here, we'll grab confirm. We'll grab cancel.

we'll grab confirm. We'll grab cancel. And then on our buttons, well, we'll wire secondaryButton at click up to the cancel method. And we'll wire our primaryButton click up to our confirm method. So let's just go through what's taking place from the top down. Here you have the modal.

from the top down. Here you have the modal. When will the modal be shown? Well, the modal will be shown whenever you call this confirmation method because the global state.show will be set to true. And our confirmation modal wrapper is listening for changes in the read-only property of state.show that we pass through this composable here. So the modal gets shown.

of state.show that we pass through this composable here. So the modal gets shown. On that modal are two buttons. One is a cancel button. One is a confirm button. Let's take cancel. If they click cancel, we're going to call the cancel method on the useConfirm composable, which will resolve the promise that we set up.

on the use confirm composable, which will resolve the promise that we set up whenever we call the confirmation method. So the promise that we've said will happen at some point will be resolved at this stage. And when that happens, it will return false as a value. Now, obviously, we should also reset the modal at this point. Let's just add that quickly.

At this point. Let's just add that quickly. Maybe we'll have a little method up here called resetModal. And resetModal is going to just reset a few different pieces of state. Let's get rid of that. That's a little typo there. So, yeah, it will say globalState.title is empty. globalState.message is empty.

So, yeah, it will say globalState.title is empty. globalState.message is empty. We don't show it, and we'll reset the resolver as well. And then down here, we can say resetModal like so. Now let's track the confirmPath. So when you click the confirm button, now we call the confirm method on the useConfirm composable, which, again, checks if we have a resolver set up.

on the use confirm composable, which, again, checks if we have a resolver set up. And as long as we've called the confirmation method previously, we do have a resolver set up. It executes that so it completes the promise, and it passes true because you confirmed. Again, after we've done that, we should reset the modal. So all that's left to do is handle the promise.

we should reset the modal. So all that's left to do is handle the promise wherever we happen to have called the confirmation method. And there are multiple ways to do this, but here's the easiest way. I'm going to alter the signature of the deleteComment method we have here by making it asynchronous. And the async keyword basically allows us to use an await command when dealing with promises.

And the async keyword basically allows us to use an await command when dealing with promises. So we now know confirmation returns a promise, and I can await the value that comes from that promise. In other words, the execution of this method alone will freeze until the promise returns a value, which we know is either going to be, you've got it, true or false. And that leaves us with an almost identical syntax to using the confirm function that JavaScript provides.

And that leaves us with an almost identical syntax to using the confirm function that JavaScript provides. If the promise returns false, then we'll return early. But if the promise has returned true, we can go ahead and carry on with deleting the comment. Shall we see if all of this works? Let's come down to our hello world comment. I'll click delete. Please confirm. Are you sure you want to delete this comment?

Please confirm. Are you sure you want to delete this comment? And by the way, that's exactly what we've typed up here. So that's being passed through correctly. If I click cancel, well, everything disappears, but we haven't deleted the comment yet. Let's refresh the page to make sure. Yep, no comment deleted. But if I do the same thing again, and now I'm going to click confirm,

But if I do the same thing again, and now I'm going to click confirm, then the comment is removed. We've confirmed the action. How cool is that? I love Vue. I love the composition API. And how nice is it that the syntax is almost identical to the simplest possible way of being able to get confirmation from a user using JavaScript?

to the simplest possible way of being able to get confirmation from a user using JavaScript? So nice. Of course, we can make use of this anywhere in our code base. So how about before you go ahead and update a comment, you're asked to confirm that you actually want to perform that update? It should be pretty simple. Let's take this useConfirm, and we'll put it further up in the script so we have access to it in updateComment.

and we'll put it further up in the script so we have access to it in updateComment. Let's make this a standard closure with braces. And then we want to check, well, do you actually want to update the comment or not? So again, we'll say if not, await confirmation. And we'll say, are you sure you want to update this comment? And we are going to return early, but at the same time, why don't we refocus the textArea so that you can carry on editing the comment?

but at the same time, why don't we refocus the textArea so that you can carry on editing the comment? Because if you've hit cancel, it probably means that you made like a typo that you just noticed and you want to make changes. So we already created a ref, commentTextAreaRef, and we can grab the value. And if there is a value, then we'll call focus on it. And then obviously we'll return early because we don't want to actually update the comment.

And then obviously we'll return early because we don't want to actually update the comment. Let's see if it works. We're getting an error here, unexpected reserve word await. And again, that's because we've used the await keyword, but the method we're calling is not classed as asynchronous. So we just need to make sure that it is an asynchronous method by prepending the closure with the async keyword. That should now disappear. Let's come back down.

That should now disappear. Let's come back down. We have hello world here. Come down to edit. Let's say we make a typo and I click update comment. Well, before it updates, it's asking me to confirm, are you sure you want to update this comment? If we click cancel, yes, it's focused the text area. We're able to make our changes. Then I'll update the comment, asks me to confirm.

We're able to make our changes. Then I'll update the comment, asks me to confirm. I hit confirm, and the comment is updated. How nice is that? Let's just do a couple of tidy ups here. So maybe as part of the confirmation method, you can also pass a title, which by default is set to null, or in fact by default could be set to please confirm. And then the globalState.title can just make use of that parameter instead.

And then the global state title can just make use of that parameter instead. So if you want to override the title shown in the modal, that is absolutely possible. One last thing I'd like to do to tidy up is when I click delete, currently my focus, and you can kind of see in the bottom right corner here, my focus is still behind the modal, and I have to tab through quite a lot until I get to the cancel and confirm buttons.

Autofocusing modal buttons19:48

and I have to tab through quite a lot until I get to the cancel and confirm buttons. So when the modal pops up, I'd like the focus to automatically jump straight to that cancel button so that you can easily hit enter on it if that's what you like. Should be quite straightforward. Let's go back into the IDE. I'm going to once again make use of watchEffect for this, which is going to be given an async closure,

I'm going to once again make use of watchEffect for this, which is going to be given an async closure, and that closure is going to be listening for state.show. So in other words, any time state.show changes, this method will be fired, and that's because watchEffect is able to understand which properties it's actually looking for changes on. It's pretty cool. So if we are showing the state, I'm first of all going to await the nextTick.

So if we are showing the state, I'm first of all going to await the next tick. In other words, wait till the DOM is updated because we can't actually focus on an element that isn't inside the DOM. But once that next tick has happened, we need to grab the cancelButton and focus on it. Let's give this a ref. So ref equals cancelButtonRef, and then down here we can create a constant for it.

So ref equals cancelButtonRef, and then down here we can create a constant for it. So we have a constant cancelButtonRef equals ref null. We are now tracking it, and we can say cancelButtonRef.value, and we would usually be able to say focus, but we don't actually have an instance of a HTML element here. We have an instance of a child component. So in order to grab the underlying element, we can say $el.focus,

So in order to grab the underlying element, we can say $L.focus, and now that should work. Let's see what happens. If I go back to the browser, I'll click delete, and yeah, the focus is straight on the cancel button. We can either tab over to confirm and hit enter and delete, or we could hit enter immediately to cancel. So how cool is that? Not only does the user get a great experience

So how cool is that? Not only does the User get a great experience in not accidentally deleting comments they didn't mean to, but as developers, we've created a beautiful API that is so simple to make use of, and request confirmation from a User. It is so laughably simple that it's zero issues to very quickly add that feature anywhere in our front end, and we'll be making heavy use of it going forward.

anywhere in our front end, and we'll be making heavy use of it going forward. So great to have that in there now. Hopefully it's demonstrated to you the power of the view composition API.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟