Labs/Ubiquity/Writing Noun Types

From MozillaWiki
< Labs‎ | Ubiquity
Jump to: navigation, search

Back to Labs/Ubiquity.

Draft-template-image.png THIS PAGE IS A WORKING DRAFT Pencil-emoji U270F-gray.png
The page may be difficult to navigate, and some information on its subject might be incomplete and/or evolving rapidly.
If you have any questions or ideas, please add them as a new topic on the discussion page.

Authors: mitcho (Michael Yoshitaka Erlewine), brandon (Brandon Pung)

Introduction

Different arguments are classified into different kinds of nouns in Ubiquity using noun types. For example, a string like “Spanish” could be construed as a language, while “14.3” should not be. These kinds of relations are then used by the parser to introduce, for example, language-related verbs (like translate) using the former argument, and number-related verbs (like zoom or calculate) based on the latter. Ubiquity nountypes aren’t exclusive—a single string can count as valid for a number of different nountypes and in particular the “arbitrary text” nountype (noun_arb_text) will always accept any string given.

In addition to the various built-in nountypes, Ubiquity lets command authors write their own nountypes as well.

This guide will walk you through some of the builtin nountypes and then describe the API for writing your own nountypes.

Builtin noun types

Before you try to write a new noun type, it's good to make sure you're not reinventing the wheel. Here are some noun types which are bundled with Ubiquity:

  • noun_arb_text (Arbitrary text)
  • noun_type_language (Name of a human language, e.g. "English", "Japanese")
  • noun_type_url (A URL)
  • noun_type_contact (An email address from your address book)
  • noun_type_date (A date, in any format, or a word like "tomorrow")
  • noun_type_time (A time, in any format)
  • noun_type_percentage
  • noun_type_address
  • noun_type_tab
  • noun_type_searchengine
  • noun_type_tag
  • noun_type_geolocation

You can look at the source of all of these noun types at nountypes.js.

Specifying your own Noun Types

Regexps as Noun Types

If none of the nountypes above is what you're looking for, there are several ways to define your own. The simplest is to use a regular expression. Suppose that (for whatever odd reason) you wanted your command to accept only arguments that begin with the letter N. The following regexp matches words that start with N:

  /^[nN]/

You could use it as a noun type, like so:

  arguments: [{role: "object",
               nountype: /^[nN]/,
               label: "word that starts with n"}]

(Note that you do not put quotes around the regexp.)

A regexp nountype will reject input that doesn't match, but it doesn't know how to help the user by suggesting appropriate input.

Lists as Noun Types

Suppose you're writing a command that takes a color as an argument (perhaps it outputs a hexadecimal RGB representation of that color.) To make a nountype that accepts colors, you can simply pass in an array of strings:

  names: ["convert color"],
  arguments: [{role: "object",
               nountype: ["red", "orange", "yellow", "green", 
                          "blue", "violet", "black", "white",
                          "grey", "brown", "beige", "magenta",
                          "cerulean", "puce"],
               label: "color to convert"}]

One benefit of using a list is that the parser can use it offer the user suggestions. If the user enters "get-color bl", for instance, Ubiquity will be able to suggest "black" and "blue" as the two valid completions based on the input. This makes list-based nountypes very useful for any command that can accept only a finite set of values.

Writing a Noun Type object from scratch

Basic structure

Scoring your suggestions

matchScore()

Asynchronous requests

Some noun types need to make calls to external web services to decide whether the users input is valid for the particular noun type. noun_type_async_address and noun_type_async_restaurant are examples of nouns that do this.

Registering an asynchronous call

If you are performing asynchronous calls to web services, your noun type must tell Ubiquity about these calls so that Ubiquity will know to wait for those requests to come back before completing the query. For this to work, all you need to do is pass an object back to Ubiquity in the return array of your suggest method which represents the asynchronous request. This could be an ajax request object, or any object that represents your request. If you're using an object other than an ajax request, you need to add a readyState attribute to that object and set it's value to 2. This tells Ubiquity that your request is still outstanding. When the async request is finished, update this value to 4. When the ready state value of the async request is 4, Ubiquity will remove it from the list of outstanding requests.

