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: dni
  • Ostatnio: dni
  • 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: dni
  • Ostatnio: dni
  • 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
   //....
 }
}
Freja Draco
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3394
terlecki
  • Rejestracja: dni
  • Ostatnio: dni
  • 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.