FREELessons: 29Length: 8 hours

Next lesson playing in 5 seconds

  • Overview
  • Transcript

5.1 Custom Events and the Observer Pattern

We're not limited to jQuery's built-in events; we can create our own with ease! Additionally, we'll review the popular Observer Pattern (PubSub), and how it can provide increased flexibility for your projects.

Popular jQuery PubSub Implementations

5.1 Custom Events and the Observer Pattern

[SOUND] Today I'd like to give you an overview of working with Custom Events, and you'll learn how they can be used in our applications and how they can make our code far more flexible. So let's jump right in and see how we can use these. Well we know that, for example, when the user clicks on the body, we can attach an event handler, body.on click, so we're listening for the click event. If I have a heading one, I write hi there, and when the user clicks, we execute a callback function and we log clicked. When we view it in Chrome, we click on the body, and sure enough, we do get clicked, so this is all old news, we learned this in the first few lessons. Now, there are also ways to trick our events, and we haven't really gone over this just yet. But watch what happens if rather than waiting for the click, we trigger the click's handler. So once again I will, maybe, say, later in the code, we grab the body, but this time we want to trigger the handler, that is connected to the click event. But now watch what happens, I reload the page and we get clicked immediately. Remember, last time the handler will only execute on the condition that we click. So it's helpful to know that if we want to execute this code, dynamically or without the user clicking on it, we can use the trigger method to instantly do that. But now here's the cool part. You see this click, this is a built in click event, but we can also use anything we want. So if we want to create an event called clickityclack, that is perfectly acceptable. But the only thing we need to think about is jQuery has no clue how clickityclack works, how it responds, so at some point in our code, we need to trigger this clickety-clack event, and this is way jQuery is working behind the scenes. So, here, we will trigger the clickety-clack event. Reload, and we're getting the exact same thing. So this can be incredibly powerful even if you're not picking up on how just yet. We can create custom events and at specific points in our code, we can trigger these events, and you can even namespace them. So if you wanna namespace something like app, then you could have events for app.clickityclack, or app.x, app.y, and then the same thing to trigger it, you would trigger app.clickityClack. Reload, we get the same thing. You also find that a lot of people use a forward slash, sort like a directory syntax, body.on app is our namespace, and whatever the event is, clickityClack, or x, or y, that has now been successfully namespace. And that's the idea, we're simply following a naming convention to make sure that we don't interfere with maybe any other event, or maybe that we don't interfere with our own custom events, if we're dealing with a big project. And then, once again, to trigger it, trigger app/x, and now that code executes. So let's consider another example, and as we learned in the previous lesson we're going to use the getJSON method. And we are going to grab once again, and our query is going to be dogs, and the callback will be anonymous function so that the value will be assigned, and then it will execute this function. So that will receive as we've learned data, and if we wanna log the results, console.log data, reload, and now from the hx request we get the results of all tweets that are searching for dog. Okay, that's incredibly easy. But now, as we've learned, because this is an asynchronous request, it's very difficult outside here, even if you have something like var data, and then within here we say data equals results, we'll change this within here, and now we're simply, once the callback executes we are overriding this global variable and making it equal to the results. And as we've learned in the previous lesson, that's not how it works, if we try to console.log data, we're going to get undefined, and that's because this is executing before the getJSON callback occurs. Remember, this runs, then getJSON, that queries the Twitter API, but it's doing so asynchronously so we continue down the chain, and then at some point, results will be fetched and subsequently this callback function will run. So this seems like a perfect opportunity to take advantage of custom events. And as you'll learn in a future lesson, we can actually use something called deferreds to make this even easier, but, nonetheless, this will illustrate custom events nicely. So, all we're going to do within here, you can do any processing you want. But at some point we're going to make an announcement so to speak. So we will simply say, just for now, we will wrap this in a document, let's say, we'll get the document, and we're going to trigger a custom event, and this custom event will be contained within maybe a Twitter name space, and we are going to trigger the result, so we are going to say twitter/results, and that is our way of making an announcement. It's our way of saying to the rest of the application, hey, I have the results, I'm going to trigger this event, any of you that are interested in this can subscribe, and we are going to take a look at Pub/Sub shortly, you can subscribe to my announcements so that when I do make that announcement you're ready to do whatever it is that you need to do. Okay, so we will trigger results and we will pass through results or we could also pass through the arguments. For now, let's keep it simple and we pass through the results. All right, so now, what we do, is I'll get rid of this global variable and we're going to subscribe to one of these announcements. We'll wrap it within the document just for now once again, and now we're going to listen for on twitter/results, our new custom event. And when that occurs, we're going to take the results and log them. I'll reload the page, and, there we go. Now, in this case actually what we're getting is two parameters, we're getting the event passed as the first and the results as the second, like so, so let's make sure that we set e like so. Then if I reload, there we go, now we have the results. So what's really neat about this is, we can in many different parts of our application subscribe to when we have results available, so maybe you have lots more code. And then somewhere down the line, you need to do something else with that. Well, once again, you can set up another subscription and say, when we have the results, then, do something else with the results, and now in this case you'll get two objects logged. But I hope you can see the flexibility that this can provide us. So the basic concept is you subscribe and publish, and we're going to create an API for that shortly. But for now we're using jQuery's built-in event system, so trigger would refer to publish, and on or bind, that would refer to subscribe. So now these guys are subscribing to when results have been fetched from the Twitter API. And when that custom event is triggered, we do whatever it is we need to do with that data, maybe you want to update a section in the sidebar, maybe you want to update a tweet count, anything you can think of. And then alternatively, when we fetch the JSON, we can make an announcement and say hey, I have the Twitter results if any of you want it, I'm letting you know right now. So that's the basic concept. So what we'll do now, is we'll create a small little API for this, so that it's easier to wrap our heads around this Pub/Sub idea, this is actually referred to as the observer pattern. So we'll do it at the top, and we'll create a self-invoking anonymous function, and to create a little more privacy this time, let's do it a little differently, I'm going to have this function [UNKNOWN] jQuery, and then we're going to pass in the named version. So this is beneficial because it's possible that outside of this anonymous function, the dollar sign refers to something else. So to counteract that, we create a self-invoking anonymous function, and this function will receive a dollar sign and that can be represented by anything we want. Dollar sign can represent hello there, it can be a string, but in this case, we want it to represent the jQuery function, so we pass that in. And now, even though it's possible that dollar may refer to something totally different outside the function, we can be sure that within this function, dollar sign will always refer to jQuery. Okay, so with that out of the way, let's create our little API for Pub/Sub. We'll begin by creating an object, and we can call this obj or just o. And this will be new to you, you probably didn't know you can do this but we can pass an [UNKNOWN] object to jQuery, that way we return an instance of jQuery and that will be saved within the variable o. So, if at this point we check out the contents of o, I'll do console.dir, what you're going to find is that we create a new instance of jQuery that has access to all of those methods and that also will have a length of one. The next step is we want to filter through and set up our API, so we'll say jQuery.each. Now we're going to filter through the ones that we want to modify. So in our case, we know that we want trigger to point to publish. So we will say trigger is going to point to publish. Next, we know that on should refer to subscribe, so on, and subscribe. And then finally, off would be the equivalent of an unsubscription. So the basic idea is we're going to filter through these and for each one, we're going to execute a function. Now this function will accept two parameters, the first one is going to be the object name or property and the second one will be the value, so we'll change that one to key, val. So now at this point we can begin setting up our API. Ultimately what we want to be able to do is something like this, jQuery.publish, jQuery.subscribe, so to set up these core methods on the jQuery name space, it's really easy. We can simply say jQuery.subscribe, but we don't want to hard code this in, so really what we want to do is something like jQuery.val, where val is equal to publish subscribe, that way, we're doing jQuery.publish jQuery.subscribe. But we can't really do this in JavaScript. In JavaScript this would be set of property called val, so if we instead want to reference a variable name, we're going to use the array notation jQuery val. And this is an important thing to understand, just as we can use the on() method in our case, using .on. We can also reference it using the array notation document on, or maybe you want to use something like add class, you can say containter.add class or the same thing you could do add class, same way to access it, and then you would pass in your parameters here. So we're going to take advantage of that, jQuery.val or in our case, jQuery.publish, jQuery.subscribe, jQuery.unsubscribe, each of those will equal a function. And all we wanna do is when those are executed, like so, jQuery.subscribe, when that is executed, what do we want to have happen? Well, we want this function to execute, and all that function is going to do is take our jQuery instance essentially, this object right here, and we will call the native version, so we will call dot trigger. If they run jQuery.subscribe, we're going to get the jQuery instance and call the on() method, or if they run jQuery.publish, alternatively, we're going to the get jQuery instance and execute the trigger method. So we do that once again using the array notation, this is the same thing as get the jQuery instance, and we are simply going to call that function. As we've learned, we can use call, to pass our parameters as variables. And we can use apply, which is the same thing, but your parameters or the values you pass will be sent through as an array, and that's the one we're going to do here. So we call jQuery.trigger and what are we going to set to this, we're going to make that jQuery object instance equal to this, and then we're simply going to pass through any arguments that might be attached, and we can reference all arguments that a function accepts by using this keyword right here. And that's it, we've successfully created a Pub/Sub model using jQuery's event system. So there are pros and cons to doing this and attaching it to the DOM. For most small to medium-sized projects, you're going to find that you likely don't need to worry about this, if you would focus on the size of your images just a little bit more, that would be a much bigger time savings. But maybe for, for massive large projects you might look at a, a more standalone solution. In any event, this will do for now. So let's try this out. Now as we don't really have an application, once again let's do that getJSON technique. We're going to grab all tweets that reference dogs and we're going to trigger the results, but rather than using the trigger method, let's make it easier on our ears by doing publish. So now we simply say jQuery.publish, we're going to make an announcement that we have received the results from twitter, and we're going to pass through those results to anyone or anything that is interested in them, so now I can get rid of that, and we have a much nicer API. Now, later in our document, anywhere we want to, where we need to respond with that specific data, we can subscribe. So now way down here, this guy is going to subscribe because he wants to be able to do something once the results from the Twitter query are available. So once that's the case he will accept the results and execute a function. And now if we log those results, I'll reload the page, once again, we get the event object as the first parameter, so most of the time you can ignore that and you're going to focus on the second parameter. One more time and there's our object containing the results. And then at this point you could do something like get the body element and we're going to set it to HTML equal to, I don't know, let's map over the results.results, and for each one, it will accepts the object and the index. So for each one, we get this object right here as well as the index, which we won't really be taking advantage of, and all we're going to do here is maybe return obj.text, and we're going to refer to that text property. And then finally, once we've updated this array, we will simply join them together, which will turn an array into a string, and all of that will be set as the HTML of the body. So let's try it out, we come back to Chrome and we just get that huge list. If you instead want to return a list item, we're being very quick here because we've already taken a look at a, a much more in-depth solution for this, I just wanna give you an example of how you can use this. Now we have a handful of tweets that all reference dog. Now while we're here, on the forums some of you mentioned that you had trouble understanding what is, why is it different from jQuery.each? Well you wanna think of working with, mostly you're working on arrays and objects, when you want to modify an array or object, .map is going to be perfect for you. On the other hand, jQuery.each is if you just want to execute a function, for a certain number of times, for every number in this array I want to execute something, that's when you would use .each, .map is really when you want to modify and array, will return a new array. So what we're doing here is we're filtering through an array of all of those tweets, but I don't want all of them, so I'm going to filter through them, and for each one, ultimately I will create a new array, with each item being equal to a list item and the text of the tweet. That will eventually be returned, you could store that within a variable, but we're doing shorthand here, where we take the results of, we join them together, because otherwise you would have an array of tweets. So we ultimately want to return, tweet text, tweet text2, we want to turn that array into a string, so we say arr.join, and we're going to turn this array in to a string, and what are we going to use to separate it, just an empty string. So I hope you've gotten something out of this, the important thing to remember is, now that we're using this Pub/Sub or observer pattern, anywhere within your application when you need to do something with the results of the Twitter query you can now just post the subscription. We're going to subscribe to when those results are available, and then maybe 200 lines below, if you need to subscribe to those results again, you absolutely can and you don't need to worry about whether you have access to those results yet or whether you know the location of where the results are stored, you simply post a subscription, and it'll take care of the rest. So I will give you a final look at maybe a little larger version of this tweet project. So I've created a new file, and I'm simply going to paste in this project, and now what we can see here is we've taken the tweet project from a couple lessons ago, and we've expanded it, so we've set up search and replace. And we've also set up this text box so that we can by default display tweets about Tuts+ Premium but then I can type anything else, so if I wanna look for references to my user name, I can type that in, and notice that it will auto-update. So I'm simply typing and it will update that as long as I have more than three characters. If I want to look for all tweets that reference jQuery, type that in, and now that's been dynamically updated. So let's take a look at the code, and how we can do this maybe from a non-MVC approach. So we scroll down, and we've set up our Pub/Sub API, and we've created a self-invoking anonymous function, we're all familiar with this, we've created a Twitter object, and this objects is going to contain all methods, that are vital to our Twitter object. Finally you can see that we are making this Twitter object available to the global namespace, just in case they need it. Window.twitter will be equal to this init() method, so we are executing this init() method. Ultimately, the Twitter object is being returned, so now anywhere in the project you can access that Twitter namespace. Now I want you to pay attention to a few things. I want this to illustrate the facts that there are so many different ways to structure an application, you can take advantage of protoypal inheritance, you can use an MVC approach, you can use a procedural approach, you can use a variety of different patterns, the module pattern using an object literal as we're doing right here, and then there's also ways with in those models to structure your code. So this time we did it slightly different than the lesson that we worked together on. This time, we have dedicated methods for storing our subscriptions, any cached values and our events. So right here, notice that we have this init() method, and this init() method is responsible for getting everything rolling, so it sets up some properties that we need, and then it also has a method dedicated to caching anything in the DOM that we need to be able to access. So we called this method and it says, within our whole project, we want to be able to access the tweet container from anywhere, so we're saving that to this.container. That way, anywhere in our project, we can reference this.container, and we don't have to requery the DOM and this is a huge mistake that newcomers make, they just continue querying the DOM for the exact same elements over and over, make sure you cache those, so that you only query the DOM once. And then we also search for the input with an ID of q, and we cache that as well. So, once we've cached it, we take care of finding any events. In this case, we only have one, but it's likely in a project you'll have many. You want to listen for, maybe, when the user clicks on something or when they blur, and blur would be maybe if you're typing into an input and then you tab off, that would be the blur event. Here, we're taking advantage of keyup, and that is listening for when the user presses a key down and then the lift it, the callback, in this case will execute every time that occurs, and this is really helpful when you want to provide very quick results. So in this case though, we don't wanna requery Twitter, every single time the user types a key, [UKNOWN] you can imagine they type a, and then it queries everything for a, then b, now they're querying for ab and you're just slaughtering the twitter API. Now while you would probably be taking advantage of caching, for now what we're doing here is we're setting up a timer. So the user keys up, and what we do is we immediately see, if there is a timer running, and if there is, we're going to clear it, then, we set time out. And what we're saying here is, if the user has typed in at least, three characters, on that condition, we are going to update the query, so this .query would be equal to whatever the user's typed in. And then we're making an announcement, we're publishing, hey, I got the query, anyone that wants it, here it is. Now the reason why we're using a time out here, is because, what if the user keeps typing? So for example, if we didn't have that time out, I could just keep typing letters, and then it would execute ten requests to the Twitter API, and you don't wanna do that. So, what we're doing is, we press a letter and immediately what we do is clear a timeout, and that way if they press tons of letters really, really fast, all we're doing is we're clearing that timeout. But then at some point they're going to finish typing and once they do, we did our last timeout, so we clear it, and then we set a new timer that will execute in 400 milliseconds. The basic idea is that we do not want to publish that we have a new Twitter query, until the user has finished typing and that way we're not slaughtering the Twitter API with every single keystroke. So, at this point, if we come back to our subscriptions, we can see that we are subscribing to when there is a new query, and, in that case, we use fetchJSON. So this fetchJSON method will simply setup a URL to the Twitter API, and it will use the getJSON method, this should look familiar to you, and we set the URL plus whatever the user typed in and we know we have access to twitter.query because this method will not execute until there has been an announcement, that one is available. So it gets the [UNKNOWN], it takes the results and notice that we don't really do anything with it, that's not really the job of the getAdjacent method callback to do all of this, it's only job is to make an announcement. Here, within the callback, it's making the announcement, hey, I have the results of Twitter, whoever wants them, here they are, so let's see what happens. Well here, a new subscription is listening for the results, and when results are available, it executes the renderResults method. RenderResults will simply filter through that Twitter data and throw it into the DOM self.container.html. Now what we're doing right here is we are updating any URLs, so see how these are clickable, by default they won't be, so what we're doing is using regular expressions. We're gonna look for anything that begins with HTTP and then I'm looking for anything that's not a space, one or more, and that'll simply look for a URL like this, HTTP and then just keep matching until you get to a space or the end of the line, and then we do the same thing with user names. And ultimately we get done with, we have an array of our tweets, we join them together into a string, and then we throw that into whatever self.container is equal to, and if we scroll back to the top, we can remember we throw them into the ul with a class of tweets. So this is a nice, it is not MVC, you really don't need that for small projects like this, but this is nice in that everything is contained within a Twitter name space or a Twitter object. So, the final thing is, I'm gonna delete this right here, and what I want you to see is, by default, we don't see anything on the page, and then you can look for something, and we can get the results. So, let's say you built this, this is exactly how you wanted it, but now your boss says well I want when the page loads let's set up a default search for them, that way we get some promotion out of it. So he says when this page loads for the user I want you to automatically show results for Tuts+ Premium in our case. Okay, well that's really easy now that we're using the observer pattern or Pub/Sub. We know that when results are available, this .renderResults will execute, because we have a subscription for that, so whenever our application publishes twitter/results, this thought render results is going to filter through any tweets that are set on the object, and throw those onto the page. And alternatively, once a query has been provided, we have a subscription that executes fetchJSON, so we have everything in place, we only need to publish those. So why don't we do this right here? We have set a property called query, and that, most of the time, going to be equal to what the user types in but we're going to set a default value. And then, I'm going to publish an announcement, and we're going to publish twitter/query. We have a query available, so anyone that's listening for a new query can do whatever it is they need to do, and we know that, when a query is available, we are using the fetchJSON method to query the Twitter API. So let's try that out. Reload the page and as easy as that with one line of code we've impressed our boss in less than five seconds. But now he says, why I want the user to have an understanding of how this works so you need to set the value of the input, qqual to that default search type, so what he wants is, this to be equal to whatever our default search is. Okay, well that's really easy too. You can have this in its own method but for now we'll do it like so, and because we know that we have a reference, to the search input right here, corresponds to this input right here. Well because that's the case, we can simply say this.searchInput, and then we can set it's value, and with jQuery, we can use the val method and set it's val equal to whatever is stored within this dot query. Reload the page, and now we've solved everything our boss asked us, and we did it literally in less than ten seconds. So yeah, there's a lot of power to be had when using the observer pattern, or, as it's commonly referred to, Pub/Sub.

Back to the top