web-dev-qa-db-fra.com

Comment surmontez-vous la limitation d'imbrication de formulaire HTML?

Je sais que XHTML ne prend pas en charge les balises de formulaire imbriquées et j'ai déjà lu d'autres réponses ici sur Stack Overflow concernant ce sujet, mais je n'ai toujours pas trouvé de solution élégante à ce problème.

Certains disent que vous n'en avez pas besoin et qu'ils ne peuvent pas penser à un scénario où cela serait nécessaire. Personnellement, je ne peux pas penser à un scénario que je n'ai pas en avait besoin.

Voyons un exemple très simple:

Vous créez une application de blog et vous avez un formulaire avec des champs pour créer un nouveau message et une barre d'outils avec des "actions" comme "Enregistrer", "Supprimer", "Annuler".

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

Notre objectif est d'écrire le formulaire de manière à ce que ne nécessite pas JavaScript , mais tout simplement l'ancien formulaire HTML et les boutons d'envoi.

Étant donné que l’URL d’action est définie dans la balise Form et non dans chaque bouton de soumission individuel, notre seule option est d’envoyer une URL générique, puis de commencer "si ... alors ... autre" pour déterminer le nom du bouton a été soumis. Pas très élégant, mais notre seul choix, car nous ne voulons pas nous fier à JavaScript.

Le seul problème est que si vous appuyez sur "Supprimer", tous les champs de formulaire du serveur seront soumis, même si la seule chose nécessaire à cette action est une entrée masquée avec le post-id. Pas très grave dans ce petit exemple, mais j'ai des formulaires avec des centaines (pour ainsi dire) de champs et de tabulations dans mes (~ ~ ~] lob [~ # ~] applications (en raison d'exigences ) doivent tout soumettre en une fois et, dans tous les cas, cela semble très inefficace et un gaspillage. Si l'imbrication de formulaire était prise en charge, je pourrais au moins envelopper le bouton "Supprimer" dans son propre formulaire avec uniquement le champ post-id.

Vous pouvez dire "Il suffit d'implémenter le" Supprimer "en tant que lien au lieu de le soumettre". Cela serait faux à de nombreux niveaux, mais surtout parce que les actions à effets secondaires telles que "Supprimer" ici, ne devraient jamais être une demande GET.

Donc, ma question (en particulier à ceux qui disent ne pas avoir besoin d'imbrication de formulaire) est: que faites-vous? Y a-t-il une solution élégante qui me manque ou le résultat final est-il vraiment "Soit requérir JavaScript ou tout soumettre"?

196
gCoder

Je mettrais cela en œuvre exactement comme vous l'avez décrit: soumettez tout au serveur et faites un simple si/sinon pour vérifier quel bouton a été cliqué.

Et puis, je mettrais en œuvre un appel Javascript lié à l'événement onsubmit du formulaire, qui vérifierait avant que le formulaire ne soit envoyé, et ne soumettrait que les données pertinentes au serveur (éventuellement via un deuxième formulaire sur la page avec l'ID nécessaire pour traiter l'élément comme une entrée masquée, ou actualisez l'emplacement de la page avec les données dont vous avez besoin transmises sous forme de requête GET, ou effectuez une publication Ajax sur le serveur, ou ...).

De cette façon, les personnes sans Javascript peuvent utiliser le formulaire, mais la charge du serveur est compensée car les personnes qui ont Javascript ne soumettent que la seule donnée nécessaire. Se concentrer sur le soutien de l’un ou de l’autre limite inutilement vos options.

Sinon, si vous travaillez derrière un pare-feu d'entreprise ou si tout le monde a Javascript désactivé, vous voudrez peut-être créer deux formulaires et utiliser la magie CSS pour que le bouton Supprimer fasse partie du premier formulaire.

53
One Crayon

Je sais que la question est ancienne, mais HTML5 offre quelques nouvelles options.

La première consiste à séparer le formulaire de la barre d'outils du balisage, à ajouter un autre formulaire pour l'action de suppression et à associer les boutons de la barre d'outils à leurs formulaires respectifs à l'aide de l'attribut 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>

Cette option est assez flexible, mais le message d'origine a également indiqué qu'il pourrait être nécessaire d'effectuer différentes actions avec un seul formulaire. HTML5 vient à la rescousse, encore. Vous pouvez utiliser l'attribut formaction sur les boutons d'envoi pour que différents boutons du même formulaire puissent être soumis à différentes URL. Cet exemple ajoute simplement une méthode de clonage à la barre d'outils en dehors du formulaire, mais elle fonctionne de la même manière que le formulaire.

<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

L'avantage de ces nouvelles fonctionnalités est qu'elles font tout cela de manière déclarative sans JavaScript. L'inconvénient est qu'ils ne sont pas pris en charge par les anciens navigateurs. Vous devrez donc effectuer un certain remplissage multiple pour les anciens navigateurs.

175
shovavnik

pouvez-vous avoir les formulaires côte à côte sur la page, mais pas imbriqués. puis utilisez CSS pour que tous les boutons soient bien alignés?

<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>
16
Jason

HTML5 a une idée de "propriétaire du formulaire" - l'attribut "formulaire" des éléments d'entrée. Cela permet d'émuler des formes imbriquées et résoudra le problème.

13
Nishi

Un peu vieux sujet, mais celui-ci pourrait être utile pour quelqu'un:

