Пользовательский элемент ввода в собственном виде
С веб-компонентами один из элементов, которые люди хотят создать и переопределить больше всего, <input>
, Входные элементы плохие, потому что они разные, в зависимости от их типа, и их сложно настроить, поэтому обычно люди всегда хотят изменить свой внешний вид и поведение.
Два года назад, более или менее, когда я впервые услышал о веб-компонентах, я был очень взволнован, и первый тип элементов, которые мне пришло в голову, - это пользовательские элементы ввода. Теперь, когда спецификация закончена, похоже, что потребность в элементах ввода не решена. Shadow DOM должен был позволить мне изменить их внутреннюю структуру и внешний вид, но входные элементы занесены в черный список и не могут иметь теневой корень, потому что у них уже есть скрытый. Если я хочу добавить дополнительную логику и поведение, пользовательские, встроенные элементы с is
атрибут должен сделать свое дело; Я не могу использовать магию теневого DOM, но, по крайней мере, у меня есть это, верно? Хорошо, что Safari не собирается внедрять его, полимер не будет их использовать по этой причине, которая пахнет стандартом, который скоро будет объявлен устаревшим.
Так что я остался с обычными пользовательскими элементами; они могут использовать теневой DOM и иметь любую логику, какую я хочу, но я хочу, чтобы они были входами! они должны работать внутри <form>
, но если я прав, элементам формы они не нравятся. Должен ли я написать свой собственный элемент формы, который копирует все то, что делает нативный? Должен ли я попрощаться с FormData
, валидация API и т. д.? Я теряю способность иметь форму с входными данными, которая работает без JavaScript?
2 ответа
Вы можете создать собственный элемент с желаемым внешним видом и поведением.
Положите в него скрытый <input>
элемент с правом name
(это будет передано <form>
).
Обновить его value
атрибут всякий раз, когда пользовательский элемент "видимое значение" изменяется.
Я разместил пример в этом ответе на аналогичный вопрос SO.
class CI extends HTMLElement
{
constructor ()
{
super()
var sh = this.attachShadow( { mode: 'open' } )
sh.appendChild( tpl.content.cloneNode( true ) )
}
connectedCallback ()
{
var view = this
var name = this.getAttribute( 'name' )
//proxy input elemnt
var input = document.createElement( 'input' )
input.name = name
input.value = this.getAttribute( 'value' )
input.id = 'realInput'
input.style = 'width:0;height:0;border:none;background:red'
input.tabIndex = -1
this.appendChild( input )
//content editable
var content = this.shadowRoot.querySelector( '#content' )
content.textContent = this.getAttribute( 'value' )
content.oninput = function ()
{
//console.warn( 'content editable changed to', content.textContent )
view.setAttribute( 'value', content.textContent)
}
//click on label
var label = document.querySelector( 'label[for="' + name + '"]' )
label.onclick = function () { content.focus() }
//autofill update
input.addEventListener( 'change', function ()
{
//console.warn( 'real input changed' )
view.setAttribute( 'value', this.value )
content.value = this.value
} )
this.connected = true
}
attributeChangedCallback ( name, old, value )
{
//console.info( 'attribute %s changed to %s', name, value )
if ( this.connected )
{
this.querySelector( '#realInput' ).value = value
this.shadowRoot.querySelector( '#content' ).textContent = value
}
}
}
CI.observedAttributes = [ "value" ]
customElements.define( 'custom-input', CI )
//Submit
function submitF ()
{
for( var i = 0 ; i < this.length ; i++ )
{
var input = this[i]
if ( input.name ) console.log( '%s=%s', input.name, input.value )
}
}
S1.onclick = function () { submitF.apply(form1) }
<form id=form1>
<table>
<tr><td><label for=name>Name</label> <td><input name=name id=name>
<tr><td><label for=address>Address</label> <td><input name=address id=address>
<tr><td><label for=city>City</label> <td><custom-input id=city name=city></custom-input>
<tr><td><label for=zip>Zip</label> <td><input name=zip id=zip>
<tr><td colspan=2><input id=S1 type=button value="Submit">
</table>
</form>
<hr>
<div>
<button onclick="document.querySelector('custom-input').setAttribute('value','Paris')">city => Paris</button>
</div>
<template id=tpl>
<style>
#content {
background: dodgerblue;
color: white;
min-width: 50px;
font-family: Courier New, Courier, monospace;
font-size: 1.3em;
font-weight: 600;
display: inline-block;
padding: 2px;
}
</style>
<div contenteditable id=content></div>
<slot></slot>
</template>
Я думаю, что ответ @ supersharp является наиболее практичным решением этой проблемы, но я также отвечу самому себе с помощью менее захватывающего решения. Не используйте пользовательские элементы для создания пользовательских входных данных и не жалуйтесь на недостатки спецификации.
Другие вещи, чтобы сделать:
Предполагая, что is
Атрибут мертв с самого рождения, я думаю, что мы можем достичь аналогичной функциональности, просто используя прокси. Вот идея, которая нуждается в уточнении:
class CrazyInput {
constructor(wowAnActualDependency) { ... }
doCrazyStuff() { ... }
}
const behavesLike = (elementName, constructor ) => new Proxy(...)
export default behavesLike('input', CrazyInput)
// use it later
import CrazyInput from '...'
const myCrazyInput = new CrazyInput( awesomeDependency )
myCrazyInput.value = 'whatever'
myCrazyInput.doCrazyStuff()
Это только решает часть создания экземпляров пользовательских элементов, чтобы использовать их с API-интерфейсами браузера, некоторые потенциально уродливые методы взлома, такие как querySelector
,appendChild
Необходимо принять и вернуть прокси-элементы и, возможно, использовать наблюдатели мутаций и систему внедрения зависимостей для автоматического создания экземпляров ваших элементов.
Что касается жалоб на спецификацию, я все еще нахожу правильный вариант хотеть чего-то лучшего. Для таких смертных, как я, у которых нет полной картины, немного сложно что-то сделать, и они могут наивно предлагать и говорить что-то вроде: эй! вместо того, чтобы иметь is
на родных элементах давайте на кастомных (<my-input is='input'>
), поэтому мы можем иметь теневой корень и пользовательское поведение для пользовательского ввода, который работает как собственный. Но, конечно, я держу пари, что у многих умных людей, которые все эти годы работали над уточнением этих спецификаций, есть хотя бы из всех вариантов использования и сценариев, где что-то другое не сработало бы в нашей сломанной сети. Но я просто надеюсь, что они будут стараться изо всех сил, потому что такой пример использования должен быть решен с помощью святого Грааля веб-компонентов, и мне трудно поверить, что мы не можем добиться большего успеха.