ugrás a tartalomhoz

onclick felülírása setAttribute-tal

pkadam · 2011. Júl. 17. (V), 12.09
Egy oldalon lévő <a> elemek onclick-jét változtatnám utólag JavaScripttel (setAttribute), amivel nincs is gond – egészen addig, amíg nem szeretném valamilyen módon a már meglévő saját onclick-eseményeiket megtartani, kombinálva az újjal. Az eredmény több mint meglepő.

links = document.getElementsByTagName('a');
for (i = 0; i < links.length; i++) {
	if (links[i].target != '_blank') {
		links[i].setAttribute('onclick', 'uj();');
	}
}
Eddig minden tökéletes. Ha visszaadnám neki a saját onclick-eseményét

links[i].setAttribute('onclick', links[i].onclick);
módon, akkor a DOM-ba <a ... onclick="[object]"> módon épül be, ami nem túl barátságos. És itt jön a több mint meglepő rész: ha már annyival megváltoztatom, hogy

links[i].setAttribute('onclick', links[i].onclick + '');
aminek tulajdonképpen semmit sem kéne számítania, máris máshogy dolgozza fel: <a ... onclick="function onclick() { eredeti(); }"> az eredmény, csakhogy ettől még ugyanúgy nem csinál semmit. Tehát hozzá kell csapni az onclick() függvény meghívását az onclick esemény végéhez, hogy le is fusson. Bonyolódik? Még nem. Az általam hozzárendelni szándékozott onclick eseménynek ugyanis van egy true/false visszatérési értéke, ami máris izgalmasabbá teszi. Na meg persze az, hogy nem minden <a> rendelkezik onclick-kel, lévén a többség csak egyszerű link.

A poszt megírása közben sikerült megoldanom, de néhány okból mégis közzéteszem:
1. talán létezik ennél elegánsabb módszer,
2. hátha jól jön valakinek, aki belefut egy hasonló problémába,
3. az "object+semmi=function" egyenlet okára kíváncsi lennék.

Illetve, a leglényegesebb, hogy kinyerhető-e az eredeti onclick esemény a function(){...} belsejéből? (Kialertezve is a function()-nnel együtt dobta ki, de a substring()-et nem ette meg.)

A működő megoldás:

for (i = 0; i < links.length; i++) {
	if (links[i].target != '_blank') {
		if (links[i].onclick != null) {
			links[i].setAttribute('onclick', links[i].onclick+'; if (!confirmLeaving()) { return false; }; onclick();');
		}
		else {
			links[i].setAttribute('onclick', 'return confirmLeaving();');
		}
	}
}
Ennek eredménye pedig már <a ... onclick="return confirmLeaving();"> illetve <a ... onclick="function onclick() { eredeti(); }; if (!confirmLeaving()) { return false; }; onclick();"> lesz, ami pontosan az elvárt működést hozza.

(A meghívott confirmLeaving() függvény – amennyiben a felhasználó az adott űrlapon módosított az eredeti értékeken – megkérdezi, hogy biztosan elnavigál-e az adott oldalról.)
 
1

NE!

Poetro · 2011. Júl. 17. (V), 12.33
A setAttribute nem erre való. Ha onclick eseményt akarsz megadni egy DOM elemre, akkor állítsd be az onclick tulajdonságát, vagy használd a böngésző eseménykezelő csatoló függvényét (attachEvent / addEventListener). És ha már onclick-et használsz, akkor ne stringet adj meg értéknek, hanem a függvényt magát add meg.
function myClick(fn) {
  return function () {
    if (!confirmLeaving()) {
      return false;
    };
    onclick();
    fn();
  }
}

for (i = 0; i < links.length; i++) {  
    if (links[i].target != '_blank') {  
        if (links[i].onclick) {  
            links[i].onclick = nagyonUj(links[i].onclick);  
        }  
        else {  
            links[i].onclick = confirmLeaving;  
        }  
    }  
}
2

Köszi, elegánsabb.

pkadam · 2011. Júl. 17. (V), 14.15
Ilyesmi elegánsabbá tételre gondoltam :) A nagyonUj() és a myClick() nyilván ugyanazt takarja (bár új függvény helyett inkább inline építettem fel), illetve az onclick()-et az fn() után kellett meghívni, de hasznos volt, köszönöm. (Az eseménykezelő-csatolós történetet későbbre halasztottam.)

links[i].onclick = function() { if (!confirmLeaving()) { return false; }; links[i].onclick; onclick(); }
 
Egyébként van bármi gyakorlati különbség, vagy inkább csak illendőségből ne használjuk a szintén működő (de nem erre kitalált) setAttribute-os megoldást?
3

