Labs/Ubiquity/Cz:Ubiquity 0.1 Tutorial Pro Autory
Zpět na Labs/Ubiquity.
Autoři: Aza Raskin, Blair McBride, Abimanyu Raja, Jono DiCarlo, Atul Varma
Přeložil: Martin Hassman původně pro magazín Zdroják
Contents
Další jazykové varianty
Tutorial tvorby příkazů pro Ubiquity 0.1
Velkou sílou Ubiquity je z pohledu vývojářů fakt, jak snadno v něm lze vytvářet příkazy. Pomocí několika řádků JavaSciptu umožní Ubiquity dokonce běžným webovým vývojářům zásadně vylepšit webový prohlížeč. Tento tutoriál vás provede od několikařádkových jednoduchých skriptů až po 50řádkový skript pro integraci s Twitterem.
Upozornění: Ubiquity se stále vyvíjí. API se pravděpodobně bude v budoucnu výrazně změnit. Ačkoliv to znamená, že příklady, které dnes napíšete, nemusí příští týden fungovat, také to znamená, že nám můžete zaslat váš feedback a ovlivnit tak směr, kterým se Ubiquity vydává. (Pozn. překl.: Některé příklady z původního textu v poslední verzi Ubiquity nefungovaly, místy jsme proto tutoriál upravili, aby fungoval i v aktuální verzi Ubiquity, tj. 0.1.5.)
Rychlý vývoj
Ubiquity po vás nebude požadovat restart Firefoxu během programování. Tomu jsme se snažili vyhnout. Místo toho Ubiquity načítá příkazy znovu pokaždé, když ho zavoláte. A pokud používáte zabudovaný editor, nemusíte dokonce nic ukládat.
Pokud chcete otevřít zabudovaný editor příkazů, zavolejte Ubiquity (control/alt + space) a vložte příkaz "command-editor". Během tohoto tutoriálu, kdykoliv budeme chtít spustit příkaz v Ubiquity, napíšeme Ubiq, např. Ubiq "command-editor".
Během následujících příkladů prostě pište do zabudovaného editoru. Příkazy se načtou, jakmile zavoláte Ubiquity.
Váš první příkaz: Hello World
Začneme tým nejtypičtějším příkladem: vypíšeme "Hello, World!".
Do zabudovaného editoru napište:
CmdUtils.CreateCommand({ name: "hello-world", execute: function() { displayMessage( "Hello, World!" ); } })
Nyní zkuste Ubiq "hello-world". Uvidíte, že se "Hello, World!" hned zobrazí na obrazovce. Pokud používáte Mac OS X s nainstalovaným Growl, objeví se Growl zpráva. Pokud používáte Windows, objeví se klasická zpráva pravém dolním rohu obrazovky.
Na Ubuntu se zobrazí:
Pokud nepoužíváte OS X s Growl ani Windows XP/Vista nebo Ubuntu, pak žádné upozornění zatím neuvidíte. V dalších verzích Ubiqity tento problém vyřešíme.
Zpět k příkladu. Výkonnou složkou našeho příkazu je funkce displayMessage(), která zobrazí zprávu způsobem obvyklým ve vašem operačním systému.
Příkaz můžete pojmenovat prakticky jakkoliv, dokonce včetně ne-ASCII znaků.
Ve jmenném prostoru CmdUtils najdete řadu užitečných funkcí. Zatím nejsou všechny dokumentovány, ale s několika užitečnými se v tomto tutoriálu seznámíte. Více informace najdete na stránce s automaticky generovanou dokumentací nebo v cmdutils.js.
Přidáme náhled
Nyní k našemu příkazu přidáme náhled. Náhled dává uživateli zpětnou vazbu dříve, než je vlastní příkaz spuštěn. Může se jednat o vizuální feedback jako je grafická reprezentace atmosférických podmínek při použití příkaz weather výše. Náhledy mohou využívat veškerou sílu HTML včetně animací, takže s nimi můžete dokázat opravdu hodně.
Důležitá poznámka k návrhu: Kód náhledu by neměl mít žádný postranní efekt, tj. samotný náhled (bez zásahu uživatele) nikdy nesmí změnit stav systému.
V našem příkazu "hello-world" nepotřebujeme dělat nic dramatického, prostě jen přidáme text sloužící pro nápovědu, který bude lepší než výchozí "Executes the hello-world command."
CmdUtils.CreateCommand({ name: "hello-world", preview: "Displays a <i>salutary</i> greeting to the planet.", execute: function() { displayMessage( "Hello, World!" ); } })
V tomto případě jsme do vlastnosti preview vložili formátovaný HTML řetězec, ale můžeme do něj vložit i funkci. To si ukážeme v další části.
Druhý příkaz: datum
Nastavujeme výběr
Často zapomínám, co je dnes za den. Možná bych měl chodit častěji ven, ale stejně jako jiní programátoři raději řeším symptomy pomocí technologie než vlastní příčinu. Proto si vytvořím příkaz, který na pozici kurzoru vloží aktuální datum.
CmdUtils.CreateCommand({ name: "date", execute: function() { var date = new Date(); CmdUtils.setSelection( date.toLocaleDateString() ); } })
Použili jsme tu novou funkci setSelection(). Ta vloží text do stránky na pozici kurzoru. Pokud je kurzor v editovatelném poli, pak dojde k vložení textu. Pokud kurzor v takovém poli není, bude setSelection() přesto schopná text vložit. (Firefox si vždy uchovává pozici kurzoru. Zobrazíte si ji pomocí klávesy F7.) Zkuste si zobrazit nějakou stránku, označit v ní kus (neměnného) textu a spusťte náš příkaz. Funguje! Jedná se o obzvláště užitečnou funkci pro příkazy typu "přelož", které vám přeloží texty na stránce.
Funkce toLocalDateString() je nativní funkce JavaScriptu. Pokud ji neznáte, podívejte se do dokumentace.
Lepší náhled
Nastal čas, abychom našemu příkazu date vytvořili lepší náhled. Přidáme tedy náhled, který nám zobrazí aktuální datum, aby uživatel viděl, co může po spuštění příkazu čekat. (Mimochodem uživatel nyní nebude dokonce muset náš příkaz spustit, aby zjistil, jaký den právě je.)
CmdUtils.CreateCommand({ name: "date", _date: function(){ var date = new Date(); return date.toLocaleDateString(); }, preview: function( pblock ) { var msg = 'Inserts todays date: "<i>${date}</i>"'; pblock.innerHTML = CmdUtils.renderTemplate( msg, {date: this._date()} ); }, execute: function() { CmdUtils.setSelection( this._date() ); } })
V tomto příkladu jsme použili 2 nové věci. Tou první je vyčlenění kódu pro zjištění aktuálního data do funkce _date(), to proto, abychom neporušili pravidlo DRY. Všimněte si, že při přístupu k této funkci jsme použili klíčové slovo this.
Tou druhou věcí je naše funkce pro náhled. Jejím prvním argumentem je DOM prvek, který bude zobrazen jako náhled našeho příkazu. Pokud upravíte obsah pblock, upravíte i náhled. V našem případě jsme jeho obsah upravili pomocí innerHTML.
A ještě jedné zajímavé věci si všimněte, použili jsme formátování pomocí funkce renderTemplate(). Ta vezme řetězec se šablonou a provede příslušné záměny podle JSON objektu, který jí předáte. Šablony umí široké spektrum funkcí, pochází z projektu Trimpath. Na jejich webu najdete další dokumentaci. V dalších verzích plánujeme použít projekt MJT.
Náhled se zobrazí uživateli okamžitě. Pokud používáte náhled, který obsahuje AJAXové volání, aby získal potřebná data, může mu takové volání chvíli trvat. Během volání byste měli uživateli něco zobrazit, např.:
preview: function( pblock ) { pblock.innerHTML = "This will show until the AJAX request returns"; // AJAX request pblock.innerHTML = getFromServer(); },
Do budoucna plánujeme tento krok ještě vylepšit.
Dokumentace a metadata
Než váš vytvořený příkaz pošlete do světa, měli byste k němu přidat alespoň následující základní atributy:
CmdUtils.CreateCommand({ name: "date", homepage: "http://azarask.in/", author: { name: "Aza Raskin", email: "aza@mozilla.com"}, contributors: ["Atul Varma"], license: "MPL", /* zbytek kódu */ })
A zcela určitě bystě měli přidat i dokumentaci:
CmdUtils.CreateCommand({ name: "date", homepage: "http://azarask.in/", author: { name: "Aza Raskin", email: "aza@mozilla.com"}, contributors: ["Atul Varma"], license: "MPL", description: "Inserts today's date.", help: "If you're in an editable text area, inserts today's date, formatted for the current locale.", /* zbytek kódu */ })
Hodnoty atributů description a help se automaticky zobrazují na stránce s výpisem všech příkazů. (Na tuto stránku se uživatel dostane zadáním příkazu "command-list".) V obou případech můžete použít i značky HTML.
Atribut description je jednořádkový popis toho, co příkaz dělá. Na druhou stranu help je delší popis, který může obsahovat příklady apod. Pokud je váš příkaz nakolik jednoduchý, že se jeho popis vejde na jednu řádku, pak použijte pouze description a help vynechte.
Nabídněte váš příkaz ostatním
Nyní, když máme hotový náš úžasný příkaz "date", jej nabídneme i ostatním. Stačí, abychom na webu vystavili javascriptový soubor a vytvořili HTML stránku, která na něj bude odkazovat pomocí značky link:
<link rel="commands" href="http://ceska-k-js-souboru" name="Sem vložte titulek" />
Poznámka: váš webserver musí nabízet soubory .js jako 'application/x-javascript'. MIME typ 'text/javascript' bude tiše ignorován.
Pokud uživatel s nainstalovaným Ubiquity tuto stránku navštíví, zobrazí se mu zpráva s nabídkou přidání vašeho příkazu.
Pokud příkaz pochází z nedůvěryhodného zdroje, uživateli se zobrazí varovná zpráva. (Neberte nijak osobně, že v Ubiquity jsou zatím všechny zdroje nedůvěryhodné.) Příkazy Ubiquity totiž mohou spustit jakýkoliv javascriptový kód s právy webového prohlížeče, proto přidáním příkazu dáte tomuto skriptu práva dělat cokoliv, co může udělat váš prohlížeč. Chceme, aby si toto nebezpečí lidé včas uvědomili, tak je trochu postrašíme.
V budoucnu budeme mít něco, co nazveme "důvěryhodná síť". Pokud vyzkoušíte příkaz z webu a bude přesvědčeni, že je bezpečný (anebo nebezpečný), zanecháte potvrzení (nebo varování). Když vaši přátelé s Ubiquity navštíví ten samý web, objeví se jim vaše potvrzení (nebo varování) na levé straně. Tímto způsobem se budou moct uživatelé spolehnout na posouzení lidí, které již znají, a kterým věří. Ti jim pomůžou rozhodnout se, zda je příkaz bezpečný.
Přidání příkazu jsme pojmenovali "subscribing". To protože pokud je javascriptový soubor aktualizován, tj. pokud vlastník webu přidá nové příkazy, odebere nebo aktualizuje původní, tak všichni uživatelé přihlášení (subscribed) k této URL dostanou automaticky aktualizace. Jedná se o pohodlný způsob pro uživatele i vývojáře, ale obsahuje další bezpečnostní problém: i přestože jsme se rozhodli, že příkaz je v tuto chvíli bezpečný, neznamená to, že zůstane bezpečný napořád. Z tohoto důvodu byste se měli ujistit, že majitel důvěryhodné sítě sleduje, kdy se příkazy změní a upozorní uživatele, pokud by se objevilo nebezpečí.
Ukaž mě na mapě, snapshoty a vkládání HTML
Příkaz "map", který Ubiquity obsahuje, je skutečně mocný. Je také slušně komplikovaný, i když to je relativní, jedná se jen několik set řádek kódu. Představte si, že si na Craigslistu (nebo na jiném webu) vybere nějaké domy nebo třeba seznam restaurací a jen zadáte příkaz "map these" a zobrazí se vám mapa, jakou potřebujete. Koncept "these" dovoluje uživatelům vytvářet mashupy. Ale to jsem odbočil. Vytvořme jednoduchý příkaz, který vloží mapu vaší aktuální polohy.
V tomto příkazu použijeme Google API pro statické mapy a Ubiquity funkci CmdUtils.getGeoLocation(), která vloží mapu s vaší aktuální polohou. Ubiquity v tuto chvíli používá API MaxMind, které odhadne vaši polohu podle IP adresy. V budoucnu to pravděpodobně ještě změníme.
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 +"'/>"); }) } })
V kódu najdete tři novinky: CmdUtils.setSelection k nastavení HTML (opravdu, i tak to můžeme udělat), CmdUtils.getGeoLocation() a CmdUtils.getImageSnapshot(), který uloží obsah obrázku.
Získání polohy na základě IP adresy není přesné, ale dává to smysl pro nastení nějaké výchozí polohy mapy. CmdUtils.getGeoLocation() vrací objekt, který má tyto vlastnosti: city, state, country, lat a long.
K čemu potřebujeme CmdUtils.getImageSnapshot()? Protože API Google Maps požaduje autorizační klíč, který je svázán s URL. Pokud bychom jen jednoduše vložili HTML značku pro obrázek do libovolné stránky, obrázek by se nenahrál, protože jeho klíč by neopovídal aktuální URL stránky. Proto použijeme funkci snapshotImage(), která zkonvertuje obrázek do data url.
Existuje další funkce CmdUtils.getWindowSnapshot(), která sejme obrázek z obsahu libovolného okna nebo panelu. Jako první parametr jí předáte objekt window a jako druhý callback funkci.
Příkazy s argumenty
Echo
Blížíme se k tvorbě zábavných a užitečných příkazů, ale začněme tvorbou velmi jednoduchého příkazu, který zopakuje vše, co mu napíšeme:
CmdUtils.CreateCommand({ name: "echo", takes: {"your shout": noun_arb_text}, preview: function( pblock, theShout ) { pblock.innerHTML = "Vypíše: " + theShout.text; }, execute: function( theShout ) { var msg = theShout.text + "... " + theShout.text + "......"; displayMessage( msg ); } })
Příkaz "echo" používá jeden argument obsahující jakýkoliv text (noun_arb_text, arbitray = jakýkoliv). Ať uživatel zadá jakýkoliv text, bude ze vstupu načten, vložen do objektu se vstupem a předán funkcím preview a execute.
Ubiquity se postará o načtení a parsování uživatelova vstupu, nemusíte se proto zatěžovat žádnými substitucemi. Zkuste si vybrat na stránce nějaký text a spustit "echo this". Ubiquity jako argument použije vybraný text.
Objekt se vstupem
Objekt s uživatelským vstupem, který obdrží funkce execute a preview, obsahuje následující atributy:
inputObject.text // řetězec obsahující vstup jako čistý text bez formátování inputObject.html // řetězec obsahující vstup v podobě formátovaného HTML včetně značek inputObject.data // pro netextová vstupní data, libovolný objekt inputObject.summary // pro velmi dlouhé vstupy, obsahuje zkrácený řetězec pro zobrazení
Náš příklad pracoval pouze s vlastností .text, protože požadoval prostý text. V mnoha případech, kdy uživatel zadá krátký jenoduchý text, budou mít atributy .text, .html a .summary stejnou hodnotu a .data bude null. Většina příkazů, které vytvoříte, budou používat pouze atribut .text. Nicméně máte k dispozici i zbylé atributy.
Představení datových typů
Všimněte si, že typ očekávaného argumentu jsme specifikovali pomocí objektu, v tomto případě předdefinovaného objektu noun_arb_text, který prohlašuje jakýkoliv vstupní text za validní argument. Pokud bychom chtěli vstup našeho příkazu více omezit, použili bychom specifičtější typ objektu ("noun_type*" objektu), např: noun_type_date, který akceptuje pouze platné datum (např. pro příkaz "check-calendar") nebo noun_type_language, který akceptuje pouze názvy jazyků (např. pro volby příkazu "translate").
Výhoda použití omezujících datových typů spočívá v tom, že Ubiquity parser dokáže uživateli lépe napovídat při zadávání vstupu. Když bude mít uživatel vybrané datum, tak příkazy, které pracují s datem, budou ve výběru upřednostněny a zobrazí se jako první v pořadí.
Existuje celá řada datových typů, které by příkazy mohly teoreticky používat např.: lidé, místa, panely prohlížeče atd. Řada z nich dosud nebyla implementována. Jedná se o oblast, ve které můžete být vývoji Ubiquity nápomocni. Datové typy dokáží zlepšit použitelnost s minimálním množstvím kódu. A mohou být použity napříč řadou příkazů.
Pokud již umíte psát příkazy pro Ubiquity, můžete se podívat do souboru nountypes.js, který obsahuje implementaci většiny datových typů. Zjistíte, které všechny datové typy jsou aktuálně dostupné, které dosud nebyly vytvořeny, a které chtějí vylepšit. I vy nám s nimi můžete pomoct.
Vkládání emailu: příkazy s argumenty se specifikovaným typem
Podívejme se na typ noun_type_contact, který na vstupu očekává osobu (buď její jméno nebo e-mailovou adresu). Pokud použijete tento typ, Ubiquity může doplňovat známé kontakty uživateli během psaní. Tímto způsobem funguje příkaz "email".
Aktuálně Ubiquity zjišťuje seznam lidí, které znáte, čtením kontaktů vašeho účtu na Gmailu. V prototypu našeho příkazu budeme používat Gmail a uživatel musí být do něj přihlášen, aby Ubiquity dokázalo zjistit, s kým se znáte. Rádi bychom časem měli rozhraní i pro další významné webmaily a podobně pro desktopové klienty jak je Thunderbird.
Ale konec otálení, začněme s dalším příkazem. Opakovaně se mi stává, že hledám e-mailovou adresu, kterou si nepamatuji. Následující příkaz mi v tom pomůže, protože obsahuje doplňování e-mailových adres.
CmdUtils.CreateCommand({ name: "insert-email", takes: {"person": noun_type_contact}, preview: "Vloží emailovou adresu podle zadaného jména.", execute: function( email ) { CmdUtils.setSelection( email.text ); } })
Na tomto příkazu můžete vidět, proč mám tak rád Ubiquity. S pouhými 8 řádky kódu jsem schopen výrazně vylepšit chování prohlížeče. Pokud bychom totéž měli dělat pomocí klasického rozšíření Firefoxu, museli bychom vytvořit několik stran kódu a vytvoření uživatelského rozhraní by zabralo mnohem víc práce. Pokud bychom totéž dělali pomocí bookmarkletu, potřebovali bychom i komponentu na serverové straně (abychom obešli omezení AJAXu na jednu doménu) a už ani nemluvím o tom, že bychom museli přimět uživatele ke sdělení hesla.
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.
TinyURL: používáme síť a jQuery
Když píšu e-maily, často vkládám příliš dlouhou URL. Rád bych ji snadno pomocí služby TinyURL zkrátil, ale přímé použití TinyURL je zdlouhavé. I zde Ubiquity pomůže.
Součástí Ubiquity je framework jQuery. Vytvořit AJAXové volání je proto snadné. TinyUrl.com nabízí jednoduché REST API, kterému pokud zašlete URL, vrátí její zkrácenou verzi. V následujícím příkazu toto API použijeme.
CmdUtils.CreateCommand({ name: "tinyurl", takes: {"url to shorten": noun_arb_text}, preview: "Zkrátí URL pomocí 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 ); }) } })
Použil jsem typ noun_arb_text, ale mohl bych použít typ noun_type_url, pokud by tedy takový typ existoval. Zatím ještě neexistuje.
jQuery je silný nástroj. Můžete s ním jednoduše vzít data z RSS, XML a dalších formátů. Můžeme jej použít i k animacím během náhledu příkazu.
Barvy: vytváříme nové datové typy
Předpokládejme, že píšete sadu příkazů pro umělce a webdesignery a máte několik příkazů, které pracují s barvami. Chtěli byste, aby některé z těchto příkazů akceptovali v argumentech jen názvy barev. Jelikož názvů barev je omezené množství, můžete si vytvořit váš vlastní datový typ pomocí pole řetězců, např. takto:
noun_type_color = new CmdUtils.NounType( "Color", ["red", "orange", "yellow", "green", "blue", "violet", "black", "white", "grey", "brown", "beige", "magenta", "cerulean", "puce"] // atd. );
Všimněte si, že vytváříme novému objektu název začínající na "noun_". Ubiquity automaticky načte všechny objekty začínající na "noun_" mezi uživatelské datové typy.
Jakmile jste definovali vlastní datový typ, můžete jej používat v kolika příkazech chcete, např.:
CmdUtils.CreateCommand({ name: "get-color-code", takes: {"color": noun_type_color}, preview: "Vloží hexadecimální kód dané barvy.", // etc...
Jednou z výhod vytvoření takového datového typu je, když uživatel zadá např. "get-color bl", Ubiquity bude schopno nabídnout "black" a "blue" jako dva možné dokončení vstupu.
Ne každý datový typ budete chtít vytvořit pomocí výčtu všech hodnot. Pokud chcete vybírat hodnoty na základě algoritmu, můžete tak učinit vytvořením vašeho vlastního typu namísto vytváření instance CmdUtils.NounType. Příklad takového použití si ukážeme příště.
Nahrazování: příkazy s více argumenty
Příkazy mohou mít i více argumentů (někdy volitelných), např. příkaz translate. Ubiquity se postará o jejich správné rozpoznání, proto se nemusíte zabývat tím, v jakém pořadí je uživatel zapsal, obdržíte slovník s požadovanými hodnotami.
Abychom si to ukázali, vytvořme si jednoduchý příkaz "replace" postavený na regulárních výrazech. Obdrží tři argumenty: předpis pro náhradu, nahrazovaný text a text, ve kterém se má náhrada provést. Zde je zmíněný příkaz:
CmdUtils.CreateCommand({ name: "replace", takes: {"what": noun_arb_text}, modifiers: {"with": noun_arb_text, "in": noun_arb_text}, preview: function( pblock, what, mods ) { // argumenty obsahují .with a .in, obojí jsou input objekty. var msg = 'Nahradí "${whatText}" za ${withText} uvnitř ${inText}.'; var subs = {whatText: what.text, withText: mods["with"].text, inText: mods["in"].text}; pblock.innerHTML = CmdUtils.renderTemplate( msg, subs ); }, execute: function( what, mods ) { // Pokud není text, ve kterém se má náhrada provést, určen, použije se aktuální výběr. var text = mods["in"].text || CmdUtils.getSelection(); var newText = text.replace( what.text, mods["with"].text, "i"); CmdUtils.setSelection( newText ); } });
Argument mods (modifiers) obsahuje slovník, ve kterém je klíčem název argumentu a hodnotou je datový typ argumentu. V dalších verzích nabídneme více možností, např. možnost určit argument jako volitelný apod.
Z kódu příkazu translate se můžete naučit víc o modifikátorech a datovém typu noun_type_language.
Twitter: použijte, co už umíte
Nyní toho umíte dost, abyste mohli vytvořit jednoduchý příkaz, který vám dovolí psát na Twitter z Ubiquity. Autorem příkazu je Blair McBride, kterému děkujeme.
Příkaz je plně funkční: prohlížeč obslouží zvláštní případy, např. pokud nejste přihlášeni.
// doporučuje se maximálně 140 znaků, ale ve skutečno je povoleno 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"); } }); } });
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.
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:
- http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/standard-feeds/
- http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/builtin-feeds/en/builtincmds.js
- http://hg.toolness.com/ubiquity-firefox/file/tip/ubiquity/feed-parts/header/en/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.