De:Ubiquity 0.1.2 Programmier-Tutorial
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.
In Ubuntu 8.04 (Hardy Heron) erscheint dagegen das hier:
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 einer Seite ( startup_ ) und nachdem sie geladen wurde (pageLoad_). Dies ist aber Stoff für ein anderes Tutorial.
Zurück zum Beispiel. Das Herzstück unsres 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
Jetzt wollen unser Kommando um eine Vorschau erweitern und geben damit dem Anwender eine Rückmeldung über die Wirkung unseres Kommandos 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 ohne Interaktion mit Anwender den Systemzustand niemals verändern.
Für unser "hallo-welt" Kommando benötigen wir keine aufwändige Vorschau: ein etwas aussagekräftiger Text als das standardmässig angezeigte "Führt das 'hallo-welt' Kommando aus" soll uns jetzt einmal 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 Curorposition 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 ablegen 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.
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
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 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.
In diesem Kommando benutzen wir Google static map API und die Ubiquity Funktion CmdUtils.getGeoLocation()
zum einfügen der Karte unsere 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.
Hier geht's nachher mit der Übersetzung weiter
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 ein 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
We'll be working towards making some fun and useful commands—commands that let you control the seething tendrils of the internet with your little pinky. But, let's start by making a simple command to echo back whatever you type.
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 ); } })
This says that the command "echo" takes one argument which is arbitrary text. Whatever text the user enters will get wrapped in an input object and passed into both the preview and execute function.
Ubiquity takes care of parsing the user's input, so you don't need to worry about handling prounoun substitution or any of the other natural-language-like features of the Ubiquity parser. Try selecting some text on a page, and Ubiq "echo this". Ubiquity should now echo the selected text.
The Input Object
The input object that your execute and preview functions receive has the following attributes:
inputObject.text // a string of the input in plain text, without formatting inputObject.html // a string of the input in formatted html, including tags inputObject.data // for non-text input types, an arbitrary data object inputObject.summary // for very long inputs, an abbreviated display string
Our example command only cares about the .text
attribute of the input, because it simply wants plain text. Often, when the user invokes your command by typing a few short words into the input box, .text
, .html
, and .summary
will all have exactly the same value, and .data
will be null. Many, if not most, commands that you write will only care about the text value. Nevertheless, the other versions of the input data are provided to you in case they differ from .text
and in case your command has a use for them.
Introduction to Noun Types
Notice that we specified the type of argument to expect by passing in an object — in this case, the predefined noun_arb_text
object, which accepts any arbitrary text as a valid argument. If we had wanted to restrict the inputs that our command could take, we could have used a more specific noun-type object to restrict the scope of the argument: for instance, noun_type_date
to accept only dates (like the "check-calendar" command) or noun_type_language
to accept only names of languages (like the optional modifiers for the "translate" command).
The benefit of specifying a more restrictive noun-type is that it helps the Ubiquity parser generate better suggestions and auto-completions based on user-input. For instance, if the user has a date selected, commands that operate specifically on dates are more likely to be apropos than commands that take arbitrary text, so Ubiquity can suggest the date-specific commands first.
There are many types of nouns that a command could conceivably take: people, dates, places, tabs, etc. Many of these noun-types aren't implemented yet, and most of the them currently have a lack-luster implementation. This is one of the areas where Ubiquity could use the greatest help. Noun-types enable creating compelling user experiences, with minimal amounts of code. It also allows for code-reuse across numerous commands.
Once you are familiar with writing commands, you should check out the nountypes.js, which has the implementation for most of the noun-types. You can see what noun types are already available for your commands to use, what still needs to be written, and where the existing implementations could use improvement — and then come get involved to help us improve them.