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 the foo()

    -function itself .

  • Calling a function through ev.target.onclick()

    throws an error, as in this context ( window

    ) no - the foo()

    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 of onclick="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 install onclick="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>

.

+3


source to share


1 answer


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.

+1


source







All Articles