De:Ubiquity 0.1.2 Programmier-Tutorial

From MozillaWiki
Jump to: navigation, search

In anderen Sprachen

Wenn Deutsch nicht Deine Muttersprache ist, dann kannst Du oben Deine eigene Sprache aus der Liste auswählen. Falls Deine Sprache dort noch nicht existiert, dann bist Du eingeladen, selbst eine Übersetzung anzufertigen.

Das Ubiquity 0.1 Kommando Tutorial

Der große Vorteil von Ubiquity - aus Sicht eines Entwicklers - ist, wie einfach es ist, eigene Befehle zu erstellen. Mit nur ein paar Zeilen JavaScript ermöglicht es Ubiquity auch Gelegenheitsprogrammierern die Fähigkeiten ihres Browsers stark zu verbessern. Von einem 8-Zeilen-Programm, mit dem man seine Kontakt-Emailadresse in jedes Textfeld einsetzen kann, zu einer 50-zeiligen Twitter-Integration zeigt dieses Tutorial die Schritte zum kreativen Umgang mit Ubiquity.

ACHTUNG: Ubiquity ist noch im Entwicklungsstadium. Dies ist eine 0.1-Release. Die API wird sich vermutlich in späteren Versionen noch ändern. Obwohl das bedeutet, dass das, was du heute an Code schreibst, morgen nicht mehr funktioniert, heißt es auch, dass du durch Programmieren und Feedback direkt auf die Entwicklung von Ubiquity Einfluss nehmen kannst.

Der Rest dieser Seite dokumentiert die Entwickler-API in der aktuellen Version 0.1.1. Falls du die neueste Quellcode-Version ausgecheckt hast, hast du eine neuere API mit einigen zusätzlichen Features, die in 0.1.1 noch nicht enthalten sind. Informationen über die neueste und beste Source-Tip API gibt es im Ubiquity Source Tip Author Tutorial.

Echtzeit-Entwicklung

Ubiquity erfordert keine Neustarts von Firefox, um den Code zu Testen. Das ist ein barbarischer Akt und das wollen wir nicht. Stattdessen lädt Ubiquity den Befehl jedesmal, wenn er gestartet wird. Wenn du den eingebauten Editor benutzt, musst du nicht einmal speichern!

Um den Ubiquity-Befehls-Editor zu öffnen startest du einfach Ubiquity (Ctrl/Alt - Leertaste) und benutzt den "command-editor"-Befehl. In diesem Tutorial sprechen wir von "Ubiq" ("Ubiquieren") wenn gemeint ist, ein Programm zu starten. Wenn du zum Beispiel den Command-Editor starten willst, ubiq einfach "command-editor".

Arbeite bei den folgenden Beispielen einfach mit diesem Editor. Updates finden beim nächsten Starten von Ubiquity statt.

Erstes Kommando: Hallo Welt

Nur eine Funktion: einfacher geht's nicht

Beginnen wir mit dem Standard-Programmierbeispiel: der Ausgabe von "Hallo Welt!". In Ubiquity sind Befehle einfach Funktionen mit verschiedenen Attributen. Beginnen wir, indem wir einen Befehl von Hand schreiben - auch wenn wir werden bald eine elegantere Methode kennen lernen werden.

Gib folgende Zeilen in den Command-Editor ein:

function cmd_hello_world() {
  displayMessage( "Hello, World!");
}

Jetzt gib "hello-world" in Ubiquity ein. "Hello World!" wird sofort auf dem Bildschirm ausgegeben. Wenn du auf Mac OSX arbeitest und Growl installiert hast, wird die Nachricht als Growl-Nachricht ausgegeben. Bei Windows schiebt sich unten reichts im Bildschirm ein kleines "Toaster"-Fenster auf.

picture1ui2.png

picture2vx2.png

In Ubuntu 8.04 (Hardy Heron) erscheint dagegen das hier:

ubiqubuntuhelloworldeq9.png

Dies funkioniert derzeit nur unter OSX mit installiertem Growl, unter Windows XP/Vista und Ubuntu Hardy/Intrepid. Ansonsten erhält man kein Meldungsfenster. Hier gibt es noch genug Arbeit für künftige Releasestände von Ubiquity.

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 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.

