00:07
form dialogue and destroy confirmation dialogue via our use resource actions. So we'll do form, dialogue, destroy and on success
00:15
and use our resource actions composable there, giving that a type of module DTO. All that and then pass it a default form state
00:25
with a name set to an empty string and a status ID set to our props. Organization statuses at that first index and its ID.
00:35
Then we can scroll on down to underneath our sortable component and let's add a button in. This will be a variant of ghost,
00:43
a size of small with a class flex gap two and we'll add in a plus icon from this side be next for the class with four H four
00:53
and the text will be add module. When we click this button, we're going to want to open up our form dialogue component,
01:00
which will hold a form input that'll have a label of name, a view model of form name, error of form errors name
01:10
and a placeholder of whatever you see fit. We can do something like my cool module and then self-close that form input. Then we'll have another form input.
01:19
That's a type of select with a label of status and a view model of form status ID.
01:26
Again, add in the error form errors status ID. And then we'll have our select items that will loop over each status
01:34
in our props organization statuses. Give that a key of status ID and a value of status ID.
01:43
Inside of that select item will display the status name. For our form dialogues props, we'll have a resource of series module,
01:52
a view model with the open flag set to dialogue is open, whether or not it is editing set to dialogue resource ID
02:02
and whether or not it is processing with our form processing flag. While we're down here, let's go ahead and add in our confirm, destroy dialogue with a view model,
02:12
open flag set to destroy is open, a title of delete module and an action href with backticks
02:20
set to and we'll have a prefix URL that we'll reuse on a couple of actions. We'll add that here momentarily. But in addition to that,
02:29
we want to do slash modules slash and then reach for that destroy resource ID. We can end that dialogue out and add,
02:37
are you sure you'd like to delete your, and then add in strong tags, destroy resource name module.
02:46
All of this modules data, including lessons will be deleted forever. We can then scroll back up to our script.
02:55
We'll start by adding that prefix URL in. So for this, we can do const prefix URL equals, set this to computed and then have that return back
03:03
slash courses and reach for the course ID off of our props. Once we have that, let's add in a couple of functions. So we'll do function on create
03:12
and we'll call our dialogue value open. In addition to opening that dialogue, we also want to add focus to the first input inside of the dialogue.
03:21
So let's give this a ref of dialogue focus L. We can scroll back up to our script and add that ref in. So const dialogue focus L equals ref
03:31
and command dot to import ref. Once we have opened up our dialogue, we can go ahead and get the next thick, give the dialogue time to actually open and register.
03:40
And then we can reach for that dialogue focus L, its value, its input L, and then the actual HTML element inside of there and focus it.
03:49
We can give our on create function a copy and a paste, switching on create to on edit that will accept in a resource of type module DTO.
03:58
We want to pass that resource into the open and also reinstantiate the default form values. So name will be resource name
04:05
and the status ID will be resource status ID. We can scroll back down to our template now
04:12
and add our on edit to our button with the pencil icon. So at click on edit and pass the module in, and then we can scroll on down to our add module
04:22
and add an at click on create to that. Inside of this right hand div now within our module list, we can add in a dropdown menu
04:32
that has a dropdown menu trigger with the class margin left to text slate 400,
04:39
hover text slate 950 and an animation duration of 300. We'll add in an ellipsis vertical icon from Lucid View next with a class with 4H4
04:49
and self-close that. Underneath our trigger, we'll have our dropdown menu content with a dropdown menu item for our edit and then another one for our delete.
04:59
When edit is clicked, we'll call our on edit and pass the module in same as we are with our pencil. And for delete, when that's clicked,
05:06
we'll call our destroy open and pass that module in. Lastly, for our form dialogue, I'm gonna go ahead and break this down into separate lines. When that is submitted,
05:16
we have a create event for when we're creating a new record that we can use our form to send a post request
05:22
out to our prefix URL slash modules. When we are updating, we can use our form to send a put
05:30
out to our prefix URL slash modules slash and then reach for the dialogue resource ID.
05:39
For both of these, we wanna pass our on success in, but we also want to preserve the scroll position as well. So we'll set that to true too. I'm gonna copy that options object
05:48
and paste it into our create there as well. That then takes us into our modules controller where we can grab access to our imports.
05:56
We'll have an async store method with an HTTP context. And we can give that a copy and a paste for our update and our destroy there as well.
06:05
So update there and destroy there. We'll need an action for each of these. So let's jump into our terminal and go ahead and create that next. So node ace make action,
06:14
put this inside of a modules folder and we'll call our first one store module and create that hit up to get that command back switching store to update module up one more time
06:24
to also create one for destroy module. And we can do node ace make validator and create a module validator as well. Clear that out, jump back into our text editor
06:33
and let's start with that validator. So we'll go into validators module and we'll export const module validator equals vine with metadata
06:43
as we'll need our organization metadata and then call compile with vine object inside of it,
06:50
accepting a name of vine string with a max length of 100 and a status ID with a vine number
06:58
that exists in our organization. So we'll use our existing organization helper to verify that this exists in our statuses table.
07:07
We can use this module for both our create and edit. It's gonna be the same for both. So we can jump into our actions next and fill those out. So we'll start with, see our modules folder there. We'll start with our stores.
07:17
That's the first action we'll need. And our programs for this is going to need our organization of type organization, our course ID so that we know what course this module is applicable to.
07:27
And that's gonna be a type number and then our data which we can infer using the type of our module validator. I'm gonna go ahead and extract the organization course ID
07:37
and data out of our handle method. And the first thing that we're gonna do here is query our course. And I'll explain why here momentarily. So let's do our organization related courses query
07:47
where the ID is our course ID and then find the first or fail. The reason we're gonna grab our course here specifically
07:55
is so that we can verify it belongs to the organization this user is working with. Since we're reaching through the organization's relationship to query for the course, we know that if we get a result back,
08:04
that course does indeed belong to the organization. If we get a 404, then the course either does not exist outright or it does not belong to the organization the user is working with.
08:14
It's a small step, but since our course ID is gonna be coming from our route parameters, this is just a simple way that we can verify that course is indeed correct in terms of our data integrity. Then since we have the course,
08:24
we can go ahead and return and use that course to reach through the relationship to its modules to create our brand new module. So we can go ahead and spread the data in,
08:33
add in the organization ID as our organization ID. And then just like everything else, we need to find the next order for the module.
08:41
So we can go ahead and add in a static async private method of find next order. And guess what? We can use that course once more to do exactly that. We need to import that course type. There we go.
08:51
So we'll do const last module equals, oh wait, course reach through its relationship to our modules query,
08:58
order it by the order field in a descending fashion so that we get the highest sort first and then find the first item matching our query.
09:07
We can then return and use a ternary check to check for the last module. And if we indeed found it, then we can use that last module and its order and increment that value by one.
09:16
Otherwise we can default this to one instead of zero. And we're using a one based index here for our modules and our modules alone, because they're the only thing that's going to have
09:24
a logical order in terms of display starting at one. They typically go 1.0, 1.1, 2.0, 2.1, so on and so forth.
09:33
And we can go ahead and add a select for the order field onto our query there as well. And then await this find next order,
09:41
passing that course in to get the next order. That should do it for our store module. Now we have our update module next. Params for this are going to be the exact same.
09:49
So we'll have our organization of type organization, the module ID instead of the course ID, and then data infer type of module validator.
09:58
Again, let's extract those out of our handle method. And we can start by finding our module. So awaits organization related modules,
10:06
query where the ID matches the ID we're looking for, and then just find the first to fail. Now we don't need our course for updating our module because the module has already been bound to the course. We don't need to recheck that here.
10:16
So we can instead just await module, merge in the updated data and then save it to the database, returning the updated module back.
10:25
Taking us next to our destroy module. Now this one's gonna be a little bit different. First, we have our organization of type organization, our course ID of type number,
10:35
and then our module ID of type number. And you guessed it, that course ID indicates that we are also going to need our course for this one. Let's extract all of those out of our handle method
10:45
and start by getting our course. So we'll await organization related courses, query where ID is our course ID, and then find the first or fail.
10:55
But then from that course, we wanna get the module. So we'll await course related modules, query where the ID matches the specific module ID
11:05
that we're looking for, and then call first or fail. Next, let's get ourselves a transaction going. So we'll import DB from Lucid Services DB and call transaction and pass a callback in
11:15
so that we can use a managed transaction here. Again, a managed transaction will automatically roll back if an error occurs inside of this callback function, and then it will commit it if no errors occur
11:25
and the end of the callback function is reached, managing that flow for us. First, we wanna bind the transaction to both of these.
11:32
So we will call use transaction for both passing TRX in. So module use transaction TRX. Since we queried them individually, that transaction won't cascade through.
11:42
If we applied the transaction to our course and then queried our module, it would. First, inside of our transaction, we want to await and then delete our module.
11:52
After we've deleted our module, we want to try and decrement the order of all modules that have an order higher than the one that we're deleting. So that's where our course comes into play.
12:01
So we'll await course related modules, call the query method and query where the order is greater than the order of the module
12:11
that we are deleting. For modules that match this query, we want to decrement their order value by one, essentially filling in the void
12:19
for the module we are deleting in terms of our ordering. Then we can go ahead and return our module to finish that action out.
12:26
So if the module we are deleting has an order of 10, this query will take all other modules bound to this course
12:33
where the order is higher than 10 and decremented by one. So 11 becomes 10, 12 becomes 11, so on and so forth.
12:41
We're now ready to fill out our modules controller. So for our store, we're gonna want params, request, response, and organization from our HTTP context.
12:51
We'll start by verifying our data is correct by using await, request, validate using, and we'll use our module validator. We also need to add in our with organization metadata,
13:01
providing our organization ID there. Then we will await store module, call the handle method, pass the course ID from our params course ID.
13:11
Then we want to pass the organization and the data in as well. Once we're through all that, we can return, response, redirect, right on back.
13:21
Next for our update, we are also going to want to validate the exact same. So we'll give that a copy. And we also want our params, request, response,
13:29
and organization from our HTTP context for that too. We can paste our validator in, await, update module, call the handle method there,
13:37
passing the ID from params ID, the organization, and that validated data in. Once more, return, response, redirect, right on back once we're done.
13:47
For our destroy, again, this one being a little different, we want params, response, and organization from our HTTP context. And from that, we can just correctly call
13:57
our destroy module action and its handle method passing the course ID from our params course ID, our ID from params ID,
14:06
and then the organization in. Then just like all the others, return, response, redirect, right on back. All right, lastly, let's jump into our web routes. Underneath our courses,
14:16
we'll have a section here for our modules and we'll have a router.post slash courses, course ID slash modules.
14:24
That will use our modules controller store method as modules store. Then we'll have a router put slash courses
14:32
slash course ID slash modules slash ID using our modules controller again
14:39
and its update method as modules update. Finally, we'll have a router delete slash courses slash course ID
14:48
slash modules slash ID using our modules controllers destroy method as modules destroy.
14:58
We can give that a save and all should be good to test out now. So let's jump back into our browser and yep, that should definitely have a question mark for the property reference.
15:07
So let's jump back into, I believe that was our sortable modules component. Scroll on down, that was the confirm destroy. Yep, right there. Add the question mark in, give that a save.
15:17
Let's try that one more time. So jump back into our browser, refresh. There we go. Okay, things are looking better now. We'll start by adding in a new module. Call this test module one.
15:27
Let's change its status to planned and add series module. Cool, so not only did that get added in, but it also got added in after our introduction module. Let's add one called delete me.
15:37
Add that one in and there we go. Let's first try editing it. So we'll just tack edit onto there and switch it from not started to recorded. Update, looks good so far.
15:47
We can test out our pencil icon, get rid of the edit, update once more and hit our three dots and delete and confirm delete. And awesome, everything is working there A-okay.