Learn to write modular Javascript applicatons using Backbone
Marionette and RequireJS/AMD Jack Killilea This book is for sale at http://leanpub.com/marionetteexpose This version was published on 2013-12-02 This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do. 2013 Jack Killilea Contents 1 Marionette Expose Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 What is Twitch.tv ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 TwitchTVExpose - My Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 Why Did You Do It, Jack? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.4 Working with Coffeescript! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2 Application Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.1 Modular Message Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.2 Observer Pattern (pub/sub) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.3 Request/Response Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.4 Command Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.5 Decoupled Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 3 My Hybrid Rails/AMD Asset Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.1 Index.html . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.2 Main.Coffee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 4 Starting the Marionette Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 4.1 Apps/load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 4.2 App.coffee - The Big Guy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 5 Modular Apps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 5.1 The About App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 5.2 The Header App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.3 The Footer App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 5.4 An Encounter With D3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.5 The Games App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5.6 The Streams App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 5.7 The Player App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 6 Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6.1 Header.entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6.2 OSS.entities (OpenSource Software) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.3 Appstate.entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.4 User.entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.5 Reference.entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 6.6 Author.entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 CONTENTS 6.7 TwitchTV.entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 7 Build and Deploy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.1 R.js the RequireJS Optimizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2 Shell script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.3 Deploying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.4 Cloud9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.5 Heroku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.6 Express Static Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.7 Using Cloud 9 With Heroku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.8 Open Source Dependency Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.9 TwitchTV API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 7.10 Working with Github . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 8 Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 8.1 Shoutout To ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 8.2 What I Have Learned Here (And Hopefully What You Learned!) . . . . . . . . . . . . . . . . 68 8.3 Outro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 1 Marionette Expose Preface This book is about my open source journey. It was a trip. Creating a modern Javascript application these days is a wild ride through the world of open source software. For many developers, open source software can be an intimidating, yet very powerful. My plan is to take you on this journey and along the way youll learn to embrace the ease and power of open source. In this book Ill be breaking down the creation of a Backbone.Marionette Application using the RequireJS module loading system. In doing so Ill be drawing upon several open source projects. There are so many ways to structure a JS application. With a multitude of frameworks, and libraries to choose from where do you begin? I have come up with my own approach that can be accurately described as a hybrid of the Rails Asset pipeline and the RequireJS module loading system. Ill be using a convention for saving my assets, much like the Rails Asset Pipeline but Ill also be using Asynchronous Module Definition (AMD) and a modular message bus to help create a highly decoupled architecture. First, a little bit about me which led to me creating the app. I play PC games alot. I held rank one in 5v5 in World of Warcraft a while back when I played. I also play Starcraft at relatively high level of play (masters protoss), and play a lot of Dota 2. Basically Im a nerd. Online gameplay can be live streamed and watched by thousands of people all around the world. Sites like Twitch.tv are popular and provide a service where users can monitor their favorite games live steams. Users can both publish and consume live streaming content. After having spent so much time on the TwitchTV site Id become accustomed to certain shortcomings in the user experience. Lots of full page post backs when selecting games and live streams waiting for a stream player and chat views to load etc. So here was an idea for a project near and dear to me. Could I improve upon the TwitchTV user experience? I did a bit of research and learned that TwitchTV has an open source repository hosted on Github. TwitchTV also support a REST API that is well documented. Could I use the TwitchTV API to create a Single Page App and improve the user experience? Should be quite interesting. Let the journey begin. 1.1 What is Twitch.tv ? Twitch.tv is the worlds leading video platform and community for gamers with more than 44 million visitors per month. They aim to connect gamers around the world by allowing them to broadcast, watch, and chat from everywhere they play. From what I saw, the UI was a bit lackluster. Clicking a game would bring you to another page where it would list out a bunch of streams, then another click would bring you to another page I found the process very tiresome as I would click around and go stream to stream. As an avid user, I saw that this could be eliminated by creating a single-page app around it. What better way to go about this than using Backbone.Marionette. Marionette Expose Preface 2 1.2 TwitchTVExpose - My Application TwitchTVExpose is a Single Page Client App implementing TwitchTVs API functionality. Using Back- bone.Marionette, RequireJS, Coffeescript, and a little D3 data-visualization to mix things up. Check out the live site : TwitchTVExpose Twitch Expose Briefly, the main Games App presents a list of top streaming games by viewers. By selecting a game from the list view, the UI transitions into a game:detail:view. The detail view shows a single game with a list of live streams for that game. Clicking on one of live steams transitions to another layout with a player view and a chat view. Marionette Expose Preface 3 1.3 Why Did You Do It, Jack? Over the summer I did a lot of research and came to the conclusion that I could rework Twitchs UI. I felt confident enough. From there, I decided it would be a perfect first project to take what TwitchTV had, and do it better. Id watched several of Brian Manns BackboneRails tutorials and determined that Backbone.Marionette would be the framework Id be using to build a responsive front-end app. I got to work all summer and this project was the outcome. When I land at Twitch.tv, I just want to find my favorite games with their live streams and watch it quickly. This app takes twitchtv, and makes the selection and viewing process faster and a better experience for the user. No hassles with page-postbacks and such. All client-side JS, in a single-page app. 1.4 Working with Coffeescript! Being the hipster nerd I am, Ive moved on from writing JS. I could go on and on about how great it is, but I dont want to be bashed. My thoughts are: If you want to read code in a book. reading Coffeescript is far easier. [Coffeescript][http://coffeescript.org/] is pretty gangster. It makes writing JS faster, in my opinion, and thus you will be reading all my source code in Coffeescript, so sadly, youll have to deal with it. For you nerds that are looking forward to learning a thing or two about it, great thats awesome. For those of you distasteful few that just want to read JS, have no fear. I took the liberty of linking you to my github repo for my project that contains the javascript source as well. [Read The Raw JS][https://github.com/xjackk/TwitchTVExpose] Go crazy. Lets look at the applications Architecture in detail as this is a key point to be understood. 2 Application Architecture 2.1 Modular Message Patterns Three main message patterns are used in my application architecture. For a more detailed discussion about javascript patterns in general, I highly recommend Essential Javascript Design Patterns 2.2 Observer Pattern (pub/sub) This pattern allows us to Trigger Events (Publish), and Listen to them (Subscribe). .trigger = Trigger Event .on = Subscribe These are specifically used for handling events. 2.3 Request/Response Pattern The Request/Response Pattern is like the Pub/Sub, yet it works with objects. .request = Requesting object .addHandler = Returning for that object The Request will request for an object, while the addHandler will return that object. These are used for specifically handling objects.. or in this case, our entities. 2.4 Command Pattern The Commands pattern is how we will be executing actions and facilitating the handling for said action. .execute = Executing an action .addHandler = Facilitating our handling for that action These are used for command handling. Commands are for Fire and forget it A command is issued and there is no waiting around for a response. Application Architecture 5 2.5 Decoupled Modules This msgbus module is where I implement these messaging patterns and is widely used throughout the application. From the MarionetteJS docs: backbone.wreqr - A simple infrastructure for decoupling Backbone and Backbone.Marionette application modules and components. Says it all right there. The msgBus module simply exposes the request/response, command and events objects frombackbone.wreqr as shown here: #msgbus decoupled from app define ["backbone.wreqr"], (Wreqr) -> reqres: new Wreqr.RequestResponse() commands: new Wreqr.Commands() events: new Wreqr.EventAggregator() Although Marionette already bakes these patterns into the Marionette.Application namespace, to use it effectively in an AMD context would require the app instance to be passed to every module/app and sub- app that needs these messaging patterns. Ulitmately this leads to circular references that cannot be resolved by RequireJS. When using AMD style modules we are in a sense throwing away javascript namespacing. Remember, each module is a self contained package. The msgbus module allows us to namespace our event/messages as they are passed around our modular architecture. Namespaced events look like this: "start:header:app" As a developer we can use namespaced events to provide better context for understanding your intent. A quick example of this is can be seen in the header application. Here is the app.coffee file for the header application. Notice the namespacing here. "start:header:app", and also the header:region # header app/module. define ["msgbus","apps/header/list/controller"], (msgBus, Controller) -> API = list: -> new Controller region: msgBus.reqres.request "header:region" #start up msgBus.commands.setHandler "start:header:app", -> API.list() The app responds to the start:header:app command by calling the API.list() function. Lets bring in the header controller as well so I can demonstate the Request/Responce message pattern in action. Application Architecture 6 # header list controller define ["msgbus","apps/header/list/views", "controller/_base", "entities/header"], (msgBus\ , Views, AppController) -> class Controller extends AppController initialize: -> links = msgBus.reqres.request "header:entities" @appstate = msgBus.reqres.request "get:current:appstate" #console.log @appstate @layout = @getLayoutView() # new appstate is now a property of the controller have the controller listen \ to the specific attribute # so from anywhere you can set the appstate's loginStatus to T/F and this butt\ on will toggle @listenTo @appstate, "change:loginStatus", (model, status) => @loginView.close() if status is true @loginView.render() if status is false @listenTo @layout, "show", => @listRegion links @loginView = @getLoginView @appstate @loginView.render() #stick-it into the DOM @show @layout getHeaderView:(links)-> new Views.HeaderView collection: links getLoginView: (model) -> new Views.LoginView model: model getLayoutView: -> new Views.Layout listRegion: (links) -> view = @getHeaderView links @layout.listRegion.show view Notice in the initialize function we have: Application Architecture 7 initialize: -> links = msgBus.reqres.request "header:entities" @appstate = msgBus.reqres.request "get:current:appstate" #console.log @appstate @layout = @getLayoutView() As you can see, we are making a request for our "header:entities". Below that we are also making another request for our "get:current:appstate. Also take note of the controllers dependencies: define ["msgbus","apps/header/list/views", "controller/_base", "entities/header"], (msgBus\ , Views, AppController) -> In our setup for the controller, we are requiring the entities/header module to be loaded first. This decoupled code right here provides a way to dynamically pull in these entities into the header app. js/entities/header.coffee A quick example of our namespacing below with our header:entites. As a programmer, I chose the name header:entities because it helps me understand its context better. This isnt something new really, just thought id point it out. Below is the source for the entities/header module : define ["backbone","msgbus"], (Backbone, msgBus ) -> API = getHeaders:-> new Backbone.Collection [ (name: "Games", url: "#games", title: "Live Games", cssClass: "glyphicon glyph\ icon-hdd" ) (name: "D3", url: "#d3", title: "Sample D3 visualization", cssClass: "glyphico\ n glyphicon-list") (name: "About", url: "#about", title: "Learn about responsive Twitch-TV", cssC\ lass: "glyphicon glyphicon-align-justify") ] msgBus.reqres.setHandler "header:entities", -> API.getHeaders() Notice how the header entities API uses the msgBus module to listen for a header:entities request and responds with a static Backbone.Collection. Nothing too crazy here. This pattern will be used literally throughout this app. This is essentially what makes this app work. After checking out the architecture, now I feel were ready to take on some code and starting of the app. Lets take a look at our asset pipeline. 3 My Hybrid Rails/AMD Asset Pipeline Lets take a good look at this hybrid rails/AMD asset line. Configuration All templates use underscore configured for Mustache. I follow an approach similar to Brian Manns BackboneRails tutorials except he is using Rails and Marionette modules and I am using RequireJS/AMD modules with a Rails convention. Sort of a Rails/AMD hybrid that works very well for me. Take a look at the About app above and notice how the files are organized: apps/about/app.coffee (the application) apps/about/show/controller.coffee (the show controller) apps/about/show/templates/*.htm (various templates) apps/about/show/views.coffee (modular views) apps/about/show/templates.coffee (modular templates) My Hybrid Rails/AMD Asset Pipeline 9 This convention is followed for all apps in this project You will notice that this process is a bit mundane, but none the less, it is very productive. The object here to to not be writing as much code as you normally would. From here on out, Im just going to be throwing a lot of code samples at you as I feel its the best way for you nerds to pickup this process. Its easy, youll get it. 3.1 Index.html Lets take a look at our Index.html page. We have a head section, a body section, and lastly our script loader. Note in our body section we break it up into three parts, the header-region, main-region, and the footer-region. This is my simantical representation of creating a single-page application layout. Notice the script data-main block, where we are pointing to js/main for our require.js loader. Essentially, this is the starting point or boot-up where our entire Javascript / dependencies get loaded. <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>Twitch-TV Expose</title> <meta name="description" content="Single Page Application Backbone Marionette RequireJ\ S"> <meta name="author" content="Jack Killilea"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> <!--[if lt IE 9]> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" me\ dia=screen> <link href="css/main.css" rel="stylesheet" media=screen> </head> <body> <div id="wrap"> <div id="header-region"></div> <div id="main-region" class="container"></div> </div> <div id="footer-region"></div> <script data-main="js/main" src="bower_components/requirejs/require.js" type="text/jav\ ascript"></script> </body> </html> My Hybrid Rails/AMD Asset Pipeline 10 3.2 Main.Coffee Our main.coffee file is very important as it loads all of our dependencies as shown here. In Section 5 we will be talking about building, maintaining your Open Source Dependencies with Bower, as well as deploying your app to Heroku. On to the code. #require bootloader require.config paths: # note these are all AMD compliant versions jquery: "../bower_components/jquery/jquery" # amd version underscore: "../bower_components/underscore-amd/underscore" # amd version backbone: "../bower_components/backbone-amd/backbone" # amd version "backbone.babysitter": "../bower_components/backbone.babysitter/lib/amd/backbone.b\ abysitter" # amd version "backbone.wreqr": "../bower_components/backbone.wreqr/lib/amd/backbone.wreqr" # am\ d version "backbone.syphon": "../bower_components/backbone.syphon/lib/amd/backbone.syphon" #\ amd version marionette: "../bower_components/marionette/lib/core/amd/backbone.marionette" # am\ d version moment: "../bower_components/moment/moment" globalize: "../bower_components/globalize/lib/globalize" text: "../bower_components/requirejs-text/text" d3: "../bower_components/d3/d3", swf: "../bower_components/swfobject-amd/swfobject", spin: "../bower_components/spin.js/spin" jqueryspin: "../bower_components/spin.js/jquery.spin" #mockjax: "../bower_components/jquery-mockjax/jquery.mockjax" #mockjson: "../bower_components/mockJSON/js/jquery.mockjson" #holderjs:"../bower_components/holderjs/holder" bootstrap:"../bower_components/bootstrap/dist/js/bootstrap" shim: #mockjax: ["jquery"] #mockjson: ["jquery"] bootstrap: ["jquery"] # ensure that base application settings are loaded before we can call the app. # templates, settings and jquery plugins etc require ["config/load", "app" ], (_config, app) -> app.start() My Hybrid Rails/AMD Asset Pipeline 11 Take note of the last line here : require ["config/load, "app" ], (_config, app) -> Before the app.start() function, we need to ensure that our config/load and app dependencies are required. This defines our order of dependencies that must be loaded before we can start our main app. Now, some more loading. Dependencies Before the js/app.coffee can be started, number of modules must be loaded first. RequireJS helps in this regard however the explicit dependencies must be established and the loading process managed. Unfortunately this is something that is not automagically done and must be handled with a little common sense. Before this modular app can load a number of Configuraion, Enity and App modules will need to be pre-loaded before the Marionette.application can start. Asset Pipeline js/main.coffee >> defines app dependencies config/load.coffee >> load app configuration and entities js/apps/load.coffee >> loads apps and sub-apps prior to the main app This is my hybrid approach to AMD/Rails esque asset pipeline. Lets check out the config/load . Config/load In the config folder I have a module named load config/load. This is where a number of libraries and configuration settings are explicitly loaded. This stuff is typically external to the app so its pre-loaded prior to our app. Here were configuring global settings for marionette, underscore and jquery plugins. This is how I do it with using AMD and I have explicit control over the order that modules are loaded. These configuration issues can get complicated but if you remember that everything traces back to the main.js file, it becomes easy to sort out the order and precedence of how and when modules are loaded. Also we will dynamically be loading our header and footer apps through this process. config\load My Hybrid Rails/AMD Asset Pipeline 12 #load base config before app define [ "config/jquery/jquery" "config/underscore/templatesettings" "config/marionette/templatecache" "config/marionette/application" "config/backbone/sync" "entities/_fetch" "entities/abstract/buttons" "entities/twitchtv" "entities/appstate" "entities/author" "entities/reference" "entities/oss" "components/form/controller" "components/loading/controller" "bootstrap" "globalize" ], -> Now that were done loading all of this configuration dependency cool-stuff, we can move on to actually starting the application. 4 Starting the Marionette Application So now that you have your dependencies all in order, we can finally start the app. 4.1 Apps/load Lets just take a quick look at our apps/load dependency. # preload all apps here define [ "apps/header/app", "apps/footer/app", "apps/games/app", "apps/d3/app", "apps/about/app", "apps/streams/app", "apps/playa/app" ], -> This is our explicit asset pipeline. This module is required to instantiate all of the modular apps along with their respective routing. Neat-o. 4.2 App.coffee - The Big Guy Before diving into each seperate app, Ill introduce the initial app for this project. This is where the Marionette Application is created. It takes control of the sub-app startup process. js/app.coffee In this big guy here, we add some planning for the rest of our project. We add our primary regions of the single page app, as well as the default routing. Before the app is even loading, notice that we are using a define statment for our dependencies order. This being a Marionette app, we need backbone as an obvious dependency. We also have our own msgBus, which I will be expanding upon below. Starting the Marionette Application 14 define ["backbone", "marionette", "msgbus", "apps/load" ], (Backbone, Marionette, msgBus )\ -> app = new Marionette.Application() app.rootRoute = "about" app.authRoute = "games" app.addRegions headerRegion : "#header-region" mainRegion : "#main-region" footerRegion : "#footer-region" app.on "initialize:before", (options={}) -> #console.log "init:before", options msgBus.reqres.setHandler "default:region",-> app.mainRegion msgBus.reqres.setHandler "header:region", -> app.headerRegion msgBus.reqres.setHandler "footer:region", -> app.footerRegion msgBus.reqres.setHandler "main:region", -> app.mainRegion msgBus.commands.setHandler "register:instance", (instance, id) -> app.register instance, id msgBus.commands.setHandler "unregister:instance", (instance, id) -> app.unregister instance, id app.on "initialize:after", (options={})-> appstate = msgBus.reqres.request "get:current:appstate" # trigger a specific event when the loginStatus ever changes (to be handled by our\ header list controller to show/hide login UI # appstate.on "change:loginStatus" (model, status)-> # msgBus.events.trigger "login:status:change", status if Backbone.history Backbone.history.start() frag = Backbone.history.fragment match = /access_token/i.test frag if match Starting the Marionette Application 15 appstate.set "accessToken", frag.split("=")[1] appstate.set "loginStatus", true #console.log "top route", @authRoute @navigate(@authRoute, trigger: true) else appstate.set "loginStatus", false #console.log appstate.get("loginStatus"), "value of login status" @navigate(@rootRoute, trigger: true) if @getCurrentRoute() is null app.addInitializer (options) -> #console.log "addinitializers" msgBus.commands.execute "start:header:app" msgBus.commands.execute "start:footer:app" msgBus.commands.execute "start:d3:app" msgBus.commands.execute "start:about:app" msgBus.commands.execute "start:games:app" msgBus.commands.execute "start:playa:app" app I think were done here, so now its time to check out our Apps. 5 Modular Apps The way to build large Javascript applications is not to build large javascript applications - Brian Mann, BackboneRails.com 5.1 The About App Alright nerds, Im going to breakdown each of the modular apps individually in detail, beginning with the About app. The About app has been configured as the default route when the user navigates to the websites application root. Recall from the main app.coffee file : # app startup. define ["backbone", "marionette", "msgbus", "apps/load" ], (Backbone, Marionette, msgBus )\ -> app = new Marionette.Application() app.rootRoute = "about" app.authRoute = "games" Ive defined a property called rootRoute on the app object and set it to about Later on during the app initialization processing I do the following : if Backbone.history Backbone.history.start() frag = Backbone.history.fragment match = /access_token/i.test frag if match appstate.set "accessToken", frag.split("=")[1] appstate.set "loginStatus", true #console.log "top route", @authRoute @navigate(@authRoute, trigger: true) else appstate.set "loginStatus", false #console.log appstate.get("loginStatus"), "value of login status" @navigate(@rootRoute, trigger: true) if @getCurrentRoute() is null Whats going on here? This code snippet has to do with the Twitch authentication. If the user logs in with an authenticated twitch credential the twitch site redirects back to my app and the authentication token included in the url fragment. Authenticated users with be redirected to the #games route and unauthenticated users are routed to the #about route. Modular Apps 17 Configuration The about apps responsibility it to display the About Page. Like most about pages mine displays general information about the application, however I decided to add a little extra to show off functionality of a Marionette Layout View with multiple regions. I display a static list of references (books/videos) that were used when putting this project together. I also wanted to present a list of all the open source projects I used building this application. Youll notice that the layout view contains three regions each containing a composite view. So the layout for the about app contains a few regions and each region contains their own views. These are the primary Marionette building blocks that youll be using again and again for rendering our templated user interfaces. It may seem complex at first but hopefully you will become more familiar with these building blocks as this design pattern will be repeated in each modular app. Responsive Web Design Oh yeah, our templates our using Bootstrap classes and support a responsive web design that renders well on a desktop, tablet or smartphone. Real G stuff. Modular Apps 18 Configuration Modular Apps 19 Lets start by looking at the app.coffee file : js/apps/about/app.coffee The About app requires our msgbus, Marionette and the show controller. Notice how these required modules are in the define function below : This file acts as the starting point of the about app; since this app is routeable we are setting up the #about route handler and we define the modules API and new up an apps/about/show/controller. The app also sets up the command handler that listens for the namespaced message: "start:about:app". Once the "start:about:app" command is detected the About/app instantiates a new Router with our controllers API. Very modular! Some more Coffeescript coming in hot. App define ["msgbus", "marionette", "apps/about/show/controller"], (msgBus, Marionette, Contro\ ller) -> class Router extends Marionette.AppRouter appRoutes: "about": "about" API = about: -> new Controller msgBus.commands.setHandler "start:about:app", -> new Router controller: API So whats going on with the apps/about/show/controller you might ask? Lets dig a little deeper and check out the controller module, shall we? About Show Controller js/apps/about/show/controller.coffee The about Controller sets up sets up the three regions we will be adding data too. About Region Books Region OSS Region Lets take a look at our Show Controller. Modular Apps 20 Controller define ["msgbus", "apps/about/show/views", "controller/_base"], (msgBus, Views, AppControl\ ler) -> class Controller extends AppController initialize:(options)-> entities=msgBus.reqres.request "reference:entities" ossentities=msgBus.reqres.request "oss:entities" #console.log ossentities @layout = @getLayoutView() @listenTo @layout, "show", => @aboutRegion() @bookRegion entities @ossRegion ossentities @show @layout, loading: entities: entities aboutRegion: -> view = @getAboutView() @layout.aboutRegion.show view bookRegion: (collection) -> view = @getBookView collection @layout.bookRegion.show view ossRegion: (collection) -> view = @getOssView collection @layout.ossRegion.show view getOssView: (collection) -> new Views.Oss collection: collection getBookView: (collection) -> new Views.Books collection: collection getAboutView: -> new Views.About getLayoutView: -> Modular Apps 21 new Views.Layout Views Here we have our views, which typically will always follow our controller. define ['apps/about/show/templates', 'views/_base', 'd3'], (Templates, AppView) -> class Book extends AppView.ItemView template: _.template(Templates.bookitem) tagName: "tr" class Oss extends AppView.ItemView template: _.template(Templates.ossitem) tagName: "tr" Books: class Books extends AppView.CompositeView template: _.template(Templates.books) itemView: Book itemViewContainer: "tbody" Oss: class Osslist extends AppView.CompositeView template: _.template(Templates.oss) itemView: Oss itemViewContainer: "tbody" About: class _item extends AppView.ItemView template: _.template(Templates.about) Layout: class DataVisLayout extends AppView.Layout template: _.template(Templates.layout) regions: aboutRegion: "#about-region" bookRegion: "#book-region" ossRegion: "#oss-region" Templates Next we have our template loaders, which are just done in a define statement. Modular Apps 22 define (require) -> about: require("text!apps/about/show/templates/about.htm") layout: require("text!apps/about/show/templates/layout.htm") books: require("text!apps/about/show/templates/books.htm") bookitem: require("text!apps/about/show/templates/bookitem.htm") oss: require("text!apps/about/show/templates/oss.htm") ossitem: require("text!apps/about/show/templates/ossitem.htm") View Markup Below Im just listing out the code for all of our html snippets. This is important to see what were doing overall. Im not going to bother describing Layout.htm <div class="row"> <div id=about-region class="col-md-12"></div> </div> <div class=row> <div id=book-region class="col-md-6"></div> <div id=oss-region class="col-md-6"></div> </div> <div class=row> </div> About.htm <div class="jumbotron"> <div class="container"> <h2>Exposing TwitchTV to a Responsive Client UI</h2> <p>Use the Twitch-TV Login to get started ! </p> <p class="text-primary"><a href="https://github.com/xjackk/TwitchTVExpose" target="_bl\ ank" class="btn btn-link" title="see the open-source project on github">Learn more</a></p> </div> </div> Books.htm Modular Apps 23 <div class="panel panel-warning"> <div class="panel-heading">Project Reference List</div> <div class="panel-body"> <p>These are some of the books and videos that provided knowledge and inspiration for \ the ideas behind this project</p> </div> <div class="table-responsive"> <table class="table"> <thead> <tr> <th>Title</th> <th>Author</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> Bookitem.htm <td><span class="glyphicon {{type=="book" ? "glyphicon-book": "glyphicon-hd-video" }}"></s\ pan> <a href="http://{{url}}" target="_blank" >{{title}}</a></td> <td>{{author}}</td> ### Oss.htm <div class="panel panel-info"> <div class="panel-heading">Open Source Software</div> <div class="panel-body"> <p>These are the major Open-Source Software projects I implemented into <strong>TwitchTV Expose</strong></p> </div> <div class="table-responsive"> <table class="table"> <thead> <tr><th>Name</th><th>Websiteurl</th></tr> </thead> <tbody></tbody> </table> </div> </div> ### Ossitem.htm <td>{{name}}</td> <td><a href="{{site}}" target="_blank">{{name}}</a></td> The {{ }} that you see is just mustache templating. That pretty much does it for the About App, so lets move on to the header app, which is less interesting than the About app from a UI perspective, however it still has an important role to play. Modular Apps 24 5.2 The Header App The header app starts with its corresponding app file, which creates out list controller, and starts the app. App file Once we have this, we can move on to our app itself. We begin to layout everything using Bootstrap in our header. Header.htm We only have one region for this header, and we lay it out, like so. Layout.htm Lastly, we need a login button, to authorize ourselves with Twitch TVs API. Twitch.TV Login After we layout all of our html, we can move onto working with our controllers, templates, and views. App # header app/module. define ["msgbus","apps/header/list/controller"], (msgBus, Controller) -> API = list: -> new Controller region: msgBus.reqres.request "header:region" #start up msgBus.commands.setHandler "start:header:app", -> API.list() Controller # header list controller define ["msgbus","apps/header/list/views", "controller/_base", "entities/header"], (msgBus\ , Views, AppController) -> class Controller extends AppController initialize: -> links = msgBus.reqres.request "header:entities" @appstate = msgBus.reqres.request "get:current:appstate" #console.log @appstate @layout = @getLayoutView() # new appstate is now a property of the controller have the controller listen \ to the specific attribute # so from anywhere you can set the appstate's loginStatus to T/F and this butt\ on will toggle Modular Apps 25 @listenTo @appstate, "change:loginStatus", (model, status) => @loginView.close() if status is true @loginView.render() if status is false @listenTo @layout, "show", => @listRegion links @loginView = @getLoginView @appstate @loginView.render() #stick-it into the DOM @show @layout getHeaderView:(links)-> new Views.HeaderView collection: links getLoginView: (model) -> new Views.LoginView model: model getLayoutView: -> new Views.Layout listRegion: (links) -> view = @getHeaderView links @layout.listRegion.show view #loginRegion: () -> # view = @getLoginView @appstate # @layout.loginRegion.show view # this method would require a public API # msgBus.events.on "login:status:change", status => # if status is true # @layout.loginRegion.close() # else # @layout.loginRegion.show() Views Modular Apps 26 # list header views define ['apps/header/list/templates', 'views/_base'], (Templates, AppView) -> class _itemview extends AppView.ItemView template: _.template(Templates.item) tagName: "li" LoginView: class Loginview extends AppView.ItemView template: _.template(Templates.login) el: "#login" HeaderView: class ListHeaders extends AppView.CompositeView template: _.template(Templates.header) itemView: _itemview itemViewContainer: "ul" Layout: class Header extends AppView.Layout template: _.template(Templates.layout) regions: listRegion: "#list-region" Templates define (require) -> item: require("text!apps/header/list/templates/itemview.htm") header: require("text!apps/header/list/templates/header.htm") login: require("text!apps/header/list/templates/login.htm") layout: require("text!apps/header/list/templates/layout.htm") View Markup Markup below, nerds. Header.htm Modular Apps 27 <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navba\ r-ex1-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#"> <span class="brand"><abbr title="A 'single-page appl\ ication'">Twitch-TV Expose</abbr></span></a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse navbar-ex1-collapse pull-right"> <ul class="nav navbar-nav"> </ul> <p id=login class="navbar-text pull-right"></p> </div><!-- /.navbar-collapse --> </nav> Itemview.htm <a href={{url}} title="{{title}}"><span class="{{cssClass}}"></span> {{name}}</a> Layout.htm <div id="list-region"></div> Login.htm <a title = "Sign-in to your Twitch-TV account" class="navbar-link" href="https://api.twitc\ h.tv/kraken/oauth2/authorize?response_type=token&client_id={{clientId}}&redirect_uri=http:\ //twitchtvexpose.herokuapp.com&scope=user_read channel_stream chat_login channel_read">Twi\ tchTV-Login</a> And just like that, were done here. The Footer App is basically the same thing. Maybe a tad different. Lets take a look. Modular Apps 28 5.3 The Footer App The footer app is virtually the same thing as the header app. We are going for that fixed footer look. You should start to see some patterns here. app # footer app/module. define ["msgbus","apps/footer/show/controller"], (msgBus, Controller) -> API = show: -> new Controller region: msgBus.reqres.request "footer:region" msgBus.commands.setHandler "start:footer:app", -> API.show() controller # footer_app controller define ["msgbus","apps/footer/show/views", "controller/_base"], (msgBus, View, AppControll\ er) -> class Controller extends AppController initialize:-> author = msgBus.reqres.request "get:authorModel:info" #console.log author footerView = @getFooterView author @show footerView getFooterView: (model) -> new View.ItemView model: model views Modular Apps 29 # show footer views. define ['views/_base', 'apps/footer/show/templates'], (AppViews, Templates) -> ItemView: class ShowFooterView extends AppViews.ItemView template: _.template(Templates.footer) modelEvents: "change" : "render" templates define (require) -> footer: require("text!apps/footer/show/templates/footer.htm") view markup More markup for you nerds. I mean, its only one file. Go crazy. footer.htm <nav class="navbar navbar-default navbar-fixed-bottom hidden-xs" role="navigation"> <p class="navbar-text pull-right"><a href="{{twitter}}" target="_blank" class="navbar-link\ ">{{fullName}} <span class="glyphicon glyphicon-leaf"></span></a></p> <p class="navbar-text pull-right"><a href="{{github}}" target="_blank" class="navbar-link"\ >Github</a></p> </nav> And, now were done here. Lets checkout some more interesting stuff though. Our Games app will feature some cooler functionality. 5.4 An Encounter With D3 Before we get into the app, I want to talk about D3 visualization for a little bit, because it became an important part of my project. I wanted a cool way to go about showing off data, and stumbled across D3. D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG and CSS, all in JS, obviously. D3s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation. Modular Apps 30 Now, this may seem a bit intimidating, but after doing some research of the documentation this was nothing that I couldnt handle. You should check out the documentation for this at [d3][http://d3js.org/]. Mike Bostock has put some quality work into this and it shouldnt go unnoticed. SVG and the Canvas with JS its too powerful. D3 Here we have a cute picture of my D3 animation for games. The size of the bubble is depending on how many viewers that the current game has. You can also see a tooltip on a mouseover event that goes on, showing the picture of the game as well. Somewhat attractive. The amount of bubbles depend on the amount of games that occur in the queue of endless scrolling games. Play around with it you can get quite a lot of them to show up on the screen. Careful though, as it is a bit processor intensive. We dont want you frying your integrated graphics now, do we? (Lets hope you bought a nice video card and processor). Here is the Coffeescript source for this. [Coffee][https://github.com/xjackk/TwitchTVExpose/blob/master/js/views/bubble.coffee] Likewise, the JS source as well. [Javascript][https://github.com/xjackk/TwitchTVExpose/blob/master/js/views/bubble.js] Data visualization is very sought after, and Im not going to go into the utmost detail about it, but I do feel that it is noteworthy enough to mention at least. If you are interested in learning more, I suggest you check out some documentation on D3, as well as checking out other books. I could not teach enough about it. With that being said, I hope you found that interesting. Now we can move on to our Games app. Modular Apps 31 5.5 The Games App The games app is the the most important app in this project. It is responsible for routing msgbus two controllers : list detail This is where we will be showing our top/games call from. Cool API work. From there, we can pick everything thing else out we want from Twitchs API. We stuff all the games into a composite view. Here is the start of our app, where we create controllers. A bit different this time around. Check it out. app define ["msgbus", "marionette", "backbone", "apps/games/list/controller","apps/games/detai\ l/controller"], (msgBus, Marionette, Backbone, ListController, DetailController) -> class Router extends Marionette.AppRouter appRoutes: "games": "list" "games/:id/detail": "detail" API = list: -> new ListController detail: (id, model) -> new DetailController gameName: id gameModel: model msgBus.events.on "app:game:detail", (model) -> Backbone.history.navigate "games/#{model.get("game").name}/detail", trigger:false API.detail model.get("game").name, model msgBus.commands.setHandler "start:games:app", -> new Router controller: API Here we create our list controller, as well as our detail controller. This is because we essentially want to create two different views of this. We have some fancy backbone.history() code going on, but this is specifically for our apps client-side routing. Modular Apps 32 One view is going to be where the games are all shown, and the other where one selected game is shown, thus, the detail controller. list controller This controller is all about working directly with our Twitch API. We set entities as our msgBus request to get the "games:top:entities" We define our gameRegion and pass through our collection. You can see were listening to a childview:game:item:clicked. This is listening to events from our itemview. This will trigger our app:game:detail event. We also have our next @listenTo which listens to the view for a scroll:more. This launches our games:fetchmore which works as an endless scrolling feature. Lastly in this controller, we have our getGameView function which passes a collection into a new view. Good stuff. define ["msgbus", "apps/games/list/views", "controller/_base", "backbone" ], (msgBus, View\ s, AppController, Backbone) -> class Controller extends AppController initialize: (options={})-> @entities=msgBus.reqres.request "games:top:entities" @layout = @getLayoutView() @listenTo @layout, "show", => @gameRegion() # @entities @listenTo @layout, "show:bubble", => @gameBubbleRegion() # @entities @show @layout, loading: entities: @entities gameRegion: -> view = @getGameView @entities @listenTo view, "childview:game:item:clicked", (child, args) -> # listen to e\ vents from itemview (we've overridden the eventnamePrefix to childview) #console.log "game:item:clicked => model", args.model msgBus.events.trigger "app:game:detail", args.model @listenTo view, "scroll:more", -> msgBus.reqres.request "games:fetchmore" @layout.gameRegion.show view gameBubbleRegion: -> Modular Apps 33 view = @getBubbleView @entities @layout.gameRegion.show view getBubbleView: (collection) -> new Views.GamesBubbleView collection: collection getGameView: (collection) -> new Views.TopGameList collection: collection getLayoutView: -> new Views.Layout detail controller You can see right from the define statement, this controller is going to be working directly with our games/detail/view. In our initialize function we are pasing through gameName, and gameModel as options. We then have our if statement, which says if our gameModel is undefined, then its going to make a msgBus.reqres.request for games:searchName which passes through a gameName. Next, we are getting our layoutview, and listening to our show event. Nothing new here. Our @gameRegion is passing through our gameModel Finally we have our functions for this controller. gameRegion passes through the model and sets its view for that function the getGameView. To get our game name here, we make a msgBus.commands.execute for the app:stream:list. getGameView passes through a model as well and makes a new detail view. define ["msgbus", "apps/games/detail/views", "controller/_base", "backbone" ], (msgBus, Vi\ ews, AppController, Backbone) -> class Controller extends AppController initialize: (options) -> {gameName, gameModel} = options #console.log "OPTIONS passed to detail controller", options if gameModel is undefined gameModel = msgBus.reqres.request "games:searchName", gameName #console.log "GameModel", gameModel @layout = @getLayoutView() @listenTo @layout, "show", => Modular Apps 34 @gameRegion gameModel @show @layout, loading: entities: gameModel gameRegion: (model) -> view = @getGameView model msgBus.commands.execute "app:stream:list", @layout.streamRegion, model.get("ga\ me").name @layout.gameRegion.show view getGameView: (model) -> new Views.Detail model: model getLayoutView: -> new Views.Layout list views Our first class GameItem is simply an ItemView with a familiar looking className. You should recognize this as bootstrapping. We need to give this GameItem a click event as well for more or less obvious reasons. Next we have our TopGameList class which is a CompositeView. This takes our GameItem class as its itemView, and gives it an id of gamelist. We need a container to put this it too, right? Of course. we give it an itemViewContainer of #gameitems. Before we end this class, we need an event for our endless scroll so we set scroll: as checkScroll. Here we have our checkScroll function which passes through one arg. Before we go into the code line by line, we need to understand how this works. VirtualHeight here is very important, as it works as the height of the actual page. Its almost imaginary in a way because we arent going to be seeing the whole height, as it is in a module smaller than the size of the whole page. this div must have the css height of 100% to enable proper calculation of the VirtualHeight. We then set scrollTop which is this els height plus the scrolltop. We set our margin to 200px. Lastly, we have our if statement which is saying if the scrollTop + margin is greater than our virtualHeight, it will trigger an event to scroll:more. You can kinda see where we are going with this. On a less important note, we have a Layout class that does this thing where it passes a template though to some regions. Neat. Modular Apps 35 define ['apps/games/list/templates', 'views/_base', 'msgbus', 'views/bubble'], (Templates,\ AppView, msgBus, BubbleChart) -> class GameItem extends AppView.ItemView template: _.template(Templates.gameitem) tagName: "li" className: "col-md-2 col-sm-4 col-xs-12 game" triggers: "click" : "game:item:clicked" TopGameList: class TopGameList extends AppView.CompositeView template: _.template(Templates.gameslist) itemView: GameItem id: "gamelist" itemViewContainer: "#gameitems" events: "scroll": "checkScroll" checkScroll: (e) => virtualHeight = @$("> div").height() #important this div must have css heig\ ht: 100% to enable calculattion of virtual height scroll scrollTop = @$el.scrollTop() + @$el.height() margin = 200 #console.log "virtualHeight:", virtualHeight, "scrollTop:", scrollTop, "elHeig\ ht", @$el.height() if ((scrollTop + margin) >= virtualHeight) @trigger "scroll:more" GamesBubbleView: class GamesBubbleView extends AppView.ItemView template: _.template(Templates.gamesbubble) id: "gamesbubble" onShow: -> $width = @$el.outerWidth(false) $height = Math.floor $width * 9 / 16 @chart = new BubbleChart @collection, @el, $width, $height @chart.start() @chart.display() Layout: class GamesLayout extends AppView.Layout template: _.template(Templates.layout) regions: gameRegion: "#game-region" Modular Apps 36 events: "click .bubble": "bubble" "click .grid": "grid" bubble:-> @trigger "show:bubble" grid:-> @trigger "show" detail views This class GameDetail is our Itemview. We are passing in our gamedetail template, and giving it a bootstrap className. Our class GamesLayout just gives us our gameRegion and streamRegion. define ['apps/games/detail/templates', 'views/_base', 'msgbus'], (Templates, AppView, msgB\ us) -> Detail: class GameDetail extends AppView.ItemView template: _.template(Templates.gamedetail) className: "col-xs-12" #triggers: # "click" : "game:item:clicked" Layout: class GamesLayout extends AppView.Layout template: _.template(Templates.layout) regions: gameRegion: "#game-region" streamRegion: "#stream-region" list templates # modular template loading define (require) -> gameitem: require("text!apps/games/list/templates/gameitem.htm") layout: require("text!apps/games/list/templates/layout.htm") gameslist: require("text!apps/games/list/templates/gameslist.htm") gamesbubble: require("text!apps/games/list/templates/gamesbubble.htm") detail templates Modular Apps 37 # modular template loading define (require) -> gamedetail: require("text!apps/games/detail/templates/gamedetail.htm") layout: require("text!apps/games/detail/templates/layout.htm") list view markup Here is our list view markup. Not to be confused with detail view markup, fool. gameitem.htm <div title = "{{game.name}}"> <img class="img-responsive img-thumbnail" src={{game.logo.large}} > </div> <p class="text-primary text-responsive"><strong>{{game.name}}</strong></p> <p class="text-muted text-responsive"><strong>{{ Globalize.format( viewers, "n0") }}</stro\ ng> viewers</p> gameslist.htm <div style="height:100%;"> <ul id="gameitems" class="list-inline"> </ul> </div> layout.htm <div class="row"> <div class="col-md-12 col-xs-12"> <div class="panel panel-primary"> <div class="panel-heading"> <p class="panel-title"> <strong>Top Games</strong><small> people are watching now</small> <span class=pull-right> <button class="btn btn-info btn-xs bubble" title="bubble chart"><span \ class="glyphicon glyphicon-dashboard"></span> bubble</button> <button class="btn btn-info btn-xs grid" title="grid view"><span class\ ="glyphicon glyphicon-th"></span> grid</button> </span> </p> </div> <div id="game-region" class="panel-body"></div> </div> </div> </div> Modular Apps 38 detail view markup Our detail view markup below. Check it. gamedetail.htm <div title = "{{game.name}}"> <img class="img-responsive img-thumbnail" src={{game.logo.large}} > </div> <p class="text-primary text-responsive"><strong>{{game.name}}</strong></p> <p class="text-muted text-responsive"><strong>{{ Globalize.format( viewers, "n0") }}</stro\ ng> viewers</p> layout.htm <div class="col-md-4 col-xs-12"> <div class="panel panel-primary "> <div class="panel-heading"> <p class="panel-title"><span class="clearfix lead">Detail<small> selected game\ </small></span></p> </div> <div id="game-region" class="panel-body"></div> </div> </div> <div class="col-md-8 col-xs-12"> <div class="panel panel-warning"> <div class="panel-heading"> <p class="panel-title"><span>Live Streaming</span></p> </div> <div class="panel-body"> <div id="stream-region" class="row"></div> </div> </div> </div> There was a lot of information that was covered in this app for sure. We will be going into more detail about our entities and msg in a later chapter, because I feel it has its place to be talked about. Doing it all at once would be a bit much. For now, take it for what it is. Now, after you select a game to watch, we are going to want to see the streams for it. Onto the streams app. Modular Apps 39 5.6 The Streams App After all has been said and done with our Games App, we can move on to our Streams section. This is not as beefy as the games section but still noteworthy. app define [ "msgbus", "apps/streams/list/controller" ], (msgBus, Controller) -> API = list:(region, name) -> new Controller region: region name: name msgBus.commands.setHandler "app:stream:list", (region, name) -> API.list region, name Here is our Streams App, where we start our list API, and pass in a region and name to the controller. Lastly we have our trusty msgBus comand, to the app:stream:list. Money. controller In our initalize function we are passing in our options. We slyly pass in name as a hash to options. Our streamEntities is just a msgBus.reqres.request to our search:stream:entities, where we pass through name as an arg. To finish off our initialize we are setting the view as @getListView and passing in our streamEntities. In our next block of code, we are listening to the click event the same way we have before. We need to listen to the events from our itemview. As you can see, we have msgBus.events.trigger to trigger app:playa:show on this @listenTo. Lastly we are creating our getListView function where we pass through our collection as a new view. Nothing we havent seen before. Modular Apps 40 define ["msgbus", "apps/streams/list/views", "controller/_base" ], (msgBus, Views, AppCon\ troller) -> class Controller extends AppController initialize:(options={})-> {name} = options #console.log "streams:list:controller OPTIONS", options streamEntities = msgBus.reqres.request "search:stream:entities", name view = @getListView streamEntities @listenTo view, "childview:stream:item:clicked", (child, args) -> # listen to\ events from itemview (we've overridden the eventnamePrefix to childview) msgBus.events.trigger "app:playa:show", args.model @listenTo view, "scroll:more", -> msgBus.reqres.request "streams:fetchmore" # event handled by the streams \ entitiy @show view, loading: true getListView: (collection) -> new Views.ListView collection: collection views Our StreamItem class is our ItemView. We give it a li tagname and the same className that we have been seeing for a while. I hope you can see a bit of a process. Of course our itemview is going to need a click event, so we add the appropriate triggers. Our next class is our StreamList which is none other than our CompositeView. StreamList simply pulls our itemView into our Compositeview, and sets our itemViewContainer as #items. Looks familiar also, eh? Lastly we have our classic endless scroll function. I went over how it worked before in the Games app, so Im not sure if i should repeat myself. You get it now. Its a bit tough. Modular Apps 41 # derive from base views and use templates for this app define ['views/_base', 'apps/streams/list/templates'], (AppViews, Templates) -> class StreamItem extends AppViews.ItemView template: _.template(Templates.streamitem) tagName: "li" className: "col-md-6 col-xs-12" triggers: "click" : "stream:item:clicked" ListView: class StreamList extends AppViews.CompositeView template: _.template(Templates.streams) itemView: StreamItem itemViewContainer: "#items" id: "streamlist" events: "scroll": "checkScroll" checkScroll: (e) => virtualHeight = @$("> div").height() scrollTop = @$el.scrollTop() + @$el.height() margin = 200 console.log "virtualHeight:", virtualHeight, "scrollTop:", scrollTop, "elHeigh\ t", @$el.height() if ((scrollTop + margin) >= virtualHeight) #console.log "scroll:more" @trigger "scroll:more" templates define (require) -> streams: require("text!apps/streams/list/templates/streams.htm") streamitem: require("text!apps/streams/list/templates/streamitem.htm") view markup Below, as usual, we have our markup. What you know bout that? streamitem.htm Modular Apps 42 <div> <img class="img-responsive img-thumbnail" src="{{preview.medium}}" > </div> <p class="text-primary text-responsive"><strong>{{channel.status}}</strong></p> <p class="text-muted"><strong>{{ Globalize.format( viewers, "n0") }}</strong> viewers</p> streams.htm <div style="height:100%;"> <ul id="items" class="list-inline"> </ul> </div> And, were done here too. Thats a wrap. Were making good progress. Lastly, we need a place to view the actual stream; this is where our player app comes into play. See what I did there? 5.7 The Player App The last thing were going to go into is our actual Player application. In our app, were setting a new controller and passing a model through to it. Nothing new here. The msgBus.events.on you see is purely for routing. We can talk about this later. app define ["msgbus", "backbone", "marionette", "apps/playa/show/controller"], (msgBus, Backbo\ ne, Marionette, Controller) -> class Router extends Marionette.AppRouter appRoutes: "player/:game/:channel": "show" API = show: (game,channel,model) -> new Controller #game: game channel: channel model: model Modular Apps 43 msgBus.commands.setHandler "start:playa:app", -> new Router controller: API msgBus.events.on "app:playa:show", (streamModel) -> #console.log model.get("channel").display_name Backbone.history.navigate "player/#{streamModel.get("game")}/#{streamModel.get("ch\ annel").display_name}", trigger:false API.show streamModel.get("game"),streamModel.get("channel").display_name, streamMo\ del controller In our player controller we are bascially passing our model to everything. In our initialize function we are passing through options, yet cleverly hashing in {model} to options once again. Next is almost a standard procedure. We need to listen for our show event, where we have a @playerRegion, @userRegion, and a @chatRegion, which all passes model through. In our playerRegion function, we are simply getting our player view, and showing it. This is the same for the chatRegion and userRegion respectively. our get functions are the same idea, just passing through model, and making a new view. Cool. Check that out. define ["apps/playa/show/views", "controller/_base","msgbus"], (Views, AppController, msgB\ us) -> class Controller extends AppController initialize:(options={})-> {channel, model} = options console.log "Player Controller options", options #console.log "game", game, "channel", channel, "model", model #if model is undefined # console.log "searching for ", channel model = msgBus.reqres.request "search:stream:model", channel if model is undef\ ined @layout = @getLayoutView() @listenTo @layout, "show", => @playerRegion model @userRegion model @chatRegion model Modular Apps 44 @show @layout, loading: entities: model playerRegion: (model) -> player = @getPlayerView model @layout.playerRegion.show player chatRegion: (model) -> chat = @getChatView model @layout.chatRegion.show chat userRegion: (model) -> userView = @getUserView model @layout.userRegion.show userView getPlayerView: (model) -> new Views.Player model: model getChatView: (model) -> new Views.Chat model: model getUserView: (model) -> new Views.User model: model getLayoutView: -> new Views.Layout views The Player class is just an ItemView just like the rest of these three classes you see. Nothing crazy at all here actually the most simple view weve seen so far. User and Chat are doing the same simply passing the template into the ItemView. Kids stuff. Lastly we have our Layout, where we are doing a more work than before, yet still, nothing to sweat over. We need three regions for these classes, as they are all seperate ItemViews, therefore we have playerRegion,userRegion and chatRegion. Modular Apps 45 define ['apps/playa/show/templates', 'views/_base', 'swf'], (Templates, AppView, swf) -> Player: class Player extends AppView.ItemView template: _.template(Templates.player) ui: panelbody: ".panel-body" onShow: -> $width = @ui.panelbody.outerWidth(false) $height = Math.floor $width * 9 / 16 flashvars=false params = allowFullScreen: "true" wmode: "transparent" allowScriptAccess: "always" allowNetworking: "all" flashvars: "hostname=www.twitch.tv&channel=#{@model.get("channel").display\ _name}&start_volume=15&auto_play=true&client_id=hqxyqc9bf41e6grm6txrsdcwncoxavz&res=720p" swf.embedSWF("https://www-cdn.jtvnw.net/widgets/live_embed_player.swf?channel=\ #{@model.get("channel").display_name}&auto_play=true", "twitchplayer", $width, $height, "9\ ", null, flashvars, params, {}); User: class User extends AppView.ItemView template: _.template(Templates.user) Chat: class Chat extends AppView.ItemView template: _.template(Templates.chat) Layout: class Layout extends AppView.Layout template: _.template(Templates.layout) regions: playerRegion: "#player-region" userRegion: "#user-region" chatRegion: "#chat-region" templates Modular Apps 46 # modular template loading define (require) -> player: require("text!apps/playa/show/templates/player.htm") user: require("text!apps/playa/show/templates/user.htm") layout: require("text!apps/playa/show/templates/layout.htm") chat: require("text!apps/playa/show/templates/chat.htm") view markup Below we have our markup, as usual. chat.htm <div class="panel panel-info"> <div class="panel-heading"><h3>Chat<small> - {{channel.display_name}}</small></h3></div> <div class="panel-body"> <iframe frameborder="0" scrolling="no" id="twitchchat" src="https://twitch.tv/chat/embed?channel={{channel.display_name}}&popout_\ chat=true" height="500" width="320"> </iframe> </div> </div layout.htm <div class=row> <div class="col-md-8 col-xs-12"> <div id="player-region"></div> <div id="user-region"></div> </div> <div id="chat-region" class="col-md-4 col-xs-12"></div> </div> player.htm Modular Apps 47 <div class="panel panel-warning"> <div class="panel-heading"><h3>TwitchTV Expose <small>{{game}} - {{channel.display_name}\ }</small></h3></div> <div class="panel-body" id="twitchplayer"> <!-- <object id="twitchExpose" type="application/x-shockwave-flash" width="600" height="338\ " data="https://www-cdn.jtvnw.net/widgets/live_embed_player.swf?channel={{channel.display_\ name}}" bgcolor="#C0C0C0"> <param name="allowFullScreen" value="true" /> <param name="wmode" value="transparent" /> <param name="allowScriptAccess" value="always" /> <param name="allowNetworking" value="all" /> <param name="flashvars" value="hostname=www.twitch.tv&channel={{channel.display_na\ me}}&start_volume=15&auto_play=true&client_id=hqxyqc9bf41e6grm6txrsdcwncoxavz&res=720p" /> </object> --> </div> </div> All done here. That was a lot of code. Good stuff. Moving on to our next section, where ill explore our entities and api calls. Kinda important. 6 Entities Its important to see how the entities use the js/msgbus.coffee pattern for intra-app communication. The way I have structured this application enables me to completely decouple the entities from the individual applications. Each of the application entities is going to listen for a namespaced event and respond via their own API. Were going to dive in each entity seperately and highlight the patterns and namespaced events. 6.1 Header.entities Our Header.entities is just a static Backbone collection. This data is used to populate our top level menu for our Headers. Check out the properties for our collection. the name, url, title, cssClass properties are all used to fill in our Menu. Note at the bottom here, we have msgBus.reqres.setHandler "header:entities", -> This is using the Request/Response pattern of our msgBus. Notice this module is listening for the header:entities namespaced message/event. When this event occurs the module API returns the API.getHeaders() function. #static header entities define ["backbone","msgbus"], (Backbone, msgBus ) -> API = getHeaders:-> new Backbone.Collection [ (name: "Games", url: "#games", title: "Live Games", cssClass: "glyphic\ on glyphicon-hdd" ) (name: "D3", url: "#d3", title: "Sample D3 visualization", cssClass: "\ glyphicon glyphicon-list") (name: "About", url: "#about", title: "Learn about responsive Twitch-T\ V", cssClass: "glyphicon glyphicon-align-justify") ] msgBus.reqres.setHandler "header:entities", -> API.getHeaders() Entities 49 6.2 OSS.entities (OpenSource Software) Our Oss.entities is just another static Backbone collection. The data here is used to populate one of the views in my About app. Check out the model properties: name, site, image, gurl are all used in a templated listview. This module sets up a handler that listens for the namespaced oss:entities event and responds with the API.getOSS() function. #static entities/collection: open source software define ["backbone","msgbus"], (Backbone, msgBus ) -> API = getOSS:-> new Backbone.Collection [ (name: "Cloud9 IDE", site: "https://c9.io", image:"https://c9.io/s\ ite/wp-content/themes/cloud9/img/slides/slides.png", ghurl:"https://github.com/ajaxorg/clo\ ud9/") (name: "Github", site: "https://github.com", image:"https://github\ .global.ssl.fastly.net/images/modules/dashboard/bootcamp/octocat_fork.png?74c9d5ac", ghurl\ :"https://github.com") (name: "Backbone", site: "http://backbonejs.org", image:"http://ba\ ckbonejs.org/docs/images/backbone.png", ghurl:"https://github.com/jashkenas/backbone/") (name: "Marionette", site: "http://marionettejs.com/", image:"http\ ://marionettejs.com/images/logo-a4052db8.png", ghurl:"https://github.com/marionettejs/back\ bone.marionette") (name: "RequireJS", site: "http://requirejs.org", image:"http://re\ quirejs.org/i/logo.png", ghurl:"https://github.com/jrburke/requirejs") (name: "Underscore", site: "http://backbonejs.org", image:"http://\ underscorejs.org/docs/images/underscore.png", ghurl:"https://github.com/jashkenas/undersco\ re") (name: "Bootstrap", site: "http://getBootstrap.com", image:"image/\ bootstrap.png", ghurl:"https://github.com/twbs/bootstrap") (name: "D3", site: "http://d3js.org", image:"http://d3js.org/ex/cl\ oud.png", ghurl:"https://github.com/mbostock/d3") (name: "JQuery", site: "http://jquery.com", image:"http://jquery.c\ om/jquery-wp-content/themes/jquery/images/logo-jquery.png", ghurl:"https://github.com/jque\ ry/jquery") ] msgBus.reqres.setHandler "oss:entities", -> API.getOSS() 6.3 Appstate.entities Our Appstate.entities this time isnt a collection, but a Backbone model. Entities 50 our class Appstate has defaults which set our clientID, accessToken, and loginStatus. This is used for interacting with our Twitch Authentication. This is a bit beyond the scope of the book.. this has to do with creating a twitch.tv API dev account. Belowthat, we are listening for the get:current:appstate namespaced event, and returning the API.getAppState() function. Even below that, we have our next handler which is listening for "get:current:token" and returning appState.get "accessToken" which is a model property. define ["entities/_backbone", "msgbus"], (_Backbone, msgBus ) -> class AppState extends _Backbone.Model defaults: clientId: "my-given-client-id" // it's a secret accessToken: false loginStatus: null appState = new AppState API = getAppState: -> appState msgBus.reqres.setHandler "get:current:appstate", -> API.getAppState() msgBus.reqres.setHandler "get:current:token", -> appState.get "accessToken" 6.4 User.entities Our User.entities has a User Backbone model, and a UsersCollection Backbone collection. setCurrentUser passes in a currentUser and adds a new User model passing in currenntUser. Below that we are listening for our namespaced setHandler "set:current:user" which we pass in our currentUser model to. API.setCurrentUser is returned with our currentUser model passed through. Entities 51 define ["entities/_backbone", "msgbus",], (_Backbone, msgBus ) -> # this _fetch is our private property added to overridden config backbone sync class User extends _Backbone.Model class UsersCollection extends _Backbone.Collection model: User API = setCurrentUser: (currentUser) -> new User currentUser msgBus.reqres.setHandler "set:current:user", (currentUser) -> API.setCurrentUser currentUser 6.5 Reference.entities Our Reference.entities is another static Backbone collection, remember this is just a client-side application, so were not pulling any data from a backend server. Were just simulating a larger app here. This data is is being used to populate our OSS view in my About app. Check out the model properties: type, title , author, and url. We set a namespaced event handler "reference:entities" and this modules API returns with API.getRESferences() function all decoupled for you. #static entities define ["backbone","msgbus"], (Backbone, msgBus ) -> API = getReferences:-> new Backbone.Collection [ (type: "book", title: "Little Book on Coffeescript", author: "Alex Mac\ Caw", url: "books.google.com/books?isbn=1449325548") (type: "book", title: "Javascript The Good Parts", author: "Douglas Cr\ ockford", url: "books.google.com/books?isbn=0596554877") (type: "book", title: "High Performance Javascript", author: "Nicholas\ C. Zakas", url:"books.google.com/books?isbn=1449388744") (type: "book", title: "jQuery Cookbook", author: "jQuery Community Exp\ erts", url:"books.google.com/books?isbn=1449383017") (type: "book", title: "HTML and CSS: Design and Build Websites", autho\ r:"Jon Duckett", url:"books.google.com/books?isbn=1118206916") (type: "book", title: "Interactive Data Visualation for the Web", auth\ or:"Scott Murray", url:"books.google.com/books?isbn=1449340253") (type: "book", title: "Developing Backbones.js Applications", author:"\ Entities 52 Addy Osmani", url:"books.google.com/books?isbn=1449328555") (type: "book", title: "Bootstrap", author:"Jake Spurlock", url:"books.\ google.com/books?isbn=1449344593") (type: "video", title: "Client Side Development", author:"Brian Mann",\ url:"vimeo.com/58787395") (type: "video", title: "Tenets of Backbone JS", author:"Brian Mann", u\ rl:"vimeo.com/58787396") (type: "video", title: "Marionette JS", author:"Brian Mann", url:"vime\ o.com/58797363") (type: "video", title: "Application Infrastructure", author:"Brian Man\ n", url: "backbonerails.fetchapp.com/sell/aezoosaw/ppc") (type: "video", title: "Getting Up and Running - Part 1", author:"Bria\ n Mann", url:"backbonerails.fetchapp.com/sell/siyeebee/ppc") (type: "video", title: "Getting Up and Running - Part 2", author:"Bria\ n Mann", url:"backbonerails.fetchapp.com/sell/ferechie/ppc") (type: "video", title: "Building a Real Application: Planet Express", \ author:"Brian Mann", url:"backbonerails.fetchapp.com/sell/ichangei/ppc") (type: "video", title: "Screencast: Loading Views", author:"Brian Mann\ ", url:"www.backbonerails.com/screencasts/loading-views") (type: "book", title: "Creating Animated Bubble Charts in D3", author:\ "Jim Vallandingham", url:"vallandingham.me/bubble_charts_in_d3.html") ] msgBus.reqres.setHandler "reference:entities", -> API.getReferences() 6.6 Author.entities Our Author.entities is a Backbone model with the defaults fullName , twitter , and github. This data is a static model for our Footer app. Again, this is only a client-side app theres no data coming from a backend, so this is me generating data for my views. We have a setHandler that listens for the namespaced "get:authorModel:info" event and returns with the API.getAuthor() function. define ["entities/_backbone", "msgbus",], (_Backbone, msgBus ) -> class Author extends _Backbone.Model defaults: fullName: "Jack Killilea" twitter: "https://www.twitter.com/jack_killilea" github: "https://www.github.com/xjackk" API = getAuthor: -> Entities 53 new Author msgBus.reqres.setHandler "get:authorModel:info", -> API.getAuthor() 6.7 TwitchTV.entities This app was built solely around a popular streaming sites API. Almost all the calls we are making are to Twitch, and not properly documenting how we would go about this would be a shame. We can take a direct look at the huge block of code that we know as our Twitch Entities. Below were going to take a look at the thought process behind what weve done here. Game and Stream are both our namespaced Models. StreamGet extends from our entities/_backbone collection. Were doing some overriding here. in streamGet we are going to be parsing our stream for response. SearchStreams extends our entities/_backbone collection as well, and takes some hashes Stream is our model in this case, and we are going to parse for another response. SearchCollection is going to be doing the same thing, except instead of setting our stream as the model we are setting it to Game. Lastly in our classes we have GamesCollection which does the same thing as before and makes Game our model. Now some logic. Our initialize function sets a namespaced handler for "Games:fetchmore" which returns @moreGames() function were then setting some values here. @limit, @offset, @loading, @previousSearch, and @_total. moreGames function is going to return true if our @loading or our @length are greater or equal to our @_total. @loading is set to true and were going to be @offseting. when loaded, we make a request for "get:current:token". This is for our authentication. We have some Jquery going on as well, when not loaded. searchName passes in _name and finds a game model and gets its name. This would be World of Warcraft or any game name. You got it. Below this we have our streamCollection which is just your everyday backbone collection. We pass in a Stream as a model. Initialize sets a handler for fetching more streams. Note the fat arrow. Below were setting some sweet properties for getting more streams. We have some cute properties below this. We want a set limit that comes back and such. Below this, we have the moreStreams function which does the exact same thing that it did for the games, except for the streams. Cool stuff. Were about done with this section of the code. Now we can start checking out some of our Public API goodies. Entities 54 This games variable is set for a new collection, as well as a timestamp for a new date below. We now get into the nitty-gritty, where we check out our first call, getGames. getGames take a url and an empty param. elapsedSeconds is exactly what you think it would be. Sets a timer on getting some games back. Our trusty OAUTH token we will be seeing often; simply used for authenticating. Were then setting a new games collection for stuff to be added to, and looking to our url. This url is given by the Twitch API. Its for making our calls. games.fetch does exactly what you would expect it to do fetch some games. We see the same pattern occur in the searchGames function. Literally the same thing, except we are not populating a gamescollection, we are populating a streamcollection. getStreams is very similar, except we are adding in streams.games as a param later down in the code. This is a custom class property, as seen in the comment. getStream is going to get us a single stream like a channel. Same thing seen here, except we are going to create a model to store this in, instead of a collection of some sort. StreamGet is our model here. After all this good stuff, we now have some set handlers to work with. Cool stuff. This will tie up some ends here. msgBus.reqres.setHandler "games:top:entities" is another classic example of our namespacing. API.getGames is going to make a call for "games/top", which according to the Twitch docs, will return the games sorted by how many views. Hence top games. We set a limit of 24 here, because I felt that was a good number to stop at for the collection. We can keep going through it until the very end with our nifty endless scrolling. Thats how we do it. msgBus.reqres.setHandler "search:games" is going to pass in a query and search with the API for search/games. Same idea here. msgBus.reqres.setHandler "games:searchName" is actually a pretty cool trick to speed up the UI. This will query our already cached games to speed up the UI process. We want everything to run as smooth as possible, eh? This isnt actually an API call, as it is just searching what we already have. Kinda neat. msgBus.reqres.setHandler "search:stream:entities" will help us search for streams by the game itself. Always keep with our proper namespacing. A game is passed through to the function, and then we make the API.getStreams call to "search/streams". A limit is set at about 12. Just my choice. msgBus.reqres.setHandler "search:stream:model" is going to pass in a channel to the function. This will act like we are grabbing a channels live stream. API.getStream will get us a streams/#{channel}. This is templating we use to take the channel from the stream/ call. All the code is below for you to checkout. Its better for you to look at it then me explain it step by step. Even download the source and play around with it if you so please. Entities 55 define ["entities/_backbone", "msgbus"], (_Backbone, msgBus ) -> # this _fetch is our private property added to overridden config backbone sync class Game extends _Backbone.Model class Stream extends _Backbone.Model # different class to handle parse of .stream object from the twitch API: looking for a\ single model class StreamGet extends _Backbone.Model parse: (response) -> response.stream class SearchStreams extends _Backbone.Collection model: Stream parse: (response) -> response.streams class SearchCollection extends _Backbone.Collection model: Game parse: (response) -> response.games class GamesCollection extends _Backbone.Collection model: Game initialize: -> msgBus.reqres.setHandler "games:fetchmore", => @moreGames() @limit = 50 @offset = 0 @loading = false @previousSearch = null @_total = null moreGames: -> return true if @loading or @length >= @_total @loading=true @offset++ #console.log "fetching page #{@offset+1} of games" loaded = @fetch remove: false data: oauth_token: msgBus.reqres.request "get:current:token" limit: @limit Entities 56 offset: @offset * @limit $.when(loaded).then => @loading=false #console.log "Loaded page", @offset+1, "Games fetched so far", @length, "T\ otal games available to fetch ", @_total searchName: (_name)-> @find (model)-> model.get("game").name is _name parse: (response) -> {@_total}=response response.top class StreamCollection extends _Backbone.Collection model: Stream initialize: -> msgBus.reqres.setHandler "streams:fetchmore", => @moreStreams() @limit = 12 @offset = 0 @loading = false @previousSearch = null @_total = null moreStreams: -> return true if @loading or @length >= @_total @loading=true @offset++ loaded = @fetch remove: false data: oauth_token: msgBus.reqres.request "get:current:token" q: @game limit: @limit offset: @offset * @limit $.when(loaded).then => @loading=false parse: (resp) -> Entities 57 {@_total}=resp resp.streams # caching timers initialize games = new GamesCollection games.timeStamp = new Date() #PUBLIC API API = getGames: (url, params = {}) -> #45 seconds elapsed time between TOP game fetches elapsedSeconds = Math.round(((new Date() - games.timeStamp ) / 1000) % 60) if elapsedSeconds > 45 or games.length is 0 _.defaults params, oauth_token: msgBus.reqres.request "get:current:token" games = new GamesCollection games.timeStamp = new Date() games.url = "https://api.twitch.tv/kraken/#{url}?callback=?" games.fetch reset: true data: params games searchGames: (url, params = {}) -> _.defaults params, oauth_token: msgBus.reqres.request "get:current:token" sgames = new SearchCollection sgames.url = "https://api.twitch.tv/kraken/#{url}?callback=?" sgames.fetch reset: true data: params sgames getStreams: (url, params = {}) -> _.defaults params, oauth_token: msgBus.reqres.request "get:current:token" streams = new StreamCollection streams.game=params.q #tack this on/custom class property streams.url = "https://api.twitch.tv/kraken/#{url}?callback=?" streams.fetch reset: true data: params streams Entities 58 # get stream by channel getStream: (url, params = {}) -> console.log "getStream", url, params _.defaults params, oauth_token: msgBus.reqres.request "get:current:token" stream = new StreamGet # model stream.url = "https://api.twitch.tv/kraken/#{url}?callback=?" stream.fetch data: params stream # initial collection search 'top games' twitchAPI msgBus.reqres.setHandler "games:top:entities", -> API.getGames "games/top", limit: 24 offset: 0 #implement TWITCHAPI call msgBus.reqres.setHandler "search:games", (query)-> API.searchGames "search/games", q: query #encodeURIComponent query type: "suggest" live: false # search internal cached collection for a game models, speed up the UI msgBus.reqres.setHandler "games:searchName", (query)-> games.searchName query #search for streams by game msgBus.reqres.setHandler "search:stream:entities", (game)-> API.getStreams "search/streams", q: game limit: 12 offset: 0 # twitchAPI, grab a channels live stream msgBus.reqres.setHandler "search:stream:model", (channel)-> API.getStream "streams/#{channel}" 7 Build and Deploy 7.1 R.js the RequireJS Optimizer From the r.js docs : r.js is a command line tool for running JavaScript scripts that use the Asychronous Module Defintion API (AMD) for declaring and using JavaScript modules and regular JavaScript script files. It is part of the RequireJS project, and works with the RequireJS implementation of AMD. r.js is a single script that has two major functions : Run AMD-based projects in Node and Rhino. Includes the RequireJS Optimizer that combines scripts for optimal browser delivery. 7.2 Shell script Ive included shell script named nodebuildAPP.sh that I use to run the r.js optimizer on the project #!/bin/bash exec node bower_components/r.js/dist/r.js -o build.js To make a shell script executeable in unix, execute the following at your terminal prompt: chmod +X nodebuildAPP.sh My simple build shell script is using NODE to call the r.js script and pass in the build.js config file: Build.js Take a look at the build.js file. Note, this is pretty much boiler plate taken straight from the RequireJS/r.js documentation. Build and Deploy 60 ({ baseUrl: "js", mainConfigFile: 'js/main.js', out: "js/main.optimized.js", include: 'main', optimize: "uglify", uglify: { toplevel: true, ascii_only: true, beautify: false, max_line_length: 1000 }, inlineText: true, useStrict: false, skipPragmas: false, pragmasOnSave: { //Just an example excludeCoffeeScript: true }, skipModuleInsertion: false, stubModules: ['text'], optimizeAllPluginResources: false, findNestedDependencies: false, removeCombined: false, fileExclusionRegExp: /^\./, preserveLicenseComments: true, logLevel: 0 }) Main.optimized.js To build our optimized Javascript takes one command : ./nodebuildAPP.sh Basically, we are saying: Use the js/main.js as input and create js/main.optimized.js as output. Use the main.optimized.js for your production site. Build and Deploy 61 7.3 Deploying Here we are going to be covering two ways of deploying your app. Technically, this is a browser-client appliaction, not a true web application. All the data is coming from Twitchs REST API, not a real backend. 7.4 Cloud9 [Cloud 9][www.c9.io] Cloud9 IDE is an online development environment for Javascript and Node.js applications as well as HTML, CSS, PHP, Java, Ruby and 23 other languages. Its written all in JS. Pretty neat. Cloud 9 was the editor that I used for this project mainly because it is a good ACE editor, but also because it was really easy for me to get started quick and involved in a project. All you need to need to do in Cloud 9 to preview your application is just preview the index.html file. Money. I am now using SublimeText2 as an editor, and I can say that it is no different than c9 just a different environment. I may go back to using c9 in the future. Who knows 7.5 Heroku What is Heroku? Heroku (pronounced her-OH-koo) is a cloud application platform a new way of building and deploying web apps. Its a service that lets devs spend more time writing code and not managing servers, deployment, ongoing operations, or scaling. Cause I mean.. who wants to do all that? For my purpose especially, I wanted to host my application on the web for free, and using Heroku made this process for me almost too easy especially using Cloud 9 as my IDE. 7.6 Express Static Server For hosting to Heroku, we needed to setup a static node express server. var express = require('express'); var app = express(); app.use(express.static(__dirname + '/')); app.listen(process.env.PORT || 3000); Build and Deploy 62 This is literally all our express server is for serving a static page. Easy eh? Express really keeps it as simple as it gets. In 4 lines, we are hosting. Money. Also to keep in mind when serving a page up with Heroku is adding a procfile. web: node web.js Three words basically If this isnt making perfect sense to you, check out Herokus documentation here. If I could get it setup, so can you [Heroku Docs][https://devcenter.heroku.com/articles/getting-started-with-nodejs] 7.7 Using Cloud 9 With Heroku Using Cloud 9 with Heroku is almost too simple. You can set up an account at their site and from there : Deploy! Go through the wizard with deploying to heroku and your done. Easy son. Deploying To Heroku Without Cloud 9 IDE Not all of you use Cloud 9 as an IDE, and I get that. Build and Deploy 63 For those of you who apply, check out Herokus documentation on pushing a project up with Git. You can find some of their documentation here : Its really good. Being comfortable with Git is very important if you are looking to be more Open-Sourcey. Now, you dont have to use Heroku to host your site. There are many other hosing sites out there, and they all get the job done. Heroku was free, and was I was recommended to use by a friend. I like it. 7.8 Open Source Dependency Management In this small section, we will be covering how to update and manage your Open Source Dependencies. Lets check out NPM and Bower. NPM Nodes goal is to provide an easy way to build scalable network programs. Node Package Manager is an Open Source manager for node modules. It is the equivalent of Ruby Gems, in Rails development. In my project, I use it for loading Coffeescript. npm install -g Coffee-Script Look at the package.json file for this project, as it lists the dependencies that will be loaded when npm install command is executed. { "name": "twitchexpose", "version": "0.0.1", "dependencies": { "express" : "3.x" }, "engines": { "node": "0.10.x", "npm": "1.2.x" } } Bower Bower is a package manager for the web. It offers a generic, unopinionated solution to the problem of front- end package management, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat. Build and Deploy 64 npm install -g Bower Once installed, you can add all the dependencies based on your bower.json file. To install your dependencies via Bower, list your dependencies in your bower.json file, then type : Bower install I hope that wasnt too much for you. Our `bower.json` file. { "name": "jackapp", "version": "0.0.0", "main": "jackapp", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "jquery": "~2.0.3", "bootstrap": "~3.0.0", "requirejs": "~2.1.8", "marionette": "~1.1.0", "requirejs-text": "~2.0.9", "backbone-amd": "~1.1.0", "underscore-amd": "1.5.2", "backbone.babysitter": "~0.0.6", "backbone.wreqr": "~0.2.0", "jquery-mockjax": "~1.5.2", "moment": "~2.3.1", "globalize": "~0.1.1", "mockJSON": "git://github.com/mennovanslooten/mockJSON.git", "backbone.syphon": "~0.4.1", "d3": "~3.3.7", "spin.js": "~1.3.0", "holderjs": "~2.1.0", "r.js": "~2.1.8", "swfobject-amd": "~2.3.0" } } Build and Deploy 65 7.9 TwitchTV API Possibly one of the most important parts of this project as a whole, Twitchs API. Twitchs API documentation was overall pretty good and easy to follow. This was my first project working with APIs and to tell you the truth it isnt as bad as I originally thought it was going to be. Nothing ever is, really. [Twitch API][https://github.com/justintv/Twitch-API] Just a link to their API. Im just going to cover some of the brief stuff. GET games/top Games are categories (e.g. League of Legends, Diablo 3) used by streams and channels. Games can be searched for by query. Endpoint Description GET /games/top Get games by number of viewers Returns a list of games objects sorted by number of current viewers on Twitch, most popular first. Parameters Name Required? Type Description limit optional integer Maximum number of objects in array. Default is 25. Maximum is 100. offset optional integer Object offset for pagination. Default is 0. hls optional bool If set to true, only returns game objects with streams using HLS Take a closer look at the Games/Top RESTAPI here TwitchTV Rest/API Example Request curl -H 'Accept: application/vnd.twitchtv.v2+json' \ -X GET https://api.twitch.tv/kraken/games/top Example Response { "_links": { "self": "https://api.twitch.tv/kraken/games/top?limit=25&offset=0", "next": "https://api.twitch.tv/kraken/games/top?limit=25&offset=25" }, "_total": 322, "top": [ { "game": { Build and Deploy 66 "name": "League of Legends", "box": { "large": "http://static-cdn.jtvnw.net/ttv-boxart/League%20of%20Legends.jpg?w=272\ &h=380&fit=scale", "medium": "http://static-cdn.jtvnw.net/ttv-boxart/League%20of%20Legends.jpg?w=13\ 6&h=190&fit=scale", "small": "http://static-cdn.jtvnw.net/ttv-boxart/League%20of%20Legends.jpg?w=52&\ h=72&fit=scale", "template": "http://static-cdn.jtvnw.net/ttv-boxart/League%20of%20Legends.jpg?w=\ {width}&h={height}&fit=scale" }, "logo": { "large": "http://static-cdn.jtvnw.net/ttv-logoart/League%20of%20Legends.jpg?w=24\ 0&h=144&fit=scale", "medium": "http://static-cdn.jtvnw.net/ttv-logoart/League%20of%20Legends.jpg?w=1\ 20&h=72&fit=scale", "small": "http://static-cdn.jtvnw.net/ttv-logoart/League%20of%20Legends.jpg?w=60\ &h=36&fit=scale", "template": "http://static-cdn.jtvnw.net/ttv-logoart/League%20of%20Legends.jpg?w\ ={width}&h={height}&fit=scale" }, "_links": {}, "_id": 21779, "giantbomb_id": 24024 }, "viewers": 23873, "channels": 305 }, ... ] } An important step in the process for me was having a user able to come to my site and login with their twitch credentials. Understanding howauthenticating works with tokens was challenging at first. Thats where our appstate.entities comes into play as I needed to create the site in a state after the user has logged in. I wanted to do something cheesey like hide the login region after the user has verified his/her info. To sum this all up, I basically read the documentation for what it was and picked out the parts that catered to my needs. I didnt need to hit every point on twitch. I just wanted to take their viewing features no real social aspects to it. Every Callback from Twitchs API is going to hit you back with JSON. Build and Deploy 67 If you dont know anything about working with REST APIs, you should read up on that before writing an app using API calling. GET POST PUT and DELETE are all you need to know. It took me a bit to read up on but I personally feel that working with [JSON][http://json-p.org/] is pretty easy. Its all just javascript after all. To get the overall picture of what this project was based on, just check out the link above for their documentation. 7.10 Working with Github I want a small section to be about working with [Github][www.github.com] because lets be honest, you need to know how to use it if you really want to be included in the Open-Source world. At the heart of GitHub is an open source version control system (VCS) called Git. Created by the same team that created Linux, Git is responsible for everything GitHub related that happens locally on your computer. Now, Imnot going to give you a lesson on Git, but you should check [this][http://git-scm.com/book/en/Getting- Started-Git-Basics] out to learn about it. Its very simple to understand. Github creates this idea of gameification where you are rewarded for your efforts essentially. The whole idea of Github is basically to be doing it probono. You want the love of the community, so you keep putting out code and such. Jacob Thornton aka @fat, one of the creators of Bootstrap mentioned this point in his What is Open-Source, and Why Do I Feel So Guilty? lecture. Check It Out The idea of achievement eggs you on to contributing more and more. You want these nerds to love you, and putting something out for the community to use is a cool feeling. Its an even cooler feeling when all these nerds are checking out, and using your stuff. 8 Appendix This is about the time where I throw props to just about everything Ive used for creating my project. It would be nothing without these! 8.1 Shoutout To ! A big shoutout to my father. He was a big motivator for me to get into the world of computer programming and Id be virtually nothing without his guidance. I still go to him to check my CS homework. Hats off to you, fool. Shouts out to @fat and @mdo for Bootstrap. You guys save me all the time in the world. I have spent way more time than I should have staring at sample code from your site. I should get it by now. Shoutout to Backbone Rails, and Brian Mann, for teaching me the ways of Backbone. If you havent checked out his tutorials he will for sure teach you a lot more than I can. Check out his screencasts. Lastly, a small shoutout to Radiohead for possibly being the best band in the world. Cant write baller JS without a quality soundtrack. 8.2 What I Have Learned Here (And Hopefully What You Learned!) After months and months of just hacking around, I finally put my knowledge to use and created what I think to be my first notable project. There will be many more to come! Gaming is something near and dear to me, and this seemed like a perfect first project. Hopefully you learned what it takes to structure a scalable Marionette application. The object of this book was to take you through a journey of open-source, and create a decoupled application. I know for people who are new to open-source it can seem very intimidating. Riding a bike was intimidating at first too, no? It all comes with practice. A behind the scenes of writing this application would show the hardships of constantly debugging, throwing fits of rage, and getting even the simplest things wrong. It was very hard. Possibly one of the hardest things that I have taken up. This book was supposed to give you a good model for learning how to write decoupled Marionette applications, by giving you readable Coffeescript, many examples from my project, as well as the direct source code which you can find on my Github. 8.3 Outro I hope that open-source isnt as intimidating any longer for you guys. For those of you that have been using open-source goodies since day one, props to you. If there is anything I want you to take from this book, its that using open-source is your friend. Everything good that has been done, you can almost find already. Use Appendix 69 it. If you cant find it, create it, and throw it out there for your fellow nerds to use. Were all in this together, after all. I am going to be keeping this book free. Donating is cool, but it isnt necessary. Charging people for this would be going against almost everything that I have been trying to teach you. Open-source is supposed to be free and having you pay for this would just make me look like a bad dude. Get to writing your apps, nerds.