Ubuntu logo

Developer

Tutorial

You have a website, and you want to integrate it with Ubuntu using the webapps API. That is great. You should first review the API documentation. This guide is for people who want to integrate a web application with Ubuntu, but don’t have access to the site’s source code. Webapp developers should integrate this API into their site’s JavaScript. For this tutorial we will be using Firefox. Feel free to use Chromium, but you might have to do a little translation of the parts where we will be using the web console.

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
  • Launcher icon progress bar
  • HUD Actions
  • Messaging menu
  • Sound menu
  • Drag and Drop

Requirements

To follow this tutorial, you will need the following:

  • Ubuntu 12.04 LTS or later - get Ubuntu
  • Only for Ubuntu 12.04 LTS users: Ubuntu Webapps Preview - install Ubuntu Webapps Preview
    • sudo add-apt-repository ppa:webapps/preview
    • sudo apt-get update
    • sudo apt-get install unity-webapps-preview
    • Restart your session (logout and back in)
  • Get webapps-applications branch from launchpad.net
    • bzr branch lp:webapps-applications

Writing your userscript

In this tutorial we are going to write an integration script for Tumblr. Integrating both the Dashboard and Tumblr blogs with Ubuntu. Tumblr makes a pretty cool example as it is a simple site that offers a lot of chances for integration. For instance:

  • 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 0. Getting the code and exploring the API

I numbered this step 0 because before beginning to write any code you should always explore an API. Once you have looked at the API, and are ready to begin coding create a new text file in the src/ directory named Tumblr.user.js.in. We’ll be working in this file from here on.

A great way of exploring this API is in the Firefox web console. Ctrl+Shift+K in Firefox will open the console which will allow you to test these code snippets without reinstalling the script and restarting your browser over and over. This is a great tool for writing your script. While you are working you can copy from your text editor and paste your whole script or just a snippet into the console to test. Some of the functions used (click, makeRedirector, …) come from utils.js.in. You may need to copy those functions into the JavaScript console before the Tumblr integration code if the snippet you are playing with makes use of a function defined in utils.js.

And to reiterate; the API reference is available online, here.

Step 1. Getting an Icon ready

If you are really itching to start coding you can skip this step, and just pass an empty string to the iconUrl parameter of Unity.init, but at some point you will need to pepare a 128px, a 64px and a 52px icon. Put each in its appropriate sub-directory of icon-themes/unity-webapps-applications/apps. Run the webapps-applications/icon-themes/build-icon-list.sh script to add your icons to the webapps-applications/icon-themes/iconlist.mk file.

Alternatively you can use a remote url to an image available on the web.

Now time to Integrate. Get a Launcher Icon and Switcher Icon going

Now we begin coding our integration script.

<br />
// ==UserScript==<br />
// @name          tumblr-unity-integration<br />
// @include       https://tumblr.com/dashboard<br />
// @version       @VERSION@<br />
// @author        WebApps Team<br />
// @require       utils.js<br />
// ==/UserScript==</p>
<p>window.Unity = external.getUnityObject(1.0);</p>
<p>Unity.init({ name: &quot;Tumblr&quot;,<br />
            iconUrl: &quot;icon://Tumblr&quot;,<br />
            onInit: null });<br />

That is it. This very simple user script is the base for all Ubuntu webapp integration scripts.

The // bits are boilerplate for a manifest file that registers available integration scripts to the browser. You do not need to worry about this file. It is automatically generated and installed when the userscripts get installed.

<br />
window.Unity = external.getUnityObject(1.0);<br />

This loads the unity object into a property of your DOM window so that you can access it in
The rest of your script. Without this you have no access to the Unity API.

<br />
Unity.init({ name: &quot;Tumblr&quot;,<br />
            iconUrl: &quot;icon://Tumblr&quot;,<br />
            onInit: null });<br />

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?

  1. Start Firefox
  2. Open the Web console (Tools > Web developer > Web console)
  3. Copy and paste the full code snippet into the console input box
  4. Go to www.tumbler.com
  5. Accept the prompt to install your script

BAM. You should now have a question mark icon in your launcher and applications switcher (try to press Alt+Tab).

In our scripts it is commonplace to add some code like the snippet below to filter out pages that do not have the DOM elements we need.

<br />
function isCorrectPage() {<br />
   var i, ids = ['content', 'tabs_outter_container'];;</p>
<p>   for (i = 0; i &lt; ids.length; i++) {<br />
       if (!document.getElementById(ids[i])) {<br />
           return false;<br />
       }<br />
   }</p>
<p>   return true;<br />
}</p>
<p>if (isCorrectPage()) {<br />
       Unity.init({ name: &quot;Tumblr&quot;,<br />
                iconUrl: &quot;icon://Tumblr&quot;,<br />
                onInit: null });<br />
}<br />

NICE. Time to start doing some deeper integrating of Tumblr into our desktop. First thing we are going to do is change our onInit to a callback that will setup our integration.

<br />
function setupTumblr()<br />
{<br />
}</p>
<p>if (isCorrectPage()) {<br />
       Unity.init({ name: &quot;Tumblr&quot;,<br />
                iconUrl: &quot;icon://Tumblr&quot;,<br />
                onInit: wrapCallback(setupTumblr) });<br />
}<br />

