Chapter 5. Collaborative Baking with Hangout Apps

Google+ Hangouts provide an easy way to create 10-way video conferences. This is a pretty cool feature on its own, but with the Hangout API you can extend them.

You can author your own JavaScript applications that have access to a data structure that is automatically synchronized between all participants in the hangout. You can also programmatically manipulate many aspects of the running hangout environment, including adding image overlays to the video streams of hangout participants.

As fun as it is to experiment alone, inviting friends takes baking disasters to the next level. You can distribute the cost of your baking party by making it an ingredient potluck. Your baking friends can each volunteer to bring some of the ingredients for the recipe that you’re going to attempt together.

The process of deciding who will bring what is highly collaborative. It’s a perfect case for a Hangout App. This is a great opportunity to extend your baking disasters web application into a hangout with a Potluck Party Planner app.

Potluck Party Planner allows the hangout participants to select a recipe that they wish to attempt. Next, a list of ingredients is displayed. Participants can then volunteer to bring an ingredient to the party by clicking it. The participant who has agreed to bring the most ingredients is rewarded with a virtual chef’s hat made possible by a media overlay. Finally, participants share a reminder to Google+ with a list of the ingredients that they have volunteered to bring. The flow is shown in Figure 5-1.

A Hangout Application consists of three parts, as shown in Figure 5-2: a gadget.xml specification file, JavaScript for the real-time interaction, and optional server-side APIs. These APIs allow your hangout application to access data that resides outside the hangout.

You’re probably eager to dive into the code, but to save headaches later you need to take care of a few things first. There are special considerations you must take into account when hosting them.

The sample applications provided by Google all run on Google App Engine. App Engine provides a great place to host Hangout Apps, but it does not support PHP and hence can’t run Baking Disasters. To achieve tight integration between your web application and Hangout App you will need to make some changes. You’ll need to serve files over SSL and configure cache control headers.

SSL is a must both during development and later when your application is in production. Hangouts themselves run in SSL. You’re free to create hangout apps that include content served over unencrypted HTTP, but web browsers do not like it when you do this. Even in web browsers configured with the default security settings you’ll see mixed content warnings and even errors resulting from files failing to load.

You must serve your static files and API requests over SSL. There are a few ways to do this. If you’re running on a subdomain of a shared domain from your provider, as is the case with Google App Engine’s appspot.com, you can leverage their existing wildcard SSL certificate. If that’s not available or you are running on your own domain, you’ll need to purchase and install an SSL certificate.

If this is a lengthy process, there is an alternative that you can use during development. You can use a self-signed SSL certificate and force your web browser or operating system to trust it.

In addition to being able to serve content over SSL, you should have control over the HTTP cache headers. While you develop your hangout application you’re going to be going through the familiar code, deploy, and test loop. In fact, you’re going to be doing it quite rapidly. The hangout APIs will respect cache directives in serving these files. This means that the default cache directives set by your web server will probably make development quite cumbersome. While you are developing your application you should disable caching, but don’t forget to then it back on when you’re ready to release your application to the world!

With these two issues taken care of, you’re ready to build.

Now that you know what you’re going to build and where you’re going to host the code, it’s time to start hacking. Starter apps are a great place to start, and the Hangouts API is no different. As mentioned above, the Google-provided sample applications are all intended to be hosted on Google App Engine, but with a few tweaks you can convert them to your PHP environment.

Download a copy of the starter app from the hangouts sample apps page: https://developers.google.com/+/hangouts/sample-apps. This archive contains several files, as shown in Figure 5-3.

Two YAML files contain App Engine configuration. Because we’re not running on App Engine, you can ignore them. The app.js and app.xml files in the static folder contain most of the Hangout App code. Following the instructions in the included readme, update the URLs inside of them to refer to your web host.

The initial contents of app.js in Example 5-1 become Example 5-2, and so on elsewhere in the starter project files.

Next, open main.py. As shown in Example 5-3, this file contains a simple AJAX handler.

Reproducing it in PHP is easy. Create a file named index.php in the root of your project with the code in Example 5-4.

1

A simple cross-origin resource sharing header to make cross domain requests easier. If you host your app.js file on the same domain as index.php, this is not strictly necessary.

2

By default PHP sets a text/html content type. Override it because this file responds with JSON.

