Who is this for
---------------
So you've got a website, and want to integrate your site with the Ubuntu using the webapps API. This is great. You should review the API docs, https://chinstrap.canonical.com/~racarr/unity-web-api-docs/unity-web-api-reference.html. This guide is for people who want to write a userscript to integrate your page without putting the code right into a site. I'll be using firefox as my browser for this tutorial. Feel free to use chromium but you might have to do some translation of the bits where I'm doing debuggging. This is meant to be an introduction to the webapps API, not a full api reference.


What integration points are you going to use?
---------------------------------------------
Unity webapps offer a solid list of integration points for your web app.

  * Launcher icon
  * Switcher icon
  * Launcher quicklist actions
  * Launcher icon count emblem
  * Launcher icon progress bar
  * HUD Actions
  * Messaging menu
  * Sound Menu


Writing our userscript
-----------------------
For this tutorial I'm going to write an integration script for tumblr.com. Both the dashboard and tumblr blogs. Tumblr is pretty cool example as it's a simple site that offers lots of chances for integration. Just off the top of my head I can see tumblr using:
  * HUD Actions for making a new post, liking, reblogging, and following
  * Launcher icon count emblems for unread new posts
  * Quicklist action for returning to the Dashboard
  * Messaging menu integration for new questions and fanmail
  * Soundmenu integration for sound posts and videos.
A pretty rockin' list if you ask me.


Step zero. Getting the code and the bits you need to write your script.
----------------------------------------------------------------------
This isn't a real step, this is prerequisite and numbering from 0 is always a fun programmer joke. You laughed right?

A great way of exploring this API is in the developer console. Ctrl+Shift+K in Firefox will open it, and allow you to test these code snippets without having to reinstall the script and restart your browser.


Step one half. Getting an Icon ready.
-------------------------------------
You'll need a 128px, a 64px and a 52px icon. Run the build-icon-list script to add your icons to the iconlist.mk file so that they'll be installed. This script only finds svg icons. If you've got .png files (svg preferred!) Add them to the list manually. You can also use a remote url if there's a suitable icon on the site you're trying to integrate.


Step one. Integrate. Get a Launcher Icon and Switcher Icon going.
------------------------------------------------------------------------
// ==UserScript==
// @name          tumblr-unity-integration
// @include       https://tumblr.com/dashboard
// @version       @VERSION@
// @author        WebApps Team
// @require       utils.js
// ==/UserScript==

window.Unity = external.getUnityObject(1.0);

Unity.init({ name: "Tumblr",
             iconUrl: "icon://Tumblr",
             onInit: null });

That's it. The // bits are boilerplate for the manifest file. The important bit that does the integration are getting the unity object into your DOM. window.Unity = external.getUnityObject and Unity.init. Here you establish the name of your webapp (the name parameter), the icon (the iconUrl parameter), and what to do once the page is loaded (the onInit parameter).

Want to test this? Open up tumblr in firefox, and then open the developer console and enter the bits from above. You can leave out the parts that are preceeded by //.

BAM. Should now have an icon in your launcher and switcher.
In our scripts it's commonplace to add some code like this to filter out pages that don't have the DOM elements we need.

function isCorrectPage() {
    var i, ids = ['content', 'tabs_outter_container'];;

    for (i = 0; i < ids.length; i++) {
        if (!document.getElementById(ids[i])) {
            return false;
        }
    }

    return true;
}

if (isCorrectPage()) {
	Unity.init({ name: "Tumblr",
                 iconUrl: "icon://Tumblr",
                 onInit: null });
}

NICE. Now let's start getting integrating tumblr deeper into our desktop.
First thing we're going to do is change our onInit to a callback that will setup our integration.

function setupTumblr()
{
}

if (isCorrectPage()) {
	Unity.init({ name: "Tumblr",
                 iconUrl: "icon://Tumblr",
                 onInit: wrapCallback(setupTumblr) });
}

A little note: there are some convenience functions in utils.js.in that we use in these scripts. wrapCallback is one of them. You'll see others used. Refer to utils.js.in for more information.


Posting via HUD
---------------
Tumblr lets us post Text, Photos, Quotes, Videos, Links, Chat, and Audio.
These are all really easy, they are just links.

function setupTumblr()
{
	Unity.addAction('/Post/Text', makeRedirector('http://tumblr.com/new/text'));
	Unity.addAction('/Post/Link', makeRedirector('http://tumblr.com/new/link'));
	Unity.addAction('/Post/Chat', makeRedirector('http://tumblr.com/new/chat'));
	Unity.addAction('/Post/Photo', makeRedirector('http://tumblr.com/new/photo'));
	Unity.addAction('/Post/Quote', makeRedirector('http://tumblr.com/new/quote'));
	Unity.addAction('/Post/Audio', makeRedirector('http://tumblr.com/new/audio'));
	Unity.addAction('/Post/Video', makeRedirector('http://tumblr.com/new/video'));
}

