(Note: Due to a problem with my post-renderer, I have to add a "\" character to escape bracketed variables like {{message}} when they appear in code. )

Meteor is a javascript framework that just launched. Its screencasts demonstrate effortless real-time applications and I don't possess the technical understanding to explain it more technically that using adjectives like "real-time". But it's X-treme and the allure of it being so new and so instantly popular encouraged me to give it a go.

The Hacker News submission announcing its launch has over 1,100 votes at the moment.

Intoxicated with opportunistic greed, this may be my only chance to write something someone will ever read.

Leaderboard demo

I'll be adding some functionality to the Leaderboard demo that comes with the application:

You can install Meteor and generate the Leaderboard demo with these commands:

$ curl install.meteor.com | sh
$ meteor create --example leaderboard
$ cd leaderboard
$ meteor 

Adding a New Player form

I'd like a simple form for adding new players.

the

First, I made a new template with a text field and a button.

<!-- leaderboard.html -->
<template name="new_player">
  <div class="new_player">
    <input id="new_player_name" type="text" />
    <input type="button" class="add" value="Add Player" />
  </div>
</template>

Note: due to a current bug that they're working on, the div wrapper is necessary because event selectors don't work on top-level elements. I punched so many pillows trying to understand why my click events wouldn't work.

I'll add a line to the leaderboard template to nest it at the bottom of the page:

<!-- leaderboard.html -->
<template name="leaderboard">
  ...
  {{> new_player }\}
</template>

Now I'll add a click event for the Add Player button.

// leaderboard.js
Template.new_player.events = {
  'click input.add': function () {
    var new_player_name = document.getElementById("new_player_name").value;
    Players.insert({name: new_player_name, score: 0});
  }
};

Notice that the event is scoped to the template name I just made.

When the Add Player button is clicked, I grab the value of the #new_player_name input field and then insert it into the Players collection with a starting score of zero.

You'll see that it's declared at the top of the javascript file:

// leaderboard.js
Players = new Meteor.Collection("players");

As players are added to the collection, Meteor automatically updates the template to reflect the changes.

Reactivity

Meteor handles automatic recomputation for you and refers to it as Reactivity.

Meteor docs: Reactivity - Quick explanation

But the main thing to understand is this:

These Meteor functions run your code in a reactive context: >

  • Meteor.ui.render and Meteor.ui.chunk
  • Meteor.autosubscribe
  • Templates

And the reactive data sources that can trigger changes are: >

  • Session variables
  • Database queries on Collections
  • Meteor.status

In other words:

When I introduce validation, I'll be using a Session variable to trigger error message updates.

Buttons to remove players

Here's what it'll look like:

delete buttons next to player names

To make the button, I added one line to the player template that's looped to create each row of the player list:

<!-- leaderboard.html -->
<template name="player">
  <div class="player {{selected}\}">
    <input type="button" value="X" class="delete" /> <!-- here it is -->
    <span class="name">{{name}\}</span>
    <span class="score">{{score}\}</span>
  </div>
</template>

And now I just need to add a click event to remove the player from the collection:

// leaderboard.js
Template.player.events = {
  'click input.delete': function () { // <-- here it is
    Players.remove(this._id);
  },
  'click': function () {
    Session.set("selected_player", this._id);
  }
};

New player name validation

The main criticism of Meteor involves its complete lack of security. The full database API is accessible from the client's console. However, this is indeed just a preview of Meteor and the creators have explained that such an obvious necessity hasn't eluded them.

I'll create two validations for any new name:

  1. Can't be blank
  2. Can't already exist in the collection

An error message will show up above the creation field if validation fails:

a a

Let's add an error message condition to our new player template:

<!-- leaderboard.html -->
<template name="new_player">
  {{#if error}\}
    <span id="error" style="color: red;">
      {{error}\} 
    </span>
  {{/if}\}
  <div class="new_player">
    <input id="new_player_name" type="text" />
    <input type="button" class="add" value="Add Player" />
  </div>
</template>

If that didn't make sense, it will when I post the code.

I chose to use a Session variable I named error. To demonstrate:

>>> Session.keys
Object { }

>>> Session.set("error", "Name can't be blank");
>>> Session.keys
Object { error="Name can't be blank" }

>>> Session.get("error");
"Name can't be blank"

>>> Session.set("error", undefined)
>>> Session.keys
Object { }

That's pretty much it, but here's the Session API doc.

Let's address the aforementioned {{error}} issue and expose a matching Template variable:

// leaderboard.js
Template.new_player.error = function () {
  return Session.get("error");
};

{{error}} in the template html expects an error() function and we'll just return the Session variable. If it's undefined, the {{#if error}} check fails.

Using a Validation object

After a brief Google tour of javascript syntax, I decided to use an object literal to organize our validation logic.

Here's the primitive API I came up with:

I clearly didn't spend much time on the design, but it does the job and helps me sleep at night knowing that my growing armada of helper functions are at least sitting behind an arbitrary object variable.

Here's the Validation object in full ensemble:

// leaderboard.js
Validation = {
  clear: function () { 
    return Session.set("error", undefined); 
  },
  set_error: function (message) {
    return Session.set("error", message);
  },
  valid_name: function (name) {
    this.clear();
    if (name.length == 0) {
      this.set_error("Name can't be blank");
      return false;
    } else if (this.player_exists(name)) {
      this.set_error("Player already exists");
      return false;
    } else {
      return true;
    }
  },
  player_exists: function(name) {
    return Players.findOne({name: name});
  }
};

Now we can add a quick validation check on our existing Add Player click event before adding a new player to the collection.

// leaderboard.js
Template.new_player.events = {
  'click input.add': function () {
    // notice the added trim()
    var new_player_name = document.getElementById("new_player_name").value.trim();

    // here's the valid_name check
    if (Validation.valid_name(new_player_name)) {
      Players.insert({name: new_player_name, score: 0});
    } 
  }
};

Note that the new player name now gets a trim() before validation so a string of spaces remains invalid.

That's it

I'm pretty sure that's everything.

I'll update this post as I add more features and discover better programming/javascript practices.

I have yet to add comments to my blog, but I'd love feedback (especially regarding the javascript!). For now, please email me at danrodneu@gmail.com.