2009-02-28 10 views
196

Sé que xhtml no admite etiquetas de formulario anidado y ya he leído otras respuestas
aquí en stackoverflow con respecto a este tema, pero todavía no he encontrado una solución elegante al problema.¿Cómo se supera la limitación de anidamiento de formulario html?

Algunos dicen que no lo necesitas y que no pueden pensar en un escenario si fuera necesario. Bueno, personalmente no puedo pensar en un escenario que NO LO HAYA necesitado.

Veamos un ejemplo muy sencillo:

Usted está haciendo una aplicación de blog y que tiene un formulario con algunos campos de la creación de un nuevo puesto y una barra de herramientas con "acciones" como "Guardar", "Borrar", "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 la forma de una manera que no requiere JavaScript, formulario HTML simplemente vieja y botones de envío.

Dado que la url de acción se define en la etiqueta de formulario y no en cada botón de envío individual, nuestra única opción es publicar en una url genérica y luego iniciar "si ... entonces ... más" para determinar el nombre del botón que se envió. No es muy elegante, sino 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 de formulario en el servidor, aunque lo único necesario 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 el anidamiento de formularios fuera compatible, al menos podría envolver el botón de envío "Eliminar" dentro de su propio formulario con solo el campo de identificación posterior.

Puede decir "Simplemente implemente el" 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.

Así que mi pregunta (particularmente a aquellos que dicen que no han necesitado anidar el formulario) es ¿Qué HACES? ¿Hay alguna solución elegante que me falta o la conclusión es realmente "O requiere Javascript o enviar todo"?

+16

Es curioso, pero XHTML permite anidar de forma indirecta según una nota interesante http://anderwald.info/internet/nesting-form-tags-in-xhtml/ - (X) HTML no permite el anidamiento de formularios como "formulario> forma" , pero permite "form> fieldset> form", el validador W3 dice que es válido, pero los navegadores tienen errores con dicho anidamiento. – Nishi

+0

posible duplicado de [¿Es válido tener un formulario html dentro de otro formulario html?] (Http://stackoverflow.com/questions/555928/is-it-valid-to-have-a-html-form-inside- another-html-form) –

Respuesta

51

Implementaré 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 Javascript atada al evento onsubmit 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 actualizar la ubicación de la página con los datos que necesita pasar como una solicitud GET, o hacer una publicación de Ajax en el servidor, o ...).

De esta manera, las personas que no tienen Javascript pueden usar el formulario muy bien, pero la carga del servidor se compensa porque las personas que sí tienen Javascript solo envían la única información necesaria. Concentrarse solo en apoyar a uno u otro realmente limita sus opciones innecesariamente.

Alternativamente, si está trabajando detrás de un cortafuegos corporativo o algo así y todos tienen Javascript desactivado, es posible que desee hacer dos formas y trabajar algo de CSS mágico para que parezca que el botón eliminar es parte del primer formulario.

+13

el original de OP indica no javascript – Jason

+24

El problema con este método es que no es RESTful. Una guardar y eliminar corresponden a diferentes acciones sobre el recurso: "Guardar" -> Post/My_Resource (la creación de un nuevo recurso) "Guardar" -> PUT/My_Resource (modificación de un recurso existente) "Borrar" - > DELETE/my_resource (destruye el recurso) RESTfully hablando, el problema es que se espera que un POST cree un nuevo recurso. Ciertamente, nunca debería eliminar un recurso existente. – user132447

2

Una forma Me gustaría hacer esto sin javascript sería añadir un conjunto de botones de radio que definen la acción a realizar:

  • actualización
  • Eliminar
  • Cualquiera que sea

Luego, el script de acción tomaría diferentes acciones dependiendo del valor del botón de radio establecido.

Otra forma sería poner dos formularios en la página como sugirió, simplemente no anidados. La disposición puede ser difícil de controlar sin embargo:

 
<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="Save" /> 
</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> 

