Skip to content
Wyatt Allen edited this page Apr 14, 2014 · 14 revisions

Getting Started

First, make sure you have git and npm installed. You can download the repository by issuing the following commands.

git clone git@github.com:whatgoodisaroad/cablejs.git
cd cablejs

Next, setup the dependencies for examples with these commands. The examples depend on client-side libraries such as jQuery and d3.js, and bower will prepare all of them so that the examples can work.

npm install
cd examples
bower install

Now that cable is downloaded an ready, we can create an HTML document that loads the cable script.

<html>
  <head>
    <title>Cable</title>
  </head>
  <body>
    <script src="path/to/cable.min.js"></script>
    <script>
      Cable.define({
        //  Node definitions go here!
      });
    </script>
  </body>
</html>

As you can see, this document loads up cable followed by a script which executes a function named Cable.define with an empty object as argument. What Cable.define does is allow us to create nodes in the cable "graph", in other words, the functionality of our app will be built up just by adding properties in that empty object.

Basic Input and Output

Let's make a really simple app that takes input in a textbox, and presents a message based on that input. First, we add <input> and <span> elements to the body.

<input id="input" type="text" value="" />
<span id="output"></span>

Now, we need to create a node in the cable graph to represent the input. This node will represent the stream of data corresponding to the value in the textbox. In cable, this is technically an event node, because it represents the event of there being a new value in the textbox.

We can create an event node by writing a property in the definition object as a function which accepts one argument named event. What an event function must do is configure the DOM or some event system such that it invokes the event argument with the new value. So, for a textbox, this means binding to the onchange and onkeyup events with a function that invokes the event argument with the value property of the textbox.

input:function(event) {
  var 
    e = document.getElementById("input"),
    f = function() { event(e.value); };

  event.setDefault(e.value);

  e.onchange = f;
  e.onkeyup = f;
}

We have now incorporated the textbox into the cable graph as an input. But input isn't useful at all without a way to do output. Let's set it up so that it outputs the current value of the textbox in the <span>.

In cable, this will be an effect node. We can write it by making a function which accepts the name of some other node as argument to be used in producing the effect. In this case, we want to make use of the node named input to display its value, so we'll write a function with an argument named input.

Cable sets up the input argument to be a getter for the input value, so we can produce the output string by invoking it. This informs the cable system to execute this function every time the value. See below.

output:function(input) {
  document.getElementById("output").innerText = (
    "You typed '" + input() + "'"
  );
}

Putting it all together we have the following HTML file.

<html>
  <head>
    <title>Cable</title>
  </head>
  <body>
    <input id="input" type="text" value="" />
    <span id="output"></span>
    <script src="path/to/cable.min.js"></script>
    <script>
      Cable.define({
        input:function(event) {
          var 
            e = document.getElementById("input"),
            f = function() { event(e.value); };
          event.setDefault(e.value);
          e.onchange = f;
          e.onkeyup = f;
        },
        output:function(input) {
          document.getElementById("output").innerText = (
            "You typed '" + input() + "'"
          );
        }
      });
    </script>
  </body>
</html>

Now, if I load this page and type "oranges" into the input, the span says:

You typed 'oranges'

This file is a working cable app. It will display whatever you type in the textbox and update the span as the input changes. However, it's a bit boring to display the value in the textbox without changing it. Let's do something more creative by creating a reversed version of the string.

First, think about reversing the string. The reversed version of the string isn't an event and it also isn't really an effect. Instead, it's something that exists between input and output. which, in cable is called a synthetic node because it synthesizes other values into a new one.

In this case, we'll be synthesizing the input into the reversed input. This is done by creating a function that accepts an argument named input (so we can use the value of the textbox), but also accepts an argument named result. We can pass a value into result to represent the synthesized data.

reversed:function(input, result) {
  result(input().split("").reverse().join(""));
}

Reversed now acts like a stream of data representing the reversed version of the stream of input data.

Now that this is defined, we can display this additional information by rewriting the output node to use reversed in a similar way to how input is used.

output:function(input, reversed) {
  document.getElementById("output").innerText = (
    "You typed '" + input() + "' the reversed is '" + reversed() + "'"
  );
}

Now, if I type "apples" into the input, the span says:

You typed 'apples' the reversed is 'sellpa'

With these streams, we can define an even more abstract synthetic which detects a whether the input is a palindrome.

isPalindrome:function(input, reversed, result) {
  result(input() === reversed());
}

And incorporate this into the result.

output:function(input, isPalindrome) {
  document.getElementById("output").innerText = (
    "'" + input() + "' is " + 
    (isPalindrome() ? "" : "not ") +
    "a palindrome"
  );
}

It's useful that isPalindrome is somewhat abstract. It's a stream of data which represents a relationship between two other streams of data. This is a powerful tool because you can write a synthetic node to represent all sorts of abstract aspects of your application, and nodes which use such nodes are updated automatically. These qualities could be things like isUserLoggedIn, isGameOver, or canZoomInFurther.

Simpler Input and Output with jQuery

Declaring the input and output as we've done in the previous section is conceptually simple, but syntactically verbose. If we add jQuery to our application, cable provides some helpers which make declaring them more convenient.

First, let's add jQuery to cable by declaring it as a library, then, declare the input and output with cable helper functions. These new versions of input and output work in the same way as before.

Cable.define({
  $:Cable.library("path/to/jquery.js"),

  input:Cable.textbox("#input"),
  output:Cable.template(
    "#output", 
    "You typed '{{input}}' and the reversed is {{reversed}}"
  )
});

Building Game

Now that we have a grasp on how to define cable nodes, let's build a more complex application, specifically a clone of the popular JavaScript game 2048. The final result of this tutorial can be found in the examples/2048 directory of the repository.

For the actual rules of the game, we'll use am implementation named 2048.js. This file has nothing to do with cable specifically, it's just a stand-alone, pure-functional implementation in normal JavaScript. The role of cable is a glue-layer to connect this implementation to the DOM.

2048.js provides the following functions:

  • blank(): Create a blank game state with no numbers.
  • initialize(): Create an initial state for the game (i.e. a blank state plus two numbers spawned).
  • move(state, direction): Accept a state and a direction (i.e. "north", "south", "east" or "west") and produce a new state created by applying that rule.
  • isGameOver(state): Accept a state and return a boolean of whether the game is over (i.e. none of the directions are possible).
  • isGameWon(state): Accept a state and return a boolean of whether the game has been won (i.e. at least one tile is the winning tile -- 2048).

Yep.

Clone this wiki locally