¿Cómo se supera la limitación de anidamiento de formularios HTML?

199

Sé que XHTML no admite etiquetas de formulario anidadas y ya he leído otras respuestas aquí en Stack Overflow con respecto a este tema, pero aún no he encontrado una solución elegante para el problema.

Algunos dicen que no lo necesita y que no pueden pensar en un escenario en el que esto sería necesario. Bueno, personalmente no puedo pensar en un escenario en el que no lo haya necesitado.

Veamos un ejemplo muy simple:

Está creando una aplicación de blog y tiene un formulario con algunos campos para crear una nueva publicación y una barra de herramientas con "acciones" como "Guardar", "Eliminar", "Cancelar".

<form
 action="/post/dispatch/too_bad_the_action_url_is_in_the_form_tag_even_though_conceptually_every_submit_button_inside_it_may_need_to_post_to_a_diffent_distinct_url"
method="post">

    <input type="text" name="foo" /> <!-- several of those here -->
    <div id="toolbar">
        <input type="submit" name="save" value="Save" />
        <input type="submit" name="delete" value="Delete" />
        <a href="/home/index">Cancel</a>
    </div>
</form>

Nuestro objetivo es escribir el formulario de una manera que no requiera JavaScript , simplemente un formulario HTML antiguo y botones de envío.

Dado que la URL de acción se define en la etiqueta del formulario y no en cada botón de envío individual, nuestra única opción es publicar en una URL genérica y luego comenzar "if ... then ... else" para determinar el nombre del botón que fué enviado. No es muy elegante, pero es nuestra única opción, ya que no queremos confiar en JavaScript.

El único problema es que al presionar "Eliminar", se enviarán TODOS los campos del formulario en el servidor, aunque lo único que se necesita para esta acción es una entrada oculta con la identificación posterior. No es gran cosa en este pequeño ejemplo, pero tengo formularios con cientos (por así decirlo) de campos y pestañas en mis aplicaciones LOB que (debido a los requisitos) tienen que enviar todo de una vez y, en cualquier caso, esto parece muy ineficiente y un desperdicio. Si se admitiera la anidación de formularios, al menos sería capaz de ajustar el botón de envío "Eliminar" dentro de su propio formulario con solo el campo post-id.

Puede decir "Simplemente implemente" Eliminar "como un enlace en lugar de enviar". Esto sería incorrecto en muchos niveles, pero lo más importante es que las acciones de efectos secundarios como "Eliminar" aquí, nunca deberían ser una solicitud GET.

Entonces mi pregunta (particularmente para aquellos que dicen que no han necesitado anidar) es ¿Qué haces TÚ? ¿Hay alguna solución elegante que me falta o la conclusión es realmente "¿Requiere JavaScript o enviar todo"?

gCoder
fuente
17
Es divertido, pero XHTML sí permite el anidamiento indirecto de formularios de acuerdo con una nota interesante anderwald.info/internet/nesting-form-tags-in-xhtml - (X) HTML no permite anidar formularios como "form> form", pero permite "form> fieldset> formulario ", el validador W3 dice que es válido, pero los navegadores tienen errores con este tipo de anidamiento.
Nishi
linkrot arreglado para el enlace de comentarios de @Nishi: web.archive.org/web/20170420110433/http://anderwald.info/…
albert

Respuestas:

53

Implementaría esto exactamente como lo describió: envíe todo al servidor y haga un simple if / else para verificar en qué botón se hizo clic.

Y luego implementaría una llamada de Javascript vinculada al evento de envío del formulario que verificaría antes de enviar el formulario, y solo enviaría los datos relevantes al servidor (posiblemente a través de un segundo formulario en la página con la ID necesaria para procesar la cosa como una entrada oculta, o actualice la ubicación de la página con los datos que necesita pasar como una solicitud GET, o haga una publicación de Ajax en el servidor, o ...).

De esta manera, las personas que no tienen Javascript pueden usar el formulario sin problemas, pero la carga del servidor se compensa porque las personas que tienen Javascript solo envían los datos necesarios. Centrarse en solo admitir uno u otro realmente limita sus opciones innecesariamente.

Alternativamente, si está trabajando detrás de un firewall corporativo o algo y todo el mundo tiene Javascript deshabilitado, es posible que desee hacer dos formularios y hacer algo de magia CSS para que parezca que el botón Eliminar es parte del primer formulario.