Vielleicht hast Du dich darüber gewundert, dass die beiden Wörter unseres Kommandos, nämlich "Hallo Welt", in der Ubiquity-Kommandozeile durch einen Bindestrich getrennt werden und nicht durch ein Leerzeichen. Das liegt ganz einfach daran, dass Ubiquity's Parser für natürliche Sprache die Verarbeitung von Kommandos aus mehreren Wörtern noch nicht beherrscht. Daran wird aber in der Zukunft noch gearbeitet.

Verwendung von CreateCommand

Bei Kommandos, die etwas komplizierter sind als unser einfaches "hello-world", kannst Du die Hilfsfunktion CmdUtils.CreateCommand() einsetzen. Diese stellt Dir einen komfortablen Satz an Optionen bereit, die Dir das Leben leichter machen können. Hier das entsprechend abgeänderte "hello-world" Kommando:

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

Natürlich bringt bei diesem simplen Kommando der Einsatz von CmdUtils.CreateCommand() keinen Vorteil gegenüber der ersten Variante. Dieser erweist sich in der Regel erst so wirklich ab einer gewissen Komplexität. Nur, um einmal ein Beispiel zu nennen, bei dem sich der Einsatz in jedem Fall lohnt: mit der Option method—unicode bist Du freier in der Namenswahl für Kommandos, denn die Verwendung nicht-englischer Zeichen stellt damit kein Problem mehr dar.

Es gibt im Namensraum von CmdUtils eine ganze Reihe weiterer nützlicher Funktionen. Leider ist deren Dokumentation noch sehr unvollständig. In diesem Tutorial wirst Du aber ein Gefühl für die gebräuchlichsten dieser Funktionen bekommen. Detailiertere Informationen findest Du unter automatically generated documentation oder unter cmdutils.js.

Hinzufügen einer Vorschau

picture3ex0.png

Jetzt wollen wir unser Kommando um eine Vorschau erweitern und geben damit dem Anwender eine Rückmeldung über dessen Wirkung noch bevor es ausgeführt wird. Eine Vorschau kann auch grafische Elemente enthalten und eignet sich deshalb hervorragend zur Erstellung aussagekräftiger visueller Rückmeldungen über die Wirkung eines Kommandos, wie z.B. die grafische Repräsentation atmosphärischer Zustände bei der Verwendung eines Wetter-Kommandos. Für die Vorschau stehen Dir sämtliche Ausdrucksmittel einer HTML-Seite zur Verfügung, inklusive Animationen, so dass hier einiges möglich ist. Jedoch darf eine Vorschau keine Seiteneffekte haben. Die Vorschau darf niemals ohne Interaktion mit Anwender den Systemzustand verändern.

Für unser "hallo-welt" Kommando benötigen wir keine aufwändige Vorschau: ein etwas aussagekräftigerer Text als das standardmässig angezeigte "Führt das 'hallo-welt' Kommando aus" soll uns vollauf genügen.

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

Die Vorschau ist in diesem Fall einfach ein HTML-formatierter String. Die Vorschau kann aber auch eine Funktion sein, wie Du im nächsten Abschnitt sehen wirst.

Zweites Kommando: Das Datum Kommando

Den Auswahlbereich setzen

Oft vergesse ich, welchen Tag wir heute haben, was daran daran liegen mag, dass ich zu viele Termine im Kopf behalten muss. Aber anstatt das Übel bei den Wurzeln zu packen, löse ich meine Probleme, wie so viele andere Programmierer auch, durch den Einsatz irgendwelcher Technologien. Meine Lösung ist also ein Kommando, dass mir das Datum auf einer Seite an der aktuellen Cursorposition einfügt.

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

Die neue Funktion, die ich hier verwende ist setSelection(). Diese fügt ganz einfach das Argument als Text auf einer Seite an der aktuellen Cursorposition ein. Dies funktionert sowohl in editierbaren Textfeldern oder Richtext-Felder, als auch in nicht-editierbaren Bereichen, selbst dann, wenn die Cursorposition ausserhalb des sichbaren Bereichs liegt.

