Přejít k navigační liště

Zdroják » JavaScript » Příkazy pro Ubiquity: rady pro pokročilé

Příkazy pro Ubiquity: rady pro pokročilé

Dnešním dílem uzavřeme seriál o tvorbě příkazů pro Ubiquity. Naučíte se ovládat panely prohlížeče z příkazů, vytvářet asynchronní datové typy, volat další rozšíření nebo vytvořit celé rozšíření pomocí Ubiquity příkazu. K ladění vašich příkazů můžete použít konzoli Firebugu.

Tento článek je překladem anglického originálu na Mozilla Wiki. Autory původního textu jsou: Aza Raskin, Blair McBride, Abimanyu Raja, Jono DiCarlo a Atul Varma.

V minulých dílech jsme probrali tvorbu jednoduchých příkazy, přidávání metadat a používání argumentů. Dnešním čtvrtým dílem celý seriál zakončíme.

Přepínání panelů

Poslední příkaz tohoto tutoriálu se bude zabývat přepínáním panelů prohlížeče. Náš cíl: napsat několik znaků, které odpovídají názvu nějakého panelu prohlížeče (v jakémkoliv okně prohlížeče) a po stisku Enter přepnout do tohoto panelu.

Napíšeme tento příkaz dvoukrokově. Prvním krokem bude vytvoření datového typu pro panely prohlížeče. Druhým krokem bude použití tohoto typu a vytvoření příkazu.

Co je Ubiquity?

Ubiquity je experimentální rozšíření prohlížeče Firefox přidávající rozhraní, jaké webové prohlížeče dosud neměly, totiž příkazovou řádku. Jedná se o nový koncept vznikající v rámci projektů Mozilla Labs. Zatímco koncept příkazů je poměrně jasný, uživatelské rozhraní je velkým experimentem a nikdo, ani Mozilla Labs, v tuto chvíli neví, kam až tento experiment zajde. Je možné, že se jedná o slepou uličku, ale možná se jedná o opravdovou revoluci.

O Ubiquity se více dočtete v článku Ubiquity – největší revoluce prohlížečů od dob Greasemonkey.

TIP: Ubiquity si můžete rozšířit o další příkazy, včetně příkazů pro české weby jako je IDOS.

Panely: vytvoření datového typu

Náš nový typ bude potřebovat jen dvě části: název a našeptávací (suggest) funkci. Časem pravděpodobně vytvoříme funkci CmdUtils.CreateNounType(), která vše ještě víc zjednoduší.

Název typu se zobrazuje během zapisování dat uživatelem. Našeptávač vrátí seznam vstupních objektů, každý z nich obsahuje název jednoho odpovídajícího panelu prohlížeče. Pro interakci s prohlížečem použijeme knihovnu FUEL, konkrétně objekt  Application.

var noun_type_tab = {
  _name: "tab name",

  // Vrátí panely ze všech oken prohlížeče
  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: implementovat lepší algoritmus
    for ( var tabName in tabs ) {
      if (tabName.match(text, "i"))
     suggestions.push( CmdUtils.makeSugg(tabName) );
    }

    // Vrať seznam vstupních objektů, maximálně 5:
    return suggestions.splice(0, 5);
  }
} 

Metoda suggest vždy obdrží jak argument text, tak html. Pokud vstup pochází z části stránky, na které uživatel něco označil, mohou se tyto hodnoty vzájemně lišit, obě budou obsahovat textové řetězce, ale html bude obsahovat i značky HTML, zatímco text nikoliv. Náš typ se zajímá pouze o textový vstup, argument html budeme ignorovat.

Použili jsme funkci CmdUtils.makeSugg() k vytvoření vstupního objektu, který parser Ubiquity očekává. Její plná definice vypadá takto:

CmdUtils.makeSugg( text, html, data ); 

Parametry html a data jsou volitelné a měly by být zadány jen v případě, že se liší od hodnoty text.

Pokud je text nebo html příliš dlouhý, pak makeSugg() vytvoří stručnou verzi a uloží ji do atributu  .summary.

Prakticky stejného výsledku bychom dosáhli i bez volání makeSugg(), pokud bychom vraceli anonymní objekty typu:

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

Vstupní objekty, které naše metoda .suggest() vytvoří, jsou tytéž objekty, které obdrží metody execute() a preview() u příkazu, který náš datový typ používá.

Vlastní příkaz

Datový typ již máme hotový, bude pro nás již jednoduché vytvořit náš příkaz pro přepínání panelů. I zde použijeme knihovnu FUEL, tentokrát k přepnutí panelu.

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