Voila! Now we've got HUD integration for posting any type. Simply summon the HUD and type 'post photo' and you'll be taken to a new photo post page. Rockin'.



Getting a count on the launcher icon
------------------------------------
Very simple. We just need to parse the page for the count, and then set the count. The API for this is very simple.

function setupTumblr()
{
	function setLauncherCount() {
    // we want the total count for new posts and messages on the launcher, so we sum the two.
    var i, total =0 , elements = document.getElementsByClassName("tab_notice_value");
    for (i = 0; i < elements.length; i++) {
      var count = elements[i].innerHTML;
      if (count > 0) {
        total += parseInt(count);
      }
    }
    if (total > 0) {
      Unity.Launcher.setLauncherCount(total);
    } else {
      Unity.Launcher.clearCount();
    }
  }

	...
	...

	setInterval(wrapCallback(checkLauncherCount), 5000);
}

Note that we do this on a timer. every 5000 ms (5 seconds) we check the count, and reset it when it's empty.


Putting questions and fanmail into the messaging menu
------------------------------------------------------
Tumblr alerts you when you've got a new question, similar to the unread post count.

function checkNewMessageCount() {
    var ib = document.getElementById("inbox_button");
    var count = document.evaluate('a//span', ib, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (count == null) {
      count = 0;
    } else {
      count = parseInt(count.innerHTML);
    }
    Unity.MessagingIndicator.showIndicator(_("Inbox"), 
      { count: count,
        onIndicatorActivated: makeRedirector("http://tumblr.com/inbox");
      }
    );
  }

This will make an indicator under the tumblr subheading with a count for new messages that when clicked, will take our user to the inbox page.


Integrating with tumblr blogs
------------------------------
We're going to do a little bit of refactoring. Tumblr has two components, the dashboard and blogs. Our integration this far has been with the dashboard, but there are some nice things we can do on blogs.
So let's start by differentiating the two. Tumblr blogs have tumblr controls on the top right hand corner.

function isDashboard() {
    var i, ids = ['content', 'tabs_outter_container'];;

    for (i = 0; i < ids.length; i++) {
        if (!document.getElementById(ids[i])) {
            return false;
        }
    }

    return true;
}

function isTumblrBlog() {
  return document.getElementById("tumblr_controls");
}

if (isDashboard() || isTumblrBlog()) {
  Unity.init({ name: "Tumblr",
               iconUrl: "icon://Tumblr",
               onInit: wrapCallback(setupTumblr),
               domain: "tumblr.com" });
}

Next we'll need to rework our setupTumblr function to take the two cases into account. Let's move what used to be setupTumblr into a new function, integrateDashboard and rework setupTumblr to look like this,

function setupTumblr()
{
  if (isDashboard()) {
    integrateDashboard();
  } else if (isTumblrBlog()) {
    integrateTumblrblog();
  }
}

And what's left is to integrate the blogs.


Adding quicklist actions to the launcher icon
----------------------------------------------

function integrateTumblrblog()
{
  Unity.Launcher.addAction(_("Dashboard"), makeRedirector("http://tumblr.com/dashboard"));
}


Testing your script
--------------------
As always proper automated tested is imperative! Most of the test is setup boiler plate. Check the source !!!LINK!!! to see the full test.

Tumblr requires authorization so we need some code to log in. The username and password get stored in a password file. in the scripts/ folder, where the test runner is. You'll see passwords.example. Copy it to a file names passwords, then modify it with valid usernames and passwords for the scripts you want to test, like so,

{
        "Tumblr.user.js.in": {
                "login": "cooltumblrdude@coolemail.xxx",
                "password": "webappsrcool4u"
        }
}

we then need to modify our test script to insert the values into the login form

    onLoad: function () {
        function authorize(login, pass) {
            var name = document.getElementById("signup_email");
            var password = document.getElementById("signup_password");
            name.value = login;
            password.value = pass;
            password.form.submit();
        }

...
...

Next we validate our call log. This is how we test. TODO: How to get the call log for matching up log indices.

    validateCallLog: function (log) {
        assertEquals(log[0].func, "Unity.init");
        assertEquals(log[0].args[0].name, "Tumblr");
        assertEquals(log[3].func, "Unity.MessagingIndicator.showIndicator");

        var i, actionsCount = 0;
        for (i = 0; i < log.length; i++) {
            if (log[i].func === 'Unity.addAction') {
                actionsCount++;
            }
        }
        assertEquals(actionsCount, 7);
    },

    scriptName: 'Tumblr.user.js.in'
};
