+1 for both other answers, Events are the best because then Components are loosly
coupled
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/
Note that in the detail
of a Custom Event you can send anything you want.
Event driven function execution:
So I use (psuedo code):
Elements that define a Solitaire/Freecell game:
-> game Element
-> pile Element
-> slot Element
-> card element
-> pile Element
-> slot Element
-> empty
When a card (dragged by the user) needs to be moved to another pile,
it sends an Event (bubbling up the DOM to the game element)
//triggered by .dragend Event
card.say(___FINDSLOT___, {
id,
reply: slot => card.move(slot)
});
Note: reply
is a function definition
Because all piles where told to listen for ___FINDSLOT___
Events at the game element ...
pile.on(game, ___FINDSLOT___, evt => {
let foundslot = pile.free(evt.detail.id);
if (foundslot.length) evt.detail.reply(foundslot[0]);
});
Only the one pile matching the evt.detail.id
responds:
!!! by executing the function card
sent in evt.detail.reply
And getting technical: The function executes in pile
scope!
(the above code is pseudo code!)
Why?!
Might seem complex;
The important part is that the pile
element is NOT coupled to the .move()
method in the card
element.
The only coupling is the name of the Event: ___FINDSLOT___
!!!
That means card
is always in control, and the same Event(Name) can be used for:
- Where can a card go to?
- What is the best location?
- Which card in the river
pile
makes a Full-House?
- ...
In my E-lements code pile
isn't coupled to evt.detail.id
either,
CustomEvents only send functions
.say()
and .on()
are my custom methods (on every element) for dispatchEvent
and addEventListener
I now have a handfull of E-lements that can be used to create any card game
No need for any libraries, write your own 'Message Bus'
My element.on()
method is only a few lines of code wrapped around the addEventListener
function, so they can easily be removed:
$Element_addEventListener(
name,
func,
options = {}
) {
let BigBrotherFunc = evt => { // wrap every Listener function
if (evt.detail && evt.detail.reply) {
el.warn(`can catch ALL replies '${evt.type}' here`, evt);
}
func(evt);
}
el.addEventListener(name, BigBrotherFunc, options);
return [name, () => el.removeEventListener(name, BigBrotherFunc)];
},
on(
//!! no parameter defintions, because function uses ...arguments
) {
let args = [...arguments]; // get arguments array
let target = el; // default target is current element
if (args[0] instanceof HTMLElement) target = args.shift(); // if first element is another element, take it out the args array
args[0] = ___eventName(args[0]) || args[0]; // proces eventNR
$Element_ListenersArray.push(target.$Element_addEventListener(...args));
},
.say( )
is a oneliner:
say(
eventNR,
detail, //todo some default something here ??
options = {
detail,
bubbles: 1, // event bubbles UP the DOM
composed: 1, // !!! required so Event bubbles through the shadowDOM boundaries
}
) {
el.dispatchEvent(new CustomEvent(___eventName(eventNR) || eventNR, options));
},