JS: addEventListener-nek fn-t átadni változóként + param

Span attribútumból szedném az functiont és a paramot, amiket szeretnék hozzáadni addEventListenerrel.

 

Pár sor kód, többet ér ezer szónál. Szóval íme, ahogyan nem megy:

<span data-function="scriptem" data-param="paraméter">

<script>

  if(e.hasAttribute("data-function")) {
            let fn = e.getAttribute("data-function");
            let param = e.getAttribute("data-param");
            let cb = (fn) => fn(param);
            e.addEventListener('click', cb);
        }

</script>

Hozzászólások

Ez sem segített:

let cb = fn +"('"+param+"')";


<span data-param="paraméter" onclick="myclick(this);/>

<script>

function myclick(element) { 
let params = element.getAttribute('data-param'); 

//handle params

}


<script>

a bindingot declaritve teszed, a handle-t meg dinamikusan. :-)

Ezt akartam el kerülni.

Nem adok a html-hez JS-t. Azt a JS teszi. HTML csak jelzi, vannak adatai ami basztathatóak.

HTML

<span data-*="" class="clickable"></span>

Íme:

function searchClickable() {
    const a_tag = document.querySelectorAll('.clickable');
    a_tag.forEach((e) => {
        if(e.hasAttribute("data-url")) {
            let link = e.getAttribute("data-url");
            let title = e.getAttribute("data-title");
            e.addEventListener('click', () => {
                getAjax({'link':link},{'title': title});
            });
        }
        
        if(e.hasAttribute("data-function")) {
            let fn = e.getAttribute("data-function");
            let param = e.getAttribute("data-param"); 
            let cb = fn +"('"+param+"')";
            e.addEventListener('click', () => eval(cb));
        }
    });
}

window.onload = searchClickable;

A clickable-t mindig hozzaadod, vagy igy engedelyezel/tiltasz dolgokat? (data-url es a data-function meglete is eleg lehet, picit osszetettebb a selector)

A window.onload igy csak egy fuggvenyt tud hivni. Most lehet, hogy ez nem gond, kesobb azza valhat.

A getAjax visszateresi erteket lenyeled. Egyebkent ha xmlHttpRequest van meg mindig mogotte, erdemes lesz megnezned a fetch-et.

Az e.dataset.* valamivel jobb a getAttribute-nal - ahogy therion javasolta.

Jo lesz ez, hajra!

A strange game. The only winning move is not to play. How about a nice game of chess?

Megoldás:

 if(e.hasAttribute("data-function")) {
            let fn = e.getAttribute("data-function");
            let param = e.getAttribute("data-param");
            let cb = fn +"('"+param+"')";
            e.addEventListener('click', () => eval(cb));
        }

 

 

Tudftam, hogy a kacsát kell megkérdeznem! :P

Így nem lenne jobb?

 if(e.hasAttribute("data-function")) {
            let fn = e.getAttribute("data-function");
            let param = e.getAttribute("data-param");
            e.addEventListener('click', function() {
                        window[fn](param);
            });
}

Jóval flexibilisebb, nem csak primitív adatot adhatsz át, hanem adott esetben referenciát is az elemre.

A function es az arrow function kozti kulonbsegre figyelj! Foleg, hogy event listenerrol van szo. Gondolom Oregon nem veletlenul akart arrow functiont. (ez nem bindolja a this-t)

A {} a listener kore meg nem biztos, hogy jo otlet. A fuggveny visszateresi erteke jelenti, hogy lekezelte-e az esemenyt, vagy menjen tovabb a lancban (true vagy false). Igy return nelkul lenyeled. A sima arrow function meg {} nelkul visszater vele:

() => {return fn();} ugyanaz, mint a () => fn()

A strange game. The only winning move is not to play. How about a nice game of chess?

e.addEventListener('click', () => window[fn](param));

Ehhez mit szol a bongeszo? Milyen hibauzenetet adott amugy az eredeti? A hianyzo parametert nem talalta (fn) ugye, vagy a stringkent atadott fuggvenynevet utalta?