Finally, deploy the updated starter project to your web host.

There is one last step that you must complete to run the Hangout App starter project. You must enable the Hangouts API on the Baking Disasters API console project. Follow these steps to configure your API console project and start the hangout in development mode.

  1. Return to the service panel for Baking Disasters on the API console: https://developers.google.com/console#:services.

  2. Enable the hangouts API on the services panel by toggling the switch to ON as shown in Figure 5-4.

  3. Switch to the newly revealed Hangouts panel shown in Figure 5-5. Paste the URL for your deployed app.xml into the Application URL field.

  4. Scroll to the bottom of the page, as shown in Figure 5-6. Click Save and then the Enter a hangout link to initiate a development mode hangout running your starter project.

  5. If you’re asked about permissions, grant them to allow the hangout app to start.

The application, shown in Figure 5-7, is quite simple. It consists of a count that is synchronized across all users, and a Get Message button, which uses AJAX to fetch the response from index.php.

Alongside the features contained in your starter project there are a some development tools. The Reset app state button deletes the contents of the shared state, and the Reload app button reloads the app’s html and JavaScript, but it does not clear the shared state. If you are not careful with your code, using these buttons independently may result in some unusual behavior.

To fully experience the starter application, now is a good time to recruit a friend to join your hangout. Since your application is still in development mode, you must add your collaborators to your development team on the API console. You can use the team panel, shown in Figure 5-8 of the API console to add your collaborators: https://developers.google.com/console#:team

Once you have added them to your project, restart your hangout and invite them to join you.

As simple as the starter app is, it contains basic usage of all of the APIs that you will need to write the potluck planner app. It uses the shared state and callbacks to maintain the synchronized count and makes AJAX calls to APIs hosted server side.

The starter project provides a solid foundation. It’s time to mold it into the potluck party planner. Taking a bottom-up approach this involves enhancing the AJAX handler to communicate recipes and ingredients, modifying the HTML in app.xml, and then adding JavaScript to synchronize participant state and manipulate the HTML. This design is shown in Figure 5-9.

Once the core interaction features are implemented, you can loop back and add enhancements like the ingredient reminder sharing and the chef’s hat overlay for the top contributor.

The hangout app needs data from Baking Disasters to function. Specifically, it needs access to a list of all available recipes and a way to list the ingredients required for each of those recipes.

REST APIs are a great way to communicate data to AJAX applications. Implementing simple REST APIs is quite easy in PHP. Create a new file, api.php. Start by adding some headers to turn this php file into a REST API, as shown in Example 5-5.

Use GET parameters to control which data you will return. If recipes is set, you know that the hangout is asking for a list of all available recipes, so fetch them from the database, convert, and print them into the response, as shown in Example 5-6.

If ingredients is set, use the id parameter to look up the ingredients for that recipe and respond with them in the same way, as shown in Example 5-7.

And finally, fall through to a simple error if neither are set, as shown in Example 5-8.

This single file API provides all of the data access that the hangout application needs.

In practice on your application, the implementation of any APIs you provide will be different. If you’re using an MVC framework, it probably provides an easy way to expose entities in your application as simple APIs.

Hangout apps, at their core, are HTML web applications that run inside of an iframe. All user interaction is made through HTML. The initial state of this HTML is specified inside the app.xml file and manipulated as the app runs by JavaScript.

The potluck party planner has two screens: one for recipe selection and another for ingredient selection. An easy way to model this is to create both screens within the initial HTML. Use CSS to hide them at startup and then use JavaScript to display at the right time.

Merge the header from Baking Disasters into app.xml to provide a foundation for further features. Example 5-9 shows the result of this merger.

With this foundation created add some content panes. The design calls to start with a list of recipes, but calling the recipe list API may take some time so start with a content panel that displays a loading message, as shown in Example 5-10.

Once the app has finished loading it should display a list of recipes. Create a section for this but make it hidden by default, as shown in Example 5-11.

1

The display: none; CSS prevents this section from being visible.

2

Recipes render within the recipe-list list.

And finally, once the recipe has been selected, users need a way to volunteer to bring ingredients. The HTML for the ingredients panel is shown in Example 5-12.

1

Ingredients render within the claim-list element of the who-bring-what table.

