photo

The MLS Digital Dev team has been working on some new features for our Golazo MatchCenter and we wanted to give our fans a chance to check them out and give us some feedback before we include them as standard features. Feel free to post feedback on Reddit or to post it on our User Voice Page. We will be running the trial features for the next week before we make decisions about what will go into production.

Moderated Stats

Golazo moderators now have the ability to generate a snapshot of a stat (along with some commentary) any time during the game for inclusion in the game timeline. You can filter out this content using the timeline filters if you prefer not to see the stats.

Stats in feed

Stats Sharing

You are now able to a click a share icon by each stat graphic on the stats page and capture a snapshot of that graphic for the current minute of the game. You will then be prompted to share the stat on Twitter, Facebook, G+, or via email. Golazo will automatically generate a rich piece of shareable content on the chosen social network with a link back to the match.

This feature is hidden behind a flag. To access it, add ?share=true to the stats page URL.

Example: http://matchcenter.mlssoccer.com/matchcenter/2014-07-27-montreal-impact-vs-portland-timbers/stats?share=true

Share Button

Choose Network

Photo Expansion

We are currently testing two alternative views of the timeline. Currently, photos are collapsed to a short preview image and must be expanded to be fully viewed. This experience works well on a mobile device but feels wrong on the desktop. We have created two new modes to try and address this.

Expand mode - Photos will all be automatically fully expanded on the desktop. To access this mode, add ?timeline-photo=expand to the feed URL.

Example: http://matchcenter.mlssoccer.com/matchcenter/2014-07-27-montreal-impact-vs-portland-timbers/feed?timeline-photo=expand

Collapse mode - Only the most recent photo will be fully expanded on the desktop. Photos lower in the feed will automatically collapse. To access this mode add ?timeline-photo=collapse to the feed URL.

Example: http://matchcenter.mlssoccer.com/matchcenter/2014-07-27-montreal-impact-vs-portland-timbers/feed?timeline-photo=collapse Choose Network

We encourage you to check out the Sounders/Galaxy game tonight using the new features. The links are below.

Have fun and let us know what you think!

Justin

 photo

Re-Sign iOS App

If you ever work with 3rd party developers, you know that getting the final project into production can take two different routes. You can either let the developer push the code/project into production or they hand it over to your internal team to release. There are pros and cons for each, but I always prefer to publish things ourselves since it reduces security concerns and gives us more control. The Apple App Store is no different, and it is actually really simple to take a developer app and turn it into an App Store version for submission.

Note: You can use this exact same process to resign the app to your developer certificate and profile, so you can install it on your own registered developer devices.

Get a developer build

Get your developer to give you the .IPA file that is signed using their normal “iPhone Developer” key. They do this all the time to test the app on physical devices they own.

Get your tools in order

You will need the following:

  1. A “Mobile Provisioning Profile”
  2. An “Entitlements.plist”
  3. An “iOS Distribution Certificate”
  4. iReSign OS X app (or you could use command line)

1. Mobile Provisioning Profile

Download (or create then download) this from the “iOS Provisioning Profiles” inside of Apple Developer Members Center.

iOS Provisioning Profile

2. Entitlements Plist

You will actually need to make this yourself. You will need two values to make the strings required. You need the App ID Prefix and the Bundle ID. You can find them in Members Center “Certificates, Identifiers & Profiles” under Identifiers > App IDs.

iOS App IDs

Open your favorite text/code editor and drop this in. Then update the two values for “PREFIX.yourappBundleID” in the following code and save it as “entitlements.plist”.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>application-identifier</key>
    <string>PREFIX.yourappBundleID</string>
    <key>aps-environment</key>
    <string>production</string>
    <key>get-task-allow</key>
    <false/>
    <key>keychain-access-groups</key>
    <array>
        <string>PREFIX.yourappBundleID</string>
    </array>
</dict>
</plist>

If you are NOT using Push Notifications, you need to remove the two lines:

<key>aps-environment</key>
    <string>production</string>

3. iOS Distribution Certificate

You should have this installed already. If not, you can get it installed via Xcode or Member Center.

  • Xcode > Preferences > Accounts

  • Find or add your Apple ID and click “View Details…”

  • ”+” “iOS Distribution” (or iOS Development) and then Refresh (bottom left)

Re-sign the .IPA

Download the iReSign app.

Fill in all the values required.

iReSign and Views Configuration

Press the “ReSign!” button and you get yourappname-resigned.ipa. You can now submit it for App Store review via the Application Loader.

 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