Widgetek írása javascriptben

Nos, itt egy újabb (saját gyártású) “sorozat”, melyet folytatok. (Ezek a cikksorozatok és egyebek szintén elérhetőek a DOKUMENTÁCIÓK & LINKEK almenüiben is.)

Korábban azt ismertettem, hogy hogyan lehet egy (hagyományos módszerekkel nem módosítható) HTML tag stílusát megváltoztatni javascripttel.
Előtte pedig azt szemléltettem, hogy hogy lehet példaképpen egy dialógus ablakot létrehozni.

Mindkét dolognak meg vannak a maga korlátai.
Az első módszer – noha széles körben alkalmazható, több példányban is – hátránya, hogy a weblap stílusa utólag változik azáltal, hogy a node-okon végigfutva kiválasztjuk a módosítandó tageket. Ezt a módszert alkalmazhatjuk azon kevés előre definiált tagen, melyeket új stílussal szeretnénk előállítani, de nem praktikus, ha többször (sokszor) végignyálazva saját tageket hozunk létre… nem is beszélve arról, hogy ütközhetnek egy későbbi szabvánnyal, vagy ne adj ég egy böngésző dobhatja a nem definiált elemeket.
A második módszernek szintén vannak hiányosságai, hibái. Az első, az objektumosztály definiálásának statikus mivolta, vagy épp az, hogy az objektum nevét definiálnunk kell. Arról nem is beszélve, hogy az ablakot document.write paranccsal hoztuk létre, ami nyilvánvalóan abban az esetben alkalmazható, ha mást nem akarunk az oldalra felvésni.

Megoldásként létrehozhatunk egy objektumbarát widgetet, mely alkalmas bonyolultabb dolgok tárolására és nem bír az előzőleg felsorolt hátrányokkal.

Első körben kezdjük megint egy objektum-osztály deklarálásával, ami az előző praktikátlan dolgokat már nem tartalmazza.

Számos bonyolultnál-bonyolultabb módszer létezik, hogy javascriptben viszonylag normális objektum-orientált módon programozzunk. Ezeket nem is elemzem. Egyrészt azért nem, mert elég ilyen cikk van a neten, másrészt azért, mert túl bonyolult a használatuk.
Nos, itt egy sokkal egyszerőbb módszer, ami csupán egy pici kompromisszumot kíván.

{code type=javascript}var ObjectClass = function ObjectClass(){};

ObjectClass.uid = 0;

ObjectClass.prototype._ObjectClass = function(objectName){
this._class = [‘Object’];
this._object = objectName;
this._uid = ObjectClass.uid++;
}

ObjectClass.prototype.addClass = function(className){
this._class.push(className);
}

ObjectClass.prototype.setObject = function(objectName){
this._object = objectName;
}

{/code}

Első körben a megszokotton eltérően egy üres osztályt fogunk definiálni.
Ennek az az oka, hogy amikor egy osztályt a hagyományos módon létrehozzuk javascriptben, akkor minden benne lévő objektum az osztályon belül statikusan jön létre, azaz minden objektum ugyanazon elemekhez fog hozzáférni. Ez könnyen belátható, hisz a származtató osztály csak egyszer lesz new-val létrehozva a származtatott osztály blokkjában. Viszont csak így lehet egyszerűen örököltetni a függvényeket.
Erre léteznek olyan trükkök, hogy osztálylétrehozó függvényeket írnak. No, ez nagyon bonyolult dolgokhoz vezet, így ezt dobtam. Helyette az osztály konstruktorát (maga az osztály definíciója) üresen hagyom és létrehozok dinamikusan egy konstruktort, amely már objektumonként fog létezni. Az eredeti konstruktor csak az öröklődés miatt kell. Így egycsapásra minden megoldódik azzal a kis kényelmetlenséggel, hogy a szülő objektumok konstruktorát külön kell lefuttatni. A legutolsó gyermeknél, ami a kódban van, már persze nem kell… ott lehet a hagyományos eljárást alkalmazni, hisz már nem kell örökíteniük, azaz a konstruktoraik dinamikusan lesznek létrehozva, külön-külön new-val.

Első körben létrehozunk egy statikus “uid” váltotót.
Ha létrehozunk egy objektumot, akkor ezt a számot inkrementáljuk így minden objektum egy külön azonosítószámot fog kapni. Mivel a gépünk egy századmásodperc alatt több osztályt képes létrehozni ez a módszer praktikusabb a date függvénytől. Hátránya, hogy egy uid-kezelő algoritmust kell írni hozzá, ami azonban nem bonyolult. Az álkonstrukturunk pont ezt teszi.
A saját konstruktort az osztály nevén fogom elnevezni, mint C-ben és egy “_”-t teszek elé jelezve, hogy privát célra alkalmazandó. Jelen esetben, az előzőektől eltérően “_”-t, használok a privát jelzéshez és nem “priv” objektumot. Ez rövidebb, kissé átláthatatlanabb, viszont kényelmesebb a privát függvényekből történő publikus függvények hívása.
Ezt az aktuális “uid” változót elmentjük az objektumunk “_uid” változójába.

