├── .gitignore ├── docs ├── things-outside-views.md ├── event-handlers-outside-views.md ├── docpress.json ├── other-links.md ├── naming-convention.md ├── README.md ├── requirejs-and-amd.md ├── document-ready-abuse.md ├── partials.md ├── namespace-convention.md ├── jst-templates.md ├── subviews.md ├── inline-templates.md ├── delegate-views.md ├── file-naming.md ├── bootstrapping-data.md ├── animation-buffer.md └── mixins.md ├── .travis.yml ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | node_modules 3 | _docpress 4 | -------------------------------------------------------------------------------- /docs/things-outside-views.md: -------------------------------------------------------------------------------- 1 | # Things outside views 2 | 3 | Put things in your view class code as much as possible. 4 | -------------------------------------------------------------------------------- /docs/event-handlers-outside-views.md: -------------------------------------------------------------------------------- 1 | # Event handlers outside views 2 | 3 | Every time you make an event handler outside a view class, consider making a new 4 | view class. 5 | 6 | ``` javascript 7 | App.PhotoView = Backbone.View.extend({ 8 | ... 9 | }); 10 | 11 | // AVOID this! 12 | $("a.photo").click(function() { ... }); 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/docpress.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": "rstacruz/backbone-patterns", 3 | "css": [ 4 | "http://ricostacruz.com/docpress-rsc/style.css" 5 | ], 6 | "markdown": { 7 | "typographer": true, 8 | "plugins": { 9 | "decorate": {} 10 | } 11 | }, 12 | "googleAnalytics": { 13 | "id": "UA-20473929-1", 14 | "domain": "ricostacruz.com" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | script: 5 | - ./node_modules/.bin/docpress build 6 | after_success: 7 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then ./node_modules/.bin/git-update-ghpages -e; fi 8 | cache: 9 | directories: 10 | - node_modules 11 | env: 12 | global: 13 | - GIT_NAME: Travis CI 14 | - GIT_EMAIL: nobody@nobody.org 15 | - GITHUB_REPO: rstacruz/backbone-patterns 16 | - GIT_SOURCE: _docpress 17 | -------------------------------------------------------------------------------- /docs/other-links.md: -------------------------------------------------------------------------------- 1 | # Other links 2 | 3 | Other links of interest: 4 | 5 | * [Backbone][backbone] official documentation 6 | * [Backbonetutorials.com][bbtutorials] by Thomas Davis covers the basics of 7 | Backbone.js in more detail than the official docs. 8 | * [Backbone Fundamentals][bbfund] by Addy Osmani is a Creative Commons book for 9 | beginners and advanced users alike. 10 | 11 | [backbone]: http://documentcloud.github.com/backbone/ 12 | [bbtutorials]: http://backbonetutorials.com 13 | [bbfund]: https://github.com/addyosmani/backbone-fundamentals 14 | 15 | -------------------------------------------------------------------------------- /docs/naming-convention.md: -------------------------------------------------------------------------------- 1 | # Naming convention 2 | 3 | Classes often start in uppercase letters, while instances start with lowercase 4 | letters. This is a throwback of the general Python and Ruby practice of having 5 | constant names start with uppercase letters. 6 | 7 | ``` javascript 8 | // Classes: 9 | Photo 10 | Album 11 | Author 12 | 13 | // Instances: 14 | photo 15 | myAlbum 16 | ``` 17 | 18 | For names with multiple words, JavaScript often calls for CamelCase. Using 19 | underscores are discouraged in JavaScript. 20 | 21 | ``` javascript 22 | // Good (CamelCase): 23 | PhotoAlbum 24 | albumCover 25 | 26 | // Avoid (under_scores): 27 | photo_album 28 | album_cover 29 | ``` 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone-patterns", 3 | "version": "1.0.1", 4 | "description": "Here, I try to document the good practices that our team has learned along the way building [Backbone][bb] applications.", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/rstacruz/backbone-patterns.git" 15 | }, 16 | "author": "Rico Sta. Cruz ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/rstacruz/backbone-patterns/issues" 20 | }, 21 | "homepage": "https://github.com/rstacruz/backbone-patterns#readme", 22 | "devDependencies": { 23 | "docpress": "0.6.13", 24 | "git-update-ghpages": "1.3.0" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [Backbone Patterns](../README.md) 4 | * Model patterns 5 | * [Bootstrapping data](/docs/bootstrapping-data.md) 6 | * View patterns 7 | * [Inline templates](/docs/inline-templates.md) 8 | * [JST templates](/docs/jst-templates.md) 9 | * [Partials](/docs/partials.md) 10 | * [Animation buffer](/docs/animation-buffer.md) 11 | * [Sub-views](/docs/subviews.md) 12 | * [Delegate views](/docs/delegate-views.md) 13 | * General patterns 14 | * [Mixins](/docs/mixins.md) 15 | * Conventions 16 | * [Naming convention](/docs/naming-convention.md) 17 | * [Namespace convention](/docs/namespace-convention.md) 18 | * [RequireJS and AMD](/docs/requirejs-and-amd.md) 19 | * Anti-patterns 20 | * [document.ready abuse](/docs/document-ready-abuse.md) 21 | * [Things outside views](/docs/things-outside-views.md) 22 | * [Event handlers outside views](/docs/event-handlers-outside-views.md) 23 | * [Other links](/docs/other-links.md) 24 | -------------------------------------------------------------------------------- /docs/requirejs-and-amd.md: -------------------------------------------------------------------------------- 1 | # RequireJS and AMD 2 | 3 | You may adopt a [Asynchronous Module Definition][amd]-style method of 4 | organization using a library like [RequireJS][require.js]. This will allow you 5 | to organize your modules in the `require(...)` way familiar to those who use 6 | NodeJS. 7 | 8 | If you adopt an AMD library, there will be no need to use namespaces for your 9 | JavaScript classes. 10 | 11 | ``` javascript 12 | define(function(require) { 13 | var Photo = require('models/photo'); 14 | var Photos = require('collections/photos'); 15 | var MenuView = require('views/menu'); 16 | var MainRouter = require('router/main'); 17 | 18 | // ... 19 | }); 20 | ``` 21 | 22 | For more information on RequireJS, AMD, and using it on your Backbone project, 23 | see: 24 | 25 | * [Organizing Backbone using Modules][bbt.modules] (via Backbonetutorials.com) 26 | * [RequireJS][require.js]'s official site 27 | 28 | [require.js]: http://requirejs.org 29 | [bbt.modules]: http://backbonetutorials.com/organizing-backbone-using-modules/ 30 | [amd]: http://requirejs.org/docs/whyamd.html 31 | -------------------------------------------------------------------------------- /docs/document-ready-abuse.md: -------------------------------------------------------------------------------- 1 | # document.ready abuse 2 | 3 | jQuery allows you to defer execution of code until when the DOM is fully-loaded 4 | with [$(document).ready(...)][jquery.ready], or its short form, `$(...)`. This 5 | is useful for getting everything set up once your HTML document is ready. 6 | 7 | [jquery.ready]: http://api.jquery.com/ready/ 8 | 9 | ``` javascript 10 | $(document).ready(function() { 11 | // Initialize the router. 12 | App.router = new App.MainRouter; 13 | 14 | // Initialize the main view. 15 | App.dashboard = new App.Dashboard({ ... }); 16 | 17 | // and so on... 18 | }); 19 | 20 | // Or its shorter form: 21 | $(function() { 22 | // ... 23 | }); 24 | ``` 25 | 26 | A common anti-pattern is to put class definitions (for views, models, and such) 27 | inside these blocks. They are not necessary. 28 | 29 | ``` javascript 30 | // AVOID this: 31 | $(function() { 32 | App.PhotoView = Backbone.View.extend({ 33 | ... 34 | }); 35 | }); 36 | ``` 37 | 38 | Your classes should be ready before the HTML DOM is. This will save you from 39 | running into problems later where certain classes may not be available at 40 | certain parts of your application. 41 | 42 | ``` javascript 43 | // Consider instead: 44 | App.PhotoView = Backbone.View.extend({ 45 | ... 46 | }); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/partials.md: -------------------------------------------------------------------------------- 1 | # Partials 2 | 3 | __The problem:__ there may be parts of HTML templates that can be reused in many 4 | parts of the application. Defining them more than once is not DRY, which may 5 | make your application less maintainable. 6 | 7 | __Solution:__ separating these snippets into partials. 8 | 9 | Partials are templates that are meant to be used *inside* other templates. 10 | 11 | One typical use of partials is for lists where the template for list items may 12 | be defined as a separate template from the list itself. 13 | 14 | ## Solution 15 | 16 | You can pass the template function for the partial as a parameter to the first 17 | template. 18 | 19 | In this example, the function `itemTemplate` is passed onto the parameters for 20 | `template()`. 21 | 22 | ``` javascript 23 | TasksList = Backbone.View.extend({ 24 | template: _.template([ 25 | "" 30 | ].join('')), 31 | 32 | itemTemplate: _.template( 33 | "
  • <%= name %>
  • " 34 | ), 35 | 36 | render: function() { 37 | var html = this.template({ 38 | items: tasks /* a collection */, 39 | itemTemplate: this.itemTemplate 40 | }); 41 | 42 | $(this.el).append(html); 43 | } 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/namespace-convention.md: -------------------------------------------------------------------------------- 1 | # Namespace convention 2 | 3 | The convention we use puts everything in one `App` namespace to keep things 4 | organized properly. 5 | 6 | ``` javascript 7 | window.App = { 8 | ... 9 | }; 10 | ``` 11 | 12 | Subsequent models, views, and other classes will be made in this namespace. 13 | 14 | ``` javascript 15 | App.Photo = Backbone.Model.extend({ 16 | ... 17 | }; 18 | ``` 19 | 20 | Some people prefer to use namespaces based on their app's name. Consider, say, 21 | `BF.Photo` (instead of `App.Photo`) if your application name is "Bacefook." 22 | 23 | Models: App.Photo 24 | Collections: App.Photos 25 | Views: App.PhotoView 26 | Main router: App.Router 27 | Custom routers: App.SpecialRouter 28 | 29 | Router instance: App.router 30 | View instances: App.photoView 31 | Singleton model instances: App.photo 32 | Collection instances: App.photos 33 | 34 | ### Variation: two-level namespace 35 | 36 | Some people prefer a verbose two-level version where the classes are divided 37 | into their own namespaces as well. 38 | 39 | This is often done to make it easy to iterate over all available models, 40 | collections, and views. 41 | 42 | Models: App.Models.Photo 43 | Collections: App.Collections.Photos 44 | Views: App.Views.Photo 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backbone
    Patterns 2 | 3 | 4 | 5 | > Building apps with Backbone.js 6 | 7 | Here, I try to document the good practices that our team has learned along the 8 | way building [Backbone][bb] applications. This document assumes that you already 9 | have some knowledge of [Backbone.js][bb], [jQuery][jq], and of course, 10 | JavaScript itself. 11 | 12 | [bb]: http://documentcloud.github.com/backbone/ 13 | [jq]: http://jquery.com/ 14 | 15 | ## Table of contents 16 | 17 | * Model patterns 18 | * [Bootstrapping data](docs/bootstrapping-data.md) 19 | * View patterns 20 | * [Inline templates](docs/inline-templates.md) 21 | * [JST templates](docs/jst-templates.md) 22 | * [Partials](docs/partials.md) 23 | * [Animation buffer](docs/animation-buffer.md) 24 | * [Sub-views](docs/subviews.md) 25 | * [Delegate views](docs/delegate-views.md) 26 | * General patterns 27 | * [Mixins](docs/mixins.md) 28 | * Conventions 29 | * [Naming convention](docs/naming-convention.md) 30 | * [Namespace convention](docs/namespace-convention.md) 31 | * [RequireJS and AMD](docs/requirejs-and-amd.md) 32 | * Anti-patterns 33 | * [document.ready abuse](docs/document-ready-abuse.md) 34 | * [Things outside views](docs/things-outside-views.md) 35 | * [Event handlers outside views](docs/event-handlers-outside-views.md) 36 | * [Other links](docs/other-links.md) 37 | 38 | ## Thanks 39 | 40 | © 2011-2016, Rico Sta. Cruz. Released under the [MIT 41 | License](http://www.opensource.org/licenses/mit-license.php). 42 | 43 | This document is authored and maintained by [Rico Sta. Cruz][rsc] with help from 44 | its [contributors][c]. 45 | 46 | * [My website](http://ricostacruz.com) (ricostacruz.com) 47 | * [Github](http://github.com/rstacruz) (@rstacruz) 48 | * [Twitter](http://twitter.com/rstacruz) (@rstacruz) 49 | 50 | [rsc]: http://ricostacruz.com 51 | [c]: http://github.com/rstacruz/backbone-patterns/contributors 52 | -------------------------------------------------------------------------------- /docs/jst-templates.md: -------------------------------------------------------------------------------- 1 | # JST templates 2 | 3 | __The problem:__ if you need to use view templates in a small-to-large Backbone 4 | application, defining your templates in JavaScript code will be unwieldy and 5 | difficult to maintain. 6 | 7 | __Solution:__ You may need put the templates in a JavaScript file. 8 | 9 | ## The structure 10 | 11 | Your app will need to serve a _dynamically-created_ JavaScript file that 12 | compiles your files. 13 | 14 | A common JST file will create the `JST` object (in the window namespace), with 15 | each of its members defined as template functions. In this example, we'll use 16 | Underscore's `_.template`, which returns functions. 17 | 18 | ``` javascript 19 | // http://myapp.com/javascripts/jst.js 20 | window.JST = {}; 21 | 22 | window.JST['person/contact'] = _.template( 23 | "
    <%= name %> ..." 24 | ); 25 | 26 | window.JST['person/edit'] = _.template( 27 | "
    35 | ``` 36 | 37 | ## Using JST templates 38 | 39 | In your JavaScript code, simply access the JST object's members to access the 40 | views. 41 | 42 | ``` javascript 43 | var html = JST['person/edit'](); 44 | 45 | var dict = { name: "Jason", email: "j.smith@gmail.com" }; 46 | var html = JST['person/contact'](dict); 47 | ``` 48 | 49 | ## Integration notes 50 | 51 | * __Rails 3.1 and above__: The Rails Asset pipeline already comes with support 52 | for JST pages. 53 | * __Rails 3.0 and below__: consider using [Sprockets][sprockets] or 54 | [Jammit][jammit]. 55 | * __In Sinatra__: The [sinatra-backbone][sinatra-backbone] gem can take care of 56 | dynamically serving JST templates. 57 | 58 | [jammit]: http://documentcloud.github.com/jammit 59 | [sprockets]: http://getsprockets.org 60 | [sinatra-backbone]: http://ricostacruz.com/sinatra-backbone 61 | -------------------------------------------------------------------------------- /docs/subviews.md: -------------------------------------------------------------------------------- 1 | # Sub-views 2 | 3 | __The problem:__ Your view code is starting to bloat as it tries to do too many 4 | things in one class. 5 | 6 | __The solution:__ Break it apart into smaller sub-views. 7 | 8 | ## The situation 9 | 10 | This is a common occurrence if you have one _giant_ view that takes care of the 11 | entire page. View classes may become unwieldy once they get up to 200 lines. 12 | 13 | ## Solution 1: Sub views 14 | 15 | It may be wise to delegate some areas of the view to be the responsibility of 16 | another view. 17 | 18 | In this example, we have a view that handles the entire application "chrome." 19 | Let's break apart some of its parts on its `render()` function. Notice that 20 | we're using `this.$()` to select elements inside the `ChromeView`'s element 21 | itself. 22 | 23 | ``` javascript 24 | App.ChromeView = Backbone.View.extend({ 25 | render: function() { 26 | // Instantiate some "sub" views to handle the responsibilities of 27 | // their respective elements. 28 | this.sidebar = new App.SidebarView({ el: this.$(".sidebar") }); 29 | this.menu = new App.NavigationView({ el: this.$("nav") }); 30 | } 31 | }); 32 | 33 | $(function() { 34 | App.chrome = new App.ChromeView({ el: $("#chrome") }); 35 | }); 36 | ``` 37 | 38 | We will then be able to access the sub-views like so: 39 | 40 | ``` javascript 41 | App.chrome.sidebar.toggle(); 42 | 43 | App.chrome.menu.expand(); 44 | ``` 45 | 46 | ## Events 47 | 48 | All Backbone objects can emit [events][events]. To maintain the separation of 49 | responsibilities of the view classes, you may have the sub-views trigger 50 | events that the parent view would need (and vice versa). 51 | 52 | [events]: http://documentcloud.github.com/backbone/#Events 53 | 54 | For instance, we may implement `SidebarView` to trigger events when the sidebar 55 | is collapsed or expanded: 56 | 57 | ``` javascript 58 | App.SidebarView = Backbone.View.extend({ 59 | toggle: function() { 60 | if ($(this.el).is(':visible')) { 61 | $(this.el).hide(); 62 | this.trigger('collapse'); // <== 63 | } else { 64 | $(this.el).show(); 65 | this.trigger('expand'); // <== 66 | } 67 | }, 68 | }); 69 | ``` 70 | 71 | And the parent view (`ChromeView`) may listen to them like so: 72 | 73 | ``` javascript 74 | App.ChromeView = Backbone.View.extend({ 75 | render: function() { 76 | this.sidebar = new App.SidebarView({ el: this.$(".sidebar") }); 77 | 78 | this.sidebar 79 | .bind('collapse', this.onSidebarCollapse) 80 | .bind('expand', this.onSidebarExpand); 81 | 82 | // ... 83 | } 84 | }); 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/inline-templates.md: -------------------------------------------------------------------------------- 1 | # Inline templates 2 | 3 | __The problem:__ if you need to use view templates in a small Backbone 4 | application, defining your templates in JavaScript code will be unwieldy and 5 | difficult to maintain. 6 | 7 | __Solution:__ You may need some view templates to be inline in the HTML page. 8 | 9 | This solution has been outlined by John Resig in his blog post about [JavaScript 10 | micro templating](http://ejohn.org/blog/javascript-micro-templating/). 11 | 12 | ## Defining inline templates 13 | You can put templates in an HTML ` 27 | ``` 28 | 29 | ## Using inline templates 30 | In JavaScript, you can get the `innerHTML` (or jQuery's [.html()][html]) of that 31 | HTML element to fetch the raw template data. You can pass this onto Underscore's 32 | `_.template` to create a template function. 33 | 34 | [html]: http://api.jquery.com/html 35 | 36 | ``` javascript 37 | $("#template-contact").html(); 38 | //=> "
    \n<%= name %> function() { ... } 42 | ``` 43 | 44 | ## Integrating into Backbone 45 | 46 | In practice, you will most likely be using this in the `render()` method of a 47 | view like so. 48 | 49 | ``` javascript 50 | var ContactView = Backbone.View.extend({ 51 | template: _.template($("#template-contact").html()), 52 | 53 | render: function() { 54 | // This is a dictionary object of the attributes of the models. 55 | // => { name: "Jason", email: "j.smith@gmail.com" } 56 | var dict = this.model.toJSON(); 57 | 58 | // Pass this object onto the template function. 59 | // This returns an HTML string. 60 | var html = this.template(dict); 61 | 62 | // Append the result to the view's element. 63 | $(this.el).append(html); 64 | 65 | // ... 66 | } 67 | }); 68 | ``` 69 | 70 | ## Limitations 71 | 72 | __Single-page apps only.__ 73 | This assumes that your Backbone application is all contained in one HTML page. 74 | If your app spans across multiple HTML pages, and each page will be needing the 75 | same templates, you may be redundantly streaming the template data to the 76 | browser unnecessarily. Consider using JST templates instead. 77 | 78 | Note that the given example assumes that the `#template-contact` element appears 79 | before you include JavaScript files, as it requires the template element to be 80 | accessible before the class is defined. 81 | -------------------------------------------------------------------------------- /docs/delegate-views.md: -------------------------------------------------------------------------------- 1 | # Delegate views 2 | 3 | __The problem:__ Your view code is starting to bloat as it tries to do too many 4 | things in one class, and making sub-views with its child elements is not an 5 | option. 6 | 7 | __The solution:__ Make a sub-view with the same element. This will allow you to 8 | [delegate][delegate] certain responsibilities to another view class. 9 | 10 | [delegate]: http://en.wikipedia.org/wiki/Delegation_pattern 11 | 12 | ## Solution 13 | 14 | You can make 2 or more views that target the same element. This is useful when 15 | there are many controls in a view, but creating sub-views (with their scopes 16 | limited to a set of elements in the bigger view) may be too messy, or just not 17 | possible. 18 | 19 | In this example, `ChromeView` will make a sub-view that shares the same element 20 | as it does. 21 | 22 | ``` javascript 23 | App.ChromeView = Backbone.View.extend({ 24 | events: { 25 | 'click button': 'onButtonClick' 26 | }, 27 | render: function() { 28 | // Pass our own element to the other view. 29 | this.tabs = new App.TabView({ el: this.el }); 30 | } 31 | }); 32 | 33 | App.TabView = Backbone.View.extend({ 34 | // Notice this view has its own events. They will not 35 | // interfere with ChromeView's events. 36 | events: { 37 | 'click nav.tabs a': 'switchTab' 38 | }, 39 | 40 | switchTab: function(tab) { 41 | // ... 42 | }, 43 | 44 | hide: function() { 45 | // ... 46 | } 47 | }); 48 | ``` 49 | 50 | ## Using delegate views 51 | 52 | You can delegate some functionality to the sub-view. In this example, we can 53 | write the (potentially long) code for hiding tabs in the `TabView`, making 54 | `ChromeView` easier to maintain and manage. 55 | 56 | ``` javascript 57 | App.ChromeView = Backbone.View.extend({ 58 | // ... 59 | 60 | goFullscreen: function() { 61 | this.tabs.hide(); 62 | } 63 | }); 64 | ``` 65 | 66 | You may also provide publicly-accessible methods to `TabView` that will be meant 67 | to be accessed outside of `ChromeView`. 68 | 69 | ``` javascript 70 | var chrome = new App.ChromeView; 71 | chrome.tabs.switchTab('home'); 72 | ``` 73 | 74 | ## Variation: private delegate views 75 | 76 | You can also make delegate views *private* by design: that is, it shouldn't be 77 | used outside the parent view (`ChromeView` in our example). 78 | 79 | As JavaScript lacks true private attributes, you can set prefix it with an 80 | underscore to signify that it's private and is not part of it's public 81 | interface. (This is a practice taken from Python's [official style 82 | guide][pep8].) 83 | 84 | ``` javascript 85 | App.ChromeView = Backbone.View.extend({ 86 | render: function() { 87 | this._tabs = new App.TabView({ el: this.el }); 88 | } 89 | }); 90 | ``` 91 | 92 | [pep8]: http://www.python.org/dev/peps/pep-0008/ 93 | 94 | -------------------------------------------------------------------------------- /docs/file-naming.md: -------------------------------------------------------------------------------- 1 | # File naming 2 | 3 | For applications that do _not_ use [Asynchronous Module Definition][amd]-style 4 | organization, there always seem to be 3 basic JavaScript files. 5 | 6 | [amd]: http://requirejs.org/docs/whyamd.html 7 | 8 | ## The main namespace 9 | 10 | This is often `app.js`, which defines the basic namespace. 11 | 12 | ``` javascript 13 | // app.js 14 | window.App = { 15 | ... 16 | }; 17 | ``` 18 | 19 | ## The individual classes 20 | 21 | If you use the namespacing method outlined earlier in this document, there are 2 22 | common naming conventions for individual classes: 23 | 24 | * Name the files as the exact class name they contain. For instance, 25 | `App.PhotoView` should be stored as `app/photoview.js`. 26 | 27 | * Place each of the class types in their own folders. For instance, 28 | the `PhotoView` may be defined as `app/views/photoview.js`, or 29 | `views/photoview.js`. 30 | 31 | In this approach, **avoid** putting code in the files other than the actual 32 | class it defines. This makes your convention predictable for the benefit of 33 | those new to your project. 34 | 35 | ``` javascript 36 | // app/photoview.js 37 | App.PhotoView = Backbone.View.extend({ 38 | ... 39 | }); 40 | ``` 41 | 42 | ## The setup/glue code file 43 | 44 | This is the file where you do miscellaneous things that do not belong in any of 45 | the Backbone classes: 46 | 47 | * Instantiate the default view 48 | * Initialize the Backbone Router 49 | * Provide options for jQuery and its plugins 50 | 51 | This is often named `application.js` or `setup.js`. 52 | 53 | In larger projects, this can span multiple files. Don't be afraid to refactor it 54 | to multiple files. 55 | 56 | This is often the only place you will want to put the onload hook 57 | `$(function() { ... })`. 58 | 59 | ``` javascript 60 | $(function() { 61 | // Set up some options for jQuery and plugins. 62 | $(document).ajaxError(function() { 63 | alert("There was an error."); 64 | }); 65 | 66 | // Provide options for your plugins. 67 | $("a[rel~=lightbox]").click(function() { 68 | $(this).openAsLightbox(); 69 | }); 70 | 71 | Backbone.emulateJSON = true; 72 | 73 | // Initialize Backbone views. 74 | App.chromeView = new App.ChromeView({ el: $("body") }); 75 | App.router = new App.Router; 76 | 77 | // Initialize the Backbone router. 78 | Backbone.history.start(); 79 | }); 80 | ``` 81 | 82 | ## Load order 83 | 84 | Consider loading them in this order: 85 | 86 | * `app.js` (the namespace) 87 | * `app/*.js` (individual classes) 88 | * `setup.js` (the glue) 89 | 90 | ``` html 91 | 92 | 93 | 94 | 95 | 96 | ``` 97 | -------------------------------------------------------------------------------- /docs/bootstrapping-data.md: -------------------------------------------------------------------------------- 1 | # Bootstrapping data 2 | 3 | __The problem:__ Your application needs models to be available on page load. 4 | 5 | __Solution:__ Bootstrap collections and models by creating collections in an 6 | inline ` 29 | 30 | ``` 31 | 32 | ## Accessing instances 33 | 34 | To get a single `Photo`, instead of creating a `Photo` instance and using 35 | `fetch()`, simply pluck it from the giant collection. 36 | 37 | ``` javascript 38 | // Gets by ID 39 | var photo = App.photos.get(2); 40 | 41 | // Gets a bunch of photos based on criteria 42 | var photo = App.photos.select(function(photo) { 43 | return photo.get('filename').match(/^IMG/); 44 | }); 45 | ``` 46 | 47 | ## In Ruby (ERB) 48 | 49 | In your server-side templates, you will probably be using `to_json` on a 50 | collection of your server-side models. 51 | 52 | ``` html 53 | 56 | ``` 57 | 58 | On Rails, you may need to use the [.html_safe][] method. 59 | 60 | ``` html 61 | 64 | ``` 65 | 66 | [.html_safe]: http://api.rubyonrails.org/classes/ActiveSupport/SafeBuffer.html 67 | 68 | ## In Ruby (HAML) 69 | 70 | If you use HAML, you will need use a syntax similar to this. 71 | 72 | ``` haml 73 | :javascript 74 | App.photos = new Photos(#{@photos.to_json}); 75 | ``` 76 | 77 | ## In PHP 78 | 79 | In your server-side templates, you will probably be using [json_encode()][] on a 80 | collection of your server-side models. 81 | 82 | ``` php 83 | 86 | ``` 87 | 88 | [json_encode()]: http://php.net/manual/en/function.json-encode.php 89 | 90 | ## In C# (Razor) 91 | 92 | In your Razor view, you may parse your server side models into JSON using [Json.Encode()][]. 93 | 94 | ``` 95 | 98 | ``` 99 | 100 | [bb.bootstrap]: http://documentcloud.github.com/backbone/#FAQ-bootstrap 101 | [Json.Encode()]: https://msdn.microsoft.com/en-us/library/system.web.helpers.json(v%3Dvs.111).aspx 102 | -------------------------------------------------------------------------------- /docs/animation-buffer.md: -------------------------------------------------------------------------------- 1 | # Animation buffer 2 | 3 | __The problem:__ When you have events that trigger animations, they can mess up 4 | when the user clicks too fast. 5 | 6 | __The solution:__ Make a buffering system to ensure that animations are fired 7 | serially (one after the other) and never parallel (at the same time). 8 | 9 | ## The situation 10 | 11 | Let's say you have this innocent code that performs an animation. 12 | 13 | One fundamental flaw here is that it assumes that `.showNext()` will only be 14 | called when it is not animating. When the user clicks "Next" while the animation 15 | is working, unexpected results will occur. 16 | 17 | ``` javascript 18 | PicturesView = Backbone.View.extend({ 19 | events: { 20 | 'click .next': 'showNext' 21 | }, 22 | 23 | showNext: function() { 24 | var current = this.$(".current"); 25 | var nextDiv = this.$(".current + div"); 26 | 27 | if (nextDiv.length == 0) { return; } 28 | 29 | // Make the current one move to the left via jQuery. 30 | // This uses jQuery.fn.animate() that changes CSS values, then fires 31 | // the function supplied when it's done. 32 | current.animate({ left: -300, opacity: 0 }, function() { 33 | current.removeClass('.current'); 34 | nextDiv.addClass('.current'); 35 | }); 36 | } 37 | }); 38 | ``` 39 | 40 | ## The solution 41 | 42 | Here's a simple buffering solution. It provides two commands: 43 | 44 | * `add(fn)` which adds a given function to the buffer, and 45 | * `next()` which moves onto the next command. This is passed onto the functions 46 | when they are called. 47 | 48 | To use this, put your animations inside an anonymous function to be passed onto 49 | `add()`. Be sure to trigger `next()` when the animations are done. 50 | 51 | ``` javascript 52 | Buffer = { 53 | commands: [], 54 | 55 | add: function(fn) { 56 | // Adds a command to the buffer, and executes it if it's 57 | // the only command to be ran. 58 | var commands = this.commands; 59 | commands.push(fn); 60 | if (this.commands.length == 1) fn(next); 61 | 62 | // Moves onto the next command in the buffer. 63 | function next() { 64 | commands.shift(); 65 | if (commands.length) commands[0](next); 66 | } 67 | } 68 | }; 69 | ``` 70 | 71 | ## Example 72 | 73 | This is our example from a while ago that has been modified to use the bufferer. 74 | 75 | ``` javascript 76 | showNext: function() { 77 | var current = this.$(".current"); 78 | var nextDiv = this.$(".current + div"); 79 | 80 | if (nextDiv.length == 0) { return; } 81 | 82 | // Ensure that the animation will not happen while another 83 | // animation is ongoing. 84 | Buffer.add(function(next) { 85 | current.animate({ left: -300, opacity: 0 }, function() { 86 | current.removeClass('.current'); 87 | nextDiv.addClass('.current'); 88 | 89 | // Trigger the next animation. 90 | next(); 91 | }); 92 | }); 93 | } 94 | ``` 95 | 96 | ## Variations 97 | 98 | You can make the `Buffer` object into a class that you can instantiate. This 99 | lets you have multiple buffers as you need. This way, you can have a buffer for 100 | each view instance. 101 | 102 | jQuery also provides a very similar function, [jQuery.fn.queue()][queue]. This 103 | may be adequate for most simple animations. 104 | 105 | [queue]: http://api.jquery.com/queue/ 106 | -------------------------------------------------------------------------------- /docs/mixins.md: -------------------------------------------------------------------------------- 1 | # Mixins 2 | 3 | __The problem:__ Sometimes you have the same functionality for multiple objects and it doesn't 4 | make sense to wrap your objects in a parent object. For example, if you have 5 | two views that share methods but don't -- and shouldn't -- have a shared 6 | parent view. 7 | 8 | __The solution:__ For this scenario, it's appropriate to use a mixin. 9 | 10 | ## Defining mixins 11 | 12 | You can define an object that has attributes and methods that can be shared 13 | across different classes. This is called a [mixin][mixin]. 14 | 15 | You can define a mixin as a regular object literal with functions in it. 16 | 17 | ``` javascript 18 | App.Mixins.Navigation = { 19 | 20 | toggle: function() { /* ... */ }, 21 | 22 | open: function() { /*... */ }, 23 | 24 | close: function() { /* ... */ } 25 | 26 | }; 27 | ``` 28 | 29 | ## Using mixins 30 | 31 | You may then extend your classes with these mixins. You can use Underscore's 32 | [_.extend][extend] function to attach these to your class prototypes. 33 | 34 | [mixin]: http://en.wikipedia.org/wiki/Mixin 35 | [extend]: http://documentcloud.github.com/underscore/#extend 36 | 37 | ``` javascript 38 | App.Views.Menu = Backbone.View.extend({ 39 | // I need to know how to toggle, open, and close! 40 | }); 41 | 42 | _.extend(App.Views.Menu.prototype, App.Mixins.Navigation); 43 | 44 | App.Views.Tabs = Backbone.View.extend({ 45 | // I too need to know how to toggle, open, and close! 46 | }); 47 | 48 | _.extend(App.Views.Tabs.prototype, App.Mixins.Navigation); 49 | 50 | ``` 51 | 52 | ## Alternative syntax 53 | 54 | The above presents two caveats, which can be problematic in some situations: 55 | 56 | * Your attributes and methods in your mixin will *override* the methods you 57 | define in the class itself (via `Backbone.View.extend`). Ideally, it should be 58 | the other way around. 59 | 60 | * The `_.extend(...)` line is after all the methods you've defined in the 61 | class, and can easily be neglected by developers new to your project. 62 | 63 | To remedy this, you can use this alterative syntax. This will let you write 64 | methods and attributes in your class that will override the mixin's default 65 | behavior. 66 | 67 | ``` javascript 68 | App.Views.Menu = Backbone.View.extend( 69 | _.extend({}, App.Mixins.Navigation, { 70 | 71 | // (Methods and attributes here) 72 | 73 | })); 74 | 75 | App.Views.Tabs = Backbone.View.extend( 76 | _.extend({}, App.Mixins.Navigation, { 77 | 78 | // (Methods and attributes here) 79 | 80 | })); 81 | ``` 82 | 83 | ## Result 84 | 85 | The prototypes for your views now both have the methods defined in your mixin. 86 | New `App.Views.Tabs` and `App.Views.Menu` instances will now be able to respond 87 | to `.toggle()`, `.open()` and `.close()`. 88 | 89 | ``` javascript 90 | var tabs = new App.Views.Tabs; 91 | 92 | // These will call the methods you've defined 93 | // in App.Mixins.Navigation. 94 | tabs.toggle(); 95 | tabs.open(); 96 | tabs.close(); 97 | ``` 98 | 99 | ## Models and routers 100 | 101 | You can also use mixins in Models and Routers as well. 102 | 103 | ``` javascript 104 | // Router 105 | App.PageRouter = Backbone.Router.extend( 106 | _.extend({}, App.Mixins.HasSettings, { 107 | 108 | // (Methods and attributes here) 109 | 110 | })); 111 | 112 | // Model 113 | App.Widget = Backbone.Model.extend( 114 | _.extend({}, App.Mixins.IsDeletable, { 115 | 116 | // (Methods and attributes here) 117 | 118 | })); 119 | ``` 120 | 121 | --------------------------------------------------------------------------------