JavaScript is the glue that puts this interface and the Baking Disasters REST APIs together, in a hangout.

You can put JavaScript in app.xml too, but it quickly becomes cumbersome. It’s easier to break it out into a separate file. Create a new file, app.js, and include it into the head element, as shown in Example 5-13.

When the hangout starts up, Potluck Party Planner must be initialized to render the list of recipes that available for selection. The Hangout API can be configured to call a function once loading is complete using the function shown in Example 5-14.

Within the init function you register a callback that executes when the Hangout API has finished its own initialization. Within this function you can register other callbacks, as shown in Example 5-15. In an application as simple as this one, you can funnel the initialization and other callbacks into a single updateUi function, as shown in Example 5-16.

6

Register the apiReady function to execute when the Hangouts API is ready for instructions.

1

Wait for the API to become fully ready.

2

Update the user interface whenever the shared state changes.

3

Update the user interface when participants join or leave the hangout.

4

Render the initial user interface.

5

Remove the apiReady callback to ensure that it does not run again.

Initially, the function shown in Example 5-17 renders the recipe list.

1

Toggle the interface to display the recipe list section and hide everything else.

2

Fetch the list of recipes from the simple API on Baking Disasters.

3

Clear the current recipe list if any. It may have since the last invocation.

4

Create HTML elements for the recipes and insert them into the recipe list.

The most important function of app.js is to respond to participant actions and to update everyone’s user interface. The Hangout API’s shared state API takes care of the heavy lifting. All you need to do is update the state in response to user actions and it will automatically trigger callbacks in every participant’s app. This includes the participant who triggered took the original action. The recommended sequence is shown in Figure 5-10.

Participants can take three possible actions when using potluck party planner. They can select or unselect a recipe and they can volunteer to bring an ingredient. The interface HTML already has JavaScript methods specified for these actions, so all you need to do is fill in the implementations.

Recipe selection, shown in Example 5-18, happens first. When a participant clicks on a recipe, the hangout app determines the recipe that they selected. Next, it makes an API call to Baking Disasters to fetch the ingredients for that recipe.

Once all of the important details about the selected recipe are known, you can update the shared state by calling gapi.hangout.data.submitDelta(). The shared state can only store strings, though, so all complex data, like the ingredients list, must be converted into a string to be stored.

1

Fetch the ingredients list for this recipe from the simple API.

2

Gather up all changes to the shared state into a single local variable.

3

Convert arrays and objects into strings to store them in the shared state.

4

Submit the updates using submitDelta.

Note

Use submitDelta() whenever multiple keys are updated rather than many calls to setValue(). Shared state updates are limited to several updates per second, but submitDelta() only counts as one call.

Unselecting recipes is much simpler. No data needs to be fetched from any API. Simply clear the ingredients list from the shared state using the gapi.hangout.data.clearValue() function, as shown in Example 5-19.

When the shared state has been updated, the Hangouts API will trigger the callback function you registered earlier. Update this function to render the correct panel based on the new value in the shared state, as shown in Example 5-20.

1

If there is no ingredient set in the shared state, display the recipe list.

Volunteering for ingredients follows a very similar flow, except since only one value is updated in the shared state, gapi.hangout.data.setValue() is sufficient.

1

Recall the ingredients list from the shared state.

2

Update the claimant for the target ingredient.

3

Store the updated ingredient list back into the shared state.

The basic flow is ready for testing. Deploy the code and start a hangout. It will look something like Figure 5-11. You and your friends can select a recipe and volunteer for ingredients together.

As it’s implemented, the final list of ingredients that each person selected is lost at the end of the hangout. The share link can help close this loop by allowing participants to post a reminder to Google+.

Content that is posted to Google+ must have a publicly accessible URL. A simple way to implement this is to create a reminder.php file, as shown in Example 5-22. It accepts reminder text as a GET parameter and formats it for sharing on Google+ with schema.org markup.

1

Schema.org does not specify a schema for a reminder, so fall back to thing.

2

Use htmlspecialchars to prevent cross-site scripting issues.

Note

For large ingredient lists, this implementation will produce very large URLs. A more robust implementation might involve POSTing a reminder and creating a shareable URL that refers to that reminder by ID.

Wiring reminder.php into the hangout app involves adding a place for the share link to render and rendering a share link with the GET parameter populated by the current ingredient list.

