TL;DR
How can I bind the callback of an inline onclick-event of a div element to the Custom Element, in which the div is placed?
I want to use onclick="foo('bar')"
instead of onclick="this.foo('bar')"
.
Long version:
Having appended an Custom Element with a clickable div to DOM as follows:
<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 the foo()
-function to my Custom Element (<my-element>
).
1st solution: Here onclick
calls a foo()
-function on this
(this.foo()
), where this
later gets bound to my Custom Element (see following code).
2nd solution: Here I want to omit this.
and again bind it to my Custom Element. But: while the binding works in the above solution (1.), it does not without a prepended this.
- that's my problem.
edited 3rd solution: Uses a Proxy to project the function call from me
to this
Custom Element. It doesn't solve the problem, but at least I tried.
So the problem is: How to get solution 2 working?
My Custom Element v1 class - including some 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:
Using
addEventListener
and setting its 3rd parameter (useCapture
) to true, I capture the event before getting executed.I'm able to log the
foo()
-function to the console. It is encapsulated in a callable function, which seems to be the reason, why I can't bind my context to thefoo()
-function itself.Calling the function via
ev.target.onclick()
throws an error, as in the given context (window
) nofoo()
-function exists.
Some thoughts:
React does something similar (I haven't used React yet), but I can't see how to transfer their solution, as they eventually somehow preprocess their onclick events (with eval?). That might be a reason why their syntax is
onclick={foo}
instead ofonclick="foo()"
. I also don't know, if passing parameters is possible in React. See: .htmlReferring to that, it might be a problem, that in my solution the
foo()
function is probably already somehow called inside the window context, what means, that it's context is already set and can't be changed anymore...? Anyways, trying to setonclick="foo"
without brackets and calling it later doesn't work either.
Good read to this topic:
/
So, I don't know if there is a solution at all. Ideas or explanations are wele anyways!
EDIT 2: Fixed.
It also works, when the element is created and appended from inside the element class:
var el = document.createElement('div')
el.innerHTML = 'click me'
el.onclick = () => {
this.foo('bar')
}
this.appendChild(el)
EDIT 3, 4, 5:
Played around a little (see solution 3) and assigned a Proxy object to a global variable ('me'), which projects any function calls to this
Custom Element - kinda __noSuchMethod__
. So... by choosing a 1- or 2-character-variable one could save at least a few hits on his keyboard by not typing this.
but me.
, vm.
or even i.
... best solution so far. Sad!
Unfortunately, solution 3 can also not be used as general pattern for all elements, as it bounds the context only to one (i.e. the last connected) Custom Element.
EDIT 6 - Preliminary conclusions
As it turns out, it seems not possible to bind a html-inline onclick-event (onclick="foo('bar')"
) to the Custom Element class, in which the clickable element is embedded.
So the most obvious ways to go to me would be:
A) use the this keyword (like onclick="this.foo('bar')"
) and bind it to the class as shown above. Why this.foo()
is bindable whereas foo()
without this
is not, remains unclear at the moment. I wonder bc both functions do not really differ - both have already bindings: this.foo(
) is bound to the clickable element, foo()
is bound to window
.
B) use eval as suggested by @Supersharp, i.e. add an event listener to the Custom Element, listen for clicks, get the value of the onclick-attribute as string, prepend "this." to the string and effectively do this: eval("this." + "foo()")
.
C) alternatively to inline onclick, I would tend to use event delegation and use declarative html attributes to indicate behaviour. To do this, again add a click event listener to the Custom Element class:
myElement.addEventListener('click', function(ev) {
if (ev.target.dataset.action)
{
this[ev.target.dataset.action]()
}
}, false)
Your clickable element would look like <div data-action="next">Show more</div>
.
TL;DR
How can I bind the callback of an inline onclick-event of a div element to the Custom Element, in which the div is placed?
I want to use onclick="foo('bar')"
instead of onclick="this.foo('bar')"
.
Long version:
Having appended an Custom Element with a clickable div to DOM as follows:
<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 the foo()
-function to my Custom Element (<my-element>
).
1st solution: Here onclick
calls a foo()
-function on this
(this.foo()
), where this
later gets bound to my Custom Element (see following code).
2nd solution: Here I want to omit this.
and again bind it to my Custom Element. But: while the binding works in the above solution (1.), it does not without a prepended this.
- that's my problem.
edited 3rd solution: Uses a Proxy to project the function call from me
to this
Custom Element. It doesn't solve the problem, but at least I tried.
So the problem is: How to get solution 2 working?
My Custom Element v1 class - including some 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:
Using
addEventListener
and setting its 3rd parameter (useCapture
) to true, I capture the event before getting executed.I'm able to log the
foo()
-function to the console. It is encapsulated in a callable function, which seems to be the reason, why I can't bind my context to thefoo()
-function itself.Calling the function via
ev.target.onclick()
throws an error, as in the given context (window
) nofoo()
-function exists.
Some thoughts:
React does something similar (I haven't used React yet), but I can't see how to transfer their solution, as they eventually somehow preprocess their onclick events (with eval?). That might be a reason why their syntax is
onclick={foo}
instead ofonclick="foo()"
. I also don't know, if passing parameters is possible in React. See: https://facebook.github.io/react/docs/handling-events.htmlReferring to that, it might be a problem, that in my solution the
foo()
function is probably already somehow called inside the window context, what means, that it's context is already set and can't be changed anymore...? Anyways, trying to setonclick="foo"
without brackets and calling it later doesn't work either.
Good read to this topic:
http://reactkungfu./2015/07/why-and-how-to-bind-methods-in-your-react-ponent-classes/
So, I don't know if there is a solution at all. Ideas or explanations are wele anyways!
EDIT 2: Fixed.
It also works, when the element is created and appended from inside the element class:
var el = document.createElement('div')
el.innerHTML = 'click me'
el.onclick = () => {
this.foo('bar')
}
this.appendChild(el)
EDIT 3, 4, 5:
Played around a little (see solution 3) and assigned a Proxy object to a global variable ('me'), which projects any function calls to this
Custom Element - kinda __noSuchMethod__
. So... by choosing a 1- or 2-character-variable one could save at least a few hits on his keyboard by not typing this.
but me.
, vm.
or even i.
... best solution so far. Sad!
Unfortunately, solution 3 can also not be used as general pattern for all elements, as it bounds the context only to one (i.e. the last connected) Custom Element.
EDIT 6 - Preliminary conclusions
As it turns out, it seems not possible to bind a html-inline onclick-event (onclick="foo('bar')"
) to the Custom Element class, in which the clickable element is embedded.
So the most obvious ways to go to me would be:
A) use the this keyword (like onclick="this.foo('bar')"
) and bind it to the class as shown above. Why this.foo()
is bindable whereas foo()
without this
is not, remains unclear at the moment. I wonder bc both functions do not really differ - both have already bindings: this.foo(
) is bound to the clickable element, foo()
is bound to window
.
B) use eval as suggested by @Supersharp, i.e. add an event listener to the Custom Element, listen for clicks, get the value of the onclick-attribute as string, prepend "this." to the string and effectively do this: eval("this." + "foo()")
.
C) alternatively to inline onclick, I would tend to use event delegation and use declarative html attributes to indicate behaviour. To do this, again add a click event listener to the Custom Element class:
myElement.addEventListener('click', function(ev) {
if (ev.target.dataset.action)
{
this[ev.target.dataset.action]()
}
}, false)
Your clickable element would look like <div data-action="next">Show more</div>
.
-
why don't you want to use the orthodox way:
this.parentElement.foo('bar')
? this don't require any extra javascript handler. – Supersharp Commented Aug 5, 2017 at 20:37 - parentElement would return the parent dom element, but I need the Custom Element (= its class) in which the clicked element is embedded. – sasha Commented Aug 5, 2017 at 22:26
- the parent Dom element is the custom element actually. you could also invoke it by its id. – Supersharp Commented Aug 5, 2017 at 23:46
- in my example, yes. but in more plex cases mostly not. would be possible to use a function to recursively walk up the dom with parentElement and find it. but I better like an easy implementation with less code. my goal was to bind a "foo()" without a prepended "this" to a class.so if thats not possible I'll stay with "this." or listen to events on attributes like "[action=openMenu]". – sasha Commented Aug 6, 2017 at 1:58
- then call the custom element by it id. see my updated answer. – Supersharp Commented Aug 6, 2017 at 22:02
1 Answer
Reset to default 2A solution is to use eval()
on the concatenation of "this."
and the content of the onclick
attribute the event.target
element.
Don't forget to use event.stopPropagation()
to stop dispatching the event.
handleClick( ev )
{
eval( 'this.' + ev.target.getAttribute( 'onclick' ) )
ev.stopPropagation()
}
If you don't want to use eval()
, you should parse the content of ev.target.getAttribute( 'onclick' )
to extract the function name et arguments and then call the element method if it exists:
if ( typeof this.name === 'function )
this[name](arguments)
Update
I suggest that you give an id
to the custom element, 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 redirect/catch the event, and the div
can be anywhere in the element.