Anda di halaman 1dari 73

Marionette Expos

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>&nbsp; &nbsp;<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}}&amp;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.

Anda mungkin juga menyukai