Programação Por Equipe DevBras

Capa do artigo: Div com contenteditable no lugar do campo: o texto não vai no formulário

Div com contenteditable no lugar do campo: o texto não vai no formulário

Entenda por que uma div editável não envia valor, não tem rótulo e aceita HTML colado, e como trocar por textarea ou, se precisar dela, sincronizar, rotular e tratar o texto no servidor.

O campo "Observações do pedido" da loja era uma div com contenteditable, porque o designer queria que ela crescesse com o texto. O placeholder era outra div por cima, escondida com script quando a pessoa digitava. Funcionou na demonstração. Em produção, os pedidos chegavam sem observação nenhuma, e o cliente que escreveu "não tocar a campainha, bebê dormindo" ouviu a campainha. Uma div editável não é campo de formulário: o texto dela não vai no envio.

A resposta curta: se é texto simples, de uma linha ou um parágrafo, use input ou textarea com label. Se você realmente precisa da área editável, dê a ela papel de caixa de texto e um rótulo, copie o conteúdo para um campo escondido no envio, prefira o modo de texto puro e trate o que chega no servidor como texto não confiável.

Por que o texto da div não chega ao servidor

Quando um formulário é enviado, o navegador monta o corpo com os valores dos controles de formulário que têm name: input, textarea, select e alguns outros. Uma div, editável ou não, não é um controle. Ela não tem name, não tem value e não participa da coleta. O texto está visível na tela e fica fora do POST.

O contenteditable transforma a div num pequeno editor de documento, e com isso você herda tudo que um campo fazia sozinho e agora precisa ser feito à mão:

  • Rótulo: o label com for só se associa a controles. Sem ele, o leitor de tela anuncia "editável" sem dizer o quê.
  • Placeholder: uma div irmã por cima não é anunciada como dica do campo e pode até roubar o clique.
  • Validação: required e maxlength não se aplicam a div.
  • Colar: no modo padrão, colar de um editor de texto traz HTML, com negrito, fontes e às vezes imagens.
  • Preenchimento automático e restauração: o navegador não guarda nem restaura o conteúdo se a página recarregar.

A correção mais simples: textarea com rótulo

Na maioria dos casos, o motivo da div era visual: crescer com o texto, não ter cara de caixa cinza. Os dois se resolvem com textarea e CSS:

<label for="observacao">Observações do pedido</label>
<textarea id="observacao" name="observacao" rows="3" maxlength="500"
          aria-describedby="observacao-dica"></textarea>
<p id="observacao-dica">Ex.: entregar após as 14h, deixar na portaria.</p>
textarea {
  width: 100%;
  border: 1px solid #4A5866;
  border-radius: 8px;
  padding: 0.75rem;
  font: inherit;
  resize: vertical;
  field-sizing: content;
  min-height: 4.5rem;
}

A propriedade field-sizing: content faz o campo crescer com o texto nos navegadores que já a suportam; nos outros, ele mantém a altura de três linhas e a pessoa pode redimensionar. Em nenhum caso o texto se perde. A dica fica visível fora do campo, porque o placeholder some ao digitar e leva a instrução junto, como mostra o guia de marca d'água no campo.

Quando a div editável é necessária, como fazer direito

Há casos legítimos: menções com @, destaque de trechos, um editor de texto rico. Se for o seu caso, siga estes passos:

  1. Prefira o modo de texto puro. O valor contenteditable="plaintext-only" faz a colagem chegar sem formatação. Ele é recente em parte dos navegadores; confira o suporte do seu público e, sem ele, trate a colagem no evento paste.
  2. Dê papel e nome. Use role="textbox", aria-multiline="true" e aria-labelledby apontando para o texto do rótulo.
  3. Faça o placeholder com CSS, não com outra div. Use o atributo aria-placeholder para o leitor de tela e um pseudoelemento para quem vê.
  4. Copie o conteúdo para um campo escondido no envio. O input type="hidden" com name é o que vai no POST.
  5. Valide e trate no servidor. Tamanho máximo, obrigatoriedade e escape na hora de exibir.
<form method="post" action="/pedidos/1234/observacao" id="form-obs">
  <span id="obs-rotulo">Observações do pedido</span>
  <div id="obs-editor" contenteditable="plaintext-only"
       role="textbox" aria-multiline="true"
       aria-labelledby="obs-rotulo"
       aria-placeholder="Ex.: entregar após as 14h"
       data-placeholder="Ex.: entregar após as 14h"></div>
  <input type="hidden" name="observacao" id="obs-valor">
  <button type="submit">Salvar</button>
</form>

<script>
  document.getElementById('form-obs').addEventListener('submit', function () {
    const texto = document.getElementById('obs-editor').innerText.trim();
    document.getElementById('obs-valor').value = texto;
  });
</script>
#obs-editor:empty::before {
  content: attr(data-placeholder);
  color: #4A5866;
}

O innerText devolve o texto com as quebras de linha que a pessoa vê, sem as tags. Um detalhe que pega muita gente: depois de digitar e apagar tudo, alguns navegadores deixam um br dentro da div, e o seletor :empty deixa de casar. O placeholder não volta. Se isso acontecer no seu caso, limpe o conteúdo no evento input quando o texto ficar vazio.

Para componentes reutilizáveis, existe ainda a via dos elementos personalizados associados a formulário, com ElementInternals e setFormValue, que fazem o componente participar do envio como um campo de verdade. É mais trabalho, e vale quando o mesmo editor aparece em muitos formulários.

No servidor, o texto é entrada do usuário

O campo escondido pode ser alterado por qualquer pessoa, como qualquer outro campo. Confira tamanho e obrigatoriedade no servidor e, ao exibir a observação no painel da loja, escape o texto. Nunca grave o innerHTML da div e o exiba de volta como HTML: é o caminho direto para injeção de script no painel de quem atende o pedido. Se o conteúdo precisa mesmo ser rico, a sanitização por lista de tags permitidas no servidor está no guia de editor rico no lugar da textarea.

Erros comuns

  • Usar div editável para texto simples. O valor não vai no POST e o pedido chega sem a informação que a pessoa escreveu.
  • Placeholder feito com outra div. O leitor de tela não o anuncia, e ele pode capturar o clique que deveria ir para a área editável.
  • Rótulo solto sem associação. Visualmente há um título; para tecnologia assistiva, a área não tem nome.
  • Enviar e exibir o innerHTML. HTML colado e script injetado aparecem no painel interno.
  • Confiar só na sincronização por script. Se o JavaScript falhar, o campo escondido vai vazio; o servidor precisa tratar ausência sem gravar lixo.
  • Esquecer o limite de tamanho. Sem maxlength possível na div, só o servidor segura um texto de um megabyte.

Checklist

  • Campos de texto simples usam input ou textarea com label.
  • Toda área editável tem role="textbox" e nome acessível.
  • O placeholder da área editável é feito com CSS e aria-placeholder.
  • O conteúdo chega ao servidor por um campo com name.
  • A colagem não traz formatação quando o campo é de texto puro.
  • O servidor valida tamanho e obrigatoriedade.
  • O texto é escapado ao ser exibido em qualquer tela.

Abra as ferramentas do navegador, envie o seu formulário com texto na área editável e olhe a aba de rede: o texto que você escreveu aparece no corpo da requisição?

Avalie este artigo

Referência: http://stackoverflow.com/questions/16941360/how-to-create-a-placeholder-for-div-that-act-like-textfield