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.
Seriál: Jak vytvářet vlastní příkazy pro Ubiquity ve Firefoxu (4 díly)
- Jak vytvářet vlastní příkazy pro Ubiquity ve Firefoxu 30. 1. 2009
- Tvoříme příkazy pro Ubiquity: metadata, přidávání a aktualizace 6. 2. 2009
- Příkazy pro Ubiquity používající argumenty 13. 2. 2009
- Příkazy pro Ubiquity: rady pro pokročilé 20. 2. 2009
Nálepky:
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

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).

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.
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