PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/_posts/2011-10-10-organizing-backbone-using-modules.md

https://github.com/duley666/backbonetutorials
Markdown | 386 lines | 300 code | 86 blank | 0 comment | 0 complexity | ab6457ca6a6d942062998d826bcee3bc MD5 | raw file
  1. ---
  2. layout: post
  3. title: Organizing your application using Modules (require.js)
  4. type: intermediate
  5. posturl: http://backbonetutorials.com/organizing-backbone-using-modules
  6. ---
  7. # Organizing your application using Modules (require.js)
  8. Unfortunately Backbone.js does not tell you how to organize your code, leaving many developers in the dark regarding how to load scripts and lay out their development environments.
  9. This was quite a different decision to other JavaScript MVC frameworks who were more in favor of setting a development philosophy.
  10. Hopefully this tutorial will allow you to build a much more robust project with great separation of concerns between design and code.
  11. This tutorial will get you started on combining Backbone.js with [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) (Asynchronous Module Definitions).
  12. ## What is AMD?
  13. [Asynchronous Module Definitions](https://github.com/amdjs/amdjs-api/wiki/AMD) designed to load modular code asynchronously in the browser and server. It is actually a fork of the Common.js specification. Many script loaders have built their implementations around AMD, seeing it as the future of modular JavaScript development.
  14. This tutorial will use [Require.js](http://requirejs.org) to implement a modular and organized Backbone.js.
  15. **I highly recommend using AMD for application development**
  16. Quick Overview
  17. * Modular
  18. * Scalable
  19. * Compiles well(see [r.js](http://requirejs.org/docs/optimization.html) )
  20. * Market Adoption( [Dojo 1.6 converted fully to AMD](http://dojotoolkit.org/reference-guide/releasenotes/1.6.html) )
  21. ## Why Require.js?
  22. p. Require.js has a great community and it is growing rapidly. [James Burke](http://tagneto.blogspot.com/) the author is married to Require.js and always responds to user feedback. He is a leading expert in script loading and a contributer to the AMD specification.
  23. <a href="https://twitter.com/jrburke" class="twitter-follow-button">Follow @jrburke</a>
  24. <script src="//platform.twitter.com/widgets.js" type="text/javascript"></script>
  25. ## Getting started
  26. To easily understand this tutorial you should jump straight into the example code base.
  27. [Example Codebase](https://github.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/modular-backbone)
  28. [Example Demo](http://backbonetutorials.com/examples/modular-backbone)
  29. The tutorial is only loosely coupled with the example and you will find the example to be more comprehensive.
  30. If you would like to see how a particular use case would be implemented please visit the GitHub page and create an issue.(Example Request: How to do nested views).
  31. The example isn't super fleshed out but should give you a vague idea.
  32. ## Example File Structure
  33. There are many different ways to lay out your files and I believe it is actually dependent on the size and type of the project. In the example below views and templates are mirrored in file structure. Collections and Models are categorized into folders kind of like an ORM.
  34. {% highlight javascript %}
  35. /* File Structure
  36. ├── imgs
  37. ├── css
  38. │ └── style.css
  39. ├── templates
  40. │ ├── projects
  41. │ │ ├── list.html
  42. │ │ └── edit.html
  43. │ └── users
  44. │ ├── list.html
  45. │ └── edit.html
  46. ├── js
  47. │ ├── libs
  48. │ │ ├── jquery
  49. │ │ │ ├── jquery.min.js
  50. │ │ ├── backbone
  51. │ │ │ ├── backbone.min.js
  52. │ │ └── underscore
  53. │ │ │ ├── underscore.min.js
  54. │ ├── models
  55. │ │ ├── users.js
  56. │ │ └── projects.js
  57. │ ├── collections
  58. │ │ ├── users.js
  59. │ │ └── projects.js
  60. │ ├── views
  61. │ │ ├── projects
  62. │ │ │ ├── list.js
  63. │ │ │ └── edit.js
  64. │ │ └── users
  65. │ │ ├── list.js
  66. │ │ └── edit.js
  67. │ ├── router.js
  68. │ ├── app.js
  69. │ ├── main.js // Bootstrap
  70. │ ├── order.js //Require.js plugin
  71. │ └── text.js //Require.js plugin
  72. └── index.html
  73. */
  74. {% endhighlight %}
  75. To continue you must really understand what we are aiming towards as described in the introduction.
  76. ## Bootstrapping your application
  77. Using Require.js we define a single entry point on our index page.
  78. We should setup any useful containers that might be used by our Backbone views.
  79. _Note: The data-main attribute on our single script tag tells Require.js to load the script located at "js/main.js". It automatically appends the ".js"_
  80. {% highlight html %}
  81. <!doctype html>
  82. <html lang="en">
  83. <head>
  84. <title>Jackie Chan</title>
  85. <!-- Load the script "js/main.js" as our entry point -->
  86. <script data-main="js/main" src="js/libs/require/require.js"></script>
  87. </head>
  88. <body>
  89. <div id="container">
  90. <div id="menu"></div>
  91. <div id="content"></div>
  92. </div>
  93. </body>
  94. </html>
  95. {% endhighlight %}
  96. You should most always end up with quite a light weight index file. You can serve this off your server and then the rest of your site off a CDN ensuring that everything that can be cached, will be. (You can also now serve the index file off the CDN using Cloudfront)
  97. ### What does the bootstrap look like?
  98. Our bootstrap file will be responsible for configuring Require.js and loading initially important dependencies.
  99. In the example below we configure Require.js to create a shortcut alias to commonly used scripts such as jQuery, Underscore and Backbone.
  100. Unfortunately Backbone.js isn't AMD enabled so I downloaded the community managed repository and patched it on [amdjs](https://github.com/amdjs).
  101. Hopefully if the AMD specification takes off these libraries will add code to allow themselves to be loaded asynchronously. Due to this inconvenience the bootstrap is not as intuitive as it could be.
  102. We also request a module called "app", this will contain the entirety of our application logic.
  103. _Note: Modules are loaded relatively to the boot strap and always append with ".js". So the module "app" will load "app.js" which is in the same directory as the bootstrap._
  104. {% highlight javascript %}
  105. // Filename: main.js
  106. // Require.js allows us to configure shortcut alias
  107. // There usage will become more apparent further along in the tutorial.
  108. require.config({
  109. paths: {
  110. jquery: 'libs/jquery/jquery',
  111. underscore: 'libs/underscore/underscore',
  112. backbone: 'libs/backbone/backbone'
  113. }
  114. });
  115. require([
  116. // Load our app module and pass it to our definition function
  117. 'app',
  118. ], function(App){
  119. // The "app" dependency is passed in as "App"
  120. App.initialize();
  121. });
  122. {% endhighlight %}
  123. ## How should we lay out external scripts?
  124. Any modules we develop for our application using AMD/Require.js will be asynchronously loaded.
  125. We have a heavy dependency on jQuery, Underscore and Backbone, unfortunately this libraries are loaded synchronously and also depend on each other existing in the global namespace.
  126. ## A boiler plate module
  127. So before we start developing our application, let's quickly look over boiler plate code that will be reused quite often.
  128. For convenience sake I generally keep a "boilerplate.js" in my application root so I can copy it when I need to.
  129. {%highlight javascript %}
  130. //Filename: boilerplate.js
  131. define([
  132. // These are path alias that we configured in our bootstrap
  133. 'jquery', // lib/jquery/jquery
  134. 'underscore', // lib/underscore/underscore
  135. 'backbone' // lib/backbone/backbone
  136. ], function($, _, Backbone){
  137. // Above we have passed in jQuery, Underscore and Backbone
  138. // They will not be accessible in the global scope
  139. return {};
  140. // What we return here will be used by other modules
  141. });
  142. {% endhighlight %}
  143. The first argument of the define function is our dependency array, in the future we can pass in any modules we like.
  144. ## App.js Building our applications main module
  145. Our applications main module should always remain light weight. This tutorial only covers setting up a Backbone Router and initializing it in our main module.
  146. The router will then load the correct dependencies depending on the current URL.
  147. {% highlight javascript %}
  148. // Filename: app.js
  149. define([
  150. 'jquery',
  151. 'underscore',
  152. 'backbone',
  153. 'router', // Request router.js
  154. ], function($, _, Backbone, Router){
  155. var initialize = function(){
  156. // Pass in our Router module and call it's initialize function
  157. Router.initialize();
  158. }
  159. return {
  160. initialize: initialize
  161. };
  162. });
  163. {% endhighlight %}
  164. {% highlight javascript %}
  165. // Filename: router.js
  166. define([
  167. 'jquery',
  168. 'underscore',
  169. 'backbone',
  170. 'views/projects/list',
  171. 'views/users/list'
  172. ], function($, _, Backbone, Session, ProjectListView, UserListView){
  173. var AppRouter = Backbone.Router.extend({
  174. routes: {
  175. // Define some URL routes
  176. '/projects': 'showProjects',
  177. '/users': 'showUsers',
  178. // Default
  179. '*actions': 'defaultAction'
  180. }
  181. });
  182. var initialize = function(){
  183. var app_router = new AppRouter;
  184. app_router.on('showProjects', function(){
  185. // Call render on the module we loaded in via the dependency array
  186. // 'views/projects/list'
  187. var projectListView = new ProjectListView();
  188. projectListView.render();
  189. });
  190. // As above, call render on our loaded module
  191. // 'views/users/list'
  192. app_router.on('showUsers', function(){
  193. var userListView = new UserListView();
  194. userListView.render();
  195. });
  196. app_router.on('defaultAction', function(actions){
  197. // We have no matching route, lets just log what the URL was
  198. console.log('No route:', actions);
  199. });
  200. Backbone.history.start();
  201. };
  202. return {
  203. initialize: initialize
  204. };
  205. });
  206. {% endhighlight %}
  207. ## Modularizing a Backbone View
  208. Backbone views usually interact with the DOM. Using our new modular system we can load in JavaScript templates using the Require.js text! plug-in.
  209. {% highlight javascript %}
  210. // Filename: views/project/list
  211. define([
  212. 'jquery',
  213. 'underscore',
  214. 'backbone',
  215. // Using the Require.js text! plugin, we are loaded raw text
  216. // which will be used as our views primary template
  217. 'text!templates/project/list.html'
  218. ], function($, _, Backbone, projectListTemplate){
  219. var ProjectListView = Backbone.View.extend({
  220. el: $('#container'),
  221. render: function(){
  222. // Using Underscore we can compile our template with data
  223. var data = {};
  224. var compiledTemplate = _.template( projectListTemplate, data );
  225. // Append our compiled template to this Views "el"
  226. this.$el.append( compiledTemplate );
  227. }
  228. });
  229. // Our module now returns our view
  230. return ProjectListView;
  231. });
  232. {% endhighlight %}
  233. JavaScript templating allows us to separate the design from the application logic by placing all our HTML in the templates folder.
  234. ## Modularizing a Collection, Model and View
  235. Now we put it altogether by chaining up a Model, Collection and View which is a typical scenario when building a Backbone.js application.
  236. First we will define our model
  237. {% highlight javascript %}
  238. // Filename: models/project
  239. define([
  240. 'underscore',
  241. 'backbone'
  242. ], function(_, Backbone){
  243. var ProjectModel = Backbone.Model.extend({
  244. defaults: {
  245. name: "Harry Potter"
  246. }
  247. });
  248. // Return the model for the module
  249. return ProjectModel;
  250. });
  251. {% endhighlight %}
  252. Now that we have a model, our collection module can depend on it. We will set the "model" attribute of our collection to the loaded module. Backbone.js offers great benefits when doing this.
  253. > Collection.model: Override this property to specify the model class that the collection contains. If defined, you can pass raw attributes objects (and arrays) to add, create, and reset, and the attributes will be converted into a model of the proper type.
  254. {% highlight javascript %}
  255. // Filename: collections/projects
  256. define([
  257. 'underscore',
  258. 'backbone',
  259. // Pull in the Model module from above
  260. 'models/project'
  261. ], function(_, Backbone, ProjectModel){
  262. var ProjectCollection = Backbone.Collection.extend({
  263. model: ProjectModel
  264. });
  265. // You don't usually return a collection instantiated
  266. return ProjectCollection;
  267. });
  268. {% endhighlight %}
  269. Now we can simply depend on our collection in our view and pass it to our JavaScript template.
  270. {% highlight javascript %}
  271. // Filename: views/projects/list
  272. define([
  273. 'jquery',
  274. 'underscore',
  275. 'backbone',
  276. // Pull in the Collection module from above
  277. 'collections/projects',
  278. 'text!templates/projects/list.html'
  279. ], function($, _, Backbone, ProjectsCollection, projectsListTemplate){
  280. var ProjectListView = Backbone.View.extend({
  281. el: $("#container"),
  282. initialize: function(){
  283. this.collection = new ProjectsCollection();
  284. this.collection.add({ name: "Ginger Kid"});
  285. // Compile the template using Underscores micro-templating
  286. var compiledTemplate = _.template( projectsListTemplate, { projects: this.collection.models } );
  287. this.$el.html(compiledTemplate);
  288. }
  289. });
  290. // Returning instantiated views can be quite useful for having "state"
  291. return ProjectListView;
  292. });
  293. {% endhighlight %}
  294. ## Conclusion
  295. Looking forward to feedback so I can turn this post and example into quality references on building modular JavaScript applications.
  296. Get in touch with me on twitter, comments or GitHub!
  297. ### Relevant Links
  298. * [Organizing Your Backbone.js Application With Modules](http://weblog.bocoup.com/organizing-your-backbone-js-application-with-modules)
  299. ### Contributors
  300. * [Jakub Kozisek](https://github.com/dzejkej) (created modular-backbone-updated containing updated libs with AMD support)