Pokročilé příklady

Na této stránce jsou zobrazeny pokročilé příklady použití JAvascriptové Knihovny.

V jednoduchých příkladech jsme si ukázali, že každá vytvářená třída pomocí ClassMakeru obsahuje povinné vlastnosti NAME, VERSION a CLASS. ClassMaker umožňuje navíc vytváření dedičností a implementaci rozhraní.

Jelikož dedičnost ani rozhraní nejsou standardní vlastnosti javascriptu, ale jsou doprogramované, chovají se mírně odlišně než u většiny standardních objektových jazyků.

Další volitelné vlastnosti objektu předávaném konstruktoru ClassMakeru jsou:

Ukázka dědičnosti pomocí ClassMakeru

EXTEND očekává odkaz na již vytvořenou třídu, ze které dědí vlastnosti a metody. Toto dědění ale nefunguje na stejném principu jako v klasických programovacích jazycích, kdy defakto třídní předpisy se děděním zřetězí a z každého dítětě se dá odkazovat na rodiče (pomocí klíčového slova: parent či super). V našem případě jsou překopírovány veškeré vlastnosti a metody nadřazené třídy do třídy vytvářené i s jejich funkčností, pokud nejsou předefinovány v odvozené třídě. Volání metody předka v přepsané metodě teoreticky není možné, neboť všechny metody jsou překopírovány do kontextu this. Nicméně ClassMaker v každé vytvořené třídě vytváří metodu $super, která tuto funkčnost umožňuje.

Následující příklad vytváří obecnou třídu Vehicle, z které dědí třídy Bus a Car, z třídy Bus navíc dědí třída SchoolBus.

/**
 * ukázka dědičnosti
 */
 
//vytvoření třídy Vehicle přes ClassMaker
 Vehicle = SZN.ClassMaker.makeClass({
 	NAME: "Vehicle",
	VERSION: "1.0"
 });

 //konstruktor Vehicle
 Vehicle.prototype.$constructor = function(man, id) {
 	//definice vlastností
 	this.manufacturer = man; //výrobce
 	this.vehicleId = id; //SPZ
 }

 //vrati SPZ dopravního prostředku
 Vehicle.prototype.getVehicleId = function() {
 	return this.vehicleId;
 }
 //metoda zobrazi vlastnosti objektu
 Vehicle.prototype.showDetail = function() {
 	str = 'Třída: ' + this.sConstructor.NAME + '\n';
	str += 'Výrobce: ' + this.manufacturer + '\n';
	str += 'SPZ: ' + this.vehicleId + '\n';
	return str;
 }


  //vytvoření třídy Car přes ClassMaker
 Car = SZN.ClassMaker.makeClass({
 	NAME: "Car",
	VERSION: "1.0",
	EXTEND: Vehicle
 });
 //konstruktor Car
 Car.prototype.$constructor = function(man, id, doors, maxSpeed) {
 	this.doors = doors;
 	this.maxSpeed = maxSpeed;
 	this.$super(man, id);   //volani konstruktoru predka
 }
 Car.prototype.showDetail = function() {
 	str = 'Třída: ' + this.sConstructor.NAME + '\n';
	str += 'Výrobce: ' + this.manufacturer + '\n';
	str += 'SPZ: ' + this.vehicleId + '\n';
 	str += 'Maximální rychlost: ' + this.maxSpeed + '\n';
	str += 'Počet dveří: ' + this.doors + '\n';
	return str;
 }


 //vytvoření třídy Bus přes ClassMaker
 Bus = SZN.ClassMaker.makeClass({
 	NAME: "Bus",
	VERSION: "1.0",
	EXTEND: Vehicle
 });
 //konstruktor Bus
 Bus.prototype.$constructor = function(man, id, seats, wheels) {
 	this.seats = seats;
 	this.wheels = wheels;
 	this.$super(man, id);  //volani konstruktoru predka
 }
 Bus.prototype.showDetail = function() {
 	str = 'Třída: ' + this.sConstructor.NAME + '\n';
	str += 'Výrobce: ' + this.manufacturer + '\n';
	str += 'SPZ: ' + this.vehicleId + '\n';
 	str += 'Počet sedadel: ' + this.seats + '\n';
	str += 'Počet Kol: ' + this.wheels + '\n';
	return str;
 }


 //vytvoření třídy SchoolBus přes ClassMaker
 SchoolBus = SZN.ClassMaker.makeClass({
 	NAME: "SchoolBus",
	VERSION: "1.0",
	EXTEND: Bus
 });
 //konstruktor SchoolBus
 SchoolBus.prototype.SchoolBus = function(man, id, seats, wheels, school) {
 	this.school = school;
 	this.$super(man, id, seats, wheels);  //volani konstruktoru predka
 }
 SchoolBus.prototype.showDetail = function() {
 	str = 'Třída: ' + this.sConstructor.NAME + '\n';
	str += 'Výrobce: ' + this.manufacturer + '\n';
	str += 'SPZ: ' + this.vehicleId + '\n';
 	str += 'Počet sedadel: ' + this.seats + '\n';
	str += 'Počet Kol: ' + this.wheels + '\n';
 	str += 'Autobus patří škole: ' + this.school + '\n';
	return str;
 }

 car = new Car('Volvo', '1A2 35 46','5','240');
 bus = new Bus('Scania', '2U3 55 66', '40', '6');
 sbus = new SchoolBus('Ford', '2S8 33 48', '25', '4', 'SPŠ Elektrotechnická');

V příkladu jsou vytvořeny tři instance, od každé třídy jedna. Tedy jedno auto, jeden autobus a jeden školní autobus. Po kliknutí na budou vypsány jejich vlastnosti. V příkladu je definovaná metoda Vehicle::getVehicleId(), která vrací SPZ a dědí ji všechny objekty. Proto si můžeme dovolit ji volat nad každým objektem. Pak můžeme pro jeho SPZ.

