Chapter 4. HTML5 Hypermedia

The only usefulness of a map or a language depends on the similarity of structure between the empirical world and the map-languages.

- Alfred Korzybski

This last hands-on design chapter covers the techniques involved when using HTML5 as the base media type for your design. Since HTML already has a rich set of hypermedia controls (A, LINK, FORM, INPUT, etc.), the process of designing hypermedia types with HTML focuses on expressing the domain semantics within the existing elements and attributes of HTML.

The scenario here involves expressing common microblogging semantics (Twitter, Identi.ca, etc.) in a hypermedia type. After capturing the semantics and completing the design, a simple server will be built that emits the hypermedia responses along with two distinct client implementations. One client relies on only the HTML responses (no JavaScript support), and the other takes advantage of the Ajax style to build a web bot that knows enough about the hypermedia type to register as a new user (if needed) and begin posting into the data stream.

Scenario

For this example, a functional microblog hypermedia type is needed. This design should allow users to:

  • Create, update, and delete accounts

  • Add new messages and share existing messages

  • Create and remove relationships to other users

  • Search for existing users and messages

Since the design will be based on HTML5 (an existing hypermedia type), the implementation should provide basic functionality without the need for client-side scripting (e.g. JavaScript). However, the design should also allow scripted clients (e.g. Ajax-style browser clients) to implement a rich user interface, too. As in the previous examples, data will be stored using CouchDB, the server will be implemented using Node.js, and the client applications will be implemented using HTML5 and JavaScript.

Designing the Microblog Media Type

The aim of this example is to illustrate the process of designing hypermedia types using HTML5 as the base format. Unlike the previous examples, which were based on XML and JSON (both formats devoid of native hypermedia controls), this example starts with a format that already supports basic hypermedia factors.

Starting with HTML5 provides both advantages and drawbacks when designing a hypermedia solution. The good news is the details of document design (which elements are valid, etc.), the specific hypermedia controls, and the method of expressing state transitions are already decided. This means designers do not need to worry about how to define and document much of the hypermedia type. However, with so many details already decided, there is less room for expressing the problem domain in ways clients can easily understand.

This duality of well-defined hypermedia controls and a limit on the options for expressing the problem domain is the key challenge behind working with existing hypermedia types and is, in part, the reason so few new APIs use HTML5 as the base. For many, it seems easier to start from zero using a non-hypermedia type such as XML or JSON and create all of the details from the beginning. But sometimes this option is not available to designers. Instead, designers often must be sure their implementations work well with existing clients (e.g. web browsers) with little or no support from custom scripting or other client-side plug-ins.

Expressing Application Domain Semantics in HTML5

In cases where the design needs to rely on existing hypermedia types like HTML5, a different approach is needed to express domain semantics. Instead of creating new elements (as in XML) or objects (as in JSON), domain semantics must be expressed using existing HTML5 elements (<article>, <section>, <p>, <span>, etc.). Instead of creating new state transition and process flow elements, designers need to use those already available in HTML5 (<form>, <input>, <a>, etc.).

However, it is still important to know the semantic value (the meaning in relation to the problem domain) of each of these already-defined elements. Instead of creating an <email> element or <update-user> transition block, designers need to use existing features of HTML5 to add semantic meaning to the representations. The way to do this is to use key HTML5 attributes to decorate the existing elements. These attributes are:

id

Used to identify a unique data element/block within the representation

name

Used to identify a state transition element (i.e. input) within the representation

class

Used to identify a non-unique data element/block within the representation

rel

Used to identify a non-unique process flow element within the representation

The id attribute can be applied to any element in the document, is a single string value (no spaces allowed), and must be unique in a document:

<span id="invoice-001">...</span>

The name attribute is also a single string value (without spaces). It can be assigned to a limited set of elements (those associated with input), and multiple elements within the same document can share the same value:

<label>Enter first invioce:</label><input name="invoice">...</input>
<label>Enter second invoice:</label><input name="invoice">...</input>

The class attribute can be applied to any element in the document, can appear on several elements within the document, and allows multiple values to be applied as long as they are separated by a space. This makes it possible to mark several document elements with the same value as well as mark the same element with several values:

<span class="important invoice overdue">...</span>
<span class="invoice pending">...</span>

The rel attribute has features of both the class and name attributes. Like the name attribute, a rel can only appear on a limited set of elements (those associated with links) and need not be unique within the document. Also, like the class attribute, the rel supports multiple strings separated by spaces:

<a href="..." rel="invoice">...</a>
<a href="..." rel="invoice overdue">...</a>

Through the proper application of these four attributes to a wide range of existing HTML5 elements, it is possible to adequately express any problem domain details within a hypermedia type design. It is worth noting that each of the above attributes has slightly different rules in HTML5.

Identifying the State Transitions

Just as in other base formats, a key step in implementing a hypermedia design using HTML5 is the process of identifying the needed state transitions. As has already been stated above, this example implements the basic features of a microblogging application. Below is a set of states that need to be represented:

  • The list of users

  • A single user

  • The list of messages

  • A single message

  • The list of possible queries (search users, view the user’s list of followers, etc.)

  • A template for creating a new user

  • A template for updating an existing user

  • A template for following an existing user

  • A template for searching for existing users

  • A template for adding a new message

  • A template for replying to an existing message

  • A template for searching for existing messages

As may be evident to the reader at this point, the above list identifies three unique block types within a representation (users, messages, queries) and seven transition blocks (create user, update user, follow user, search user, add message, reply to a message, and search messages). The unique blocks can be expressed using HTML5’s <div> element and the transition blocks can be expressed using the <form> element. Each of these blocks will have a number of child elements.

Note

The usual error representation has been left out of this design to save time and reduce repetition of the same material. When creating a complete design for production use, you should always include an error representation.

The details of these element blocks can easily be expressed in short HTML5 examples. These examples will serve as reference material when creating the application profile later in this chapter (see The Microblog Application Profile).

State blocks

This example identifies three main types of content that may appear within a representation: users, messages, and queries. These can be expressed as HTML5 <div> elements with unordered lists (<ul>, <li>) and other child elements. Below are examples of each of the three main content blocks.

Users

There are two ways to represent users within this design: as a list of users and as a single user.

The list of users is represented using an HTML5 unordered list:

<!-- representing a list of users -->
<div id="users">
  <ul class="all">
    <li>
      <span class="user-text">User1</span>
      <a rel="user" href="..." title="profile for User1">profile</a>
      <a rel="messages" href="..." title="messages by User1">messages</a>
    </li>
    ...
    <li>
      <span class="user-text">UserN</span>
      <a rel="user" href="..." title="profile for UserN">profile</a>
      <a rel="messages" href="..." title="messages by UserN">messages</a>
    </li>
  </ul>
</div>

A single user is represented in a similar way:

<!-- representing a single user -->
<div id="users">
  <ul class="single">
    <li>
      <img class="user-image" src="..." alt="image of User1"/>
      <a rel="user" href="..." title="profile for User1">
        <span class="user-text">User One</span>
      </a>
      <span class="description">
        I am known to all as User One
      </span>
      <a rel="website" href="..." title="website">
        User1's web site
      </a>
      <a rel="messages" href="..." title="messages by User1">messages</a>
    </li>
  </ul>
</div>
Messages

Messages can also be represented in two ways (as a list and as a single message).

Here is the list representation:

<!-- representing a list of messages -->
<div id="messages">
  <ul class="all">
      <li>
        <span class="message-text">
          this is a message
        </span>
        @
        <a rel="message" href="..." title="message">
          <span class="date-time">
            2011-08-17 04:04:09
          </span>
        </a>
        by
        <a rel="user" href="..." title="User1">
          <span class="user-text">User1</span>
        </a>      
      </li>
      ...
      <li>
        <span class="message-text">
          this is also a message
        </span>
        @
        <a rel="message" href="..." title="message">
          <span class="date-time">
            2011-08-17 04:10:13
          </span>
        </a>
        by
        <a rel="user" href="..." title="UserN">
          <span class="user-text">UserN</span>
        </a>      
      </li>
  </ul>
</div>

And here is the way a single message is represented:

<!-- representing a single message -->
<div id="messages">
  <ul class="single">
    <li>
      <span class="message-text">
        This is a message
      </span>
      <span class="single">@</span>
      <a rel="message" href="..." title="message">
        <span class="date-time">
          2011-08-17 04:04:09
        </span>
      </a>
      <span class="single">by</span>
      <a rel="user" href="..." title="User1">
        <span class="user-text">User1</span>
      </a>
    </li>
  </ul>
</div>
Queries

This design uses a small set of simple queries. These are just links that return the list of messages, users, or the registration page:

<!-- representing the queries list -->
<div id="queries">
  <ul>
    <li><a rel="messages-all index" href="..." title="Home page">Home</a></li>
    <li><a rel="users-all" href="..." title="User List">Users</a></li>
    <li><a rel="register" href="..." title="Register">Register</a></li>
  </ul>
</div>

Note that the first link in the list has two values for the rel attribute.

Transfer blocks

This example identifies seven client-initiated state transfers. Each of them can be expressed as HTML5 <form> elements with <input> child elements. Below are each of the transfer blocks along with the details for each child element.

Create new user

The server will include this block in the representation to allow clients to create a new microblog user:

<!-- state transfer for adding a new user -->
<form method="post" action="..." class="user-add">
  <input type="text" name="name" value="" required="true"/>
  <input type="text" name="email" value="" required="true"/>
  <input type="password" name="password" value="" required="true" />
  <textarea name="description"></textarea>
  <input type="file" name="avatar" value="" />
  <input type="text" name="website" value="" />
  
  <input type="submit" value="Send" />
</form>

Notice that some fields are grouped under the SHOULD keyword and others are under the MAY keyword. These are just comments to indicate which fields “should” be returned by the server and which ones “may” be returned by the server. These annotations will be used later when writing up the documentation.

Update existing user

Once a user has been created, it should be possible to update that user account:

<!-- state transfer for updating an existing user -->
<form method="post" action="..." class="user-update">
  <input type="text" name="name" value="" required="true"/>
  <input type="text" name="email" value="" required="true"/>
  <input type="password" name="password" value="" required="true"/>
  <textarea name="description"></textarea>
  <input type="file" name="avatar" value="" />
  <input type="text" name="website" value="" />
  
  <input type="submit" value="Send" />
</form>

It is likely that update transitions will be prepopulated with existing data. It is also possible that one or more of the <input /> elements will be marked as read-only by the server. These details will be left up to each server implementation to determine.

Follow a user

Following a user is as simple as sending the identifier of the user you wish to follow:

<!-- state transition to follow an existing user -->
<form method="post" action="..." class="user-follow">
  <input type="text" name="user" value="" required="true"/>
  
  <input type="submit" value="Send" />
</form>

You may notice that there is nothing in the above transition to indicate the current user that wants to follow the identity in the user input element. For all of the transition examples shown here, the server is assumed to be able to know (when it is important) the identity of the current (or logged-in) user. This can be done via information in the action URI or via control data (HTTP Headers) such as the Authorization header or the Cookie header (see Current user and state data for details).

Search for users

Here is the transition block for searching for users:

<!-- state transfer for searching users -->
<form method="get" action="..." class="user-search">
  <input type="text" name="search" value="..." required="true"/>
  
  <input type="submit" value="Send" />
</form>

Since this is a search action, the method attribute is set to GET, not POST.

Add a new message

Adding a new message is also a simple state transfer:

<!-- state transfer for adding a message -->
<form method="post" action="..." class="message-post">
  <textarea name="message" requried="true"></textarea>
  
  <input type="submit" value="Send" />
</form>
Reply to an existing message

Replying to an existing message involves sending both the original user identifier and any reply text, which may include the original message text, to the server:

<!-- state transfer for replying to a message -->
<form method="post" action="..." class="message-reply">
  <input type="hidden" name="user" value="..." requried="true"/>
  <textarea name="message"></textarea>
  
  <input type="submit" value="Send" />
</form>
Search for messages

The message search transfer block is almost identical to the one used for user searches:

<!-- state transfer for searching messages -->
<form method="get" action="..." class="message-search">
  <input type="text" name="search" value="..." requried="true"/>
  
  <input type="submit" value="Send" />
</form>

Selecting the Basic Design Elements

Since the point of this chapter is to cover implementation details when using HTML5, the remaining standard design elements have already been decided. However, it is still valuable to go over each of them here.

Using HTML5 as the base format means that the State Transfer style will be ad hoc. HTML5 Forms will be used to make all client-imitated transfers via GET (read-only) or POST (add/update). Even though the ad hoc style is used in HTML5, designers can still establish required and optional inputs in their hypermedia design. In this example, the design uses both.

The domain style of HTML5 is agnostic. The element and attribute names are all independent of any domain semantics. However, as mentioned earlier in this chapter (see Expressing Application Domain Semantics in HTML5), HTML5 supports domain semantic expression using common attribute values (id, name, class). Finally, the Application Flow style for HTML5 is applied via values for the rel attribute on href tags.

Note

Usually hypermedia designs use the rel attribute (or its equivalent in the data format) to mark all state transitions, including ones that require arguments. However, HTML5 does not support the rel attribute on the form element. For this reason, transitions that require arguments (e.g. forms) will be marked with a class attribute instead.

Although HTML5 is a domain-agnostic base format, the built-in state transfer and application flow elements make most of the design details very easy. The only creative work that needs to be done is arranging existing HTML5 elements (article, section, div, p, span, etc.) and decorating them with the necessary attributes (id, name, class, rel).

