Como você supera a limitação de aninhamento de formulários HTML?

199

Sei que o XHTML não suporta tags de formulário aninhadas e já li outras respostas aqui no Stack Overflow sobre esse assunto, mas ainda não descobri uma solução elegante para o problema.

Alguns dizem que você não precisa e que eles não conseguem pensar em um cenário onde isso seria necessário. Bem, pessoalmente, não consigo pensar em um cenário que não precisei.

Vamos ver um exemplo muito simples:

Você está criando um aplicativo de blog e possui um formulário com alguns campos para criar uma nova postagem e uma barra de ferramentas com "ações" como "Salvar", "Excluir", "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>

Nosso objetivo é escrever o formulário de uma maneira que não exija JavaScript , basta usar o formulário HTML antigo e enviar botões.

Como o URL da ação é definido na tag Form e não em cada botão de envio individual, nossa única opção é postar em um URL genérico e iniciar "if ... then ... else" para determinar o nome do botão que foi submetido. Não é muito elegante, mas é a nossa única opção, pois não queremos confiar no JavaScript.

O único problema é que pressionar "Excluir" enviará TODOS os campos do formulário no servidor, mesmo que a única coisa necessária para essa ação seja uma entrada Oculta com o ID da postagem. Não é grande coisa neste pequeno exemplo, mas tenho formulários com centenas (por assim dizer) de campos e guias em meus aplicativos LOB que (por causa dos requisitos) precisam enviar tudo de uma só vez e, de qualquer forma, isso parece muito ineficiente e um desperdício. Se o aninhamento de formulários fosse suportado, eu seria capaz de quebrar o botão de envio "Excluir" dentro de seu próprio formulário, apenas com o campo pós-identificação.

Você pode dizer "Basta implementar o" Excluir "como um link em vez de enviar". Isso seria errado em muitos níveis, mas o mais importante é que ações de efeito colateral como "Excluir" aqui nunca devem ser uma solicitação GET.

Portanto, minha pergunta (principalmente para aqueles que dizem que não precisavam de aninhamento de formulários) é O que você faz? Existe alguma solução elegante que estou faltando ou o resultado final é realmente "Exija JavaScript ou envie tudo"?

gCoder
fonte
17
É engraçado, mas o XHTML permite o aninhamento indireto de formulários de acordo com uma nota interessante anderwald.info/internet/nesting-form-tags-in-xhtml - (X) HTML desabilita o agrupamento de formulários como "form> form", mas permite "form> fieldset> formulário ", o validador W3 diz que é válido, mas os navegadores têm erros com esse aninhamento.
Nishi
linkrot correção para o link do comentário de @ Nishi: web.archive.org/web/20170420110433/http://anderwald.info/…
albert

Respostas:

53

Eu implementaria isso exatamente como você descreveu: envie tudo para o servidor e faça um simples if / else para verificar em qual botão foi clicado.

E, em seguida, implementaria uma chamada Javascript vinculando o evento onsubmit do formulário, que seria verificado antes do envio do formulário e apenas enviaria os dados relevantes ao servidor (possivelmente por meio de um segundo formulário na página com o ID necessário para processar a coisa como uma entrada oculta, ou atualize o local da página com os dados que você precisa passar como uma solicitação GET ou faça uma postagem do Ajax no servidor ou ...).

Dessa forma, as pessoas sem Javascript podem usar o formulário corretamente, mas a carga do servidor é compensada porque as pessoas que possuem Javascript estão enviando apenas os dados necessários. Ficar focado em apenas apoiar um ou outro realmente limita suas opções desnecessariamente.

Como alternativa, se você trabalha com um firewall corporativo ou algo assim e todo mundo tem o Javascript desabilitado, convém fazer dois formulários e trabalhar um pouco de CSS para parecer que o botão excluir faz parte do primeiro formulário.