//Ajax Example:
var noun_type_example = {
  label: "example",
  suggest: function (text, html, callback, selectionIndices) {
    var asyncRequest = jQuery.ajax({
                         //insert ajax call properties
                       });
    return asyncRequest;
  }
};
//Non-Ajax Example:
var noun_type_example = {
  label: "example",
  suggest: function (text, html, callback, selectionIndices) {
    var asyncRequest = {readyState: 2};
    // start running something asynchronously
    Utils.history.search(text, function(results) {
      asyncRequest.readyState = 4;
      //do something with results
    }
    return asyncRequest;
  }
};

Returning results asynchronously

The suggest method of your noun type is passed a callback function which you can use to return results asynchronously. When you are ready to send Ubiquity a new suggestion, simply execute the callback function with your suggestion as the argument.

//Example:
var noun_type_example = {
  label: "example",
  suggest: function (text, html, callback, selectionIndices) {
    var asyncRequest = jQuery.ajax({
                         url: //some url
                         dataType: //some dataType
                         success: function (data) {
                           if (data matches what you're looking for)
                             callback([CmdUtils.makeSugg(text, text, null, 0.9, selectionIndices)]);
                       });
    return asyncRequest;
  }
};

The noExternalCalls property

In an effort to limit the number of network calls made, noun types use a special property for telling Ubiquity whether or not they use external calls, called noExternalCalls.

By default, Ubiquity assumes that all noun types use external calls. If your noun type does not make external calls, you need to explicitly tell Ubiquity this by adding the property noExternalCalls: true.

//Example:
var noun_type_example = {
  label: "example",
  noExternalCalls: true,
  suggest: function (text, html, callback, selectionIndices) {
    .......
  }
};

Note: It is important that you inform Ubiquity if your noun type does not use external calls. Ubiquity exhibits different behavior for the noun types depending on whether or not this property is true. If your noun type declares that it doesn't use external calls, Ubiquity will test your noun type during noun-first suggestions. Such that if the first thing the user types into Ubiquity is a noun, all noun types that don't do external calls will be queried to see if they match the input. If a noun matches the input, then associated commands that use that noun will be suggested to the user.

Making the most of noun caching

Ubiquity has a caching feature which allows it to remember the suggestions that a noun type gave for a particular input. The tricky part of this caching is in deciding when to flush the cached results for each noun type. The length of time that a noun type's results remain accurate can differ greatly depending on the nature of the noun type. For instance, a noun type based on your open tabs may only remain accurate for a few seconds, while a noun type for street addresses will probably remain accurate for weeks/months, and a noun type that is strictly regex based will be accurate forever. There are two ways for a noun type to declare when it should be flushed: the cacheTime property and and the registerCacheObserver method.

The cacheTime property

By default, a noun type's results will be cached for one day. However, you can specify a different duration for the cache by adding the cacheTime property to your noun type with some integer value that represents the length of time to keep results cached in seconds. A cacheTime of 0 will result in no caching, while a cacheTime of -1 will result in the results being cached forever.

// Example: Cache results for 1 minute
var noun_type_example = {
  label: "example",
  cacheTime: 60,
  suggest: function (text, html, callback, selectionIndices) {
    .......
  }
};

The registerCacheObserver property

While the cacheTime property allows noun types to specify a fixed amount of time between flushings of the cache, for some noun types it makes more sense to have the cache flushed on certain events. For example, noun_type_tab flushes it's cache every time a new tab is opened or an old tab is closed. In order to register observers for flushing, simply add the registerCacheObserver property to your noun type. This property should be a function which takes a single argument that is a flush function. Within registerCacheObserver you can set event listeners which call this flush function anytime you want to flush the cache of the noun type.

// Example
var noun_type_example = {
  label: "example",
  registerCacheObserver: function (flush) {
    //register observers here
    thing.addEventListener("someAction", flush, false);
  }
  suggest: function (text, html, callback, selectionIndices) {
    .......
  }
};

Best practices

Test your Noun Type with the Tuner

The Nountype Tuner is a new tool to help both Ubiquity core developers and command authors check their nountypes against others and to “tune” their behavior and scores. The nountype tuner will take your input and throw it against all of the nountypes referenced in your active verbs and display the suggestions returned with their scores.

Tuner.png

The Nountype Tuner can be found at chrome://ubiquity/content/tuner.xhtml.

The heart and soul of the Nountype Tuner is this scale:

Tuner-top.png

This scale tells you, in plain English, what different scores represent and correspond to, in two sets of vocabulary: “in terms of a guess” and “in terms of a match.” While still subjective, this scale helps developers just different input/output pairs and their scores. For example, “lian” → “http://lian” is given 0.5, so it’s an okay guess or a possible match… does that seem right to you? Or “lian” → “Italian” being between “okay” and “good.” Appropriate? We can look at such statements, decide how we feel about them, and tweak if necessary.