Usando el campo oculto "tarea" en la secuencia de comandos de manipulación en rama apropiada.

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 queden 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> 
+4

Sí y se siente demasiado hackish. Además, en una página muy grande (imagines pestañas y subpestañas y botones de anidación de presentación 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

, siempre es un equilibrio entre lo que quiere hacer y lo que puede hacer.parece que esas son las opciones, ya sea una forma manejada en el servidor, o una forma 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 envíe todo el formulario al servidor y luego use javascript discretamente para los clientes que lo soportan para interceptar la submición de formulario y solo enviar lo que realmente se necesita. Ese es el mejor compromiso. – gCoder

-1

Si realmente no desea utilizar formularios múltiples (como Jason sugests), utilice los botones y los manejadores 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 (utilizo jQuery aquí para easyness) (a pesar de que es bastante excesivo para la adición de algunos manipuladores onclick)

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

También sé, esto hará que la mitad de la página no trabajar sin javascript

+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 para la acción de eliminación (incorrecta). De hecho, es un poco más burdo, ya que requiere JavaScript para lograr algo que una antigua etiqueta de enlace simple podría hacer. –

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 alinee con los otros botones para hacer que la interfaz se vea como una forma unificada, incluso si no lo es.

O, por supuesto, rediseñe su interfaz, y permita que las personas eliminen en otro lugar completamente lo que no requiere que vean el enorme formulario en absoluto.

13

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

+1

¿Alguna referencia sobre este enlace valioso? – dakab

+0

Ver https://www.w3.org/TR/html5/forms.html#form-owner "Un elemento asociado al formulario está asociado, de forma predeterminada, con su elemento ancestral más cercano, pero, si es reasociable, puede tener un atributo de formulario especificado para anular esto ". – Nishi

-2

Alternativamente podría asignar la forma actiob sobre la marcha ... podría no ser la mejor solución, pero seguro exime 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> 
+3

-1, no se debe alentar a los controladores de eventos en línea. – StackOverflowNewbie

+0

¿Por qué? Lo pregunto seriamente, porque no hay una manera fácil de hacer clic en el botón. ¿Estás diciendo que se debe usar algo como el método de jQuery's click() para configurar el manejador onclick del botón? ¿O que el problema es manejar el evento click antes de enviar el formulario? –

1

utilizar un iframe de forma anidada . Si necesitan compartir campos, entonces ... realmente no está anidado.

+1

Usar un iframe es una gran idea, es una lástima que en la mayoría de los casos necesite javascript para cambiar el tamaño (ya que no puede saber cuál será el tamaño del texto del usuario, por lo tanto, qué tan grande será). –

+0

@Nicholas, ¿cómo puedes usar Javascript para cambiar el tamaño? El HTML iframed no puede hablar con el iframe, y viceversa, a menos que estén en el mismo dominio. –

+0

He proporcionado el código en una segunda respuesta. –

-1

En respuesta a una pregunta publicada por Yar en un comentario a su propia respuesta, presento algunos JavaScript que cambiarán el tamaño de 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. Va a tener 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 lo llaman así:

<iframe ... frameborder="0" onload="if(window.parent && window.parent.resizeIFrame){window.parent.resizeIFrame(this);}"></iframe> 
172

Sé que esto es una cuestión de edad, pero HTML5 ofrece un par de nuevas opciones.

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

<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 menciona que puede ser necesario realizar diferentes acciones con un solo formulario. HTML5 viene al rescate, de nuevo. Puede usar el atributo formaction en los botones de envío, por lo que diferentes botones en el mismo formulario pueden enviarse 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 lo hacen todo esto de forma declarativa sin JavaScript. La desventaja es que no son compatibles con los navegadores más antiguos, por lo que tendrías que hacer algunos polifilling para navegadores más antiguos.

+4

+1 - pero sería maravilloso saber qué soporte de navegador para 'form =' es - CanIUse realmente no dice. http://www.caniuse.com/#feat=forms – AKX

+1

Bien, esto es excelente, pero a día de hoy, una característica que no es compatible con IE todavía lo descalifica para el uso de 'producción' – Jako

+3

Es una mala práctica usar '' para declarar botones. el elemento '

-1

Me acaba de ocurrir 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> 
0

Bueno, si envía un formulario, el navegador también envía un nombre y un valor de envío de entrada. Así que lo que yo puedo 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> 

así sucesivamente lado del servidor que acaba de ver para el parámetro que se inicia cadena de ancho de la "acción" y la parte restante le indica qué acción tomar

por lo que cuando se hace clic en botón Guardar navegador que envía algo así como foo = asd & acción: save = Guardar

11

tipo de viejo tema, pero éste podría ser útil para alguien:

como alguien ha mencionado anteriormente - se puede utilizar la forma ficticia. Tuve que superar este problema hace algún tiempo. Al principio me olvidé por completo de esta restricción HTML y simplemente agregué los formularios anidados. El resultado fue interesante: perdí mi primer formulario del anidado. Luego resultó ser una especie de "truco" simplemente agregar un formulario falso (que se eliminará del navegador) antes de los formularios anidados reales.

En mi caso es 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. El $ _REQUEST global está vacío, independientemente de las entradas. Las formas internas parecen funcionar bien en todas partes.

No he probado otra sugerencia que se describe aquí - fieldset alrededor de formularios anidados.

EDIT: Frameset no funcionó! Simplemente agregué formulario principal después de los demás (no más formularios anidados) y usé el "clon" de jQuery para duplicar 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 como un amuleto.

+0

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 utilizar para el complemento jQuery Validation (https://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 porque no podía encontrar el formulario interno, ya que Chrome lo eliminó del bloque de html agregado usando jQuery.html(). Al agregar una pequeña forma ficticia primero, Chrome abandonó la real. –

+0

Esto funcionó para mí. Gracias por este bonito hack –

+0

Esto parece no funcionar más. Firefox eliminó la segunda etiqueta

y luego cerró mi formulario con la primera etiqueta
. Esto dejó una etiqueta no válida en la parte inferior de la página y un formulario que no se enviará correctamente.:( – Tarquin

1

Mi solución es tener los botones llaman funciones JS, que escriben y luego se someten formas encuentre fuera de la principal forma

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

di la vuelta a la cuestión mediante la inclusión de una casilla de verificación en función de qué forma la persona que quería hacer. Luego usó 1 botón para enviar todo el formulario.

+1

Bienvenido a [so]. Es mejor describir cómo esto resuelve el problema, en lugar de solo decir lo que hizo. Consulte [respuesta] para obtener sugerencias sobre cómo crear respuestas excelentes en Stack Overflow. ¡Gracias! –

0

Esta discusión todavía me interesa. 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 próximos años), lo entiendo.

Sin forzar el marco (todas las organizaciones van a querer asegurarse de la compatibilidad/solidez, y no estoy usando esta discusión como justificación para el marco), las soluciones de Drupal 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 utiliza su amplia funciones de form.inc para encontrar el elemento desencadenante (sí, ese es el nombre en el código). Consulte la parte inferior del código que figura en la página de 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 volver a 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 'eliminar' datos en URL/formularios separados, probablemente por las razones mencionadas en la publicación original. Esto necesita algún tipo de paso de búsqueda/listado (gemido ¡otra forma !, pero es fácil de usar) como una cuestión preliminar. Pero esto tiene la ventaja de eliminar el problema de "enviar todo". La gran forma con los datos se usa para su propósito, creación/actualización de datos (o incluso una acción de 'fusión').

En otras palabras, una forma de solucionar el problema es convertir el formulario en dos, luego el problema desaparece (y los métodos HTML también se pueden corregir mediante un POST).

Cuestiones relacionadas