Du kannst das Kommando jetzt einmal ausprobieren. Gehe auf eine Seite, markiere einmal eine nicht-editierbare Textstelle und führe das Kommando aus. Es funktioniert! So etwas ist machmal auch für andere Kommandos, wie z.B. "translate", mit dem Du vielleicht eine nicht-editierbare Textstelle gegen ihre Übersetzung austauschen willst, recht nützlich.

Die Funktion toLocalDateString() ist eine native Javascript-Funktion: Falls Dir diese Funktion also nicht geläufig sein sollte, dann ziehe einfach eine JavaScript-Dokumentation zu Rate, z.B.: Date object.

Eine bessere Vorschau

Nun ist es an der Zeit, unserem Kommando eine bessere Vorschau zu verpassen. Lass uns in der Vorschau einfach das aktuelle Datum anzeigen. Ein positiver Nebeneffekt wäre dann auch, dass der Anwender das Datum Kommando nicht unbedingt ausführen muss, nur weil er mal eben abchecken will, welcher Tag heute ist.

CmdUtils.CreateCommand({
  name: "Datum",
  
  _date: function(){
    var date = new Date();
    return date.toLocaleDateString();
  },
  
  preview: function( pblock ) {
    var msg = 'Fügt : "<i>${date}</i>" an der aktuellen Cursor-Position ein!';
    pblock.innerHTML = CmdUtils.renderTemplate( msg, {date: this._date()} );
  },
  
  execute: function() {
    CmdUtils.setSelection( this._date() );
  }
})

Wir haben hier jetzt zwei Dinge getan.

Zum Ersten haben wir den Code für die Datumsermittlung in eine Funktion _date() gepackt. In dieser Weise brechen wir nicht mit DRY durch Wiederholung von Code im preview-Bereich und im execute-Bereich. Beachte, dass wir beim Zugriff auf _date() mit dem Schlüsselwort this den Bezug zu unserer Instanz herstellen müssen.

Zum Zweiten haben wir hier eine Vorschau-Funktion eingesetzt. Das erste Argument ist das DOM-Element, welches für unsere Vorschau verwendet werden soll. Veränderst Du pblock, dann veränderst Du die Vorschau. In unserem Fall verwenden wir die Eigenschaft innerHTML unseres Vorschau-Blocks zur Darstellung der gewünschten Meldung.

Eine weitere Sache, die ich hier gemacht habe, ist die Ausgabe-Formatierung unserer Meldung. Dafür habe ich die renderTemplate() Funktion verwendet. Diese übernimmt einen Template String und führt die erforderlichen Substitutionen anhand des darin übergebenen JSON Objektes durch. Templates können einen weiten Bereich an Funktionalitäten abdecken. Wir setzen aktuell TrimPath's JavascriptTemplates ein. Für weitergehende Informationen solltes Du deren Seite lesen. Obwohl JavascriptTemplates über einige nette Eigenschaften verfügt, erwägen wir, igendwann auf MJT umzustellen.

Eine Vorschau zeigt dem Anwender immer irgend etwas sinniges bezüglich des Kommandos an. Falls Deine Vorschau einen AJAX request erfordert, weil sie eine zeitintensive Anfrage enthält, kannst Du dem Anwender bis zum Eintreffen der Antwort einen passenden Platzhalter anzeigen, so dass er weiss, was da gerade so im Gange ist.

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

Irgendwann in der Zukunft könnten wir dann ja auch mal an Ubiquity's Streaming-Fähigkeiten herangehen.

Dokumentation and Metadaten

Wenn Du Dein Kommando der Öffentlichkeit zur Verfügung stellen willst, solltest Du vielleicht auch einige Angaben über Dich selbst machen

CmdUtils.CreateCommand({
  name: "Datum",
  homepage: "http://azarask.in/",
  author: { name: "Aza Raskin", email: "aza@mozilla.com"},
  contributors: ["Atul Varma"],
  license: "MPL",
  
  /* Hier folgt der restliche Code */
})

und Du solltest definitiv eine Dokumentation hinzufügen:

CmdUtils.CreateCommand({
  name: "Datum",
  homepage: "http://azarask.in/",
  author: { name: "Aza Raskin", email: "aza@mozilla.com"},
  contributors: ["Atul Varma"],
  license: "MPL",
  description: "Fügt das heutige Datum an der Cursorposition ein.",
  help: "Befindet sich der Cursor in einem editierbaren Textabschnitt, dann
wird das aktuelle Datum mit landesspezifischer Formatierung an der Cursorposition eingefügt.",
  
  /* Hier folgt der restliche Code */
})

