Użycie bind(this) nie tracąc odniesienia do elementu klikniętego

Użycie bind(this) nie tracąc odniesienia do elementu klikniętego
terlecki
  • Rejestracja:prawie 14 lat
  • Ostatnio:ponad 3 lata
  • Postów:36
0

Witam,

Szukam rozwiązania problemu, na który napotkałem tworząc oskryptowanie do aplikacji PWA.
Mam na stronie odnośniki <a>, które mają domyślnie podane adresy w atrybucie "href" (czyli standardowy html). Na stronie działa klasa JS, która odpowiada za interfejs i wszelkie interakcje z nim - w tym za kliknięcia w odnośniki. Idea jest taka, żeby kliknięty odnośnik był obsłużony przez javascript, a zawartość podstorny pobrana z servera przez ajax bez całego szkieletu strony, bez przeładowania przy czym sam odnośnik ma mieć formę standardową aby dało się go udostępnić.

Przykładowy element menu:

Kopiuj
<div class='side-menu-item'>
	<a href='/asd/qwe/5875/dfg/'>
		<div class='eb-img-button'>
			<img src='/interface/buttons/any-icon.png' width='100%'>
		</div>
		<div class='item-caption'>Odnośnik 1</div>
	</a>
</div>

W JavaScript mam funkcję, która dodaje nasłuch kliknięcia na każdy element <a> przy czym odpala funkcję w tej samej klasie, która ma obsłużyć ten odnośnik.

Kopiuj
class Application {
    linkClickHandler(){
        var thisParent = arguments[0];
        var event = arguments[1];
        /*console.log("Arguments: ", arguments);
        console.log("Parent: ", thisParent);
        console.log("Event: ", event);
        console.log("This: ", this);*/
        if(event.target.nodeName == "A"){
            console.log("Jest A");
            console.log(this);
            console.log(event);

            // TUTAJ SKRYPT OBSŁUGUJĄCY LINK POPRZEZ AJAX
            event.preventDefault();
            event.stopPropagation();
            return false;
        } else console.log("To nie A....."+event.target.nodeName);
    }

    bindHandlers(){
        var el = document.querySelectorAll('a');
        var c = 0;
        var f = 0;
        for(var i=0;i<el.length;i++){
            if(!el[i].hasAttribute("eb-handle")){
                // el[i].addEventListener("click", this.linkClickHandler.bind(this), true);
                el[i].addEventListener("click", this.linkClickHandler.bind(null,this), true); // zmiana bubble/capture nie jest zauważalna przy suzkaniu rozwiązania albo zwyczajnie nie umiem z tego korzystać :)
                el[i].setAttribute("eb-handle", "true");
                c++;
            } else f++;
        }
        console.log('Handlers binded:'+c.toString()+'; Ignored: '+f.toString());
    }
}

window.addEventListener("load", () => {
    window.application = new Application();
    window.application.bindHandlers();
});

obraz_2021-10-12_140552.png

"this" musze mieć zbindowane, ponieważ wewnątrz funkcji "linkClickHandler()" będę odwoływał się do właściwości i funkcji klasy. Niestety w ten sposób tracę domyślny uchwyt do klikniętego linku <a>, który przy braku bindowania domyślnie znajduje się pod słowem kluczowym "this".

Linki zawierają również inne elementy html takie jak <img>, <div>, <svg> i przy kliknięciu na nie, JS w event.target widzi właśnie tego div-a zamiast <a> a ja muszę pobrać atrybuty z użytego linku <a>. Myślałem o wspinaniu się w górę po DOM-ie do najbliższego "A" ale zastanawiam się, czy jest jakiś lepszy temat, który zamknię się w odpowiednim użyciu addEventListener() i Bind().

Xarviel
  • Rejestracja:ponad 3 lata
  • Ostatnio:około 3 godziny
  • Postów:847
2

Zamiast używać metody bind, możesz wykorzystać arrow function, które działa w bardziej logiczny sposób według mnie.

