Changes

Jump to: navigation, search

Labs/Ubiquity/Ubiquity Source Tip Author Tutorial

43,184 bytes added, 22:39, 10 September 2008
New page: Back to Labs/Ubiquity. Author: Aza Raskin, Blair McBride, Abimanyu Raja, Jono DiCarlo, Atul Varma = The Ubiquity 0.1 Command Tutorial = The great power of Ubiquity—from a deve...
Back to [[Labs/Ubiquity]].

Author: Aza Raskin, Blair McBride, Abimanyu Raja, Jono DiCarlo, Atul Varma

= The Ubiquity 0.1 Command Tutorial =

The great power of Ubiquity—from a developer standpoint—is how easy it is to create commands. With only a couple of lines of Javascript, Ubiquity enables even casual web developers to drastically enhance the features of the browser. From an 8-line command to insert a contact's email address in any text field, to a 50-line Twitter integration, this tutorial walks you through the process of being generative with Ubiquity.

The rest of this page documents the command developer API as it currently stands in the tip of the source tree. This API is '''constantly in flux'''. We will attempt to keep this documentation up to date, but be warned that the API documented here is only usable to people who have the source checkout of Ubiquity. If you have downloaded the released Ubiquity extension file, though, you'll have an older version of the API. That version is documented on [[Labs/Ubiquity/Ubiquity 0.1 Author Tutorial|Ubiquity 0.1 Author Tutorial]].

== Real Time Development ==

Ubiquity doesn't require you to restart Firefox as you develop. That's a barbaric act, and we want none of it. Instead, Ubiquity reloads the commands every time it is summoned. When you are using the built-in editor then you don't even need to save!

