Попытка передать событие в пользовательский элемент HTML из одного из его компонентов - PullRequest
0 голосов
/ 10 января 2019

Я пытаюсь создать элемент управления, позволяющий пользователю устанавливать значение как с помощью числового ввода, так и с помощью ползунка. Прочитав соответствующие документы , вот с чем я пришел:

class NumberAndRange extends HTMLElement {
    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'})
		
        this.number = document.createElement('input')
        this.number.type = 'number'
        this.range = document.createElement('input')
        this.range.type = 'range'
		
        this.number.addEventListener('input',
            ()=>{this.range.value = this.number.value}
        )
        this.range.addEventListener('input',
            ()=>{this.number.value = this.range.value}
        )
		
        shadow.appendChild(this.number)
        shadow.appendChild(this.range)
    }
}
customElements.define('number-and-range', NumberAndRange)
<number-and-range></number-and-range>

Как вы можете видеть, это работает (по крайней мере, в браузерах, которые поддерживают это). Для краткости я пропустил код, который гарантирует, что установка атрибутов min, max, step и т. Д. На numeric-and-range также устанавливает их на input s, и что чтение этих атрибутов из numeric-and-range возвращает значимое результаты.

Однако есть одна функция, которую я не могу добавить. Ясно, что мы хотели бы, чтобы запуск события input на numeric и range также вызывал это событие на numeric-and-range; так что мы можем забыть, что numeric-and-range на самом деле состоит из двух входов и вместо этого рассматривать его, как если бы это был один элемент управления. Поэтому:

class NumberAndRange extends HTMLElement {
    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'})
		
        this.number = document.createElement('input')
        this.number.type = 'number'
        this.range = document.createElement('input')
        this.range.type = 'range'
		
        this.number.addEventListener('input',
            ()=>{this.range.value = this.number.value}
        )
        this.range.addEventListener('input',
            ()=>{this.number.value = this.range.value}
        )
    
        const whole = this
        this.number.addEventListener('input',
            (ev)=>{whole.dispatchEvent(ev)}
        )
        this.range.addEventListener('input',
            (ev)=>{whole.dispatchEvent(ev)}
        )
		
        shadow.appendChild(this.number)
        shadow.appendChild(this.range)
    }
}
customElements.define('number-and-range', NumberAndRange)
<number-and-range></number-and-range>

Но это не работает! По причинам, не зависящим от меня, движение слайдер бросает InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable Какой объект нельзя использовать? И почему бы его не использовать? Я смотрю на свой код и не могу найти ошибку.

Не могли бы вы просветить меня, что я делаю не так?

Редактировать: Идея, предложенная в комментариях, мне тоже не подходит:

class NumberAndRange extends HTMLElement {
    connectedCallback() {
        const whole = this
        this.number.addEventListener('input',
            (ev)=>{whole.dispatchEvent(ev)}
        )
        this.range.addEventListener('input',
            (ev)=>{whole.dispatchEvent(ev)}
        )
    }

    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'})
		
        this.number = document.createElement('input')
        this.number.type = 'numeric'
        this.range = document.createElement('input')
        this.range.type = 'range'
		
        this.number.addEventListener('input',
            ()=>{this.range.value = this.number.value}
        )
        this.range.addEventListener('input',
            ()=>{this.number.value = this.range.value}
        )
		
        shadow.appendChild(this.number)
        shadow.appendChild(this.range)
    }
}
customElements.define('number-and-range', NumberAndRange)
<number-and-range></number-and-range>

Ответы [ 2 ]

0 голосов
/ 11 января 2019

Вы уже нашли свою проблему в том, что события не могут быть отправлены повторно.

Но вам не нужно отправлять любое пользовательское событие, используйте oninput событие по умолчанию

http://jsfiddle.net/dannye/6zk4vgc0

class NumberAndRange extends HTMLElement {
  connectedCallback() {
    let input = ['number','range'].map(
      (type, idx) => 
      Object.assign(
        this.appendChild(document.createElement('input')),
        { 
          type, 
          min : 20,
          max : 50,
          value : 42,
          oninput : _ => input[~~!idx].value = input[idx].value
        }))
  }
}
customElements.define('number-and-range', NumberAndRange);

примечание: ~~!idx совпадает с idx == 0 ? 1 : 0, только на несколько байтов короче

0 голосов
/ 10 января 2019

Спасибо @Pavlo за указание на проблему: события не могут быть отправлены повторно. Поэтому решение состоит в том, чтобы создать новое событие вместо:

class NumberAndRange extends HTMLElement {
    // Handling of the value attribute should probably be done better
    // quick and dirty here
    get value() {return this.number.value}

    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'})
		
        this.number = document.createElement('input')
        this.number.type = 'number'
        this.range = document.createElement('input')
        this.range.type = 'range'
		
        this.number.addEventListener('input',
            ()=>{this.range.value = this.number.value}
        )
        this.range.addEventListener('input',
            ()=>{this.number.value = this.range.value}
        )
    
        const whole = this
        this.number.addEventListener('input',
            ()=>{whole.dispatchEvent(new Event('input'))}
        )
        this.range.addEventListener('input',
            ()=>{whole.dispatchEvent(new Event('input'))}
        )
		
        shadow.appendChild(this.number)
        shadow.appendChild(this.range)
    }
}
customElements.define('number-and-range', NumberAndRange)
<number-and-range oninput="console.log(this.value)"></number-and-range>
...