De:Ubiquity 0.1.2 Programmier-Tutorial: Difference between revisions

 
(41 intermediate revisions by the same user not shown)
Line 53: Line 53:
Die einzige Zeile Programm-Code in unserem Kommando ist selbsterklärend, so dass wir direkt zu dessen allgemeinen Aufbau übergehen können. Jede Funktion, die mit dem Präfix cmd_ beginnt, wird automatisch zu einem Ubiquity Kommando. Hier ist die Magie der Namensräume am Werk und macht für uns die Kommando-Entwicklung super einfach.
Die einzige Zeile Programm-Code in unserem Kommando ist selbsterklärend, so dass wir direkt zu dessen allgemeinen Aufbau übergehen können. Jede Funktion, die mit dem Präfix cmd_ beginnt, wird automatisch zu einem Ubiquity Kommando. Hier ist die Magie der Namensräume am Werk und macht für uns die Kommando-Entwicklung super einfach.


Es gibt weitere Präfixe mit anderer Wirkung wie z.B. die Code-Ausführung beim Start einer Seite ( startup_ ) und nachdem sie geladen wurde (pageLoad_). Dies ist aber Stoff für ein anderes Tutorial.
Es gibt weitere Präfixe mit anderer Wirkung wie z.B. die Code-Ausführung beim Start von FireFox( startup_ ) und nachdem eine Seite geladen wurde (pageLoad_). Dies ist aber Stoff für ein anderes Tutorial.


Zurück zum Beispiel. Das Herzstück unseres Kommandos ist die Funktion displayMessage(). Durch sie wird eine Nachricht in betriebssystem-spezifischer Weise auf dem Bildschirm angezeigt.
Zurück zum Beispiel. Das Herzstück unseres Kommandos ist die Funktion displayMessage(). Durch sie wird eine Nachricht in betriebssystem-spezifischer Weise auf dem Bildschirm angezeigt.
Line 198: Line 198:
== Das Kommando Veröffentlichen ==
== Das Kommando Veröffentlichen ==


Jetzt, da wir unser beeindruckendes neues "Datum" Kommando fertig gestellt haben, wollen wir es auch dem Rest der Welt zur Verfügung stellen. Dafür brauchst Du es nur irgendwo auf Deinem Server als JavaScript-Datei ablegen und irgendwo auf Deiner WebSite einen entsprechenden Link mit "link rel"  zu setzen.
Jetzt, da wir unser beeindruckendes neues "Datum" Kommando fertig gestellt haben, wollen wir es auch dem Rest der Welt zur Verfügung stellen. Dafür brauchst Du es nur irgendwo auf Deinem Server als JavaScript-Datei abzulegen und irgendwo auf Deiner WebSite einen entsprechenden Link mit "link rel"  zu setzen.


   <pre><link rel="commands" href="http://path-to-js" name="Title Goes Here" /></pre>
   <pre><link rel="commands" href="http://path-to-js" name="Title Goes Here" /></pre>
Line 223: Line 223:
= Drittes Kommando: Map-Me! Eigenen Standort ermitteln und in einer Karte anzeigen  =
= Drittes Kommando: Map-Me! Eigenen Standort ermitteln und in einer Karte anzeigen  =