Die description und help Attribute werden beide automatisch zusammen mit dem Kommando-Namen in der Ubiquity Kommandoliste angezeigt. (Der Anwender kann sich diese Liste mit dem Kommando "command-list" anzeigen lassen.) In beiden Strings sind HTML-Attribute erlaubt.

Während das Attribut description die Funktion des Kommandos in einer einzigen Zeile kurz zusammenfassend beschreibt, ist das Attribut help eine detailierte Beschreibung mit Beispielen usw. Falls das Kommando so einfach ist, dass seine Beschreibung in eine Zeile passt, dann ist es natürlich vollkommen ok, wenn Du auf das Attribut help ganz verzichtest.

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 abzulegen und irgendwo auf Deiner WebSite einen entsprechenden Link mit "link rel" zu setzen.

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

Hinweis: Dein WebServer muss .js files als 'application/x-javascript' ausliefern. Der MIME-Typ 'text/javascript' wird stillschweigend ignoriert.

Jedem Deiner Seitenbesucher, der Ubiquity installiert hat, wird nun mittels einer kleinen Meldung Dein Kommando zur Anmeldung angeboten.

Subscribe.png

Geht dieses Angebot von einer nicht-vertrauenswürdigen Seite aus, dann wird eine entsprechende Sicherheitswarnung ausgegeben, bevor das Kommando angemeldet werden kann. In Ubiquity 0.1 gelten derzeit übrigens alle Quellen als nicht-vertrauenswürdig, deshalb nimm das bitte nicht persönlich. Wir haben die Warnung ein wenig furchterregend gestaltet, weil Ubiquity Kommandos beliebigen JavaScript-Code mit Chrome-Privilegien ausführen und von daher mit dem Browser prinzipiell machen können was sie wollen. Es muss einfach sichergestellt sein, das dies den Leuten bewusst ist, bevor sie ein Kommando anmelden Warning.PNG

In Zukunft werden wir aber so etwas wie ein "Vertrauens-Netzwerk" aufbauen. Wenn Du dann ein Ubiquity Kommando von einer bestimmten Seite ausprobiert hast, dann kannst Du für Deine Freunde, die auch Ubiquity installiert haben und dieselbe Seite besuchen, eine Freigabe-Meldung oder eine Warn-Meldung hinterlassen, je nach dem, ob Du das Kommando als sicher oder als unsicher einstufst. Die Anwender erhalten also dadurch eine Entscheidungshilfe bei der Frage, ob sie ein Kommando anmelden sollen oder nicht, indem sie auf das Urteil ihnen bereits bekannter Leute zurückgreifen.

Übrigens, der Grund, weshalb wir von Kommando "anmelden" sprechen, anstatt von Kommando "installieren" ist der Umstand, das Änderungen an der JavaScript-Datei -- also der Seiteninhaber fügt neue Kommandos hinzu, entfernt alte oder verändert bestehende -- automatisch bei allen Anwendern, die ein Kommando an dieser URL angemeldet haben, zu einem entspechenden Update führen.

Hinweis des Übersetzers: Im Moment verstehe ich nicht, ob die Anmeldung eines einzigen Kommandos einer WebSite dazu führt, dass man auch alle anderen Kommandos dieser WebSite sozusagen implizit gleich "mitbestellt", oder ob der englischsprachige Autor sich hier nur ein wenig unglücklich ausgedrückt hat. Logisch wäre eigentlich letzteres. Ich teste das aber in jedem Fall einmal aus und formuliere dann abschliessend meine Übersetzung entsprechend eindeutig. Für den Moment habe ich eher in Richtung "letzteres" formuliert. Also, seid vorsichtig, wir bewegen uns hier auf einem Experimentierfeld, das solltet ihr nicht vergessen.