The application of these attribute decorations is covered in the next section.

The Microblog Application Profile

Since HTML5 is a domain-agnostic media type, all domain-specific information (both the data elements and the transition details) needs to be specified as additional information in each representation. As has already been pointed out earlier in this chapter (see Expressing Application Domain Semantics in HTML5), in HTML5 you can express domain-specific information using a set of attributes (id, name, class, and rel).

Also, this implementation will require users to log in before posting new messages. That means the implementation will need to be able to identify the current logged-in user.

Current user and state data

This example implementation will rely on HTTP’s Authorization header to identify the currently logged-in user. That means this example server will use HTTP Basic Authentication for selected state transitions (Update a User, Follow a User, Add a New Message, and Reply to a Message). It is important to point out that user identification is not specified in the media type design; it is an implementation detail left to servers and clients to work out themselves.

The decision to leave user authentication independent of the media type has a number of advantages. First, this allows clients and servers to negotiate for an appropriate authentication scheme at runtime (the HTTP WWW-Authenticate head is used to advertise supported authentication schemes). Second, leaving it out of the media type means that servers are free to establish and transition details on their own. For example, Open Auth (OAuth) has a set of requirements for interacting with more than one web server in order to complete authentication. Finally, leaving authentication details out of the media type design allows servers to take advantage of whatever means may become available in the future.

ID attribute values

This design relies on three unique identifiers for representations:

messages

Applied to a div tag. The list of messages in this representation. This list may contain only one message.

queries

Applied to a div tag. The list of valid queries in this representation. This is a list of simple queries (represented by the HTML anchor tag).

users

Applied to a div tag. The list of users in this representation. This list may contain only one user.

Class attribute values

There are a number of class attributes that can appear within a representation. Clients should be prepared to recognize the following values:

all

Applied to a UL tag. A list representation. When this element is a descendant of DIV.id="messages" it MAY have one or more LI.class="message" descendant elements. When this element is a descendant of DIV.id="users" it MAY have one or more LI.class="user" descendant elements.

date-time

Applied to a SPAN tag. Contains the UTC date-time the message was posted. When present, it SHOULD be valid per RFC3339.

description

Applied to a SPAN tag. Contains the text description of a user.

friends

Applied to a UL tag. A list representation. When this element is a descendant of DIV.id="messages" it contains the list of messages posted by the designated user’s friends and MAY have one or more LI.class="message" descendant elements. When this element is a descendant of DIV.id="users" it contains the list of users who are the friends of the designated user and MAY have one or more LI.class="user" descendant elements.

followers

Applied to a UL tag. A list representation of all the users from the designated user’s friends list. MAY have one or more LI.class="user" descendant elements.

me

Applied to a UL tag. When this element is a descendant of DIV.id="messages" it contains the list of messages posted by the designated user and MAY have one or more LI.class="message" descendant elements. When this element is a descendant of DIV.id="users" it SHOULD contain a single descendant LI.class="user" with the designated user’s profile.

mentions

Applied to a UL tag. A list representation of all the messages that mention the designated user. It MAY contain one or more LI.class="message" descendant elements.

message

Applied to an LI tag. A representation of a single message. It SHOULD contain the following descendant elements:

SPAN.class="user-text"
A.rel="user"
SPAN.class="message-text"
A.rel="message"

It MAY also contain the following descendant elements:

IMG.class="user-image"
SPAN.class="date-time"
message-post

Applied to a FORM tag. A link template to add a new message to the system by the designated (logged-in) user. The element MUST be set to FORM.method="post" and SHOULD contain a descendant element:

TEXTAREA.name="message"
message-reply

Applied to a FORM tag. A link template to reply to an existing message. The element MUST be set to FORM.method="post" and SHOULD contain the following descendant elements:

INPUT[hidden].name="user" (the author of the original post)
TEXTAREA.name="message"
single

When this element is a descendant of DIV.id="messages" it contains the message selected via a message link. SHOULD have a single LI.class="message" descendant element. When this element is a descendant of DIV.id="users" it contains the user selected via a user link. SHOULD have a single LI.class="user" descendant element.

messages-search

Applied to a FORM tag. A link template to search of all the messages. The element MUST be set to FORM.method="get" and SHOULD contain the following descendant elements:

INPUT[text].name="search"
message-text

Applied to a SPAN tag. The text of a message posted by a user.

search

Applied to a UL tag. A list representation. When this element is a descendant of DIV.id="messages" it contains a list of messages and MAY have one or more LI.class="message" descendant elements. When this element is a descendant of DIV.id="users" it contains a list of users and MAY have one or more LI.class="user" descendant elements.

shares

Applied to a UL tag. A list representation of all the messages posted by the designated user that were shared by other users. It MAY contain one or more LI.class="message" descendant elements.

user

Applied to an LI tag. A representation of a single user. It SHOULD contain the following descendant elements:

SPAN.class="user-text"
A.rel="user"
A.rel="messages"

It MAY also contain the following descendant elements:

SPAN.class="description"
IMG.class="avatar"
A.rel="website"
user-add

Applied to a FORM tag. A link template to create a new user profile. The element MUST be set to FORM.method="post" and SHOULD contain the following descendant elements:

INPUT[text].name="user"
INPUT[text].name="email"
INPUT[password].name="password"

It MAY also contain the following descendant elements:

TEXTAREA.name="description"
INPUT[file].name="avatar"
INPUT[text].name="website"
user-follow

Applied to a FORM tag. A link template to add a user to the designated user’s friend list. The element MUST be set to FORM.method="post" and SHOULD contain the descendant element:

INPUT[text].name="user"
user-image

Applied to an IMG tag. A reference to an image of the designated user.

user-text

Applied to a SPAN tag. The user nickname text.

user-update

Applied to a FORM tag. A link template to update the designated user’s profile. The element MUST be set to FORM.method="post" and SHOULD contain the following descendant elements:

INPUT[hidden].name="user"
INPUT[hidden].name="email"
INPUT[password].name="password"

It MAY also contain the following descendant elements:

TEXTAREA.name="description"
INPUT[file].name="avatar"
INPUT[text].name="website"
users-search

Applied to a FORM tag. A link template to search of all the users. The element MUST be set to FORM.method="get" and SHOULD contain the descendant element:

INPUT[text].name="search"

Name attributes values

HTML5 uses the name attribute to identify a representation element that will be used to supply data to the server during a state transition. Clients should be prepared to supply values for the following state transition elements:

description

Applied to a TEXTAREA element. The description of the user.

email

Applied to an INPUT[text] or INPUT[hidden] element. The email address of a user. When supplied, it SHOULD be valid per RFC5322.

message

Applied to a TEXTAREA element. The message to post (for the designated user).

name

Applied to an INPUT[text] element. The (full) name of a user.

password

Applied to an INPUT[password] element. The password of the user login.

search