Kopiuj
class Application {
    linkClickHandler(e) { // <--- tutaj dodajesz parametr odpowiedzialny za dane z eventu
      console.dir(e.target) // lub e.currentTarget  <-- tak się odwołujesz do klikniętego linku. Wtedy pod "e" masz właściwości elementu z eventu, a pod this metody klasy
   }

  bindHandlers() {
   //....
    el[i].addEventListener("click", (e) => this.linkClickHandler(e)) // <-- tutaj modyfikujesz podpięcie eventu click
   //....
 }
}
edytowany 3x, ostatnio: Xarviel
terlecki
Tak jest chyba faktycznie prościej. Czy masz pomysł jak zrobić, żeby dostać się do nadrzędnego "A", do którego przypisany jest event czy jednak "wspinanie" się po DOM-ie to najlepsza metoda w tym przypadku? W obecnej sytuacji jest srcElement: div.item-caption, target: div.item-caption i currentTarget: null. Zdarzenie przechwytywane jest przez dziecko odnośnika.
Xarviel
@terlecki Jeśli currentTarget zwraca null to spróbuj przenieść stopPropagation i preventDefault na samą górę funkcji (https://stackoverflow.com/questions/53811586/why-is-event-currenttarget-null-and-e-target-always-the-same) bo tutaj jest napisane, że może to się zmieniać podczas "propagacji" zdarzenia, ale nie jestem do końca pewny jak to działa. Ewentualnie dodaj dodatkowy parametr do funkcji, gdzie podczas addEventListener, będziesz przekazywać bieżące "A"
Freja Draco
Freja Draco
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 3 lata
  • Postów:3394
0

terlecki
  • Rejestracja:prawie 14 lat
  • Ostatnio:ponad 3 lata
  • Postów:36
1

@Xarviel: Łącząc Twoją radę z kombinacjami doszedłem do takiego rozwiązania :)

Kopiuj
class Application {
    linkClickHandler(event){
        var fo = false;
        var i = 0;
        var t = null;
        while(!fo&&i<event.path.length){ // pentla znajduje nadrzędny "A". Jest to najwyżej jeden-dwa poziomy wyżej więc nie rzutuje to na wydajności raczej
            if(event.path[i].nodeName == "A"){
                fo=true;
                t=event.path[i];
            }
            i++;
        }
        if(t != null){
            var h = t.getAttribute('href'); // pobieram adres docelowy do przetwarzania
            var dataTarget = t.getAttribute('data-target'); // pobieram atrybut, w którym będę przechowywał informację jak ma działać link
            if(h != '#'){
                if(dataTarget != 'window'){
                    console.log("Obsługa linku wewnętrznego..."); // tutaj będą obsługiwane linki wewnętrznie przez AJAX
                    event.preventDefault();
                    return false;
                }// linki z dataTarget = "window" będą obsługiwane jako zwykłe linki z przeładowaniem strony
            } else { // Linki zawierające sam # stosuję gdy służą do odpalenia jakiejś funkcji a nie przeniesienia do podstrony, prawdopodobnie zmienię funkcję dodającą listenery aby ignorowała takie linki i nie bindowąła im tej funkcji
                console.log("Link wyłączony z obsługi.");
                event.preventDefault();
                return false;
            }
        }
    }

    bindHandlers(){
        var el = document.querySelectorAll('a');
        var c = 0;
        var f = 0;
        for(var i=0;i<el.length;i++){
            if(!el[i].hasAttribute("eb-handle")){
                el[i].addEventListener("click", (e) => this.linkClickHandler(e));
                el[i].setAttribute("eb-handle", "true");
                c++;
            } else f++;
        }
        console.log('Handlers binded:'+c.toString()+'; Ignored: '+f.toString());
    }
}

P.S.
Pokombinuje jeszcze z tym stopPropagation() tak jak sugerujesz.
Bardzo dziękuję za pomoc :)

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.