Das mag sowohl für die Anwender, als auch für die Entwickler recht bequem sein, jedoch eröffnet sich hier auch ein weiteres Sicherheitsrisiko. Nur weil zu einem bestimmten Zeitpunkt ein Kommando als sicher eingestuft worden ist, heisst das nicht, dass es in Zukunft auch sicher bleibt. Aus diesem Grund müssen wir dafür sorgen, dass das Sicherheits-Netzwerk solche Änderungen in einer Änderungshistorie protokolliert und die Anwender benachrichtigt, sobald ein Kommando unsicher geworden ist.

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 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 static map API und die Ubiquity Funktion CmdUtils.getGeoLocation() zum einfügen der Karte unseres aktuellen Standorts. Ubiquity benutzt derzeit die MaxMind API um zu versuchen, Deinen Standort aus Deiner IP-Adresse zu ermitteln. Das wird sich in Zukunft wahrscheinlich noch ändern.

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 +"'/>");
    })
  }
})

Hier sind zwei Dinge neu: der Gebrauch von CmdUtils.getGeoLocation() um die aktuelle geografische Position zu ermitteln und von CmdUtils.getImageSnapshot() um ein Bitmap der Karte zu erstellen.

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. Die Funktion CmdUtils.getGeoLocation() gibt ein Objekt zurück, das über die folgenden Eigenschaften verfügt: city, state, country, lat und long.

Wehalb müssen wir hier eigentlich die Funktion CmdUtils.getImageSnapshot() 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 snapshotImage() in ein data url um und betten dieses dann als Inline-Grafik in die betreffende Seite ein, sozusagen einen Schnapschuss der Karte.

Es existiert auch eine CmdUtils.getWindowSnapshot() 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

Echo

Wir sind jetzt dabei, uns einige unterhaltsame und nützliche Kommandos zu erstellen, mit denen die Kontrolle der wild auswuchernden Ranken des Internets für Dich zum Kinderspiel wird. Aber lass uns mit einem einfachen Kommando beginnen, das einfach in einem Benachrichtigung wiederholt, was immer Du auch an Text eingetippt haben magst.

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 );
  }
})

Das Kommando "echo" bekommt im einzigen Argument irgendeinen beliebigen Text übergeben. Was immer der Anwender eingetippt hat, wird in ein Eingabe-Object gepackt und sowohl an die Vorschau, als auch an die execute-Funktion durchgereicht.

Ubiquity kümmert sich um das Parsen der Anwender-Eingabe, so dass Du Dir keine Sorge um den Umgang mit Prounom Substitutionen oder irgendwelchen anderen umgangssprachlichen Eigenarten machen musst. Wähle auf irgendeiner Seite etwas Text und probiere das Ganze mal aus.

Das Eingabe-Objekt

Das Eingabe-Objekt, dass an Deine execute-Funktion und an Deine preview-Funktion übergeben wird, hat die folgenden Eigenschaften:

  inputObject.text  // ein Klartext-String ohne jede Formatierung
  inputObject.html  // ein HTML-formatierte String, inclusive der Tags
  inputObject.data  // für beliebiege Daten
  inputObject.summary // für sehr lange Eingaben, Anzeige eines verkürzten Strings

In unserem Beispiel wird nur die .text Eigenschaft benutzt weil hier auch einfach nur Klartext verarbeitet werden soll. In vielen Fällen, in denen ein Anwender lediglich einige kurze Wörter eintippt, enthalten die Eigenschaften .text, .html und .summary exakt denselben Wert und die .data Eigenschaft ist Null. Nicht alle, aber viele der Kommandos, die Du schreiben wirst, werden mit der .text - Eigenschaft auskommen. In den anderen Fällen stehen Dir dann aber in jedem Fall noch die drei anderen Eigenschaften zur Verfügung, die Du dann Deinen Bedürfnissen entsprechend einsetzen kannst.

Einführung in die Substantiv-Typen

Beachte, dass wir für das Argument unseres Echo-Kommandos einen Typ spezifiziert haben, nämlich das vordefinierte Objekt noun_arb_text, welches beliebigen Text als gültige Eingabe akzeptiert. Deshalb erwartet unser Echo-Kommando bei seiner Ausführung jetzt jetzt auch einen beliebigen Eingabetext. Hätten wir für unser Kommando eine mehr spezifische Eingabe vorschreiben wollen, dann müssten wir einen entsprechend spezifischeren Substantiv-Typ für das Argument wählen, z.B.:noun_type_date für Datumsangaben (wie beim "check-calendar" Kommando ) oder noun_type_language, das nur gültige Sprachennamen akzeptiert (wie der optionale Modifikator des "translate" Kommandos).