Několik tipů

Nyní byste měli vědět vše, co potřebujete k tvorbě vlastních užitečných příkazů pro Ubiquity.

Přidáme ještě několik tipů, které se nikam jinam nevešly.

Zdrojový kód vestavěných příkazů

Zkoumání zdrojového kódu vestavěných příkazů a datových typů může být velmi užitečné. Pokud jste získali zdrojový kód Ubiquity (viz návod), důležitý zdrojový kód se nachází v těchto souborech a adresářích:

ubiquity/standard-feeds/
ubiquity/builtin-feeds/en/builtincmds.js
ubiquity/feed-parts/header/en/nountypes.js 

Pokud nemáte zdrojový kód, najdete poslední verze souborů na těchto adresách:

standard-feeds
builtincmds.js
nountypes.js 

Jak interagovat s dalšími rozšířeními

Příkaz get-lyrics

Je to jednoduché. Ukažme si příkaz (jeho autorem je Abimanyu Raja, kterému děkujeme), který hledá texty písniček. Můžete jednoduše napsat „get-lyrics wild international“, ale příkaz umí i využít rozhraní rozšíření FoxyTunes (pokud je máte nainstalované) a přidat právě přehrávanou skladbu do našeptávače. Komunikace s dalšími rozšířeními je jednoduchá, protože si můžete zobrazit jejich zdrojový kód.

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

Implementace asynchronního našeptávání

Všechny datové typy, které jsme zatím viděli, fungovaly synchronně, tj. vrátily své hodnoty okamžitě. Ubiquity podporuje i asynchronní datové typy. Jsou užitečné, pokud datový typ vyžaduje časově náročný úkon před nabídnutím hodnot, nejčastěji pokud volá externí službu.

Implementace asynchronního našeptávání je jednoduchá. Kdykoli volá parser v Ubiquity metodu suggest, předává jí odkaz na callback funkci, kterou můžete použít k předání hodnot. Nejčastější scénář je, když metoda suggest vytváří AJAX požadavek a zavolá callback funkci z callback funkce AJAXu.

Následuje jednoduchý příklad: datový typ, který nabízí témata z Freebase založená na textu, který zadal uživatel, a příkaz freebase-lookup, který tento typ používá.

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

Několik poznámek:

  • Callback funkce parseru očekává jen jeden datový objekt (nikoliv jejich pole), musí být proto zavolána pro každý našeptávaný výraz jednou, a to i v případě, kdy datový typ má několik dostupných hodnot najednou (stejně jako v příkladu Freebase výše). To je trochu odlišné od synchronního volání, u kterého metoda suggest vrací pole.
  • Metody suggest typicky vrací prázdné pole, když budou odpovídat asynchronně, ale mohou také vrátit jednu nebo více hodnot synchronně, pokud jsou dostupné.
  • Jelikož činnost asynchronních volání bývá časově náročná a protože metoda suggest může být volána po každém stisku klávesy uživatele, měli byste zvážit implementaci zpožděného vykonávání a cachování. Ubiquity to v tuto chvíli nechává na jednotlivých datových typech.
  • Mnohem lepší implementaci datových typů a la Freebase najdete na graynorton.com.

Spouštění skriptu po načtení stránky a při startu

Abyste mohli spustit nějaký kód po načtení stránky, vytvořte funkci s prefixem pageLoad_. Například, pokud chcete říct „Ahoj“ pokaždé, když se nahraje stránka, může váš kód vypadat takto:

function pageLoad_hi(){
 displayMessage("Ahoj");
} 

Pokud funkci upravíte a chcete vidět změny, nezapomeňte před tím zavolat Ubiquity. Ačkoliv vaše funkce, jako ta výše, není Ubiquity příkazem, je cachovaná.

Podobně, pokud chcete spustit kód vždy při startu Firefoxu, vytvořte funkci s prefixem  startup_.

Úžasné na tom je, že tímto způsobem můžete vytvořit kompletní rozšíření Firefoxu (pokud bude používat jen minimální uživatelské rozhraní) jako Ubiquity plugin, a to jen s malým množstvím kódu. Nemusíte se starat o chrome.manifest nebo install.rdf. Další výhodou je, že nemusíte restartovat Firefox během vývoje (tedy poukud nepoužíváte spouštění kódu při startu prohlížeče).

Ukázka vyhledávání v Googlu

Tady je kód pro Keyscape, což je příkaz pro Ubiquity, který využívá pageLoad a startup pro znovuvytvoření funkce rozšíření Search Keys od Jesse Rudermana. Tento příkaz vám dovolí procházet výsledky Googlu stiskem patřičného čísla. Ke každému odkazu přidá číslo jako nápovědu.