A little note: there are some convenience functions in utils.js.in that we use in these scripts. wrapCallback is one of them. You will 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.

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

Voila! Now we have got HUD integration for posting any type. Simply summon the HUD (press ALT key) and type “post photo” and you will 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.

<br />
function setupTumblr()<br />
{<br />
       function setLauncherCount() {<br />
   // we want the total count for new posts and messages on the launcher, so we sum the two.<br />
   var i, total =0 , elements = document.getElementsByClassName(&quot;tab_notice_value&quot;);<br />
   for (i = 0; i &lt; elements.length; i++) {       var count = elements[i].innerHTML;       if (count &gt; 0) {<br />
       total += parseInt(count);<br />
     }<br />
   }<br />
   if (total &gt; 0) {<br />
     Unity.Launcher.setLauncherCount(total);<br />
   } else {<br />
     Unity.Launcher.clearCount();<br />
   }<br />
 }</p>
<p>       &#8230;<br />
       &#8230;</p>
<p>       setInterval(wrapCallback(checkLauncherCount), 5000);<br />
}<br />

Note that we do this on a timer. every 5000 ms (5 seconds) we check the count, and reset it when it is empty. You will see the notification count on tumblr’s launcher icon.

Putting questions and fanmail into the messaging menu

Tumblr alerts you when you have got a new question, similar to the unread post count.

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

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. Click the messaging icon found on right of desktop panel to see it.

Integrating with tumblr blogs

We are 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. We will begin by differentiating the two in code. Tumblr blogs have tumblr controls on the top right hand corner.

<br />
function isDashboard() {<br />
   var i, ids = ['content', 'tabs_outter_container'];;</p>
<p>   for (i = 0; i &lt; ids.length; i++) {<br />
       if (!document.getElementById(ids[i])) {<br />
           return false;<br />
       }<br />
   }</p>
<p>   return true;<br />
}</p>
<p>function isTumblrBlog() {<br />
 return document.getElementById(&quot;tumblr_controls&quot;);<br />
}</p>
<p>if (isDashboard() || isTumblrBlog()) {<br />
 Unity.init({ name: &quot;Tumblr&quot;,<br />
              iconUrl: &quot;icon://Tumblr&quot;,<br />
              onInit: wrapCallback(setupTumblr),<br />
              domain: &quot;tumblr.com&quot; });<br />
}<br />

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,

<br />
function setupTumblr()<br />
{<br />
 if (isDashboard()) {<br />
   integrateDashboard();<br />
 } else if (isTumblrBlog()) {<br />
   integrateTumblrblog();<br />
 }<br />
}<br />

On to integrating tumblogs!

Adding quicklist actions to the launcher icon

<br />
function setupQuicklist()<br />
{<br />
 Unity.Launcher.addAction(_(&quot;Dashboard&quot;), makeRedirector(&quot;http://tumblr.com/dashboard&quot;));<br />
}<br />

Adds a link to your dashboard right to the launcher icon. As an exercise, try extending this function to be an item that takes you to the dashboard when you are viewing a blog, and links to your blogs from the “My Blogs” control when you are viewing the dashboard.

Testing your script

As always proper automated tested is imperative! Most of the test is setup boiler plate. Check the source to see the full test. This is the only part of the tutorial that can’t be done in the developer console.

Create a new file named Tumblr.js in the tests/ directory. 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,

<br />
{<br />
       &quot;Tumblr.user.js.in&quot;: {<br />
               &quot;login&quot;: &quot;cooltumblrdude@coolemail.xxx&quot;,<br />
               &quot;password&quot;: &quot;webappsrcool4u&quot;<br />
       }<br />
}<br />

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

<br />
   onLoad: function () {<br />
       function authorize(login, pass) {<br />
           var name = document.getElementById(&quot;signup_email&quot;);<br />
           var password = document.getElementById(&quot;signup_password&quot;);<br />
           name.value = login;<br />
           password.value = pass;<br />
           password.form.submit();<br />
       }</p>
<p>&#8230;<br />
&#8230;<br />

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

<br />
   validateCallLog: function (log) {<br />
       assertEquals(log[0].func, &quot;Unity.init&quot;);<br />
       assertEquals(log[0].args[0].name, &quot;Tumblr&quot;);<br />
       assertEquals(log[3].func, &quot;Unity.MessagingIndicator.showIndicator&quot;);</p>
<p>       var i, actionsCount = 0;<br />
       for (i = 0; i &lt; log.length; i++) {<br />
           if (log[i].func === &#8216;Unity.addAction&#8217;) {<br />
               actionsCount++;<br />
           }<br />
       }<br />
       assertEquals(actionsCount, 7);<br />
   },</p>
<p>   scriptName: &#8216;Tumblr.user.js.in&#8217;<br />
};<br />

To run the test, execute this bit of shell in the toplevel unity-webapps directory
$ ./scripts/test.sh tests/Tumblr.js

Conclusions

This tutorial demonstrates how to easily create webapps for the Unity shell to provide integrated desktop experience for web services.