JAK: JavaScriptová knihovna z dílny Seznamu

V tomto článku nahlédneme pod pokličku Seznamu – seznámíme se s JavaScriptovou knihovnou JAK, kterou firma již delší dobu používá, vyvíjí a nabízí ostatním vývojářům jako Open Source. Představí ji jeden ze spoluautorů, Ondřej Žára ze společnosti Seznam.

Počátky projektu JAK sahají do temné minulosti Internetu, do dob, kdy Internet Explorer 6 byl král a Mark Zuckerberg tahal kačera po kolejích Harvardu. Ambiciózní projekty typu mapy.cz (ve své – dnes archaické – první verzi) potřebovaly solidní základ ve formě javascriptové knihovny, ovšem žádné ucelené řešení tehdy neexistovalo. Tak začal Seznam, věren tradičnímu heslu „co si neudělám sám, to nemám“, vyvíjet vlastní sadu nástrojů, která se do dnešních dní pěkně vyvinula a rozšířila. JAK je tak dnes k vidění na velkém množství webů; krom zmiňovaných map také třeba na TV programu, Novinkách, Seznam.cz a dalších.

Vývoj knihovny JAK stále probíhá, tak jak přicházejí nové požadavky a nové technologie. Před několika týdny byly veškeré zdrojové kódy přesunuty na Github, a zároveň došlo k vydání nové, zpětně nekompatibilní verze. Každý tak může sledovat průběh vývoje, pomáhat s hledáním chyb a přidávat novou funkcionalitu.

Struktura projektu není složitá. Hlavní součástí knihovny je soubor jak.js obsahující veškerou základní funkcionalitu. Zbytek JAKu je tvořen velkým množstvím volitelných doplňkových modulů, knihoven a widgetů. Na domovské stránce projektu je zdokumentována naprostá většina kódu, včetně ukázek. Tento článek se soustředí na ty části, které nemají žádnou vizuální reprezentaci. Podívejme se proto nyní detailněji na některé zajímavé či netradiční prvky JAKu.

V dalším textu bude řada ukázek zdrojového kódu. Nejednoho čtenáře přitom může zmást použitý jmenný prostor těchto ukázek. Zatímco současná (zvolna zastarávající) verze knihoven používá objekt SZN, ve všech ukázkách je zde použit objekt JAK. To proto, že nový namespace JAK je jednou z nejviditelnějších změn ve zbrusu nové verzi, k dostání na Githubu. 

jak.js

Jádro JAKu vzniklo zřetězením těch nejčastěji používaných knihoven, modulů a funkcí. Obsahuje vše nutné pro překonávání rozdílů mezi prohlížeči, práci se stromem stránky, zpracovávání událostí, dědičnost, XHR a další.

Objekty, dědičnost a spol.

Všechny objektově orientované potřeby pokrývá modul JAK.ClassMaker. Zastřešuje funkcionalitu spojenou s realizací dědičnosti (ano, tím správným způsobem), emulací vícenásobné dědičnosti, voláním metod předka, tvorbou jedináčků a dekorátorů. Použitá terminologie silně odráží techniku duck typing, která nás i v JavaScriptu ospravedlňuje ohánět se „termity“ jako třída, instance, rozhraní apod. Namísto dlouhého psaní hned uvedu ukázku.

var Clovek = JAK.ClassMaker.makeClass({
    NAME: "Clovek",
    VERSION: "1.0"
});

Clovek.prototype.$constructor = function(jmeno) {
    this._jmeno = jmeno;
}

Clovek.prototype.pozdrav = function() {
    alert("<" + this._jmeno + "> takze dobry vecer.");
}
var jarda = new Clovek("Jaroslav");

.

<jarda> takze dobry vecer.

V kódu si všimneme dvou zajímavostí:

  1. Do funkce makeClass předáváme sadu metadat, popisujících nově vznikající třídu. NAME se hodí zejména pro snazší ladění, VERSION třeba k definování závislostí. Další metadata budou k vidění brzy.
  2. Metoda $constructor je automaticky vykonána při instancializaci (operátorem new).

S jednou třídou bychom toho moc neudělali, rozšíříme proto naši ukázku.

var IProgramator = JAK.ClassMaker.makeInterface({
    NAME: "IProgramator",
    VERSION: "1.0"
});

IProgramator.prototype.umimJazyk = function(jazyk) {
    this._jazyky.push(jazyk);
}

var Programator = JAK.ClassMaker.makeClass({
    NAME: "Programator",
    VERSION: "1.0",
    EXTEND: Clovek,
    IMPLEMENT: [IProgramator]
});

Programator.prototype.$constructor = function(jmeno, jazyk) {
    this.$super(jmeno);
    this._jazyky = [];
    if (jazyk) { this.umimJazyk(jazyk); }
}

var lojza = new Programator("Alois", "js");
var emil = new Programator("Emil", "python");

lojza instanceof Programator;      // true
lojza instanceof Clovek;           // true
lojza.constructor === Programator; // true
lojza._jmeno === 'Alois';          // true
lojza.pozdrav === jarda.pozdrav;   // true
lojza.$super === jarda.$super;     // true