nehezebb

Poetro · 2011. Júl. 17. (V), 14.20
Igaz, menet közben írtam át a függvény nevét. Azért nem jó a setAttribute, mert az csak egy stringet tud beállítani, amit a JavaScript motornak újból kóddá kell alakítani, aminek több veszélye is van. Az előbb írt inline függvényed pedig azért nem egészséges, mert a ciklus minden iterációjában létrejön egy új függvény, azaz ha 100 elemed van, akkor 100 új függvény, valamint hogy az i-re való hivatkozás nem lesz jó, mivel az az utolsó értéke lesz az i-nek, a függvény lefutásakor. Lásd Closure a JavaScript függvények cikkemben.
4

Világos

pkadam · 2011. Júl. 17. (V), 15.11
Á, értem a különbséget a két módszer feldolgozásában, úgyhogy maradt a terjedelmesebb külön függvény.

Viszont a setAttribute-tal ellentétben most az eredeti onclick-kel is rendelkező elemek esetében a confirm után dob egy "A várt elem objektum" hibát a debugger, méghozzá az új függvény által meghívott onclick()-re (aztán lefut). Gyanítom azért, mert ez foglalt kifejezés... Ezt el lehet kerülni valahogy?
12

Jól emlékeztem, hogy láttam

H.Z. v2 · 2011. Júl. 31. (V), 17.29
Jól emlékeztem, hogy láttam egy ilyet! :-)
A setAttribute nem erre való.

Egy kis értetlenkedés részemről: ez nem úgy van, hogy a DOM végeredményben egy objektum, aminek vannak attribútumai és metódusai? Valamikor régen azt olvastam, hogy egy objektum attribútumaihoz nem illik közvetlenül hozzáférni, helyette inkább getter/setter metódusokat kell használni.
Ahogy az előbb a sitepoint-on nézegettem a DOM leírását, nekem az jött le, hogy végeredményben pl. az onclick is egy attribútum a sok közül és ha hű akarok maradni az OOP szokásokhoz/elvárásokhoz, akkor a setAttribute metódus segítségével kell beállítanom, nem közvetlen értékadással.
Akkor most mégsem? Vagy hogy van ez?
13

nekem az jött le, hogy

kuka · 2011. Júl. 31. (V), 17.53
nekem az jött le, hogy végeredményben pl. az onclick is egy attribútum a sok közül és ha hű akarok maradni az OOP szokásokhoz/elvárásokhoz, akkor a setAttribute metódus segítségével kell beállítanom, nem közvetlen értékadással.

