javascript - attribute - Elemento de entrada personalizado en forma nativa
title html attribute (2)
Con los componentes web, uno de los elementos que la gente desea crear y reemplazar más es <input>
. Los elementos de entrada son malos porque son muchas cosas según su tipo y, por lo general, difíciles de personalizar, por lo que es normal que las personas siempre quieran modificar su apariencia y comportamiento.
Hace más o menos dos años, cuando escuché por primera vez sobre componentes web, estaba bastante emocionado y el primer tipo de elementos que me vinieron a la mente que quería crear eran elementos de entrada personalizados. Ahora que la especificación está terminada, parece que la necesidad que tenía para los elementos de entrada no está resuelta. Se suponía que Shadow DOM me permitía cambiar su estructura interna y apariencia, pero los elementos de entrada están en la lista negra y no pueden tener una raíz de la sombra porque ya tienen una oculta. Si quiero agregar lógica y comportamiento adicionales, los elementos personalizados e integrados con el atributo is
deberían hacer el truco; No puedo hacer la magia DOM oscura, pero al menos tengo esto, ¿verdad? Bueno, Safari no lo va a implementar, el polímero no los usará por esa razón, que huele a un estándar que pronto será desaprobado.
Así que me quedo con elementos personalizados normales; pueden usar el DOM de la sombra y tener la lógica que yo quiera, ¡pero quiero que sean entradas! deberían trabajar dentro de un <form>
, pero si estoy en lo cierto, a los elementos de formulario no les gustan. ¿Tengo que escribir mi propio elemento de formulario personalizado que replique todo lo que hace el nativo? ¿Tengo que despedirme de FormData
, la API de validación, etc.? ¿Pierdo la capacidad de tener un formulario con entradas que funcionan sin javascript?
Creo que la respuesta de @supersharp es la solución más práctica para este problema, pero también me responderé con una solución menos interesante. No utilice elementos personalizados para crear entradas personalizadas y quejarse de que la especificación esté defectuosa.
Otras cosas que hacer:
Suponiendo que el atributo is está muerto desde su nacimiento, creo que podemos lograr una funcionalidad similar con solo usar proxies. Aquí hay una idea que necesitaría un poco de refinamiento:
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()
Esto solo soluciona la parte de la creación de instancias de los elementos personalizados, para usarlos con las API del navegador, algunos métodos de piratería potencialmente feos en torno a los métodos como querySelector
, appendChild
deben hacerse para aceptar y devolver los elementos procesados y probablemente utilizar observadores de mutaciones y una inyección de dependencia. Sistema para crear automáticamente instancias de tus elementos.
En la queja sobre el lado de las especificaciones, todavía encuentro una opción válida para querer algo mejor. Para los mortales como yo, que no tienen el panorama completo, es un poco difícil hacer algo y pueden proponer ingenuamente y decir cosas como: ¡Hey! en lugar de tener elementos nativos, tengamos elementos personalizados ( <my-input is=''input''>
) para que podamos tener una raíz oculta y un comportamiento definido por el usuario en una entrada personalizada que funcione como nativa. Pero, por supuesto, apuesto a que muchas personas inteligentes que han trabajado en refinar esas especificaciones durante todos estos años han analizado todos los casos de uso y escenarios en los que algo diferente no funcionaría en esta red rota de la nuestra. Pero solo espero que se esfuercen más porque un caso de uso como este es algo que debería haberse resuelto con los componentes web de Holy Grail y me cuesta creer que no podamos hacerlo mejor.
Puede crear un elemento personalizado con el aspecto y el comportamiento que desee.
Coloque dentro un elemento <input>
oculto con el name
correcto (que se pasará a la <form>
).
Actualice su atributo de value
cada vez que se modifique el elemento personalizado "valor visible".
Publiqué un ejemplo en esta respuesta a una pregunta similar de 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>