https://github.com/jkat98/imgettinit_netmvc
At the end of last year I set some goals for myself, one of which was to start using and become proficient in Backbone.js. I’ll be honest in saying that I struggled for a bit to really wrap my head around everything that was going on. There are a lot of great resources out there to learn Backbone.js, but the goal of this article isn’t to teach Backbone.js from the very beginning. It is geared towards newbies (like myself) but you should have at least a basic understanding before proceeding.
One of the biggest issues I faced when originally trying to tackle Backbone was the lack of any real-world examples. Every tutorial article and website seems to feature, more or less, the same exact “To Do” application. That’s great and all, but almost immediately you realize that there are some fairly huge, yet fundamental, pieces missing!
The goal of this article is to take a somewhat basic, yet real-world, web application that I recently created and learn from it. Since I originally wrote it so that I could myself learn Backbone and require, I’m hoping I can shed some light on some pitfalls and hurdles I struggled with along the way, as well as provide an eye opening example on how a real website could be built.
I’m going to take the ImGettin.it website I announced a few months ago and completely rewrite it from scratch using Backbone.js and require.js. The focus of this article is going to be mostly front-end. If you want to learn more about how the backend was created you should check out my SPA Project Template post.
Planning a head
Before we do anything, lets do a little planning a head so we know what we are getting into.
- The project we will be reviewing is going to be a library of video games that haven’t yet been released.
- It was built using the Single Page Application (SPA) style that is becoming so popular.
- Users can browse through the list of upcoming games via Pinterest style “masonry” columns.
- Clicking on any game will popup details for that game. Included in the details is:
- game title/header banner image
- description
- release date
- consoles its being released on
- any retail preorder bonuses available
- your friends that are getting the game
- a list of other upcoming games
- finally the main button/dropdown that users will use to mark a game they intend to get.
- Technologies being used:
- ASP.net C#, Entity Framework, WebAPI – backend
- Backbone.js, its dependency underscore.js, and require.js – frontend
- Plugins/Libraries:
- moment.js – for handling dates/times formatting
- masonry.js – for displaying games in Pinterest style vertical columns
- simplemodal.js – for displaying games details via modal popup window
- imagesloaded.js – for detecting all images have been loaded before executing the masonry layout
Getting Started: Organizing our code and files
Most of the focus of this article is going to be on our /scripts folder – so lets take a quick look at how the files in there are going to be organized:
- main.js – This is our main javascript “boot” script.
- /app – contains all of our actual app scripts
- app.js – basically the Backbone router for our app
- config.js – some basic configuration options
- globals.js – global variables and collections
- /models – all of our Backbone models
- /views – all of our Backbone views
- /lib – contains our 3rd party libraries and plugins
Index.CShtml
Since this is going to be a Single Page Application (SPA) we are going to only have 1 html document, the index.cshtml document in our root. It contains all of the standard boilerplate stuff you would expect (including header, design elements, navigation, etc as well as the all important core div that will contain our app with an id of “app”), however lets focus on the scripts tags at the bottom:
[sourcecode lang=”html”]
@RenderPage(“Views/game.cshtml”)
@RenderPage(“Views/gameDetails.cshtml”)
[/sourcecode]
First, we decided to load jQuery using Google’s CDN (and not load it async using require).
Secondly, we loaded moment.js manually right in the page as well and that is so we can use moment in our templates (or other areas in the actual HTML and not Backbone or require).
We will store all of our actual HTML templates as pages in our MVC project Views folder, and just include them in the index.cshtml page using Razor’s @RenderPage() calls. This is just for convenience/organization and we could have just as easily put the Script blocks right in our index.cshtml file.
Finally, the most important line is the require.js include. Here we have the data-main attribute set to the main.js file in our scripts folder. (Note that by convention, never include the .js part as require will always add this automatically.) This is important because it tells require the main bootloader script file to run that will kick start everything. Its also the only script file loaded by default out of all of our scripts. The rest will all load asynchronously as needed by require.
main.js – our Bootloader
Our main.js file (which is loaded by default because of the data-main attribute of our require.js script tag) is what we are referring to as our “bootloader”. This script will configure require, declare paths and dependencies, and then startup the app:
[sourcecode lang=”javascript”]
(function () {
var root = this;
require.config({
baseUrl: “/scripts/app/”,
paths: {
underscore: ‘../lib/underscore.min’,
backbone: ‘../lib/backbone.min’,
‘jquery.masonry’: ‘../lib/masonry’,
‘jquery.imagesLoaded’: ‘../lib/imagesloaded’,
‘jquery.simplemodal’:’../lib/jquery.simplemodal.1.4.4.min’
},
shim: {
underscore: { exports: ‘_’ },
backbone: { deps: [‘underscore’, ‘jquery’], exports: ‘Backbone’ },
‘jquery.masonry’: { deps: [‘jquery’], exports: ‘masonry’ },
‘jquery.imagesLoaded’: { deps: [‘jquery’], exports: ‘imagesloaded’ },
‘jquery.simplemodal’: { deps: [‘jquery’], exports: ‘modal’ }
}
});
//these are defined externally so they can be used in our view templates:
define(‘jquery’, [], function () { return root.jQuery; });
define(‘moment’, [], function () { return root.moment; });
require([‘app’], function (app) {
app.run();
});
})();
[/sourcecode]
In the above code, we start by configuring require. First we set our baseUrl, which tells require every time we reference a dependency in any of our external script files, to assume that they start with the baseUrl (saves us typing redundant code basically). Then we declare our paths Line: 6
, these are for scripts that are in different urls other than our baseUrl (i.e. plugins and other libraries in our lib folder). We also use paths to set easier naming conventions for scripts that have complex file names (i.e. jquery.simplemodal = jquery.simplemodal.1.4.4.min etc)
After our paths, we setup a shim Line: 13
– which basically allows us to pre-define dependencies. So, for example, whenever we need Backbone, we don’t want to also have to say that we need underscore and jQuery too, we just want require to know that ahead of time.
Finally, we use define to setup those scripts that have already been loaded (jQuery from Google’s CDN and moment – both loaded manually from our index.cshtml file) Line: 23
.
Once the configuration is complete, we’re ready to call require and reference our app (app.js) Line: 27
and execute it. Lets take a look at app.js now.
App.js
Our app basically exists and runs in 1 script file, app.js. That is to say, this is where the main Backbone router exists and is initialized. From here, everything else then falls into place.
[sourcecode lang=”javascript”]
define([‘backbone’, ‘models/m_game’, ‘views/v_games’, ‘views/v_game’, ‘jquery.masonry’, ‘jquery.imagesLoaded’],
function (Backbone, Game, GamesView, GameView, masonry, imagesloaded) {
var run = function () {
bootBackboneRouter();
};
var bootBackboneRouter = function () {
var AppRouter = Backbone.Router.extend({
initialize: function () {
this.games = new Game.Collection();
},
routes: {
””: “main”
},
main: function () {
var that = this;
var view = new GamesView({ collection: that.games });
games.fetch({
//sample search parameters:
//data: {page: 2, searchstr: ‘some query’},
success: function () {
$(‘#app’).html(view.render().el);
$(‘#app ul’).imagesLoaded(function () {
$(‘#app ul’).masonry({
// options
itemSelector: ‘li’,
isFitWidth: true,
isAnimated: true,
columnWidth: 250
});
});
}
});
}
});
//boot up the app:
var appRouter = new AppRouter();
Backbone.history.start();
}
return {
run: run
};
});
[/sourcecode]
First thing to point out is the define call – which includes a laundry list of all of our dependencies for this particular chunk of code. You can see that in addition to backbone being required (obviously) we also need a game model, games and game views, and both jQuery masonry and jQuery imagesLoaded plugins.
What this code is doing is declaring a new Backbone router. First we initialize it by creating a new empty Games Backbone collection and assigning it to the property “this.games” Line: 10
. Then we setup our routes – just setting up a default root route and pointing it to the “main” function.
Inside our “main” function, the first thing we do is setup a holder for our “this” object. We named it “that” simply by convention/habit, but it can be named whatever you want Line: 17
. (It’s common to see “that” as a reference to “this” when inside a different scope.) Then we create a new GamesView Backbone view, and initialize its collection property with the empty games collection (created earlier in our initialization).
Using the router object’s games collection object, we call fetch() on it. Backbone’s collection.fetch() method will execute an ajax REST call against the objects predefined “url” property. What that means is that the games collection, which originally started out empty, will have its fetch() perform a GET against the server, and the server will return a JSON list of “games” that will then become the populated data of our games collection. Inside the fetch() call we have a success function declared. Line: 22
In here we basically tell the view (that we created right before our fetch()) “now that we have data, lets render out some html and populate the screen!” (basically).
Inside the success function, we populate our main div#app’s (in our index.cshtml file) internal html with the html that is output by the GamesView render().el Line: 23
. From there, we simply call some regular jQuery (the imagesLoaded plugin just makes sure all of the game’s boxarts are finished downloading) and then setup masonry to actually format the output of the html into fancy Pinterest style vertical columns. We could have just as easily ended our function after line 23.
Finally, we simply new up our AppRouter() and then launch Backbone via its history.start() method. To keep things simple and compartmentalized, this app object really only returns an ojbect with a single method “run”, and that method does the work outlined above.
Summary
OK, wow! That was a lot to cover. Lets go over some of that stuff a little more high level before moving on.
Basically, we have main.js which configures require.js and then calls require.js passing only a single dependency “app”. Inside that require call we simply execute app.run().
App.run() contains our core Backbone router. The router only defines a single route right now, the root of our application. Once this route is triggered (basically by default whenever you load the page), the route calls a functioned called “main”. Main will create a GamesView Backbone view, and call the games collection fetch() method which retrieves JSON from our server via a REST GET call. Once that data has been successfully retrieved, we will then execute our GamesView render() method and then populate our #app div html with the newly created html returned by the GamesView render().el.
So at this point, if we boot up our SPA, we should see a Pinterest style list of games displayed on the screen.
In the next part of this article, we will cover the actual GamesView view as well as its model and collection. We’ve seen them in action in our app.run() but lets actually dissect them and see exactly how they were built and setup.
[button link=”http://kroltech.com/?p=449″ block=”on” class=”” icon=”” style=”btn-warning” size=”btn-default” type=”btn-flat-square” target=”_self”] Proceed to Part 2 of Building a web app using Backbone.js and require.js [/button]