Úgy van. Pontosabban addEventListener() (Explorerben 9 verzió előtt attachEvent()) metódus hívással kellene felvétetni a DOM node-dal az eseménykezelőit. Illetve az eltávolításuk removeEventListener() ((Explorerben 9 verzió előtt detachEvent()) metódus hívással kellene történjen.
14

Magyarán Poetronak abban

H.Z. v2 · 2011. Júl. 31. (V), 18.13
Magyarán Poetronak abban igaza van, hogy a setAttribute nem erre való, de abban nincs, hogy az onclick attribútumnak közvetlenül adjon értéket, igaz?
(bocs, kicsit figyelmetlen voltam szokásomhoz híven, csak addig olvastam, hogy "nem erre való" + az általa beírt példában a onclick=-ig)
16

Magyarán Poetronak abban

kuka · 2011. Júl. 31. (V), 18.29
Magyarán Poetronak abban igaza van, hogy a setAttribute nem erre való, de abban nincs, hogy az onclick attribútumnak közvetlenül adjon értéket, igaz?
Eseménykezelő beállítására setAttribute()-ot használni helytelen, onclicket használni pedig elavult.

Régebben azzal szoktunk mentegetőzni, hogy a régi böngészők kedvéért használunk onclickes eseménykezelő beállítást, de manapság már a lustaság sem lehet kifogás, mert a JavaScript keretrendszerek elvégzik az addEventListener()/attachEvent() különbség kezelését. Személy szerint én az egyszerűsége és függetlensége miatt fórumra beírt kódban még mindig onclicket szoktam használni, de máskülönben már nem.
15

attribútum vs tulajdonság

Poetro · 2011. Júl. 31. (V), 18.19
Csak a kettő között van különbség. A setAttribute a DOM elem attribútumát, míg másik a tulajdonságát állítja be. Az egyik megjelenik a DOM fában, a másik nem. És egy másik fontos dolog, hogy a DOM attribútum egy string, míg a tulajdonság bármilyen típust felvehet. Ezért is szerencsésebb tulajdonságként hozzárendelni az eseménykezelőket, mert akkor közvetlenül le tudja a böngésző futtatni a függvényt, nem kell előbb eval-oznia a stringet (minden alkalommal), ráadásul az eseménykezelő is sokkal barátságosabban működik (és működik minden böngészőben), míg a setAttribute esetében ez kevésbé megbízható.
17

Hm... na ezt még gyakorolni

H.Z. v2 · 2011. Júl. 31. (V), 18.53
Hm... na ezt még gyakorolni kell. Attribútum <> property...
OOP könyvekben az utóbbit használják az osztály-/példányváltozókra, én meg szokás szerint összekevertem őket.
5

Szasztok!

Karvaly84 · 2011. Júl. 18. (H), 01.22
Ha jól értem itt most az a lényeg ha kikkelünk felugrik egy confirm ablak és ha elhagyja az oldalt le fut az onclick ha nem akkor meg return false?
6

Pontosan

pkadam · 2011. Júl. 18. (H), 02.19
Pontosan így. Amennyiben a formon volt változtatás, és nem új ablakban nyíló linkről van szó, megerősítést kér, hogy elnavigálsz-e a szerkesztőből.
7

és a formot letárolod

Karvaly84 · 2011. Júl. 18. (H), 16.19
és a formot letárolod valamiben? hogyan csekkolod le hogy változott e a tartalma? amúgy ezt a dolgot sokkal egyszerűbben is lehet csinálni, te kicsit nagyon el bonyolítottad. azért kérdeztem rá hogy jól értelmeztem e a kérdést.

használj egy sima addEventListenert vagy ha csak egy eseménykezelőt állítasz a linkekre a sima DOM level 0 szintü megoldás is megteszi. és ott vizsgálódsz, onnan indítod a confirm ablakot is és ha az false értékkel tér vissza megállítod a link default működését, amit az event.preventDefault() függvényel tudsz megtenni. mindjárt mutatok egy példát
8

Igen, érzem, hogy biztosan

pkadam · 2011. Júl. 18. (H), 16.34
Igen, érzem, hogy biztosan lehetne egyszerűbben, de élveztem a megvalósítását :) Természeetsen a form alapértékei lapbetöltéskör eltárolódnak JS változókban, és azzal van összehasonlítva a kattintáskori állapotuk. Köszi, ha mutatsz valami egyszerűsítést.
9

<!DOCTYPE html PUBLIC

Karvaly84 · 2011. Júl. 18. (H), 16.42

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>weblabor.hu - forum</title>
<script type="text/javascript">

function confirmLeaving() {
    return window.confirm('Valóban elhagyod az oldalt?');
}

function linkHandler(e) {
    if (!confirmLeaving()) {
        if (e.preventDefault) {
            e.preventDefault();
        }
        else {
            e.returnValue = false;
        }
    }
}

window.onload = function() {
    for (var i = 0, links = document.links, link; i < links.length; i++) {
        link = links[i];
        if (link.target === '_blank') continue;
        link.onclick = linkHandler;
    }
}



</script>
</head>
<body>
<a href="http://weblabor.hu">weblabor.hu</a>
</body>
</html>
Szerintem ez alapnak megteszi csak egészítsd ki te speckós dolgaiddal.
10

Alakul (bonyolódik)

pkadam · 2011. Júl. 19. (K), 06.14
Köszönöm a segítséget, de közben azért bonyolódik a helyzet :) A legegyszerűbb volt azt áthidalni, hogy a document.link csak a href-et tartalmazó <a>-kat gyűjti be, így maradnia kellett a getElementsByTagName('a')-nak. Ezután két dolgot kellett megoldani: az eredeti onclick-et tartalmazó linkeket, illetve, hogy az IE nem fogadta el az e.returnValue = false; -t – csak akkor, ha nem link.onclick-kel csináltam, hanem a Poetro által ajánlott addEventListener/attachEvent megoldásokkal. (Az rejtély, hogy ezek híján miért dob hibát a neki valóban ismeretlen e.preventDefault miatt, hiszen egyrészt ellenőrizve van, hogy létezik-e, másrészt eseménykezelők esetén nem zavartatja magát.)

Az eredeti onclick-ek megtartására egy meglehetősen csúnya megoldást találtam ki: mielőtt felüldefiniálnám, megkapja az értékét a link.ondblclick, majd megerősített oldalelhagyás után meghívom this.ondblclick() módon. Ez minden, csak nem elegáns – szebb utat viszont nem találtam arra, hogy "helyileg" tároljam a linkben. (A Te verziódból pedig ugye ki kellett vennem a preventDefault-os részt, hogy IE-vel is működjön, így maradt az egyszerű return false.) Működőképesen így néz ki:

function linkHandler(e) {
	if (!confirmLeaving()) {
		return false;
	}
	else {
		if (this.ondblclick) this.ondblclick();
	}
}

window.onload = function() {
	for (var i = 0, links = document.getElementsByTagName('a'), link; i < links.length; i++) {
		link = links[i];
		if (link.target === '_blank') continue;
		link.ondblclick = link.onclick;
		link.onclick = linkHandler;
	}
}
Ha az elegánsabb, eseménykezelős történettel oldanám meg, akkor viszont IE-ben bonyolódik annak lehetősége, hogy az eredeti onclick-eket meghívjam, mert attachEvent-tel a this a teljes dokumentumra fog hivatkozni. A Quirksmode-on ezt írják:
The event handling function is referenced, not copied, so the this keyword always refers to the window and is completely useless.

Intenzívebben utánajárva az event.srcElement lesz a megoldás (ez talán még másnak is hasznos lehet a későbbiekben), tehát az elvileg professzionálisabb kód így fest:

function linkHandler(e) {
	if (!confirmLeaving()) {
		if (e.preventDefault) {
			e.preventDefault();
		}
		else {
			e.returnValue = false;
		}
	}
	else {
		if (this.ondblclick) this.ondblclick();
		if (e.srcElement.ondblclick) e.srcElement.ondblclick();
	}
}

window.onload = function() {
	for (var i = 0, links = document.getElementsByTagName('a'), link; i < links.length; i++) {
		link = links[i];
		if (link.target === '_blank') continue;
		link.ondblclick = link.onclick;
		link.onclick = '';
		if (link.addEventListener) {
			link.addEventListener('click',linkHandler,false);
		}
		else {
			link.attachEvent('onclick',linkHandler);
		}
	}
}
Gondolom, annak ellenére, hogy bonyolultabb, a második megoldás az ajánlott. Arra mindenesetre szívesen fogadnék ötleteket, hogy az ondblclick-be való átpasszolásnál miképp tehetném meg ezt elegánsabban (rakhatom persze pl. a rel-be, vagy egyedi attribútumba, de egyik sem az igazi). Köszönöm még egyszer a végigolvasást, és az időtök rászánását!

(Apropó, annak van különösebb jelentősége, hogy confirm() helyett window.confirm()-et írtál a kódba?)
11

Javítom

Karvaly84 · 2011. Júl. 19. (K), 18.05
IE nem fogadta el az e.returnValue = false; -t

Bocs de ezt én néztem be, mivel ilyen módon nagyon ritkán akasztok esemény kezelőket elemekre. A lényeg hogy kihagytam egy fontos sort ami a IE-hez kell ez pedig így néz ki kijavítva:

function linkHandler(e) {
    e = e || window.event; // Ez az a bizonyos sor.
    if (!confirmLeaving()) {
        if (e.preventDefault) {
            e.preventDefault();
        }
        else {
            e.returnValue = false;
        }
    }
}
Az Explorer debuggere kicsit mókás tud lenni, ugyan is nem a preventDefault volt a fő probléma hanem az h maga az esemény objektuma nem volt átadva így helyesen az a e változó miatt kellet volna sipákolnia nem a preventDefault miatt.

Ha a DOM újabb modellje szerint (attachEvent/addEventListener) akasztod rá a esemény kezelőket egy elemre a régebbi, azt hiszem a 8as verzióig az Explorer valóban rosszul definiálja a this értékét, ez a szabványban úgy működik hogy a this azt az elemt adja vissza amihez kapcsoltad a függvényt függetlenül attól hogy a e.target vagy e.srcElement mire mutat mert egy buborékban el is térhet a kettő értéke, ha mégis akarod a this-t használni akkor neked optimalizálni kel, erre Poetro-nak pl van egy kiindulási alapja, amihez szükséges a closure technika megértése amihez szintén Poetronak van egy cikke itt. A linkHandler függvényt most javítottam avval a sorral, így már működnie kell.

Apropó, annak van különösebb jelentősége, hogy confirm() helyett window.confirm()-et írtál a kódba?

Nagy jelentősége sokszor nincs de ritkán előferdül, hogy meglepetés éri az embert a globális függvények használata során, ha pl. valamiért megváltozik a kód futási kontextusa, ez legtöbször az eval-al tud szörnyű lenni ez nálam már csak megszokás, hogy tutira megyek.