JS> привязать событие HTML onclick к объекту пользовательского элемента v1


TL; DR

Как я могу привязать обратный вызов встроенного onclick-события элемента div к пользовательскому элементу, в который помещен div? Я хочу использовать onclick="foo('bar')" вместо onclick="this.foo('bar')".

длинная версия :

Добавив пользовательский элемент с кликабельным div к DOM следующим образом:

<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>

... теперь я хочу привязать foo()-функцию к моему пользовательскому элементу (<my-element>).

1-е решение : здесь onclick вызывает foo() - функцию на this (this.foo()), где this позже привязывается к моему пользовательскому элементу (см. следующий код).

2-е решение : здесь я хочу опустить this. и снова привязать его к моему пользовательскому элементу. Но: пока связывание работает в вышеуказанном решении (1.), это не без предлога this. - вот в чем моя проблема.

Отредактированное 3-е решение : использует прокси для проецирования вызова функции из me в this пользовательский элемент. Это не решает проблему, но ... по крайней мере, я пытался.

Итак, проблема заключается в следующем: Как заставить решение 2 работать?

Мой пользовательский элемент v1 класс-включая некоторый код, который я попробовал:

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)

Некоторые пояснения:

  • Используя addEventListener и установив его 3-й параметр (useCapture) в true, я фиксирую событие перед выполнением.

  • Я могу записать foo()-функцию в консоль. Он инкапсулирован в вызываемую функцию, которая, по-видимому, является причиной, почему я не могу связать свой контекст к самой функции foo().

  • Вызов функции через ev.target.onclick() вызывает ошибку, так как в данном контексте (window) никакой foo()-функции не существует.

Некоторые мысли:

  • React делает что-то похожее (я еще не использовал React), но я не вижу, как передать их решение, поскольку они в конечном итоге каким-то образом предварительно обрабатывают свои события onclick (с eval?). Это может быть причиной, почему их синтаксис onclick={foo} вместо onclick="foo()". Я также не знаю, возможна ли передача параметров в React. Смотрите: https://facebook.github.io/react/docs/handling-events.html

  • В связи с этим может возникнуть проблема, что в моем решении функция foo(), вероятно, уже каким-то образом вызвана внутри контекста окна, что означает, что ее контекст уже установлен и больше не может быть изменен...? В любом случае, попытка установить onclick="foo" без скобок и вызвать его позже тоже не работает.

Хорошо прочитайте эту тему:

Http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-component-classes/

Итак, я не знаю, есть ли вообще решение. Идеи или объяснения приветствуются в любом случае!

Правка 2: исправлено.

Он также работает, когда элемент создается и добавляется из класса элемента:

var el = document.createElement('div')
el.innerHTML = 'click me'
el.onclick = () => {
    this.foo('bar')
}
this.appendChild(el)

Правка 3, 4, 5: Немного поиграл (см. Решение 3) и назначил прокси-объект глобальной переменной ('me'), который проецирует любые вызовы функций на this пользовательский элемент - kinda __noSuchMethod__. Так... выбрав 1-или 2-символьную переменную, можно сэкономить по крайней мере несколько нажатий на клавиатуре, не набирая this., но me., vm. или даже i.... пока что это лучшее решение. Грустно! К сожалению, решение 3 также не может быть использовано в качестве общего шаблона для всех элементов, так как оно ограничивает контекст только одним (т. е. последним подключенным) пользовательским элементом.


[44]}правка 6-предварительные выводы

Как оказалось, привязать HTML-inline onclick-event (onclick="foo('bar')") к классу пользовательского элемента, в который встроен кликабельный элемент, не представляется возможным.

Таким образом, наиболее очевидными способами обратиться ко мне были бы: A) используйте ключевое слово this (например, onclick="this.foo('bar')") и свяжите его с классом, как показано выше. Почему this.foo() связывается, тогда как foo() Без this нет, остается неясным на данный момент. Интересно, что обе функции на самом деле не отличаются - у обеих уже есть привязки: this.foo() привязывается к кликабельному элементу, foo() привязывается к window.

B) используйте eval, как предлагает @Supersharp, то есть добавьте прослушиватель событий в пользовательский элемент, прослушайте клики, получите значение атрибута onclick в виде строки, добавьте "this."к струне и эффективно сделать это: eval("this." + "foo()").

C) в качестве альтернативы встроенному onclick я бы предпочел использовать делегирование событий и декларативные атрибуты html для указания поведения. Для этого снова добавьте прослушиватель событий click в Пользовательский класс элемента:

myElement.addEventListener('click', function(ev) {

        if (ev.target.dataset.action)
        {
            this[ev.target.dataset.action]()
        }

    }, false)

Ваш кликабельный элемент будет выглядеть как <div data-action="next">Show more</div>.

1 3

1 ответ:

Решение состоит в использовании eval() на конкатенации "this." и содержании атрибута onclick элемента event.target.

Не забудьте использовать event.stopPropagation(), чтобы остановить отправку события.

handleClick( ev )
{
    eval( 'this.' + ev.target.getAttribute( 'onclick' ) )

    ev.stopPropagation()
}

Если вы не хотите использовать eval(), Вы должны проанализировать содержимое ev.target.getAttribute( 'onclick' ), чтобы извлечь имя функции et аргументы, а затем вызвать метод элемента, если он существует:

if ( typeof this.name === 'function ) 
    this[name](arguments)

Обновить

Я предлагаю вам дать id пользовательскому элементу, а затем вызвать идентификатор + метод:

<my-element id="me">
    <div onclick="me.foo('bar')">click me</div>
</my-element>

Таким образом, вам не нужно перенаправлять/перехватывать событие, и div может быть в любом месте элемента.