Applied to an INPUT[text]. The search value to use when searching messages (when applied to FORM.class="message-search") or when searching users (when applied to FORM.class="users-search").

user

Applied to an INPUT[text] or INPUT[hidden] element. The public nickname of a user.

avatar

Applied to an INPUT[file] element. The image for the user.

website

Applied to an INPUT[text]. The URL of a website associated with the user profile. When supplied, it SHOULD be valid per RFC 3986.

Rel attribute values

This design also identifies a number of possible simple state transitions, or static links, that may appear within representations. These will appear as HTML anchor tags with the following rel attribute values:

index

Applied to an A tag. A reference to the starting URI for the application.

message

Applied to an A tag. A reference to a message representation.

message-post

Applied to an A tag. A reference to the message-post FORM.

message-reply

Applied to an A tag. A reference to the message-reply FORM.

message-share

Applied to an A tag. A reference to the message-share FORM.

messages-all

Applied to an A tag. A reference to a list representation of all the messages in the system.

messages-search

Applied to an A tag. A reference to the messages-search FORM.

user

Applied to an A tag. A reference to a user representation.

user-add

Applied to an A tag. A reference to the user-add FORM.

user-follow

Applied to an A tag. A reference to the user-follow FORM.

user-update

Applied to an A tag. A reference to the user-update FORM.

users-all

Applied to an A tag. A reference to a list representation of all the users in the system.

users-friends

Applied to an A tag. A reference to list representation of the designated user’s friend users.

users-followers

Applied to an A tag. A reference to list representation of the users who follow the designated user.

users-search

Applied to an A tag. A reference to the users-search FORM.

website

Applied to an A tag. A reference to the website associated with a user.

Sample Data

The test data for this example design can be represented using three different documents in CouchDB: the User, Message, and Follows documents. This implementation also relies on a CouchDB design document that includes a handful of views and a validation routine for writing documents into the data store.

User Documents

For this implementation, users will be represented in the data store as follows:

{
  "_id" : "mamund",
  "type" : "user",
  "name" : "Mike Amundsen",
  "email" : "mamund@yahoo.com",
  "password" : "p@ssW0rd",
  "description" : "learnin hypermedia",
  "imageUrl" : "http://amundsen.com/images/mca-photos/mca-icon-b.jpg",
  "websiteUrl" : "http://amundsen.com",
  "dateCreated" : "2011-06-21"
}

Message Documents

Each message document in the data store looks like this:

{
  "type" : "post",
  "text" : "My first message!",
  "user" : "mamund",
  "dateCreated" : "2011-06-29"
}

Follow Documents

The system will also track which users follow each other using a Follow document:

{
  "type" : "follow",
  "user" : "mamund",
  "follows" : "lee"
}

Design Document

The predefined views and validation routines for the CouchDB data for this implementation are:

{
  "_id" : "_design/microblog",
  
  "views" : {
    
    "users_search" : {
      "map" : "function(doc){
        if(doc._id && doc.type==='user') {
          emit(doc._id,doc);
        }
      }"
    },
    "users_by_id" : {
      "map" : "function(doc){
        if(doc._id && doc.type==='user') {
          emit(doc._id,doc);
        }
      }"
    },
    
    "posts_all" : {
      "map" : "function(doc) {
        if(doc._id && doc.type==='post') {
          emit(doc.dateCreated.split('-'),doc);
        }
      }"
    },
    "posts_by_id" : {
      "map" : "function(doc) {
        if(doc._id && doc.type==='post') {
          emit(doc._id,doc);
        }
      }"
    },
    "posts_by_user" : {
      "map" : "function(doc) {
        if(doc._id && doc.type==='post') {
          emit(doc.user, doc);
        }
      }"
    },
    "posts_search" : {
      "map" : "function(doc) {
        if(doc._id && doc.type==='post') {
          emit(doc.user, doc);
        }
      }"
    },
    "posts_by_user" : {
      "map" : "function(doc) {
        if(doc.user && doc.type==='post') {
          emit(doc.dateCreated.split('-').concat(doc.user),doc);
        }
      }"
    },
    
    "follows_user_is_following" : {
      "map" : "function(doc) {
        if(doc.user && doc.type==='follow') {
          emit(doc.user, {_id:doc.follows});
        }
      }"
    },
    "follows_is_following_user" : {
      "map" : "function(doc) {
        if(doc.follows && doc.type==='follow') {
          emit(doc.follows, {_id:doc.user});
        }
      }"
    }
  },

  "validate_doc_update": "function(newDoc, oldDoc, userCtx) {
  
    function require(field, message) {
      message = message || field + ' is required';
      if (!newDoc[field]) {
          throw({forbidden : message});
      }
    };
    
    function unchanged(field) {
      if(oldDoc && toJSON(oldDoc[field]) !== toJSON(newDoc[field])) {
        throw({forbidden : field + ' is read-only'});
      }
    };
    
    if(newDoc._deleted) {
      return true;
    }
    else {
      switch(newDoc.type) {
        case 'user':
          require('name');
          require('email');
          require('password');
          break;
        case 'post':
          require('text');
          require('user');
          require('dateCreated');
          break;
        case 'follow':
          require('user');
          require('follows');
          break;    
      }
    }    
  }"
}

The Server Code

With sample data in place, the next step is to implement server code to test the design. Since this example application has quite a few state transitions, the server-side code is a bit more involved than previous examples. However, an advantage of using HTML5 as the base media type is that it is rather easy to test since the server output will easily render within common web browsers.

Authenticating Users

This implementation relies on HTTP Basic Authentication to identify users. Below is the top-level routine to handle requesting the username and password and then comparing the results to information stored in a CouchDB User document:

/* validate user (from  db) via HTTP Basic Auth */
function validateUser(req, res, next) {

  var parts, auth, scheme, credentials; 
  var view, options;
  
  // handle auth stuff
  auth = req.headers["authorization"];
  if (!auth){
    return authRequired(res, 'Microblog');
  }  
  
  parts = auth.split(' ');
  scheme = parts[0]
  credentials = new Buffer(parts[1], 'base64').toString().split(':');
  
  if ('Basic' != scheme) {
    return badRequest(res);
  } 
  req.credentials = credentials;

  // ok, let's look this user up
  view = '/_design/microblog/_view/users_by_id';
  
  options = {};
  options.descending='true';
  options.key=String.fromCharCode(34)+req.credentials[0]+String.fromCharCode(34);;
  
  db.get(view, options, function(err, doc) {
    try {
      if(doc[0].value.password===req.credentials[1]) {
        next(req,res);
      }
      else {
        throw new Error('Invalid User');
      } 
    }
    catch (ex) {
      return authRequired(res, 'Microblog');
    }
  });
};