Tu je již ClassMaker v celé parádě. Nejprve s ním vytváříme rozhraní (metoda makeInterface) – ve smyslu mix-in, tedy skupina metod, sloužících pro zdědění. Dále vidíme metadata EXTEND (dědičnost) a IMPLEMENT – soupis rozhraní, jejichž metody třída dostane. Vposled je vidět, že ClassMaker všem třídám přidává do prototypu metodu $super; ta vykoná metodu předka se stejným názvem, jako ta, ve které se nacházíme. Volání metody předka může být klidně rekurzivní.

ClassMaker má dále ještě metodu makeSingleton  – u výsledné třídy pak nelze vyrobit instanci, ale lze ji získat voláním tradiční statické metody getInstance.

<jarda> nazdar kluci
<lojza> ahoj
<emil> teda takhle trapny pojmenovani uz jsem dlouho nevidel
<emil> treba "$constructor" je desne dlouhy a zdaleka ne tak sexy jako treba "MooTools"
<lojza> na druhou stranu je to pekne popisny nazev
<lojza> a dolar na zacatku jasne rika, ze jde o neco specialniho
<emil> a ta zdlouhava deklarace "tridy" - jako by nevedeli, ze v JS tridy nejsou
<jarda> nu, je tu nejaka pausalni rezie na zacatku
<jarda> ale pomer rezie a uzitneho kodu se stejne pri
        pridavani dalsich metod limitne blizi nule. Bazinga!

Události

JAK rozlišuje mezi normálními DOM událostmi (k jejich ovládání je určen objekt JAK.Events) a uživatelskými událostmi (o ty se stará JAK.Signals). Správa DOM událostí zahrnuje navěšování, odvěšování a interakci s právě zpracovávanou událostí.

Clovek.prototype.poslouchej = function() {
    var tlacitko = JAK.gel("tlacitko"); // document.getElementById
    this._idUdalosti = JAK.Events.addListener(tlacitko, "click", this, "kliknuto"); // treti parametr je objekt, ctvrty jeho metoda
}

Clovek.prototype.kliknuto = function(e, elm) {
    JAK.Events.stopEvent(e);
    alert("Posluchac na prvku " + elm.id);
    JAK.Events.removeListener(this._idUdalosti);
}

Jak vidno, uživatel je přímo vybízen k používání instancí a jejich metod (namísto běžných funkcí). Obdobně je tomu i při používání vlastních událostí (neboli, v JAK hantýrce, signálů). Signály korespondují s návrhovým vzorem Observer a jsou tak vhodnou formou pro realizaci volnější vazby mezi různými objekty.

<emil> probuh, proc je ten callback specifikovan jako dve oddelene hodnoty?
<lojza>, aby byl nebohy uzivatel odstinen od nutnosti pouzivat bind
        a jine zpusoby, jak zachovat volani ve spravnem kontextu
<jarda> mimochodem, metodu (4. parametr) je mozne zadat take referenci, pokud retezec nevyhovuje
<lojza> - a hele, to je dobre jak cyp - ten prvek, na kterem byla udalost poslouchana,
        je predan do callbacku jako druhy parametr!
Programator.prototype.pracuj = function(e) {};

var komunikacniKanal = new JAK.Signals();
komunikacniKanal.addListener(lojza, "novaPrace", "pracuj");
komunikacniKanal.addListener(emil, "novaPrace", "pracuj");

komunikacniKanal.makeEvent("novaPrace", jarda);

Metodě addListener předáváme naslouchající objekt, dále název události a vposled název metody, která se při vyvolání události vykoná.

<jarda> dobre, chlapci, az reknu, tak zacnete makat
<lojza> neni mi ale jasne, k cemu je druhy parametr metody makeEvent
<jarda> ten rika, kdo je "odesilatelem" te udalosti
        vy ted sice poslouchate na udalost "novaPrace" bez ohledu na to, kdo ji vysila,
        ale mohli byste chtit zadavat praci jen od specifickeho odesilatele.
<emil>, jenom mi to cele porad prijde dost ukecane

Abychom vyšli Emilovi vstříc, rozšíříme ještě ukázku tak, že využijeme předpřipravené rozhraní JAK.ISignals. To poskytuje třídě zjednodušené varianty obou relevantních metod.

var SignalizacniProgramator = JAK.ClassMaker.makeClass({
    NAME: "SignalizacniProgramator",
    VERSION: "1.0",
    EXTEND: Programator,
    IMPLEMENT: [JAK.ISignals]
});

SignalizacniProgramator.prototype.$constructor = function(jmeno, jazyk) {
    this.$super(jmeno, jazyk);
    this._idUdalosti = this.addListener("novaPrace", "pracuj"); // az prijde nova prace, vykonej this.pracuj();
}

SignalizacniProgramator.prototype.pracuj = function() {
    /* ... */
    this.removeListener(this._idUdalosti);
}

var sp1 = new SignalizacniProgramator("Tydlitak");
var sp2 = new SignalizacniProgramator("Tydlitek");