Aztán létrehozunk egy “_class” változót.
Az eddigiektől eltérően ez egy tömb lesz. Így azt is követhetjük, hogy egy osztály, milyen osztályból származik.

Végezetül elmentjük az objektum nevét.

Persze ezek a létrehozott változók nyilván nem szükségesek, de egy esetleges debug folyamat esetén igen hasznosak tudnak lenni.

Akkor jöjjön a “Widget” osztály.

{code type=javascript}Node.prototype.setAttributes = function(attributes){
for (i = 0; i < attributes.length; i++) this.setAttribute(attributes[i][0],attributes[i][1]); }var WidgetClass = function WidgetClass(){} WidgetClass.prototype = new ObjectClass();WidgetClass.prototype._WidgetClass = function(parentNode,objectName,cssClass){ this._ObjectClass(objectName); this.addClass('Widget'); this.setParent(parentNode,null,cssClass); } WidgetClass.prototype.setParent = function(parentNode,pos,cssClass){ if (this._realized) this.destroy(); this._parent = parentNode?parentNode:document.body; this._widget = document.createElement('div'); this._widget.setAttributes([ ['style','display:none;'], ['class',cssClass], ['id',this._uid], ]); if ((pos == null) || (pos == -1) || (pos > this._parent.length)) {
this._parent.appendChild(this._widget);
} else {
this._parent.inserBefore(this._widget,this._parent.childNodes[pos])
}
this._realized = true;
}

WidgetClass.prototype.setCSSClass = function(cssClass){
this._widget.setAttribute(‘class’,cssClass);
}

WidgetClass.prototype.getParent = function(){
return this._parent;
}

WidgetClass.prototype.getWidget = function(){
return this._widget;
}

WidgetClass.prototype.getCSSClass = function(){
return this._widget.getAttribute(‘class’);
}

WidgetClass.prototype.isRealized = function(){
return this._realized;
}

WidgetClass.prototype.destroy = function(){
if (this._realized) {
this._parent.removeChild(this._widget);
this._widget = null;
this._realized = false;
}
}

WidgetClass.prototype.show = function(display){
if (this._realized) this._widget.style.display = display?display:’inherit’;
}

WidgetClass.prototype.hide = function(){
if (this._realized) this._widget.style.display = ‘none’;
}{/code}

Rögtön kezdjük egy trükkel! A javascript lehetőséget ad arra, hogy meglévő “osztályok” függvényeit a saját kódunkban bűvítjük. 🙂
Ezt rögtön meg is tesszük.
A node-oknak csak “setAttribute” metódusa létezik. Nem létezik olyan, hogy több attribútumot egyszerre változtassunk, márpedig néha praktikus lenne. Így a prototype segítségével létrehozunk egyet.

Ismét üres osztálydefiníciót hozunk létre.

Megint definiáljuk a saját külön bejáratú konstruktorunkat.
Meghívjuk az “ObjectClass” privát célú konstruktorát, amiből a “WidgetClass” származik. Mindössze ennyi a kényelmetlenség. 😉
Hozzáadjuk a “Widget” sztringet az osztályok listájához.
Végezetül megadjuk (majd) a szülő osztályt, ahova a widgetet (ami egy div) be kell majd csatolni, a pozíciót (mert egy node több másik node-ot is tartalmazhat), majd egy css osztályt.
Itt jön a képbe a több attribútum egyszerre történő megadása: display:none (a widget nem látható), class (css osztály), id (ami az “_uid” lesz, jelen esetben).
A widgetet a papa alá soroljuk be. Ha a pozíció nem definiált, vagy nem legális, akkor a végére, egyébként az adott pozícióba. Ezzel azt is elérhetjük, hogy valami felé/elé helyezzük a widgetünket.

A set és get parancsokról nem érdemes többet beszélni.

Realizáltnak tekintjük a widgetet, ha már be van csatolva a struktúrában és még nincs törölve.

A widgetet el is pusztíthatjuk. Ekkor eltávolítjuk a szülő node-ból és töröljük.

Végezetül a widgetet megjeleníthetjük, vagy eldughatjuk.

Tegyük fel, hogy pl. egy DialogClass ebből a WidgetClassból származik, de a DialogClassból már semmi más nem származik. Ebben az esetben már nem a szokásos módon járunk el, hanem az eredeti konstruktorban futtatjuk a WidgetClass konstruktorát, valamint ugyanitt deklarálhatjuk a változókat is.

{code type=javascript}var DialogClass = function DialogClass(){
this._WidgetClass(…);

}
DialogClass.prototype = new WidgetClass();
….{/code}

Ennek az az oka, hogy a továbbiakban már nem egy példányban lesz létrehozva az osztály, mint eddig:

{code type=javascript}dialog1 = new DialogClass();
dialog2 = new DialogClass();
….{/code}

Látható, hogy a DialogClass objektumonként van létrehozva, azaz megengedhető az “eredeti” konstruktor típus.
Ennek a megoldásnak az az előnye, hogy itt már nem kell külön meghívni a konstruktor függvényt.