Ubiquity's "map" Kommando ist ziemlich mächtig, es ist vergleichsweise kompliziert anzuwenden und besteht zudem noch aus einigen hundert Zeilen Programm-Code. Das Kommando bietet aber dem entsprechend weitergehende Möglichkeiten. So könntest Du einige Häuser aus einer Immobilienangebotsliste auswählen oder einige Restaurants aus einer Liste mit Restaurantnamen und Ubiquity würde daraus Deine "Wunsch-Karte" erzeugen. Das dahinter stehende Konzept legt die ganze Macht der Rekombinationsmöglichkeit von WebInhalten in die Hand des Anwenders. Aber ich schweife hier doch zu sehr ab. Lass uns ein einfaches Kommando bauen, das lediglich Deine aktuelle geografische Position in Form einer Karte an der Cursorposition einfügt.
Ubiquity's "map" Kommando ist ziemlich mächtig, es ist vergleichsweise kompliziert anzuwenden und besteht zudem noch aus einigen hundert Zeilen Programm-Code. Das Kommando bietet aber auch dementsprechend weitergehende Möglichkeiten. So könntest Du einige Häuser aus einer Immobilienangebotsliste auswählen oder einige Restaurants aus einer Liste mit Restaurantnamen und Ubiquity würde daraus Deine "Wunsch-Karte" erzeugen. Das dahinter stehende Konzept legt die ganze Macht der Rekombinationsmöglichkeit von WebInhalten in die Hand des Anwenders. Aber ich schweife hier doch zu sehr ab. Lass uns ein einfaches Kommando bauen, das lediglich Deine aktuelle geografische Position in Form einer Karte an der Cursorposition einfügt.