var ZadavatelPrace = JAK.ClassMaker.makeClass({
    NAME: "ZadavatelPrace",
    VERSION: "1.0",
    IMPLENT: [JAK.ISignals]
});

ZadavatelPrace.prototype.$constructor = function() {
    this.makeEvent("novaPrace");
}

new ZadavatelPrace(); // oba pracuji

Přidávání i odebírání posluchačů je syntakticky shodné s používáním DOM událostí.

XML HTTP Request

Práce s HTTP požadavky je k dispozici formou třídy JAK.Request.

Programator.prototype.pracuj = function() {
    var request = new JAK.Request(JAK.Request.XML, {method:"post"});
    request.setCallback(this, "odpovedOdServeru");

    var data = {id:123, foo:"bar"};
    request.send("/", data);
};

Programator.prototype.odpovedOdServeru = function(data, status) {
    alert("HTTP/"+status);
};

První parametr určuje typ požadavku – na výběr je TEXT, XML, JSONP (JSON data s callbackem) a BINARY (pro přenos binárních dat). Dále lze ovlivnit použitou HTTP metodu, hlavičky požadavku, synchronnost a timeout. Právě probíhající požadavek lze také přerušit metodou abort.

<emil> a to je jako vsechno? (nekomprimovany) soubor jak.js ma 120 kB ...
<jarda> ani nahodou!
        jenze na probrani veskere funkcionality by bylo potreba mnohem vice casu i mista
<lojza> navic po pouziti nastroje kjscompress se velikost scvrkne na par kilobajtu.

Další knihovny a widgety

<emil> to bych rad videl, s cim se vytahnou. Vsadim boty, ze jQuery to vsechno davno umi.
<lojza> jenomze widgety pro jQuery pise kdejaky jouda, zatimco tady je vse soucasti jenoho projektu
<jarda>, coz automaticky znamena kompletni dokumentaci, podporu, zajistovani zpetne kompatibility...

Popisovat všechny další moduly a widgety by bylo plýtvání místem; čtenáře proto odkážeme na oficiální stránku, potažmo přímo do repozitáře. Zmiňme ale tu nejdůležitější a nejzajímavější volitelnou funkcionalitu:

  • Widget LightBox pro pokročilé zobrazování fotogalerie
  • Widget WYSIWYG Editor
  • Knihovna Graphics, poskytující zastřešení vektorvých implementací SVG a VML
  • Widgety PieChart a LBChart – tvorba koláčových, čárových a sloupcových grafů
  • Knihovna EXIF pro čtení fotografických metadat ze souborů JPG
  • Knihovna Compress pro client-side kompresi
  • Modul Timekeeper, umožňující plynulé vykonávání mnoha animací naráz

Součástí JAKu je také rozšíření některých prototypů nativních objektů. Téměř vždy se tak ale děje v souladu s oficiálním standardizačním postupem ES5 – nejlépe je to vidět na nových metodách pro pole a také na Function.proto­type.bind.

Testy a dokumentace

Pro většinu základní funkcionality (jak.js) jsou k dispozici unit testy, vyrobené pro systém JsUnit. Netýkají se proto obtížně testovatelných partií JAKu, tedy widgetů.

Základní dokumentace je obsažena přímo v kódu, podporovaný systém dokumentačních komentářů je JsDoc Toolkit. Ze zdrojových kódů tak lze vyrobit kompletní dokumentaci – je k vidění též na oficiálním webu. Komentáře jsou psané česky, popisují funkci jednotlivých komponent, parametry a návratové hodnoty funkcí.

<emil> mnom. stejne porad nevidim duvod, proc tenhle nastroj zacit pouzivat.
<jarda> toz, nikdo te k tomu nenuti, ze ano
<lojza> nekolik duvodu me napada: lety provereny kod, stale probihajici vyvoj,
        pouziti na nejctenejsich webech v .cz, ...

Cílem tohoto článku skutečně nebylo přesvědčit stávájící uživatele jiných řešení o tom, že JAK je spásná volba. Náhodný kolemjdoucí i ostřílený programátor však možná ocení některá specifika knihovny – a když ne, alespoň si rozšíří obzory.

Autor pracuje ve společnosti Seznam na všem, co alespoň trochu souvisí s JavaScriptem. Ve volném čase se mimo jiné zabývá věcmi, které alespoň trochu souvisí s JavaScriptem. O obojím občas tweetuje jako @0ndras.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 17

Přehled komentářů

Ped komentare cesky...
avatar Glow
karel Re: Glow
czechspekk Re: Glow
František Kučera Re: Glow
libcha k článku
rebus Re: k článku
zyx Interface
ffrr Re: Interface
maX Re: Interface
Z Re: Interface
Ondřej Žára Re: Interface
Z Re: Interface
Lopata Interface spíše mixin/trait
Radek Re: JAK: JavaScriptová knihovna z dílny Seznamu
Ondřej Žára Re: JAK: JavaScriptová knihovna z dílny Seznamu
aichi Re: JAK: JavaScriptová knihovna z dílny Seznamu
Zdroj: https://www.zdrojak.cz/?p=3207