De:Ubiquity 0.1.2 Programmier-Tutorial: Difference between revisions

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


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


Zurück zum Beispiel. Das Herzstück unseres Kommandos ist die Funktion displayMessage(). Durch sie wird eine Nachricht in betriebssystem-spezifischer Weise auf dem Bildschirm angezeigt.
Zurück zum Beispiel. Das Herzstück unseres Kommandos ist die Funktion displayMessage(). Durch sie wird eine Nachricht in betriebssystem-spezifischer Weise auf dem Bildschirm angezeigt.
Line 661: Line 661:
</pre>
</pre>


== Implementing Asynchronous Noun Suggestions ==
== Implentierung asynchroner Substantiv-Vorschläge ==


The noun types we've seen so far in this tutorial have all worked synchronously, returning their suggestions right away. However, Ubiquity also supports asynchronous noun suggestions. These are useful for when a noun type needs to do some potentially time-consuming work before it can make suggestions &mdash; most commonly when it needs to call an external service.
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.


Implementing asynchronous suggestions is simple. Whenever the Ubiquity parser calls a noun type's <code>suggest</code> function, it includes a callback function that may be used to send suggestions back to the parser as they become available. In the most typical case, the noun type's <code>suggest</code> function makes an AJAX request, invoking the parser's callback function from within the callback function for the AJAX request.
Die Implementierung asynchroner Vorschläge ist recht einfach. Wann immer der Ubiquity-Parser die <code>suggest</code> - Funktion eines Substantiv-Typs aufruft, schliesst er dabei auch eine Rückruf-Funktion ein, die für die Rücksendung von Vorschlägen an den Parser verwendet werden, sobald diese verfügbar werden. Typischer Weise führt die <code>suggest</code> - Funktion des Substantiv-Typs einen AJAX-Request aus und ruft dabei die Rückruf-Funktion des Parser aus der Rückruf-Funktion des AJAX-Request heraus auf.


