What do you want to learn?
Skip to main content
Application Building Patterns with Backbone.js
by Rob Conery
In this production we build a single page application with BackboneJS that breaks the bonds of the "silly demo." No more To-do lists - we're building an Open Source MongoDB document explorer, a perfect use case for a Single Page App powered by...
Start CourseBookmarkAdd to Channel
Table of contents
What is BackboneJS? Who Cares?
What is BackboneJS and Why Use It?
Installation and Setup
I'm not going to focus on Node server bits too much. I'll give a quick explanation of the code we're going to use and I highly recommend you take this start directory that we have offered in our code downloads and drop the files just directly into WebMatrix from there. Or better yet, just open the Start folder and the code download as a site in WebMatrix. Let's take a quick walk through the code so you're at least familiar with what we're doing on the server. If you don't understand what you're seeing here, that's okay. This is simply a website that's exposing data from MongoDB via JSON. It's not critical to completing our mission of creating a Backbone app. As you can see here, we have our script file style sheets and images sitting happily under the public folder. If you're wondering what those green checkmarks are, that's a Git plugin I've installed with WebMatrix. It's really handy. Next, let's take a look at the lib directory, specifically Congo.js. In here, I've added some routes to our application that Backbone will use later on. Routes are defined much like Sinatra where app is our application, get or post is a method invoked based on the HTTP verb, and the route matcher is the string value that's passed into that method. Parameters are preceded with colons and then a callback handles the response. The rest of what you see is simply accessing Mongo using Mongoskin and Node.js driver for working with MongoDB. I'm not going to spend any time on this, but I do encourage you to check out the project on GitHub or explore the code here in our application. So, let's run this and take a look at the initial app, making sure we don't have any 404 load errors and that our CSS is correctly applied. And, there we are. We're up and running on Node. Now, I have to say I'm happily surprised at how well Node works with WebMatrix and the IIS Node module. There are some great advantages, mainly that IIS Node handles the Node process seamlessly for you. Now developing directly against Node.exe, you have to restart the process every time you make a change to your application. You can install washer programs that reload things for you, like Supervisor, which is a nice Node module. However, many of them don't work on Windows due to using Unix-based file monitors. Anyway, we're all set up and rocking WebMatrix, so now let's write some code.
A Simple View
Getting Started and A Simple View
Wiring up a View Event
Why all this ceremony for displaying HTML? Well, we have a link that we've embedded. What happens when you click it? Right now, nothing. And, since this is a single-page application, you don't want that link to be working because you don't want to be navigating the browser away and refreshing the page, thus restarting your application. So, what do you do? Well, you wire up an event to handle that. And, let's wire it up and see how that works. To set up DOM events for a view, you wire them up by sending them in with your object literal when you declare your Backbone view. Here, you specify the selector syntax for the event, then the name of the function you want to invoke. This needs to be a string, by the way. Don't pass a function reference here for the event or Backbone won't understand what you want. Next, you have to declare the function to be called. Here, I'm just going to pop an alert box. Let's see how this works. Refreshing the browser, clicking the link, and boom. Now, you may be wondering, "Who cares?" Well, you do. Wiring your events to dynamic DOM elements can be tricky, especially when you have dynamic elements all over your page and they're working with other dynamic elements and underlying data. Now, it's not impossible to do this to be sure. All you need is a bit of organization. And, that's precisely what Backbone gives you. A little bit of organization. It's not a big library at all. Just over 600 lines of code.
Organizing and Summary
We've covered some ground so far showing how to render a view to a page. We've talked a bit about the view's DOM element, el, which we will talk about more later on. And, we've wired up some events. We've also started to create a bit of a mess, so let's take a second and organize things. People have different approaches to organizing their Backbone files. Personally, I like to organize them by purpose. We're working on navigation. So, let's create a file in the Congo directory and I'm going to call mine nav.js. In here, I'll put the code for the view. Basically a wholesale copy and paste. Next, I'll put the wire up in the init function inside Congo.init. So, in here I will instantiate my view and then I will render it immediately. Finally, I'm going to go back here and add a reference to my new script and then wire everything to startup when the DOM loads using the JQuery's document ready function. Let's see if it works. Refreshing our browser. And, everything's working just fine. Checking our console to make sure we don't have any errors and we do not. We got our feet wet a little bit today by rendering out the very first view. It was incredibly simple. But, that's okay. We're going to get more complicated later on. We introduced some concepts here that if you've never seen Backbone, it might seem a little bit weird, so we needed to take it slowly. We also wired a simple event to that view to pop an alert box. And, we've kept things organized. In the next part, we'll do it all over again, but we're going to transition to nested views backed by some data that render in response to the events on that data. That's the end of this episode. I'll see you again next time.
The List View
Creating a List View
Using a Template
Organizing and Summary
For right now, let's take a look ahead at what's coming up next. And, I want to show you if I go to the Mongo-api DBs, well, there's the databases that are inside my local MongoDB instance. That is some really nice JSON that we can work with. But, how do we work with it? Well, next time we're going to tackle working with data and I'm going to show you how to marry up the data with our view. For now, I'll give you a sneak peak. Let's create a brand new object called Congo.databases. And, I'm going to extend a brand new thing we haven't worked with called a Backbone.collection. And, it is going to have a URL to it and it's the URL that we just entered. Next, I'll add Congo.database, which is a model. Use the two bits of data constructs that Backbone gives you. Then, I've got to tell my collection what model it's going to work with. And, can you guess what that is? It's the database. So, Databases is a collection of our Congo database model instances. Whoop-de-do. Who cares? Well, you do. Let's go back to our favorite tool here, our console. Refresh. And, inside of here, I'll create a brand new variable. I'll create it to our collection databases. And, then I can use a simple method here called "Fetch". Fetch is going to go out to our URL and return some data. If I drop this thing down, well you can see it's just an Ajax, Ajax call object. But, if I say dbs.models now, I have two objects in there. Let's drop those objects open and take a look at what those are. Take a look at the attributes and boom. There's a URL and there is the name of a database. It is just that easy to go grab data. That's coming next time. We will wire up our collections and our models and you're going to start seeing the simplicity and power of Backbone emerge.
Working with Collections and Data
bind() and an Event Driven Model
Well, it's neat that it works, but it really is not very good code. There is nothing responsive or event-driven about this process. If you look at the code inside the initialization of our app, I am synchronously fetching the data and then I'm instantiating the list view and telling it to render. And, it's just not a good thing. So, what can we do instead? Well, let's take a look once again at our database's collection. So, if I come down and select databases, now we can work with it and I can bind an event to our databases. Let's bind a reset event that gets fired whenever the data is, well, completely reset inside the collection. And, in this case, I'll just pass in a callback function that's going to log something to the screen. So, whenever the collection's reset, we should see console.log. And, how do we reset the collection? We call fetch. And, there it is. "I been reset". That's good. That's what we want to see. Let's bind something else. In addition to binding collections, you can also bind models. We'll be doing this a lot in this production. So, I'm going to bind the first model inside of our collection and I'm going to bind the change event. And, once again, I will use an inline anonymous function and I will just log something out to the screen, just say "model reset". Well, how do we fire the change event on the model? Well, we can just reach into the models. Again, I'll take the first one and I'll use set, that's how you set a value on a model, and I'll set the name to Steve. Boom. The change event gets fired. In fact, you can do an event binding on a collection for a change event on one of its models. In this case, let's just have a change event be fired if the name is changed on a model that the collection contains. And, once again, I'll log this out. My model has been changed. The binding is set. So now, let's set the name once more and we'll set it to something else like Bill. Notice that two events were fired. The model was changed and the model was reset. So, as you can see, there's lots of different event bindings that you can set up for your models and collections. We'll talk more about those event bindings later on. Well, this is good news for us. That means we can clean things up here a lot. So, let's get rid of this fetch method. I'll need to, of course, instantiate the view and also our collection. But, I want to get rid of everything else. And, what I'll do is I'll go down to our database list view and I'll create an initialize function that gets fired a bit like a constructor. It gets fired whenever the database list view is instantiated. Then, inside of here, what I'm going to do now is I'm going to bind some events. So, just like you saw me do in the console, I'll say this.collection.bindreset and then I'll bind that reset event to this.render so the view will render itself. I'll do the same thing for add and remove. Add will get fired whenever I add a model and remove will get fired, you know, whenever I remove a model. We've introduced a new concept here. So, let's take a second and dive into it. We managed to avoid a very important method that you'll be using a lot of: The initialize method of the view. As you can imagine, it gets kicked off whenever the view is instantiated. And, you can do all kinds of things in here, but mostly you'll be wiring up events and various settings. In our case, we know that we're going to have a database collection backing this database list view. The view is useless otherwise. And, we can use that collection's eventing to cause our view to render itself. Now, for many web developers, this is a backwards concept. We're used to shoving HTML around in a purposeful way, telling things where to render, how, and when. We don't necessarily write code that reacts to events on other objects. Which brings me to Rob's rule of Backbone development: Views respond. If you've ever done any kind of desktop development, well, you know that responding to events is key to making a scaleable application. Backbone works the same way. You don't orchestrate what, when, and where, and how a view renders on a page. The view does that in response to a change in underlying data or some input from the user. When I was learning Backbone and single-page app development in general, this was a key bit of understanding for me. These apps share more with the desktop development experience than they do with the web development experience. And, if you don't quite get what I'm talking about, don't worry. I'll be touching on this point repeatedly throughout this production. It will slowly sink in, I promise. For now, look over the code above and see that we're binding the reset, add, and remove events on our collection to the render method of the view. That means that every time we call fetch, every time we add a model, and every time we remove a model, the view will render itself. The last "this" that you see there is simply to make sure that the scope is set properly for the binding, which is the current view. We're going to talk more about that later on. Binding events to view is key to a clean, well-coded Backbone application. We will be revisiting this topic many times. For now, it bears repeating: Views respond. They respond to events that you wire up for the user and events that you wire into the underlying models and collections. All right, well, we have bound our view to render itself when reset is called. How do we get it to be called? Well, we simply call "fetch". Now, if we go back and refresh, look at that. Much cleaner. That is the Backbone way.
Refactoring for Clarity
Wiring Up Add() Functionality
Well, we've come a long way, but let's take a moment and review what we need to do next. This is the completed application as you can see here. I'm adding a new database and I can also delete a database. So, we lack the ability to add and delete. That's what we're going to be adding in today. This is what we have now, which is just a bunch of placeholder stuff, a button that says "Click Me" that doesn't really do anything. So, I'm going to start off by adding in a section here using some bootstrap styling so we can have add form. And, I'll drop the form right in here. And, just with the power of editing, have some HTML. There we go. So, this is a simple form in the Jade view engine that's going to allow me to add a database. Refreshing. And, there it is. Let's also take a second and pretty up our database table here. And, I'm going to change out that HTML. I'm going to change it to use Jade. And, yes, Jade will work inside of a script template. Interesting stuff. So, there's our Delete buttons. Let's change the height of the names. There we go. I'm going to change those to h4s, as you can see. That looks much better. Okay, well let's flip back to the code and I need to create a new view for our database options area there. So, I will extend the Backbone.view as you've seen me do before. And, as we watch this process unfold, it's going to be the first time I mention Boilerplate. Code you write over and over and over again. Initialize and render. You start to see these patterns emerge more and more. We're going to talk about that in a later episode. Just keep it in the back of your mind for now. Well, for the render function to work, I'm going to be sucking the template out of a script tag like I've done before. So, I just need to create that really quickly and make sure, once again worth mentioning, that you set the type on the script tag so that the browser doesn't render it. In this case, I'm just calling it text/backbone. Then I just move the Jade right into our template just like that. So, now our form should be working fine. Switching back over to our code, we can finish off our render function. Again, we need to use the HTML method, jQuery's HTML method, and drop in our compiled template into this.el. Now, that's not going to work, of course, unless this is a jQuery object. So, we have to go in and wrap this. Now, this used to always be the case. You have to wrap this every time with jQuery and it became a bit of a bore. That is until Backbone decided to give you a shortcut: this.$el makes the DOM element, this.el, into a jQuery object, which is really handy. One other thing to notice while I have this zoomed in is that in our initialize function, I am calling this.render. So, whenever I instantiate a new database option view, it renders itself. I will take advantage of that here in my database view because we want the option view to always render when our list view renders. So, what I'll do is I'll create a brand new function called renderOptionView. And, here I am simply going to instantiate a brand new database option view. Then, I'm going to pass in the element that I want it rendered onto. So, in this case, I just need to go back over here and there it is: DB Options. So, I'm passing in the element this time. I'm not letting it create its own element. Okay. So now that I've done that, I just need to call it. And, I will call this.renderoptionview from initialize. So, let's go back and see if it will work. Heading back over to the browser, let's refresh. Whoops. Well, if you see an error like this and you know it's defined, that typically means you have a namespacing problem. And, indeed I do. I forgot to put Congo. and so let's refresh this. And, there it is. Okay, well. Being good programmers, you might be thinking, "Is this really how it's supposed to be done?" And, the answer is not really. I'm going to talk about that later on in this episode and also in future episodes. You've got to build the case for it, though, right now, so hang tight. So, let's wire in the submit event for the form. You've seen me do this before. We just have to wire up submit and then the search element form. And, then I'm going to create a function called addDb. And addDb, for right now, what I'm going to do is something rather simple. When you handle the submit event of a form, well, it doesn't mean that it intercepts it, if you will. You still have to cancel that form submit. So, that's the first thing we're going to do. It's a jQuery event and so the event information will be passed right in. So, I wanted to make sure I use event.preventdefault. And, a lot of people return "false". That's not a good idea, that messes up the event chaining. But, right now if I click addDb, the form isn't firing, that is step 1. That is something you'll have to do a lot of when you bind to the submit of a form. Once again, Boilerplate. Well, again, we're going to talk about Boilerplate more in later episodes. The next thing I'm going to do is create a brand new model. So, the newDb equals a new Congo database. And then, what do I pass into that? Well, what we're going to do is we're going to use jQuery to pull out the name. So, I will call it newDbName, put it into a variable, and I'll use jQuery to take a look at our form input, which has the ID of newDb. So, using that, I will pull out val and that will pull out the name of our brand new database that I've entered into that input. And, inside here, I will just specify inside of an option I want name set to newDb name. And, that's how you new up a model in Backbone. So, let's take a look at what that model looks like. I will just output it straight to the console. And, heading back over to our page, let's refresh and I will enter some silliness, and there is our model. And, you can see it's got a bunch of stuff that Backbone has put onto it including attributes. And, one of the attributes is a name and that is the name that I entered. So, everything is looking as we might expect. Big deal. Well, let's play around with our console a little bit more. I'm going to instantiate a brand new Congo.database and let's call it "Steve" for right now. And, the first thing I'm going to want to do is send it to JSON so we can take a look at the object itself. There it is, name, Steve, okay. So, let's save it. Backbone allows us to just call a save method. And, it should propagate it back to the server, but it doesn't. Uh-oh. A URL property or function must be specified. Okay. Well, we already did this for our collection, so the model needs a URL as well. That way, Backbone knows where to send it: Back to the server. All right. So now, let's try this one more time. I'll instantiate a brand new model and then I'm going to call save. And, we get an error. It's a 404. Can't post to the URL that we offered. Why is it trying to post? Well, because it's brand new. Backbone uses the same RESTful convention that Rails and other frameworks use. If it's a new object, meaning the ID is not defined as you see here, well, it's going to try and do a post. Many frameworks lean on this RESTful style of pivoting on HTTP verbs. Backbone is no exception. Whenever you have to create a record, a post is sent to your server. If you're updating one, well, it's put and then also destroy fires delete. This is going to influence how you set up your routes on your back-end. We're going to see more of this in just a second. Now, we have a problem. If I go to our Mongo shell and look at the routine I'm using to pull back the databases, well, databases don't have IDs, they just have names. So, looking at this list, what uniquely identifies our database? Well, it's the name. So, how do we tell Backbone that? Well, we can reset this, and often you find yourself resetting what Backbone considers to be the ID by using the ID attribute. And, this time we'll set it to "name". If you don't specify this, Backbone's convention is to look for a thing that says "ID". Okay, let's do this again. I'm going to create a brand new Congo database and, look it, our DB.ID is "Steve". That's what we want to see. So, now if I call save, oh, close. Now, notice that it does a put because it has an ID. It uses the convention of using the put verb, the HTTP verb, if the ID exists, meaning it thinks it's an update. So, let's head back down. We can see, there it is, app.put, that's our routine. It's expecting a database name to be sent in through the URL. We don't have a database name as part of our URL. So, what I need to do is I need to set URL to be a function that is going to return a string. Well, what string? Well, let's return Mongo API DBs, but this time, I'm going to concatenate in the ID. And, you might think this looks nuts. Well, it kind of is. But, it's a pattern that you'll get used to when you're working with non-Railsy, if you will, applications that have different ID names other than just plain old "ID". Okay, well, that is our fix for now. Let's flip back over to our browser, refresh it, reload all of our DOM stuff. Once again, I'll create a brand new model in the console window and I'll hit save. And, boom. Look at that. I think it worked. I'll refresh the screen. Hey, there's Steve. Steve got popped into our database. Although probably not exactly the way Backbone wants us to do it, that's okay. The goal here is to show you how the mechanism works and also that you can get around it if you need to. All right, so, in my addDB function, I am going to just save my model. And then, when it's saved, I am going to call Add on our collection and add in the model to our collection. Now, why do we care about that? Because when we add a new item to the collection, well, guess what's bound to it? The render method of the list view. Remember how I always say "Views react", so our view is going to react to a brand new database, it's going to re-render itself. That's exciting. So, let's go and keep with our guys' names from the 50s. I've added it and then, look at that. It shows right up. That's exciting. So exciting, I'm going to do it again. Joe. Woo-hoo.
Adding a Delete() Function
All right, well, let's continue on here. You might be thinking, "Well, that's kind of silly. What if someone puts in silliness for a name or nothing at all?" Well, the neat thing is that models have the ability to validate information and you do that by passing in a validate function. That function will receive the attributes of the model. So, what we can do is we can interrogate these. And, we can say is the attribute name empty? Is the attribute's name null? Is it defined? Well, this gets pretty tedious rather quickly for what is really just a check to see if the thing is even there. Well, the good news is we can use underscore and we can just say _.isempty and we can check for the attribute's name. Working with validations in Backbone can be really confusing. At least it confused me when I started working with them. It's tempting to think that you're working with the instance, that you should be able to say something like this.name shouldn't be empty, for example. But, that's not what you're doing. You're actually extending the Backbone.model.prototype and instead of working directly against an instance, you're handed a variable. In this case, a bunch of attributes that I've named "atts". But, it gets more complicated still. Atts only contains the things that have changed. It does not necessarily contain all of the information on your model. You're just handed stuff that's changed. In our case, since I've just added a name, the name should be on there and I can check it using _.isempty. We will come back to validations later on. That is just a simple primer. Okay, so, I've got this in here. Now, let's take a closer look at our validations using our console. All right, well ,I'll start by creating a brand new instance of our Congo database. And, we'll ask if this thing is valid. And, like we expect: False. There's no name present. So, it's not valid. Can we save it? Let's go to db.save. Uh-oh. That should be false as well. Refreshing and, oh no. DBs. Can you spot what the problem is? Let's go back and create a new database instance again. We're going to say db.set and we'll set the name, in this case, to "null". Let's see what happens. False. It wasn't set because it was null, which means it was empty and our validation got fired as we'd expect. db.getname, there it is. It's undefined, it's not there, it was never set to begin with. db.isvalid, false. The way Backbone works is that if the record's not valid, an error will be thrown and save will not be called. That's the way validations are supposed to work. You can trap that error like I'm doing here. You can say db.on error, let's bind the error event, and I'll just output that to the console. So, let's try and set one more time. We'll set the name to "null" and, boom. We need a name. You've logged that out to the console. That is everything you would expect to see. Now, when we try save, error gets thrown, but the object still gets saved back to our server. Welcome to the Backbone weaves. What's going on here? This is completely unexpected. This goes totally against the way Backbone should work. Save should never be called. Let's go and click on our undefined here. This is our post off to our server. Yep, it's doing a post. As you can see, it's pulling out the URL. It's passing undefined to our server. So, why is our server entering the term DBs? Well, looking through the routes, we can scroll on down. Where is app.post? Something that listens out for a post request. Aha, I see it right there. That is the route that is trapping our errant request. And, it's pulling in the name as DBs because, well, it's part of the URL. And, the way that Mongo works, if the database isn't present when you request it, it will make it for you. Well, if the thing isn't valid, then why in the world is save even firing? Well, let's go back down here and we'll take a look at the source code of Backbone. I have it in the project as Backbone_dev. Here's the save function and you could see that it's doing some operations on setting the attributes of the object. And, if we look up a few lines, we can see that it's validating the attributes as set unless it's the key. Oh boy. Do you see the problem? We have named our ID attribute to name. We also have a validation on name. Do you see the problem yet? In Backbone, the ID of the object needs to be able to be null or empty. If it is, that means it's going to be creating a record. Now, you might be thinking, "Rob, why did you just show me all of this? Why didn't you just show me how validate is supposed to work and we move on?" Well, the simple answer is this is the Backbone development process. For some reason, people seem to be prone to falling backwards into these crazy errors. Trying to bend Backbone a little too hard and, boom. What we did today was we tried to do two things that countered the typical patterns you see in Backbone that, frankly, just aren't discoverable easily. We screwed up our rendering of our view, which clipped our events and caused us to lose some time until we figured out how jQuery works. And, here we screwed up the validation process because we decided to set our ID attribute to name and then set a validation on that, which, again, is countered to the way Backbone does things. Now, we're going to be working with these functions later on, but we're going to know what to do. My way of thinking, understanding the pitfalls before you actually start writing code is usually the thing that, well, helps you write good code. Well, thanks again for coming along with me on this journey today. I hope you learned something and Backbone is starting to get a little bit more complex. See you again next time.
Refactoring and Using Inheritance
Naming: Concepts and Conventions
I promised that we'd move quickly from the basics into more complex issues, and here we are. As we start laying structure and abstraction into our application, it's important that we start using appropriate naming conventions. This is a great exercise, because if you're having a hard time naming something, well, usually that means the concept you're trying to work isn't very clear to you, at least that's what happens with me. So let's take a second and work up some naming. The first thing I like to do when thinking about structure and abstraction is to ask, has somebody else already thought about this, and dealt with it? And our answer here is, yes! As I've been saying all along, Backbone is a great framework, but it leaves a lot to you when it comes to abstraction and structure. Given that, a number of frameworks have popped up over the years that offer the structure we're looking for. Marionette.js from Derick Bailey is one such project. It's quite popular. It's a framework created in the tradition of all great frameworks, including Backbone itself. It was extracted from real world projects, and the patterns that emerged from working with Backbone day in and day out. Now, I'm not going to plugin Marionette.js just yet, as I don't really have a need for it. After all, we've just gone through some basic abstraction, why plugin a framework on top of a framework unless our complexity demands it? In a few episodes we'll revisit Marionette. For now, let's take a look at some of the structure it provides, and see if we can steal some of it. So Marionette.js is based on a set of patterns from Microsoft called Prism that Derick Bailey used while developing Windows Desktop Applications a few years back. These patterns seem appropriate for complex, single-phase Backbone applications because, well many of the interactions are the same; Composition, Layouts, Event Handling, and Messaging to a remote server. Now initially the terms you start to use come off as a bit, well, jargony, something that application astronauts might use in the architecture cafeteria while running code reviews, sipping their cappuccinos. To me, terms like Event Aggregator reek of nonspecificity, bordering on project manager speak. But what are the alternatives? I could name something Steve and Bill, I suppose. But as I looked around, I found that the lingo Marionette uses makes fine sense as long as we all agree on a definition for these rather bland words. So let's get to that. First, we know what we have been dealing with Views, Backbone Views. These Views have taken on various duties in our application, and have been assigned certain roles. Our breadcrumb navigation is always present, and lets the user know what they're looking at, and also provides a bit of history that a user can go back to. We have a list of databases, and finally a form to add a new database, which we've called our databaseOptions. Abstracting these out a bit, patterns are starting to resolve that we can write code to. We have a Navigation View, our breadcrumbs, a List View, and Item View, which we've already extracted, and finally, an Options View, and that's all good, but we need more. To get these Views to show up properly, we found ourselves hardcoding jQuery calls, as well as positional, and event information into our Backbone code. It's not a good idea. It's becoming apparent that we need some level of View management. A typical thing that Backbone developers do is to use an Application View, the mother ship, if you will, that contains all the other Views. It's responsible for rendering and placing things in response to user interactions in a global sense. But we need a bit more fine-grained control than that. We need another set of Views which can manage when, and where the navigation is shown, as well as the detail information of our app. These are called Layouts. Right now we're only showing a List and some Options. That might not always be the case, so encompassing everything in a single Layout, a Details Layout, makes good sense. Finally, the Views inside a Layout are referred to as Regions. These are generic placeholders for things that go inside Layouts. So let's recap our new set of jargon. An App View contains everything. Then come the Layouts, which contain Regions, and Regions contain the individual Views, like Lists and Forms. A nice way that you can conceptualize this is using a bit of HTML with some id tags. One thing I like to keep in mind is that if I create a tag with an id, it should be a Layout, or a Region of some kind. Here you can see how I've translated our new jargon into something useable, and recognizable. And you might be wondering what the benefit of all this structure is. Well, having a nested structure like this allows you to have complete control over what views are rendered, where they're rendered, and how they are rendered without hardcoding DOM information deep in your application. For instance, you might have a View that needs to have the entire application space on a page. You can do that easily if you have a top level Application View. Okay, now that we have our jargon down, let's get back to coding.
A Cleaner View Structure
Alright, armed with the knowledge of people that have been doing this for a long time, let's see if we can implement some good patterns. I'll rename our details view to layout, following Derick Bailey's Marionette convention, removing all of the hardcoded stuff from before. Alright, we know that Layouts need to work with Regions. For our purposes, I'll just assume that regions is going to be an object literal with various properties on it that have selectors and names for the regions. And, because we'll be using a loop, we got to do our old favorite thing, var self = this. Now if you're wondering why are you looping over this stuff, this doesn't make any sense. Hold tight. Hopefully this will make sense in just a second. So what I really want to do is I want to have these regions be exposed explicitly as jQuery objects. So what I'm going to do is loop over each of the region settings, and I am going to drop on a property to this View based on the name, and I'll have that value be the selector. You'll see what I mean in just a second. Finally, I want to have a method to call, like an event callback. I'll call it layoutReady. If there's a callback set, then I'm going to call it directly. Basically, I'm going to have my Layout View here just essentially prepare itself for where all the sub-views are going to go. This looks odd. I'm going to cover this in detail in just a few minutes, so hang tight. Okay, so now that we have a Layout, let's implement a Layout View for our database, and we'll call it a DatabaseLayoutView, surprise! I'll set the template to be our details template, and then I'll declare a property on this thing called regions. Regions is just going to be an object literal, and you can loop over object literals, and pull out the keys, and the values. In this case, I want to have keys and values that identify what the regions are. Our first region's going to be a databaseList, and it's going to have the selector of database list, and you can guess the rest. Well now that we have our regions, it's time to write that function that I set up as a bit, well, an event callback, layoutReady, meaning that the regions have been turned into jQuery selectors, and so in here I can simply create our Views. Just like you saw me do before, and I said it was hardcoded, well it doesn't matter if I do this in this class, because this is a dedicated Layout View for our databases. So I'll instantiate the ListView, and instead of setting the collection to the global Congo.databases, I will set it to this.collection. This is always a good idea if you can avoid direct calls like that. It keeps your Views a little bit more nimble. And I'll set our optionView to a new instance of our DatabaseOptionView, and it has nothing that needs to be sent in. Well now that we have our Views instantiated, what are we going to do with them? Where do we append them into the DOM? Well that's what we did in our Congo.Layout object. If we go back and review, this code this starts to make a little bit more sense. We are declaring self name, or popping a property on, that's going to be a jQuery object. So, in our case here, databaseList is now a property on our View. Same with databaseOptions. The value of that property is the jQuery selector, so I can say this.databaseList and .append, because this value is a jQuery selector, dbListView.render.el. The same pattern that you have seen before, except we're being a lot more explicit. And same thing with databaseOptions, do .append, and then the optionView, we'll render it out, and drop in the element. That looks pretty good, so let's go and wire it up, and see if everything works. Now head over to index.js, where our Congo app is declared, and let's clean things up a bit. I'll remove Congo.details because we don't have that anymore. I just blew it away. So, what I'll do is I will be explicit about where I want to do here, which is to show the databases. So I'll create a brand new function called showDatabases, and inside this function I will be free to declare and manipulate whatever I need to show those databases. So, first thing I'll do is instantiate a brand new dbLayout View, or DatabaseLayoutView. One last bit of refactoring, it doesn't make much sense to fetch the databases in start here. I mean, it would still work, but I'd rather do that fetch routine inside of our showDatabases function, and then, in start I'll just say Congo.showDatabases. That is much more readable and conveys a lot more what the app is supposed to do when it starts up. And then Refreshing our browser, and look at that! It works, sort of. We got a bit of a spacing issue in there. Let's shrink a few things, and KABOOM! That works pretty nice. But, does the functionality, did we retain that? Let's go and add a database, and I'll just put in one that is called works, and yeah, look it shows up, and I can Delete straightaway, so no eventing problems. Everything works the way we expect. Developing with Backbone gets complex rather quickly, so it's important to drop some structure in when you need it. In our case, we needed a little abstraction so we stopped writing the same code over and over again. My thanks to Derick Bailey for helping me out, showing me some sensical patterns, giving me a lot of ideas. Alright, we'll see you again next time.
Introduction to Complexity
Adding in a Router
So I've added a bunch of things, and let's get you caught up. First, I added in collections.js. These are Mongo collections, not Backbone, and with a snap of my fingers, KABOOM, you can see the code that I've added in. Now I won't drag you through all of this again. We've already covered these concepts, and you know what these things are. I created the same set of Views, and Layouts, and ItemViews, and ListViews that I did for databases, because I'm doing the exact same thing. I'm just showing a list of Mongo collections. Next, I have refactored out an ItemView, because before we were just using a regular old Backbone View, but that would result in code duplication because we would have two remove routines, and so I wanted to refactor that out so we can put remove on the base class. And there you go, and that means that I can now go over to our databases file, and I can change the Congo.View to Congo.ItemView, and then I can take out the removeDb function because that has now been abstracted into our ItemView. (Typing) So let's make sure everything works. This is a key with Backbone. Go slowly. Take small steps. And I click on Delete, and yep, there is our new message. Let's go ahead and add a new DB in, and make sure our new dialog works, and it does. And we'll Delete that back out. We are good to go. But, if we click on a database link, well nothing happens. Let's wire that up. What we want to see is a list of Mongo collections for that database. And once again I'll write code for the click of a link that is in our ItemView here. So, what this will do is every time someone clicks on a database name, it's going to fire a function. I'll name that function showDb. And then we'll add that function down below here. Now the question is, what am I going to do when that thing is fired? Well the first thing is, of course, I want to pass in the event, because the last thing we want is for the anchor tag to behave as an anchor tag. We want to prevent the default click from happening, because that's just going to throw a pound up in the URL, and then the whole page will Refresh. The next thing I'm going to want to do is pull out the database name. I have put that in a data tag, an HTML data attribute, and I've named it db. So assuming that we get the DB name out, what do we do next? How do we get to show the collections? Well, step one is to have something to show. Heading over to index.jade, I have added a bunch of templates in, and these are basically just flat duplicates of what I had before for databases. As you can see, I have db details, and then new db template, and then I also have a new collection template of collection details, and so on, and they are literally just copies. Now, of course, you could call this a bit of a smell, as having duplication is something we've been trying to avoid. But I think we're going to end up customizing the user interface a lot in the future. So, for now, I'll leave it the way it is. Okay, so where does that put us? Refreshing the page, clicking the link, nothing happens. And this is sort of what we would expect. We've just wired the click event, but we haven't told our application to actually do anything. So that is our next step. And I'll head over to index.js, and this is where I'm going to create my router. Router is sort of an application-level concern. Where you put it, well, it's up to you. Typically, I will put it in the global app file, because I feel, as I said before, it's kind of a global application concern. To create the router, you simply do Backbone.Router.extend, which is what you've seen before, and low and behold, there's an initialize event. Again, you've seen this before. So the router's sole purpose in life is to respond to URL links, and to also navigate the application. So for that, we're going to need a set of routes. And this is just an object literal that we're going to specify here, and the default route is an empty string. And when you have a default route, you have to give it, well, a default function to navigate to, so let's create that function and we'll call it index. You can call yours home, or if you want to be purpose driven, call it whatever you like. So the index function is going to do a simple thing. In here, I'll just do console.log and "The index route!" If I've done everything right, this thing should fire immediately whenever we Refresh the page. So up in our init function, let's start off by initializing the router. Just as we've done before, I'll tack it onto the Congo object namespace, and then I'll just say new Congo.Router. There's nothing I need to pass in. Well that's it. Let's go play with this thing! Let's go Refresh the page. And now, just in the console here, say Congo.router. Let's take a look at what we got. A little letter d. We've come to see that before. It says you've got a Backbone object. So, I'll raise this thing up so we can see a little bit better, and I'll use the method that you're probably going to use most on the router, navigate, and I'll just put in blah. And if I hit Return, undefined, nothing, again. There's also an overload we can pass in there. It's just a boolean, and again, undefined. What's going on? We have a router. It's not working. This is a gotcha with Backbone. I hate to say it. If you don't instantiate the history API, well then the router won't work. Well, you might be saying, what the heck is a history API. I'll talk about that in just a second. So we have to start up the history so we can record where we've been and what we're doing in the app, Backbone.history.start. This is going to allow us to use the Back button, Forward button to move around the application. So once we start recording, if you will, now we can use the router. And look at that! The index route! BOOM! Things are starting to work. Let's navigate somewhere, Congo.router.navigate, and then I'll say BOOM, and look at that. Up top there, you can see we have BOOM tacked onto our URL with a hash-bang. That's interesting, but we don't have any way of handling BOOM, until now. So when they navigate to BOOM, let's goBoom and see how this whole thing works. We'll give it a function. So the router is going to respond to this route by firing goBoom, and for how we'll just do an alert box just so we can see how this whole thing works. Heading back to the page, we'll start again at the root. We're at the index route. Now we're going to navigate over to BOOM, and let's give it a shot here. Ergh. Undefined. Nothing. But our URL changed! How confusing is that? If you answered very, you'd be right. Routers are confusing beasts. Let's go back though, and you can see that we have retained history, and that we can go backwards and everything is fine, and we're back at the index route. So the Back button works, the index route got fired. What are we doing wrong? Well the answer is, we forgot to pass the boolean argument true when we use navigate. If you do that, that means yes, go ahead and fire the function associated with this route. If we Refresh, it gets fired automatically for us, BOOM. Is that confusing? Well it confused me the first time I started playing with it, so let's look at it again. We're going to navigate to BOOM, and we're going to say true, yes, go ahead and fire the function associated with this route. And it does! But if we don't pass true, then that function won't be fired, and believe it or not, there are times that you're going to want this. When we Refresh and come back in, well, this is first time that we're coming into this route, as far as our app is concerned, so by default the router will fire that action that is associated with the route. Welcome to stateful programming on the web. If that doesn't make much sense, well let's dive into it a little bit more. How do we know when things are firing? How do we know when the page is loading? What's going on here? So let's Refresh the page. I've tacked in a PAGE IS LOADED console log for whenever the page is loaded. That's fired by jQuery. And notice that when I navigate, the page doesn't reload itself, DOM doesn't load itself, so everything is fine. When I use navigate and pass in true, the function associated with the route is fired, but the page is not reloaded. In other words, the DOM is left as it is. Now if I just do a straight Refresh, well, as you can imagine, the DOM is loaded, and the route is honored. If you had trouble following any of that, I really suggest you rewind a little bit, watch it all over again. Routes can be confusing to be sure. We'll be playing with the router enough today that hopefully, after a little repetition, if it's confusing to you, it'll all start to make sense. So we're going to treat the router as a bit of a controller. The first thing, of course, I want to do is remove some of the mess that I just put in for the demo. And instead of using our global application object to do things, now that I have a controller, if you will, I can use the index action to start showing all the databases. I'll also take a second to move some things around. Before, our Congo global app, if you will, was responsible for doing a little bit more than it should. Really, it should just do some initialization, and have some basic settings attached to it. Now, we're separating things out a bit, and we're embracing our router as a bit of a controller. Here in our start function we're going to start off by initializing things, and then finally we're going to start the history. That starts to make a lot more sense. Now, I could put Congo.init in the initialization of the router, but that's a little bit backwards. I'd rather have my global app object tell the router what to do. That's an architectural decision, and we're going to talk more about that in the coming episodes. Okay, well let's now wire things up so when the route is clicked that we show a database. Our route is going to be :db, and colon anything means this is a parameter. And when that route is matched, the showDatabase function will get fired. So what I'll do is I'll just log something out to the console and say that this database was selected. But which database are we talking about? Well, when the parameter is matched, in this case :db, that parameter is passed into whatever function is handling that route. In this case, it's just going to be a single value that we have labeled db. And so we can be sure that everything works out, I am just going to log that out to the console. Let's go back into our databases.js file. We now know what we need to do. We need to tell the router to navigate whenever that event is fired, the click of a link. But where we going to navigate to? How does this work? Let's take a look at our route again. It's just expecting a single entry, a db, a database name. The default route is empty. That's the root of our app. If we pass anything in there, well that's going to be a single parameter, and it's just going to be the name of the db, DbName. So let's take a look at how this might work. I'll Refresh the page, and click a link, and it kind of works. Our URL changed, but where's the console log message? Oh, right. I forgot to make sure that that function fires. It's another little gotcha from Backbone. Oh, geesh. So let's Refresh, click it again. Hey look at that, database selected. That's what we want to see. Baby steps. That's what it's all about. One thing at a time, go slow, make sure it works. Alright, as I mentioned, I'm going to treat our router as a bit of a controller. For smaller apps like this one, that idea works just fine. So I'm going to move the body of showDatabases off of our app, down into our controller. This is, well, orchestration if you will, and that's what a controller's supposed to do. And given that I'm going to be doing just about the same thing to show Mongo collections, I'm going to move that entire method body up into showDatabase. Inside here, I'm just going to change a few variable names, so we have a collectionLayout, and it's going to instantiate a brand new collectionLayout that I've made previously. I showed you that in the very beginning. Its right over here in collections.js, here's our CollectionLayoutView, and our OptionView, our ListView, this is all the same stuff you saw before with the database work that we've done. And I'll go in here and I'll change our collection to be, hmm, I haven't really created anything that we can work with. Well, I'll call it currentCollection, because that indicates that it has been selected, and I'll deal with that in just a second. Next up, we just need to tell the Layout to render, and then finally stick the element of the Layout into details. And then of course, since we're doing a transition, that details needs to be emptied out straightaway. So let's do the same above and below. We have to empty out the details element so we can append something in so it doesn't get doubled up. And then finally, we'll kick it off calling fetch on the collection. Well, alright, I'm kind of doing things in bits and pieces, and I'm sort of throwing stuff against the wall. Let's have some organization to all this. This is what init function is all about. It's initializing all of the things we're going to need for our application to work, including Layouts, bits of data, and well, different Views. So let's figure out what this current collection is going to be. I want it to be a brand new MongoCollection collection, yes, that's a hard thing to name. If we head back over to collections.js, well you can see I just made it plural. Again, these are exactly the same setup as the Mongo database stuff I did before. So, I'll just set this to be a brand new MongoCollections. And then I'll do the same thing for databases that you saw me do before. I'll put the Layout here, and we'll call it Congo.dbLayout. Finally, just need to be sure that our namespace is applied to our variables down below. This code is kind of looking repetitive isn't it? Well, I might have to take care of that in just a minute, but first, baby steps. Let's go back, Refresh our page, and it lays out. Good. And we click the link, and well, we're almost there. There're no collections. And taking a look at our OptionView for adding a collection, well that's there, so that's a good thing. But now I want to go backwards, and how do I do that? I could hit the Back arrow, but, well that's what this breadcrumb thing is there for, and it just pops a silly thing, so, bleh.
The App Layout and Reducing Duplication
Okay, well before we do anything else, I can't stand this duplication, so let's address that now. I mentioned in the previous episodes that I would be introducing the concept of an AppLayout, a governing view, if you will, that contains all other Views in the application, and that seems to have presented itself to us right now. And we don't need to do anything fancy in here, really. We know that our application consists of two Regions, or two Layout Regions, if you will. We have Navigation, with our breadcrumb View, and then we also have Details. So I'm going to be explicit about this. Our AppLayout actually doesn't need to do much other than render the Navigator, and render the Details, and help us get rid of some duplicated code that is now in our router. So I'll just Paste that code in here, and let's see what we can do. So the first thing I'll do is get rid of the hard reference here to Congo.collectionLayout. We can probably pass that in as an argument. But we need to have a place to put that, and I want to be able to specify the region in which these layouts are supposed to be rendered. And right now that is, well, #details, so let's handle that in initialization. So I come up, and in our init function, where everything is supposed to be initialized, I will initialize our AppLayout. First thing I'll do is I'll tell it which element to append itself to. I really don't know if I need to do this. I could go by convention, and just say the AppLayout should always use #app, but this works out okay. Then I'm going to tell its detailRegion is #details, and we can now use this in our AppLayout removing this hardcoded reference to #details in our AppLayout. And the way we reference that is by saying this.options.detailRegion. But what the heck is that? When you pass information into a Backbone construct, like a Model, View, or a Collection, it gets tacked on to the options object that every Backbone construct has. So, in our case here, I told our view what detailRegion it belongs to. To access that information, I have to use this.options. This can be really confusing, because you've seen this structure before, but we were using it in a different way. Let's take a look at what I mean. This is the code for our DatabaseLayoutView, and if you notice in here, we're adding ad hoc things like regions, and databaseList, databaseOptions, and so on, and later on in the code, we're referencing that without having to use the options variable at all. What's the difference? Well, in this code here, these are Settings. Notice that we are just declaring what a DatabaseLayoutView is, we're not instantiate a new one. With our AppLayout, however, this is an instantiation, and we're passing options in. We're using a built-in option, el, and we're also specifying one of our own, detailRegion, which again we need to access from this.options. If this isn't very clear to you, that's okay, as I keep saying, we're going to keep doing this over and over again, but it's important that you understand the difference, because they look so much the same. Okay, well let's go back and finish up our render details function. I will use the same accessor here, this.options.detailRegion. I'll empty it out, and then I'm going to append something, and that something is going to be the detailView that I pass in as an argument. Final step is to call render on our detailView. Looks simple enough. Let's clean this thing up a bit. Very nice. Alright, let's see if it works. I can strip out all this unneeded code, which makes me happy. I'll just comment that out taking small steps, making sure I can go backwards. Now, I can just say Congo.appLayout.renderDetails, and then I'll pass in the dbLayout. I think that reads pretty well, don't you? Let's make sure it works. Go back to our browser, Reload, and look at that! That worked pretty good. So let's strip out our commented code, and I will Copy what we did here, and then I'll head back up to showDatabase. This is starting to look more and more like a nice, clean controller, that's what I want to see. And then we'll say render the details, and use our collectionLayout. Let's Refresh, and we'll click on our Shlonk database, and, well, we're almost there. This collection should be showing up, but they're not. Well, why not? Let's head over to our collections.js, and in here, well there's our problem. In order for our collection to load itself, it needs to understand what the current database is, and we're not setting that anywhere. Where do you think would be a good place to set that? Logically, put it in the controller. So, we just need to say Congo.currentDatabase, and we'll set it to db. I really don't like using global settings like this, but, well, as you can see, it works! It's not so bad, but I'll make a mental note, this might be something I want to clean up later on. Let's go and make sure that our collections work as we expect them to. After all, we basically just copied and pasted everything from databases, and yay, we're able to add a collection in, Refresh it, and it's there, and look at our URL is routing as we expect. We Delete that. We have state, if you will, sort of a state, and yeah, so collections work. And, yecch, our breadcrumb navigation does not, and well, I'll be honest, this is where things start to get a little complicated. I'm not really looking forward to this. So our breadcrumb navigation has to basically cycle itself with where we are in the application. Right now, we are simplistically rendering the thing when it's initialized. But, that's not really what we want to do, because this thing is now governed by our AppLayout. And, well, so what we should probably do is pass this thing in, and say our navigatorView is Congo.breadcrumbs, so our AppLayout knows what to do with it. But as we start to solve this problem, things just kind of stop making sense. I find that when you're inner dialog starts going off, and you're looking at something, and it doesn't feel right, test it out, make sure it works. Sometimes that's just the way you got to do it. Other times, there's probably a better way. So let's run this whole thing through. Basically, I'm just moving things around here. I'm sweeping things under the rug, if you will. I'm simply telling the navigator now to render on the initialize of the AppLayout. That sort of makes sense. It's a global navigator. It's always going to be there, so why not? Let's make sure this works. I'll still pass in the el, that's kind of what I needed to do. I'll go Refresh this, and it works, but, as I mentioned, I'm just not happy with it. So let's keep going. I'll build out our breadcrumb View. Right now, it's not doing anything, which makes sense. So, the breadcrumb is going to change based on what we're looking at, and what route we're using. So, I'll devote the render function to render specifically for each route. So renderIndex, well that should just show a list. It shouldn't be linkable, because we're already on the index page. So I want to show that thing straightaway, and ooh, I can just use initialize, and renderIndex. No, this does not feel right, because we can initialize the breadcrumb View from just about any URL. Rendering Index by default, that breaks. This whole thing is starting to collapse in on itself, but I'm glad we saw it through. Now, I want to do something completely different. And if you remember, Rob's golden rule of Backbone, Views react. We're not really reacting to anything here, but we can. We can react to changes on the router, specifically which route is fired. We just have to specify a route:, and if we switch back over to our router, you can see we have two routes the find, index, and showDatabase. So, we can say, whenever the index route is fired, we want to renderIndex. We want that function to be called. Also, as a side note, it's good to be in the habit of passing context along whenever you fire events like this. In this case, we'll just pass this, so the context is retained. Alright. Let's go back to our page and Refresh, and nothing. This happens a lot with Backbone. Well, let's put our debugging hats on. Let's see what is being fired and what's not. For this, I'll just use some simple console.log, and let's make sure that the index route is indeed being fired. So, I'll Refresh this. Yep, there it is. So, we know that the index route is being fired. Of course it is, because everything is showing up as we intend, except for our breadcrumbs. What's going on with that? Well that tells us that it's likely something to do with our event getting picked up, so let's go down to renderIndex and see if that's being fired. And, I'll save this, and I'll give us a better prompt, and I'll Refresh. Ahh, nothing. It's not being fired, which means our wire up is probably, d'oh, I can't spell, that's the problem. Now, it's worth it to go through a nice debug exercise anyway. Refreshing this, and look at that, we have got breadcrumb! That's exciting. Had I gone a little bit slower, and tested things out along the way, using baby steps, I might not have gotten bit by that. Okay. Let's clean this thing up, and have it show a database when a database is selected. So, now I can do renderDatabase as a standalone function here. And I can basically just Copy and Paste some code above, and create a new route for which route? Let's go back over, and the showDatabase route, that's the one I'm most interested in. When that route fires, I will have our renderDatabase function fire. And here's a neat thing, the route information is also going to be passed in. Whatever parameters that you specified in the route, well those will be passed along to any event handlers that you wire up. And finally, I'll output some HTML. I could use a template for this. Since I am just writing stuff in line like this is much easier, especially if you only have one or two lines, and this will work fine for us right now. If it gets crazy, I'll change it later on. Alright, let's comment out our event, and I don't want to hook up the event just yet, I just want to make sure everything's laying out. Again, I want to take baby steps. Yes! And the layout is working as we would expect. However, clicking on DATABASES, that's just a regular old live link. It takes us back to the home page, and well messes things up. We want to intercept that click, so let's go back and wire up an event. And looking at this, well, we're just handling the click of a link. Let's be a little bit more specific. We'll throw an id in here, and I'll call it summary. It's going to take us back to this summary, or index page. And I'll handle that down below, and then call navIndex. What's navIndex going to do? Well, you've probably guessed already. We simply need to tell the router to navigate us back to the index route, and we do that passing in empty string, because that is the matter that the router is looking for. Finally, let's prevent the default click action from moving the browser around. We do not want that, so I'll Refresh it. Let's pick a DATABASE, and, oh boy, that looks like a big fat mess. I've got things rendering everywhere. Oh boy. I think I broke my rule about baby steps. Well the first thing, take a look at that. Our breadcrumb is double rendering, and that usually means you forgot to empty an element, and I sure did. So let's Refresh that and see if we can fix that problem first. Clicking on a link, and clicking Back, yep, we empty out the element, reappend DATABASES in there, and we're good to go, but what about the rest of this stuff? Well, let's use our trusty console to see if it can help us figure out what's going on. So we know that our dbLayout looks pretty bad. Dropping it open, ew, we've got a double render, looks like a double render problem there. The same thing is being rendered twice. That's not good. Inside of here, we have our list, and we have our options, and inside options, that looks okay, I guess. No, that's double rendering, and that's not good. You can see there're two forms in there, and there're two tables that contain the lists of our databases. Usually when you see a doubled up thing like this, well, it's usually because you forgot to empty a layout, just like I did before. And looking at the render function of our Congo.Layout base class, indeed, we are not emptying that thing out. I'm not sure how that code escaped my eyes, but yes, calling myself a toad, it usually works, and we should be able to solve this problem by saying this.$el.empty. And what a silly error. Almost had a good demo there, almost! And we click Back. We have breadcrumb navigation. It looks pretty good if I do say so myself. Well that brings us to the end of this episode, but before I let you go, I'm going to assign you some homework. Let's head over to our nav.js file here, and take a look at what we've done. And it works, which is good. But is it elegant? Is it the right way of doing things? I don't know if there's a right answer here, but I do know that I just wired up a View to listen to events on a thing that I'm calling a controller. If we're adhering to MVC, that is a pretty big no-no. Now working code is working code, and that's something we have to pay attention to. However, in the long run, as complexity grows in this application, will what we've written here scale? That's your homework assignment. I know of at least one or two different ways to handle navigation, but I'd like to play a little game of what if. Let's say Derick Bailey comes in, takes over as a senior project manager of our Mongo Explorer, and says, I hate routers! Get that thing out of here. I want you to write a different way. That's your assignment, deal with Derick. How would you rewrite this? What problems do you think you'd run into? That's what we're going to tackle in the next episode, and I'm very interested to see what you come up with. Thanks for watching. Talk to you then.
Going Around Backbone, Part 1
Introduction and Adding in Mongo Documents-Dev Speed
At some point when working with any framework, you find the edges of what it can and can't handle. In this episode, I'll pick up the pace, and I'll cover familiar ground once again. I think repetition is a good thing, as it gets your eyes familiar with the process of working with a tool, or a framework, like we are with Backbone. So rather than listen to me drone on, I invite you to watch the code come together at dev speed, if you will. See if you can follow along with the process of adding in the concept of a Mongo document, where we add a List, Detail, and Layout, Collection, Model, and so on, just as we've done before, but this time a little bit faster, and a little less talking. We'll transition to working with the Ace Text Editor. It's the perfect fit for what we need, but unfortunately, it's also rather demanding of where, and how it's rendered. My goal today is simple. Accommodate this editor, and just get it to show up. I tried for hours to get it to render properly inside of a Backbone View, and all I can tell you is that it just wouldn't! I thought it might be interesting to find out why. There's no errors, no logs, no nothing. But I decided I didn't want to derail our process entirely here by focusing on the editor. This is the Backbone production, not the Ace Editor production. I'd just rather get the thing to work! So, I decided to go around Backbone entirely in an effort to get this thing out onto the page. In part two, next week, I'll focus a bit more on interacting with the Ace Editor, and our Backbone Models and Views. For now, let's just see if we can make it show up. (Typing)
Editing Documents with the Ace Editor
Going Around Backbone, Part 2
Cleaning Up: Paying Down Some Technical Debt
Today we're picking up where we left off last time, working with the Ace Text Editor in an effort to get it to play nicely with our app. I mentioned in the last episode that I tried everything to get it to work within the Backbone structure, but it just wasn't possible. Well, I've since found a way to do it, and although it's not technically within the Backbone structure, I've made the Ace Text Editor work, well as closely with our application as I think is possible. Finally, I don't want to dig myself into a nasty hole with regards to technical debt, so I'll clean things up a bit as I go along. This is the last episode before we push everything into Marionette.js with Derick Bailey, and I don't want to make Derick mad. First thing I want to do today is to pay off some technical debt. One thing I know that is if I keep on coding this application in the style that I've been doing, well pretty soon all this crappy code is going to, well, it's going to pile up on me. So let's refactor our navigation stuff. Our breadcrumbs are fine. I hate all this HTML that is embedded in the code, and I can clean that up with some code. Now it's been suggested to me that I could just use templates, using the underscore templating library, but I'll tell you what, all those script tags on my View page, it's starting to get to me. I'm not a big fan of it. In this case, it's really simple stuff that I'm doing. I have a couple of listItem tags, some span tags, I can do this programmatically quite simply. So I have three methods now, Crumb, CrumbWithLink, and Separator, and they're just going to output this stuff for me. You can see its working just fine. But I want to keep going because I have a ton of repetition, and I hate repetition. It's really good to pay off the technical debt when you can. In this case, it's just some simple refactoring. So, I have a specific need where I need to show some links. Number one, I need to show home link. So, I want to show the word databases that will link back to the list of DATABASES. I need to show collections, and I also need to show individual documents. So, let's be explicit about it. I can use my refactored stuff here, and now I can just say showHomeLink, and showHomeLink is simply going to append a crumb with a link to the DOM. There we go! So, well, I've decided to spell DATABASES right. And making sure that this all works. It does! That's good. So we can keep on going here. I have showHomeLink, showDbLink, and showCollectionLink. These are going to take the repetition out of the code down below, and that makes me happy. So using the power of video editing, let's just fast forward into the future, and now you can see how this has tidied up a lot. I'm sure that if I sat and thought about this long enough, I could probably come up with a better breadcrumbing system, but as I mentioned, this is working. It's not repetitive. Refresh, take this thing for a spin. I click on the links, and make sure that the navigation is showing up as it should be, and it is. I can click the links, move around the application. That is all I ask, and it's good, and it's working. The next thing I want to take care of is I have these Congo.Router.Navigate calls sort of spread out throughout the application, and I'd rather have it be explicit. So let's take them out of here. I'm going to put them on index.js in our Congo application itself. My goal here is to centralize how the application, well, moves between state. Since we're invoking the router, and we're firing the routes, that is essentially changing the state of the application. Having said that sentence, it seems to make more sense to put this on the application object itself. I can also take the additional step of accounting for when a database, or collection is not specified in navigation. It's logical to assume that if someone wants to navigate to a database, well if they haven't specified which one, well, then we're going to go to the currently selected database. Same with a collection, so I can account for that here. This is going to free up a lot of code. So, heading back over to nav.js, I can take out all the dbName, the collectionName stuff, just remove it from each one of these methods, and simply call the counterpart on our Congo object, Congo.navHome, and Congo.navDatabase, which, you know, is always going to be the currently selected database, so I can just leave this empty. And then I can do the same for collection, Congo.navCollection. Once again, I don't need to specify the database, and I also don't need to figure out which collection, because this is always going to be the currently selected collection, because if the link is visible, we're just going backwards, which means the user has already selected the collection they're interested in. Sure enough, giving this thing a test run, it works pretty well.
Getting the Ace Editor to Work Better
So we've paid down a little technical debt. Let's go and see if we can tackle the next bit, which is this editor code. I mentioned in the last episode that I couldn't get this editor to work nicely with Backbone. I have since found a way, and I'm happy about it, sort of. To start things off, I'm going to create an editor template, and this is just going to be a basic underscore template that I am going to pop some code in here. So let's just Copy and Paste from our editor tag, and I'll drop it down below here inside of our script tag. Still planning on rendering the editor to the Ace Editor div tag here, but I want to use the Backbone mechanisms to do it. I'm going to go really slowly, so let's refactor this step by step. So step one, I removed the Ace Editor markup from the DOM, and I put it inside of a template. I'm calling it directly from the renderEditor method on AppLayout. Let's see what breaks, and BOOM. Cannot read property env of null. This is what happens when Ace can't find the div tag that you've told it to render in. In our case, it's the Ace Editor div tag. So let's go and create a View. We need to have a place for this editor to be rendered to, so inside of document.js, I will create a new View called Congo.EditorView, and this is going to extend our basic Congo View. We know that we need to push the Ace Editor div tag into the DOM before we ask Ace to render itself. So, let's concentrate on the render function straightaway. Inside the render function, we know that we need to at least create the editor. So that's what I'm going to do. I will Copy and Paste from base.js. I'll Copy and Paste this editor code, and tell it that we want it to live in the Ace Editor DOM element. I'm going to set this thing globally, because I know I'm going to need to work with the editor later on, so I'll just set it to be Congo.Editor. That'll be our reference to the Ace Editor. For right now, because we don't have a layout, I am just simply going to call render on initialize, because I need this thing jammed into the DOM. So we've made some small changes. Let's go see if we have broken anything, and indeed we have. Div element has no method setValue. Hmm. Well setValue's being called from here, I'm not sure why it thinks it's a div element. But that makes sense. There is no editor, so we need to account for that. SetValue is in place because we need to load the editor up with some text whenever we select a model. We can't load the EditorView in the same way that we've been doing in the past. We actually have to do it explicitly whenever we have a model value. This is because the Ace Editor doesn't like working within the Backbone Framework, it needs to be in the DOM, or otherwise it's just not going to show up. So, I'm going to create a setModel function here that we can call from elsewhere. That setModel function is simply going to take in a model. It's going to turn it into JSON, and that's going to be a JSON string, and then we can set the value of the editor to that JSON string to show up properly. If you're confused right now, hold tight. Hopefully this will become clear rather soon. Okay, so we have a setModel function. Where we going to use that from? Well, inside the renderEditor method on the AppLayout, we can call this thing directly. And, we can pass our model in, which I am descriptively calling thing for right now. Right. We've taken a few more steps. Refresh. Got another error. That's okay. Can't call setModel is something that's undefined. Well, that's because we haven't defined our editorView. We've created the code for it, the declaration, but we haven't actually instantiated it. So let's instantiate that right here in index.js, in init, and see if that works. No. We still have a problem. Hmm. Let's go back over to documents.js. What is going on? We've got our editorView, and we're declaring a new editorView. And, oh geesh, can you see what the problem is? Once again, I can't spell. Okay, fixing that we'll Refresh, ooh boy, can't read property env of null. Sort of feels like we're going in circles here doesn't it. The problem is that Ace can't find the div tag to render to. Can you see what the problem is? Well, I keep saying that the Ace Editor requires the element that it's rendering to be in the DOM before it is rendered. It can't be a DOM element that is assigned to a variable. It has to actually be in the DOM, and live. Checking it with jQuery, well we can see Ace Editor is not in the DOM. Hmmm. So, wonder if I can fix that by doing return this? I don't know, just got to cover all the bases. Nope. The Ace Editor is not in the DOM. It's an empty tag there. To get around this, we are going to need to hardcode the element for the editorView to a DOM element that's already in the DOM. In our case, the editor tag that is already in the DOM. We left it there on index.jade. Not only that, but we are going to need to reach in to the template, which we haven't done yet, and pull out the template information for our editorView. These are two steps that we missed critically. So, we are going to pull the HTML from our editor template. We're going to compile it using underscore. And then we're going to append the compiled source to this.el and this.el is, do you remember? Well, we're passing it in. It's the editor div tag. That is in the DOM. And guess what? It worked! Believe me, I was pretty surprised to see this work. I had maintained steadfastly that this was not possible, but I took it step by step, made sure that I understood all the differences, and well, it worked. So let's make this thing look a little bit better. I'm going to update the CSS, and some of the HTML here so that this will look a little bit better. Refreshing. Yay! That looks a whole lot better. Now we have our editor in the DOM, part of a View, boy am I happy. But, you know what would make me happier? Reducing some more technical debt! Let's do it. In our router, we are repeating all over the place currentDatabase equal to db, Congo.selectedCollection equal to collections, selectedDocumentId, da-ta-da. Let's put this all in one place, and we'll create a function here called setState. Too much repetitive code, well, that is the easiest kind of technical debt to get yourself around. So, let's refactor this a little bit. I'll have a setState function here, and I'll just say if there is a db variable passed in, we'll set that to Congo.currentDatabase, and then we'll refactor out the code by just saying this.setState. Ah, much cleaner. I like this a lot..
Hooking Up the Real Document Data
Okay. On to the next task. Right now we're just sending in an object that says Hello!, message Hello! It's not the document that we want to edit. So, let's make that happen. We know the id of the document. We just need to now go get it from the database, and the good news is that Backbone helps us with this, and makes things pretty easy. So I'll start by creating a brand new Congo.MongoDocument. And I'll pass in the id that was given to us by the route. If I set the id on my document, then I can use the fetch method that Backbone gives us. Fetch simply goes out to the server, and it refreshes the document with the information that the server has. The one condition, needs to have an id, and we've just set that. So I'll say document.fetch. It also takes some options here. You can set a success function, which I'm going to use, and it'll give you back a model, and the model itself, and the response. It also has an error function, but I'm not going to set that just yet. So, when the document comes back, I simply need to say, render the editor, and I'm going to pass in the model straightaway. It's important to keep in mind that this is a Backbone model. This isn't just an object that I can turn into JSON. So that means I need to go over to my editorView into the setModel method, and I need to be sure to call model.toJson because we're expecting a Backbone model to come in here. And let's Refresh. Ooh. Sort of. That's pretty good, but we got some weird JSON here. Do you see the problem? Have to admit this is an unexpected result, but I think the problem is that I used id instead of _id. Remember, that is our identifier for this model. Refreshing, and indeed, that was our problem. And now, we have a document that we can play with in our editor. How exciting.
Adding Save and Delete
Plugging in MarionetteJS
Setting Marionette Up
Item and List Views
So, you got Marionette plugged in there. First thing we want to do, like I said, is find a little spot that's going to be easy to put these Marionette pieces into. So, if you go back over to your browser real quick, I want to take a look at some of the pieces that you have. So this is your home screen where you get this list of DATABASES basically, and you have, well, a list of databases on the screen there. Each of those has a little bit of functionality. You can click on them, or click Delete, or whatever. Yep. So, this is basically what I would call a ListView or a CollectionView in Marionette. So I actually have a Marionette.collectionView that we can define, and build a nice little list like this. But before we do a ListView, the ListView, of course, contains Items. And in Marionette, I have an ItemView. So let's take a look at what you have for your ItemViews right now to list each of those individual databases, and let's see if we can replace your version with a Marionette version, and see what happens to the code. So what I have right now is a straight up ListView, and initialize. I just bind to events in the collection, add, remove, reset. Mm hmm. And then, duh, I'm not doing much different anything. I'm just looping over the ItemView, setting the model, rendering it, and then, I'm actually stuffing everything into an array. Okay. And then, I'm using this.el.html, which I probably shouldn't do. Actually, let me just fix this. Didn't we, didn't we solve this problem before? Yeah, I think we did. It's one of those things where it works in some ways and in some scenarios, and other ways in others. Yeah. There we go. I was just, fix that there right now. Okay, so yeah, that's what I'm doing. Let's make sure that works. Baby steps Rob, baby steps. Actually, I don't think that will because it's an array. Oh no, it does work, okay. I guess jQuery is smart. I heard that too. So that's it. That's my ListView and ItemView. Yeah! That's great. That is almost line for line what I started with when I was building my ListView with Marionette. Okay. Super simple. So let's go and see if we can replace this. Maybe what I'll do is my, I have a DatabaseListView right here. Mm hmm. I bet you we can, let's just see if this works the DatabaseListView. Well, let's do the ItemView first before we get to the ListView. Okay. Alright, so where you have Congo.ItemView, right there on line 29. Yep. You can actually just replace the word Congo with Marionette, because I happen to call it Marionette.ItemView. Beyond that, what is your showDb function do? When is that getting called? It's ah. Oh, that's from the events. Okay. Yeah. So this just gets the click of the link and it navigates us off. I get it, okay. So, at this point you happen to have built a pretty darn nice structure for your Congo.ItemView, and I'm happy to say that Marionette.ItemView works exactly the same from the basic API. Well, well, well. Let's Refresh it and see what happens. Ohhh, okay, there's no item listenTo. What version of Backbone are you on? I don't know. I'm betting you're on 0.9.2, and you need to update to 0.9.10 for this version of Marionette. Okay. Here's the backbone.marionette.zip. Okay. Well this is cool because, yeah, I need this, and it's here. Woo-hoo! Hey, it's right there in that zip file, everything you need. Hmmm. So, this is already minified? No, that is the unminified version of Backbone. Okay. So if you do want the minified version, you are going to have go back out to their site and grab it. I think I can live without it. I prefer developing with the unminified versions, honestly, it makes it easier to debug. Yeah. Okay. So we've updated Backbone. Let's see if that changes anything. Jooop. Hey! Yay! It works. It does. Cool beans. Okay. Yeah. So good for you for building a good API into your Congo.ItemView. Why thank you. Alright, so the second thing would be to take the ListView and replace that with a Marionette.collectionView. So, like that? Yeah, there is one minor difference between yours and mine. On line 47 there, that ItemView, should be a lowercase i for the Marionette itemView. Okay. Is that it? That's it. Dude. No way! Yeah. Whoa. Look it. That's cool. So it hadn't passed out your add and delete functionality as well, see if that works. BoomBoom, Adding. It does. Deleting. Wow. Okay. Yeah. Okay, wait, so hold on. If I delete that, it's gone, right? Yep. Which is exciting. Refresh it, it's still there! Ooohhh. So it didn't really work did it? No, what, Derick, what kind of magic voodoo trickery you got going on there? Well, yeah, sorry. There're a couple of different things going on here. First of all, if you go back to your ItemView. Mm hmm. So in your Congo.ItemView, you had a remove function in there, which did your whole confirmation to remove the item, and all that kind of stuff. It's this guy right here. Yes. Yeah. Well, in Backbone 0.9.9 and later, Backbone.View has a remove method built into it. But, here's the thing. Backbone.View remove method only removes it from the DOM. It doesn't delete the model, it doesn't clean up anything. It just removes the DOM elements from the DOM. Okay. Alright. That's, alright. So then, what I'll need to do is have my own remove function. Yeah, exactly. So looking. Name it something other than remove. Right. Because. So looking at this, my first inclination is, wait I layered in, and I don't want to call an abstraction, I layered in a helper, and ended up with more code. Yeah. Well, you moved code around. You didn't really end up with more code. That's true. Good point. So, am I getting anything else from using Marionette.ItemView here that I can't? Kind of like memory management, or hell yes. Yeah, absolutely. There's a lot of stuff built into Marionette's ItemView that you would not get from your standard Congo View without writing a couple a hundred more lines of code. Oh, okay. Including memory management, as well as some additional UI features and other ways to bind to models that make it really, really nifty to do things. Okay. Alright, so it's worth it to do it, good. So that was simple. Tell you what, let's, I mean all the other ListViews are the same, so let's, with the magic of video editing, I'm going to go and just plug everything in, because I got, they're all the same. Let's do it now. Let's do a montage. Dadaditado. Okay, I have plugged in Marionette magic. I just literally overlaid it on all my ListViews and ItemViews. I'll tell you what, I'm thinking about this. If I would have started with Marionette, I'm just thinking about all of the code that I wouldn't have had to write. Mm hmm. It would've been nice.
Ace Editor Conversion
More Marionette Possibilities
Rob Conery co-founded Tekpub and created This Developer's Life. He is an author, speaker, and sometimes a little bit opinionated.
Released9 Feb 2013