De:Ubiquity 0.1.2 Programmier-Tutorial: Difference between revisions
Line 497: | Line 497: | ||
</pre> | </pre> | ||
= | = Registerkarten umschalten = | ||
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. | |||
== | == Umschalten: Schreib Deinen eigenen Substantiv-Typ == | ||
Ein Substantiv-Typ benötigt nur zwei Dinge: einen Namen und eine Vorschlags-Funktion. Soon, we'll probably move to having a convenience <code>CmdUtils.CreateNounType()</code>, which will simplify things even more. | |||
The name is what shows up when the command prompts for input. Suggest returns a list of input objects, each one containing the name of a matching tab. We're using [http://developer.mozilla.org/en/docs/FUEL FUEL] to interact with the browser, which is where the "Application" variable comes from. | The name is what shows up when the command prompts for input. Suggest returns a list of input objects, each one containing the name of a matching tab. We're using [http://developer.mozilla.org/en/docs/FUEL FUEL] to interact with the browser, which is where the "Application" variable comes from. |
Revision as of 05:48, 10 March 2009
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 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
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.
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 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
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.
Umschalten: Schreib Deinen eigenen Substantiv-Typ
Ein Substantiv-Typ benötigt nur zwei Dinge: einen Namen und eine Vorschlags-Funktion. Soon, we'll probably move to having a convenience CmdUtils.CreateNounType()
, which will simplify things even more.
The name is what shows up when the command prompts for input. Suggest returns a list of input objects, each one containing the name of a matching tab. We're using FUEL to interact with the browser, which is where the "Application" variable comes from.
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); } }
The suggest method of a noun type always gets passed both text and html. If the input is coming from a part of a web page that the user has selected, these values can be different: they are both strings, but the html value contains markup tags while the text value does not. The Tab noun type only cares about the plain text of the tab name, so we ignore the value of html.
We use the convenience function CmdUtils.makeSugg()
to generate an
input object of the type that the Ubiquity parser expects. The full signature of this function is
CmdUtils.makeSugg( text, html, data );
but html and data are optional and need be provided only if they differ from text.
If the text or html input is very long, makeSugg()
generates a summary for us, and puts it in the .summary
attribute of the input object.
We could have accomplished mostly the same thing without calling makeSugg()
by returning a list of anonymous objects like these:
{ text: tabName, html: tabName, data: null, summary: tabName };
The input objects that our .suggest()
method generates are the same objects that will eventually get passed in to the execute()
and preview()
methods of any commands that use this noun type.
Switching Tabs: The Command
Now that we are armed with the tab noun-type, it is easy to make the tab-switching command. Again, we use FUEL to focus the selected tab.
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."; } })