reede, 30. aprill 2010

JS: "this" ja sulundid 2 (HTML inline sündmused)

JavaScript võimaldab kasutada mitmesuguseid sündmuseid - näiteks on võimalik käivitada programmi funktsioon kui kasutaja klikib lehel mingil kindlal elemendil või kui hiir liigub üle selle elemendi. Kuigi kõige lihtsam on seda teha otse HTML koodi sees, nn. inline (<div onclick="alert('klikk!')">kliki mind</div>), siis soovitatav kasutusjuht oleks siiski defineerida sündmus HTML koodist eraldi, eristada programmiline osa dokumendist (element.addEventListener("click", callback, false);). Mida aga teha siis, kui meil on tarvis siiski kasutada inline sündmust, kuid tegu pole mitte "päris" sündmusega, vaid hoopis kohandatud, ise defineeritud ja väljakutsutava sündmusega?

Ütleme et meil on olukord, kus tahame HTML elementidele lisada inline kohandatud sündmuseid, näiteks pookida elemendile külge väljamõeldud sündmust nimega kell12, mis rakendub juhul kui kell saab 24:00 (eeldusel et sama lehekülg on brauseris sel ajal lahti)

<span onkell12="alert(this.innerHTML)" style="display:none">Käes on südaöö!</span>

Sündmuse parameetri prefiksiks on seatud sarnaselt teistele inline sündmustele (onclick, onmouseover jne) "on", kuid see ei ole tehniline vaid põhimõtteline seadistus, sama hästi võiks see olla lihtsalt "kell12" või "kellasyndmus". Näite järgi peaks kell 24:00 tulema kasutajale teade, mille sisuks on konkreetse SPAN elemendi sisu (innerHTML, "Käes on kesköö!").

Kuidas siis sellist sündmust tööle panna?

Esiteks tuleb elemendist sündmuse  sisu kuidagi kätte saada. Seda saab teha getAttribute meetodiga.

var syndmus = span_element.getAttribute("onkell12","");

Kui sündmuse sisu on käes, tuleks see käivitada. Aga kuna tegu pole mitte funktsiooni-, vaid tekstilise väärtusega, tuleb saadud tekst implementaatoris programmiks muuta. Selle jaoks kasutatakse reeglina käsklust eval. Antud käsklus võtab parameetrina saadud teksti ning muudab selle JavaScripti koodiks. Näiteks nii - eval("alert(1)");

Siinjuhul eval siiski ei kõlba, kuna kuidagi on vaja siduda this väärtus käivitatavas tekstis õige SPAN elemendiga, eval puhul oleks this väärtuseks aga window. Eelmisest postitusest tutvustatud bind meetodit vms. ei saa siin otse kasutada, kuna eval käivitab, mitte ei tagasta funktsiooni. Siin tuleb appi konstruktor Function - nimelt Function konstruktor käitub eval käsule sarnaselt, teisendades teksti programmiks, kuid käivitamise asemel tagastab funktsiooniobjekti ning sellele saab juba rakendada this väärtuse sidumiseks meetodit call.

Function(syndmus).call(span_element);

Antud konstruktsioon kõigepealt loob tekstilisest lähtekoodist (muutuja syndmus sisu) funktsiooniobjekti; ning seejrel funktsiooni meetod call seob funktsioonis this väärtuseks HTML elemendi span_element ning käivitab niiviisi saadud funktsiooni.

Kokkuvõttes oleks skript siis järgmine. Seadistatakse timeout käsklus, mis käivitatakse täpselt kell 24:00. Lehelt otsitakse käivituse käigus üles kõik SPAN elemendid ning üritatakse kasutada nende elementide onkell12 atribuute.