Un crayón
fuente
15
el OP original no declaró javascript
Jason
26
El problema con este método es que no es RESTful. Un guardar y eliminar corresponden a diferentes acciones en el recurso: "Guardar" -> POST / my_resource (crear un nuevo recurso) "Guardar" -> PUT / my_resource (modificar un recurso existente) "Eliminar" -> ELIMINAR / my_resource (destruir el recurso) RESTANTE hablando, el problema es que se espera que un POST cree un nuevo recurso. Ciertamente, nunca debe eliminar un recurso existente.
user132447
178

Sé que esta es una pregunta antigua, pero HTML5 ofrece un par de opciones nuevas.

El primero es separar el formulario de la barra de herramientas en el marcado, agregar otro formulario para la acción de eliminar y asociar los botones en la barra de herramientas con sus respectivos formularios utilizando el formatributo.

<form id="saveForm" action="/post/dispatch/save" method="post">
    <input type="text" name="foo" /> <!-- several of those here -->  
</form>
<form id="deleteForm" action="/post/dispatch/delete" method="post">
    <input type="hidden" value="some_id" />
</form>
<div id="toolbar">
    <input type="submit" name="save" value="Save" form="saveForm" />
    <input type="submit" name="delete" value="Delete" form="deleteForm" />
    <a href="/home/index">Cancel</a>
</div>

Esta opción es bastante flexible, pero la publicación original también mencionó que puede ser necesario realizar diferentes acciones con un solo formulario. HTML5 viene al rescate, de nuevo. Puede usar el formactionatributo en los botones de envío, por lo que diferentes botones en el mismo formulario pueden enviar a diferentes URL. Este ejemplo solo agrega un método de clonación a la barra de herramientas fuera del formulario, pero funcionaría igual anidado en el formulario.

<div id="toolbar">
    <input type="submit" name="clone" value="Clone" form="saveForm"
           formaction="/post/dispatch/clone" />
</div>

http://www.whatwg.org/specs/web-apps/current-work/#attributes-for-form-submission

La ventaja de estas nuevas características es que hacen todo esto de manera declarativa sin JavaScript. La desventaja es que no son compatibles con navegadores más antiguos, por lo que tendría que realizar algunos rellenos polifónicos para navegadores más antiguos.

shovavnik
fuente
44
+1 - pero sería maravilloso saber para qué es el soporte del navegador form=- CanIUse realmente no dice. caniuse.com/#feat=forms
AKX
1
Correcto, esto es excelente, pero a partir de hoy una característica no admitida por IE todavía lo descalifica para el uso de 'producción'
Jako
3
Es una mala práctica usar <input>para declarar botones. El <button>elemento se introdujo hace 15 años para este propósito. Realmente personas.
Nicholas Shanks
27
Su punto es innecesariamente polémico e irrelevante para la pregunta del OP. El OP preguntó sobre la limitación de anidamiento de formularios sin usar JavaScript y la respuesta ofrece una solución. Si usa elementos <input>o <button>es irrelevante, aunque los botones suelen ser más apropiados para las barras de herramientas, como usted dice. Por cierto, los elementos de los botones no han sido compatibles con todos los navegadores durante 15 años.
shovavnik
66
@AKX Aunque es una declaración anterior, podrá ver el soporte para esta función aquí: html5test.com/compare/feature/form-association-form.html . IE es una cancelación, la mayoría de los otros navegadores de escritorio parecen tolerables.
graso
16

¿Puede tener los formularios uno al lado del otro en la página, pero no anidados? luego usa CSS para hacer que todos los botones estén alineados

<form method="post" action="delete_processing_page">
   <input type="hidden" name="id" value="foo" />
   <input type="submit" value="delete" class="css_makes_me_pretty" />
</form>

<form method="post" action="add_edit_processing_page">
   <input type="text" name="foo1"  />
   <input type="text" name="foo2"  />
   <input type="text" name="foo3"  />
   ...
   <input type="submit" value="post/edit" class="css_makes_me_pretty" />
</form>
Jason
fuente
55
Sí, y se siente demasiado hackear. Además, en una página muy grande (imagine pestañas y subpestañas y botones de envío de anidamiento en varios niveles de alcance) es casi imposible separar por completo la posición de la pantalla de los elementos de su posición en el documento.
gCoder
3
bueno, siempre es un equilibrio entre lo que quieres hacer y lo que puedes hacer. parece que esas son las opciones: ya sea un formulario manejado en el servidor o uno múltiple que se vuelve difícil de mantener, elija sabiamente :)
Jason
3
Sí, desafortunadamente con todas esas limitaciones en html, me limitaré a lo que es compatible y enviaré todo el formulario al servidor y luego usaré JavaScript discretamente para que los clientes que lo admiten intercepten el formulario y solo envíen lo que realmente se necesita. Ese es el mejor compromiso.
gCoder
13