Der Nutzen dieser restriktiveren Substantiv-Typen ist, dass mit ihrer Hilfe der Ubiquity Parser bessere Vorschläge und Auto-Vervollständigungen auf der Basis von Benutzer-Eingaben generieren kann. Wenn z.B. ein Anwender irgendwo ein Datum auswählt, dann ist es wahrscheinlicher, dass er mit einem Datum-spezifischen Kommando weiterbeiten will als mit einem, dass beliebigen Text verarbeitet. In diesem Fall schlägt Ubiquity die Datum-spezifischen Kommandos zuerst vor.

Es sind die unterschiedlichsten Substantiv-Typen für die Verwendung in Kommandos denkbar: Leute, Termine, Orte, Registerkarten und was weiss ich noch alles. Derzeit sind die wenigsten dieser Typen bereits implementiert und viele dieser Implementierungen sind noch nicht mehr als eine hübsche Hülle. Und so ist dies auch eines der Betätigungsfelder, auf denen Ubiquity noch die meiste Hilfe benötigt. Substantiv-Typen aktivieren "zwingende Benutzererfahrungen" und sie ermöglichen die Wiederverwendung von Code über viele Kommandos hinweg.

Anmerkung des Übersetzers: Die Vorstellung "zwingender Benutzererfahrungen" hat mich derart fasziniert, dass ich sie zunächst einmal kritiklos in die Übersetzung aufgenommen habe. Wenn ich diesbezüglich ein wenig klarer sehe, werde ich an dieser Stelle etwas besseres abliefern. Ich hoffe, dass ihr bis dahin auch so zurecht kommt.

Wenn Du also einmal etwas mehr Erfahrung in der Programmierung von Kommandos gesammelt hast, dann sieh Dir mal die nountypes.js an, in der Du die Implementierung der meisten Substantiv-Typen finden kannst. Du wirst sehen, dass es dort Typen gibt, die Du bereits in Deinen Kommandos verwenden kannst, aber Du wirst auch nicht wenige finden, deren Implementierung noch nichts weiter ist als eine hübsche Hülle und andere die unbedingt noch verbessert werden müssen. Dann ist Dein Zeitpunkt gekommen, beteilige Dich und helf uns beim Verbessern.

Email einfügen: Kommandos mit spezifischen Argument-Typen

Lass uns jetzt einmal einen genaueren Blick auf einen der spezifischeren Substantiv-Typen werfen: noun_type_contact. Dieser macht Ubiquity bekannt, dass in der Eingabe ein Kontakt erwartet wird - in Form entweder des Namens, oder aber der E-Mail-Adresse. Durch die Verwendung dieses Substantiv-Typs wird Ubiquity auch eine Autovervollständigung zu bereits bekannten Kontakten durchführen, während der Anwender das Kommando eingibt. Dies wird in dem integrierten Ubiquity Kommando "email" angewendet.

Im Moment ermittelt Ubiquity Deine bekannten Kontakte aus Deiner Gmail-Kontaktliste. In dieser Prototyp-Version musst Du also Gmail benutzen und dort eingeloggt sein, damit Ubiquity auf Deine Kontaktliste zugreifen kann. Irgendwann wollen wir aber dazu in der Lage sein, alle wichtigen Webmail-Dienste anzusteuern und natürlich auch Desktop-Anwendungen wie Thunderbird.

Lass uns jedoch zum Stand der Dinge zurückkehren, es ist an Zeit für ein neues Kommando. Ich befinde mich ständig in der Situation, dass ich irgendjemandes E-Mail-Adresse irgendwo herauskopieren muss, um sie dann an anderer Stelle in ein Textfeld einzufügen, weil ich sie so aus dem Gedächnis nicht mehr weiss oder mir die "Handarbeit" in dem Momente einfach nur zu lästig ist. Unser neues Kommando ermöglicht das Einfügen einer E-Mail-Adresse durch Anwendung von Auto-Vervollständigung.

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 );
  }
})

