Comment surmonter la limitation d'imbrication des formulaires HTML?

199

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 à ce sujet, mais je n'ai toujours pas trouvé de solution élégante au 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. Eh bien, personnellement, je ne peux pas penser à un scénario dont je n'ai pas 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 d'une manière qui ne nécessite pas JavaScript , juste un vieux formulaire HTML et des boutons d'envoi.

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

Le seul problème est qu'en appuyant sur "Supprimer", tous les champs du formulaire seront soumis sur le serveur, même si la seule chose nécessaire pour cette action est une entrée caché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 d'onglets dans mes applications LOB qui (en raison des exigences) doivent tout soumettre en une seule fois et en tout cas cela semble très inefficace et un gaspillage. Si l'imbrication des formulaires était prise en charge, je serais au moins capable d'envelopper le bouton de soumission «Supprimer» dans son propre formulaire avec uniquement le champ post-id.

Vous pouvez dire "Il suffit d'implémenter le" Supprimer "comme lien au lieu de soumettre". Ce serait faux à tant de niveaux, mais surtout parce que les actions à effet secondaire comme «Supprimer» ici, ne devraient jamais être une demande GET.

Donc ma question (en particulier à ceux qui disent qu'ils n'ont pas eu besoin d'imbrication de forme) est Que faites-vous? Y a-t-il une solution élégante qui me manque ou la ligne de fond est vraiment "soit exiger JavaScript ou tout soumettre"?

gCoder
la source
17
C'est drôle, mais XHTML permet l'imbrication indirecte de formulaires selon une note intéressante anderwald.info/internet/nesting-form-tags-in-xhtml - (X) HTML interdit les formes d'imbrication comme "form> form", mais autorise "form> fieldset> forme ", le validateur W3 dit qu'il est valide, mais les navigateurs ont des bogues avec une telle imbrication.
Nishi
Correction de linkrot pour le lien de commentaire de @ Nishi: web.archive.org/web/20170420110433/http://anderwald.info/…
albert

Réponses:

53

J'implémenterais cela exactement comme vous l'avez décrit: soumettez tout au serveur et faites un simple if / else pour vérifier sur quel bouton vous avez cliqué.

Et puis j'implémenterais un appel Javascript lié à l'événement onsubmit du formulaire qui vérifierait avant que le formulaire ne soit soumis, et ne soumettrais que les données pertinentes au serveur (éventuellement via un second formulaire sur la page avec l'ID nécessaire pour traiter la chose comme une entrée masquée, ou actualisez l'emplacement de la page avec les données dont vous avez besoin transmises en tant que demande GET, ou effectuez une publication Ajax sur le serveur, ou ...).

De cette façon, les personnes sans Javascript peuvent très bien 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 uniquement sur le soutien de l'un ou de l'autre limite vraiment inutilement vos options.

Alternativement, si vous travaillez derrière un pare-feu d'entreprise ou quelque chose et que tout le monde a désactivé Javascript, vous voudrez peut-être faire deux formulaires et travailler un peu de magie CSS pour donner l'impression que le bouton de suppression fait partie du premier formulaire.

One Crayon
la source
15
l'OP original n'a déclaré aucun javascript
Jason
26
Le problème avec cette méthode est qu'elle n'est pas RESTful. Une sauvegarde et une suppression correspondent à différentes actions sur la ressource: "Enregistrer" -> POST / my_resource (créer une nouvelle ressource) "Enregistrer" -> PUT / my_resource (modifier une ressource existante) "Supprimer" -> DELETE / my_resource (détruire la ressource) RESTfully parlant, le problème est qu'un POST est censé créer une nouvelle ressource. Certes, il ne devrait jamais supprimer une ressource existante.
user132447
178

Je sais que c'est une vieille question, mais HTML5 offre quelques nouvelles options.

La première consiste à séparer le formulaire de la barre d'outils dans le 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' formattribut.

<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 mentionné qu'il peut être nécessaire d'effectuer différentes actions avec un seul formulaire. HTML5 vient à la rescousse, encore une fois. Vous pouvez utiliser l' formactionattribut sur les boutons d'envoi, afin que différents boutons du même formulaire puissent envoyer des URL différentes. Cet exemple ajoute simplement une méthode de clonage à la barre d'outils en dehors du formulaire, mais cela fonctionnerait de la même manière imbriquée dans 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 sur les anciens navigateurs, vous devrez donc effectuer un polyfilling pour les anciens navigateurs.

shovavnik
la source
4
+1 - mais ce serait bien de savoir à quoi form=sert le navigateur - CanIUse ne le dit pas vraiment. caniuse.com/#feat=forms
AKX
1
C'est excellent, mais à ce jour, une fonctionnalité non prise en charge par IE le disqualifie toujours pour une utilisation en «production»
Jako
3
C'est une mauvaise pratique à utiliser <input>pour déclarer des boutons. l' <button>élément a été introduit il y a 15 ans à cet effet. Vraiment des gens.
Nicholas Shanks
27
Votre argument est inutilement controversé et sans rapport avec la question du PO. L'OP a posé des questions sur la limitation d'imbrication des formulaires sans utiliser JavaScript et la réponse propose une solution. Que vous utilisiez <input>ou des <button>éléments n'a pas d'importance, bien que les boutons soient généralement plus appropriés pour les barres d'outils, comme vous le dites. Soit dit en passant, les éléments des boutons ne sont pas pris en charge par tous les navigateurs depuis 15 ans.
shovavnik
6
@AKX Bien qu'il s'agisse d'une ancienne déclaration, vous pourrez voir la prise en charge de cette fonctionnalité ici: html5test.com/compare/feature/form-association-form.html . IE est une radiation, la plupart des autres navigateurs de bureau semblent tolérables.
gras
16

pouvez-vous avoir les formulaires côte à côte sur la page, mais pas imbriqués. puis utiliser CSS pour aligner tous les boutons?

<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
la source
5
Oui et cela semble trop hackish. De plus, sur une très grande page (imaginez des onglets et des sous-onglets et des boutons de soumission imbriqués dans plusieurs niveaux de portée), il est presque impossible de séparer complètement la position de l'écran des éléments de leur position dans le document.
gCoder
3
eh bien, c'est toujours un équilibre entre ce que vous voulez faire et ce que vous pouvez faire. il semble que ce sont les options - soit un formulaire géré sur le serveur, soit un formulaire multiple qui devient difficile à gérer - choisissez judicieusement :)
Jason
3
Oui, malheureusement avec toutes ces limitations en html, je vais m'en tenir à ce qui est pris en charge et envoyer le formulaire entier au serveur et ensuite utiliser discrètement javascript pour les clients qui le prennent en charge pour intercepter la soumission du formulaire et envoyer uniquement ce qui est vraiment nécessaire. C'est le meilleur compromis.
gCoder
13

HTML5 a une idée de «propriétaire de formulaire» - l'attribut «formulaire» pour les éléments d'entrée. Il permet d'émuler des formes imbriquées et résoudra le problème.

Nishi
la source
1
Une référence à ce sujet mérite d'être reliée?
dakab
Voir w3.org/TR/html5/forms.html#form-owner "Un élément associé à un formulaire est, par défaut, associé à son élément de formulaire ancêtre le plus proche, mais, s'il est réassociable, un attribut de formulaire peut être spécifié pour remplacer ce."
Nishi
11

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

Comme quelqu'un l'a 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 j'ai juste ajouté les formulaires imbriqués. Le résultat était intéressant - j'ai perdu ma première forme 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 réels.

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 très bien avec Chrome, FireFox et Safari. IE jusqu'à 9 (pas sûr environ 10) et Opera ne détecte pas les paramètres dans le formulaire principal. Le global $ _REQUEST est vide, quelles que soient les entrées. Les formes intérieures semblent bien fonctionner partout.

Je n'ai pas testé une autre suggestion décrite ici - fieldset autour des formulaires imbriqués.

EDIT : Frameset 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 du formulaire en cliquant sur le bouton. Ajout de .hide () à chacune des entrées clonées pour garder la disposition inchangée et maintenant cela fonctionne comme un charme.

BDJoe
la source
Cela a parfaitement fonctionné pour moi. Je travaillais sur une page asp.net, qui avait un formulaire tout compris. J'avais un formulaire interne imbriqué à utiliser pour le plug-in jQuery Validation ( github.com/jzaefferer/jquery-validation ), et bien qu'il fonctionnait parfaitement dans FireFox et IE, il a échoué dans Chrome avec 'TypeError: Impossible d'appeler la méthode' élément 'de indéfini'. Il s'est avéré que l'appel d'initialisation validate () a échoué car il n'a pas pu trouver le formulaire interne, car Chrome l'a supprimé du bloc de code HTML ajouté à l'aide de jQuery.html (). L'ajout d'un petit formulaire factice a d'abord fait que Chrome quitte le vrai.
John - Not A Number
Cela ne semble plus fonctionner. Firefox a supprimé la deuxième balise <form>, puis a mis fin à mon formulaire avec la première balise </form>. Cela a laissé une balise </form> non valide au bas de la page et un formulaire qui ne sera pas envoyé correctement. :(
Tarquin
Très utile pour moi. J'ajoute un widget Web qui contient un formulaire dans une page asp.net. Surpris par WebForm qu'il génère toujours un formulaire renfermant tout dans le corps HTML. Le formulaire joint devient un casse-tête pour le widget Web. La forme fictive semble être une solution rapide et efficace à cette situation. Il fonctionne parfaitement dans Chrome et Safari jusqu'à présent, n'a pas encore testé Firefox. Merci du partage, ça m'a fait gagner beaucoup de temps!
JM Yang
4

Je pense que Jason a raison. Si votre action "Supprimer" est minime, faites-la sous une forme seule et alignez-la avec les autres boutons afin de faire ressembler l'interface à une forme unifiée, même si ce n'est pas le cas.

Ou bien sûr, repensez votre interface et laissez les gens supprimer complètement ailleurs, ce qui ne les oblige pas du tout à voir l'énorme forme.

AmbroseChapel
la source
2

Une façon de le faire sans javascript serait d'ajouter un ensemble de boutons radio qui définissent l'action à entreprendre:

  • Mettre à jour
  • Supprimer
  • Peu importe

Ensuite, le script d'action prendrait différentes actions en fonction de la valeur de l'ensemble de boutons radio.

Une autre façon serait de mettre deux formulaires sur la page comme vous l'avez suggéré, mais pas imbriqués. La disposition peut cependant être difficile à contrôler:

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

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

Ron Savage
la source
1

Cette discussion m'intéresse toujours. Derrière le message d'origine, il y a des "exigences" que l'OP semble partager - c'est-à-dire un formulaire avec une compatibilité descendante. En tant que personne dont le travail au moment de la rédaction doit parfois revenir à IE6 (et pour les années à venir), je creuse cela.

Sans pousser le cadre (toutes les organisations vont vouloir se rassurer sur la compatibilité / robustesse, et je n'utilise pas cette discussion comme justification du cadre), les solutions Drupal à ce problème sont intéressantes. Drupal est également directement pertinent car le framework a une politique de longue date "il devrait fonctionner sans Javascript (uniquement si vous le souhaitez)", c'est-à-dire le problème du PO.

Drupal utilise ses fonctions form.inc assez étendues pour trouver le triggering_element (oui, c'est le nom dans le code). Voir le bas du code répertorié sur la page API pour form_builder (si vous souhaitez creuser dans les détails, la source est recommandée - drupal-x.xx / includes / form.inc ). Le générateur utilise la génération automatique d'attributs HTML et, via cela, peut en 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 formulaires, Drupal divise les actions de suppression de données en URL / formulaires séparés, probablement pour les raisons mentionnées dans le message d'origine. Cela nécessite une sorte d'étape de recherche / référencement ( gémir un autre formulaire !, mais est convivial) au préalable. Mais cela a l'avantage d'éliminer le problème "soumettre tout". Le grand formulaire avec les données est utilisé dans le but prévu, la création / mise à jour des données (ou même une action de «fusion»).

En d'autres termes, une façon de contourner le problème est de décomposer le formulaire en deux, puis le problème disparaît (et les méthodes HTML peuvent également être corrigées via un POST).

Rob Crowther
la source
0

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

Dan Rosenstark
la source
1
L'utilisation d'un iframe est une excellente idée, c'est juste dommage que dans la plupart des cas, vous auriez besoin de Javascript pour le redimensionner (car vous ne pouvez pas savoir quelle sera la taille du texte de l'utilisateur, d'où la taille du bouton).
Nicholas Shanks
@Nicholas, comment pouvez-vous utiliser Javascript pour le redimensionner? Le HTML iframed ne peut pas parler à l'iframe, et vice-versa, à moins qu'ils ne soient sur le même domaine.
Dan Rosenstark
@Nicholas, merci, je voulais juste m'assurer qu'ils doivent être dans le même domaine
Dan Rosenstark
0

Ma solution est 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>
scotweb
la source
-1

Si vous ne voulez vraiment pas utiliser plusieurs formulaires (comme le suggère Jason), utilisez des boutons et des gestionnaires 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>

Et puis en javascript (j'utilise jQuery ici pour plus de facilité) (même si c'est assez exagéré pour ajouter des gestionnaires onclick)

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

Je sais aussi que cela fera que la moitié de la page ne fonctionnera pas sans javascript.

Pim Jager
la source
1
Sauf si j'ai oublié quelque chose, ce n'est pas mieux que de lier à l'action de suppression: le résultat final sera une demande GET à l'action de suppression (mauvaise). En fait, c'est un peu plus grossier, car il nécessite JavaScript pour accomplir quelque chose qu'une ancienne balise de lien simple pourrait faire.
Kyle Fox
-1

En réponse à une question postée par Yar dans un commentaire à sa propre réponse, je présente du JavaScript qui redimensionnera un iframe. Dans le cas d'un bouton de formulaire, il est sûr de supposer que l'iframe sera sur le même domaine. C'est le code que j'utilise. Vous devrez modifier les mathématiques / constantes de votre propre site:

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

Ensuite, appelez-le comme ceci:

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

Je viens de trouver une belle façon de le faire avec 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
la source
-1

Eh bien, si vous soumettez un formulaire, le navigateur envoie également un nom et une valeur de soumission d'entrée. Donc ce que vous pouvez 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>

donc côté serveur, il vous suffit de rechercher le paramètre qui démarre la chaîne de largeur "action:" et la partie restante vous indique quelle action entreprendre

donc quand vous cliquez sur le bouton Enregistrer le navigateur vous envoie quelque chose comme foo = asd & action: save = Save

Lauri Lüüs
la source
-1

J'ai contourné le problème en incluant une case à cocher selon la forme que la personne voulait faire. Ensuite, j'ai utilisé 1 bouton pour envoyer le formulaire complet.

Gregws
la source
2
Bienvenue dans Stack Overflow . Il vaut mieux décrire comment cela résout le problème, plutôt que de simplement dire ce que vous avez fait. Voir Comment répondre pour des conseils sur la création de bonnes réponses sur Stack Overflow. Je vous remercie!
Qantas 94 Heavy
-2

Alternativement, vous pouvez attribuer le formulaire actiob à la volée ... ce n'est peut-être pas la meilleure solution, mais vous soulagez certainement la logique côté serveur ...

<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
la source
Pourquoi? Je demande sérieusement, car il n'y a pas de moyen facile d'obtenir quel bouton a été cliqué.Etes-vous en train de dire que quelque chose comme la méthode click () de jQuery devrait être utilisé pour définir le gestionnaire onclick du bouton? Ou que la gestion de l'événement de clic avant l'envoi du formulaire est le problème?
Zack Morris