HTML5 tiene una idea de "propietario del formulario": el atributo "formulario" para los elementos de entrada. Permite emular formas anidadas y resolverá el problema.

Nishi
fuente
1
¿Alguna referencia sobre esto vale la pena vincular?
dakab
Consulte w3.org/TR/html5/forms.html#form-owner ". Un elemento asociado a un formulario está, por defecto, asociado con su elemento de formulario ancestro más cercano, pero, si es reasociable, puede tener un atributo de formulario especificado para anular esta."
Nishi
11

Tipo de tema antiguo, pero este podría ser útil para alguien:

Como alguien mencionó anteriormente, puede usar la forma ficticia. Tuve que superar este problema hace algún tiempo. Al principio, me olvidé por completo de esta restricción de HTML y acabo de agregar los formularios anidados. El resultado fue interesante: perdí mi primera forma de la anidada. Luego resultó ser una especie de "truco" simplemente agregar un formulario ficticio (que se eliminará del navegador) antes de los formularios anidados reales.

En mi caso se ve así:

<form id="Main">
  <form></form> <!--this is the dummy one-->
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  ......
</form>

Funciona bien con Chrome, Firefox y Safari. IE hasta 9 (no estoy seguro acerca de 10) y Opera no detecta parámetros en la forma principal. $ _REQUEST global está vacío, independientemente de las entradas. Las formas internas parecen funcionar bien en todas partes.

No he probado otra sugerencia descrita aquí: conjunto de campos alrededor de formularios anidados.

EDITAR : Frameset no funcionó! Simplemente agregué el formulario Principal después de los otros (no más formularios anidados) y usé el "clon" de jQuery para duplicar las entradas en el formulario al hacer clic en el botón. Se agregó .hide () a cada una de las entradas clonadas para mantener el diseño sin cambios y ahora funciona de maravilla.

