photo

Part 3 - Documentation

Welcome to the third and final installment of our three part series about the MLS API. Check out the first and second posts in case you missed them.

User documentation is important. Especially if you expect anyone outside your organization (or anyone other than the original developer, really) to actually use your API. The docs need to show how to use the API and cover all the available routes, the parameters they accept, and so on. Most developers consider writing docs a huge pain. We certainly do.

Manually writing documentation is, at best, a tedious and error prone process. Like any tedious, error prone process, we wanted to figure out a way to automate it. In this post, we will show you how we did just that. Using our Respectify module and a handful of templates, we are able to generate documentation directly from the API code, thus guaranteeing that it will always stays up-to-date and greatly reducing the surface area for documentation errors.

In the last Respectify post, we went over how we define specifications and the type of information a Respectify specification can return. Now, in order to make these specifications into something more palatable to humans, we create markdown templates to transform the specifications to markdown documents which can be rendered to HTML by a variety of tools.

Example markdown template:

## Table of Contents

* [Routes](#routes)
<% specs.forEach(function(spec) { %>
  * [<%= spec.route %>](#<%= spec.route.replace(/:|\//g, '').toLowerCase() %>)
<% }) %>

## Routes

<% specs.forEach(function(spec) { %>
#### [<%= spec.route %>](#<%= spec.route.replace(/:|\//g, '').toLowerCase() %>)
<% if (spec.description) { %>
<%= spec.description %>
<% } %>
Method: `<%= spec.method %>`<br />
Versions: `<%= spec.versions.join('`, `') %>`
<% if (spec.parameters && spec.parameters.length) { %>
##### Parameters

<% (spec.parameters || []).forEach(function(param) { %>* `<%= param.name %>` -<% if (param.description) { %> <%= param.description %><% } %> (`<%= param.dataTypes.join('`, `') %>`)<% if (!param.required) { %> (optional<% if (param.default) { %>, default `<%= param.default %>`<% } %>)<% } %><% if (param.dataValues && param.dataValues.length) { %>
  - Valid values are: `<%= param.dataValues.join('`, `') %>`<% } %><% if (param.hasOwnProperty('min')) { %>
  - Minimum: `<%= param.min %>`<% } %><% if (param.hasOwnProperty('max')) { %>
  - Maximum: `<%= param.max %>`<% } %><% (param.notes || []).forEach(function(note) { %>
  - ***Note:*** <%= note %><% }) %>
<% }) %><% } %>

<% }) %>

Route documentation generated during the build process can easily be incorporated into existing documentation. The code below output’s the template into our API project’s documentation.md file. We then use Github’s markdown rendering engine to display it. This can easily be done by using an HTML template instead of markdown.

var _ = require('underscore')
  , fs = require('fs')
  , restify = require('restify')
  , Respectify = require('respectify')
  , server = restify.createServer()
  , respect = new Respectify(server)
  , template = '...assume above template...'

server.use(respect.middleware)

// add some routes...

var specs = respect.loadSpecs()

var output = _.template(template, {
  specs: specs
})
fs.writeFileSync('path/to/docs.md', output)

Here is what the output will look like:

This gives us a readable markdown file that contains the documentation for our API, but let’s take this one step further and convert the markdown into HTML to create a static documentation site. We use the marked library to render the markdown files during our build process. As this can be done many ways (or skipped altogether) we won’t go into the details.

We were able to use some of the time we saved not hand editing files to create some fancier layouts and styles. Here is what our finished documentation pages come out as:

The main benefit in this strategy is that we automatically generate and deploy our external documentation whenever we build and deploy changes to the API. This ensures that our clients always have access to current, accurate, documentation (assuming there are no bugs).

The Respectify module has enabled us to ensure our API has consistent parameter validation, programmatically readable route information, and automated documentation. But, from a busy developer’s point of view, the best part of all is that we never have to manually update markdown or HTML files. We can just hit the deploy button, sit back, and drink our morning coffee.

 photo

The MLS platform utilizes the Nodequeue and Views modules to create blocks of content that can be curated by our editors. The nodequeue can be configured to hold a number of nodes limited by maximum count or node type. The nodequeue itself is populated based on an autocomplete node reference fields and can be re-ordered or edited. This gives editors a simple interface to curate multiple blocks on our site. Once configured, a view’s content can be controlled by a nodequeue relationship.

Nodequeue and Views Configuration

Controlling the output of the view can be difficult, since you can only set a maximum number of nodes allowed in the queue, but not a minimum. We solved this issue and gave site editors the freedom to dynamically change the layout based on the number of items. This allows us to build one template that works for a variety of use cases.

In this post, we will use several examples to demonstrate how to use a “block” display from a view driven by a nodequeue relationship.

Nodequeue Layouts

Adding a Count Class

Adding a class to the container div of the view will allow us to style the elements inside. This is accomplished fairly easily. You can create a view override template suggestion for the parent container (views-view.tpl.php); we used the views-view-nodequeue.tpl.php. Don’t forget to scan for files once you create your override.

Adding the following code to the top of your newly created override will add the nq-count-X class to the view output container, where X is the number of nodes in the queue.

$row_content = $variables['view']->result;
$nq_count = count($row_content);
$classes .= ' nq-count-' . $nq_count;

This will allow for customization of the children elements. However, we want to use different image presets based on the item count and that involves a little more code.

Modifying Image Presets Based on Count

First thing we want to do is create a view override template suggestion for the image we are manipulating (views-view-field.tpl.php). In our example the override will be for the “primary-image” field used in our post content type. The contents of the field override are simple–we just output the themed image from our field based on the view settings:

print $output;

We will be excluding that field from our view display and handling the output via our custom function:

$output = nodequeue_process_nq_image($row, $view);
print $output;

The function that this code is referencing can live either in the template.php file or in a custom module. We wrote a custom module that handles some other nodequeue manipulation, so our function lives there:

/**
 * nodequeue_process_nq_image().
 * Determine which image preset to use via count.
 * @param $row: The raw SQL result of the image field.
 * @param $view: View object.
 *
 * @return The processed themed image.
 */
function nodequeue_process_nq_image($row, $view) {
  $file_uri = '';
  $row_count = count($view->result);
  $row_index = $view->row_index;
  $image_map = 'count-' . $row_count;
  $nq_image_styles = array(
    'count-1' => array(
      'image_landscape',
    ),
    'count-2' => array(
      'image_default',
      'image_thumbnail',
    ),
    'count-3' => array(
      'image_landscape',
      'image_thumbnail',
      'image_thumbnail',
    ),
  );
  // Check to ensure we have a mapping.
  if (isset($nq_image_styles[$image_map][$row_index])) {
    $image_nid = node_load($row->field_field_post_primary_image[0]['raw']['nid']);
    $file_uri = $image_nid->field_image_image[LANGUAGE_NONE][0]['uri'];
  }
  $image_output = ($file_uri) ? theme('image_style', array('path' => $file_uri, 'style_name' => $nq_image_styles[$image_map][$row_index])) : '';
  return $image_output;
}

Now our image presets can be changed dynamically via queue count. But what if we want to do multiple layouts?

Creating More Dynamic Nodequeue Count Layouts

The above example will work fine for just one layout for the block chosen. We wanted to have more than just one layout available to our editors. The issue that we encountered is that we need a way to determine which block we are processing in order to determine which image presets to use. The machine name of the block from the view is available for us to use in our code. It is important to note that the id must be unique PER VIEW. When you clone a view, the ids will remain in place. This allows site moderators to clone these views and still allow our code to determine which view we are modifying.

Note: when you create a new view and then create a block display, the machine name will be “block”. You need to alter this value. It is under the advanced tab in the view display (Other->machine name)

For our example we have two layouts, primary_dl and post_callout. This could be extended this to as many presets as you like.

We created a view block and modified the machine name to primary_dl and another view display block with a machine name of post_callout. Now we modify our code to look at the machine name and map according to which view display is being processed.

Now we can extend our existing function:

$file_uri = '';
$row_count = count($view->result);

// Machine name for view display id.
$block_name = $view->current_display;
$row_index = $view->row_index;
$image_map = $block_name . '-count-' . $row_count;
$nq_image_styles = array(
  'primary_dl-count-1' => array(
    'image_landscape',
  ),
  'primary_dl-count-2' => array(
    'image_default',
    'image_thumbnail',
  ),
  'primary_dl-count-3' => array(
    'image_landscape',
    'image_thumbnail',
    'image_thumbnail',
  ),
  'post_callout-count-1' => array(
    'image_default',
  ),
  'post_callout-count-2' => array(
    'image_default',
    'image_default',
  ),
  'post_callout-count-3' => array(
    'image_thumbnail',
    'image_thumbnail',
    'image_thumbnail',
  ),
);

We also want to account for our site maintainers cloning the display. When you clone a display in views, it increments the machine name (i.e. primary_dl_1, primary_dl_2, etc.). To account for that we can just look at the base machine name, disregarding the _* (via cloning):

$available_nqs = array(
  0 => 'primary_dl',
  1 => 'post_callout',
);
foreach ($available_nqs as $key => $nq_name) {
  if (strpos($block_name, $nq_name) !== false) {
    $block_name = $nq_name;
    break;
  }
}

And now we are all set to theme it properly using our base machine names.

Here is the complete function for reference:

/**
 * nodequeue_process_nq_image().
 * Determine which image preset to use via count.
 * @param $row: The raw SQL result of the image field.
 * @param $view: View object.
 *
 * @return The processed themed image.
 */
function nodequeue_process_nq_image($row, $view) {
  $file_uri = '';
  $row_count = count($view->result);
  // Machine name for view display id.
  $block_name = $view->current_display;
  $row_index = $view->row_index;
  $available_nqs = array(
    0 => 'primary_dl',
    1 => 'post_callout',
  );
  foreach ($available_nqs as $key => $nq_name) {
    if (strpos($block_name, $nq_name) !== false) {
      $block_name = $nq_name;
      break;
    }
  }
  $image_map = $block_name . '-count-' . $row_count;
  $nq_image_styles = array(
    'primary_dl-count-1' => array(
      'image_landscape',
    ),
    'primary_dl-count-2' => array(
      'image_default',
      'image_thumbnail',
    ),
    'primary_dl-count-3' => array(
      'image_landscape',
      'image_thumbnail',
      'image_thumbnail',
    ),
    'post_callout-count-1' => array(
      'image_default',
    ),
    'post_callout-count-2' => array(
      'image_default',
      'image_default',
    ),
    'post_callout-count-3' => array(
      'image_thumbnail',
      'image_thumbnail',
      'image_thumbnail',
    ),
  );
  // Check to ensure we have a mapping.
  if (isset($nq_image_styles[$image_map][$row_index])) {
    $image_nid = node_load($row->field_field_post_primary_image[0]['raw']['nid']);
    $file_uri = $image_nid->field_image_image[LANGUAGE_NONE][0]['uri'];
  }
  $image_output = ($file_uri) ? theme('image_style', array('path' => $file_uri, 'style_name' => $nq_image_styles[$image_map][$row_index])) : '';
  return $image_output;
}

Like all things Drupal, there are many ways to solve these kinds of problems. Feel free to reach out to me with questions and comments.

Hans Gutknecht - @hansyg

 photo

MLS LIVE on Chromecast

Today we are excited to announce MLS LIVE is now Google Cast enabled! Use your Android phone, Android tablet, iPhone, and iPad to cast live, full replay, and condensed games to your Chromecast. Just download the latest version of MLS Matchday from Google Play and the App Store, setup a Google Chromecast, and login with your MLS LIVE subscription.

Our setup is using a Custom Receiver and the Media Player Library to provide the streams directly to your HD television. We worked with our mobile development partner, Double Encore, so the custom receiver displays the team logos when you start watching a game. If you are looking to get started on your own receiver we can suggest starting with the UX Design Checklist and the open-source samples on Github.

What’s next?

This is just the first Chromecast update. We are working to add Google Cast support to the web version of MLS LIVE via Chrome. We are also still developing a custom receiver to support our video on-demand content like MLS Insider and AT&T Goal of the Week, which is hosted by Ooyala. You can expect a few other new features to roll out in the coming months.

Chromecast with Nexus 5

 photo

MatchCenter

We are very excited to unveil the newest version of MatchCenter. MLSsoccer.com will have coverage for every World Cup game with MatchCenter, and we are making it even better! The new large screen version is a huge update after our successful tablet design and mobile design updates. In addition to the responsive design work, there was a significant update to the webapp architecture. The data for a match is updated in real-time regardless of what tab you are currently on. This enables you to switch tabs near instantly because it doesn’t require communication with the server.

Player Details

Detailed Player Card

Fans can now select specific players to highlight. Touch or select a player name from the Lineup, Feed, or Boxscore tabs to reveal a player card. Player cards contain vital stats for players and some general information. Additionally, the player’s name is highlighted everywhere, so fans can quickly find player events.

ProTip: Touch the name on the player card to jump to his MLSsoccer.com bio page.

New Boxscore

Detailed Player Card

There is an all new Boxscore tab that highlights individual stats such as goals, discipline, and substitutions. Additional player stat tables have been added to allow fans to compare individual players. The Opta Chalkboard also returns to MatchCenter, and Flash is no longer required. Fans can now slice and dice a variety of stats using the interactive tool.

New Stats

Detailed Player Card The stats tab now features shot accuracy with visual charts for direction and outcome. Team stats are easier to compare now with bar charts. Overall stat numbers are automatically highlighted with team colors to quickly identify each leader.

Match Preview Integration

Match Preview

The match preview is the perfect starting point for pre-game research. You can quickly find historical data about each club with quick links for Previous Meetings, Current Form, and Season Goal Leaders.

Videos

Videos

A new videos experience makes it even easier to watch match highlights. Videos are now grouped into 3 sections, Full Highlights, Highlights, and Related Videos. During a match, you will also see a Recent Highlight section that will always have the latest highlight clip. We’ve also added an auto-advance function to move you through the highlight clips in match order. Don’t worry, you can turn this off with a single click and we will remember that for future matches.