A strange game. The only winning move is not to play. How about a nice game of chess?

Ezt nem így kell csinálni. A "dataset" az esetedben kulcsfontosságú:

 

document.addEventListener('DOMContentLoaded', function() {
  var spanok = document.querySelectorAll('[data-function]')

  spanok.forEach(function(span) {
    var scriptem = span.dataset.function

    var paramétereim = Object
      .keys(span.dataset)
      .filter(key => key.startsWith('param'))
      .map(key => span.dataset[key])

      if (typeof window[scriptem] === 'function') {
        window[scriptem].apply(undefined, paramétereim)
    }
  });
});

 

Valami ilyesmi.

Egy gondolat ehhez a megoldáshoz. Ennél az adott use case-nél (attribútumok nem változnak betöltés után, "statikus" a HTML) lehet, hogy érdemesebb inkább a 

if (typeof window[scriptem] === 'function') {

  window[scriptem].apply(undefined, paramétereim)

}

részbe tenni az event listener ráaggatását, mert akkor nem kell a listenernek minden kattintáskor végigszűrni-mappelni a datasetet:

if (typeof window[scriptem] === 'function') {

  span.addEventListener('click', function () {

    window[scriptem].apply(undefined, paramétereim);

  }

}
 if (typeof window[scriptem] === 'function') {

Ez kód már nem megy, amióta kiraktam egy külső js-be, amit az app.js telibe behúz.

Ezt írja: Uncaught TypeError: window[e.dataset.function] is not a function

A hibaüzenetet értem, de fogalmam sincs mi okozza. Ugye a function ugyanabban a modulban van, és ráadásul már importáltam.

Felépítés:

app.js

import { my_init, searchClickable, openLayer, closeLayer } from "./base.js"

window.addEventListener("load", (event) => {
    my_init();
});

Minden a base.js-ben van, a függvények export taggel el vannak látva.

Kód ami csak félig megy:

function searchClickable() {
// MEGY
    document.querySelectorAll('[data-url]').forEach((e) => {
        e.addEventListener('click', () => {
            getAjax({'link':e.dataset.url, 'title': e.dataset.title});
        });
    });
    
// NEM MEGY
    document.querySelectorAll('[data-function]').forEach((e) => {
        let scriptem = e.dataset.function
        let paramétereim = Object
            .keys(e.dataset)
            .filter(key => key.startsWith('param'))
            .map(key => e.dataset[key])
        if (typeof window[scriptem] === 'function') {
            window[scriptem].apply(undefined, paramétereim)
            console.log('kész')
        } else {
            console.log(scriptem)  // EZ FUT LE
        }
    });
}

Ez mit ir ki? Esetleg meg a tipusat is kiirathatod.

console.log(scriptem)

Felig off: azok az ekezetes elnevezesek mukodnek amugy? Sose jutott eszembe ilyet, angolul szoktam, [_a-zA-Z][_a-zA-Z0-9]* formaban. (tudom, hogy eredetileg is paramétereim volt a neve)

Az addEventListener kimaradt az alsobol.

A strange game. The only winning move is not to play. How about a nice game of chess?

Ékezet nem gond, én sem használom a példakedvéért visszaírtam.

Lényegében annyit ír ki, hogy nincs definiálva az fv, de ez nem igaz (max a scope-ban nincs). Valami elérés változhat meg azáltal, hogy kikerült egy importálandó js-be, de ennek  a működését még nem értem.

A window tartalmazza a scripteket? mert ott keresi window[a_szkriptem] és ehhez úgy kell definiálni hogy:

window.szkript1 = function (param1, param2) {
  console.log('Ezt a szkriptet futtattam: szkript1')
  console.log(`Paraméterek: ${param1}, ${param2}`)
}

window.szkript2 = function (param1) {
  console.log('Ezt a szkriptet futtattam: szkript2')
  console.log(`Paraméter: ${param1}`)
}

Ha egy függvény csak egy modulon belül létezik, és nincs globálisan hozzáadva a window objektumhoz, akkor azt nem lehet közvetlenül a window objektum segítségével meghívni. Azaz, ha egy függvényt modulban definiálsz, akkor az alapvetően csak az adott modulon belül lesz látható, és nem lesz globálisan elérhető.

Fentebb irta:

function xy(...) {...} helyett window.xy = function(...) {...}

vagy ha mar adott a function xy(...) {...}, akkor utana: window.xy = xy;

A strange game. The only winning move is not to play. How about a nice game of chess?

a base.js-ben ilyeneket csinálj:
 

window.szkript1 = function (param1, param2) {
  console.log('Ezt a szkriptet futtattam: szkript1')
  console.log(`Paraméterek: ${param1}, ${param2}`)
}

window.szkript2 = function (param1) {
  console.log('Ezt a szkriptet futtattam: szkript2')
  console.log(`Paraméter: ${param1}`)
}

és csak ezt írd az app.js-be:
 

import "./base.js";

Talán...???

Nagyon köszi mindenkinek. Hasznos volt. Már látom az elvi hibámat is, miért lett olyan szegény ember frameworkje életérzésű.

Bővebben:
Szeretném, ha nem lenne JS kód a HTML-ben, de ugye a dataset-be csak berakok egy JS-ből származó fn nevet.

Elvárás/Probléma:
Szeretném a projektet pure kóddal készíteni. Illetve minden ami egységes és visszatérő az is saját kód legyen. Semmi 3rd party.

Azért persze szépen egyszerűen le lehet írni, hogy klikkelni szeretnék és sztringből függvényt hívni paraméterekkel, az nem is olyan bonyolult. De mielőtt JavaScript felé fordulsz, van néhány dolog amin nem lehet átlépni, főleg ha Te szeretnél mindent megírni.

Nekem a C# nyelv áll legközelebb a szívhez, ezért a JavaScript/TypeScript amit írni szoktam eléggé hasonlít rá. A szememnek kényelmes, ha ránézek a kódra, és egyforma a struktúra. Ezért amit itt látni fogsz, az ezért olyan amilyen.

A click esemény önmagában mondhatni elavult, és el kell felejteni. Sokkal többről van szó mint a click. Alapból ott van az egér, a ceruza és az ujjbegy. Napi szinten beépült az emberek életébe, és bármit is csinálsz, mindenki a berögzült mozdulataival fog interakcióba lépni a kijelzővel. Ezért alkották meg a Pointer Events API-t. (https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) Minden bemenetet támogat egyszerre, ha felhasználó eszköze elég modern hogy a frissítések minden OS-en meglegyenek. Viszont vannak régebbi mobilok, amik frissítés nélkül maradtak bármilyen okból is, ezért szükséges még a Touch Events API használata. (https://developer.mozilla.org/en-US/docs/Web/API/Touch_events) Így garantált hogy minden eszközön működni fog.

Ha mindent Te szeretnél megírni, akkor szükséged lesz egy kiinduló pontra, hogy kezeld ezeket az interakciókat. Nem írtam kommentet, helyette próbáltam úgy fogalmazni a kódban, hogy ha végig olvasod akkor érthető legyen. Rakd bele a scripteket egy "Szkriptem.js" fájlba, vagy nevezd ahogy szeretnéd, csak a html-ben is módosítsd.

Ez a két osztály kezeli a pointer* és touch* eseményeket egy klikkhez. De úgy van megírva, hogy bővíthesd több ujjas interakciókhoz.

/* eslint-disable no-console */

class Négyszög {
  constructor() {
    this.kezdőX = 0
    this.kezdőY = 0
    this.végX = 0
    this.végY = 0
  }

  kezdőPont(x, y) {
    this.kezdőX = x
    this.kezdőY = y
  }

  végPont(x, y) {
    this.végX = x
    this.végY = y
  }
}

class MutatóVagyUjjbegy {
  constructor(elem, visszahívhatóFüggvény, maximálisEltérésAKezdőponttól = 15, maxMutatóSzám = 1) {
    if (!elem) {
      throw new Error('Nem adtál meg elemet')
    }

    if (!visszahívhatóFüggvény) {
      throw new Error('Nem adtál meg visszahívható függvényt')
    }

    this._ezBiztosMutató = Boolean(window.PointerEvent)

    this._elem = elem
    this._visszahívhatóFüggvény = visszahívhatóFüggvény
    this._maximálisEltérésAKezdőponttól = maximálisEltérésAKezdőponttól
    this._maxMutatóSzám = maxMutatóSzám
  }

  _tutiHogyKlikk(négyszög) {
    return Math.abs(négyszög.kezdőX - négyszög.végX) < this._maximálisEltérésAKezdőponttól &&
           Math.abs(négyszög.kezdőY - négyszög.végY) < this._maximálisEltérésAKezdőponttól
  }

  _hívjukAFüggvényt() {
    this._visszahívhatóFüggvény(this._elem)
  }

  figyelés() {
    this._mutatók = {}

    if (this._ezBiztosMutató) {
      this._elem.addEventListener('pointerdown', this._pointerDownKezelő.bind(this), false)
      this._elem.addEventListener('pointerup', this._pointerUpKezelő.bind(this), false)
    } else {
      this._elem.addEventListener('touchstart', this._touchStartKezelő.bind(this), false)
      this._elem.addEventListener('touchend', this._touchEndKezelő.bind(this), false)
    }
  }

  _pointerDownKezelő(event) {
    if (Object.keys(this._mutatók).length < this._maxMutatóSzám) {
      const négyszög = new Négyszög()
      négyszög.kezdőPont(event.clientX, event.clientY)
      this._mutatók[event.pointerId] = négyszög
    }
  }

  _pointerUpKezelő(event) {
    const négyszög = this._mutatók[event.pointerId]
    if (négyszög) {
      négyszög.végPont(event.clientX, event.clientY)

      if (this._tutiHogyKlikk(négyszög)) {
        this._hívjukAFüggvényt()
      }

      delete this._mutatók[event.pointerId]
    }
  }

  _touchStartKezelő(event) {
    for (const touch of event.changedTouches) {
      if (Object.keys(this._mutatók).length < this._maxMutatóSzám) {
        const négyszög = new Négyszög()
        négyszög.kezdőPont(touch.clientX, touch.clientY)
        this._mutatók[touch.identifier] = négyszög
      }
    }
  }

  _touchEndKezelő(event) {
    for (const touch of event.changedTouches) {
      const négyszög = this._mutatók[touch.identifier]
      if (négyszög) {
        négyszög.végPont(touch.clientX, touch.clientY)

        if (this._tutiHogyKlikk(négyszög)) {
          this._hívjukAFüggvényt()
        }

        delete this._mutatók[touch.identifier]
      }
    }
  }

  dispose() {
    this._visszahívhatóFüggvény = null
    if (this._ezBiztosMutató) {
      this._elem.removeEventListener('pointerdown')
      this._elem.removeEventListener('pointerup')
    } else {
      this._elem.removeEventListener('touchstart')
      this._elem.removeEventListener('touchend')
    }

    super.dispose()
  }
}

 

Ez a két osztály és az egyéb kód pedig függetlenül a kijelző interakciótól, valósítja meg azt a logikát amit szeretnél elérni.

 

class FuttatomASzkriptem {
  constructor(elem) {
    if (elem && this._mindenDatasetetBeállítottam(elem)) {
      this.mutatóVagyUjjbegy = new MutatóVagyUjjbegy(elem, this.fussForestFuss.bind(this))
      this.mutatóVagyUjjbegy.figyelés()
    }
  }

  // eslint-disable-next-line no-unused-vars
  fussForestFuss(elem) {
    // Az elem is elérhető ha szükséges
    window[this.szkriptem].apply(undefined, this.paramétereim)
  }

  _mindenDatasetetBeállítottam(element) {
    if (element.dataset.function) {
      this.szkriptem = element.dataset.function

      this.paramétereim = Object
        .keys(element.dataset)
        .filter(key => key.startsWith('param'))
        .map(key => element.dataset[key])

      if (typeof window[this.szkriptem] === 'function') {
        return true
      }
    }

    return false
  }

  dispose() {
    this.mutatóVagyUjjbegy?.dispose()
    super. Dispose()
  }
}

class DOMLogika {
  constructor() {
    this._szkriptjeim = []

    if (document.readyState === 'complete') {
      this._dokumentumBetöltődött()
    } else {
      document.addEventListener('DOMContentLoaded', () => this._dokumentumBetöltődött())
    }
  }

  _dokumentumBetöltődött() {
    const gombok = document.querySelectorAll('[data-function]')
    for (const gomb of gombok) {
      this._szkriptjeim.push(new FuttatomASzkriptem(gomb))
    }
  }

  dispose() {
    for (const szkript of this._szkriptjeim) {
      szkript.dispose()
    }

    super.dispose()
  }
}

window.szkript1 = function (param1, param2) {
  console.log('Ezt a szkriptet futtattam: szkript1')
  console.log(`Paraméterek: ${param1}, ${param2}`)
}

window.szkript2 = function (param1) {
  console.log('Ezt a szkriptet futtattam: szkript2')
  console.log(`Paraméter: ${param1}`)
}

window.DOMLogika = new DOMLogika()

És a hozzá tartozó html:

<!doctype html>
<html lang="hu">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Szkript futtató demo</title>
  <style>
    .szkriptGomb {
      display: inline-block;
      padding: 10px;
      border: 1px solid black;
      margin: 10px;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <h1>Szkript futtató demo</h1>

  <button aria-label="ARIA label fontos!!!" type="button" class="szkriptGomb" data-function="szkript1" data-param1="paraméter 1" data-param2="ez a második">szkript 1 gomb</button>

  <button aria-label="ARIA label fontos!!!" type="button" class="szkriptGomb" data-function="szkript2" data-param1="kettes szkript paraméter 1">szkript 2 gomb</button>

  <script src="Szkriptem.js"></script>
</body>

</html>

Köszi, hogy ennyi munkát beleteszel. Sokat fogok belőle tanulni azt már látom. Sajnos az agyam mára kuka. Holnap ezzel kezdek.

A C# jó cucc, a VStudio még jobb, de együtt talán leghatékonyabb eszközrendszer. Egyelőre maradok a php-nál, mert 1998 óta ebben van kb minden üzleti dolgom. Viszont idén beleszerettem a JS-be, mert már nem full szopás benne fejleszteni (Vanilla) és lényegében két meghatározó böngésző engine van. Ha minden mást lerszarok (ezt megtehetem), akkor elég ha a felhasználó egy kb 5 évnél fiatalabb eszközzel érkezik és menni fog minden. Jól gondolom?

MDN-t elkezdtem olvasni, mert sok hiányosságom van a  témában. (Nem szakmám, 5-10 évente készítek UI-t)

Ahhoz hogy dönts hogy mi az a browser mennyiség és verzió amit támogatni akarsz, erre a .browserslistrc fájl szolgál, erről itt olvashatsz: https://github.com/browserslist/browserslist#readme

Nézd meg hogy a fejlesztő környezeted hogyan támogatja. 

Én se foglalkozok olyan dolgokkal, ami nem kompatibilis. Az ügyfél nem fizeti ki ezeknek a hibáknak a kezelését, túl sokba kerül ezt karbantartani. 

én ezt használom a .browserslistrc fájlban, amit a bootstrap-ből loptam anno (https://github.com/twbs/bootstrap/blob/main/.browserslistrc), és bőven elég:

>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11

Kód felfogva, mdn elolvasva. Azt látom, hogy jelenleg nem kell nekem a pointer kezelés. Később, amikor az egyik alkalmazásban majd alaíratni szeretnék, ott majd igen. Köszi, hogy ilyen sok időt szántál rám. Ügyfelek nagyrésze desktopot használ. Ugyanis ez egy b2b webshop.

Az alapötletről mi a véleményed? Buktatók?

Egy régi php-s alkalmazást akarok vele felfrissíteni, hogy ne legyen állandóan oldalbetöltés.