One Crayon
fonte
15
o original do OP não declarou javascript
Jason
26
O problema com esse método é que ele não é RESTful. Um salvamento e exclusão correspondem a diferentes ações no recurso: "Salvar" -> POST / meu_resource (criando um novo recurso) "Salvar" -> PUT / meu_resource (modificando um recurso existente) "Excluir" -> DELETE / meu_resource (destruir RESTfully falando, o problema é que um POST deve criar um novo recurso. Certamente, ele nunca deve excluir um recurso existente.
User132447
178

Sei que essa é uma pergunta antiga, mas o HTML5 oferece algumas novas opções.

A primeira é separar o formulário da barra de ferramentas na marcação, adicionar outro formulário para a ação de exclusão e associar os botões na barra de ferramentas aos respectivos formulários usando o 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>

Essa opção é bastante flexível, mas a postagem original também mencionou que pode ser necessário executar ações diferentes com um único formulário. O HTML5 vem em socorro, novamente. Você pode usar o formactionatributo nos botões de envio, para que botões diferentes no mesmo formulário possam ser enviados para URLs diferentes. Este exemplo apenas adiciona um método clone à barra de ferramentas fora do formulário, mas funcionaria da mesma forma aninhada no formulário.

<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

A vantagem desses novos recursos é que eles fazem tudo isso declarativamente sem JavaScript. A desvantagem é que eles não são suportados em navegadores mais antigos; portanto, você precisará fazer um polyfilling para navegadores mais antigos.

shovavnik
fonte
4
+1 - mas seria bom saber para que form=serve o suporte do navegador - o CanIUse não diz realmente. caniuse.com/#feat=forms
AKX
1
Por aqui é excelente, mas a partir de hoje um recurso não é suportado pelo IE ainda desqualifica-lo para uso 'produção'
Jako
3
É uma prática ruim usar <input>para declarar botões. o <button>elemento foi introduzido há 15 anos para esse fim. Realmente pessoas.
Nicholas Shanks
27
Seu argumento é desnecessariamente controverso e irrelevante para a pergunta do OP. O OP perguntou sobre a limitação de aninhamento de formulários sem usar JavaScript e a resposta oferece uma solução. Se você usa <input>ou <button>elementos é irrelevante, embora os botões sejam geralmente mais apropriados para as barras de ferramentas, como você diz. A propósito, os elementos do botão não são suportados por todos os navegadores há 15 anos.
shovavnik
6
@AKX Embora seja uma declaração antiga, você poderá ver o suporte para esse recurso aqui: html5test.com/compare/feature/form-association-form.html . O IE é uma anulação, a maioria dos outros navegadores de área de trabalho parece tolerável.
gordo
16

você pode ter os formulários lado a lado na página, mas não aninhados. então use CSS para deixar todos os botões alinhados?

<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
fonte
5
Sim e parece muito hackish. Além disso, em uma página muito grande (imagine guias e sub-guias e botões de envio de aninhamento em vários níveis de escopo) é quase impossível separar completamente a posição da tela dos elementos da sua posição no documento.
gCoder
3
bem, é sempre um equilíbrio entre o que você quer fazer e o que você pode fazer. parece que essas são as opções - ou uma forma manipulada no servidor, ou um formas múltiplas que se tornam difíceis de manter - escolha sabiamente :)
Jason
3
Sim, infelizmente com todas essas limitações em html, continuarei com o que é suportado e envie o formulário inteiro para o servidor e depois use o javascript sem obstrução para os clientes que o suportam para interceptar o envio do formulário e enviar apenas o que é realmente necessário. Esse é o melhor compromisso.
gCoder
13

O HTML5 tem uma ideia do "proprietário do formulário" - o atributo "formulário" para os elementos de entrada. Permite emular formulários aninhados e resolverá o problema.

Nishi
fonte
1
Alguma referência a este link vale a pena?
Dakab # 26/16
Consulte w3.org/TR/html5/forms.html#form-owner "Um elemento associado ao formulário é, por padrão, associado ao seu elemento ancestral mais próximo, mas, se for reassociável, pode ter um atributo de formulário especificado para substituir isto."
Nishi
11

Tipo de tópico antigo, mas este pode ser útil para alguém:

Como alguém mencionado acima - você pode usar a forma fictícia. Eu tive que superar esse problema há algum tempo. No começo, esqueci completamente essa restrição de HTML e apenas adicionei os formulários aninhados. O resultado foi interessante - perdi minha primeira forma do aninhado. Acabou sendo uma espécie de "truque" para simplesmente adicionar um formulário fictício (que será removido do navegador) antes dos formulários aninhados reais.