To open the Ubiquity command editor, summon Ubiquity (control/alt + space) and use the "command-editor" command. Throughout this tutorial, when we want you to run a command in Ubiquity, we'll say '''Ubiq''' it. For instance, to open the editor, just Ubiq "command-editor".

In the following examples, just type in this editor. Updates happen the next time you summon Ubiquity.

= Hello World: The First Command =

== Just a Function: As Simple as it Gets ==

Let's start with the standard programing trope: printing "Hello, World!". In Ubiquity, commands are simply functions with various attributes tacked on. We'll start by manually making a function to define a command, but we'll quickly move to using a more elegant method.

In the command editor type the following:

<pre>
function cmd_hello_world() {
displayMessage( "Hello, World!");
}
</pre>

Now try Ubiq-ing "hello-world". You'll see that "Hello, World!" is immediatly displayed on the screen. If you are on Mac OSX with [http://en.wikipedia.org/wiki/Growl_(software) Growl] installed the message will appear as a Growl notification. If you are on Windows, then it will appears as a standard "toaster" notification in the bottom right-hand corner of the screen.

http://img367.imageshack.us/img367/7051/picture1ui2.png

http://img517.imageshack.us/img517/7726/picture2vx2.png

In Ubuntu 8.04 (Hardy Heron) this appears thus:

http://img293.imageshack.us/img293/7746/ubiqubuntuhelloworldeq9.png

If you don't have Growl installed on OSX, or aren't on a Windows XP/Vista or Ubuntu Hardy, then you won't get any sort of notification. That's something to be [http://labs.toolness.com/trac/ticket/19 worked on] in future released of Ubiquity.

There's not much in this command, so let's dive straight in. Any function that starts with <code>cmd_</code> automatically becomes a Ubiquity command. It's a little bit of namespace magic that makes development super-simple.

There are other prefixes that have other effects, like running code on page load (<code>pageLoad_</code>), and startup (<code>startup_</code>), but that's for a different tutorial.

Back to the example. The meat of the command is in the function <code>displayMessage()</code>, which displays the message in whichever way the operating system can.

You may be wondering why there is a hyphen in the name, instead of a space. That's because the Ubiquity natural language parser isn't yet smart enough to handle commands that are multiple words <i>and</i> arguments that are multiple words. It's something we'll be working on in the future.

== Using CreateCommand ==

For commands that are more complicated than our simple "hello-world" command, you can use the helper function <code>CmdUtils.CreateCommand()</code>, which takes an options dictionary. To redo the "hello-world" command using the convenience function, we'd write:

<pre>
CmdUtils.CreateCommand({
name: "hello-world",
execute: function() {
displayMessage( "Hello, World!" );
}
})
</pre>

This may not seem to be much of a win (and it isn't for simple commands) but as the commands become more complex, you'll see that it helps a lot. For one, you can name the command more freely with the method&mdash;unicode non-English characters are now fair game.

There are a number of other useful functions in the CmdUtils namespace. We don't yet have full documentation for these commands, but you'll get a sense of the useful ones in this tutorial. For more detailed information, take a look at [http://hg.toolness.com/ubiquity-firefox/file/9a6c9935da9f/ubiquity/chrome/content/cmdutils.js cmdutils.js].

== Adding a Preview ==

http://img352.imageshack.us/img352/1002/picture3ex0.png

Let's add a preview to our new command. Previews give the user feedback about what a command does before it's executed. Previews are great for providing rich visual feedback like displaying a graphical representation of atmospheric conditions when using the weather command as shown above. Previews have the full expressive power of HTML, including animations, so there's a lot you can do with them.

One point of design: Preview code should never have side-effects. That is, a preview should never (without user interaction) change the state of the system.

For the "hello-world" command, we don't need anything fancy: just some help text that is more descriptive than the default "Executes the hello-world command."

<pre>
CmdUtils.CreateCommand({
name: "hello-world",
preview: "Displays a <i>salutary</i> greeting to the planet.",
execute: function() {
displayMessage( "Hello, World!" );
}
})
</pre>

Here the preview is an HTML-formatted string. The preview can also be a function. We'll get to that in the next section.

= The Date Command: The Second Command =

= Setting the Selection =

I often forget what day it is. That may be because I need to go outside more often, but, like any programmer, I generally solve my problem's symptoms with technology rather then addressing the root cause. My solution is to create a command that inserts the date at the location of the cursor.

<pre>
CmdUtils.CreateCommand({
name: "date",
execute: function() {
var date = new Date();
CmdUtils.setSelection( date.toLocaleDateString() );
}
})
</pre>

The new function here is <code>setSelection()</code>. This inserts the passed-in text onto the page at the location of the cursor. If the cursor is in an editable text or rich-text fields, the text gets dumped there. If the cursor isn't in an editable area, <code>setSelection()</code> will still be able to insert the date. (Even when it isn't displayed, Firefox always keeps track of a cursor position. To see it, type F7.) Try going to a page, selecting some non-mutable text, and using the command. See, it works! This is particularly useful for commands like "translate", where you want to replace non-editable text with its translation.

The <code>toLocalDateString()</code> function is native to Javascript, so if you're not familiar with it check out the documentation for the Javascript [http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Date Date object].

== A Better Preview ==

It's time to add a better preview to the date command. Let's have the preview show the date, so that the user will know what to expect when they execute the command. (As a side benefit the user doesn't even need to execute the command to do a quick check of the day.)

<pre>
CmdUtils.CreateCommand({
name: "date",

_date: function(){
var date = new Date();
return date.toLocaleDateString();
},

preview: function( pblock ) {
var msg = 'Inserts todays date: "<i>${date}</i>"';
pblock.innerHTML = CmdUtils.renderTemplate( msg, {date: this._date()} );
},

execute: function() {
CmdUtils.setSelection( this._date() );
}
})
</pre>

We've done two things here. The first was to factor out the code for getting the date into the <code>_date()</code> function. This way we don't break [http://en.wikipedia.org/wiki/Don%27t_repeat_yourself DRY] by repeating code across the preview and execute functions. Notice that to access the <code>_date()</code>, we use the <code>this</code> keyword.

The second thing we've done is to add a preview function. The first argument is the DOM element that gets displayed as the preview for your command. Modify <code>pblock</code> and you modify the preview. In this case, we set the <code>innerHTML</code> of the preview block to be the message we want.

The other thing I've done is to do some string formatting using the <code>renderTemplate()</code> function. This takes a template string and performs the appropriate substitution given the passed-in JSON object. Templates can handle a wide range of functionality, as we are currently using TrimPath's [http://code.google.com/p/trimpath/wiki/JavaScriptTemplates JavascriptTemplates]. You should read their site for more documentation. Although JavascriptTemplates has some nice properties, we are contemplating moving to [http://mjtemplate.org/ MJT] sometime soon.

Previews display something meaningful to the user immediately. If you have a preview that requires an AJAX request&mdash;say, to fetch some search results&mdash;that call might take a while to return. In the meantime, your command should display a placeholder preview giving the user feedback.

<pre>
preview: function( pblock ) {
pblock.innerHTML = "This will show until the AJAX request returns";
// AJAX request
pblock.innerHTML = getFromServer();
},
</pre>

In the future, we may work on streamlining this process.

== Documentation and Metadata ==

Before you share your command with the world, you should consider adding some attributions to the code:

<pre>
CmdUtils.CreateCommand({
name: "date",
homepage: "http://azarask.in/",
author: { name: "Aza Raskin", email: "aza@mozilla.com"},
contributors: ["Atul Varma"],
license: "MPL",

/* THE REST OF THE CODE HERE */
})
</pre>

And you should <em>definitely</em> add some documentation:

<pre>
CmdUtils.CreateCommand({
name: "date",
homepage: "http://azarask.in/",
author: { name: "Aza Raskin", email: "aza@mozilla.com"},
contributors: ["Atul Varma"],
license: "MPL",
description: "Inserts today's date.",
help: "If you're in an editable text area, inserts today's date, formatted for the current locale.",

/* THE REST OF THE CODE HERE */
})
</pre>

The <code>.description</code> and <code>.help</code> attributes are both automatically displayed alongside your command's name on the command-list page. (The user can get to this page at any time by issuing the "command-list" command.) HTML tags can be used in both of these strings.

Description is a one-line summary of what the command does, while Help is a longer description that can include examples, caveats, and so on. If your command is simple enough that all you have to say about it fits in one line, it's OK to use a description alone and leave out the help.

== Sharing it with the World ==

Now that we've got our awesome new "date" command, let's share it with the world. All you have to do is put it the javascript file on the web somewhere, and make an html page linking to it with "link rel".

<pre><link rel="commands" href="http://path-to-js" name="Title Goes Here" /></pre>

''Note: your webserver must serve .js files as 'application/x-javascript'. The mime-type 'text/javascript' is silently ignored.
''

Anyone with Ubiquity who visits will get a message offering them the choice of subscribing to your command.

[[Image:Subscribe.png]]

If the user chooses to subscribe to a command from an untrusted source, they will get a security warning message before they can install the command. (And in Ubiquity 0.1, ALL sources are considered untrusted, so don't take it personally!) Because Ubiquity commands can execute arbitrary javascript with chrome privileges, subscribing to a command from a website means allowing that site full access to do whatever it wants to your browser. We want to make sure people understand the dangers before subscribing to commands, so we made the warning page pretty scary.

[[Image:Warning.PNG]]

In the future, we're going to have something set up that we call a "trust network". When you try out a Ubiquity command from a website, and determine that the command is safe (or unsafe), you'll be able to leave an approval (or a warning). When your friends with Ubiquity installed visit the same site, they'll see the approval or the warning that you left. In this way, users will be able to rely on the judgement of other people they already know and trust in order to help them make decisions about whether a command is safe to install or not.

By the way, the reason we call it "subscribing" to a command, rather than "installing" a command, is that if the javascript file changes -- if the site owner adds new commands, removes old commands, or updates existing commands -- all users subscribed to that URL will automatically get the updates. This will be very convenient for both users and developers, but it will also introduce another type of security risk: just because you decided a command was safe at one point in time doesn't mean that the command will always remain safe. For this reason, we'll need to make sure that the trust network keeps track of when commands have been modified, and notifies users of changes that may make a command unsafe.

== Map Me! Location, Snapshots, and Inserting HTML ==

The "map" command that comes with Ubiquity is fairly powerful. It's also fairly complicated&mdash;well, comparatively. It's still only a couple hundred lines of code. The command, though, can get even more useful. Imagine being able to select some houses on Craigslist, or a list of restaurant names, and Ubiq "map these" to get just the map you want. The concept of "these" puts the power of mashups into the users hands. But I digress. Let's make a simple command that inserts a map of your current location.

In this command, we use the Google [http://code.google.com/apis/maps/documentation/staticmaps/ static map API] and the Ubiquity function <code>CmdUtils.getGeoLocation()</code> to insert a map of your current location. Ubiquity currently uses the [http://www.maxmind.com/app/api MaxMind] API for trying to guess your location from your IP. That will probably change in the future.

<pre>
CmdUtils.CreateCommand({
name: "map-me",

_getMapUrl: function() {
var loc = CmdUtils.getGeoLocation();
var mapUrl = "http://maps.google.com/staticmap?";

var params = {
center: loc.lat + "," + loc.long,
size: "500x400",
zoom: 14,
key: "ABQIAAAAGZ11mh1LzgQ8-8LRW3wEShQeSuJunOpTb3RsLsk00-MAdzxmXhQoiCd940lo0KlfQM5PeNYEPLW-3w"
};

return mapUrl + jQuery.param( params );
},

preview: function( pblock ) {
var msg = "Inserts a map of your current location: <br/>";
msg += "<img src='%s'/>".replace( /%s/, this._getMapUrl() );
pblock.innerHTML = msg;
},

execute: function( ) {
CmdUtils.getImageSnapshot( this._getMapUrl(), function(imgData) {
CmdUtils.setSelection( "<img src='" + imgData +"'/>");
})
}
})
</pre>

There are three new things here: <code>CmdUtils.setSelection</code> to set HTML (yep, it can do that); the use of <code>CmdUtils.getGeoLocation()</code>; and using <code>CmdUtils.snapshotImage()</code> to capture the bits for the image.

I find getting the location&mdash;as imprecise as IP-based location can be&mdash;useful for doing sensible defaults for location-based commands, like Yelp. <code>CmdUtils.getGeoLocation()</code> returns an object which has the following properties: city, state, country, lat, and long.

Why do we need to use <code>CmdUtils.snapshotImage()</code>? Because the Google Maps API requires a key that is tied to a particular URL. If we naively inject the image tag into a random web page, the image won't load because the key doesn't match that random web page's URL. Thus, we use the <code>snapshotImage()</code> function to convert the image into a [http://en.wikipedia.org/wiki/Data:_URI_scheme data url].

There's also a <code>CmdUtils.snapshotWindow</code> function, which allows you to get the image data for any tab/window. The function takes a window as the first paramater, and a callback for the second.

= Commands with Arguments =

== Echo ==

We'll be working towards making some fun and useful commands&mdash;commands that let you control the seething tendrils of the internet with your little pinky. But, let's start by making a simple command to echo back whatever you type.

<pre>
CmdUtils.CreateCommand({
name: "echo",
takes: {"your shout": noun_arb_text},
preview: function( pblock, theShout ) {
pblock.innerHTML = "Will echo: " + theShout.text;
},
execute: function( theShout ) {
var msg = theShout.text + "... " + theShout.text + "......";
displayMessage( msg );
}
})
</pre>

This says that the command "echo" takes one argument which is arbitrary text. Whatever text the user enters will get wrapped in an input object and passed into both the preview and execute function.

Ubiquity takes care of parsing the user's input, so you don't need to worry about handling prounoun substitution or any of the other natural-language-like features of the Ubiquity parser. Try selecting some text on a page, and Ubiq "echo this". Ubiquity should now echo the selected text.

=== The Input Object ===

The input object that your execute and preview functions receive has the following attributes:

<pre>
inputObject.text // a string of the input in plain text, without formatting
inputObject.html // a string of the input in formatted html, including tags
inputObject.data // for non-text input types, an arbitrary data object
inputObject.summary // for very long inputs, an abbreviated display string
</pre>

Our example command only cares about the <code>.text</code> attribute of the input, because it simply wants plain text. Often, when the user invokes your command by typing a few short words into the input box, <code>.text</code>, <code>.html</code>, and <code>.summary</code> will all have exactly the same value, and <code>.data</code> will be null. Many, if not most, commands that you write will only care about the text value. Nevertheless, the other versions of the input data are provided to you in case they differ from <code>.text</code> and in case your command has a use for them.

=== Introduction to Noun Types ===

Notice that we specified the type of argument to expect by passing in an object &mdash; in this case, the predefined <code>noun_arb_text</code> object, which accepts any arbitrary text as a valid argument. If we had wanted to restrict the inputs that our command could take, we could have used a more specific noun-type object to restrict the scope of the argument: for instance, <code>noun_type_date</code> to accept only dates (like the "check-calendar" command) or <code>noun_type_language</code> to accept only names of languages (like the optional modifiers for the "translate" command).

The benefit of specifying a more restrictive noun-type is that it helps the Ubiquity parser generate better suggestions and auto-completions based on user-input. For instance, if the user has a date selected, commands that operate specifically on dates are more likely to be apropos than commands that take arbitrary text, so Ubiquity can suggest the date-specific commands first.

There are many types of nouns that a command could conceivably take: people, dates, places, tabs, etc. Many of these noun-types aren't implemented yet, and most of the them currently have a lack-luster implementation. This is one of the areas where Ubiquity could use the greatest help. Noun-types enable creating compelling user experiences, with minimal amounts of code. It also allows for code-reuse across numerous commands.

Once you are familiar with writing commands, you should check out the [http://hg.toolness.com/ubiquity-firefox/file/6caa9d66b3bb/ubiquity/chrome/content/nlparser/en/nountypes.js nountypes.js], which has the implementation for most of the noun-types. You can see what noun types are already available for your commands to use, what still needs to be written, and where the existing implementations could use improvement &mdash; and then come [[Labs/Ubiquity#Participation|get involved]] to help us improve them.

== Insert Email: Commands with Specific Argument Types ==

Let's take a look at one of the special noun-types: <code>noun_type_contact</code>. This lets Ubiquity know to expect a person (either by name or email address). By using the noun-type, Ubiquity will also autocomplete to known people while the user is entering the command. This is what the built-in Ubiquity command "email" uses.

At the moment, Ubiquity figures out what people you know through reading your Gmail contacts. In this prototyped version, you'll need to use Gmail and be logged in for for Ubiquity to know who you know. Eventually, we'd like to be able to interface with all major web-mail sites, as well as desktop software like Thunderbird.

Enough rambling. It's time for a command. I constantly find that I need to fetch someone's email address to paste into a text field because I don't know it off-hand. This command solves that by letting you insert someone's email address using autocomplete.

<pre>
CmdUtils.CreateCommand({
name: "insert-email",
takes: {"person": noun_type_contact},
preview: "Inserts someone's email address by name.",
execute: function( email ) {
CmdUtils.setSelection( email.text );
}
})
</pre>

This one command sums up what I love about Ubiquity. In 8 lines of code, I can fundamentally enhance the browser's functionality. Doing the same thing using a normal Firefox extension methodology takes pages and pages of code&mdash;and the interface would take more thought still. Doing the same thing using a bookmarklet would require a server-side component (to get around cross-site Ajax request ban) as well as forcing the user to give up their email password.

Ubiquity increases the surface area of innovation for the browser many-fold, by making anyone who can write simple Javascript into an agent for bettering the browser and the open Web.

== TinyURL: Network Calls and jQuery ==

Often while writing emails, I'll discover that I've pasted in a URL long enough to be used for unfortunate analogies. I'd like to be able to quickly turn that into a [http://en.wikipedia.org/wiki/Tinyurl TinyURL]&mdash;but the process of making a TinyURL involves lots of fiddly steps. Ubiquity to the rescue.

Because we include [http://en.wikipedia.org/wiki/jQuery jQuery] with Ubiquity, it is simple to perform Ajax calls as well as parse returning data. TinyUrl.com provides an easy to use RESTful API where you pass a URL and it returns its shortened form. We use that API in this command.

<pre>
CmdUtils.CreateCommand({
name: "tinyurl",
takes: {"url to shorten": noun_arb_text},
preview: "Replaces the selected URL with a TinyUrl.",
execute: function( urlToShorten ) {
var baseUrl = "http://tinyurl.com/api-create.php";
var params = {url: urlToShorten.text};
jQuery.get( baseUrl, params, function( tinyUrl ) {
CmdUtils.setSelection( tinyUrl );
})
}
})
</pre>

Although I used the <code>noun_arb_text</code> command noun-type, I should have used the <code>noun_type_url</code>&mdash;if such a thing existed. It doesn't yet.

jQuery is a powerful tool. With it, you can fairly effortlessly cherry-pick the data you need from RSS feeds, XML, and all sorts of other data formats. It also makes doing in-preview animations a breeze.

== Color: Creating Bounded Noun Types ==

Suppose you're writing a set of commands for artists and
web designers, and you know that several of the commands will operate
on colors. You'd like to be able to specify that certain commands
expect names of colors as arguments. Since there are a finite number of
named colors, you can define a custom noun type for them based on a list
of strings, like so:

<pre>
noun_type_color = new CmdUtils.NounType( "Color",
["red", "orange", "yellow", "green", "blue", "violet", "black", "white",
"grey", "brown", "beige", "magenta", "cerulean", "puce"] // etc...
);
</pre>

Note that we gave the new object a name starting with "<code>noun_</code>".
The Ubiquity command loader automatically detects objects starting with
"<code>noun_</code>" as custom noun-types, in the same way as it auto-detects
functions starting with "<code>cmd_</code>" as custom commands.

Once you've defined a custom noun-type, you can use it in as many commands
as you like, thus:

<pre>
CmdUtils.CreateCommand({
name: "get-color-code",
takes: {"color": noun_type_color},
preview: "Inserts the HTML hex-code for the given color.",
// etc...
</pre>

One benefit of creating the custom color noun-type is that if the user
has entered "get-color bl", for instance, Ubiquity will be able to suggest
"black" and "blue" as the two valid completions based on the input.

Of course, not every type of noun you'd be interested in can be represented
as a finite list. If you want to be able to accept or reject input based
on some algorithmic test, you can do so by creating your own noun-type
implementation instead of instantiating <code>CmdUtils.NounType</code>. There is an example of this in the section on the tab commands, below.

== Replace: Commands With Multiple Arguments ==

Commands, like the translate command, can take multiple (and possibly optional) arguments. Ubiquity takes care of the parsing&mdash;you don't have to worry about what order the user types them in, you'll just get passed a dictionary with the appropriate entries.

To illustrate that, let's make a simple regular-expression-based "replace" command. It will take three arguments: the thing to replace, the replacement, and the scope-text to do the replacing in. Here's the command:

<pre>
CmdUtils.CreateCommand({
name: "replace",
takes: {"what": noun_arb_text},
modifiers: {with: noun_arb_text, in: noun_arb_text},

preview: function( pblock, what, mods ) {
// args contains .with and .in, both of which are input objects.
var msg = 'Replaces "${whatText}" with ${withText} in ${inText}.';
var subs = {whatText: what.text, withText: mods.with.text, inText: mods.in.text};

pblock.innerHTML = CmdUtils.renderTemplate( msg, subs );
},

execute: function( what, mods ) {
// If the scope text isn't specified, use the current selection.
var text = mods.in.text || CmdUtils.getTextSelection();
var newText = text.replace( what.text, args.with.text, "i");
CmdUtils.setSelection( newText );
}
});

</pre>

(In earlier prototypes, modifier arguments could only accept a single-word value, but this has now been fixed.)

The modifiers argument takes a dictionary, where the key is the name of the argument and the value is the noun-type of the argument. In later releases we may include further options, like the ability to specify an argument as required/optional, etc.

The translate command is a good place to learn more about modifiers and the <code>noun_type_language</code>.

== Specifying Defaults for your Arguments ==

Having a sensible default for an argument can make a command much easier to use, because if the default is what the user wants, then the user doesn't have to type anything extra.

If a command specifies a default value for one of its arguments, that default value will be suggested whenever the user doesn't provide a value for the argument. A default value can be provided for a direct object like so:

<pre>
CmdUtils.CreateCommand({
name: "echo",
takes: {"your shout": noun_arb_text},
DODefault: "Hallooooooo!",
// etc. etc. etc...
});
</pre>

And for a modifier like so:

<pre>
CmdUtils.CreateCommand({
name: "replace",
takes: {"what": noun_arb_text},
modifiers: {with: noun_arb_text, in: noun_arb_text},
modifierDefaults: {with: "cheese"}
// etc. etc. etc...
});
</pre>

Aside from giving defaults to individual command arguments, you can also give a custom nounType a default() method which returns an argument suggestion. For example, this default() method of the URL nounType suggests the URL of the currently loaded page:

<pre>
var noun_type_url = {
_name: "url",
suggest: function(fragment) {
//etc etc etc...
},
default: function() {
var url = Application.activeWindow.activeTab.document.URL;
return CmdUtils.makeSugg(url);
}
}
</pre>

(Notice it returns a single suggestion, not a list. Currently only one default can be provided. Support for multiple default suggestions is planned for later inclusion.)

Arguments actually provided by the user are always used if available. If an argument is not provided, the default defined by the command argument will be used. If no default is defined by the command argument, only then will the default provided by the nounType be used. Finally, if no argument is provided by the user and no default is defined by either the command or the nounType, the argument will be left blank.

= Twitter: Putting It All Together =

We've now covered everything we need to cover in order to write a command that allows us to [http://en.wikipedia.org/wiki/Twitter Twitter] from Ubiquity. Many thanks to [http://theunfocused.net/moz/ubiquity/verbs/ Blair McBride] for writing this command. This is a fully functioning command: the browser takes care of the odd edge cases, like not being logged in.

<pre>
// max of 140 chars is recommended, but it really allows 160
const TWITTER_STATUS_MAXLEN = 160;

CmdUtils.CreateCommand({
name: "twitter",
takes: {status: noun_arb_text},

homepage: "http://theunfocused.net/moz/ubiquity/verbs/",
author: {name: "Blair McBride", homepage: "http://theunfocused.net/"},
license: "MPL",

preview: function(previewBlock, statusText) {
var previewTemplate = "Updates your Twitter status to: <br/>" +
"<b>${status}</b><br /><br />" +
"Characters remaining: <b>${chars}</b>";
var truncateTemplate = "<br />The last <b>${truncate}</b> " +
"characters will be truncated!";
var previewData = {
status: statusText.text,
chars: TWITTER_STATUS_MAXLEN - statusText.text.length
};

var previewHTML = CmdUtils.renderTemplate(previewTemplate,
previewData);

if(previewData.chars < 0) {
var truncateData = {
truncate: 0 - previewData.chars
};

previewHTML += CmdUtils.renderTemplate(truncateTemplate,
truncateData);
}

previewBlock.innerHTML = previewHTML;
},

execute: function(statusText) {
if(statusText.text.length < 1) {
displayMessage("Twitter requires a status to be entered");
return;
}

var updateUrl = "https://twitter.com/statuses/update.json";
var updateParams = {
source: "ubiquity",
status: statusText.text
};

jQuery.ajax({
type: "POST",
url: updateUrl,
data: updateParams,
dataType: "json",
error: function() {
displayMessage("Twitter error - status not updated");
},
success: function() {
displayMessage("Twitter status updated");
}
});
}
});
</pre>

= Switching Tabs =

The final command in this tutorial is for switching between tabs. The end goal is this: type a few keys to that matches the title of an open tab (in any window), hit return, and you've switched to that tab.

We'll write this command in two steps. The first step is creating a tab noun-type. The second step is using that noun-type to create the tab-switching command.

== Switching: Writing your own Noun-Types ==

A noun-type needs to only have two things: A name and a suggest function. Soon, we'll probably move to having a convenience <code>CmdUtils.CreateNounType()</code>, which will simplify things even more.

The name is what shows up when the command prompts for input. Suggest returns a list of input objects, each one containing the name of a matching tab. We're using [http://developer.mozilla.org/en/docs/FUEL FUEL] to interact with the browser, which is where the "Application" variable comes from.

<pre>
var noun_type_tab = {
_name: "tab name",

// Returns all tabs from all windows.
getTabs: function(){
var tabs = {};

for( var j=0; j < Application.windows.length; j++ ) {
var window = Application.windows[j];
for (var i = 0; i < window.tabs.length; i++) {
var tab = window.tabs[i];
tabs[tab.document.title] = tab;
}
}

return tabs;
},

suggest: function( text, html ) {

var suggestions = [];
var tabs = noun_type_tab.getTabs();

//TODO: implement a better match algorithm
for ( var tabName in tabs ) {
if (tabName.match(text, "i"))
suggestions.push( CmdUtils.makeSugg(tabName) );
}

// Return a list of input objects, limited to at most five:
return suggestions.splice(0, 5);
}
}
</pre>

The suggest method of a noun type always gets passed both text and html. If the input is coming from a part of a web page that the user has selected, these
values can be different: they are both strings, but the html value contains markup tags while the text value does not. The Tab noun type only cares about the plain text of the tab name, so we ignore the value of html.

We use the convenience function <code>CmdUtils.makeSugg()</code> to generate an
input object of the type that the Ubiquity parser expects. The full signature of this function is
<pre>
CmdUtils.makeSugg( text, html, data );
</pre>
but html and data are optional and need be provided only if they differ from text.

If the text or html input is very long, <code>makeSugg()</code> generates a summary for us, and puts it in the <code>.summary</code> attribute of the input object.

We could have accomplished mostly the same thing without calling <code>makeSugg()</code> by returning a list of anonymous objects like these:
<pre>
{ text: tabName,
html: tabName,
data: null,
summary: tabName };
</pre>

The input objects that our <code>.suggest()</code> method generates are the same objects that will eventually get passed in to the <code>execute()</code> and <code>preview()</code> methods of any commands that use this noun type.

== Switching Tabs: The Command ==

Now that we are armed with the tab noun-type, it is easy to make the tab-switching command. Again, we use FUEL to focus the selected tab.

<pre>
CmdUtils.CreateCommand({
name: "tab",
takes: {"tab name": noun_type_tab},

execute: function( directObj ) {
var tabName = directObj.text;
var tabs = noun_type_tab.getTabs();
tabs[tabName]._window.focus();
tabs[tabName].focus();
},

preview: function( pblock, directObj ) {
var tabName = directObj.text;
if( tabName.length > 1 ){
var msg = "Changes to <b style=\"color:yellow\">%s</b> tab.";
pblock.innerHTML = msg.replace(/%s/, tabName);
}
else
pblock.innerHTML = "Switch to a tab by name.";
}
})
</pre>

= Development Hints =

You now know all you need to know to get started developing useful Ubiquity commands of your own.

Here are some miscellaneous tips that didn't fit elsewhere on this page, that may make development easier for you.

== The Source Code of Built-In Commands ==

Looking at the source code of built-in commands and built-in noun types can be a very useful aid to development. If you have the source checkout of Ubiquity (see [https://wiki.mozilla.org/index.php?title=Labs/Ubiquity/Ubiquity_0.1_Development_Tutorial | the development tutorial] to find out how to get this), the source code can be found in the files:

ubiquity/chrome/content/builtincmds.js
ubiquity/chrome/content/nlparser/en/nountypes.js

If you don't have a checkout of the source code, you can view the latest version on the web here:

[http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/chrome/content/builtincmds.js builtincmds.js]
[http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/chrome/content/nlparser/en/nountypes.js nountypes.js]

== Interacting with Other Extensions ==

http://img363.imageshack.us/img363/1906/picture7cm5.png

There isn't much to say here besides that it's easy. For example, here's a command (thanks to [http://foyrek.com/lyrics.html Abimanyu Raja] for writing this code) that finds the lyrics for a song. You can simply Ubiq something like "get-lyrics wild international" but the command will also interface with the FoxyTunes extension (if it is installed) and add the currently playing song to the suggestion list. Interfacing with other extensions, too, is easy because you can view the source code for every Firefox extension.

<pre>
var noun_type_song = {
_name: "song name",
suggest: function( text, html ) {
var suggestions = [CmdUtils.makeSugg(text)];
if(window.foxytunesGetCurrentTrackTitle){
suggestions.push(CmdUtils.makeSugg(window.foxytunesGetCurrentTrackTitle()));
}
return suggestions;
}
}


CmdUtils.CreateCommand({
name: "get-lyrics",
takes: {song: noun_type_song},
preview: function(pblock, directObject) {

searchText = jQuery.trim(directObject.text);
if(searchText.length < 1) {
pblock.innerHTML = "Searches for lyrics of the song";
return;
}

var previewTemplate = "Searches for the lyrics of <b>${query}</b>";
var previewData = {query: searchText};
pblock.innerHTML = CmdUtils.renderTemplate(previewTemplate, previewData);

},
execute: function(directObject) {
var url = "http://www.google.com/search?q={QUERY}"
var query = directObject.text + " lyrics";
var urlString = url.replace("{QUERY}", query);
Utils.openUrlInBrowser(urlString);
}
});
</pre>

== Defining Synonyms for your Command ==



== Running on page load and startup ==

In order to run some code on page load, you simply have to prefix your function with <code>pageLoad_</code>. For example, if you want to say "Hi" every time a page is loaded, your code would look like this:

<pre>
function pageLoad_hi(){
displayMessage("hi");
}
</pre>

If you modify the function and want to see the changes, remember to first invoke Ubiquity. Although your function like above, might not be a Ubiquity command, this is necessary to refresh the cached code.

Similarly, if you want to run some code everytime Firefox starts up, you just have to prefix the function with <code>startup_</code> .

The awesome thing about these functions is the ability to develop whole Firefox extensions (that require minimal UI) as Ubiquity plugins in lesser lines of code. You don't need to worry about chrome.manifest or install.rdf. Another added benefit is that you never have to restart your Firefox during development unless of course, you are running code on Firefox startup.

<center>http://img388.imageshack.us/img388/3086/picture5eo9.png</center>

Here's the code for [http://foyrek.com/keyscape.js Keyscape] which is a Ubiquity command that makes use of <code>pageLoad</code> and <code>startup</code> to recreate the functionality of the [https://addons.mozilla.org/en-US/firefox/addon/339 Search Keys extension] by Jesse Ruderman. In line with Ubiquity's aim to let you do things quicker using your keyboard, this command lets you go to search results on Google by just pressing a number. It'll add hints to show the number of each link.

<pre>
//A lot of this code is borrowed from the Search Keys extension
//Many thanks to Jeese Ruderman

function startup_keyscape() {
window.addEventListener("keydown", keyscape_down, true);
}

function pageLoad_keyscape(doc){

var uri = Utils.url(doc.documentURI);
//If we are on about: or chrome://, just return
if(uri.scheme != "http")
return;

//Check if the page we are on is google
if( keyscape_isGoogle(uri) ){

for(var num=1; num<=10; num++){

var link = jQuery(doc.body).find('a.l')[num-1];

if( link ){

var hint = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
hint.style.color = "blue";
hint.style.background = "white";
hint.style.padding = "1px 2px 1px 2px";
hint.style.marginLeft = ".5em";
hint.appendChild(doc.createTextNode(num == 10 ? 0 : num));
link.parentNode.insertBefore(hint, link.nextSibling);
}
}
}
}

function keyscape_isGoogle(uri){
return uri.host.indexOf("google") != -1
&& (uri.path.substr(0,8) == "/search?"
|| uri.path.substr(0,8) == "/custom?");
}

function keyscape_down(event){

var doc = Application.activeWindow.activeTab.document;
var uri = Utils.url(doc.documentURI);

if( keyscape_isGoogle(uri) ){

var key = parseInt(event.keyCode || event.charCode);
var num;

if(48 <= key && key <= 57) //number keys
num = key - 48;
else if(96 <= key && key <= 105) //numeric keypad with numlock on
num = key - 96;
else
return;

//Don't do anything if we are in a textbox
//or some other related elements
var elt = window.document.commandDispatcher.focusedElement;

if (elt) {
var ln = new String(elt.localName).toLowerCase();
if (ln == "input" || ln == "textarea" || ln == "select" || ln == "isindex")
return;
}

//Get the link url from the search results page
var url_dest = jQuery(doc.body).find('a.l').eq(num-1).attr('href');

if(event.altKey){
//Open in new tab
Application.activeWindow.open(Utils.url(url_dest));
}else{
//Open in same tab
doc.location.href = url_dest;
}
}
}
</pre>

If Ubiquity does indeed become ubiquitous, a lot of extensions can be re-written as Ubiquity commands. This is much nicer for the end-user, as well, because the Ubiquity command installation process is a lot easier.

In the future, Ubiquity is also likely to have the ability to convert your Ubiquity commands into proper Firefox extensions. Look [http://labs.toolness.com/trac/ticket/3 here] to check on the progress of this functionality.

== Firebug ==

You should enable Chrome Errors and Warnings if you want the errors in your code to appear in the Firebug console. Use CmdUtils.log() rather than console.log() ''Note: For now, you can only pass one argument to log()''

= Conclusion =

To reiterate a point I made before: Ubiquity increases the surface area of innovation for the browser many-fold, by making anyone who can write simple Javascript into an agent for bettering the browser and the open Web. You are one of those agents.

Now, go forth and create.
1,007
edits

Navigation menu