window.setTimeout(function(){
    var spans = document.body.getElementsByTagName("span");
    for(var i=0, len = spans.length; i<len; i++){
         Function(
           spans[i].getAttribute("onkell12","")
         ).call(spans[i]);
    }
},new Date(+new Date()+86400000).setHours(0,0,0,0)-new Date());
Järgmises postituses üritan millalgi kirjutada XHMTL elementide laiendamisest oma nimeruumi elementidega (näiteks nagu FaceBook'i XFBML elemendid ) - seal muutub oluliseks ka siin postituses kirjeldatud inline sündmuste süsteem, kuna nii on mugav võõraid elemente enda lehele külge pookida.

neljapäev, 29. aprill 2010

JS: Kuidas hoida "this" sulundites alles

Üheks tuntud probleemiks JavaScriptis on muutuja this sidumine funktsioonis a) aktiivse objekti meetodi juurde või b) kõigil teistel juhtudel globaalse objekti window juurde. Sulundite korral kui funktsiooni deklaratsioon asub objekti meetodi sees ei liigu this sulundfunktsiooni edasi ning see tekitab tihti segadust.

var omadus = 321;
var objekt = {
    omadus: 123,
    meetod: function(){
        alert(this.omadus); // 123
        window.setTimeout(function(){
            alert(this.omadus); // 321
        },0);
    }
}
objekt.meetod();

Nagu näites näha asub objekti meetodi meetod sees kaks alert käsklust, mis mõlemad väljastavad this.omadus sisu. Esimene alert käsklus asub otse objekti meetodis, teine aga selle meetodi skoopi jäävas sulundfunktsioonis. Mõlemad kasutavad küll sama nimetust muutuja esitamiseks (this.omadus), kuid ometigi on tulemus täiesti erinev - esimesel juhul korrektselt 123, teisel juhul aga hoopis 321.

Siit selgubki, et this on seatud korrektselt ainult vahetult meetodi skoobis, kuid mitte sulundina selle sees. Viimasel juhul on ei viita this enam mitte aktiivsele objektile, milleks peaks olema näite juures objekt, vaid hoopis globaalsele objektile window. Kuna aga kõik "globaalsed muutujad," (st. ei kuulu kindlasse skoopi) sh. omadus on tegelikult window objekti omadusteks, siis this.omadus anonüümses sulundfunktsioonis teisendub window.omadus ja sellest juba lihtsalt omadus muutujaks, mille väärtuseks ongi näites 321.

Juhul kui kasutusel on Prototype raamistik saab olukorra mugavaks lahendamiseks kasutada raamistiku poolt pakutavat vahekihti loovat meetodit bind, mis seob this väärtuse funktsioonis vajaliku objektiga.

var objekt = {
    omadus: 123,
    meetod: function(){
        alert(this.omadus); // 123
        window.setTimeout((function(){
            alert(this.omadus); // 123
        }).bind(this),0);
    }
}
objekt.meetod();

See meetod võtab funktsiooniobjekti kujul (function(){}) ning rakendab sellel Prototype lisatud funktsiooni meetodit bind mis omakorda tagastab uue funktsiooniobjekti, mis antakse setTimeout parameetriks.

Kuidas bind töötab?

Juhul kui Prototype puudub võib sellise funktsiooni meetodi lihtsalt ise teha.

Function.prototype.bind = function(){
    var that = this,
        params = Array.prototype.slice.call(arguments),
        obj = params.shift();
    return function(){
        that.apply(obj, params);
    }
}

Näites on defineeritud kolm muutujat:
that on viide funktsiooniobjekti juurde, params on funktsiooni kõiki parameetreid sisaldav arguments objekt teisendatuna massiiviks (arguments on massiivilaadne objekt, aga mitte massiiv) ning obj, milleks saab params massiivi esimene objekt.

Funktsioon tagastab uue funktsiooniobjekti, mille käivitamisel käivitatakse esialgne funktsioon, aga juba korrektselt seatud this väärtusega.

Arvestada tuleb ainult et native objektide prototype muutmine pole reeglina kunagi hea mõte ning võibolla tasuks funktsiooni meetod defineerida hoopis eraldisesiva funktsioonina.

neljapäev, 15. aprill 2010

GitHub kasutamise õpetus

Kuna viimasel ajal on vaja olnud mitmete projektide jaoks kasutada GitHub versioonihaldust, siis kirjutasin selle jaoks vastava juhendi http://tahvel.info/git_github