What do you want to learn?
Skip to main content
by Kyle Simpson
Start CourseBookmarkAdd to Channel
Table of contents
Beyond the Basics
Public vs. Private
Task 1 - Introduction
Task 1 - Events
All that was a long preamble to say, we want to attach click handlers to these two links, and there's a couple of different ways that we can do that, but the easiest is for me to say, you'll notice that there is a control, a js-controls wrapper div around the two header links. So if I were to find an element by attribute, and this is the syntax you use for finding things by attribute, we put brackets in, rel='js-controls'. What that means is go find any element in the page that has a rel attribute whose value is js-controls. So I'm saying here, go find one of those, and get me the child element, js-register. Ah, so a question about rels having more than one element. Yeah, as you'll notice here in my HTML, I've got multiple rel attributes here, and I only care about one of them. It's the js-register. Alright, so now I want to say a child element where the rel attribute has js-register somewhere in it, so that is almost the same, except we use *=js-register. That's one way of me getting it that link. You also might note that if I make js-register totally unique, then I don't even really need this part, and if I want both of these links to be attached with the same handler, which I do, I can use the compound syntax, I can do a comma here, and say rel*=js-login. So here I'm saying, go get me an element that has js-register as it's rel attribute, and another one that has js-login in its rel attribute. I know in my page there's only going to be one of each of those, so I know I now have a collection of only those two elements. There's literally a half dozen other ways that I can write that same line of code. You also should have been provided, in addition to the jsorg you would have gotten the jsorg-solutions folder, and you'll notice in that jsorg-solutions folder I have broken down task1, 2, 3, and 4. Those map to the tasks, the high level tasks that we're doing in our README. So task1 is the completed form of this task. Task2 is the completed form of task2 and so forth. So, if I took a quick little cheat peek at the solution file for header.js this is another way of doing it, which is I'm going to attach, not even to the controls, but to the header element itself, you'll notice in my index.html I also have an element here called js-header, and there's a thing called event bubbling, which means if I click on something and I don't have a handler on that the bubble will make that event fire on any of its parent elements too, so I could take advantage of attaching a click handler to the header element, and these link clicks will just bubble up for me, which is nice and convenient. So that's actually the way I'm going to prefer to do this, but like I said, there's like a dozen other ways that you could do it. (Student) Did you cover that --- what does that star mean? Star means find it anywhere in the value if you're going to have multiple values. So I'm instead going to attach to the header element. I'm going to attach a click handler, on("click"), and I want to limit my clicks to only ones that were on those js elements. So if we come back to our indx.html I want to limit it to js-register or js-login. There's a couple of different ways of going about that, but the easiest way to go about that is to give it a string that says, so it just needs to be one of the things that has a js- in it, and actually, I'm going to slightly switch from my solution file. We're going to use, or I'm going to go back to using controls rather than header here. Finally, we give it a click handler. So what we know inside of this function is that if we get this function called we know that one of our two links was clicked, which is what we wanted. We want to make sure that one of our two things was clicked. Now, in terms of event management with jQuery, you've probably, if you've had any experience with jQuery before or you've read stack overflow posts, you've probably seen people do things like return false, which we don't want to do here. So there's another way of managing this event, so that we handle it, and it doesn't bubble anywhere or do anything else, and that's to call evt.preventDefault, evt.stopPropagation, and evt.stopImmediatePropagation. So that just simply means that we're handling the event, we don't want it to bubble anywhere else, we're going to take care of it.
Task 1 - AJAX
Task 2 - Carousel
Task 2 - Details Pane
Task 3 - Header Module
Task 3 - Carousel and Details Modules
Armed with that as our header.js, I want you to do the exact same thing for the carousel file and for the details file, okay? Same mindset, same process, same syntax, but it gives you some practice doing a real world module. If you have been feeling like, wow, this is bending my brain a little bit or I'm not quite --- this is the most important of any of the exercises that I've given you today is to try your hand at it. Try your hand at doing what I just did on header.js, try doing that on the details.js or the carousel.js because, as I said earlier, there's value in the confusion because when you work through the confusion, and you have the lightbulb moment and it works that solidifies it way more than anything that I could give you as a lecture. So I will give you about 8 or 9 minutes or so. We'll come back, and I will talk you through how I do that conversion carousel.js, but try your hand at converting both of those to modules. So I have zoomed way out, so you could see it all at once, but the major patterns to be looking for, if I can use some folding magic here to kind of keep the unnecessary details away from us. The major details to look for are an outer function, which needs to run at least once. Here we only need it to run once because we only need one header instance. If we made it a regular function we could call it multiple times and get multiple modules, but we need it to run at least once, and we need to have some private stuff. So we've got some private variables and private functions in here, and then we need to have a public API that gets returned back, and that's what gets assigned to our instance variable. That pattern of doing modularization is essentially identical to every module I've ever written. Like there's little tweaks and bits and pieces of things that we do sometimes differently, but the basic concept there is an outer function with inner private functions and variables, return an object that's a public API, and that's really it. So while we can get overwhelmed a little bit sometimes in the weeds with jQuery syntax and stuff don't miss the bigger picture here, which is that this pattern is about code organization. If I gave you just a bunch of simple foo bar examples you don't really pick up on why this module pattern is so useful, so I have to give you a somewhat more complex app that's got some of these other moving parts in it for you to really pick up on why that is an improvement over what we had before. Okay, so let's quickly try our hand at it. I'll start with the details one. Let's quickly try our hand at, we need a var Details, we'll use another IIFE because we only need one of them. I'll take the ready thing out for right now. We'll put that at the bottom. There's my IIFE closed. Here's my internal private module function called loadPerson. Here is my init code, essentially. This stuff is what I'd wrap in an init function, and then I'd notice right away that I'm going to need those variables across multiple functions, so I want to take those vars out basically, and put them at the module level, so I need var items and var content, and then what's the next missing piece? I need a public API, right? So what's my public API? Init: init. And now on the outside we have Details.init. Do you see how that was the exact same pattern as what I did in the previous file. This is one of the most useful hammers that you can have in your toolbox. You're going to pull it out all the time. Bam. Bam. Just hammer the nail everywhere that you see some code organization, module is probably going to be your first, should be your first instinct. Oh, I should use a module to organize that code. Okay, so with that out of the way let's try the last one, the carousel. Oh, there's a question. We moved the var declarations out, but the question is, why are these in there? We can't run this code until we're sure that the DOM is ready, so we leave the initialization of those variables in the init, but outside of init we have the declaration, so that we can share those variables across all of our internal functions. Sorry. Init is shorthand for initialize. It's just the common name that people refer to, the kind of first initialization step of any entity. Okay, I had renamed personClicked back to loadPerson, so it's still there, it's still inside of our module. The question was, what happened to personClicked? I had removed person --- I had renamed personClicked to loadPerson in an earlier session. Okay, so let's switch to carousel. Same basic principle. Var Carousel, put in an IIFE. Take the, oh I didn't even have a doc --- oh there it is. I was like, where's my document ready? Take the document ready out, move it outside. These are all fine as private, internal functions. That's good. These are all things that are going to need to be inside of an init function, but those declarations need to come out, so I'm going to take all those vars out. Those need to be at the module level, so that they can be shared with the other functions in the module. So I'm just going to have one big var list of those (Typing). What do I need next? (Student) Return the API. We need an API, right? Public API. (Student) Yes. Either I've completely lost you or we've run out of coffee or both. Are people following along? Yell at me if I'm going too quick. (Student) Yes. Are we following it? (Student) Yes. Okay, so init: init. Now what do we put down here inside of the document ready call? (Student) What the name of the function was. Multiple students speaking. (Student) The module's carousel, right? That's the carousel --- Module Carousel.init.
Task 3 - Questions
Task 3 - Refactoring Details and Carousel
Task 3 - app.js
Task 4 - Event Emitters
Task 4 - Initializing Modules
Secure Phrase Generator
Quick question? Yeah. Is there a way that I can tell the watch to ignore certain file names? Oh yeah. If you look at the documentation for the watch module, there's all kinds of special configurations and filters and stuff. Good old Emacs creates special files. Yep. I don't see any other questions. A couple people just mentioning that they do or don't like certain things about the templates. Alright, so the next thing that we want to talk about, that's how I'm reusing my template engine. So I'm doing my rendering exactly the same in both places. And actually, let me just prove that to you. So here I show you how I'm doing it on the server. I'm calling that View.getPageHTML. If I open up web, js, Pages.js, I'm sorry, View.js, no let's look at Pages. Pages is my page controller. If I look at that, this is the thing that's running in the browser, and whenever something is asked for, like if we tried to load up a--- So this one is my click handler for lengths that I basically want to AJAXify. I'm listening for any lengths that aren't marked as being ignorable. And if I recognize the href of that tag as being an internal page that I have a template for, then I simply call this gotoPage function, which, not terribly surprisingly, is going to call View.getPageContentHTML. So it's the exact same path, and this code ends up running exactly identically between server and browser. Okay, let's see here. Would you say React would be good here? React is awesome, but I think it's a much bigger hammer than most people need. That's what I'll say. Oh, by that way, on that topic of like what are other potential options or whatever, I don't know how many of you here in person, or online, or listening to the video have heard of Ember, but it's kind of a, it's one of the big three, if you will. So there's Ember, there's Angular, there's React. It's kind of one of the big three at the moment in that whole like framework, rendering sort of thing. Now I'm not using Ember. I'm not an Ember user. I know quite a few people who are. They're really smart people. So I am not in any way, shape, or form suggesting that Ember is bad because I don't use it. It's way overkill for what I need, and that's why I don't use it. I always start with the simple stuff first rather than pull in a huge framework that is mostly what I don't need. But there's one particular point I want to bring up about Ember. And this is a praise of them. It's not a criticism at all. Just recently, Ember released a whole series of new stuff, and if you have kept up at all or read anything about it, they released a new thing called Glimmer, which is a new way of doing their rendering and stuff like that where they're looking at their template partials, they call them different things, but they're essentially template partials, and figuring out the minimum template partial that needs to be re-rendered and then redoing it. That's really, really cool stuff. And that's exactly why I built grips five years ago because I was doing that exact same thing. So I'm really excited to see a really smart set of people come along with a much bigger platform than me, and they're starting to do the same stuff. And then they have their other, their server-side engine, the Ember CLI tool, and they have, the name is escaping me, but they have now a process where you can run their rendering on the server in Node just the same as you can run the Ember app in the browser. And if you dig into the details of how that particular tool works, that's exactly the same ideas that I'm espousing here, reusing the code in both places, doing initial page load render in the server, and then letting the app take over and progressively enhance to a single-page app. That's exactly the same stuff I've been talking about here. It's entirely compatible with the ideas that I've had with middle end for years. So I'm super excited. Even though Ember is maybe more overkill than I need, I'm super excited that a really smart group of people has come to similar conclusions, and they're now pushing that forward. We're going to see a lot more people get on that bandwagon I think because there's a lot of value to this simple idea of being able to share code and have it work. Yeah, FastBoot, that's exactly what I was talking about. People doing the same things with React right now on rendering the templates on both sides. And then the same with Angular, and I know Angular2 right now, like if you look at the reading notes, they're really discussing this concept in detail. So the good news is over the next couple of years basically everybody's going to be doing it. And you can do it the simple way with no frameworks and just a little bit of code yourself, or you can use a framework, and the frameworks are going to be doing it. But the value is in the idea, not the implementation.
Adding a Shared Module
Creating a Random Number Module
But let's decide that we want to make ourselves, just because I'm not creative enough to come up with something that we have time for that's like the secure phrase generation, so I'm just literally going to create a random number module, which does nothing but call math.random. But we're going to illustrate again this point of creating some code and using it in both places. So what we want to do is first come up here to our place where I've got my hybrid server browser modules, and you notice I have one here that was kind of already a placeholder anticipation. So I'm just going to uncomment that. We're going to call our random number thing Foo for now. So uncomment that variable, and then come down here. I've got it commented out. You can uncomment that line, which is going to expect to find, as you see on line 67 there, it's going to expect to find a Foo.js file in the web.js directory. It's the place where I'm putting my hybrid shared modules. That file doesn't exist, but we're going to create it next. So I don't want to retype all that boilerplate code, so what I'm going to do is open up one of those ones that already exists in that location. So come to the js directory. Let's see, what's a good one to use? Let's use View.js. Just open up View.js, and let's take out the stuff in the inner contents of View.js. And don't save over View because you need it. Save As, and duplicate it, and call it Foo.js. One little minor change you'll want to make sure you do is change that word View there on line 5, change it to the word Foo so we get the proper naming for our module. Okay, so from a perspective of defining ourselves a module, we want to have some internal characteristics inside of our module, and then we want to have a public API that we can interact with in some way with our module. So my internal characteristics are let's suggest that I have a function that I call random, and its job is to return me a Math.random number. I know that's not terribly interesting, but I'm just showing you the pattern for how to create something that we can reuse. Now if you were not using the UMD style, if you wanted to write your code in CommonJS style in Node or you wanted to write in AMD style, then the only thing that would be extra on top of what we're doing here is that you'd need to have Browserify running, and Browserify would take your file, much like templates are automatically converted, Browserify would have a watch that would take this file and automatically convert it to something that would run in the browser. I just didn't want that step involved in our thought processes today, and that's why I've chosen the UMD, but there's really no difference in terms of the concept of what's happening on the inside. So we need a public API to express that, or expose that function, so we'll call that public API random. Now, I want to use that information on both the server and on the client. I have a module now that's being loaded into my server, but I actually want to invoke it and use it. So I'm going to come back to the server.js file, and I want to set up a--- trying to figure out the best place to put this. So I think it's going to go right after loadPage. So right before the default route we're going to make a new route. Essentially, we're going to make a Foo API, a Foo API call. So I'm going to say routes.push, put in my little comment, and then this route is a function, which we'll call the FooRoute. Takes a request object and a response object. This is exactly the same sort of thing you'd do if you were going to define a new route with Express or any of those other frameworks. You'd have to do some sort of middleware handler for it. So that's what we're going. We're making a little handler, or a little middleware for it. Now, I need to figure out if the request that's come in is one that I recognize as being something that I want to do. So I'm going to say if req.url, that's the incoming URL, if it is equal to /Foo with a capital F. And for just good measure, well, no, no, no, let's just be simple, req.url if it's /Foo. So that means we recognize that this is a route that we want to handle. So what I'm going to do then is say--- and I'm going to invoke and use my Foo module. So I have a Foo there that I can just say Foo.random on. Need to pull out a number. Since this is an API request, basically what I need to do is package it up as a JSON and send it back to the server, I mean send it back to the browser. This is all Node stuff, so basically what we're going to do is say res.writeHead, and we want to give it the 200 status response, and then we want to give it a Content-Type header of application/json. And then I want to say res.end, and the final data that I'm going to send along, I'm going to stringify an object, which has one property in it called answer that has the value num that I created. Is everybody following? So that's going to package up a JSON object with one property in it that has the response that we got. Now, I made this simple. This was a synchronous thing that I could answer because it's just random number generation. Had that needed to be something like a database request, which many real API calls do, we might've been calling off to a Foo module and asking it to do some stuff and waiting for it to respond. And it's that asynchronous process that we'd want to invoke something like a promise system so that it was clean and clear for us instead of doing a bunch of nested callbacks, a promise system. So if instead of getting num back I had gotten a promise back, then I would've said something like .then, and this code here would've been inside of my .then because it would've been, I would only want to run this when I was finished with the promise. But we're keeping things simple. We're not doing an asynchronous module at the moment, so I just can manually get that random number right away. And then these three lines of code are really, I mean these four lines code are really nothing special at all. That's exactly how Node does it. It's exactly how any framework out there sends out their responses. So the code from lines 186 to 189 is code that doesn't need to be shared with a browser because it doesn't even make any sense in a browser context. Talking about status codes and header management. None of that needs to be. But if there was something that we were doing there that we would want to share, we'd want to offload that into a module like we've offloaded Foo. Does everybody see that? So you want to look for places where there's something that does make sense to do in both and not repeat yourself. And the basic way that I'm suggesting you go about that is whenever there's something that you want to do in multiple places, that is both places, put it in its own module and do it like I just showed you how we did that Foo. Put a little wrapper around it, or invoke Browserify, or whatever, but put that code inside of its own module. Here we just have this one touch point, this Foo.random that we want to get. So we've essentially made ourselves a route for an API endpoint. But I want to also--- Actually, for now we'll just keep it simple. I keep trying to make it more complex. For now we'll just say our server uses Foo through an API call.
Calling the API
Rendering on the Page
Shared Data Validation
Somebody asked in the chat how could we do something with some shared data validation code, so let's try that. Hadn't thought about it, but we'll just on the fly try to figure it out. So, I will take my Foo.js module, and I'll make another module that I'm going to call validate. So I'll just say Save As Validate.js, take out its contents for now. Make sure to update the name there. Alright, let's start first by integrating it into the server. We could start either way, but I'm going to integrate it into the server first. So I'm going to go back to where I'm listing my modules here. List another one called Validate, and then I'll come down here and load up a Validate module. (Typing) So now I have access to that Validate module from inside of my code. So let's put some validation into our API call. Let's actually send in some, we'll need to send in some data and validate that the data that we're sending in is correct. So I'm trying to think on the fly what kind of data would be appropriate. Let's think that we need to pass in two pieces of data, a min and a max, and they should be numbers, and the min should be less than the maximum number. I'm just making up some data inputs that we could do some validation rules against. So, we'll assume that the request URL is going to receive this information through a get. So we're going to, instead of wanting saying equal to Foo, we're going to want to say the way we test this URL is to put in a regular expression. So the URL needs to start with /Foo. Now I want to parse out the parameters that might've been on my get API, so I'm going to say data is equal to, and I've got a module already loaded in called url_parser here, so it's already going to handle that URL parsing for me. (Typing) And I think just req.url. Alright, so that data's now going to be an object with whatever my get parameters on my URL were. So now we're expecting that there should be a min property and a max property, and we want those to be validated. So what we're going to first is we're going to say--- let's just assume our validations here are going to be synchronous, but you could do asynchronous validation if you invoke promises. So we're going to say we're going to invoke our Validate thing, and we're going to say checkMinMax, that'll be the method that we call it, and we're going to pass in data.min and data.max. So if that check succeeds, then we want to do this work that we were doing before. I forgot, by the way, it didn't bite us before, but I forgot that we want to always return true if we have an affirmative answer to an API request so that the 404 doesn't try to play in. But if the validation fails, then I want to do--- (Typing) Let's send back a JSON response. (Typing) And I could've pulled that error message from the validator, but I'm not going to go to that step. So let's just, we'll ask Validate.checkMinMax. It should give us a true or a false. If it's true, we'll proceed and respond with an API request. If it's false, we'll let the browser know that there was a failure by sending back a JSON object with an error in it. So now let's write that validate rule. We're going to have a function that's called checkMinMax. Takes in a min and a max number, and it, the first rule that we'll say is if--- Don't you need the data value? What's that? Don't you need the value you're checking? What do you mean? Min/max is a bound right? Yeah, but those are the things that we're passing in. We're going to use min/max to constrain our random number. Okay, got you. So we're checking to make sure that min/max is what we want it to be. So let's say that, just for simplicity, we're not going to do data transformations here. You could've done data formatting on the inbound request, but let's just do our data formatting in here. We'll make sure that min is definitely a number. So there's our coercion playing in. Make sure it's definitely a number, and make sure max is also definitely a number. You typed min. Oops. Alright, so the first way that this is going to fail is if max was less than min. We should respond back nope, that's not good. So we can just do a return false rather than sending an error message here. But that's one way that it should fail. And let's say that another way it should fail is if min is less than 0. That's also a failure case. So let's fail the validation there. And if max is great than 100. Just making stuff up. So those are our three data validation rules. Now, the point I want to make about data validation that we're going to share in this way is that this is essentially stateless data validation. We don't need to know anything about our back-end state to do this. So checking to see that an email address is well formed is a stateless validation. Checking to see that an email address is unique within your system, that's not stateless, that's stateful, and you can't really do stateful validations entirely in the browser. So we're constraining ourselves. The shared data validations are the ones that are stateless. So if all of those pass, then we just simply return true. Now this function as written does not care in any way, shape, or form what environment it's running in. It's just a data thing. It just does some data checks. So we're passing in a min and a max, and it's just going to answer true or false. Let's expose that on our public_api. Here's how we're reusing it on the server. Let's just make sure. So Validate.checkMinMax. The min and the max should definitely come in correctly, so that should work. Let's actually use those though to actually constrain our Foo.random number. So let's start out num being that random number, but that's a floating point. So if we said num times 1E9, which is a really, really big number, and we say Math.trunc, so that truncates it, so now we have a really, really big random integer. And what we want to then say, the way that we constrain it to a min and a max is that we're going to do Math. I'm sorry. We're going to take that number that's been constrained. We're going to mod it by data.max. We're going to mod it by the subtraction of those, so data.max - data.min. So if I said that the minimum should be 5 and the maximum should be 10, we'll subtract those two. So we're going to mod our big number by 5, so we're going to get values from 0 to 4, and then we're going to add back in our data.min. Don't worry if that math sounds kind of crazy. I've just done it a bunch of times. That's how you're going to constrain a random number to a particular range. So now I'm going to say that's my new num. (Typing) So now instead of sending back some kind of floating point thing, I'm sending back a piece of data that has definitely--- Now actually, now that I'm thinking about it, that logic is something we want to do in both places. We want to do that on the browser and in the server, and here we've constrained it to only happening inside of our API handler. This is something that the Foo.random method should do. So let's take that code actually, the code that I just wrote, and let's put it inside of Foo. Let me open back up Foo. Let's say that the Foo.random takes a min and a max. That's different than what the Math.random takes. So let's put this logic here. Let's say first that num is equal to Math.random, and then let's say return this stuff. And we just need to update it so it's not referring to data.max and data.min anymore. The benefit of our data validation rules are we know that max is already greater than min because we've already validated that. We would not have gotten to this point if that validation hadn't already happened. So now it's going to return me back a random number that's constrained to a particular integer range. (Typing)
Using the Validation Module
So now we want to do that same kind of check and call logic in the About, but first we need to give a way to specify those. So let's come to our template, our about.grips template, and let's add in a couple of input boxes. (Typing) So we've got two input boxes that you need to be able to set some numbers into. So in our init function, this call, this function's already starting to get long, so I'm going to refactor by pulling that out. Somebody said Foo.js is wrong. Yep, it does. Good catch. Thanks Vincent. We would've found that eventually I'm sure. Alright, so I was pulling out that function, and instead of using an inline function I want to make that its own thing because I'm going to be adding stuff to it. (Typing) Okay, let's get our min and our max out. (Typing) First step before we try it make the request is we want to do our validation, right? So I want to invoke that Validate function. I want to say if Validate.checkMinMax. Question is can I share all this code that I'm writing? Absolutely. We'll make this available as well. You're not passing in the actual values. You're passing in the--- You're right. I need to call .val here. (Typing) Alright, so if I'm local, then all I need to do is pass those locally, but if I'm doing an API, I need jQuery to add that data on. So I'm going to set up this ajax call with my data as my object min: min and max: max. (Typing) And jQuery should take care of packaging up this data object as the get parameters, sending that along to the API call, of course, which our server code that we're already written will parse that out. If I'm not forgetting anything about jQuery at least, that's how it should work. So, we are using the exact same Validate.checkMinMax rule set in both places, and the only differences in code here is that in the DOM we have to deal with DOM elements, and on the server we're dealing with request URL parameters. So those parts we don't share, but the part, the main meat of it, the validation rules are hidden away inside of our Validate module, and we're just going to share that thing in both places. It may be minor, but if you're doing it locally, you want to check the min/max, but if you're doing it remotely, wouldn't you want the remote side to do the check? So that's actually a great point. Most of the time when people write apps, that's a really good point, segue here, most of the time when people write apps, they actually want to invoke the data validation in the browser so that they can give an immediate response to the user without waiting for a roundtrip. So here we would want to validate first in the browser even though we know the server is also going to validate. And the reason for that is I'd rather pay the penalty of validating twice and letting the user get immediate feedback. If you didn't want that, if you literally just wanted the server to do it, then there wouldn't be any need to run it here, and you could throw an AJAX request against it, but the difference then would be they'd get immediate errors in a local case and non-immediate errors later. Yeah, I get that. So typically people do run data validation rules in the browser before sending the server. Your example though isn't ever going to hit the remote side validation. It'll just always be true. Except for the fact is somebody tried to hijack my app and pass data, again, that's why we have to--- what's that? Are you going to hijack your app? We can try. I can create a get URL that tries it, right? But the point is we have to validate it in both places. The server has to always validate because it can't trust that the browser did validate. We're validating in the browser so that we can give an immediate response. So if we didn't correctly pass the validation, we do want to give an immediate response to the user. So what I'm actually going to do to give an immediate response is I'm going to reuse my renderAnswer just so I don't have to do any extra work here. I'm just going to reuse my renderAnswer--- (Typing) and I'm going to render my error message directly in the same place that that random number gets through in to. So a bunch of different moving pieces. Hopefully all of these pieces--- I'm not thinking of anything that I've done wrong, but obviously there's a pretty good chance that something's broken. But let's just try it. Let's restart our server. We're only restarting our server because we actually made changes to the server code. If everything that we changed was purely browser side or template related, we don't need to restart the server at all. We just need to refresh the browser. So I'll be optimistic and close my Console. Uh-oh, something failed. Probably a good sign that something's wrong with my--- Look on your first shell. No, I've got a problem here. No, my first shell is my watch on my templates. But this is why I have a server log. It's telling me that I got a problem. What problem is it giving me? Server line 188. I'm missing a parenthesis. (Typing) Probably a good idea to check that server log before just assuming that things are working, so I'm just going to 0 it out for now. And then when I restart my server, I'm just going to watch the log. Okay, so I'm not getting any errors thrown. That's a better sign. Let's try to reload here. That was an easy fix. Alright, let's go to about. There's our input boxes. Now, we have not entered anything in yet, and we know that these empty strings are going to end up getting treated as either 0s or as NaNs depending on how that coercion ends up happening. So if we pull out the value of an input box, that ends up with an empty string, which we know that on our number coercion inside of our validator that's going to end up coercing the empty string to 0 and 0. So we would intuit that we would expect if I just tried it just as is, we'd expect a failure. Is everybody following me? We'd expect to get an error message. So let's try it. It didn't work, so what did I miss. Validate is not defined. That's interesting. So it's not finding my Validate module. I forgot to load it. Duh. Let's try that again. We're assuming that we should get an error here. We didn't get an error, we got a 0, so let's do some debugging.
The first step, I know I'm in the local path here. Let's see, about.js. So let's set ourselves up a breakpoint here, make sure that--- Actually, that's not going to be terribly helpful. Let's set ourselves up a breakpoint inside of the validator. (Typing) Normally I'd do that inside of my thing. I'm not really sure. I think it might be my cache busting or something. I'm not really sure why Chrome is freaking out on my local debugging. Let's check and see what we're getting in. We got in a 0 and a 0, which is what we're expecting. Let's look at those rules. Max less than min, which it's not. Min less than 0. Oh, so I intuited wrong. Actually, that should pass. So we probably should add ourselves an extra validation rule. So my bug isn't necessarily in. It's just that I'm missing a validation rule. We should probably say if max less than or equal to min because that's a case that we probably don't want, a min and a max equal to each other. That's not going to be very helpful. So let's just try that. I'm going to reload here. Since I changed a file that is going to be used on the server, I'm going to want to restart my server. (Typing) There we got our validation rule. Now, we didn't actually handle. I need to handle that. This is a detail I forgot. We didn't actually handle what happens if the AJAX response gives us an error back because that's one of those cases where validation could fail at the API level and not here. We know that won't actually happen, but this is a case that we should handle just in case something was broken in the browser. Maybe you had an old version of the validate function or something like that. So we want to handle the .fail case. Actually, not the .fail case. That's still going to be success, but we want to look at the response to figure out whether it had an answer or an error in it. So if it had resp.answer, then we'll render the answer, and if it had resp.error, we'll render the error. We can't really test that that stuff's going to work without trying to break our app, but that's just a piece that I don't want to forget. Okay, so let's try 3 and 10. I got back a random number, but it's not constrained properly, which lets me know that I wrote something wrong about my constraints logic. So let's go to my Foo module. So that would have been this number mod 7 plus, or mod 8, which is 0 to 7, plus the min of 3, which should've constrained us between 3 and 10. Why is that not working? (Typing) Let me verify that we're not getting something weird coming in. (Typing) Ah, we're being way too trusting about the inputs. We're converting them to numbers for the validate purpose, but we're not converting them in the Foo module, so we should be doing that. We should say min = Number(min), max = Number(max). (Typing) Let's check that. Alright, that looks better. So let's take out my debugger so I'm not going to be annoyed over and over. Appears to be working there. Let's try something slightly bigger like 10 and 50. What happens if I make the max too big? My validation rule fails. Now, let's switch this to Local. Let's give it something. Let's just verify that--- I mean to remote. Let's verify that we're going to in fact still get stuff working if we send it off to our API. Let me restart my server because I think I might've made changes that the server needs to have. (Typing) Alright, let's go between 10 and 40, and let's see what the server has to say. So it give us a 39, 38. What happens if we give something bad to the server? There we got our error message back. Actually, that error message didn't come back from the API. That was our local validation. We can very easily break our data validation to verify that the server is still backing us up, which will be the last step, and then we'll wrap for the day. In our about.js, all we need to do verify is just skip the local validate step entirely. So we're not going to do any local validation, and we're going to now try it in a non-local case and verify that the server validation is still working correctly. So 10 and 40 still works, but 10 and 400, we got back the failed from our API. Alright, so let me just recap, and then we'll finish up. I'll make sure there's--- Is there any validation preventing 0 and 0? Yeah, the validation of that they can't be less than or equal to each other. That's the test.
Kyle is a freelance developer based in Austin, TX. He runs several open-source projects (such as LabJS), writes books, and speaks at meetups and conferences.
Released23 Feb 2018