Comme mentionné ci-dessus, vous pouvez utiliser un formulaire factice. J'ai dû surmonter ce problème il y a quelque temps. Au début, j'ai totalement oublié cette restriction HTML et je viens d'ajouter les formulaires imbriqués. Le résultat était intéressant - j'ai perdu mon premier formulaire du niché. Ensuite, il s'est avéré être une sorte de "truc" pour simplement ajouter un formulaire factice (qui sera supprimé du navigateur) avant les formulaires imbriqués.

Dans mon cas, cela ressemble à ceci:

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

Fonctionne bien avec Chrome, FireFox et Safari. IE jusqu'à 9 (pas sûr de 10)) et Opera ne détecte pas de paramètres dans le formulaire principal. La variable globale $ _REQUEST est vide, quelles que soient les entrées Les formes intérieures semblent bien fonctionner partout.

Nous n'avons pas testé une autre suggestion décrite ici - champs autour de formulaires imbriqués.

EDIT : Le jeu de cadres n'a pas fonctionné! J'ai simplement ajouté le formulaire principal après les autres (plus de formulaires imbriqués) et utilisé le "clone" de jQuery pour dupliquer les entrées dans le formulaire en cliquant sur le bouton. Ajout de .hide () à chacune des entrées clonées pour que la disposition reste inchangée. À présent, cela fonctionne comme un charme.

11
B.D.Joe

Je pense que Jason a raison. Si votre action "Supprimer" est minimale, faites-en une forme et alignez-la avec les autres boutons de manière à ce que l'interface ressemble à une forme unifiée, même si ce n'est pas le cas.

Ou bien sûr, refaites votre interface et laissez les gens supprimer complètement un autre endroit, ce qui ne les oblige pas à voir la forme énorme.

4
AmbroseChapel

Pour ce faire, javascript serait utile en ajoutant un ensemble de boutons radio qui définissent l'action à prendre:

  • Mise à jour
  • Effacer
  • Peu importe

Ensuite, le script d'action prend différentes actions en fonction de la valeur du jeu de boutons radio.

Une autre solution consisterait à placer deux formulaires sur la page, comme vous l’avez suggéré, mais pas imbriqués. La mise en page peut être difficile à contrôler si:

 <form name = "editform" action = "the_action_url" method = "post"> 
 <type d'entrée "caché" name = "task" value = "update" />
 <input type = "text" name = "foo" />
 <input type = "submit" name = "save" value = "Enregistrer" />
</ form> 
 
 <form name = "delform" action = "the_action_url" method = "post"> 
 <input type = "hidden" name = "tâche" value = "delete"/> 
 <input type = "hidden" name = "post_id" value = "5" />
 <input type = "submit" name = "delete" value = "Delete" /> 
 </ form> 

Utilisation du champ "tâche" masqué dans le script de traitement pour créer une branche appropriée.

2
Ron Savage

Cette discussion m'intéresse toujours. Derrière le message original se trouvent des "exigences" que le PO semble partager - c’est-à-dire un formulaire avec une compatibilité ascendante. En tant que personne dont le travail au moment de la rédaction doit parfois prendre en charge IE6 (et pour les années à venir), je creuse cela.

Sans pousser le cadre (toutes les organisations vont vouloir se rassurer sur la compatibilité/la robustesse, et je n’utilise pas cette discussion pour justifier le cadre), les Drupal solutions à ce problème sont: intéressant. Drupal est aussi directement pertinent car le framework a depuis longtemps une politique de "ça devrait fonctionner sans Javascript (seulement si vous voulez)" c'est-à-dire le problème de l'OP.

Drupal utilise des form.inc fonctions assez étendues pour trouver l'élément triggering (oui, c'est le nom dans le code). Voir le bas du code répertorié sur la page de l'API pour form_builder (si vous souhaitez creuser dans les détails, le source est recommandé - drupal-x.xx/includes/form.inc ). Le générateur utilise la génération automatique d'attributs HTML et, grâce à cela, peut, à son retour, détecter le bouton sur lequel vous avez appuyé et agir en conséquence (ceux-ci peuvent également être configurés pour exécuter des processus distincts).

Au-delà du générateur de formulaire, Drupal divise les actions de suppression de données en URL/formulaires distincts, probablement pour les raisons mentionnées dans l'article d'origine. Cette opération nécessite une sorte d'étape de recherche/création de liste (groan un autre formulaire !, mais est convivial) au préalable. Mais cela a l’avantage d’éliminer le problème du "tout soumettre". Le grand formulaire contenant les données est utilisé aux fins prévues, à savoir la création/mise à jour des données (ou même une action de "fusion").

En d'autres termes, une façon de résoudre le problème consiste à diviser le formulaire en deux, puis le problème disparaît (et les méthodes HTML peuvent être corrigées via un POST aussi).

1
Rob Crowther

Utilisez un iframe pour le formulaire imbriqué. S'ils ont besoin de partager des champs, alors ... ce n'est pas vraiment imbriqué.

1
Dan Rosenstark

Ma solution est de faire en sorte que les boutons appellent les fonctions JS qui écrivent puis soumettent des formulaires en dehors du formulaire 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>
0
scotweb

Eh bien, si vous soumettez un formulaire, le navigateur envoie également une entrée, soumettez le nom et la valeur. Alors qu'est-ce que tu peux faire c'est

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

ainsi, côté serveur, il suffit de chercher le paramètre qui commence la chaîne de largeur "action:" et la partie restante vous indique les mesures à prendre.

ainsi, lorsque vous cliquez sur le bouton Enregistrer, le navigateur vous envoie quelque chose comme foo = asd & action: save = Save

0
Lauri Lüüs