Add a placeholder reminder button to app.xml in the head element of the interface HTML, as shown in Example 5-23.

Next, add a function to app.js that updates the placeholder reminder link to share the list of ingredients that the current user has committed to bring, as shown in Example 5-24.

1

Recall the ingredients list from the shared state.

2

Crudely URL-encode the list of ingredients claimed by the current participant.

3

Construct the share link URL.

4

Update the href attribute of the reminder link to point to the share link URL.

5

Add a JavaScript callback to open the share link in a new window.

6

Display the share link if it is still hidden.

This share link must be updated in response to ingredient selection. Add it to the updateUi() function, as shown in Example 5-25.

The next time you run the hangout, a reminder link will appear. Clicking on the reminder link will bring up a share window. Share it with yourself, or whoever does your grocery shopping. An example reminder is shown in Figure 5-12.

The shared state APIs provide you everything you need to get the job done, but hangouts are supposed to be fun. You can use the media APIs to make the application a little more fun and provide an incentive for users to volunteer to bring ingredients: during ingredient selection, the user who has volunteered to bring the greatest number of ingredients to the party will be rewarded with a chef’s hat that appears in their thumbnail to everyone in the hangout.

Media overlays are created from images and later attached to a user’s face with a different API call. You can create media overlays dynamically with any URI, even a data URI, but this use case can be satisfied by reusing a single overlay. Create the overlay at startup from a static image and store it into a global variable for use later, as shown in Example 5-26.

1

Use a global variable to store the overlay.

2

Create the overlay by calling createHatOverlay from init.

This code specifies some seemingly arbitrary values for the attachment point, scale, and offset. These parameters behave consistently, but it’s usually easiest to guess some reasonable values for your image and make adjustments to the working application.

Now that you have an overlay to apply, you must tell the app when to render it. The app already has an updateUI() function that updates the interface after a user selects an ingredient. Use the same logic to trigger the assignment of the chef’s hat, as shown in Example 5-27.

Each time an ingredient is selected you can inspect the shared state to determine the participant who has volunteered to bring the greatest number of ingredients. Next, loop through the participants, assign the hat to the top contributor and clear out the hats worn by everyone else. Example 5-28 shows how to do this.

1

Recall the ingredients list from the shared state.

2

Determine the participant who has claimed the greatest number of ingredients.

3

Display the chef’s hat if the current participant claimed the most ingredients.

Deploy the code, start a hangout, and select a few ingredients to see a chef’s hat render above your head, as shown in Figure 5-13.

In this exercise you applied a media overlay. The media APIs provide other features that may be useful in your application. You can play sounds in the hangout. The sound APIs leverage the noise-canceling features of the hangout to ensure they are only heard by the correct participants. You can also affix images to the thumbnail view itself instead of the participants’ faces. You can even access the coordinates of the participants’ facial features programmatically. You can learn more about the available media API methods in the Hangout API reference documentation: https://developers.google.com/+/hangouts/api/gapi.hangout.av.effects

The Hangouts API is quite broad. It has many other methods and callbacks related to other hangout features. You can change the state of cameras and microphones, make your app react to changes in the On Air broadcast state of the hangout, and even embed video feeds into your main application pane. The best way to explore these features is to pursue the API reference documentation: https://developers.google.com/+/hangouts/api/gapi.hangout

Now that you have a working hangout application, it’s time to allow people outside of your development team to use it. Follow these steps to make your hangout app public.

  1. Return to the API console, https://developers.google.com/console.

  2. Open the Hangouts panel for your application.

  3. Provide URLs for the Privacy Policy, Terms of Service, and Contact fields.

  4. Create a Chrome Web Store account and verify it by paying a one-time $5 fee, as shown in Figure 5-14.

  5. Check the published checkbox on the API console, shown in Figure 5-15, and click Save.

  6. Create and add the hangout button to your website. Start by copying your application ID from the URL in the address bar on the API console, as shown in Figure 5-16.

  7. Create a button with that ID. The resulting markup is shown in Example 5-29.

Add it to a webpage, such as the Baking Disasters main index as shown in Figure 5-17, and click the button to start a hangout.

Use the built in hangout invitation features to invite your friends. You can now plan your potluck disaster.