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

Odebírat
Upozornit na
guest
1 Komentář
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
Zobrazit všechny komentáře
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

Přístupnost není jen o splnění norem: nový pohled na inkluzivní design

Přístupnost a inkluze možná nepatří mezi nejžhavější témata digitálního světa – dokud o nich nezačne mluvit Vitaly Friedman. Na WebExpo 2024 předvedl, že inkluzivní design není jen o splněných checkboxech, ale hlavně o lidech. S energií sobě vlastní obrátil zažité přístupy naruby a ukázal, že skutečně přístupný web je nejen možný, ale i nezbytný.

Efektivnější vývoj UI nebo API: Co si odnést z WebExpo 2025?

Různé
Komentáře: 0
Jak snadno implementovat moderní uživatelské rozhraní? Které funkce brzdí rychlost vašeho webu? A kdy raději sami přibrzdit, abychom využitím AI nepřekročili etické principy? Debatu aktuálních dev témat rozdmýchá sedmnáctý ročník technologické konference WebExpo, která proběhne v Praze od 28. do 30. května. Který talk či workshop si rozhodně nenechat ujít? Toto je náš redakční výběr z vývojářských hroznů.

Zapřáhněte AI jako nikdy předtím. Květnová konference WebExpo přivítá hvězdy technologického světa

Od 28. do 30. května 2025 promění pražský Palác Lucerna na tři dny technologická konference WebExpo. Na programu je více než 80 přednášek a workshopů od expertů z celého světa. WebExpo tradičně propojuje vývojáře, designéry, marketéry i byznysové lídry a nabízí praktické dovednosti, strategické myšlení a přináší nejnovější trendy nejen v oblasti AI.