00:18
And this behavior is made possible thanks to Vue Draggable's grouping behavior. We've assigned a modules group to all of our modules, meaning that we can move any module between the module group.
00:27
And we've added lessons to a specific lessons group throughout those modules, again, meaning that we can take any lesson and move it to any other lessons group there as well.
00:35
So as we move our third lesson from our first module to our third module, we want to update not only the ordering inside of that third module for the lessons, but also inside of our first module for its lessons as well, while at the same time updating the module for that third lesson from first to third.
00:54
So it's just kind of added in a whole layer of complexity to the situation.
00:58
And on our client side, since this involves changing modules, we want to have this outside of our sortable lessons component inside of our sortable modules component so that we can have access to the new lessons, its new modules and the modules new lessons ordering.
01:14
So inside of our sortable modules, we'll add in a new function just below our onModuleOrderChange called onLessonOrderChange. And we want to use our modules as the starting point.
01:24
So we do const data equals modules value and we can map over each module in that array.
01:31
We want to return back a new object containing the modules ID as well as the lesson IDs that are now bound to that module.
01:40
So we can get access to that via module.lessons and we can map over those each individual lesson to grab the specific lesson ID.
01:47
So we'll have an array of objects where the object contains the modules ID and an array of IDs for our lessons.
01:55
Before we move on to adding that validation, though, we want to scroll down to our sortable lessons as we're going to need to bubble up the at end event from inside of here out into our sortable modules.
02:05
So we'll add an at end event handler that we'll call our onLessonOrderChange event. And then we'll dive into our sortable lessons component so that we can bubble that upward.
02:14
On our sortable component, just like we did with our modules, we want to then add that at end handler. And we're going to want to emit that end event up to the parent.
02:23
And it looks like we don't have that defined in our emits. So we'll scroll up to there and add end to that. That should do it for our client side. So we can scroll on up to our lesson validators.
02:33
And after our patch, let's add in an additional one.
02:36
So export const lesson order validator equals bind compile and then bind object adding the modules key to it.
02:45
We'll have an array of modules and inside of this array is going to be an object. Each object contains the modules ID.
02:53
So this is going to be a bind number. And in addition to the modules ID, we're going to have an array of the lessons IDs as well. So we'll have a bind array that is also a type bind number.
03:03
Again, just like with our module order validator, with how we're going to be reordering these inside of our actions, we don't really need to worry about whether or not it exists in our organization here,
03:12
as we're going to be re querying them and using the query values as our source of truth. And then if we're reordering something, we're going to have at least one item in that array.
03:20
So we don't need to worry about making any of these items optional in our validator. So we can give it a save and move on now to our lessons.
03:28
And we haven't made it yet. So let's dive into our terminal next and do node ace. Make action lessons update lesson order and create that action.
03:38
Clear that out. Jumping back into our text editor and now into our new update lesson order validator.
03:43
For our params, we're going to want our organization of type organization, our course ID of type number,
03:50
and then our data, which we can infer from our lesson order validator. We're going to be using that validators type in a few spots.
03:57
So I'm going to go ahead and plop that into a new type just called sort data. So infer type of lesson order validator.
04:04
And then we can assign that type sort data to our rams data. And we can go ahead and extract all of those out into our handle method.
04:12
Very first, let's grab our course. So we'll await organization related courses query where the ID matches our course ID.
04:22
And then find the first or fail. First check number one, making sure that the course that we're working with actually exists in the organization. Next, we want to grab our lessons. Set that equal to await using our course.
04:32
And it has many through relationship to our lessons. Querying them all and selecting specifically their ID, module ID, and their order.
04:42
We're using our course to grab all of our lessons for the course so that we have a flat list of our lessons directly to work with. In addition to that, our lessons might have changed modules,
04:52
which could make finding a lesson a little bit more difficult going off of our data.
04:56
This is also check number two, making sure that all of the lessons that we're working with exist inside of the course that we're working with as well.
05:03
Our next task is to take our sort data, which is an array of modules with nested arrays of lessons inside of them,
05:09
and flatten it all into a flat array so that we just have a single array of lessons and then the modules that those lessons are bound to to work with.
05:17
We're going to add in a new static private method called flatten data that takes that data in, which is a type sort data.
05:24
We can return data and then reduce over our modules to end up with a flat array of lessons data.
05:31
So we'll be looping over each module to do that, starting with an empty array as our initial data.
05:38
Now we need to provide a type for that initial data so that we know what type it is that we're working with here. So we'll create a new type up here at the top of our file called lesson lat data.
05:47
And this will have an ID of type number, a module ID of type number, and then an order type number as well. And then we'll assign that type to our reducer.
05:57
So lesson lat data, just like so. And that's going to provide a type here for our initial array here as well.
06:04
So ultimately, what we want to do is as we're reducing over our modules and essentially looping over each module to get a module instance,
06:11
we want to take that modules lessons and flatten it up and then return it back, merging it with the final lessons array.
06:17
So we'll grab the modules lessons by looping over the module dot lessons using a map.
06:23
And the lessons array off of our module is an array of lesson IDs.
06:28
So we can give this a name of ID and then grab the index so that we know what the lessons order is inside of the actual module that we're looping over.
06:36
Then we'll return back a new object with that lessons ID. The modules ID that we're currently looping over, which will be the module dot ID.
06:43
And then the order, which will be the index inside of our module lessons array. That makes up our lesson flat data that we're ultimately after.
06:51
So we just need to merge that in with our final lessons array and send it on back from a reducer.
06:56
So we'll spread in our lessons and then spread in the modules lessons to the end of that.
07:02
You want to add the modules lessons after the lessons to keep the sort intact with the module data and its lessons data.
07:09
Give it a save to see whether or not the squigglies are due to formatting and they are not. So let's see what this one is. Type lesson flat data must have a symbol iterator method that returns an iterator.
07:19
Ah, yes, we're working with an array of lesson flat data. So we need to set that there to an array for our reducer type. OK, so now we need to make use of this flat data method.
07:28
So we'll do const lessons data equals this flattened data, passing our data into that. Then, just like we did with our modules, if anything here with our update fails,
07:38
we don't want to save anything as that could result in mixed and matched data at the end of the day.
07:43
So we can go ahead and await and import DB from Lucid Services DB and get ourselves a transaction going, providing in a managed transaction callback.
07:52
We're going to loop over our lessons data as it's a flat array of all of our updated values
07:57
and then find its applicable lesson inside of our lessons array to use as our actual update object. We don't need to do this sequentially. We really didn't need to do our module sequentially either,
08:07
but it's a little bit more impactful here as we have more nested data that we're working with and we're more likely to have a lot more lessons than we do modules.
08:15
So let's get ourselves an array of update promises going and we can get that by looping over our lessons data using a map.
08:23
Inside of this map, we'll extract out the ID, module ID, and order from our lessons flat data object and then start our callback.
08:31
Inside of our callback, the very first thing that we want to do is find the applicable lesson model object for the data item that we're looping over.
08:38
So we'll do that as const lesson equals lessons.find and we'll call this item so that we're not reusing the lessons variable.
08:45
And we want to find the item where the ID is equal to the lessons data ID that we're looping over. Then we can have this lesson use our transaction
08:54
and then return lesson merge in the module ID in its updated order and then save that to the database.
09:02
So if our lessons data has changed a lessons module, we'll pick that up by merging in the module ID right there
09:09
and then we're also updating the order at the same time by passing order into that merge call as well. Now we can make this a little cleaner on ourselves by doing a check to first see whether or not we found a lesson match
09:19
and if we did find a lesson match, we can check and see whether or not we need to save anything at all. So if this lessons data didn't change, if the module in order is the exact same,
09:27
we don't need to send anything to the database as nothing's changed for that lesson. So we can just skip it inside of our array rather than sending an additional call to the database.
09:35
So let's add a variable called is same that checks to see whether or not the found lesson, if we found one, has an order that matches the order that would be updated for it
09:45
and whether or not it has a module ID that matches what its updated module ID would be.
09:51
So essentially checking to see whether or not the merge data would change anything for this lesson.
09:56
If we don't have a lesson or that lessons data is the exact same as what would be updated with this loop,
10:02
then we go ahead and just return out of the callback, skipping this item and leaving it alone. Then we need to await for our promises to resolve before ending out our transaction.
10:12
So outside of our lessons data map, where we have our promises array, we want to return promise.all and then pass our promises in.
10:21
The resolved value of our promises is going to be our lesson model object or undefined if we ended up bailing out of that loop item.
10:29
By returning that promise.all back from our managed transaction callback, our managed transaction will return that same value as well.
10:36
Whatever you return from the callback of a managed transaction, it will return in favor. So we can return back the exact same data that we have in our promises here just by simply returning back the result of our DB transaction,
10:46
thus in turn ending out our update lesson order action here. So as a quick recap, before we move on to defining the route definition here,
10:54
our sort data comes through as our lesson order validator, which contains an array of modules, each having a module ID and an array of lesson IDs inside of it
11:04
with the updated sort of a module's lessons inside of it. We then line all of that data up with our lesson flat data object,
11:11
just caring about the ID of the lesson, the updated module ID for the lesson, and the updated order for the lesson as well. We do two checks.
11:19
First, that the course that we're wanting to work with actually exists inside of the organization and that all of the lessons that we're wanting to update exist inside of that course.
11:27
If any lesson IDs get patched up with our payload that don't exist in this course, we won't find it in our array here and we'll ultimately just end up skipping over it
11:36
and not saving anything to the database for it. We then start our transaction so that we ensure all of our updates succeed
11:43
or none of our updates succeed, and then loop over our flat list of updated data, finding that lesson, checking then to see whether or not anything's changed for that lesson.
11:52
If nothing has changed, then we don't worry about it and return back, moving on to the next loop item. If that lesson has changed, then we go ahead and tell it to use our transaction
12:01
to merge in the updated data and save that to the database. I'm getting rid of those question marks here because we have the check right here to see whether or not we have a lesson, meaning that those are no longer potentially undefined.
12:11
We then take the end results of all of these promises that we're returning back from our save into our promises array, ensuring that they all resolve inside of our managed transaction
12:21
and then return the result back from that managed transaction, which in turn returns it back as well. So that is the whole flow there.
12:29
Let's next jump into our lesson controller so that we can add a method for this. So we'll put this right underneath our tag as async, order, and we'll need our params,
12:38
request, response, and organization from our HTTP context.
12:43
First, validating our data using await, request, validate using, lesson, order, validator.
12:49
Then we can await, update, lesson, order, import that action, and call its handle method passing in the course ID as params.courseID,
12:59
the organization, and then the validated data. At the end of the day, we'll return, response, redirect, and send the user right on back where the request came from,
13:08
taking us then into our web routes. And this route definition is going to be a different URL structure than our other lesson routes as we need the course ID along with it.
13:17
So we'll send a router patch to slash, and since we need a course ID, we'll prefix this with slash courses, slash,
13:24
and then we'll have that course ID, slash, lessons, slash, order, and tell it to use our lessons controllers order method as lessons order.
13:33
I'm not entirely sure whether or not when we added this on the front end, if I included the course ID. So I'm going to scroll back down to the sortable modules here and scroll back up to our script.
13:43
And, oh, look at that, we never finished our thought here. I had a feeling that something was missing with this. Okay, so we want to send this using InertiaJS's router as a patch request
13:53
to, and we can use our prefix URL for this as well, since it starts with slash courses and then our course ID,
14:00
and then do slash, lessons, slash, order, providing our payload data here with a key of modules into that patch request.
14:10
And then just like we did with our order update for our modules, we want to preserve the scroll position, so we'll set that to true.
14:16
We can't call our data here modules because we already have modules used elsewhere inside of our script. So we'll call it data here and then rename it to modules for that patch request
14:25
so that it matches our validator's expected key. Okay, now that we have everything set, we should be able to jump back into our browser. We saw that do its refresh there.
14:32
And let's try moving our third lesson to first in our first. And, oh, shoot, did I do the exact same thing that I did in the last lesson? I sure did.
14:42
Okay, we need value on that prefix URL. Sorry about that. And jump back into here, undo that. Let's refresh. There we go. Okay, let's try that again.
14:50
Let's take our third lesson and move it up to first inside of our first module. And whenever I drop that, you saw that the lesson ordering actually updated,
14:58
meaning that it went out to our server and it came back with its updated ordering. So our third item went from 3.1 to now 3.0 and first went from 3.0 to now 3.1.
15:08
If I now take that third and move it over into the third module, drop it there, you see first in our first module went from 3.1 to now 3.0.
15:17
Fourth went from 3.2 to 3.1. And third is now 2.0 rather than 3.0. If we give our page a refresh, all of those orderings persist,
15:27
meaning that this is actually saved inside of our database as such. We can then take fourth, move it up to our second module, and everything there updates accordingly.
15:35
You can also see that the ordering count for the number of lessons inside of a module updated there as well. So everything seems to be working okay thus far.