V příkladu je možné si všimnout, že konstruktor se nejmenuje $constructor ale dle názvu třídy. ClassMaker při vytváření třídy zjišťuje existenci metody $constructor a tu spouští, pokud ji nenajde, tak se pokouší najít metodu s názvem stejným jako název třídy a tu spustí. Nepředefinované metody předka lze normálně volat a proto využijeme možnosti pojmenovaných konstruktorů. Tím pádem nemusíme využít volání callSuper.

Použití metody $super

Metoda $super, umožňuje přímo volat metodu předka bez předávání odkazu na volající metodu pomocí arguments.callee. K obejití tohoto omezení využívá nestandardní rozšíření arguments.caller, které implementují všechny moderní prohlížeče (Opera od verze 9.6). Tato metoda bude v knihovně JAK v budoucnu preferovaná a metoda callSuper bude je za zastaralou. V příkladu s auty, byla použita ve volání konstruktoru předka, v následujícím příkladě je použita ve volání metod předka z přepsaných metod:

 /**
  * zakladni trida svetla
  **/
 NormalLight = SZN.ClassMaker.makeClass({
 	NAME: 'NormalLight',
 	VERSION: '1.0'
 });

 NormalLight.prototype.$constructor = function(id) {
 	this.shine = false;

 	this.box = SZN.gEl(id);
 }

 NormalLight.prototype.isShining = function() {
 	this.box.innerHTML = 'NormalLight: Světlo ' + (this.shine ? 'svítí' : 'nesvítí') + '
' + this.box.innerHTML; } NormalLight.prototype.turnOn = function() { this.box.innerHTML = 'NormalLight: Zapínám světlo
' + this.box.innerHTML; this.shine = true; } NormalLight.prototype.turnOff = function() { this.box.innerHTML = 'NormalLight: Vypínám světlo
' + this.box.innerHTML; this.shine = false; } /** * konkretni trida svetla s vetrakem **/ LightWithFan = SZN.ClassMaker.makeClass({ NAME: 'LightWithFan', VERSION: '1.0', EXTEND: NormalLight }); LightWithFan.prototype.turnOn = function() { this.box.innerHTML = 'LightWithFan: Zapínám větrák
' + this.box.innerHTML; this.$super(); } LightWithFan.prototype.turnOff = function() { this.box.innerHTML = 'LightWithFan: Vypínám větrák
' + this.box.innerHTML; this.$super(); } LightWithFan.prototype.isRunning = function() { this.box.innerHTML = 'LightWithFan: Větrák ' + (this.shine ? 'běží' : 'neběží') + '
' + this.box.innerHTML; } light = new LightWithFan('lightsMessageBox');

Jsou definované dvě třídy, jedna obecná NormalLight pro světlo a jedna specializovaná pro světlo s větrákem LightWithFan, která dědí z třídy obecné a rozšiřuje její funkčnost. Vytvoříme si instanci světla s větrákem a budeme pozorovat chování přepsaných metod, které volají metody předka s pomocí $super.

V boxu níže se budou objevovat zprávy našeho světla. Světlo můžeme a a také se zeptat na stav: .

Požadované knihovny:

Implementace rozhraní

V objektových programovacích jazycích je možné předepsat jistou funkčnost pomocí navržení rozhraní a jeho implementaci v konkrétní třídě. V javascriptu nic takového neexistuje. Nicméně ClassMaker se k tomuto problému staví obdobě jako k problému dědění a to tak, že kopíruje prototypové metody z rozhraní do vytvářené třídy. Rozdíl mezi implementací rozhraní a děděním je v našem případě takový, že dědit můžeme pouze z jednoho rodiče, kdežto implementovat můžeme libovolný počet rozhraní. Druhá odlišnost je, že při implementaci se nespouští konstruktor implementované třídy.

Vyspělé jazyky umožnují zeptat se zda objekt dědí rozhraní (např. pomocí instanceof, is), toto v javascriptu není možné.

Tvorbu rozhranní má v ClassMakeru na starosti metoda makeInterface:

/**
 * ukázka dědičnosti a implementace rozhraní
 */

 //vytvoření obecného rozhraní, které má metodu pro výpis všech vlastnosti objektu
 IDetails = SZN.ClassMaker.makeInterface({
 	NAME: "IDetails",
	VERSION: "1.0"
 });

 IDetails.prototype.showDetails = function() {
	str = "";
	 for (key in this) {
 		if (typeof(this[key]) == "string") {
 			str += key +": "+ this[key] +"\n";
		 }
	 }
 	return str;
 }


 //vytvoření třídy Vehicle2 přes ClassMaker
 Vehicle2 = SZN.ClassMaker.makeClass({
 	NAME: "Vehicle",
	VERSION: "1.0",
	IMPLEMENT: [IDetails]
 });

 //konstruktor Vehicle2
 Vehicle2.prototype.$constructor = function(man, id) {
 	//definice vlastností
 	this.manufacturer = man; //výrobce
 	this.vehicleId = id; //SPZ
 }

 //vrati SPZ dopravního prostředku
 Vehicle2.prototype.getVehicleId = function() {
 	return this.vehicleId;
 }


  //vytvoření třídy Car2 přes ClassMaker
 Car2 = SZN.ClassMaker.makeClass({
 	NAME: "Car2",
	VERSION: "1.0",
	EXTEND: Vehicle2
 });
 //konstruktor Car
 Car2.prototype.$constructor = function(man, id, doors, maxSpeed) {
 	this.doors = doors;
 	this.maxSpeed = maxSpeed;
 	this.$super(man, id);
 }


 //vytvoření třídy Bus2 přes ClassMaker
 Bus2 = SZN.ClassMaker.makeClass({
 	NAME: "Bus2",
	VERSION: "1.0",
	EXTEND: Vehicle2
 });
 //konstruktor Bus2
 Bus2.prototype.$constructor = function(man, id, seats, wheels) {
 	this.seats = seats;
 	this.wheels = wheels;
 	this.$super(man, id);
 }


 //vytvoření třídy SchoolBus2 přes ClassMaker
 SchoolBus2 = SZN.ClassMaker.makeClass({
 	NAME: "SchoolBus2",
	VERSION: "1.0",
	EXTEND: Bus2
 });
 //konstruktor SchoolBus
 SchoolBus2.prototype.$constructor = function(man, id, seats, wheels, school) {
 	this.school = school;
 	this.$super(man, id, seats, wheels);
 }


 car2 = new Car2('Volvo', '1A2 35 46','5','240');
 bus2 = new Bus2('Scania', '2U3 55 66', '40', '6');
 sbus2 = new SchoolBus2('Ford', '2S8 33 48', '25', '4', 'SPŠ Elektrotechnická');

V tomto příkladě je výpis vlastností objektů vytvořen pomocí metody rozhraní, která je zděděna do všech vytvořených objektů. Po kliknutí na budou vypsány jejich vlastnosti.

Požadované knihovny:

Vzor Jedináček (Singleton)

Vzor Jedináček slouží k tomu, abychom zajistili pouze jednu instanci z dané třídy. Toto chování lze vynutit privátním konstruktorem a privátní statickou vlastností, ve které uchováváme konkrétní jednu instanci. Pro přístup k této instanci se většinou používá metoda getInstance, nebo factory.

Pokud v Javascriptu využíváme prototypovou dědičnost (JAK využívá), pak nelze definovat privátní vlastnosti a metody, tudíž nelze udělat privátní konstruktor. Nicméně pomocí ClassMakeru a jeho metody makeSingleton jsme schopni vytvořit takový konstruktor, který jde spustit pouze jednou a tudíž nejde vytvořit více než jedna instance.

Pro správné použití vytváří ClassMaker i metodu getInstance, která je jediná oprávněná vytvářet a vracet konkrétní instanci.

Vzor Singleton můžeme chtít použít např. pokud chceme vytvořit logger, který loguje události v kódu:

 /**
  * trida loggeru
  */ 
 Logger = SZN.ClassMaker.makeSingleton({
 	NAME: 'Logger',
 	VERSION: '1.0'
 });
 
 /**
  * konstruktor se zasobnikem zprav
  */   
 Logger.prototype.$constructor = function() {
 	this.messages = [];
 }
 
 /**
  * metoda pro pridani zpravy
  */    
 Logger.prototype.log = function(message) {
	this.messages.push(message); 	
 }
 
 /**
  * metoda pro vypis zprav ze zasobniku do predaneho elementu
  */   
 Logger.prototype.show = function(elm) {
 	elm.innerHTML = this.messages.join('
\n'); } /** * metoda cisti zasobnik zprav */ Logger.prototype.clear = function() { this.messages = []; }

Obslužný kód tlačítek využívající instance jedináčka, získané voláním metody getInstance:

 //metoda pro zalogovani promtu uzivatele
 function logg() {
 	//vytvoreni instance loggeru
 	Logger.getInstance().log(prompt('Zalogovat:'));
 }
 
 
  //logovaci funkce volana automaticky, kazde 2 vteriny
 function logTime() {
 	var logger = Logger.getInstance();
 	var date = new Date();
 	logger.log(date.format('d.m.Y H:i:s'));
 }
 
 var interval = null;
 
 //zacatek automatickeho logovani
 function startLog() {
 	interval =  setInterval(logTime, 2000);
 }
 
 //konec automatickeho logovani
 function stopLog() {
 	clearInterval(interval);
 }
 
 function showLog() {
 	Logger.getInstance().show(SZN.gEl('loggerMessageBox'));
 }
 
 //naveseni udalosti na buttony
 SZN.Events.addListener(SZN.gEl('logger1'), 'click', window, 'logg');
 SZN.Events.addListener(SZN.gEl('logStart'), 'click', window, 'startLog');
 SZN.Events.addListener(SZN.gEl('logStop'), 'click', window, 'stopLog');
 SZN.Events.addListener(SZN.gEl('showLog'), 'click', window, 'showLog');

Nyní máme instanci Loggeru lg, do které můžeme přidávát zprávy . Dále budeme chtít periodicky zaznamenávat událost, tlačítkem logování spustíme a tímto zastavíme. Logovací funkce získává voláním getInstance tu samou instanci. Nakonec bychom rádi získali výpis všech zpráv .

Výpis z Loggeru:

Požadované knihovny:

Vzor Decorator

Vzor Decorator se využívá především tam, kde by přidávání funkčnosti vedlo k rapidnímu zvýšení počtu tříd. Představme si, že máme tlačítka která odesílají formulář. Dále některá z nich mají po kliknutí navíc upozornit uživatele na nebezpečnou akci. Pak přijde další požadavek kdy některé z nich mají udělat AJAXový dotaz a data získaná, mají přidat do odesílaného formuláře. A takto požadavky rostou, pro každý typ tlačítka děláme třídu a nakonec dojde na to, že jedno tlačítko má umět všchno a my nemáme vícenásobnou dědičnost k dispozici. Zde nastupuje koncept Dekorátorů.

V původním vzoru je dekorátor třída, která dědí z třídy, kterou bude dekorovat. Instance dekorátoru v konstruktoru získává instanci dekorovaného objektu a volání metod, které neupravuje, deleguje na dekorovaný objekt. Takto můžeme dekorátory vesele balit na sebe jako šlupky cibule. Použití dekorátoru má ale jednu negativní vlastnost a to tu, že v kódu pracujícím s instancemi původní třídy najednou dostáváme "jiné objekty". Ve většině jiných jazyků to nemusí být problém, ale v JavaScriptu to problém je. Pokud v nějaké metodě navěšujeme události a metody při vzniku události jsou volány v kontextu this, pak se nezavolají metody ostatních dekorátorů, protože nemáme jak zjistit kdo koho odekoroval (princip dekorátorů).

Tato obtíž nás trápila, proto jsme přistoupili na tzv. Monkey Patch, kdy námi vytvořené dekorátory modifikují předanou instanci přímo, místo toho aby jí obalily. To má za následek, že this je vždy objekt instance, za druhé to vede k tomu, že metody dekorátoru jsou kopírovány do instance a při nevhodně zvolených názvech privátních metod v dekorátorech si je můžou dekorátory navzájem přepsat. V metodách dekorátorů lze použít metodu $super(), pak je zavolána předešlá metoda instance, tedy ta, která by byla volána, pokud by nebyla instance dekorována daným dekorátorem.

Dekorátory v JAKu musí vždy dědit z třídy SZN.AbstractDecorator. Tímto děděním získá dekorátor metodu decorate(). V této metodě musíme zajistit vlastní překopírování vlastních upravených metod na dekorovanou instanci. Dost často chceme překopírovat všechny definované metody, proto můžeme dědit z třídy SZN.AutoDecorator a metoda decorate() udělá vše za nás.

 //trida osoby
 Person = SZN.ClassMaker.makeClass({
 	NAME:'Person',
 	VERSION: '1.0'
 });
 
 //konstruktor
 Person.prototype.$constructor = function(name) {
 	this.name = name;
 }
 
 Person.prototype.getName = function() {
 	return this.name;
 }
 
 //metoda kterou ziskam popis aiktiivty
 Person.prototype.getActivity = function() {
    return 'Jako každý '+this.getName()+' se akorád umím válet na gauči'; 
 }
 
 //trida dekoratoru, dedi z autoDecoratoru, tudiz se mi automaticky 
 //propisi metody do dekorovane instance
 Sprinter = SZN.ClassMaker.makeSingleton({
 	NAME: 'Sprinter',
 	VERSION: '1.0',
 	EXTEND: SZN.AutoDecorator
 });
 
 //prepsani metody decorate, je zde ukazano volani metody predka (SZN.AutoDecorator.decorate)
 // a take rozsireni dekorovane instance o novou vlastnost
 Sprinter.prototype.decorate = function(instance, params) {
 	this.$super(instance, params);
 	
 	instance.__sprinterActivity = 'Jako sprinter umím běhat stovku pod 10s.';
 }
 
 //upravena metoda getActivity dekoratorem, s ukazkou volani metody dekorovane
 //instance tridy Person
 Sprinter.prototype.getActivity = function() {
 	var act = this.$super();
 	return act + '\n' + this.__sprinterActivity;
 }
 
 
 var pepa = new Person('Pepa');
 Sprinter.getInstance().decorate(pepa);
 alert(pepa.getActivity());

Po kliknutí na tlačítko vyvoláme metodu getActivity() na dekorované instanci.

Kód v příkladu si zaslouží bližší komentář. Třída Person je běžná třída a tak je to správně. Dekorátor Sprinter jak je vidět dědí ze SZN.AutoDecorator, tudíž očekáváme automatické klonování prototypových metod na dekorovanou instanci. Za povšimnutí stojí, že dekorátor je vlastně Jedináčkem. Není důvod aby nebyl, protože z jeho instance je vše naklonováno do dekorované instance.

Dalším zajímavým momentem je metoda decorate(), která zajišťuje vlastní dekoraci předané instance. V této metodě můžeme instanci předat např. nové atributy, tak jak je naznačeno. this a $super() se zde vztahují k instanci dekorátoru. V ostatních metodách, které budou naklonovány dekorované instanci se this a $super() váže k dekorované instanci.

Na předposledním řádku je ukázáno jak dekorátor použít. Nicméně pokud vytváříme třídu, u které dopředu víme, že by mohla být dekorovatelná, může implementovat rozhranní IDecorable. Pak dostane do vínku metodu decorate(). Tato metoda zajistí získání instance dekorátoru a vlastní odekorování, viz ukázka níže:

  //trida osoby
 Person = SZN.ClassMaker.makeClass({
 	NAME:'Person',
 	VERSION: '1.0',
 	IMPLEMENT: [IDecorable]
 });
 
 ...
 ...
 
 var pepa = new Person('Pepa');
 pepa.decorate(Sprinter);
 alert(pepa.getActivity());

Metodě decorate() dekorátoru je možné kromě instance předat druhý parametr, kterým do dekorátoru předáme další parametry potřebné pro dekorování.

Požadované knihovny:

Vlastní události - implementace vzoru Observer

Pokud píšeme větší knihovnu, kde hrozí narůstání závislostí objektů, je vhodné využít návrhové vzory Observer nebo Mediator. Principem vzoru Observer je, že definujeme jeden objekt (třída Observer v odkazovaném text), který upozorňuje na vzniklé události všechny registrované posluchače.

V JAKu je Observerem třída Signals, která vystavuje metody:

 /**
  * objekt auta, ktere budeme animovat
  */
 BoxedCar = SZN.ClassMaker.makeClass({
 	NAME: "BoxedCar",
 	VERSION: "1.0"
 });

 //konstruktor, predavame, element ktery budeme animovat
 BoxedCar.prototype.$constructor = function(elm) {
 	this.elm = null;
 	this.leftPosition = 0;
	 this.setVisualImplementation(elm);
 	//nabindovani do this.move metodu, tak aby pro volani pres interval byla volana v kontextu objektu (this)
	this.move = SZN.bind(this, this.move);
 }

 //metoda ocekava DOM element, ktery bude animovat
 BoxedCar.prototype.setVisualImplementation = function(elm) {
 	this.elm = elm;
 	position = SZN.Dom.getFullBoxPosition(elm);
 	elm.style.position = "absolute";
 	elm.style.top = position.top+"px";
 	elm.style.left = position.left+"px";
 	//uchování pozice pro animaci, abychom nemuseli aktuální pozici stále parsovat
 	this.leftPosition = position.left;
 }
 //metoda pro animaci auta
 BoxedCar.prototype.move = function() {
 	this.leftPosition = this.leftPosition -15;
	this.elm.style.left = this.leftPosition+'px';

 	//nastal crash
	if (this.leftPosition <= 0) {
 		clearInterval(interval);
 		observer.makeEvent('crash', this, false, new Date().getTime());
	 }
 }

 /**
  * objekt, který se dokáže navěsit na SigInterface a odchytávat a vypisovat události
  */
 MessageDisplayer = SZN.ClassMaker.makeClass({
 	NAME: "MessageDisplayer",
 	VERSION: "1.0"
 });
 //konstruktor, predavame element (nejlepe P) do ktereho budeme vypisovat
 MessageDisplayer.prototype.$constructor = function(elm) {
 	this.elm = elm;
 	this.elm.innerHTML = ""; //vyčištění obsahu
 }
 //metoda pro vypis udalosti na autu
 MessageDisplayer.prototype.displayEvent = function(event) {
 	str = '<span style="color:';
 	 if (event.type == 'crash') {
 		str += 'red';
 	} else if (event.type == 'start') {
 		str += 'orange';
 	} else if (event.type == 'stop') {
 		str += 'green';
 	} else {
 		str += 'black';
 	}
 	str += ';">';
	str += new Date(event.timeStamp).toString() + ": " + event.type;
 	str += '</span>';

 	this.elm.innerHTML += str +'<br />';
 }

 /**
  * objekt, který mění stavy tlačítek dle událostí
  */
 ButtonDisabler = SZN.ClassMaker.makeClass({
 	NAME: "ButtonBisabler",
 	VERSION: "1.0"
 });
 //konstruktor, ocekava reference na objekty auta a tlacitka pro vypnuti
 ButtonDisabler.prototype.$constructor = function(elmCar, elmStopper) {
 	this.elmCar = elmCar;
 	this.elmStopper = elmStopper;
 }
 //odchytavac udalsoti
 ButtonDisabler.prototype.eventHandler = function(event) {
 	if (event.type == "start") {
 		this.elmCar.disabled = true;
 		this.elmStopper.disabled = false;
	 } else if (event.type == "stop") {
 		this.elmCar.disabled = false;
 		this.elmStopper.disabled = true;
 	} else {
 		this.elmCar.disabled = true;
 		this.elmStopper.disabled = true;
 	}
 }

 /**
  * kód vykonaný při spuštění
  */
 //vytvoření instance auta
 bCar = new BoxedCar(SZN.gEl('boxedCar'));
 //vytvoření instance zobrazovače událostí
 ev = new MessageDisplayer(SZN.gEl('eventWindow'));
 //vytvoreni instance objektu disablujiciho tlacitka
 bd = new ButtonDisabler(SZN.gEl('boxedCar'), SZN.gEl('pauseCar'));
 //proměnná uchovávající interval
 var interval = null;
 //navěšení události animace
 SZN.Events.addListener(SZN.gEl('boxedCar'), 'click', window, 'animate');
 function animate(e, elm) {
 	interval = setInterval(bCar.move, 300);
 	observer.makeEvent('start', elm, 'private', new Date().getTime());
 }
 //navěšení události pro zastavení animace
 SZN.Events.addListener(SZN.gEl('pauseCar'), 'click', window, 'pauseAnimation');
 function pauseAnimation(e, elm) {
 	clearInterval(interval);
 	observer.makeEvent('stop', elm, 'private', new Date().getTime());
 }

 //nastavení Observeru
 var observer = new SZN.Signals();
 observer.addListener(ev, 'crash', 'displayEvent');
 observer.addListener(ev, 'start', 'displayEvent');
 observer.addListener(ev, 'stop', 'displayEvent');

 observer.addListener(bd, 'crash', 'eventHandler');
 observer.addListener(bd, 'start', 'eventHandler');
 observer.addListener(bd, 'stop', 'eventHandler');

Po kliknutí na tlačítko "Auto" bude spuštěna jeho animace nárazu do zdi. Náraz jde přerušit stiskem . Jakmile auto dojede k levému okraji okna, dojde k nárazu.

Navěšení posluchače nemusí být vždy na konkrétní událost. Jde omezit naslouchání pouze na konkrétní objekt. Pak je tento objekt čtvrtým parametrem metody addListener. V příkladu výše bychom tedy mohli navěšovat události konkrétně na instanci auta:

 observer.addListener(bd, 'crash', 'eventHandler', bCar);

Velice snadno také můžeme napsat posluchače, který bude odposlouchávat veškeré poslané signály v rámci dané instance signálů. Stačí za název události dosadit znak *:

 observer.addListener(window, '*', 'spy');

 function spy(evt) {
 	var b = SZN.gEl('carObserverBox');
 	b.innerHTML = evt.type+': '+(evt.target instanceof BoxedCar ? 'BoxedCar' : 'HTMLElement button='+evt.target.innerHTML)+'
' + b.innerHTML; //.target.constructor.NAME window.w = evt; }

Posluchač do okna níže vypisuje komunikaci tlačítek a auta z předchozího případu a vlastně tak supluje tři navěšení metody displayEvent:

Ve většině aplikací není potřeba ani vytvářet vlastní ínstanci signálů jako výše:

 //nastavení Observeru
 var observer = new SZN.Signals();

protože JAK vytváří jednu globální instanci ve svém jmeném prostoru automaticky. Tato instance je dostupná jako SZN.signals a pak můžeme rovnou psát:

 SZN.signals.addListener(...);

Dále signály umožňují při vyslání předat uživatelsky definovaná data. Při volání metody makeEvent je očekáván jako poslední (pátý) nepovinný parametr, který je předán všem posluchačům. Tento parametr musí být objektem.

 ...
 //vyslani signalu
 SZN.signals.makeEvent('stop', elm, 'private', new Date().getTime(), {id: 123, name: 'blabla'});
 ...

 ...
 //zpracovani signalu ve volane metode
 function zpracovatelSignalu(event) {
 	alert(event.data.id);
 }

Předaná data jsou do metody posluchače předaná v parametru události jako atribut data.

Požadované knihovny:

Využití metody bind()

Knihovna JAK vynucuje psaní kódu objektově tak, že do metod Events::addListener() nebo Signals::addListener() vyžaduje předat odkaz na objekt, ve kterém bude spuštěna předaná metoda. Aby v této metodě byl odkaz na klíčové slovo this objekt nad kterým je metoda volána a ne objekt Events či Signals, je vnitřně volána metoda SZN.bind().

 ... {
 this.animate = SZN.bind(this, this.animate);
 ... }

Voláním této metody docílíme toho, že místo metody objektu je vytvořena anonymní metoda, která volá původní metodu s kontextem předaného objektu, v tomto případě "this".

Pokud chceme využít funkci setTimeout() nebo setInterval() pro volání metod objektů, musíme vždy předtím volanou metodu přebindovat, protože argumentem funkcí setInterval a setTimeout je jen odkaz na funkci a ne objekt.

 ...
 this.interval = setInterval(this.animate, 200);
 ...
Požadované knihovny:

Využití rozhraní SigInterface pro implementaci Observeru

Pro lehčí integraci volání událostí a získávání událostí ve vlastních třídách je možné využít rozhraní SigInterface. Toto rozhraní zapouzdřuje volání metod objektu Signals (Observer). Implementace použije globální objekt SZN.signals, nebo očekává, že objekt obsahuje vlastnost se jménem signals, která obsahuje instanci třídy SZN.Signals. Třídy implementující SigInterface rozhraní získají tyto metody:

Metody jsou volány vždy v kontextu objektu, který implementuje dané rozhraní. Potom auto z předchozího příkladu může vypadat jako v ukázce níže, kde jsou zvýrazněny změny.

 /**
  * objekt auta, ktere budeme animovat
  */
 BoxedCar = SZN.ClassMaker.makeClass({
 	NAME: "BoxedCar",
 	VERSION: "1.0",
	IMPLEMENT: [SZN.SigInterface]
 });

 //konstruktor, predavame element ktery budeme animovat
 BoxedCar.prototype.$constructor = function(elm) {
 	this.elm = null;
 	this.leftPosition = 0;
 	this.setVisualImplementation(elm);
 	//nabindovani do this.move metodu, tak aby pro volani pres interval byla volana v kontextu objektu (this)
 	this.move = SZN.bind(this, this.move);
 }

 //metoda ocekava DOM element, ktery bude animovat
 BoxedCar.prototype.setVisualImplementation = function(elm) {
 	this.elm = elm;
 	position = SZN.Dom.getFullBoxPosition(elm);
 	elm.style.position = "absolute";
 	elm.style.top = position.top+"px";
 	elm.style.left = position.left+"px";
 	//uchování pozice pro animaci, abychom nemuseli aktuální pozici stále parsovat
 	this.leftPosition = position.left;
 }
 //metoda pro animaci auta
 BoxedCar.prototype.move = function() {
 	this.leftPosition = this.leftPosition -15;
	this.elm.style.left = this.leftPosition+'px';

 	//nastal crash
	if (this.leftPosition <= 0) {
 		clearInterval(interval);
 		this.makeEvent('crash', false);
	 }
 }
Požadované knihovny:

Oddělení vizuální interpratace od implementace

Dobrým zvykem při psaní rozsáhlých aplikací je důsledné oddělení implementace algoritmů od jejich vizuální interpretace. Můžeme si to představit nad problémem s kalkulačkou. Pokud si odmyslíme kalkulačky s prefixovou logikou, tak všechny umí vypočítat zápis 4+4. Tedy funkčnost je stejná, mění se pouze jejich design. Pokud se tohoto důsledného oddělení držíme i při programování, vede nás to ke znovupoužitelnosti výkonného kódu.

V následujícím skriptu je velice jednoduchý objekt kalkulačky:

	/**
	 * objekt kalkulačky
	 */
	Calculator = SZN.ClassMaker.makeClass({
		NAME: "Calculator",
	   VERSION: "1.0"
	});

	//konstruktor kalkulačky
	Calculator.prototype.$constructor = function() {
		this.input = '';
		this.output = '';
	}

	//metoda které předáme části výrazu, který budeme chtít  vypočítat
	Calculator.prototype.addInput = function(inp) {
		this.input += inp+'';
	}

	//vymaže input zásobník
	Calculator.prototype.clearInput = function() {
		this.input = '';
	}

	//vrací obsah input zásobníku
	Calculator.prototype.getInput = function() {
		return this.input;
	}

	//metoda provadejici vypocet
	Calculator.prototype.calculate = function() {
		this.output = eval(this.input);
		this.input = this.output;//dokud nedáme clearInput, tak chceme s výsledkem počítat
	}

	//získání výsledku
	Calculator.prototype.getOutput = function() {
		return this.output;
	}

Poté je možné vytvořit třídu s vizuálním rozhraním:

 /**
  * objekt vizuální reprezentace kalkulačky
  */
 VisualCalculator = SZN.ClassMaker.makeClass({
 	NAME: "VisualCalculator",
 	VERSION: "1.0"
 })

 //konstruktor, přebírá instanci nevizuálního části
 VisualCalculator.prototype.$constructor = function(calc) {
 	this.calc = calc;
 	this.outputElement = null;
 }

 //metoda je navěšená na všechny tlačítka, která přidávají obsah do fronty pro vyhodnocení
 //a dle nich volá metodu Calculator::addInput()
 VisualCalculator.prototype.recieveInput = function(e, elm) {
 	this.calc.addInput(elm.value);
 	this.outputElement.value = this.calc.getInput();
 }

 VisualCalculator.prototype.clear = function(e, elm) {
 	this.calc.clearInput();
 	this.outputElement.value = '';
 }

 VisualCalculator.prototype.execute = function(e, elm) {
 	this.calc.calculate();
 	this.outputElement.value = this.calc.getOutput();
 }

 //zobrazení kalkulačky v předaném DOM elementu
 VisualCalculator.prototype.showIn = function(elm) {
 	elm.innerHTML  = "<input type='text' id='calc_output' readonly='readonly' /><br />";
 	elm.innerHTML += "<input type='button' value='7' id='calc_7' /> <input type='button' value='8' id='calc_8' /> <input type='button' value='9' id='calc_9' /> <input type='button' value='/' id='calc_div' /><br />";
 	elm.innerHTML += "<input type='button' value='4' id='calc_4' /> <input type='button' value='5' id='calc_5' /> <input type='button' value='6' id='calc_6' /> <input type='button' value='*' id='calc_multiple' /><br />";
 	elm.innerHTML += "<input type='button' value='1' id='calc_1' /> <input type='button' value='2' id='calc_2' /> <input type='button' value='3' id='calc_3' /> <input type='button' value='-' id='calc_minus' /><br />";
 	elm.innerHTML += "<input type='button' value='0' id='calc_0' /> <input type='button' value='c' id='calc_clear' /> <input type='button' value='=' id='calc_execute' /> <input type='button' value='+' id='calc_plus' />";

 	this.outputElement = SZN.gEl('calc_output');

 	SZN.Events.addListener(SZN.gEl('calc_0'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_1'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_2'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_3'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_4'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_5'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_6'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_7'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_8'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_9'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_plus'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_minus'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_div'), 'click', this, 'recieveInput');
 	SZN.Events.addListener(SZN.gEl('calc_multiple'), 'click', this, 'recieveInput');

 	SZN.Events.addListener(SZN.gEl('calc_clear'), 'click', this, 'clear');
 	SZN.Events.addListener(SZN.gEl('calc_execute'), 'click', this, 'execute');
 }

 calc = new Calculator();
 vc = new VisualCalculator(calc);

Pokud kód spustíme a vizuální část, můžeme používat jednoduchou kalkulačku.

Požadované knihovny:

Složitější ukázka více tříd s využitím komponent

V následujícím příkladu je ukázka zobrazení auta. V našem příkladě se auto sestává ze světel a motoru, kde obojí je připojeno jako komponenta.

V objektu, který skládá komponenty, je v konstruktoru třeba definovat pole komponent a jejich následnou registraci:

 MyCar = SZN.ClassMaker.makeClass({
 	NAME: 'MyCar',
 	VERSION: '1.0',
 	IMPLEMENT: [SZN.Components, SZN.SigInterface]
 });

 MyCar.prototype.$constructor = function(name,setting){
 	//registrace komponent
 	this.components = [ {name:'motor', part:Engine},
 			{name:'svetla', part: Lights}];
 	this.addAllComponents();

 	//registrace signalů do kořenové komponenty
 	 this.signals = new SZN.Signals(this, 'signals');
 	 ...
 }

Pole komponent obsahuje literálové objekty s vlastnostmi name, part a setting. Jediná povinná vlastnost je part, která obsahuje odkaz na třídu, ze které bude komponenta vytvořena. Setting je objekt, který se předá konstruktoru komponenty, pokud je definován. Konstruktoru každé komponenty se předává owner, name a volitelně setting, owner je odkaz na vlastníka v hiearchii komponent a name je název pod kterým bude ve vlastníkovi vytvořena vlastnost, ve které bude instance dané komponenty.

Kořenová třída stromu komponent musí definovat proměnnou TOP_LEVEL = true. Poté je možné v každé komponentě libovolně zanořené ve stromu získat odkaz na kořenovou komponentu voláním this.getMain();.

Jelikož si v našem příkladu předáváme zprávy mezi komponentami přes rozhraní SigInterface, je pak možné v komponentách získat objekt this.signals z kořenové komponenty:

 Lights = SZN.ClassMaker.makeClass({
 	NAME: 'Lights',
 	VERSION: '1.0',
 	IMPLEMENT: [SZN.Components, SZN.SigInterface]
 });

 Lights.prototype.$constructor = function(owner,name){
 	this._name = name;
 	this._owner = owner;
 	this.main = this.getMain();  //získání rodiče v hiearchii komponent, pokud je třeba
 	this.signals = this.setInterface('signals'); //získání objektu Signals z kořenové komponenty

 	...
 }

V příkladu si můžete zobrazit auto, které můžete pojmenovat a určit mu barvu. Po vykreslení ho lze zapnout, nastartovat, rozjet, zastavit a vypnout.

Vytvořit auto jménem a s barvou

Dalším usnadněním v používání složité hiearchie komponent je registrace metod komponent do rodičovského objektu. Tedy místo volání např. car.engine.fuelPump.switch('off'); lze metodu switch objektu fuelPump propagovat jednak do objektu engine, nebo až car. Toho lze docílit tak, že po definování metody komponenty, této metodě přiřadíme statickou vlastnost access, která obsahuje řetězec s obsahem "public":

 Lights.prototype.switch = function(state){
 	if (state == 'on') {
 		//zapni světla
 	} else {
 		//vypni světla
 	}
 };
 Lights.prototype.switch.access = 'public';

Pak v konstruktoru komponenty Lights je nutné volat registredMethod() pro registraci těchto metod:

 Lights = SZN.ClassMaker.makeClass({
 	NAME: 'Lights',
 	VERSION: '1.0',
 	IMPLEMENT: [SZN.Components, SZN.SigInterface]
 });

 Lights.prototype.$constructor = function(owner,name){
 	this._name = name;
 	this._owner = owner;
 	this.main = this.getMain();  //získání rodiče v hiearchii komponent, pokud je třeba
 	this.signals = this.setInterface('signals'); //získání objektu Signals z kořenové komponenty

 	this.registredMethod(this.main);
 }

Argumentem je odkaz na objekt, do kterého budeme metody propagovat, v aktuálním případě bude propagována do kořenové komponenty. V kořenové komponentě bude registrována metoda s názvem <třída><metoda>, tedy v našem případě lightsSwitch. Pokud potřebujeme, aby se metoda jmenovala úplně jinak (např. vytváříme strukturu komponent, která je ještě v betaberzi a bude se často měnit, ale chceme mít již stabilní rozhraní), pak ji můžeme přejmenovat za klíčovým slovem public při definici statické vlastnosti metody, tedy:

 Lights.prototype.switch = function(state){
 	if (state == 'on') {
 		//zapni světla
 	} else {
 		//vypni světla
 	}
 };
 Lights.prototype.switch.access = 'public zapniSvetla';

Třídy k příkladu auta jsou ke stažení v car.zip.

Požadované knihovny:

Spuštění kódu, když je manipulace s DOMem bezpečná

Spustit kód, když je již DOM načten a inicializován, je možné několika způsoby. Z počátku bylo běžné tento kód navěsit na událost window.onload, ale nyní, když stránky obsahují mnoho velkých obrázků, je tato událost spuštěna až s velkým spožděním a uživateli se nemusí dostat po dlouhou dobu požadované funkčnosti.

Naopak v Seznamu používáme druhou možnost a to, že inicializaci manipulujícího skriptu vkládáme do stránky ihned za DOM elementy se kterými pracuje. To má za následek, že funkčnost je k dispozici skoro ihned, když je prvek zobrazen.

Třetí střední cestou je navěšení spuštění kódu až na DOMReady událost, která je dostupná ve většině JS knihoven. Spouštěč na této události byl do JAKu dopsán pro vás. Používá se jednoduše zavoláním:

 SZN.Events.onDomReady(obj, 'func');

Pak je metoda func objektu obj zavolána, až když je DOM kompletně inicializován.

Požadované knihovny:

Přidané prototypové metody

Knihovna JAK rozšiřuje prototyp Array o metody definované v JavaScriptu 1.6, resp. jen tehdy, pokud tyto metody již nejsou definovány. Proto je nutné ve všech vašich skriptech používat pro procházení polí konstrukcí for (var i = 0; i < arr.length; i++){} a ne for (key in arr){}, která se používá pro procházení objektů. Jinak v prohlížečích, kde tyto metody nejsou definovány prohlížečem ale pouze naším kódem, by tyto metody byly v tomto cyklu vypsány.

Dále jsou přidány čtyři nové prototypové vlastnosti. Tři do objektu String a jedna do Date. Do objektu String jsou přidány metody lpad a rpad pro doplnění řětězce na požadovanou velikost pomocí zadaného řetězce znaků a trim pro oříznutí bílých znaků zleva i zprava.

 "2".lpad();			//vrátí: 02, výchozí doplňovaný znak je 0 a počet znaků v řetězci 2
 "7".lpad(0,3);			//vrátí: 007
 "Ahoj".lpad('-=', 9);	//vrátí: -=-=-Ahoj

 "1".rpad();			//vrátí: 10
 "Ahoj".rpad("!", 7);	//vrátí: Ahoj!!!
 "xyz".rpad('_abc',10);	//vrátí: xyz_abc_ab

 "  test ".trim();		//vrátí: test bez mezer

Metody lpad a rpad očekávájí dva parametry. První je řetězec s výplní a druhým parametrem je počet znaků výsledného řetězce. Pokud nejsou zadány, jsou výchozí hodnoty 0 a 2, tedy výsledný řetězec by měl mít dva znaky a popřípadě bude doplňen nulou. Tyto hodnoty jsou výhodné pro třetí novou prototypovou vlastnost pro formátovaní data.

Objekt Date obsahuje novou prototypovou metodu format, která by měla zjednodušit výpis data v uživatelském formátu. Parametrem metody je formátovací řetězec. Tento ředězec má shodné formátovací vlastnosti jako metoda date v PHP.

 var d = new Date();
 d.setTime(1234567890 * 1000);	//valentýn roku 2009
 d.format('Y-m-d');				//vrátí: 2009-02-14
 d.format('d.M.Y H:i:s');		//vrátí: 14.Feb.2009 00:31:30

 //chceme české názvy
 d._monthNames = ["Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"];
 d.format('d.F Y');				//vrátí: 14.Únor 2009

Objekt data dostává také do vínku 4 prototypové vlastnosti pro formátování data s názvy měsíců a dnů:

Pokud budeme v aplikaci chtít formátovat data pro různé jazyky je vhodné předdefinovat tyto proměnné lokálně, viz ukázka výše, jinak je vhodné předdefinovat přímo prototypové vlastnosti.