-
Notifications
You must be signed in to change notification settings - Fork 3
Jabberdy Walkthrough
This page walks you through a functioning Sail.js-based app -- Jabberdy.
The basic structure of a sail.js app looks like this:
agents/
css/
myapp.css
js/
myapp.js
sail.js/
index.html
Take a look at https://github.com/educoder/sail.jabberdy as an example.
agents
contains agents. These are usually server-side, and won't be discussed in detail here.
css
contains the stylesheet(s) for your app. Generally you will have a yourappname.css
file in there, although this is arbitrary.
js
contains the javascript code (the "controller"). Later you'll create yourappname.js
in here.
js/sail.js
contains a copy of the sail.js libraries and dependencies. In the sail.jabberdy example this is installed as a git submodule via the .gitmodules
file (at the root of your app).
index.html
is your app's "view". Most apps will only have one index.html, but others might have multiple or might use PHP instead of static HTML.
Additionally, in the sail.jabberdy
example app you will find a server.js
file. This is a self-contained HTTP server for running your app with reverse-proxying to the XMPP server built right in. See below.
You might want to just make a copy of the sail.jabberdy
app as a template. Rename all instances of "jabberdy" to your app's name.
To clone the sail.jabberdy
sample app:
git clone git://github.com/educoder/sail.jabberdy.git
Then to download the submodules (sail.js
and sail.rb
):
cd sail.jabberdy
git submodule init
git submodule update
For XMPP connectivity, you need reverse proxying. This can be set up via Apache, but for development it might be easier to use the built-in server. To run it you will need node.js installed along with npm.
On Mac OS X, to install node.js:
brew install node
(you may need sudo
)
Then install npm:
curl http://npmjs.org/install.sh | sh
(you may also need to add sudo
in front of the sh
)
Then install the http-proxy
and node-static
packages using npm:
npm install http-proxy
npm install node-static
Now you should be able to run the server by cd
ing into the app's directory and running:
node server.js
The contents of the directory will be served at http://localhost:8000
For full functionality, you will also have to run the agents. sail.jabberdy
's agents are all written in Ruby. You will need some rubygems before you can run them:
gem install json rest_client blather pidly ruby-debug
(I may be forgetting others...)
Then to run the agents cd
into the agents directory and run:
ruby daemon.rb
The agents will be fired up in the background. To see what's going on:
tail -f logs/jabberdy_agents.log
The index.html
is just a standard, static HTML page with some javascript. Take a look at jabberdy's index.html. We're going to break it down piece by piece:
The <head>
starts off with:
<link href="css/jabberdy.css" rel="stylesheet" type="text/css" />
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/start/jquery-ui.css" rel="stylesheet" type="text/css" />
<link href="js/sail.js/css/sail.css" rel="stylesheet" type="text/css" />
<link href="js/sail.js/css/sail.user-list.css" rel="stylesheet" type="text/css" />
<script src="js/sail.js/sail.js" type="text/javascript"></script>
<script type="text/javascript">
Sail.load()
.then('js/jabberdy.js')
.thenRun(function() { return Sail.init(Jabberdy) })
</script>
You can copy this exactly. Just change all instances of jabberdy
to yourappname
and rename your app's .css and .js files.
Once sail.js
is loaded using the old-fashioned <script>
tag...
- the
Sail.load
function loads all of the necessary dependencies, - your app's code is loaded (
js/yourappname.js
), - and finally, we initialize the app via
Sail.init(YourAppName)
Your app's entire UI is defined in the <body>
up front using good old HTML. There's nothing special here. Just a bunch of <div>
tags and such. Note that most of the elements defined here are initially hidden (style="display: none"
). Also note that they all have unique IDs. This is so that they can be referred to later in your javascript.
For example, the "join" dialog that asks users whether they want to play or watch looks like this:
<div id="join-dialog" class="widget-box dialog" style="display: none">
<p>Welcome <span id='username'></span>!<br />What would you like to do?</p>
<button id='watch'>Watch</button> <button id='play'>Play</button>
</div>
Again, it is initially hidden (display: none
) and is given the "widget-box"
and "dialog"
classes for nicer formatting. All <button>
tags are automatically converted to jQuery.UI buttons in the Sail.UI.init()
call earlier in your <head>
.
You'll probably want to give your UI elements like widgets and panels the class "widget-box"
. This gives them a consistent look and feel (round corners, thick borders, drop shadow, etc.)
That's it for the view. Pretty straight forward. Note that except for the initial setup in the <header>
there is no javascript here. Keep your js code out of the html!
Open up js/jabberdy.js. The Jabberdy app is defined as a one big javascript object:
Jabberdy = {
/** ... **/
}
At the top are some configuration settings, and then a few member variables used by the app. This is followed by an init()
method, some additional helper methods, and event handlers (methods defined under events
that start with on
-- e.g. events.authenticate
).
Jabberdy = {
conf1: "some value",
conf2: "some other value",
internalVar1: null,
internalVar2: null,
init: function() {
/* ... */
},
/* helper methods */
/* event handlers (on____) */
events: {
sail: {
},
/* .. */
}
}
The first method is init
. This initializes the application -- it's called by Sail.init(Jabberdy).
The first part of init
makes several chained calls to Sail.modules
. This loads in a bunch of Sail modules that automatically endow the Jabberdy app with certain features. For example, Rollcall.Authenticator
enables Rollcall-based authentication for the app; Strophe.AutoConnector
automatically establishes an XMPP connection based on authentication credentials set by Rollcall.Authenticator
; and AuthStatusWidget
displays a "You are logged in as ..." indicator at the top right of the page. Finally, .thenRun
runs a block of code after all of the modules have been loaded.
One thing to note inside .thenRun
is the call to Sail.autobindEvents
:
Sail.autobindEvents(Jabberdy)
What this does is it looks for all of the methods defined in Jabberdy.events
t\and automatically binds them as custom event listeners. For example if Jabberdy.events
has a method authenticated
, Sail.autobindEvents
will automatically set up a listener for the event authenticated
so that your authenticated
function is called whenever Jabberdy
receives the event. Have a look at jQuery's bind() and trigger() if you're not familiar with custom javascript events.
Finally, init
binds some event listeners to the page elements (the elements we defined earlier in index.html). For example, clicking the button with id play
will trigger the 'choseToPlay' event in our Jabberdy app.
The event handlers (methods under events
) are automatically wired by Sail.autobindEvents
to respond to events triggered on Jabberdy. For example, if you take a look at the submitGuess
method definition, you'll notice that when a new word guess is submitted, the submittedGuess
event is triggered using the following code:
$(Jabberdy).trigger('submittedGuess')
This will automatically call the event.submittedGuess
method.
But why not just call submittedGuess()
directly instead of the event trigger? The reason is that using events allows for better decoupling of components. The code triggering the call doesn't need to know who's going to receive it. If, for example, down the line you wanted to add a widget that shows a count of how many guesses the user submitted, you could just bind it to listen for the submittedGuess
event, without having to make any changes to the code that triggers it.
Looking at the Jabberdy source, you'll see that most of the code you will be writing for your Sail app consists of event handlers and event triggers.
The connected
event handler is called once the connection to the XMPP server has been established (this is taken care of by the Strophe.AutoConnector
module). Once the connection is established, the connected
event is triggered and handled by this function. You must now call Jabberdy.groupchat.join()
in order to actually join the XMPP groupchat room that Sail events are broadcast on.
The connected
event handler is also a good place to take care of enabling functionality that should be available only after the user is connected. In this case we trigger the choosingWhetherToWatchOrPlay
event to signal to the app that the user is now choosing how they want to participate in the Jabberdy game.
The events.sail
hash maps Sail events to local Javascript events.
events: {
// mapping of Sail events to local Javascript events
sail: {
'guess': function(sev) {...},
'set_definition': function(sev) {...},
'wrong': function(sev) {...},
'bad_word': function(sev) {...},
'win': function(sev) {...}
},
/* .. */
}
An important distinction to note here that there are two different notions of "event" in sail.js:
- There are the regular javascript events (the familiar built-in ones like
click
,keypress
, etc. as well as the custom ones for our app likejoined
,authenticated
, etc.). These events occur locally in the client/browser. - There are also "Sail Events", which are basically just XMPP messages with a certain format. These events come from other networked clients and agents.
In the jabberdy code, the variable name sev
is used for "Sail Event" to distinguish from regular Javascript events (ev
).