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

Zdroják » JavaScript » JAK: JavaScriptová knihovna z dílny Seznamu

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.

Komentáře

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

Predpokladam ze ciste anglicka verze neni, co? :( Nic pro mne, ale hezky pocin, preji hodne stesti.

avatar

Mnoho väčších firiem siahne k vytvoreniu vlastnej JS knižnice. BBC si jeden takisto vytvorila: http://www.bbc.co.uk/glow/ Mne osobne sadol práve tento, ale to je samozrejme čiste subjektívny pocit.

karel

Díky za tip, Glow vypadá skutečně dobře.

czechspekk

Hlavnim duvodem proc BBC zacalo vyvijet Glow byly starsi verze IE, ktere jiz nebyly podporovany Jquery.

Jelikoz BBC ma pomerne striktni guidelines pro dostupnost obsahu a jednotnost formy, vznik vlastniho frameworku podporujici archaicke verze IE byl volbou mezi odriznutim starsich browseru v podobe upravy guidelines (velkou casti bbc-dev podporovana :) a nebo castecnym reinventing the wheel v podobe Glow)

Reinventing the wheel dostalo prednost a Glow to behem lonskeho dotahl do public sceny.

viz: http://www.bbc.co.uk/glow/docs/articles/what_is_glow.shtml

latest news / dev talks / community support – glow-users@googlegrou­ps.com

František Kučera

A vylepšit jQuery je nenapadlo? (i kdyby měli udržovat vlastní větev, do doby, než ty archaické verze MSIE odumřou).

libcha

Strašně se mi líbí ty dialogy v článku, hezky to oživuje čtení. Díky autore :)

rebus

+1 clanek je super – diky autorovi.

zyx

Jeden detail na úvod – jarda se jmenuje Jaroslav, takže ten první výstup je špatně ;)

Ad interface – AFAIK je interface deklarace, která nemá žádné funkce, tudíž nerozumím tomu, proč se mu připojuje něco do prototype.

A k názvosloví – vždycky, když vidím maďarštinu ala <b>I</b>Signals či <b>I</b>Progra­mator, tak zdrhám hodně daleko. Že jde o interface, má být vidět z dokumentace, nikoli tím zatěžovat vývojáře při používání. Doporučuju pro inspiraci prostudovat čistou a easy-to-use Javu.

Definice třídy s prominutím vypadá příšerně, že se to stejně zkompresí, je sice hezké, ale ono jde taky o přehlednost zdroje. Opět, inspirace definicí třídy v Javě neuškodí (jak bylo zmíněno v odkazovaném článku i diskusích).

Jsem zvědav na pokračování, zatím mě to příliš nepřesvědčilo…

ffrr

osobne mi to tiez pride matuce, kedze Interface != Mixin. Nasilu tlacit vsetky koncepty z klasickej class based inheritance do prototypal inheritance je imho popieranie konceptu jazyka, ale pokial to niekomu ulahci zivot… ;)

maX

Tiez tak trosku pracujem na JS frameworku … namiesto interface (resp. mix-in) pouzivam implement … pride mi to menej proti srsti.

Z

Presne tak, usili ktere nekteri lide vyviji ktomu aby popreli prototypovost javascriptu me nikdy neprestane udivovat.

A nepochopil jsem tu poznamku o duck typingu. js sam o sobe ducktyping velmi dobre umoznuje, jak to ale souvisi s ‚emulaci‘ trid a interfacu v JAKu mi uniklo. Chtel tim snad autor naznacit ze i kdyz projdu vsim tim zdlouhavym definovanim trid tak JAK nakonec stejne neprovede typovou kontrolu?? :)

A propos, pomer rezie a uzitneho kodu se sice limitne blizi nule, ale ta ukecanost zvysuje fixni naklad s kterym je treba pocitat. Tento je navic tim vetsi cim je vetsi projekt a slozitejsi navrh. Ergo v malem projektu si limitni povahu funkce ‚neuziju‘ a ve velkem me to zase stoji vic nez kdybych pri navrhu pocital s pravou povahou jazyka. Resil by to nejaky generator kodu z navrhu trid ;) Tahle argumentace ma tedy smys jen u dlouho udrzovanych a upravovanych mensich projektu. Coz se u js – uznavam – stava :)

Kazdopadne je tam i par zajimavych myslenek, rozhodne dobre vedet ze tu neco takoveho je.

Z

Ano, v tomhle je pristup JAKu skutecne sympaticky, pracuje s tridami a s rozhranimi ale pritom jejich implementaci stavi na prototypovosti. To ale nic nemeni na faktu ze mi to prijde jako maskarada. Misto toho abych se s prototypovosti jazyka smiril, premyslel v ni a prizpusobil tomu navrh, budu navrhovat ‚tak jak jsem zvykly‘, ale za cenu dalsi mezivrstvy. Me to zkratka nepripada rozumne, ale to je asi vec nazoru a predevsim potreby. Uznavam, ze jsou projekty ( a navrhy ), kde se to hodi.

Ohledne duck typingu – uz rozumim, dekuji za vysvetleni. Je to asi podobne jako kdyz v js pouzivam k definovani objektu ( a vytvoreni prototypu ) funkci a rikam ji ‚trida‘ nebo ‚konstruktor‘ ( v pripade nove instance ) ale ve skutecnosti je to porad jenom funkce. (nebo se pletu?)

Lopata

Ano, tak, jak to prezentuje JAK, to opravdu vypadá na že interface má implementaci, takže by bylo lepší zvolit název mixin, trait apod.

Radek

Jak je to s tím zastaráváním? Na oficiálním webu nic o změně ze SZN na JAK není. Přijde mi, že jde tedy o změnu v nevydané vývojové verzi…

Michal Aichinger

Nové verze se konečně dočkal i web http://jak.seznam.cz, tedy i zde se používá jmenný prostor JAK a vše co je zde ke stažení je již přepsáno do nové verze.

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.