//Velká část tohoto kódu pochází z rozšíření Search Keys
//Děkujeme Jesse Rudermanovi

function startup_keyscape() {
  window.addEventListener("keydown", keyscape_down, true);
}

function pageLoad_keyscape(doc){

  var uri = Utils.url(doc.documentURI);
  //Pokud se jedná o about: nebo chrome://, nic nedělej a vyskoč
  if(uri.scheme != "http")
    return;

  //Zkontroluj, zda zobrazujeme stránku Googlu
  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) //numerická klávesnice se zapnutým numlockem
     num = key - 96;
   else
     return;

   //Pokud jsme v textboxu nebo podobném prvku, nic nedělej
   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;
   }

   //Získej URL z stránky s výsledky
   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;
   }
  }
} 

Pokud se Ubiquity stane všudypřítomné (pozn. překladatele: jedná se o slovní hříčku, protože všudypřítomný = ubiquitous), může být řada rozšíření přepsána pomocí Ubiquity příkazů. Bude to příjemnější pro koncového uživatele, protože instalace příkazů pro Ubiquity je mnohem snazší.

V budoucnu bude mít Ubiquity pravděpodobně také možnost zkonvertovat příkaz do formy řádného rozšíření. Podívejte se na aktuální stav této vlastnosti.

Firebug

Pokud chcete, aby se vaše chybová a varovná hlášení objevovala v konzoli Firebugu, měli byste povolit Chrome chyby a varování. Používejte raději CmdUtils.log() než console.log() Poznámka: zatím můžete metodě log() předat pouze jeden argument.

Přidávání příkazů z kódu

Následuje ukázka kódu, která zaregistruje příkaz obsažený v rozšíření Firefoxu.

// Pomocná funkce najde lokální adresář s rozšířením,
// které obsahuje příkaz k instalaci. Pomocí ní vytvoříme
// URL k javascriptovému souboru, který obsahuje implementaci
// příkazů k přidání.
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;
}

// vaše rozšíření musí zavolat příkaz addUbiquity vždy, když se spustí
// nová session prohlížeče a nahraje vaše rozšíření
function addUbiquityCommands(){
     // url souboru s implementaci příkazu, který chceme přidatof the commands
     var url = getBaseUri() + "content/app/ubiquity/commands.js"

     // odkaz na ubiquity setup modul.
     var jsm = {};
     Components.utils.import("resource://ubiquity/modules/setup.js", jsm);

     // najdi feed manager a přidej nový příkaz. Poznámka: používáme volbu
     // isBuiltIn=true, aby se příkaz přidal jen během doby jedné session prohlížeče
     // To zjednoduší životní cyklus příkazu: když bude
     // rozšíření vypnuté nebo odinstalované, příkaz bude automaticky "odstraněn"
     jsm.UbiquitySetup.createServices().feedManager.addSubscribedFeed( {
    url: url,
        sourceUrl: url,
        canAutoUpdate: true,
        isBuiltIn: true
     });
} 

Uvnitř vašeho příkazu můžete používat importování modulů nebo XPCOM singletonových komponent pro zpětné volání metod vašeho rozšíření. Zde je ukázka příkazu, který tak činí:

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

Poznámka: Pokud váš příkaz potřebuje načíst další javascriptové soubory vašeho rozšíření, můžete použít následující kód na začátku souboru s příkazem, který přidáváte do Ubiquity.

function include( partialURI )
{
    // Načtení JS knihoven
    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" ); 

Závěr

Můžeme zopakovat, co jsme řekli již dříve: Ubiquity mnohonásobně zvyšuje prostor pro inovace webových prohlížečů tím, že umožní komukoliv, kdo dokáže napsat jednoduchý JavaScript, vylepšit používání webu. Třeba i vám.

Tak začněte a zkuste něco vytvořit.

Článek, který jste právě dočetli, je překladem anglického originálu a je zde publikován s laskavým svolením Mozilla Corporation.

Komentáře

Subscribe
Upozornit na
guest
1 Komentář
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Eremita

Ahoj vsem, udelal jsem si takovou drobnustku, ale myslim celkem praktickou – upravil jsem to preview pro funkci s vyberem tabu, ze to rovnou ukaze nahled tabu, tak kdyby to nekoho zajimalo:

preview: function( pblock, directObj ) {
var tabName = directObj.text;
Cmdutils.getTabSnapshot(tabName, width 200);
}

Eremita

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.