No meu caso, fica assim:

<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 bem com Chrome, FireFox e Safari. IE até 9 (não tenho certeza sobre 10) e o Opera não detecta parâmetros na forma principal. O global $ _REQUEST está vazio, independentemente das entradas. Formas internas parecem funcionar bem em todos os lugares.

Não testei outra sugestão descrita aqui - fieldset em torno de formulários aninhados.

EDIT : Frameset não funcionou! Simplesmente adicionei o formulário Principal após os outros (não há mais formulários aninhados) e usei o "clone" do jQuery para duplicar as entradas no formulário, clicando no botão. Adicionado .hide () a cada uma das entradas clonadas para manter o layout inalterado e agora funciona como um encanto.

BDJoe
fonte
Isso funcionou perfeitamente para mim. Eu estava trabalhando em uma página asp.net, que tinha um formulário abrangente. Eu tinha um formulário interno aninhado para usar no plugin jQuery Validation ( github.com/jzaefferer/jquery-validation ) e, embora funcionasse perfeitamente no FireFox e no IE, falhava no Chrome com o elemento 'TypeError: Não é possível chamar o método' de ' Indefinido'. Aconteceu que a chamada de inicialização validate () estava falhando, pois não conseguiu encontrar o formulário interno, pois o Chrome a removeu do bloco de html adicionado usando jQuery.html (). Adicionar um pequeno formulário fictício primeiro fez com que o Chrome deixasse o real.
João - Não é um número
Isso parece não funcionar mais. O Firefox retirou a segunda tag <form> e depois encerrou meu formulário com a primeira tag </form>. Isso deixou uma tag </form> inválida na parte inferior da página e um formulário que não será enviado corretamente. :(
Tarquin
Muito útil para mim. Estou adicionando um widget da web que contém formulário em uma página asp.net. Surpreendido pelo WebForm, ele sempre gera um formulário que encerra tudo no corpo do HTML. O formulário anexo se torna uma dor de cabeça para o widget da web. A forma dummy parece ser uma solução rápida e eficaz para essa situação. Até agora, funciona perfeitamente no Chrome e Safari, ainda não testou o Firefox. Obrigado por compartilhar, me salvou muito tempo!
JM Yang
4

Eu acho que Jason está certo. Se a sua ação "Excluir" é mínima, faça com que ela esteja em um formulário por si só e alinhe-a com os outros botões para fazer com que a interface pareça um formulário unificado, mesmo que não esteja.

Ou, é claro, redesenhe sua interface e permita que as pessoas excluam completamente outro lugar que não exija que elas vejam a forma enormo.

AmbroseChapel
fonte
2

Uma maneira de fazer isso sem javascript seria adicionar um conjunto de botões de opção que definam a ação a ser tomada:

  • Atualizar
  • Excluir
  • Tanto faz

Em seguida, o script de ação executará ações diferentes, dependendo do valor do conjunto de botões de opção.

Outra maneira seria colocar dois formulários na página, como você sugeriu, mas não aninhados. O layout pode ser difícil de controlar:

<nome do formulário = "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 = "Salvar" />
</form>

<nome do formulário = "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 = "Excluir" />
</form>

Usando o campo "tarefa" oculto no script de manipulação para ramificar adequadamente.

Ron Savage
fonte
1

Essa discussão ainda é do meu interesse. Por trás da postagem original, existem "requisitos" que o OP parece compartilhar - ou seja, um formulário com compatibilidade com versões anteriores. Como alguém cujo trabalho no momento da redação deve às vezes apoiar o IE6 (e nos próximos anos), eu entendo isso.

Sem pressionar a estrutura (todas as organizações vão querer se assegurar da compatibilidade / robustez, e eu não estou usando essa discussão como justificativa para a estrutura), as soluções Drupal para esse problema são interessantes. O Drupal também é diretamente relevante porque a estrutura possui uma política de muito tempo de "deve funcionar sem Javascript (somente se você quiser)", isto é, o problema do OP.

Drupal usa suas funções form.inc bastante extensas para encontrar o elemento triggering (sim, esse é o nome no código). Consulte a parte inferior do código listado na página da API para form_builder (se você quiser pesquisar detalhes, a fonte é recomendada - drupal-x.xx / includes / form.inc ). O construtor usa a geração automática de atributos HTML e, com isso, pode voltar a detectar qual botão foi pressionado e agir de acordo (eles também podem ser configurados para executar processos separados).

Além do construtor de formulários, o Drupal divide as ações de exclusão de dados em URLs / formulários separados, provavelmente pelos motivos mencionados na postagem original. Isso precisa de algum tipo de etapa de pesquisa / listagem ( gere outro formulário !, mas é fácil de usar) como preliminar. Mas isso tem a vantagem de eliminar o problema "enviar tudo". A grande forma com os dados é usada para a finalidade pretendida, criação / atualização de dados (ou até mesmo uma ação de 'mesclagem').

Em outras palavras, uma maneira de contornar o problema é devolver o formulário em dois, depois o problema desaparece (e os métodos HTML também podem ser corrigidos por meio de um POST).

Rob Crowther
fonte
0

Use um iframepara o formulário aninhado. Se eles precisam compartilhar campos, então ... não está realmente aninhado.

Dan Rosenstark
fonte
1
Usar um iframe é uma ótima idéia, é uma pena que na maioria dos casos você precise de javascript para redimensioná-lo (já que você não pode saber qual será o tamanho do texto do usuário, portanto, qual será o tamanho do botão).
Nicholas Shanks
@ Nicolas, como você pode usar o Javascript para redimensioná-lo? O HTML iframed não pode falar com o iframe e vice-versa, a menos que esteja no mesmo domínio.
Dan Rosenstark
@Nicholas, obrigado, só queria ter certeza de que eles têm de estar no mesmo domínio
Dan Rosenstark
0

Minha solução é fazer com que os botões chamem funções JS que escrevem e depois enviam formulários fora do formulário 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
fonte
-1

Se você realmente não deseja usar vários formulários (como Jason sugere), use botões e manipuladores ao clicar.

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

E então em javascript (eu uso o jQuery aqui para facilitar) (mesmo que seja um exagero adicionar alguns manipuladores onclick)

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

Também sei que isso fará com que metade da página não funcione sem javascript.

Pim Jager
fonte
1
A menos que eu tenha esquecido algo, isso não é melhor do que vincular à ação de exclusão: o resultado final será uma solicitação GET para a ação de exclusão (incorreta). Na verdade, é um pouco mais grosseiro, pois requer JavaScript para realizar algo que uma tag de link antiga simples poderia fazer.
Kyle Fox
-1

Em resposta a uma pergunta postada por Yar em um comentário à sua própria resposta, apresento algum JavaScript que redimensionará um iframe. No caso de um botão de formulário, é seguro assumir que o iframe estará no mesmo domínio. Este é o código que eu uso. Você precisará alterar as constantes matemáticas / do seu 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;
    }
}

