00:08
then we'll layer the lessons on top of the modules after we get that done. Our very first step for this though, is to get the modules added to our courses query.
00:16
So whenever we get a specific course, we're also going to want to get the modules along with it. And that's the additional thing that we're going to return back here.
00:23
So we're going to query that separately from our course, just so it's a little easier to work with inside of our page. So let's add in another static async method, and we'll make this one private and call it
00:33
get modules. This will accept in the course of type course, and we'll import that model.
00:39
Inside of here, we're going to want to return course related, reach through our courses to its modules and query from there. For this query, we will preload the status.
00:49
And then while we're here, we can go ahead and preload in our lessons that we won't be working with that data here today.
00:55
So we'll add in an additional query for those lessons that we can preload the access level as well as the status, and then order it by the order field.
01:05
We also want to order our modules by the order field as well, both of those orders being in an ascending fashion.
01:12
Then after we've gotten our course, we want to get the module.
01:16
So do const module equals await, and then we can call this get modules passing that course, and then we can add it to our returned data.
01:25
That should be modules with an S. There we go. Now on our course show page, we're also going to display counts for how many modules and how many lessons we have.
01:32
We already have the modules count just strictly based off of the length of the modules array, but our lessons are dug into each module.
01:39
So we can make that a little bit more easier to grab for ourselves by adding a width count onto our course query that counts the lessons through our modules relationship.
01:48
Thanks to that has many through relationship that we have defined on our course model. If we jump into there, it's reaching through the module to grab the modules lessons, since
01:57
it's a has many through both of those layers. So at the end of the day, we're going to be able to directly work with our lessons from
02:03
our course, despite it not being directly bound to the course itself. And since we're returning back an additional modules object up here from our get course,
02:11
we're also going to want to dive into our controllers and our courses controller to update the show method inside of here as we need to extract that out as modules and add
02:21
it into our view state as modules and casting it to our module DTO using the from array
02:27
helper, passing those modules in that then takes us down to our courses show page. So let's dive into there next and add our modules into the props so that we have them
02:36
readily available to work with. So that'll be a type module DTO array. And just as we have here with our course, we're going to want to do something relatively similar for our modules.
02:45
So we'll do internal modules equals ref. And rather than being a shallow clone, we instead want to get a deep clone.
02:52
So for that, I'm going to use structured clone to pass in the props modules.
02:57
Now view takes in props as reps and structured clone isn't going to work properly with that. It doesn't work well with proxies.
03:06
So we need to change our prop modules from a proxy to a raw value. And we can do that with view by passing it through the two raw method.
03:14
And we went to command dot and add our import for that to view. So it just takes that props values in, gets rid of the proxy and gets us the underlying
03:21
value that we can then pass into the structured clone that we then pass into our ref so that we have it readily available to work with inside of this page.
03:28
And I'm going to go ahead and copy the structured clone portion because we also want to do that with our watch effect to capture prop changes for this as well.
03:37
So we do internal modules value equals and then pass that structured clone portion in there.
03:43
Now, we're going to go ahead and scroll down our page down to just above our course actions. And let's add in another div with a class PX2.
03:51
Inside of this div, we'll add another div with a class order bottom, order slate 200,
03:57
text slate 400, text small, adding to and margin bottom two. And inside of this divs, we're going to add in our count.
04:04
So we'll have our modules count just via modules dot length and label that with modules there. And then we'll have our lessons count by reaching through our course into the metadata and we
04:14
should have a lessons count inside of there and we can label that with lessons. Now we've come across this red squiggly for our meta here before and last time it was because it wasn't inside of our DTO.
04:24
So let's jump into our course DTO and sure enough, we need to add it here as well. So we can declare meta as type record string any and then scroll down to our constructor
04:34
and add this meta equals course and then pass it in the extras object. Give that a save. And now we should be able to jump back into our show page and that'll be happy.
04:43
At present, we don't have any modules or lessons bound to any of our courses. So this is just going to both be zero. So we can move onward to listing out our modules.
04:51
Now so that our page doesn't get too long, we're going to plop these into another component. So we'll right click on our components, new file, and we'll call this our sortable modules dot view.
05:00
We'll have a script set up lang TS. We're going to want to import and I'm going to call this sortable again from view dragable.
05:08
And then we can define our props with a type accepting in the organization of type organization course is course DTO.
05:17
And then we also want the model value and that'll be of type module DTO array. So the model value here for our view component is going to be the list of modules that we
05:26
want to actually display inside of our sortable component. So if we begin to add our templates in, we'll start with that sortable component and our view model for this.
05:36
At the end of the day, we want to sync with that model value. Just as we've done in the past, that means that we're going to want to go through a computed property. If we can call this variable modules, since our actual modules are coming through as the
05:46
model value and not named modules, and then we can set this to a computed view property
05:50
with a getter passing through the props model value and a setter that takes in the new value. And we want to also grab our emit.
05:59
So const emit equals define emits, and we want to update model value.
06:04
And we can emit that update model value with our new modules value, now giving us the ability
06:10
to jump back to our sortable component and pass our modules in as the view model. As updates happen to our sortable, it will go out to our modules, call the setter, emit
06:19
that up to the parent component, which updates the model value, which updates the getter, that whole view model circle again that we've mentioned in the past.
06:27
And just as we did with our difficulties and such, we want to add our template with our item inside of our sortable component that passes through the item as an element.
06:36
And we can freely rename that back to module so that it is a little bit more verbose to us as to what the actual value there is.
06:43
Before we fill out our template, let's go ahead and round out our sortable props. First, the key of each of the items inside of our view model is ID.
06:52
So we want to specify the item key as ID there. And we'll have this be a tag of ul whenever it renders through to raw HTML, giving us
07:00
headway to have each of our elements as allies. And in addition to this, whenever we get through to our lessons, we're going to add in another
07:06
layer of sortable elements for each one of the modules that we have, listing out each of the lessons inside of the module so that the sortable component knows what's what.
07:15
With that, we want to add in a group name that we can call modules or our modules layer. And then whenever we get through to our lessons, we'll give that a group name of lessons so
07:24
that the sortable component is going to be able to differentiate between the two entities that we have sortable on the page.
07:31
Lastly, we want our handle to be controlled by the element with a handle selector that we'll add into our ally. Now we just need to fill out our ally.
07:39
So let's add a class flex flex call order bottom order slate 200 adding bottom to margin bottom two.
07:47
And then we'll want another div inside of this ally with a class flex item center justified
07:53
between rounded MD P2 hover BG slate 50.
07:59
Give a duration of 300 on that hover animation, add group to this, and then make it relative as well. We're going to have two sides to this div now.
08:08
First is the sort handle, the name and the lessons count for the module itself. So we'll have flex item center and gap of four on this side, and that'll be on the left hand side.
08:18
Then to the right hand side, we'll have another div flex gap to items center, and we'll justify that to the end.
08:25
And this is where our actions are going to end up residing, which will be that same tag selector that we created prior to be able to change the module status as well as the edit and delete dialogues.
08:35
So we'll get to this right hand side in a later lesson. For now, let's continue focusing on our details.
08:40
So we'll have a span with a class of font bold, and this one's going to hold our modules name.
08:47
Then we're going to have another span with a class text plate, 400 text, small flashed zero hidden MD in line block.
08:55
That's going to hold our module lessons length value, which will end up being the number of lessons inside of the module.
09:03
When it comes to editing, we'll add in a little pencil icon when this is hovered after the lessons count.
09:09
So we'll have this default to an opacity of zero with a group hover opacity of 100 at a
09:15
duration 300 to that animation, margin left of two, and then make that relative inside of there.
09:21
We'll add in a button with a variant of ghost, a size of icon, a class of absolute left zero
09:28
up one half negative translate Y one half, which we'll just vertically center it. And then at a width of seven and a height of seven and inside of the button, we'll add
09:38
in a pencil icon from new side of you next with the class with 3.5 H 3.5.
09:44
That should leave us with just needing to implement our handle icon and selected element for our sortable component.
09:51
And we'll put that before we have the module name for this, we'll add a div with a class text slate, 400 hover text slates, nine 50.
10:01
We'll add in that handle selector class for the sortable component. Specify the cursor to be the move cursor, add in the opacity zero, and then a group
10:09
hover opacity of 100 with a animation duration of 300 on that set this to be absolute.
10:16
And then top one half negative translate Y one half again, vertically centering it and
10:21
then padding left up to, then we'll use the grip vertical icon from this side of you next giving it a class with four H four, and I believe that should do it.
10:31
So let's give that a save. And then we need to implement this on our show page next.
10:36
So let's dive into here and add in our sortable modules with a view model of internal modules
10:43
passing in the organization from our organization prop and the course from our course prop. Give that a save. And we should be able to jump into our browser.
10:52
Now let's try giving the page here a refresh and okay. So we have zero modules and zero lessons. So our count looks good. And then we don't have any modules.
11:01
Now we do indeed want to verify that things are actually working here before we round this lesson out. So let's dive back into our terminal, jump into a node ACE REPL session and await load
11:10
models. Now I don't quite remember what organization it is that we're working with. So I'm going to right click on the page here, inspect, open up the view dev tools, and we
11:19
should be able to just jump in any one of our pages here, take a look at organization and it's got an ID of two while I'm in here. All that I care about is one of our statuses. So I'm just going to grab a status ID from here.
11:29
Looks like six is not started and I'll tell you what, while we're in here, we also need to know our courses ID. So let's take a look at that and it is one.
11:36
OK, so organization is two, status is six and course is one. Let's jump back into our REPL session and we should be able to await models module and
11:46
call create to create a module with a name and we'll just call this introduction.
11:51
Give it an organization ID of two, a course ID of one and a status ID of six and that
12:00
object out and run it. And OK, cool. There we go. So we now have created our module. If we jump back into our browser, we can close our dev tools out, give it a refresh.
12:10
And that is not what I was expecting in the slightest. So let's go ahead and right click, inspect, jump into our console and let's refresh the page.
12:19
Some of these hydration mismatches I've been expecting. Let's see if they're all ones that I would be expecting to be here. So let's scroll up through these warnings.
12:28
And it looks like the first one is trying to read undefined off of one of our length calls. So that could be a potential issue.
12:35
These other hydration warnings, if we continue scrolling up, these ones for the RADIX view drop down menu triggers, those are hydration mismatches that I would be expecting at this
12:45
point. And it's a view 3.4 and ShodCM view issue where the trigger ID differs from what's on the server and what's on the client.
12:53
So if you take a look at the server version, it's got an ID of 24 and then the client has an ID of six. View 3.5 has a fix for that. We'll be able to update later on and get that all fixed up.
13:03
So we're ignoring these drop down menu triggers or the trigger hydration mismatch warnings at present as a heads up. And it looks like the only one that we have here that we really care about at this point
13:12
is our undefined on reading length. So let's dive into our view dev tools, jump into our app layout, jump into the show and let's make sure that everything's getting populated here. So we have our modules.
13:22
Our modules do not have lessons. So that's probably where our issue is as one of the lengths that we're trying to read is off of our lessons property.
13:30
So yeah, let's go try to fix that real quick. Jump back into our text editor here. That is most likely a DTO issue. So let's dive into the DTO for our module.
13:40
We'll declare lessons lesson DTO array as the type for that and drop down and do this
13:47
lessons equals lesson DTO dot from array passing the module lessons in. Give that a save. Let's try jumping back into our browser now.
13:57
Let's give it a sanity refresh here. Let's check our. There we go. Okay. So now we have our module listed out here and when we hover over it, we see our little
14:05
pencil icon and we also see the move icon, but it's not quite where it should be. So let's fix that up before we end things here.
14:13
So let's jump back into our text editor, back into our sortable modules page and let's scroll down to where we have that move icon. Our issues most likely to do with the absolute positioning. Yeah.
14:22
So we have the top one half negative translate Y one half, but we are missing right full. So let's add that in, give that a save and let's try jumping back into our browser. Now over it.
14:32
There we go. That's the position I'm looking for. Cool. So we should be good to move forth now and we'll focus on our module crud and then we'll fill the lessons in once we have more than one module here that we're working with.