Dieses Kommando verdeutlicht, was ich so an Ubiquity liebe. Mit nur acht Zeilen Programm-Code kann ich die Browser-Funkionalität grundlegend verbessern. Wollte ich dies unter Verwendung der normalen FireFox-Extension-Methodologie erledigen, erforderte dies Seiten über Seiten an Code und die Schnittstelle würde mich zudem einiges an Nerv kosten. Würde ich es unter Verwendung eines Bookmarklets machen, erforderte dies zur Umgehung des AJAX-Cross-Site-Verbots eine server-seitige Komponente und zudem noch, dass ich den Anwender dazu zwingen müsste sein E-Mail-Passwort bekannt zu geben.

Ubiquity erhöht das Innovationspotential des Browsers um ein Vielfaches dadurch, dass es jedem, der ein wenig von JavaScript versteht, die Möglichkeit gibt selbst an der Verbesserung des Browsers und des offenen Webs mitzuwirken.

TinyURL: Netzwerk-Aufrufe und jQuery

Oftmals beim schreiben von E-Mails fällt mir plötzlich auf, dass ich schon wieder einmal jemanden einen Link schicke der so lang ist, dass einem dazu nichts mehr einfällt. Ich wäre dann gerne dazu in der Lage, dieses Monster mal eben in eine TinyURL zu verwandeln, aber das ist leider immer eine knifflige Angelegenheit. Mit Ubiquity ist Rettung in Sicht.

Da wir jQuery in Ubiquity eingefügt haben, lassen sich Ajax Aufrufe sowie das Parsen der zurückgegebenen Daten mit Leichtigkeit durchführen. TinyUrl.com unterstützt eine einfach anwendbare RESTful API, der Du eine URL übergibst und die Dir dann eine URL in verkürzter Fassun zurückgibt.Wir benutzen genau diese API in unserem Kommando.

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 );
    })
  }
})

Obwohl ich hier den noun_arb_text Typ benutz habe, hätte ich eigentlich den noun_type_url Typ nehmen müssen, wenn er denn existieren würde, aber leider ...

jQuery ist ein wirklich mächtiges Werkzeug. Damit kannst Du ziemlich problemlos Deine gewünschten Daten aus einem RSS-Feed herauspicken und Vorschau-Animationen werden damit zu einem Kinderspiel.

Farbe: Definieren von Benutzer-Substantiv-Typen

Angenommen, Du schreibst einen Satz an Kommandos für Künstler und Web-Designer und Du weisst, dass einige der Kommandos mit Farben arbeiten. Du wärst dann wahrscheinlich gerne dazu in der Lage, bestimmte Kommandos nur definierte Farbbezeichungen akzeptieren zu lassen. Nun, die Anzahl benannter Farben ist begrenzt und so kannst Du einen Benutzer-Substantiv-Typ definieren, der auf einer Listen von Strings basiert, etwa so:

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

Beachte, dass wir dem neuen Objekt einen Namen gegeben haben, der mit "noun_" beginnt. Der Ubiquity Kommando-Lader erkennt Objekte die mit "noun_" beginnen automatisch als Benutzer-Substantiv-Typen, genaus so, wie er Benutzerdefinierte Kommandos automatisch erkennt, wenn diese mit "cmd_" beginnen.

Hast Du erst einmal einen Benutzer-Substantiv-Typ definiert, kannst Du ihn in so vielen Kommandos verwenden wie Du willst, also:

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

Ein Vorteil dieses benutzerdefinierten Farb-Typs ist nun, dass der Anwender z.B. "get-color bl" eingibt und Ubiquity ihm dann "black" und "blue" als mögliche Eingaben automatisch vorschlägt.

Natürlich kann nicht jeder Substantiv-Typ, der Dich interessieren würde, als endliche Liste dargestellt werden. Wenn Du Benutzer-Eingaben aufgrund eines algorithmischen Tests akzeptieren oder zurückweisen willst, dann kannst Du das durch eine eigene Substantiv-Typ-Implementierung erreichen, die Du anstatt des CmdUtils.NounType verwendest. Ein Beispiel dafür findest Du im Abschnitt über das Umschalten von Registerkarten weiter unten.

