00:08
reorder them. Now whenever we query our difficulties to display them to our user, we're always going to order them by whatever this order is that they've set specifically for this organization so that it is perfectly attuned to their liking.
00:18
So our first step is to jump back into our terminal because we want to install a dependency called view-draggable and specifically it's next tag.
00:27
Without that next tag there, we're not going to get the appropriate version that supports view three. So it's very important to include that here. So we'll hit enter there to get that installed.
00:35
And this is the package that we are going to use in place of our UL to list out our actual difficulty items.
00:43
So let's start by scrolling up and importing it. So import and we'll call it sortable, even though it comes from view-draggable because
00:51
for the purpose of our sake, we're going to be using it to sort our items rather than just dragging them. And let's replace our UL with that sortable component.
00:59
Also updating our end tag there to sortable as well, but we can keep the underlying HTML element to UL by setting the tag to UL there.
01:07
So semantically nothing's going to change with how this is added into our HTML, but we will now be able to add in a view model pointing to our difficulties list, specify that the
01:16
items key is the ID property. And now this sortable component is going to manage the loop and listing of our items for us.
01:25
So we no longer need the V4 or the key on our LI as the sortable component will be managing that on our behalf. Now you will notice that all of our items are now squiggled and that's because we no
01:34
longer have an instance of it. The way that we get that is by adding in a slot named item and the sortable component provides us those items as element.
01:44
So we can either rename item here to element everywhere that we're making use of it, or we can rename element to item, whatever you prefer there.
01:51
And let's also add in our end template and give everything there an indent. Now we want to add a specific element within here that the user can click on to drag an
02:01
item to reorder it. So on our sortable, we can specify a specific element for that by adding in a handle prop and giving it a selector. So we can provide in a class name of handle.
02:11
And then inside of our flex item center gap four, we can add an additional div with that class handle. And let's also style it up a little bit. So text slate 300.
02:21
Whenever we hover over the group, which is still our list item, we want to switch that text slate to 950 set a duration three, so it animates slightly and then set the cursor
02:29
to move so that our users know that they can click and drag via this element. Let's use LucideView next to add in a grip vertical icon with the class of width four
02:39
H4 and we can end that out. Now, if we give this a save, let's go ahead and jump back into our browser. You'll see that we have this little dotted icon over here on the left of each of our difficulty items.
02:49
Whenever we hover over them, it becomes a little bit darker and easier to see. And now if we click on any one of them, we can drag it vertically to reorder where that item is positioned.
02:58
What we now need to do is store that position wherever the user drops an item into a position. So if we switch hard to the second item, whenever we drop that, that's when we
03:07
want to commit it to our database. So let's jump back into our text editor. We can capture this via the event called end. So on end, we'll create a function called on order update.
03:17
Scroll on up to our template here and we can add this along with our other functions on order update. And we don't need anything excepted in for this because we'll be able to directly
03:27
reference our list ref in order to get that update. So we can do const IDs as all that we care about is the ordering of those IDs from our
03:36
list value, map over that, take an item and then just grab that item's ID. To persist this to our database, we can use Inertia's router.
03:44
So let's import that and call the put method, sending this out to slash difficulties slash order. We'll want to send the IDs up with it.
03:53
And we also want to preserve the scroll position. I hit tab to auto import that. Let me go ahead and try hit command dot import that. There we go. We now need to define that route.
04:02
So let's jump into our web routes and add that along with our other difficulty routes. So it's a put. So I'm going to put it right here. And there is a reason why I'm putting it right here. We'll discuss that here momentarily.
04:12
Calling this difficulties order, reaching for our difficulties controller, and we will create an order method as difficulties dot order.
04:19
OK, the reason why I put it right here rather than anywhere further down is because we also have this router put going to difficulties ID.
04:27
We haven't specified any validation for what that ID route parameter is. So order would have been a valid ID for this route.
04:34
So if we were to put order after our router put with the ID route parameter, we would never reach our order route because it would get captured by the ID route.
04:44
So by placing the order one first, Adas.js will check for this route before checking for this route, meaning that our order will successfully use the appropriate route for our use case here.
04:53
Next, we're going to want to add in a difficulty. So let's go into our difficulty validator there. And I'm going to add an export const called difficulty order validator.
05:03
Set that equal to vine compile. Set a vine object inside of there. And this is just going to accept in our IDs with a vine array of type vine number. Give that a save.
05:12
And we can now jump into our difficulties controller. And let's go in between our update and destroy here and add in an async order method that
05:19
takes in our request response and organization from our HTTP context.
05:24
We're going to want to grab those IDs out of our request validate using difficulty order validator.
05:32
At the end of the day, we will return response, redirect the user right on back where they sent this request from.
05:38
And then we also want a new action to node ace make action within our difficulties folder to update the difficulty order.
05:46
Clear out of that back into our text editor and let's await update difficulty order to get that imported called the handle method. And for this, we just need to provide in the organization and those IDs.
05:56
I'm going to click into that action now for our programs. We want that organization of type organization and those IDs, which is a number array.
06:06
So extracting those out of our handle method, therefore use. You may have noticed that we did not within our difficulty validator validate that the
06:15
IDs being passed in exist inside of the organization. We didn't add it to our difficulties order validator here because that query to check
06:22
the existence would run for each one of the IDs that we have in our post data. So instead, we're going to go about that a slightly different way to verify ownership there. What we'll do is grab our difficulties.
06:32
So const difficulties equals awaits off of our organization. We've added in that get difficulties helper method that will return all of them bound to the organization for us.
06:42
And we will use the difficulties coming directly from our organization to verify that the IDs being passed in do indeed exist on the organization itself before we save anything.
06:52
I'm going to also add in another static async method called update order. That's going to be in charge of actually updating the order here. This accepts in those difficulties.
07:01
So that's going to be a difficulty array. And then the IDs, which will be a number array. Now we don't need to do this update sequentially. We can kind of have them all going at the same time.
07:09
So we'll grab an array of those promises as we're saving things by looping over our IDs with a map. Pick in the ID and the order as the index there.
07:19
We want to find the difficulty specific to the ID by doing difficulties dot find, taking
07:25
in that record and then do record dot ID equals ID coming from our IDs map. We can also check whether or not this is the default difficulty by doing order and just
07:34
checking if that equals zero as the first item is always going to be our default. Once we found our difficulty model and whether or not it's going to be the default, we can
07:43
go ahead and merge that into its data. So difficulty merge, pass in the order and the is default flag. And you'll notice that optional chaining got added on there as well, because since we're
07:53
grabbing all of our difficulties directly off of the organization, if an invalid ID from some other organization gets passed in, we won't be able to find a difficulty here.
08:02
And ultimately it'll just end up getting skipped over because this won't be populated. And whenever we return difficulty save, this won't run due to that optional chaining again.
08:11
So it won't commit anything to our database either. And by returning the direct result of our save, we're returning that saves promise into our promises array.
08:20
So before we finish out of our update order, what we want to do is await for all of our promises to finish. So we can return promise dot all promises. And we can do that once more inside of our actual handle method.
08:30
Just return this update order, difficulties and IDs. With all of that set, we should now be able to jump back into our browser.
08:38
And I'm going to go ahead and right click, inspect, jump into the console, make sure XHR is on so that we can actually see this go out here.
08:44
So I'm going to drag expert up to our first index, second item and give it a drop and you'll see our difficulties order put request went up and we got redirected right back to this
08:54
page. And if we give the page a refresh, we should now see expert persisted in the second item spot. So jump up here, refresh and sure enough, it's right there still.
09:04
If we drag expert up to the first item, our zero index, you'll see that this is now labeled as our default difficulty.
09:11
Again, if we refresh the page that persists as well. Let's go ahead and put this back to how we had it with expert last and you'll see our default got reset back to easy.