In the above routine, the code first checks for the presence of the Authorization header. If it does not exist, the server sends a response that asks the client to supply credentials before continuing (the authRequired method call). Once an Authorization header is supplied by the client, the code first ensures that it uses the Basic authentication scheme and then proceeds to parse the header into its two key parts (username:password). The first part (username) is used to perform a look up against the data store and, if a record is found, the second part (password) is compared against the data store in the User document. If there is a match, then the user has been authenticated and the code execution continues as usual (the next method call).

Registering a New User

Since this implementation supports adding new users, there is a view for rendering the state transition form and code to handle both returning the form and processing the posted data (using POST).

First, here is the code to return the input form:

/* get user register page */
app.get('/microblog/register/', function(req, res){

  res.header('content-type',contentType);
  res.render('register', {
    title: 'Register',
    site: baseUrl,
  });
});

And the view template to render that form:

<h2 id="page-title"><%= title %></h2>
<form class="user-add" action="<%=site%>users/" method="post">
  <fieldset>
    <h4>Account</h4>
    <p class="input">
      <label>Handle:</label>
      <input name="user" value="" 
        placeholder="coolhandle" required="true"/>
    </p>
    <p class="input">
      <label>Password:</label>
      <input name="password" type="password" value="" 
        placeholder="mys3cr3t" required="true" />
    </p>
  </fieldset>
  <fieldset>
    <h4>User Info</h4>
    <p class="input">
      <label>Email Address:</label>
      <input name="email" type="email" value="" 
        placeholder="user@example.com"  required="true"/>
    </p>
    <p class="input">
      <label>Full Name:</label>
      <input name="name" value="" placeholder="Jane Doe"/>
    </p>
    <p class="input">
      <label>Description:</label>
      <textarea name="description"></textarea>
    </p>
    <p class="input">
      <label>Avatar URL:</label>
      <input name="avatar" type="url" value="" 
        placeholder="http://example.com/images/my-avatar.jpg"/>
    </p>
    <p class="input">
      <label>Website URL:</label>
      <input name="website" type="url" value="" 
        placeholder="http://example.com/my-blog/"/>
    </p>
  </fieldset>
    <p class="buttons">
      <input type="submit" value="Submit" />
      <input type="reset" value="Reset" />
    </p>
</form>

In the above template, you can see that some fields are marked as required="true" and some have placeholder values included to give users a hint on how to fill out the various inputs.

Below is the server code that runs when users submit the state transition form:

/* post to user list page */
app.post('/microblog/users/', function(req, res) {

  var item,id; 

  id = req.body.user;
  if(id==='') {
    res.status=400;
    res.send('missing user');  
  }
  else {
    item = {};
    item.type='user';
    item.password = req.body.password;
    item.name = req.body.name;
    item.email = req.body.email;
    item.description = req.body.description
    item.imageUrl = req.body.avatar;
    item.websiteUrl = req.body.website;
    item.dateCreated = today();
    
    // write to DB
    db.save(req.body.user, item, function(err, doc) {
      if(err) {
        res.status=400;
        res.send(err);
      }
      else {
        res.redirect('/microblog/users/', 302);
      }
    });    
  }
});

Message Responses

There are two responses for representing messages: the message list and message details. There is also a state transition for adding a new message to the data store.

Below is the code that returns the message list:

/* starting page */
app.get('/microblog/', function(req, res){

  var view = '/_design/microblog/_view/posts_all';
  
  var options = {};
  options.descending = 'true';

  ctype = acceptsXml(req);
  
  db.get(view, options, function(err, doc) {
    res.header('content-type',ctype);
    res.render('index', {
      title: 'Home',
      site: baseUrl,
      items: doc
    });  
  });
});

And the view template that renders the message list:

<h2 id="page-title"><%= title %></h2>
<form class="message-post" action="<%=site%>messages/" method="post">
  <fieldset>
    <h4>What's Up?</h4>
    <textarea name="message" cols="50" rows="1" size="140" required="true"></textarea>
    <span class="message-buttons">
      <input type="submit" value="Submit" />
      <input type="reset" value="Reset" />
    </span>
  </fieldset>
</form>

<div id="messages">
  <ul class="all">
    <% for(i=0,x=items.length;i<x;i++) { %>
      <li>
        <span class="message-text">
          <%=items[i].value.text%>
        </span>
        @
        <a rel="message" href="<%=site%>messages/<%=items[i].value._id%>" title="message">
          <span class="date-time">
            <%=items[i].value.dateCreated%>
          </span>
        </a>
        by
        <a rel="user" href="<%=site%>users/<%=items[i].value.user%>" title="<%=items[i].value.user%>">
          <span class="user-text"><%=items[i].value.user%></span>
        </a>      
      </li>
    <% } %>
  </ul>
</div>

Note that this view template also includes the state transition block for adding a new message to the system (message-post). Below is the server code that handles the message-post state transition:

// add a message
app.post('/microblog/messages/', function(req, res) {
  
  validateUser(req, res, function(req,res) {
  
    var text;
    
    // get data array
    text = req.body.message;
    if(text!=='') {
      item = {};
      item.type='post';
      item.text = text;
      item.user = req.credentials[0];
      item.dateCreated = now();
      
      // write to DB
      db.save(item, function(err, doc) {
        if(err) {
          res.status=400;
          res.send(err);
        }
        else {
          res.redirect('/microblog/', 302);
        }
      });  
    }
    else {
      return badReqest(res);
    }
  });
});

Finally, here is the code to handle representing a single message. This can be reached by activating the rel="message" links in the message list:

/* single message page */
app.get('/microblog/messages/:i', function(req, res){

  var view, options, id;
  id = req.params.i;
  
  view = '/_design/microblog/_view/posts_by_id';
  options = {};
  options.descending='true';
  options.key=String.fromCharCode(34)+id+String.fromCharCode(34);
  
  db.get(view, options, function(err, doc) {
    res.header('content-type',contentType);
    res.render('message', {
      title: id,
      site: baseUrl,
      items: doc
    });  
  });
});

Below is the template for rendering a single message response:

<div class="message-block">
  <div id="messages">
    <ul class="single">
      <% for(i=0,x=items.length;i<x;i++) { %>
        <li>
          <span class="message-text">
            <%=items[i].value.text%>
          </span>
          <span class="single">@</span>
          <a rel="message" href="<%=site%>messages/<%=items[i].value._id%>" title="message">
            <span class="date-time">
              <%=items[i].value.dateCreated%>
            </span>
          </a>
          <span class="single">by</span>
          <a rel="user" href="<%=site%>users/<%=items[i].value.user%>" title="<%=items[i].value.user%>">
            <span class="user-text"><%=items[i].value.user%></span>
          </a>
        </li>
      <% } %>
    </ul>
  </div>
  &#160;
</div>

User Responses

This sample implementation has two representations for user responses: the user list and the user details. Below is the user list server code:

/* get user list page */
app.get('/microblog/users/', function(req, res){

  var view = '/_design/microblog/_view/users_by_id';
    
  db.get(view, function(err, doc) {
    res.header('content-type',contentType);
    res.render('users', {
      title: 'User List',
      site: baseUrl,
      items: doc
    });  
  });
});

And the user list view to match:

<h2 id="page-title"><%= title %></h2>
<div id="users">
  <ul class="all">
    <% for(i=0,x=items.length;i<x;i++) { %>
      <li>
        <span class="user-text"><%= items[i].value.name %></span>
        <a rel="user" href="<%=site%>users/<%=items[i].value._id%>" 
           title="profile for <%=items[i].value._id%>">profile</a>
        <a rel="messages" href="<%=site%>user-messages/<%=items[i].value._id%>" 
           title="messages by <%=items[i].value._id%>">messages</a>
      </li>
    <% } %>
  </ul>
</div>

There is also code to return a single user record:

/* single user profile page */
app.get('/microblog/users/:i', function(req, res){

  var view, options, id;
  id = req.params.i;
  
  view = '/_design/microblog/_view/users_by_id';
  options = {};
  options.descending='true';
  options.key=String.fromCharCode(34)+id+String.fromCharCode(34);
  
  db.get(view, options, function(err, doc) {
    res.header('content-type',contentType);
    res.render('user', {
      title: id,
      site: baseUrl,
      items: doc
    });  
  });
});

Along with the view template for rendering single user responses:

<h2 id="page-title"><%= title %></h2>
<div id="users">
  <ul class="single">
      <li>
        <% if(items[0].value.imageUrl) { %>
        <img class="avatar" src="<%=items[0].value.imageUrl%>" />
        <% } %>
        
        <a rel="user" href="<%=site%>users/<%=items[0].value._id%>" 
          title="profile for <%=items[0].value._id%>">
          <span class="user-text"><%= items[0].value.name %></span>
        </a>

        <% if(items[0].value.description) { %>
        <span class="description">
          <%=items[0].value.description%>
        </span>
        <% } %>
        
        <% if(items[0].value.websiteUrl) { %>
        <a rel="website" href="<%=items[0].value.websiteUrl%>" title="website">
          <%=items[0].value.websiteUrl%>
        </a>
        <% } %>
        
        <a rel="messages" href="<%=site%>user-messages/<%=items[0].value._id%>" 
           title="messages by <%=items[0].value._id%>">messages</a>
      </li>
  </ul>
</div>

Note that the single user template will optionally include the user’s profile image (user-image), description, and website URL if they have been supplied. You should also notice that this representation includes a link to see all of the messages created by this user (rel="messages”). The server code and view template for that response are:

/* user messages page */
app.get('/microblog/user-messages/:i', function(req, res){

  var view, options, id;
 
  id = req.params.i;
  
  view = '/_design/microblog/_view/posts_by_user';
  options = {};
  options.descending='true';
  options.key=String.fromCharCode(34)+id+String.fromCharCode(34);;
  
  db.get(view, options, function(err, doc) {
    res.header('content-type',contentType);
    res.render('user-messages', {
      title: id,
      site: baseUrl,
      items: doc
    });  
  });
});
<h2 id="page-title">Messages from <%=title%></h2>
<div class="user-message-block">
  <div id="messages">
    <ul class="search">
      <% for(i=0,x=items.length;i<x;i++) { %>
        <li>
          <span class="message-text">
            <%=items[i].value.text%>
          </span>
          <span class="single">@</span>
          <a rel="message" href="<%=site%>messages/<%=items[i].value._id%>" 
            title="message">
            <span class="date-time">
              <%=items[i].value.dateCreated%>
            </span>
          </a>
          <span class="single">by</span>
          <a rel="user" href="<%=site%>users/<%=items[i].value.user%>" 
            title="<%=items[i].value.user%>">
            <span class="user-text"><%=items[i].value.user%></span>
          </a>
        </li>
      <% } %>
    </ul>
  </div>
  &#160;
</div>

The Client Code

Once the server is implemented, it’s time to work through example clients. As was already mentioned, using HTML5 as the based media type means that the implementation will just run within common web browsers without any modification. However, plain HTML5 (without a Cascading Style Sheet, or CSS) is not very pleasing to the eye. It is a rather easy process to create a CSS stylesheet to spiff up plain HTML5 into a decent looking client. This results in a client that supports all of the required functionality without relying on any client-side scripting. This is sometimes called a Plain Old Semantic HTML or POSH client.

It is also possible to treat well-formed HTML5 as an XML document and render the responses using an Ajax-style user interface. This does, however, require that the HTML5 be rendered as valid XML. Lucky for our case, the view templates already meet this requirement.

The POSH Example

The basic HTML5 that is rendered by the server is fully functional, but a bit unappealing to view as can be seen in Figure 4-1.

Microblog Plain POSH Client Screenshot
Figure 4-1. Microblog Plain POSH Client Screenshot

However, with just a bit of CSS work, this view can be turned into a much more inviting user interface (see Figure 4-2).

Microblog CSS POSH Client Screenshot
Figure 4-2. Microblog CSS POSH Client Screenshot

The CSS file for this rendering includes rules for rendering the home page and message lists:

body {
  background-color: #FFFFCC;
  font-family: sans-serif;
}

div#queries {
  width:500px;
  float:right;
}

div#queries ul {
  margin:0;
  list-style-type:none;
}
div#queries ul li {
  float:left;
  padding-right: .4em;
}

div#messages ul {
  margin:0;
  list-style-type:none;
}
div#messages ul li {
  margin-top: .3em;
}

div#messages ul.single li span.message-text {
  font-size:large;
  font-weight:bold;
}

div.message-block {
  border: 1px solid black;
  padding:.5em;
  width:500px;
  margin:auto;
  -moz-border-radius: 25px; 
  border-radius: 25px; 
}

div#messages ul.single li a,
div#messages ul.single li span.single {
  float:left;
  margin-right: .3em;
}

ul.single {
  margin:0;
  list-style-type:none;
}

ul.single a,
ul.single span,
ul.single img
{
  display:block;
}

The CSS document also includes details for rendering state transition blocks (HTML forms):

form.message-post {
  width:600px;
}
form.message-post textarea {
  display:block;
  float:left;
} 
span.message-buttons {
  display:block;
  float:left;
}

fieldset {
  margin-top: 1em;
  -moz-border-radius: 25px; 
  border-radius: 25px; 
  background-color: #CCCC99;
}
fieldset h4 {
  margin:0;
}

form.user-add {
  width:300px;
}

p.input {
  margin-top:0;
  margin-bottom:0;
}
p.input label {
  display:block;
  width:150px;
}
p.input input,
p.input textarea {
  float:left;
  width:200px;
}

span.message-buttons input {
  background-color: #ffffcc;
  -moz-border-radius: 15px; 
  border-radius: 15px; 
}
p.buttons input {
  background-color: #cccc99;
  -moz-border-radius: 15px; 
  border-radius: 15px; 
}

