Skip to content

Jabberdy Walkthrough

montanaflynn edited this page Mar 14, 2013 · 4 revisions

Jabberdy Walkthrough

This page walks you through a functioning Sail.js-based app -- Jabberdy.

sail.js app directory layout

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.

downloading and running the app

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 cding 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 view - index.html

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:

loading js and css

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...

  1. the Sail.load function loads all of the necessary dependencies,
  2. your app's code is loaded (js/yourappname.js),
  3. and finally, we initialize the app via Sail.init(YourAppName)

ui elements

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!

the controller - yourappname.js

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: {
     },

     /* .. */
   }
    
}

.init()

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.

event handlers

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.

events.connected()

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.

sail event mapping

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 like joined, 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).