BDJoe
fuente
Esto funcionó perfectamente para mí. Estaba trabajando en una página asp.net, que tenía un formulario completo. Tenía una forma anidada interna para usar con el complemento jQuery Validation ( github.com/jzaefferer/jquery-validation ), y aunque funcionó perfectamente en FireFox e IE, falló en Chrome con 'TypeError: No se puede llamar al método' elemento 'de indefinido ". Resultó que la llamada de inicialización validate () estaba fallando ya que no podía encontrar la forma interna, ya que Chrome la eliminó del bloque de html agregado usando jQuery.html (). Agregar una pequeña forma ficticia primero provocó que Chrome dejara la real.
John - No es un número
Esto ya no parece funcionar. Firefox eliminó la segunda etiqueta <form> y luego finalizó mi formulario con la primera etiqueta </form>. Esto dejó una etiqueta no válida </form> en la parte inferior de la página y un formulario que no se enviará correctamente. :(
Tarquin
Muy útil para mi. Estoy agregando un widget web que contiene el formulario en una página asp.net. Sorprendido por WebForm de que siempre genera un formulario que encierra todo en el cuerpo HTML. El formulario adjunto se convierte en un dolor de cabeza para el widget web. La forma ficticia parece ser una solución rápida y efectiva para esta situación. Funciona perfectamente en Chrome y Safari hasta ahora, aún no he probado Firefox. Gracias por compartir, me ahorró mucho tiempo!
JM Yang
4

Creo que Jason tiene razón. Si su acción "Eliminar" es mínima, haga que esté en un formulario por sí mismo y alinéelo con los otros botones para que la interfaz se vea como un formulario unificado, incluso si no lo es.

O, por supuesto, rediseñe su interfaz y permita que las personas eliminen por completo cualquier otro lugar que no requiera que vean la enorme forma.

AmbroseChapel
fuente
2

Una forma de hacerlo sin javascript sería agregar un conjunto de botones de radio que definan la acción a tomar:

  • Actualizar
  • Eliminar
  • Lo que sea

Luego, el script de acción tomaría diferentes acciones dependiendo del valor del conjunto de botones de opción.

Otra forma sería poner dos formularios en la página como sugirió, simplemente no anidados. Sin embargo, el diseño puede ser difícil de controlar:

<form name = "editform" action = "the_action_url" method = "post">
   <input type = "hidden" name = "task" value = "update" />
   <input type = "text" name = "foo" />
   <input type = "submit" name = "save" value = "Guardar" />
</form>

<form name = "delform" action = "the_action_url" method = "post">
   <input type = "hidden" name = "task" value = "delete" />
   <input type = "hidden" name = "post_id" value = "5" />
   <input type = "submit" name = "delete" value = "Delete" />
</form>

Usar el campo "tarea" oculto en el script de manejo para bifurcarse adecuadamente.

Ron salvaje
fuente
1

Esta discusión sigue siendo de mi interés. Detrás de la publicación original hay "requisitos" que el OP parece compartir, es decir, un formulario con compatibilidad con versiones anteriores. Como alguien cuyo trabajo al momento de escribir a veces debe ser compatible con IE6 (y en los años venideros), lo entiendo.

Sin impulsar el marco (todas las organizaciones van a querer tranquilizarse sobre la compatibilidad / robustez, y no estoy usando esta discusión como justificación para el marco), las soluciones de Drupal para este tema son interesantes. Drupal también es directamente relevante porque el marco ha tenido una política a largo plazo de "debería funcionar sin Javascript (solo si lo desea)", es decir, el problema del OP.

Drupal usa sus funciones form.inc bastante extensas para encontrar el triggering_element (sí, ese es el nombre en el código). Consulte la parte inferior del código que figura en la página API para form_builder (si desea profundizar en los detalles, se recomienda la fuente: drupal-x.xx / includes / form.inc ). El constructor utiliza la generación automática de atributos HTML y, a través de eso, puede detectar qué botón se presionó y actuar en consecuencia (estos pueden configurarse para ejecutar procesos separados también).

Más allá del generador de formularios, Drupal divide las acciones de 'eliminación' de datos en urls / formularios separados, probablemente por las razones mencionadas en la publicación original. Esto necesita algún tipo de paso de búsqueda / listado (¡ gruñe otra forma !, pero es fácil de usar) como preliminar. Pero esto tiene la ventaja de eliminar el problema de "enviar todo". La gran forma con los datos se usa para el propósito previsto, la creación / actualización de datos (o incluso una acción de 'fusión').

En otras palabras, una forma de solucionar el problema es delegar el formulario en dos, luego el problema desaparece (y los métodos HTML también pueden corregirse a través de un POST).

Rob Crowther
fuente
0

Use un iframepara la forma anidada. Si necesitan compartir campos, entonces ... no está realmente anidado.

Dan Rosenstark
fuente
1
Usar un iframe es una gran idea, es una pena que en la mayoría de los casos necesite JavaScript para cambiar su tamaño (ya que no puede saber cuál será el tamaño del texto del usuario, por lo tanto, qué tan grande será el botón).
Nicholas Shanks
@Nicholas, ¿cómo puedes usar Javascript para cambiar su tamaño? El HTML iframed no puede hablar con el iframe, y viceversa, a menos que estén en el mismo dominio.
Dan Rosenstark
@Nicholas, gracias, solo quería asegurarme de que tienen que estar en el mismo dominio
Dan Rosenstark
0

Mi solución es hacer que los botones llamen a funciones JS que escriben y luego envían formularios fuera del formulario principal

<head>
<script>
function removeMe(A, B){
        document.write('<form name="removeForm" method="POST" action="Delete.php">');
        document.write('<input type="hidden" name="customerID" value="' + A + '">');
        document.write('<input type="hidden" name="productID" value="' + B + '">');
        document.write('</form>');
        document.removeForm.submit();
}
function insertMe(A, B){
        document.write('<form name="insertForm" method="POST" action="Insert.php">');
        document.write('<input type="hidden" name="customerID" value="' + A + '">');
        document.write('<input type="hidden" name="productID" value="' + B + '">');
        document.write('</form>');
        document.insertForm.submit();
}
</script>
</head>

<body>
<form method="POST" action="main_form_purpose_page.php">

<input type="button" name="remove" Value="Remove" onclick="removeMe('$customerID','$productID')">
<input type="button" name="insert" Value="Insert" onclick="insertMe('$customerID','$productID')">

<input type="submit" name="submit" value="Submit">
</form>
</body>
telaraña
fuente
-1

Si realmente no desea utilizar varios formularios (como sugiere Jason), utilice botones y controladores onclick.

<form id='form' name='form' action='path/to/add/edit/blog' method='post'>
    <textarea name='message' id='message'>Blog message here</textarea>
    <input type='submit' id='save' value='Save'>
</form>
<button id='delete'>Delete</button>
<button id='cancel'>Cancel</button>

Y luego en javascript (uso jQuery aquí para facilitarlo) (aunque es demasiado exagerado para agregar algunos controladores onclick)

$('#delete').click( function() {
   document.location = 'path/to/delete/post/id'; 
});
$('#cancel').click( function() {
   document.location = '/home/index';
});

También lo sé, esto hará que la mitad de la página no funcione sin JavaScript.

Pim Jager
fuente
1
A menos que haya pasado por alto algo, esto no es mejor que vincular a la acción de eliminación: el resultado final será una solicitud GET a la acción de eliminación (incorrecto). De hecho, es un poco más asqueroso, ya que requiere JavaScript para lograr algo que una simple etiqueta de enlace antigua podría hacer.
Kyle Fox
-1

En respuesta a una pregunta publicada por Yar en un comentario a su propia respuesta, presento algunos JavaScript que redimensionarán un iframe. Para el caso de un botón de formulario, es seguro asumir que el iframe estará en el mismo dominio. Este es el código que uso. Tendrá que alterar las matemáticas / constantes para su propio sitio:

function resizeIFrame(frame)
{
    try {
        innerDoc = ('contentDocument' in frame) ? frame.contentDocument : frame.contentWindow.document;
        if('style' in frame) {
            frame.style.width = Math.min(755, Math.ceil(innerDoc.body.scrollWidth)) + 'px';
            frame.style.height = Math.ceil(innerDoc.body.scrollHeight) + 'px';
        } else {
            frame.width = Math.ceil(innerDoc.body.scrollWidth);
            frame.height = Math.ceil(innerDoc.body.scrollHeight);
        }
    } catch(err) {
        window.status = err.message;
    }
}

Entonces llámalo así:

<iframe ... frameborder="0" onload="if(window.parent && window.parent.resizeIFrame){window.parent.resizeIFrame(this);}"></iframe>
Nicholas Shanks
fuente
-1

Se me ocurrió una buena manera de hacerlo con jquery.

<form name="mainform">    
    <div id="placeholder">
    <div>
</form>

<form id="nested_form" style="position:absolute">
</form>

<script>
   $(document).ready(function(){
      pos = $('#placeholder').position();
      $('#nested_form')
         .css('left', pos.left.toFixed(0)+'px')
         .css('top', pos.top.toFixed(0)+'px');
   });
</script>
mordy
fuente
-1

Bueno, si envía un formulario, el navegador también envía un nombre y un valor de envío de entrada. Entonces, lo que puedes hacer es

<form   
 action="/post/dispatch/too_bad_the_action_url_is_in_the_form_tag_even_though_conceptually_every_submit_button_inside_it_may_need_to_post_to_a_diffent_distinct_url"  
method="post">  

    <input type="text" name="foo" /> <!-- several of those here -->  
    <div id="toolbar">
        <input type="submit" name="action:save" value="Save" />
        <input type="submit" name="action:delete" value="Delete" />
        <input type="submit" name="action:cancel" value="Cancel" />
    </div>
</form>

entonces, en el lado del servidor, solo busca el parámetro que comienza con la cadena de ancho "acción:" y el resto le dice qué acción tomar

así que cuando haces clic en el botón Guardar, el navegador te envía algo como foo = asd & action: save = Save

Lauri Lüüs
fuente
-1

Abordé el problema al incluir una casilla de verificación dependiendo de la forma que la persona quería hacer. Luego usé 1 botón para enviar el formulario completo.

Gregws
fuente
2
Bienvenido a Stack Overflow . Es mejor describir cómo esto resuelve el problema, en lugar de solo decir lo que hiciste. Consulte Cómo responder para obtener consejos sobre cómo crear excelentes respuestas en Stack Overflow. ¡Gracias!
Qantas 94 Heavy
-2

Alternativamente, podría asignar el formulario actiob sobre la marcha ... podría no ser la mejor solución, pero seguro alivia la lógica del lado del servidor ...

<form name="frm" method="post">  
    <input type="submit" value="One" onclick="javascript:this.form.action='1.htm'" />  
    <input type="submit" value="Two" onclick="javascript:this.form.action='2.htm'" />  
</form>
Qiniso
fuente
¿Por qué? Lo pregunto seriamente, porque no hay una manera fácil de saber en qué botón se hizo clic. ¿Está diciendo que algo como el método click () de jQuery debería usarse para configurar el controlador onclick del botón? ¿O que manejar el evento de clic antes de enviar el formulario es el problema?
Zack Morris el