hasFocus ir activeElement už 400 baitų

Vienas labiausiai internete užknisančių dalykų yra tai, kad kai kurios svetainės visiškai manęs negerbdamos paima ir perrašo įvesties laukų turinį tada kai aš jau ten bandau kažką rašyti. Ši situacija kyla iš to, kad nėra paprasto būdo nustatyti ar (ir kuris) HTML elementas yra fokusuotas ar ne. Internet Exploreris jau senokai turi document.activeElement, bet kas iš to, jeigu tai negalioja kitoms naršyklėms. HTML5 specifikacijoje yra standartizuotos abi -element.hasFocus ir document.activeElement galimybės, tačiau ką daryti šiandien? Tingiu skaityti – duok galutinį sprendimą.

Aprašytos problemos sprendimo algoritmas yra gana paprastas – prikabinti įvykių gaudytojus prie reikalingų elementų ir pačiam nustatyti reikalingus parametrus. Tačiau jeigu nesusimąstytume – greitas ir paprastas įgyvendimas turėtų kelias problemas.

Sprendimo problemos

Pavyzdys: krauname puslapį, jame yra paieškos laukas, kurio viduje yra tekstas “Paieška”. Fokusuojant lauką tas tekstas turi pradingti. Be abejo, pats “standartinio teksto” patternas yra jau savaime nelabai geras, tačiau kartais be jo tiesiog neįmanoma išsiversti. Taigi, turime du variantus: a) tekstą įrašyti lauke iš anksto (input value="Paieška"), fokusuojant jį ištrinti; b) palikti tuščią lauką, ir užkrovus puslapį tą tekstą įrašyti. Pirmu atveju turime semantikos problemą, o taipogi jis nėra netrukdantis, t.y. tam, kad naudotis lauku, reikalingas įjungtas Javascript. Taip pat, abiem atvejais, neįdėjus papildomų pastangų, perrašymas gali įvykti jau po to kai vartotojas fokusuoja lauką (pvz. dėl lėtai kraunamų Javascript bibliotekų arba IE6 paspaudus refresh, kai kursorius yra viename iš laukų).

Konkrečiam pavyzdžiui yra dar ir trečiasis variantas – naudoti onfocus ir onblur atributus ir Javascript rašyti tiesiog HTML viduje (inline Javascript), tačiau šis būdas tinkamas tik tuo atveju, jeigu nesilaikote sužymėjimo ir elgsenos atskyrimo principo. Be to, jis apkrauna kiekvieną siunčiamą puslapį papildomu kodu, kas nėra pageidautina, ypač jeigu tokių laukų yra ne vienas. Na ir aišku, jis neišsprendžia pagrindės šio įrašo problemos – hasFocus – vieningu, paprastu, universaliu būdu.

Kodo kiekis ir geriausios praktikos

Akivaizdu, kad hasFocus simuliavimui reikės kiekviename puslapyje įtraukti gabaliuką Javascript, kuris priešingai negu geriausios praktikos rekomendacijos, turės būti įvykdomas dar prieš galutiniai “nupiešiant” puslapį, t.y. <head> viduje. Tai reiškia, kad pats kodas turi būti minimalus – tiek instrukcijų kiekiu, tiek savo apimtimi, nes kiekvienas persiųstas baitas ir vykdymas kainuoja vertingas milisekundes (prezentacija).

Įvykių burbuliavimas

Su ankstesniu reikalavimu atsiranda ir kitas – neturime galimybės įvykių gaudyti kiekvienam elementui individualiai. Net jeigu tai ir nebūtų idelaus sprendimas vien dėl apkrovimo priežasčių, tie elementai dar paprasčiausiai neegzistuoja dokumente. Taigi turime rinktis reglib požiūrį – prikabinti savo funkcijas prie viso dokumento ir jų viduje nustatinėti elementą, kuriame viskas vyksta. Šioje vietoje derėtų paminėti, kad remiantis standartais, focus ir blur įvykiai neburbuliuoja į viršų, t.y. tėvinis elementas teoriškai neturėtų žinoti, kad jo vaikuose vyksta būtent šie įvykiai.

Naršyklių skirtumai

Skirtingos naršyklės eventus gaudo skirtingais būdais, taipogi naudoja skirtingą metodiką perduoti susiijusius elementus. Tradiciškai ši problema buvo sprendžiama vykdant skirtingas kodo šakas skirtingoms naršyklėms, tai nustant pagal user-agent eilutę. Tokia metodika yra žalinga ir nėra amžina. Pastaruoju metu tiek jQuery, tiek kitos bibliotekos perėjo prie efektyvesnio būdo – tikrinti ar egzistuoja tam tikros galimybės, o ne tikrinti ar lankytojas naudoja konkrečią naršyklę. Feature sniffing taipogi yra reikalavimas šiai užduočiai.

Sprendimas pažingsniui

  1. Nusistatom įvykių gaudymo funkciją (f) – IE naudoja attachEvent, padorios naršyklės – addEventListener.
  2. Nusistatom įvykių vardus (n). focus ir blur kaip minėjau neturėtų burbuliuoti į viršų, todėl su IE reikia naudoti onfocusin ir onfocusout. Šia tema galima pasiskaityti ir daugiau, tačiau šiam sprendimui šito užtenka.
  3. Pagrindinė fokuso nustatymo funkcija (s) ima du parametrus – pirmas nurodo ar fokusą įjungti, ar atjungti, antrasis gi nurodo elementą.
  4. Neesu įsitikinęs, kad čia pats taupiausias variantas, tačiau įdedame dvi funkcijas – viena gaudo focus (i), kita blur (o), ir kviečia pagrindinę funkciją.
  5. Viską sukišame į closure ir sutrumpinę iki minimumo pagaliau prisegame įvykius – d[f](n[0],i,true);.

Galutinis sprendimas

(function() {
    var d = document,
	s = function(h, t) {
		if (t.tagName) {
			t.hasFocus=h;
			if (a) document.activeElement=t;
		}
	},
	i = function(e) {
		s(true, e.target || e.srcElement); },
	o = function(e) {
		s(false, e.target || e.srcElement); },
	f = d.addEventListener ?
		"addEventListener" : "attachEvent",
	n = typeof d.onfocusin=="object" ?
		['onfocusin','onfocusout'] : ['focus','blur'],
	a = typeof document.activeElement=="object" ?
		false : true;

	d[f](n[0],i,true); d[f](n[1],o,true);
})();

Panaikinus bereikalingus tarpus ir suspaudus viską į vieną eilutę, šis sprendimas užimtų vos daugiau nei 400 baitų – pakankamai maža kaina už funkcionalumą. Jeigu netyčia nepalikau kokios klaidos kur nors – viskas turėtų veikti su IE6/7, FF2/3, Opera 9.6 ir Safari 3.x.

Taipogi galite pasižiūrėti veikiančią demonstraciją. Po dviejų sekundžių nuo užkrovimo gausite iššokantį langą, kuriame bus surašyta kas turi fokusą.

One Response to “hasFocus ir activeElement už 400 baitų”

Komentarų RSS