Quite a bit more work could be done in this area, too. The CSS specification offers a wide range of options that make rendering POSH responses very easy and straightforward. The example here is given as just a starter for those who want to explore the area of user interface design via CSS.

The Ajax QuoteBot Example

In this example, a small Ajax client that knows enough about the microblogging hypermedia profile to interact with any server that supports this media type will be implemented. The bot shown here is able to determine if it needs to register as a new account on the server and then post quotes into the data stream at regular intervals. This example application shows that HTML can be successfully used as a machine-to-machine media type. It also provides guidance on one way to write stand-alone client applications for machine-to-machine scenarios.

Below is an example run of the QuoteBot Figure 4-3.

Microblog QuoteBot Screenshot
Figure 4-3. Microblog QuoteBot Screenshot

The QuoteBot scenario

For this example, the QuoteBot client will have the job of writing quotes to the microblogging server. If needed, this bot will also be able to register a new account on the target server. In order to accomplish these tasks, the QuoteBot will need to understand enough of the microblogging profile to perform a handful of low-level tasks such as loading the server’s home page, getting a list of users, finding and completing the user registration form, posting new messages, etc. Below is a list of these tasks along with notes on how the bot can use the microblogging profile specification to accomplish them:

  • Load an entry page on the server (the starting URI)

  • Get a list of registered users for this server (find the @rel="users-all" link)

  • See if the bot is already registered (find the @rel="user" link with the value of the bot’s username)

  • Load the user registration page (find the @rel="register" link)

  • Find the user registration form (locate the @class="user-add" form on the page)

  • Fill out the user registration form and submit it to the server (find the input fields outlined in the spec, populate them, and send the data to the server)

  • Load the message post page (@rel="message-post" link)

  • Find the message post form (locate the @class="message-post" form on the page)

  • Fill out the message post form and submit it to the server (find the input fields identified in the spec, populate, and send)

As the list above illustrates, the process of coding a machine-to-machine client for hypermedia interactions involves identifying two types of elements in response representations: links and forms. Clients need to be able to activate a link (i.e. follow the link) and fill in forms and activate them, too (i.e. submit the data to the server). Essentially, the client application needs to understand links and forms in general as well as the semantic details of the particular hypermedia profile.

QuoteBot HTML5

The HTML5 for the QuoteBot example is very minimal. All of the activity on the page is driven by the associated JavaScript. Below is the complete HTML5 markup:

<!DOCTYPE html>
<html>
  <head>
    <title>MB QuoteBot</title>
    <script type="text/javascript" src="javascripts/base64.js"></script>
    <script type="text/javascript" src="javascripts/mbclient.js"></script>
    <style>
      h1 {margin:0;}
      h2,h3,h4 {margin-bottom:0;}
      p {margin-top:0;}
      div#side {float:left;margin:auto 1em auto auto;}
      div#main {float:left;}
    </style>
  </head>
  <body>
    <sidebar>
      <div id="side">
        <img src="http://amundsen.com/images/robot.jpg" />
      </div>
    </sidebar>
    <article>
      <div id="main">
        <header>
          <h1>MB QuoteBot</h1>
        </header>
        <section>
          <p>
            This bot understands the Microblogging profile enough to 
            register a new user account and 
            start posting quotes to the stream.
          </p>
        </section>
        <section>
          <h3>Progress</h3>
          <p id="status"></p>
        </section>
      </div>
      <div id="output">
      </div>
    </article>
  </body>
</html>

QuoteBot JavaScript

The JavaScript for the QuoteBot looks as if it is complicated, but is actually rather simple. The code can be separated into a handful of sections:

Setup

This code contains the initialization code, the variables used to fill out expected forms, and the list of quotes to send to the server.

Making requests

This is a short bit of general code used to format and excecute an HTTP request. The code is smart enough to include a body and authentication information, if needed.

Processing responses

This section contains all of the methods used to process the response representations from the server. This includes parsing the response, looking for links, and looking for and filling in forms. These routines either conclude with an additional request or, in case of an error, stop and report the status of the bot and stop.

Supporting routines

These are utility functions to inspect arguments in the URI, format a URI for the next request, and look for elements in the response.

Setup code

The setup code includes details on shared state variables, details on forms to fill out, error messages, and a list of quotes to send to the server:

  var g = {};
  
  /* state values */
  g.startUrl = '/microblog/'
  g.wait=10;
  g.status = '';
  g.url = '';
  g.body = '';
  g.idx = 0;
    
  /* form@class="add-user" */
  g.user = {};
  g.user.user = 'robieBot5';
  g.user.password = 'robie';
  g.user.email = 'robie@example.org';
  g.user.name = 'Robie the Robot';
  g.user.description = 'a simple quote bot';
  g.user.avatar = 'http://amundsen.com/images/robot.jpg';
  g.user.website = 'http://robotstxt.org';

  /* form@class="message-post" */
  g.msg = {};
  g.msg.message = '';
      
  /* errors for this bot */
  g.errors = {};
  g.errors.noUsersAllLink = 'Unable to find a@rel="users-all" link';
  g.errors.noUserLink = 'Unable to find a@rel="user" link';
  g.errors.noRegisterLink = 'Unable to find a@rel="register" link'; 
  g.errors.noMessagePostLink = 'Unable to find a@rel="message-post" link';
  g.errors.noRegisterForm = 'Unable to find form@class="add-user" form';
  g.errors.noMessagePostForm = 'Unable to find form@class="message-post" form';
  g.errors.registerFormError = 'Unable to fill out the form@class="add-user" form';
  g.errors.messageFormError = 'Unable to fill out the form@class="message-post" form';
     
  /* some aesop's quotes to post */
  g.quotes = [];
  g.quotes[0] = 'Gratitude is the sign of noble souls';
  g.quotes[1] = 'Appearances are deceptive';
  g.quotes[2] = 'One good turn deserves another';
  g.quotes[3] = 'It is best to prepare for the days of necessity';
  g.quotes[4] = 'A willful beast must go his own way';
  g.quotes[5] = 'He that finds discontentment in one place is not likely to find happiness in another';
  g.quotes[6] = 'A man is known by the company he keeps';
  g.quotes[7] = 'In quarreling about the shadow we often lose the substance';
  g.quotes[8] = 'They are not wise who give to themselves the credit due to others';
  g.quotes[9] = 'Even a fool is wise-when it is too late!';
Making requests

