JS> bind HTML onclick event to Custom Element v1 object
TL; DR
How to bind the inline onclick event callback of the div element to the custom element that the div is hosted in? I want to use onclick="foo('bar')"
instead onclick="this.foo('bar')"
.
Long version:
By adding a Custom element with an interactive div to the DOM like this:
<my-element>
<!-- 1. works -->
<div onclick="this.foo('bar')">click me</div>
<!-- 2. not working -->
<div onclick="foo('bar')">click me</div>
<!-- edit: 3. playground (works) -->
<div onclick="me.foo('bar')">click me</div>
</my-element>
... now I want to bind a foo()
-function to my custom element ( <my-element>
).
1st solution : here onclick
calls the foo()
-function on this
( this.foo()
) where it this
later binds to my custom element (see following code).
2nd solution . Here I want to omit this.
and bind it again to my custom element. But: while the binding works in the above solution (1.), it is not without the foregoing this.
- my problem.
edited 3rd solution . Uses a proxy to project a function call from me
to a this
Custom element. It doesn't solve the problem, but at least I've tried it.
So the problem is how to work with solution 2?
My custom element v1 class - including the code I tried:
class MyElement extends HTMLElement
{
constructor()
{
super()
}
connectedCallback()
{
var self = this
this.addEventListener('click', this.handleClick, true)
// Proxy for solution 3
window.me = new Proxy({},
{
get: function(target, prop)
{
return self[prop]
}
})
}
handleClick(ev)
{
console.log(ev.target.onclick)
// returns: ƒ onclick(event) { foo("bar") }
// > is typeof function
// provides the binding to "this" for solution 1
ev.target.onclick = ev.target.onclick.bind(this)
// call
ev.target.onclick()
// works on first solution
// throws error in 2nd solution: foo is not defined
}
foo(str)
{
console.log(str)
}
}
customElements.define('my-element, MyElement)
Some explanations:
-
By using
addEventListener
and setting my third parameter (useCapture
) to true, I will capture this event before executing it. -
I can register the function
foo()
to the console. It is encapsulated in the called function, which seems to be the cause, so I cannot bind my context to thefoo()
-function itself . -
Calling a function through
ev.target.onclick()
throws an error, as in this context (window
) no - thefoo()
function exists.
Some thoughts:
-
React does something like this (I haven't used React yet), but I can't see how to port their solution as they end up somehow handling their onclick events (with eval?). This may be the reason why their syntax is
onclick={foo}
instead ofonclick="foo()"
. I also don't know if parameter passing is possible in React. See: https://facebook.github.io/react/docs/handling-events.html -
Referring to this, maybe the problem is that in my solution the function
foo()
is probably already called in some way within the window context, which means it is already set and cannot be changed anymore ...? Either way, trying to installonclick="foo"
without brackets and call it later also doesn't work.
Read this thread well:
http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-component-classes/
So, I don't know if there is a solution at all. Ideas or explanations are welcome anyway!
EDIT 2: Fixed.
It also works when an element is created and added from within the element's class:
var el = document.createElement('div')
el.innerHTML = 'click me'
el.onclick = () => {
this.foo('bar')
}
this.appendChild(el)
EDIT 3, 4, 5: Played a bit (see Solution 3) and assigned the Proxy object to a global variable ('me') that triggers any this
custom Element function calls - kinda __noSuchMethod__
. So ... by choosing 1 or 2 variable characters, you can save at least a few strokes on the keyboard without typing this.
, but me.
, vm.
or even i.
... the best solution is this far. Sad! Unfortunately, solution 3 also cannot be used as a common template for all elements, as it restricts the context to only one (i.e., last connected) Custom Element.
EDIT 6 - Preliminary findings
As it turns out, it is not possible to bind the html-inline onclick-event ( onclick="foo('bar')"
) to the Custom Element class that has an inline clickable element.
So the most obvious ways for me would be:
A) use this keyword (for example onclick="this.foo('bar')"
) and bind it to the class as shown above. Why it this.foo()
is a binder, while it foo()
is this
not without , remains unclear at the moment. I am wondering that both functions are the same - both have bindings already :) are this.foo(
attached to the clickable element, foo()
bound to window
.
B) use eval as suggested by @Supersharp i.e. add event listener to custom element, listen for clicks, get onclick attribute value as string, add "this". to prompt and efficient to do so: eval("this." + "foo()")
.
C), alternatively for inline onclick, I would like to use event delegation and use the declarative html attributes to specify the behavior. To do this, add the click event listener again to the Custom Element class:
myElement.addEventListener('click', function(ev) {
if (ev.target.dataset.action)
{
this[ev.target.dataset.action]()
}
}, false)
Your interactive element will look like <div data-action="next">Show more</div>
.
source to share
The solution is to use the eval()
concatenation "this."
and content attribute onclick
for the element event.target
.
Remember to use event.stopPropagation()
to stop sending the event.
handleClick( ev )
{
eval( 'this.' + ev.target.getAttribute( 'onclick' ) )
ev.stopPropagation()
}
If you don't want to use eval()
, you must parse the content ev.target.getAttribute( 'onclick' )
to extract the et function name arguments and then call the element method if it exists:
if ( typeof this.name === 'function )
this[name](arguments)
Update
I suggest you specify id
for the custom element and then call the id + method:
<my-element id="me">
<div onclick="me.foo('bar')">click me</div>
</my-element>
This way you don't need to route / catch the event, but div
can be anywhere on the element.
source to share