Here's a simple example: a noun type that suggests [http://www.freebase.com/ Freebase] topics based on the text the user has typed or selected, and a barebones <code>freebase-lookup</code> command that uses the noun type.
Dazu hier ein einfaches Beispiel: ein Substantiv-Typ, der [http://www.freebase.com/ Freebase] Themen vorschlägt, die auf dem Text basiert, den der Anwender eingetippt oder ausgewählt hat sowie ein ridimentäres <code>freebase-lookup</code> Kommando, das diesen Substantiv-Typ benutzt.


<pre>
<pre>
Line 706: Line 706:
</pre>
</pre>


A few notes:
Ein paar Anmerkungen dazu:


* The parser's callback function expects only one suggestion (not an array of suggestions), so it must be called one time for each suggestion, even if the noun type has multiple suggestions available at the same time (as in the Freebase example above). This is a bit different from the synchronous case, in which the <code>suggest</code> function is expected to return an array.
* Die Rückruf-Funktion des Parsers akzeptiert lediglich einen einzelnen Vorschlag ( nicht ein Array von Vorschlägen ), deshalb muss sie für jeden Vorschlag einzeln aufgerufen werden, auch dann, wenn der Substantiv-Typ mehrere Vorschläge gleichzeitig verfügbar hat ( wie in dem Freebase-Beispiel oben ). Das unterscheidet sich ein wenig von dem synchronen Fall, in welchem von der  <code>suggest</code> - Funktion die Rückgabe eines Arrays erwartet wird.
* A noun type's <code>suggest</code> function typically returns an empty array when it intends to make asynchronous suggestions, but it can return one or more suggestions synchronously if it has them available.
* Die  <code>suggest</code> - Funktion eines Substantiv-Typs gibt typischer Weise ein leeres Array zurück, wenn sie einen asynchronen Vorschlag beabsichtigt, aber sie kann einen oder mehrere Vorschläge synchron zurückgeben, wenn ihr diese zur Verfügung stehen.
* Because the work being done to generate asynchronous suggestions is generally somewhat expensive, and because a noun type's <code>suggest</code> function may be called for every keystroke the user makes, you should probably consider implementing a delay before starting the work and/or caching the work at some level. Ubiquity currently leaves this up to each noun type individually.
*Weil der Aufwand für die Erzeugung asynchroner Vorschläge relativ hoch ist und die <code>suggest</code> - Funktion eines Substantiv-Typs bei jedem Tastendruck des Anwenders aufgerufen wird, sollte man eine Zeitverzögerung für den Aufruf implementieren und/oder die bereits ermittelten Vorschläge für weitere Aufrufe cachen. Ubiquity überlässt das zur Zeit jedem Substantiv-Typ selbst.
* A much more robust implementation of Freebase-derived noun types can be found [http://graynorton.com/ubiquity/freebase-nouns.html here].
* Sieh Dir auch einmal diese wesentlich [http://graynorton.com/ubiquity/freebase-nouns.html robustere Implementierung eines Freebase-abgeleiteten Substantiv-Typs] an.
 
== Codeausführung nach dem Laden einer Seite und beim Start von FireFox ==
 
Wenn Du irgendwelchen Code nach dem Laden einer Seite ausführen willst, brauchst Du einfach nur den Namen Deiner Funktion mit dem Präfix <code>pageLoad_</code> beginnen zu lasen. Willst Du also zum Beispiel jedesmal "Hi!" sagen, nachdem eine Seite geladen wurde, dann müsste Dein Code in etwa wie folgt aussehen:
 
<pre>
function pageLoad_hi(){
displayMessage("Hi!");
}
</pre>
 
Wenn Du diese Funktion abänderst und willst dann die Änderungen sehen, vergiss nicht, zuerst Ubiquity aufzurufen. Obwohl eine Funktion wie diese nicht unbedingt ein Ubiquity-Kommando sein muss ist dennoch ein Refresh des gecachten Codes erforderlich, ebenso, wenn Du irgendwelchen Code ausführen willst, jedesmal wenn FireFox startet.
 
Das beeindruckendste an solcher Art Funktionen ist, das Du damit komplette  Firefox Extensionen (die mit einem minimalen Benutzerschnitstelle auskommen) als Ubiquity-Plugins mit wesentlich weniger Zeilen Code erstellen kannst. Du brauchst Dir keinerlei Gedanken wegen chrome.manifest oder install.rdf zu machen. Ein weiter Vorzug ist, dass Du während der Entwicklung niemals FireFox neu starten musst, ausgenommen natürlich, wenn Dein Code ausgeführt werden soll, nachdem FireFox gestartet ist.
 
<center>http://img388.imageshack.us/img388/3086/picture5eo9.png</center>
 
Hier ist der Code für [http://foyrek.com/commands/keyscape.js Keyscape], einem Ubiquity Kommando, das die <code>pageLoad</code> -  Funktion und die <code>startup</code> - Funktion benutzt,  um die Funktionalität der [https://addons.mozilla.org/en-US/firefox/addon/339 Search Keys extension] von Jesse Ruderman nachzugbilen. In Übereinstimmung mit Ubiquity's Ziel, Dir zu ermöglichen, Dinge schneller und einfacher per Tastatur zu erledigen, kannst Du mit diesem Kommando lediglich durch Eingabe einer Nummer aus einem Google-Suchresultat auswählen. Das Kommando fügt den Links entprechende Anmerkungen zu.
 
<pre>
//Eine Menge diese Codes ist der  Search Keys Extension entliehen
//Vielen Dank an Jeese Ruderman
 
function startup_keyscape() {
  window.addEventListener("keydown", keyscape_down, true);
}
 
function pageLoad_keyscape(doc){
 
  var uri = Utils.url(doc.documentURI);
  //If we are on about: or chrome://, just return
  if(uri.scheme != "http")
    return;
 
  //Check if the page we are on is google
  if( keyscape_isGoogle(uri) ){
         
    for(var num=1; num<=10; num++){
 
      var link = jQuery(doc.body).find('a.l')[num-1];
     
      if( link ){
 
        var hint = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
        hint.style.color = "blue";
        hint.style.background = "white";
        hint.style.padding = "1px 2px 1px 2px";
        hint.style.marginLeft = ".5em";
        hint.appendChild(doc.createTextNode(num == 10 ? 0 : num));
        link.parentNode.insertBefore(hint, link.nextSibling);
      } 
    }
  }
}
 
function keyscape_isGoogle(uri){
  return uri.host.indexOf("google") != -1
&& (uri.path.substr(0,8) == "/search?"
        || uri.path.substr(0,8) == "/custom?");
}
 
function keyscape_down(event){
 
  var doc =  Application.activeWindow.activeTab.document;
  var uri = Utils.url(doc.documentURI);
  if( keyscape_isGoogle(uri) ){
  var key = parseInt(event.keyCode || event.charCode);
  var num;
  if(48 <= key && key <= 57) //number keys
    num = key - 48;
  else if(96 <= key && key <= 105) //numeric keypad with numlock on
    num = key - 96;
  else
    return;
 
  //Don't do anything if we are in a textbox
  //or some other related elements
  var elt = window.document.commandDispatcher.focusedElement;
 
  if (elt) {
    var ln = new String(elt.localName).toLowerCase();
    if (ln == "input" || ln == "textarea" || ln == "select" || ln == "isindex")
        return;
  }
   
  //Get the link url from the search results page
  var url_dest = jQuery(doc.body).find('a.l').eq(num-1).attr('href');
 
  if(event.altKey){
    //Open in new tab
    Application.activeWindow.open(Utils.url(url_dest));
  }else{
    //Open in same tab
    doc.location.href = url_dest;
  }
  }
}
</pre>
 
Falls Ubiquity tatsächlich allgegenwärtig sein wird, können eine Menge Extensionen als Ubiquity-Kommandos neu geschrieben werden.Dies ist wesentlich freundlicher für den Endanwender genau so, wie auch die Installation von Ubiquity-Kommandos selbst wesentlich einfacher ist.
 
In Zukunft wäre es auch nicht schlecht die Möglichkeit zu haben, Deine Ubiquity-Kommandos in ordnungsgemässe FireFox-Extensionen konvertieren zu können. Siehe einmal  [http://labs.toolness.com/trac/ticket/3 hier] nach, wie weit diese Funktionalität schon fortgeschritten ist.
 
== Firebug ==
 
Du solltest Chrome Fehler und Warnungen aktivieren wenn Du möchtest, dass die Fehler Deines Codes in der FireBug-Konsole angezeigt werden. Benutze CmdUtils.log() anstatt console.log() ''Hinweis: Derzeit ist lediglich die Übergabe eines Arguments an  log()  möglich.''
 
== Programmatisches hinzufügen von Kommandos ==
 
Hier ist ein Stückchen Code das Dir zeigt, wie ein Entwickler ein Kommando programmatisch in einer FireFox-Extension registrieren kann.
 
<pre>
 
// Helper function used to determine the local directory where the
// extension which contains the command is installed. This is used
// to create the URL of the js file which includes the implementation
// of the list of commands you want to add.
function getExtensionDir() {
    var extMgr = Components.classes["@mozilla.org/extensions/manager;1"]
                .getService(Components.interfaces.nsIExtensionManager);
    return extMgr.getInstallLocation( "feedly@devhd" ).getItemLocation( "feedly@devhd" );
}
 
function getBaseUri() {
    var ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);
    var extDir = getExtensionDir();
    var baseUri = ioSvc.newFileURI(extDir).spec;
    return baseUri;
}
 
// your extension needs to call the addUbiquity command each time a new browser
// session is create and your extension is loaded.
function addUbiquityCommands(){
    // url of the file which contains the implementation of the commands
    // we want to add.
    var url = getBaseUri() + "content/app/ubiquity/commands.js"
    // link to the ubiquity setup module.
    var jsm = {};
    Components.utils.import("resource://ubiquity/modules/setup.js", jsm);
    // look up the feed manager and add a new command. Note: we are using the
    // isBuiltIn=true option so that the command is only added for the duration
    // of the browser session. This simplifies the overall lifecycle: if the
    // extension is disabled or un-installed, it will be automatically
    // "removed" from ubiquity.
    jsm.UbiquitySetup.createServices().feedManager.addSubscribedFeed( {
url: url,
        sourceUrl: url,
        canAutoUpdate: true,
        isBuiltIn: true
    });
}
</pre>
 
Innerhalb Deiner Kommando-Implementierung kannst Du Import-Module benutzen oder in einer Singleton XPCOM Komponente nachschlagen, um Dein Kommando zu der Funktionalität zurück zu verlinken, die in Deiner Extension eingekapselt ist.
 
Hier ein Beispiel-Kommando, das genau dies tut:
 
<pre>
var StreetsUtils = function(){
    var that = {};
    that.lookupCore = function(){
        return Components.classes["@devhd.com/feedly-boot;1"]
.getService(Components.interfaces.nsIFeedlyBoot)
.wrappedJSObject
.lookupCore();
    };
    return that;
}();
 
CmdUtils.CreateCommand({
  name: "my-extension-test",
  takes: {"message": noun_arb_text},
  icon: "chrome://my-extension-test/skin/icon-16x16.png",
  modifiers: {to: noun_type_contact},
  description:"Testing the feedly+ubiquity integration",
  help:"This is a test help message",
  preview: function(pblock, directObj, modifiers) {
    var html = "Testing my-extension-test ";
    if (modifiers.to) {
      html += "to " + modifiers.to.text + " ";
    }
    if (directObj.html) {
      html += "with these contents:" + directObj.html;
    } else {
      html += "with a link to the current page.";
    }
    pblock.innerHTML = html;
  },
 
  execute: function(directObj, headers) {
      CmdUtils.log( ">>> my-extension core? " + ( StreetsUtils.lookupCore() != null ) );
  }
});
</pre>
 
Beachte: Wenn Dein Kommando andere JS-Dateien in die Extension einbinden oder laden muss, kannst Du dafür das folgende Code-Stückchen an den Anfang der entsprechenden Kommando-JS-Datei setzen, welche Du zu Ubiquity hinzufügst.
 
<pre>
function include( partialURI )
{
    // Load JS libraries
    var u = "chrome://feedly/content/app/" + partialURI;
    var jsl = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader); 
    jsl.loadSubScript( u );
}
 
include( "ubiquity/templates.ubiquity.js" );
</pre>
 
== Ubiquity programmatisch schliessen ==
 
Hier ist die Zeile Code, die Entwickler benutzen können, um Ubiquity programmatisch zu schliessen (z.B. von preview ):
 
<pre>
context.chromeWindow.gUbiquity.closeWindow();
</pre>
 
= Zusammenfassung =
 
Um nochmals einen Punkt zu wiederholen, den ich bereits zuvor schon einmal angeführt habe: Ubiquity erhöht das Innovationspotential des Browsers um ein Vielfaches dadurch, dass es jeden ,der einigermassen mit JavaScript umgehen kann, dazu in die Lage versetzt, sich an der Verbesserung des Browsers und des offenen Web persönlich zu beteiligen. Du bist einer dieser Menschen.
 
Ziehe jetzt also dahin und verbessere.
166

edits