When the page first loads, a set of state variables are populated based on data in the query string. This data is then used to fire off a request to the microblogging server:

  function init() {
    g.status = getArg('status')||'start';
    g.url = getArg('url')||g.startUrl;
    g.body = getArg('body')||'';
    g.idx = getArg('idx')||0;

    updateUI();
    makeRequest();
  }
    
  function newQuote() {
    g.idx++;
    nextStep('start');
  }
  
  function updateUI() {
    var elm;
    
    elm = document.getElementById('status');
    if(elm) {
      elm.innerHTML = g.status + '<br />' + g.url + '<br />' + unescape(g.body);
    }    
  }

  function makeRequest() {
    var ajax, data, method;
    
    ajax=new XMLHttpRequest();
    if(ajax) {
      ajax.onreadystatechange = function() {
        if(ajax.readyState==4 || ajax.readyState=='complete') {
          processResponse(ajax);
        }
      };
      
      if(g.body!=='') {
        data = g.body;
        method = 'post';
      }
      else {
        method = 'get';
      }
      
      ajax.open(method,g.url,true);
      
      if(data) {
        ajax.setRequestHeader('content-type','application/x-www-form-urlencoded');
        ajax.setRequestHeader('authorization','Basic '+Base64.encode(g.user.user+':'+g.user.password));
      }
      
      g.url='';
      g.body='';
      
      ajax.setRequestHeader('accept','application/xhtml+xml');
      ajax.send(data);
    }
  }
Processing responses

Processing responses is the heart of this example application. Each response representation from the server can potentially contain links and/or forms of interest for this bot. The code needs to know what is expected in this response (e.g. “There should be a ‘register’ link in this response somewhere...”) and know how to find it (e.g. “Give me all of the links in this response and see if one is marked rel='register'”). This first code snippet shows the routine used to route response representations to the proper function for processing:

  /* these are the things this bot can do */
  function processResponse(ajax) {
    var doc = ajax.responseXML;

    if(ajax.status===200) {
      switch(g.status) {
        case 'start':
          findUsersAllLink(doc);
          break;
        case 'get-users-all':
          findMyUserName(doc);
          break;
        case 'get-register-link':
          findRegisterLink(doc);
          break;
        case 'get-register-form':
          findRegisterForm(doc);
          break;
        case 'post-user':
          postUser(doc);
          break;
        case 'get-message-post-link':
          findMessagePostForm(doc);
          break;
        case 'post-message':
          postMessage(doc);
          break;
        case 'completed':
          handleCompleted(doc);
          break;
        default:
          alert('unknown status: ['+g.status+']');
          return;
      }
    }
    else {
      alert(ajax.status)
    }
  }

The following function looks for a link and responds accordingly:

  function findMyUserName(doc) {
    var coll, url, href, found;

    found=false;
    url=g.startUrl;
    
    coll = getElementsByRelType('user', 'a', doc);
    if(coll.length===0) {
      alert(g.errors.noUserLink);
    }
    else {
      for(i=0,x=coll.length;i<x;i++) {
        if(coll[i].firstChild.nodeValue===g.user.user) {
          found=true;
          break;
        }
      }

      if(found===true) {
        g.status = 'get-message-post-link';
      }
      else {
        g.status = 'get-register-link';
      }
      nextStep(g.status,url);
    }    
  }

This bot is also able to locate a form and fill it in based on data already in memory:

  function findRegisterForm(doc) {
    var coll, url, msg, found, i, x, args, c, body;
    
    c=0;
    args = [];
    found=false;
    
    elm = getElementsByClassName('user-add','form',doc)[0];
    if(elm) {
      found=true;
    }
    else {
      alert(g.errors.noRegisterForm);
      return;
    }
    
    if(found===true) {
      url = elm.getAttribute('action');
      
      coll = elm.getElementsByTagName('input');
      for(i=0,x=coll.length;i<x;i++) {
        name = coll[i].getAttribute('name');
        if(g.user[name]!==undefined) {
          args[c++] = {'name':name,'value':g.user[name]};      
        }
      }
      coll = elm.getElementsByTagName('textarea');
      for(i=0,x=coll.length;i<x;i++) {
        name = coll[i].getAttribute('name');
        if(g.user[name]!==undefined) {
          args[c++] = {'name':name,'value':g.user[name]};      
        }
      }
    }
    
    if(args.length!=0) {
      body = '';
      for(i=0,x=args.length;i<x;i++) {
        if(i!==0) {
          body +='&'
        }
        body += args[i].name+'='+encodeURIComponent(args[i].value);
      }
      alert(body);
      nextStep('post-user',url,body);
    }
    else {
      alert(g.errors.registerFormError);
    }
  }

There are a number of other processing routines in the working example. See for Source Code details on how to locate and download the full source for this book.

Support routines

This implementation contains a few routines used to support the high-level processing outlined in previous sections. For example, below is a function to parse the query string of the current document and a function used to assemble the URI for the next request in the task chain:

  function getArg(name) {
    var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
    return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
  }

  function nextStep(status,url,body) {
    var href,adr;
    
    href = window.location.href;
    href = href.substring(0,href.indexOf('?'));
    adr = href + '?status=' + status;
    adr += '&idx=' + g.idx;
    if(url) {adr += '&url=' + encodeURIComponent(url);}
    if(body) {adr += '&body=' + encodeURIComponent(body);}
    
    window.location.href = adr;
  }

This last routine handles parsing a response representation to find elements that match a specific link-relation value. This is very similar to looking for elements with specific values in the class attribute:

  function getElementsByRelType(relType, tag, elm)
  {
    var testClass = new RegExp("(^|\\s)" + relType + "(\\s|$)");
    var tag = tag || "*";
    var elements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
    var returnElements = [];
    var current;
    var length = elements.length;
    for(var i=0; i<length; i++){
      current = elements[i];
      if(testClass.test(current.getAttribute('rel'))){
        returnElements.push(current);
      }
    }
    return returnElements;
  }

Summary

This chapter covered the topic of using HTML5 as a base media type. This has the advantages of a fully-functional hypermedia type (supports almost all the H-Factors identified in Identifying Hypermedia : H-Factors). Using HTML5 also means that implementing the basic server will result in a working client implementation that can run in common web browsers without the need for client-side scripting. This is a great way to build quick sample implementations to test server transition details and media type design aspects.

Since HTML5 is a domain-agnostic media type, it has no built-in domain specific markup elements and no predefined transitions as does Atom/AtomPub and the Collection+JSON example in Chapter 3. This example showed how designers can use HTML5 attributes (id, name, class, and rel) to apply domain-specific details to responses.

A server implementation of a simple CouchDB data model was created that included support for HTTP Basic Authentication. This showed that user authentication details can be implemented independently of the actual media type design. And finally, two sample clients were reviewed. The first was simply a CSS restyling of the plain HTML5 rendered by the server (the POSH client). The second client was an Ajax-style web bot implementation that parsed the HTML5 responses looking for desired links and forms in order to post messages to the server.

This implementation showed that it is possible to use an already-existing hypermedia type as the basis for your own unique design. It also showed the importance of documenting domain-specific details in ways that both server and client implementors can understand. The next (and final) chapter explores the role of documentation in more detail.

Get Building Hypermedia APIs with HTML5 and Node now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.