Então chame assim:

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

Eu apenas vim com uma boa maneira de fazer isso com o 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>
mordido
fonte
-1

Bem, se você enviar um formulário, o navegador também enviará um nome e um valor de envio de entrada. Então, o que você pode fazer é

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

no lado do servidor, basta procurar o parâmetro que inicia a string de largura "action:" e a parte restante informa qual ação executar

então, quando você clica no botão Salvar navegador, envia algo como foo = asd & action: save = Save

Lauri Lüüs
fonte
-1

Eu contornei o problema incluindo uma caixa de seleção, dependendo da forma que a pessoa queria fazer. Em seguida, use o botão 1 para enviar o formulário inteiro.

Gregws
fonte
2
Bem-vindo ao Stack Overflow . É melhor descrever como isso resolve o problema, em vez de apenas dizer o que você fez. Consulte Como responder para obter dicas sobre como criar ótimas respostas no Stack Overflow. Obrigado!
Qantas 94 Heavy
-2

Como alternativa, você pode atribuir o formulário actiob em tempo real ... pode não ser a melhor solução, mas com certeza alivia a lógica do 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
fonte
Por quê? Estou perguntando a sério, porque não há uma maneira fácil de obter qual botão foi clicado. Você está dizendo que algo como o método click () do jQuery deve ser usado para definir o manipulador onclick do botão? Ou que lidar com o evento click antes do envio do formulário é o problema?
Zack Morris