In diesem Kommando benutzen wir Google [http://code.google.com/apis/maps/documentation/staticmaps/ static map API] und die Ubiquity Funktion <code>CmdUtils.getGeoLocation()</code> zum einfügen der Karte unsere aktuellen Standorts. Ubiquity benutzt derzeit die [http://www.maxmind.com/app/api MaxMind] API um zu versuchen, Deinen Standort aus Deiner IP-Adresse zu ermitteln. Das wird sich in Zukunft wahrscheinlich noch ändern.
In diesem Kommando benutzen wir Google [http://code.google.com/apis/maps/documentation/staticmaps/ static map API] und die Ubiquity Funktion <code>CmdUtils.getGeoLocation()</code> zum einfügen der Karte unseres aktuellen Standorts. Ubiquity benutzt derzeit die [http://www.maxmind.com/app/api MaxMind] API um zu versuchen, Deinen Standort aus Deiner IP-Adresse zu ermitteln. Das wird sich in Zukunft wahrscheinlich noch ändern.


<pre>
<pre>
Line 260: Line 260:


Hier sind zwei Dinge neu:  der Gebrauch von <code>CmdUtils.getGeoLocation()</code> um die aktuelle geografische Position zu ermitteln und von <code>CmdUtils.getImageSnapshot()</code> um ein Bitmap der Karte zu erstellen.
Hier sind zwei Dinge neu:  der Gebrauch von <code>CmdUtils.getGeoLocation()</code> um die aktuelle geografische Position zu ermitteln und von <code>CmdUtils.getImageSnapshot()</code> um ein Bitmap der Karte zu erstellen.
<pre>Hier geht's nachher mit der Übersetzung weiter</pre>


Trotz aller Ungenauigkeit, die ein IP-basiertes Verfahren zur Standortermittlung auch haben mag, empfand ich es doch als nützlich, es in Standort-basierten Kommandos wie Yelp für die Erzeugung entsprechend sensitiver Vorgaben zu verwenden.
Trotz aller Ungenauigkeit, die ein IP-basiertes Verfahren zur Standortermittlung auch haben mag, empfand ich es doch als nützlich, es in Standort-basierten Kommandos wie Yelp für die Erzeugung entsprechend sensitiver Vorgaben zu verwenden.
Line 268: Line 266:
Wehalb müssen wir hier eigentlich die Funktion <code>CmdUtils.getImageSnapshot()</code> verwenden? Nun, die Google Maps API erfordert einen Schlüssel, der an eine spezielle URL gebunden ist. Würden wir jetzt die Karte einfach mit einem Image-Tag in eine beliebige WebSite einbauen, so würde diese womöglich nicht geladen, weil der Schlüssel wahrscheinlich nicht zu deren URL passt. Deswegen wandeln wir sie mit <code>snapshotImage()</code> in ein [http://en.wikipedia.org/wiki/Data:_URI_scheme data url] um und betten dieses dann als Inline-Grafik in die betreffende Seite ein, sozusagen einen Schnapschuss der Karte.
Wehalb müssen wir hier eigentlich die Funktion <code>CmdUtils.getImageSnapshot()</code> verwenden? Nun, die Google Maps API erfordert einen Schlüssel, der an eine spezielle URL gebunden ist. Würden wir jetzt die Karte einfach mit einem Image-Tag in eine beliebige WebSite einbauen, so würde diese womöglich nicht geladen, weil der Schlüssel wahrscheinlich nicht zu deren URL passt. Deswegen wandeln wir sie mit <code>snapshotImage()</code> in ein [http://en.wikipedia.org/wiki/Data:_URI_scheme data url] um und betten dieses dann als Inline-Grafik in die betreffende Seite ein, sozusagen einen Schnapschuss der Karte.


Es existiert auch ein <code>CmdUtils.getWindowSnapshot()</code> Funktion mit der Du dasselbe machen kannst, aber das Bild kann sich in irgend einem Tab/Fenster befinden. Die Funktion erwartet als ersten Parameter das Fenster und einen callback als zweiten.
Es existiert auch eine <code>CmdUtils.getWindowSnapshot()</code> Funktion mit der Du dasselbe machen kannst, aber das Bild kann sich in irgend einem Tab/Fenster befinden. Die Funktion erwartet als ersten Parameter das Fenster und einen callback als zweiten.


= Kommandos mit Argumenten =
= Kommandos mit Argumenten =
Line 498: Line 496:
});
});
</pre>
</pre>
= Registerkarten umschalten: Unser letztes Kommando =
Das letzte Kommando in diesem Tutorial ist zum Umschalten der Registerkarten des Browsers. Das Endziel ist: tipp ein paar Buchstaben ein die mit dem Titel einer Registerkarte übereinstimmen und drück' RETURN, dann hast Du zu dieser Registerkarte umgeschaltet.
Wir schreiben dieses Kommando in zwei Schritten. Zuerst erzeugen wir einen Registerkarten Substantiv-Typ und dann bauen wir damit unser Registerkarten-Umschalt-Kommando.
== Registerkarte: Schreib Deinen eigenen Substantiv-Typ ==
Ein Substantiv-Typ benötigt nur zwei Dinge: einen Namen und eine Vorschlags-Funktion. Demnächst wird es zum erzeugen benutzerdefinierter Substantiv-Typen wahrscheinlich auch mal ein bequemes <code>CmdUtils.CreateNounType()</code> geben und uns die Arbeit wesentlich erleichtern.
Der Name wird in der Kommandozeile angezeigt, wenn das Kommando auf eine Eingabe wartet. Die Vorschlags-Funktion gibt eine Liste von Eingabeobjekten zurück, von denen jedes den Namen einer der im Browser geöffeten Registerkarten enthält. für die Interaktion mit dem Browser benutzen wir [http://developer.mozilla.org/en/docs/FUEL FUEL],  von wo auch die "Application"-Variable stammt.
<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>
Die Vorschlags-Methode eines Substantiv-Typs erfordert stets die beiden Parameter "text" und "html".  Die Eingabe stammen von dem Teil einer WebSeite, den der Anwender ausgewählt hat. Die Werte der beiden Paramter können sehr unterschiedlich sein: beides sind Strings, jedoch enthält der "html"- Parameter, im Gegensatz zum "text"-Parameter, auch HTML-Code. Unser Registerkarten-Substantiv-Typ kümmert sich aber nur um den Registerkarten-Namen im Klartext, so dass wir den "html"-Parameter ignorieren können.
Wir verwenden die bequeme <code>CmdUtils.makeSugg()</code> - Funktion zum erzeugen des vom Ubiquity-Parser erwarteten Eingabeobjektes. Die vollständige Signatur dieser Funktion ist:
<pre>
CmdUtils.makeSugg( text, html, data );
</pre>
wobei html und data optional sind und nur dann unterstützt werden brauchen, wenn ihr Inhalt von text abweicht.
Falls die "text"- oder "html"-Eingabe sehr lang ist, erzeugt <code>makeSugg()</code> eine Zusammenfassung für uns und hinterlegt sie im <code>.summary</code> Attribut des Eingabeobjekts.
Das Gleiche liesse sich auch ohne den Einsatz von <code>makeSugg()</code> erreichen, indem wir eine Liste von anonymen Objekten zurückgeben. Das könnte dann so aussehen:
<pre>
{ text: tabName,
  html: tabName,
  data: null,
  summary: tabName };
</pre>
Die Eingabeobjekte, die von unserer <code>.suggest()</code> - Methode erzeugt werden sind genau die Objekte, die möglicherweise an die <code>execute()</code> - Methode und an die <code>preview()</code> - Methode der Kommandos übergeben werden, die unseren Substantiv-Typ verwenden.
== Registerkarte umschalten: Das Kommando ==
Nun sind wir mit einen Registerkarten-Substantiv-Typ ausgerüstet, so dass es ein Leichtes für uns ist, das Registerkarten-Umschalt-Kommando zu bauen.
Zur Erinnerung: wir benutzen FUEL zur Fokussierung der gewählten Registerkarte.
<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>
= Hinweise zur Entwicklung =
Jetzt weisst Du all das was notwendig ist, um mit der Entwicklung eigener nützlicher Ubiquity-Kommandos zu beginnen.
Nachfolgend aber noch einige wichtige Tips, die nicht mehr in den Rahmen der vorhergegangenen Abschnitte gepasst haben, Dir wahrscheinlich aber das Entwickler-Dasein etwas erleichtern können.
== Die Quell-Codes der eingbauten Kommandos ==
Das Studium der Quell-Codes der eingbauten Kommandos kann bei der Entwicklung eigener Kommandos sehr hilfreich sein. Wenn Du den Quell-Checkout hast (lies im [https://wiki.mozilla.org/index.php?title=Labs/Ubiquity/Ubiquity_0.1_Development_Tutorial the development tutorial] nach, wie diesen erhalten kannst ), findest Du den Quell-Code der einzelnen Kommandos in den folgenden Dateien und Verzeichnissen:
ubiquity/standard-feeds/
ubiquity/builtin-feeds/en/builtincmds.js
ubiquity/feed-parts/header/en/nountypes.js
Hast Du den Checkout des Quell-Codes nicht, dann halte an den folgenden Stellen im Web Ausschau nach der jeweils letzten Version:
[http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/standard-feeds/ standard-feeds]
[http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/builtin-feeds/en/builtincmds.js builtincmds.js]
[http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/feed-parts/header/en/nountypes.js nountypes.js]
== Interaktion mit anderen Extensionen ==
http://img363.imageshack.us/img363/1906/picture7cm5.png
Darüber gibt es eigentlich nicht viel mehr zu sagen, als das es sehr leicht ist. Als Beispiel sei hier mal ein Kommando angeführt, das die Lyrik zu einem Lied findet ( vielen Dank an [http://foyrek.com/lyrics.html Abimanyu Raja] für das Schreiben des Codes ). Du gibst ganz einfach so etwas wie  "get-lyrics wild international" in Ubiquity um den Liedtext zu finden, aber das Kommando greift auch auf die Extension "FoxyTunes" zurück ( sofern sie denn installiert ist ) und fügt das aktuell gespielte Lied in die Vorschlagsliste ein. Der zugriff auf andere Extensionen ist auch deshalb so einfach, weil Du jederzeit Einblick in deren Quell-Codes nehmen kannst.
<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>
== Implentierung asynchroner Substantiv-Vorschläge ==
Alle Substantiv-Typen, die wir bisher gesehen haben, arbeiteten bei der Rückgabe ihrer Vorschläge synchron. Ubiquity unterstützt aber auch asynchrone Substantiv-Vorschläge. Das ist nützlich in Fällen, in denen ein Substantiv-Typ zuerst eine zeitintensiive Arbeit durchführen muss, bevor er die Vorschlagsliste ausgeben kann, meisten dann, wenn er einen externen Dienst konultieren muss.
Die Implementierung asynchroner Vorschläge ist recht einfach. Wann immer der Ubiquity-Parser die <code>suggest</code> - Funktion eines Substantiv-Typs aufruft, schliesst er dabei auch eine Rückruf-Funktion ein, die für die Rücksendung von Vorschlägen an den Parser verwendet werden, sobald diese verfügbar werden. Typischer Weise führt die <code>suggest</code> - Funktion des Substantiv-Typs einen AJAX-Request aus und ruft dabei die Rückruf-Funktion des Parser aus der Rückruf-Funktion des AJAX-Request heraus auf.
Dazu hier ein einfaches Beispiel: ein Substantiv-Typ, der [http://www.freebase.com/ Freebase] Themen vorschlägt, die auf dem Text basiert, den der Anwender eingetippt oder ausgewählt hat sowie ein ridimentäres <code>freebase-lookup</code> Kommando, das diesen Substantiv-Typ benutzt.
<pre>
var noun_type_freebase_topic = {
  _name: "Freebase topic",
 
  suggest: function suggest( text, html, callback ) {
    jQuery.ajax( {
      url: "http://www.freebase.com/api/service/search",
      dataType: "json",
      data: { prefix: text, limit: 5 },
      success: function suggestTopics( response ) {
        var i, results, result;
        results = response.result;
        for ( i = 0; i < results.length; i++ ) {
          result = results[ i ];
          callback( CmdUtils.makeSugg( result.name, result.name, result ) );
        }
      }
    } );
    return [];
  }
}
 
CmdUtils.CreateCommand( {
  name: "freebase-lookup",
  takes: { topic: noun_type_freebase_topic },
  preview: function preview( container, topic ) {
    var text = topic.text || "any topic";
    container.innerHTML = "Go to the Freebase topic page for " + text + ".";
  },
  execute: function goToFreebase( topic ) {
    if ( topic ) {
      Utils.openUrlInBrowser( "http://www.freebase.com/view" + topic.data.id );
    }
  }
} );
</pre>
Ein paar Anmerkungen dazu:
* Die Rückruf-Funktion des Parsers akzeptiert lediglich einen einzelnen Vorschlag ( nicht ein Array von Vorschlägen ), deshalb muss sie für jeden Vorschlag einzeln aufgerufen werden, auch dann, wenn der Substantiv-Typ mehrere Vorschläge gleichzeitig verfügbar hat ( wie in dem Freebase-Beispiel oben ). Das unterscheidet sich ein wenig von dem synchronen Fall, in welchem von der  <code>suggest</code> - Funktion die Rückgabe eines Arrays erwartet wird.
* Die  <code>suggest</code> - Funktion eines Substantiv-Typs gibt typischer Weise ein leeres Array zurück, wenn sie einen asynchronen Vorschlag beabsichtigt, aber sie kann einen oder mehrere Vorschläge synchron zurückgeben, wenn ihr diese zur Verfügung stehen.
*Weil der Aufwand für die Erzeugung asynchroner Vorschläge relativ hoch ist und die <code>suggest</code> - Funktion eines Substantiv-Typs bei jedem Tastendruck des Anwenders aufgerufen wird, sollte man eine Zeitverzögerung für den Aufruf implementieren und/oder die bereits ermittelten Vorschläge für weitere Aufrufe cachen. Ubiquity überlässt das zur Zeit jedem Substantiv-Typ selbst.
* Sieh Dir auch einmal diese wesentlich [http://graynorton.com/ubiquity/freebase-nouns.html robustere Implementierung eines Freebase-abgeleiteten Substantiv-Typs] an.
== Codeausführung nach dem Laden einer Seite und beim Start von FireFox ==
Wenn Du irgendwelchen Code nach dem Laden einer Seite ausführen willst, brauchst Du einfach nur den Namen Deiner Funktion mit dem Präfix <code>pageLoad_</code> beginnen zu lasen. Willst Du also zum Beispiel jedesmal "Hi!" sagen, nachdem eine Seite geladen wurde, dann müsste Dein Code in etwa wie folgt aussehen:
<pre>
function pageLoad_hi(){
displayMessage("Hi!");
}
</pre>
Wenn Du diese Funktion abänderst und willst dann die Änderungen sehen, vergiss nicht, zuerst Ubiquity aufzurufen. Obwohl eine Funktion wie diese nicht unbedingt ein Ubiquity-Kommando sein muss ist dennoch ein Refresh des gecachten Codes erforderlich, ebenso, wenn Du irgendwelchen Code ausführen willst, jedesmal wenn FireFox startet.
Das beeindruckendste an solcher Art Funktionen ist, das Du damit komplette  Firefox Extensionen (die mit einem minimalen Benutzerschnitstelle auskommen) als Ubiquity-Plugins mit wesentlich weniger Zeilen Code erstellen kannst. Du brauchst Dir keinerlei Gedanken wegen chrome.manifest oder install.rdf zu machen. Ein weiter Vorzug ist, dass Du während der Entwicklung niemals FireFox neu starten musst, ausgenommen natürlich, wenn Dein Code ausgeführt werden soll, nachdem FireFox gestartet ist.
<center>http://img388.imageshack.us/img388/3086/picture5eo9.png</center>
Hier ist der Code für [http://foyrek.com/commands/keyscape.js Keyscape], einem Ubiquity Kommando, das die <code>pageLoad</code> -  Funktion und die <code>startup</code> - Funktion benutzt,  um die Funktionalität der [https://addons.mozilla.org/en-US/firefox/addon/339 Search Keys extension] von Jesse Ruderman nachzugbilen. In Übereinstimmung mit Ubiquity's Ziel, Dir zu ermöglichen, Dinge schneller und einfacher per Tastatur zu erledigen, kannst Du mit diesem Kommando lediglich durch Eingabe einer Nummer aus einem Google-Suchresultat auswählen. Das Kommando fügt den Links entprechende Anmerkungen zu.
<pre>
//Eine Menge diese Codes ist der  Search Keys Extension entliehen
//Vielen Dank an 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>
Falls Ubiquity tatsächlich allgegenwärtig sein wird, können eine Menge Extensionen als Ubiquity-Kommandos neu geschrieben werden.Dies ist wesentlich freundlicher für den Endanwender genau so, wie auch die Installation von Ubiquity-Kommandos selbst wesentlich einfacher ist.
In Zukunft wäre es auch nicht schlecht die Möglichkeit zu haben, Deine Ubiquity-Kommandos in ordnungsgemässe FireFox-Extensionen konvertieren zu können. Siehe einmal  [http://labs.toolness.com/trac/ticket/3 hier] nach, wie weit diese Funktionalität schon fortgeschritten ist.
== Firebug ==
Du solltest Chrome Fehler und Warnungen aktivieren wenn Du möchtest, dass die Fehler Deines Codes in der FireBug-Konsole angezeigt werden. Benutze CmdUtils.log() anstatt console.log() ''Hinweis: Derzeit ist lediglich die Übergabe eines Arguments an  log()  möglich.''
== Programmatisches hinzufügen von Kommandos ==
Hier ist ein Stückchen Code das Dir zeigt, wie ein Entwickler ein Kommando programmatisch in einer FireFox-Extension registrieren kann.
<pre>
// Helper function used to determine the local directory where the
// extension which contains the command is installed. This is used
// to create the URL of the js file which includes the implementation
// of the list of commands you want to add.
function getExtensionDir() {
    var extMgr = Components.classes["@mozilla.org/extensions/manager;1"]
                .getService(Components.interfaces.nsIExtensionManager);
    return extMgr.getInstallLocation( "feedly@devhd" ).getItemLocation( "feedly@devhd" );
}
function getBaseUri() {
    var ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);
    var extDir = getExtensionDir();
    var baseUri = ioSvc.newFileURI(extDir).spec;
    return baseUri;
}
// your extension needs to call the addUbiquity command each time a new browser
// session is create and your extension is loaded.
function addUbiquityCommands(){
    // url of the file which contains the implementation of the commands
    // we want to add.
    var url = getBaseUri() + "content/app/ubiquity/commands.js"
    // link to the ubiquity setup module.
    var jsm = {};
    Components.utils.import("resource://ubiquity/modules/setup.js", jsm);
    // look up the feed manager and add a new command. Note: we are using the
    // isBuiltIn=true option so that the command is only added for the duration
    // of the browser session. This simplifies the overall lifecycle: if the
    // extension is disabled or un-installed, it will be automatically
    // "removed" from ubiquity.
    jsm.UbiquitySetup.createServices().feedManager.addSubscribedFeed( {
url: url,
        sourceUrl: url,
        canAutoUpdate: true,
        isBuiltIn: true
    });
}
</pre>
Innerhalb Deiner Kommando-Implementierung kannst Du Import-Module benutzen oder in einer Singleton XPCOM Komponente nachschlagen, um Dein Kommando zu der Funktionalität zurück zu verlinken, die in Deiner Extension eingekapselt ist.
Hier ein Beispiel-Kommando, das genau dies tut:
<pre>
var StreetsUtils = function(){
    var that = {};
    that.lookupCore = function(){
        return Components.classes["@devhd.com/feedly-boot;1"]
.getService(Components.interfaces.nsIFeedlyBoot)
.wrappedJSObject
.lookupCore();
    };
    return that;
}();
CmdUtils.CreateCommand({
  name: "my-extension-test",
  takes: {"message": noun_arb_text},
  icon: "chrome://my-extension-test/skin/icon-16x16.png",
  modifiers: {to: noun_type_contact},
  description:"Testing the feedly+ubiquity integration",
  help:"This is a test help message",
  preview: function(pblock, directObj, modifiers) {
    var html = "Testing my-extension-test ";
    if (modifiers.to) {
      html += "to " + modifiers.to.text + " ";
    }
    if (directObj.html) {
      html += "with these contents:" + directObj.html;
    } else {
      html += "with a link to the current page.";
    }
    pblock.innerHTML = html;
  },
  execute: function(directObj, headers) {
      CmdUtils.log( ">>> my-extension core? " + ( StreetsUtils.lookupCore() != null ) );
  }
});
</pre>
Beachte: Wenn Dein Kommando andere JS-Dateien in die Extension einbinden oder laden muss, kannst Du dafür das folgende Code-Stückchen an den Anfang der entsprechenden Kommando-JS-Datei setzen, welche Du zu Ubiquity hinzufügst.
<pre>
function include( partialURI )
{
    // Load JS libraries
    var u = "chrome://feedly/content/app/" + partialURI;
    var jsl = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader); 
    jsl.loadSubScript( u );
}
include( "ubiquity/templates.ubiquity.js" );
</pre>
== Ubiquity programmatisch schliessen ==
Hier ist die Zeile Code, die Entwickler benutzen können, um Ubiquity programmatisch zu schliessen (z.B. von preview ):
<pre>
context.chromeWindow.gUbiquity.closeWindow();
</pre>
= Zusammenfassung =
Um nochmals einen Punkt zu wiederholen, den ich bereits zuvor schon einmal angeführt habe: Ubiquity erhöht das Innovationspotential des Browsers um ein Vielfaches dadurch, dass es jeden ,der einigermassen mit JavaScript umgehen kann, dazu in die Lage versetzt, sich an der Verbesserung des Browsers und des offenen Web persönlich zu beteiligen. Du bist einer dieser Menschen.
Ziehe jetzt also dahin und verbessere.
166

edits