This course has been updated! We now recommend you take the Deep JavaScript Foundations, v3 course.
Transcript from the "Challenge 5: Solution, Part 2" Lesson
[00:00:00]
>> Kyle Simpson: Okay, part 2. A little bit more challenging than part one. A little bit more to weed through. Hopefully you're feeling a little bit better about what it means to think modularly, because this is as much a design pattern as it is a set of syntax. So let's talk through the ex fixed b, the part two solution, if you will.
[00:00:24] First, right off the bat, I have now a helper's object. And I went ahead and moved those three constants, max visible work description length, min work description length, and max work time. I went ahead and moved those from constants to properties on this name space object. Cuz they're also really being used by these helpers anyway.
[00:00:44] So they are not needed anywhere else other than on this name space. Now, we need to move those three functions as we described. I will need to validate work entry format, work description and format time, move that to this object. And the only thing that I needed to change about the internals of them was their reference needed to have a Helpers dot in front of this constance.
[00:01:08] So I have Helpers., Helpers.maxVisible, Helpers.maxVisible down here.
>> Kyle Simpson: Any questions about moving the static functions into that Helpers name space?
>> Kyle Simpson: Okay, this isn't a module, remember? Because it's not using encapsulation, it's not stateful. So it's not a module, but it is a helpful way to organize our code into a name space.
[00:01:38] Now, let's look at the setup UI function. This is part of what our previous [INAUDIBLE] module has. So you'll notice that the variables I moved in here were the ones that the UI-related stuff would need like the templates. These are my DOM elements that I'm gonna store references to, and importantly I have two new objects here.
[00:02:03] Which are gonna store by ID, the DOM elements that I create for each entry in my data model. So projectElements, I'm gonna store by project ID all of those project DOM elements that I create. And then, workElements, it's gonna store each one of the individual ones. So when I receive a call and a I receive a workEntry ID I'm gonna be able to take that ID, look up and find the DOM element that it's associated with.
[00:02:29] That's instead of taking my DOM elements and polluting my data model with them, it's keeping a UI data model separate from my business logic data model, okay? Now, I declared a public API, which has my init, addProject basically, the methods that are UI related, I went ahead and exposed those on my UI.
[00:02:50] Now, this function setupUI can be called, theoretically, it could be called multiple times, so we theoretically could be creating multiple UI instances. You're probably only gonna create one, but it's not a bad idea to go ahead and create it as a function just in case, maybe for testing purposes, we'd like to create multiple UI instances to mock stuff out.
[00:03:11] So, now, I have it as a function rather than as a singleton. And up here's how I created it, I created an instance of that module by calling setupUI. I store it in the UI variable, and I call UI.init. So I initialized the user interface portion of the application, all the DOM element stuff has been initialized.
[00:03:30] Now, I'm gonna call app. Now, I need to set up app function, which we don't have before, which we haven't had before. I need to pass in my UI instance, so my app knows which UI instance to talk to. That's again helpful for test mocking if we needed to have the app talk to a mock dom, mock UI if you will.
[00:03:52] So let's look at setup app. Setup app, again, on line 187 there. It takes the UI instance. Now, you'll notice I'm creating some other variables, here I've got a list of projects. So my list of projects that was in app is still here. I've got a total time variable now, because I didn't need that stored on the projects array, now it's stored as it's own variable.
[00:04:17] And my public API here has methods that relate to adding and getting data about my data model. addProject, and addWorkToProject existed, getWorkEntryCount and getWorkEntryLocation were ones that I invented, because I needed to provide information about the data model to the UI method, so it knew what to do.
[00:04:40] So let's look at those, because those two are different than what we had before. Get work entry code pretty straight forward based upon the project ID called against, I need to look up that project ID, and then return the length of its work array. So I just need to know how many work elements have been added to a particular project.
[00:05:01] Work entry locations are a little bit more involved, I not only need to find out which index, or I mean, what ID is the adjacent one to add something into the DOM with. But I also need to figure out whether, or not I should insert before, or after.
[00:05:17] So what's going on here is, it's easy to insert the data into my data model, and then just call sort on my data model, so that it's sorted by descending amount of time. But it's not so easy to do that with the DOM. The DOM I need to exactly which DOM element I need to insert after, or insert before.
[00:05:38] So the way I figure out which DOM element it is, is to ask for the current DOM element that I am dealing with. So, in other words, find the current DOM element, and then I either need to know should I insert before, or insert after. So that's what this function's about, it's just grabbing that information and returning that.
[00:05:57] I'm returning both of those pieces of information in an array, so that the UI function can use it. So we'll show you in a moment how the UI function uses it. You notice that there's nothing in my app anymore that deals with DOM elements, it's all purely data model oriented stuff.
[00:06:15] So this is a much cleaner separation of concerns.
>> Kyle Simpson: When I wanna call the UI, like for example, here on 211, when I have added a project to the data model, now I want to add that project into the UI. So I call off the UI add project to list, which creates the project entry, the box at the bottom.
[00:06:36] And then, add project selection adds the entry to my drop down for my projects. I just call those two UI methods and I pass along the data that they need. I pass along the project ID. Pass along the project description. So let's look at what those methods look like.
[00:06:58] Add project to list. Just takes the idea in the description shouldn't be even really needed to change this one, but the one thing that's different here now is line 110, where I need to store a reference to that project's DOM element, so I can use it later. So I create the project DOM element, and then I store it into that projectElements object at the projectId location.
[00:07:25]
>> Kyle Simpson: Same thing with adding project work entries, I store the workEntry in workElements at its ID location. Those objects then are letting me look up later when I need to get at some DOM element to do something with it, I now have a list of those DOM elements that I can go and look them up by ID, okay?
[00:07:48]
>> Kyle Simpson: All right, so let's follow the workflow here. In initUI, we set up a subNewWorkEntry click handler. What does submit new work entry do? Well, it grabs the data from the form like the project ID that was selected in the drop down, the description, the minutes. It validates them before, now calling Helpers.validate.
[00:08:11] And it unsets the form, so that we basically reset the form each time. And now, it calls app.addwork to project. That's a data model-oriented thing. So let's hand that data off to the data model to let it do its thing. Now, at .addworktoproject, if we come down here, we already looked at this one, this does the data model stuff.
[00:08:34] It updates the total amount of time accrued across all projects. It creates a project entry. Time, it adds to the time in the project entry. It creates a work entry for the new work that's been added and adds that, pushes that on the list. If this list is more than one, it goes ahead and sorts it.
[00:08:59] And then, we call these three UI functions. The UI function is going to say add that work entry to my list, update the project total time, that's the little thing that gives us the updated time, and update the work log total time. That's for each project. I mean, that's for the total overall versus the project.
[00:09:19] Passing in the data, so let's go look back at add work entry to list, we're taking a projectID in the workEntryData. We pass along, here is how we create those DOM elements, we save them. Here's how I use the stuff in app. I say, App.getWorkEntryCount, so I'm asking how many work entries are there.
[00:09:40] I don't want to inspect the dome to figure that out, that's a data model question, not a DOM question. So I ask the data model, how many entries are there? If there's more than one entry, then I know I need to figure out a specific location to insert this thing.
[00:09:53] If this was the only entry, I can just append it, no big deal, okay? Let's say I do need to figure out where to insert it. Well, I need to get that information, so I'm gonna store the adjacentworkentryid and the insertbefore. I am gonna get those by calling that getworkentrylocation and this syntax, if you haven't seen that before that's called array destructuring.
[00:10:17] I get back a single array from that function and I break it into it's two individual pieces of data. So, now, I say, if you told me to insert before the ID. I look for the work element of the ID and I call before. If you told me to not insert before, then I'm gonna look up that work element ID and insert it after.
[00:10:42] So that's given me the information I need to know about which element should I insert this either before, or after in the DOM.
>> Kyle Simpson: If I don't have more than one element, it's as simple as make the project entry itself visible and append the work entry into the list.
[00:11:00] A lot of that logic was already there, but I needed to figure out a clean way to separate the data model and the UI part. This is just one way of doing it. You may have chosen to keep those pieces of logic separate in a different way. This is not the only way, but I wanted to cleanly separate the semantics of what I do in the UI with DOM elements versus what the data model tracks.
[00:11:21] What the app data model tracks.
>> Kyle Simpson: We also solve the calls from app to these two UI methods update project total time, and update work log total time, those are pretty straightforward they just grab the appropriate DOM element, and update their information.
>> Kyle Simpson: So, big picture here, we now have a Helpers name space with the static Helpers on it.
[00:11:55] And we have a UI module, which its state that it tracks is all the state of the UI. And then, we have the app module, who's state is all of the business logic data model stuff. Three separate entities that do their own, that take care of their own concerns.
[00:12:13] Now, I chose a very deliberate architecture here, which might be different then the way you would have architected this app, and that's entirely okay. It was to take you down a path of understanding what can modules do for me. Many people these days seem to be enamored with components, it's the hot rage in react and all the other frameworks out there.
[00:12:31] And in the component world, we think about everything, UI, business logic, all that stuff, all in one component. So if you thought about it that way, you could have made an individual module for each of these different concerns in the page, and it would have track its own DOM and its own data model.
[00:12:51] And then, you'd have another one over here tracking its own DOM and its own data model. So you might have had a component for the form, and then you might have had another component for that total time thing that's being printed, and another component for each of the projects.
[00:13:06] And then, within those components, you'd have a component for each of the work entries within it. That's the more React style, or component style way of architecting apps. And if that feels more familiar to you, that's fine. The point I'm trying to make is that you could've done that with modules as well.
[00:13:21] That doesn't necessarily mean that you have to go the classes route like React does. We could've gone that route with modules. I wanted to illustrate a simpler way of just dividing out the behavior [COUGH] using the module pattern.
>> Kyle Simpson: What questions could I answer about this approach? So I was talking a little earlier there's a question about why didn't I use the IIFE pattern on the UI?
[00:13:48] Why'd I make a set-up UI function? Primarily I made a setup UI function instead of an IIFE, because theoretically if we were doing a test suite for this, we might wanna mock out multiple different UI instances, so I'd wanna be able to call the setup UI function multiple times.
[00:14:06] Same thing with app, I'd wanna be able to create multiple apps, so that I can mock against different UI instances. So I might wanna have multiple App instances. So it was basically just future thinking if I wanted to do testing.
>> Speaker 2: Is the helpers thing, is that a name space just for convenience's sake of this demo?
[00:14:26] Or why would that not be a module?
>> Kyle Simpson: The reason it's not a module is, because it doesn't have any state. You could make it into a module, and then it's a bunch of unnecessary boiler plate for no benefit. So for me, the best code is the simplest code that gets the job done appropriately.
[00:14:44] Here, because that stuff, by its very nature does not have any state to it, there's no reason to make it into a module, or a class, or anything else. It's just stuff that needs to be collected into a name space. So I think that's the appropriate way to organize that particular piece of code.
[00:15:04] Yeah?
>> Speaker 3: I'm not sure how to phrase this question, but when writing this, do you go into it going these are my helper functions? I know for a fact that they're not gonna have any state, so I'm gonna write it like this. Or do you end up refactoring it after the fact?
[00:15:18] Are you pre-planning that this is gonna be stateless type object, or I'm just wondering about your methodology. Or do you end up coming back through, and refactoring it? Maybe you did make it a module, thinking it was. Just how do you start writing? Cuz we're refactoring existing code, but if he starts, or I start, or someone starts writing it, then we start writing in a module pattern, and then you come back, or do you start to plan that stuff out in advance?
[00:15:43]
>> Kyle Simpson: That's actually a much bigger question than even the scope of what we're discussing within this course. So, for example, the functional course that I teach, we talk a lot more about being very careful to design real functions, meaning I got input, and I pull data back out and that sort of thing.
[00:16:01] And I've got a book on that topic. So my instinct is to look for any opportunity that I can to create real functions like that, that are stateless, and take data in and get data back out. Because, that's more amenable to functional programming principles, which makes code easier to read and verify, and all of that.
[00:16:22] But I would say in real response to your answer both. You start out hopefully looking for that probably make some mistakes and have to go back and refactor a lot of it. It is a highly irritative process. So even in making this exercise for this course I redid it a bunch of different times, cuz I kept changing my mind on not only what would be good to demonstrate to people taking the course.
[00:16:44] But also just, what's good architecture, what's a good clean separation. I didn't have that preplanned out perfectly. I figured it out as I went along and I think that's generally how I write real code is sort of, I've got a general idea on what's a good practice, but I have to figure out as the rubber meets the road, what's the best way to do that for any given piece.
[00:17:07] Unfortunately, there's no magic formula there, it's just developing the instinct for, well, if this doesn't need to be stateful, it's probably better if it isn't. Because state list code is a lot easier to verify and maintain and audit than sateful code. So I wanted to make as much of that state list as I could.
[00:17:29]
>> Speaker 4: So, in this case, is it truly a decision of separating the code which has side effects from the pure code?
>> Kyle Simpson: I mean, that's more of a question for the functional products.
>> Speaker 4: If you consistently want to refactor the direction, eventually end up having functions as a side effect code.
[00:17:57]
>> Kyle Simpson: It's not that what you're saying is wrong, I'm just saying it's an orthogonal topic. To ask should I always do this, or should I not? If you don't like functional programming you don't wanna got that direction. If you like functional programming you wanna go that direction as much as you possibly can, but that's separate from the questions of given a set of things where I do need to manage some state can the module pattern help me do that?