Replace: Kommandos mit mehreren Argumenten

Kommandos wie "translate" können mehrere Argumente ( möglicherweise auch optionale ) übernehmen. Ubiquity kümmert sich um das Parsen, so dass Du Dir keine Gedanken darüber machen musst, in welcher Reihenfolge der Anwender sie eingibt, denn Ubiquity schlägt ihm stets nur geeignete Werte vor.

Um das einmal zu illustrieren, werden wir jetzt ein einfaches "ersetzen" Kommando bauen, das auf Regulären Ausdrücken basiert. Es wird drei Argumente erwarten: das, was ersetzt werden soll; das, womit ersetzt werden soll und der Bereich, in dem die Ersetzung stattfinden soll. Hier ist das Kommando:

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.getSelection();
    var newText = text.replace( what.text, mods["with"].text, "i");
    CmdUtils.setSelection( newText );
  }
});

(In früheren Prototypen konnten Modifizier-Argumente nur einzelne Wörter akzeptieren, dies ist jetzt aber behoben.)

Das Modifizier-Argument arbeitet mit einem Wörterbuch, das den Namen des Substantiv-Typs als Schlüsselwert enthält. In einer späteren Version könnten wir weitergehende Optionen ermöglichen, wie zum Beispiel erforderliche und optionale Argumente usw. In later releases we may include further options, like the ability to specify an argument as required/optional, etc.

Das "translate" Kommando ist bestens dafür geeignet, mehr über Modifizierer und den Substantiv-Typ noun_type_language zu lernen.

Twitter: Wir fügen alles zusammen

In den vorhergegangenen Abschnitten haben wir jetzt alles besprochen was nötig ist, um ein Kommando zu schreiben, das uns erlaubt mit Ubiquity zu Twittern. Vielen Dank an Blair McBride für das Schreiben dieses Kommandos. Es handelt sich um voll funktionsfähigen Code: der Browser kümmert sich um Sonderfälle wie "nicht eingeloggt sein".

// 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");
      }
    });
  }
});

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 CmdUtils.CreateNounType() 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 FUEL, von wo auch die "Application"-Variable stammt.

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);
  }
}

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 CmdUtils.makeSugg() - Funktion zum erzeugen des vom Ubiquity-Parser erwarteten Eingabeobjektes. Die vollständige Signatur dieser Funktion ist:

CmdUtils.makeSugg( text, html, data );

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 makeSugg() eine Zusammenfassung für uns und hinterlegt sie im .summary Attribut des Eingabeobjekts.

Das Gleiche liesse sich auch ohne den Einsatz von makeSugg() erreichen, indem wir eine Liste von anonymen Objekten zurückgeben. Das könnte dann so aussehen:

{ text: tabName,
  html: tabName,
  data: null,
  summary: tabName };

Die Eingabeobjekte, die von unserer .suggest() - Methode erzeugt werden sind genau die Objekte, die möglicherweise an die execute() - Methode und an die preview() - 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.

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.";
  }
})

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 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:

standard-feeds
builtincmds.js
nountypes.js

Interaktion mit anderen Extensionen

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 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.

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);
  }
});

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 suggest - 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 suggest - 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 Freebase Themen vorschlägt, die auf dem Text basiert, den der Anwender eingetippt oder ausgewählt hat sowie ein ridimentäres freebase-lookup Kommando, das diesen Substantiv-Typ benutzt.

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 );
    }
  }
} );

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 suggest - Funktion die Rückgabe eines Arrays erwartet wird.
  • Die suggest - 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 suggest - 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 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 pageLoad_ 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:

function pageLoad_hi(){
 displayMessage("Hi!");
}

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.

picture5eo9.png

Hier ist der Code für Keyscape, einem Ubiquity Kommando, das die pageLoad - Funktion und die startup - Funktion benutzt, um die Funktionalität der 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.

//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;
   }
  }
}

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 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.


// 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
     });
}

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:

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 ) );
  }
});

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.

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" );

Ubiquity programmatisch schliessen

Hier ist die Zeile Code, die Entwickler benutzen können, um Ubiquity programmatisch zu schliessen (z.B. von preview ):

context.chromeWindow.gUbiquity.closeWindow();

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.