eventos de alteração editáveis

359

Eu quero executar uma função quando um usuário edita o conteúdo de um divcom contenteditableatributo. Qual é o equivalente de um onchangeevento?

Estou usando o jQuery, portanto, qualquer solução que use o jQuery é preferida. Obrigado!

Jourkey
fonte
Eu costumo fazer isso: document.getElementById("editor").oninput = function() { ...}.
Basj

Respostas:

311

Eu sugiro ouvintes associados aos principais eventos disparados pelo elemento editável, mas você precisa estar ciente de que keydowne keypresseventos são disparados antes que o próprio conteúdo é alterado. Isso não abrangerá todos os meios possíveis de alterar o conteúdo: o usuário também pode usar recortar, copiar e colar nos menus Editar ou navegador de contexto, portanto, você também pode manipular os eventos cut copye paste. Além disso, o usuário pode soltar texto ou outro conteúdo, para que haja mais eventos ( mouseuppor exemplo). Você pode pesquisar o conteúdo do elemento como um substituto.

ATUALIZAÇÃO 29 de outubro de 2014

O evento HTML5input é a resposta a longo prazo. No momento da redação deste artigo, ele é suportado para contenteditableelementos nos navegadores atuais Mozilla (do Firefox 14) e WebKit / Blink, mas não no IE.

Demo:

document.getElementById("editor").addEventListener("input", function() {
    console.log("input event fired");
}, false);
<div contenteditable="true" id="editor">Please type something in here</div>

Demo: http://jsfiddle.net/ch6yn/2691/

Tim Down
fonte
11
Gostaria também de acrescentar que é possível mudar um elemento editável usando o menu de edição do navegador ou menu de contexto para cortar, copiar ou colar conteúdo, de forma que você precisa para lidar com cut, copye pasteeventos em navegadores que os apoiam (IE 5+, o Firefox 3.0+, Safari 3+, Chrome)
Tim Down
4
Você pode usar o keyup e não terá um problema de tempo.
Blowsie
2
@ Blowsie: Sim, mas você pode acabar sofrendo muitas alterações após um pressionamento de tecla repetido automaticamente antes do evento ser acionado. Também existem outras maneiras de alterar o texto editável não considerado por esta resposta, como arrastar e soltar e editar nos menus Editar e contexto. A longo prazo, tudo isso deve ser coberto pelo inputevento HTML5 .
Tim Baixo
11
Estou fazendo keyup com debounce.
Aen Tan
11
O @AndroidDev testo o Chrome, e o evento de entrada também é acionado na cópia colada e recortada conforme exigido pelas especificações: w3c.github.io/uievents/#events-inputevents
espinha de peixe
188

Aqui está uma versão mais eficiente que é usada onpara todos os conteúdos editáveis. É baseado nas principais respostas aqui.

$('body').on('focus', '[contenteditable]', function() {
    const $this = $(this);
    $this.data('before', $this.html());
}).on('blur keyup paste input', '[contenteditable]', function() {
    const $this = $(this);
    if ($this.data('before') !== $this.html()) {
        $this.data('before', $this.html());
        $this.trigger('change');
    }
});

O projeto está aqui: https://github.com/balupton/html5edit

Balupton
fonte
Funcionou perfeito para mim, obrigado por CoffeeScript e Javascript
jwilcox09
7
Existem algumas alterações que não serão capturadas por esse código até que o conteúdo editável seja desfocado. Por exemplo: arrastar e soltar, recortar no menu de contexto e colar na janela do navegador. Eu configurei uma página de exemplo que usa esse código com o qual você pode interagir aqui .
Jrullmann
@jrullmann Sim, eu tive problemas com este código quando arrastar e soltar as imagens porque não ouvir a carga de imagem (que era um problema para lidar com uma operação de redimensionamento no meu caso)
Sebastien Lorber
@jrullmann Very nice, isso funciona mesmo assim mudança de valor com javascript, tente no console: document.getElementById('editor_input').innerHTML = 'ssss'. Desvantagem é que ele requer IE11
11
@NadavB não deveria, pois é o que "if ($ this.data ('before')! == $ this.html ()) {" é para
balupton 16/08/1918
52

Considere usar MutationObserver . Esses observadores são projetados para reagir a alterações no DOM e como substituto de desempenho para eventos de mutação .

Prós:

  • Dispara quando ocorre alguma alteração, o que é difícil de conseguir ouvindo os principais eventos, conforme sugerido por outras respostas. Por exemplo, tudo isso funciona bem: arraste e solte, itálico, copie / recorte / cole no menu de contexto.
  • Projetado com desempenho em mente.
  • Código simples e direto. É muito mais fácil entender e depurar o código que escuta um evento em vez do código que escuta 10 eventos.
  • O Google possui uma excelente biblioteca de resumo de mutações, o que facilita o uso de MutationObservers.

Contras:

  • Requer uma versão muito recente do Firefox (14.0+), Chrome (18+) ou IE (11+).
  • Nova API para entender
  • Ainda não há muitas informações disponíveis sobre práticas recomendadas ou estudos de caso

Saber mais:

  • Escrevi um pequeno trecho para comparar o uso de MutationObserers para lidar com uma variedade de eventos. Eu usei o código de Balupton, já que sua resposta tem mais votos.
  • Mozilla tem uma excelente página na API
  • Dê uma olhada na biblioteca MutationSummary
jrullmann
fonte
Não é exatamente o mesmo que o inputevento HTML5 (que é suportado contenteditableem todos os navegadores WebKit e Mozilla que oferecem suporte a observadores de mutação), já que a mutação DOM pode ocorrer por script e por entrada do usuário, mas é uma solução viável para esses navegadores. Eu imagino que isso poderia prejudicar o desempenho mais do que o inputevento também, mas não tenho provas concretas disso.
Tim Baixo
2
+1, mas você percebeu que os Eventos de mutação não relatam os efeitos do feed de linha em um conteúdo editável? Pressione enter no seu snippet.
Citykid #
4
@ cityity: Isso porque o snippet está apenas observando alterações nos dados dos personagens. Também é possível observar alterações estruturais do DOM. Veja jsfiddle.net/4n2Gz/1 , por exemplo.
Tim Baixo
O Internet Explorer 11+ suporta Observadores de Mutação .
Sampson
Consegui verificar (pelo menos no Chrome 43.0.2357.130) que o inputevento HTML5 é acionado em cada uma dessas situações (especificamente arraste e solte, itálico, copie / recorte / cole no menu de contexto). Também testei negrito c / cmd / ctrl + be obtive os resultados esperados. Também verifiquei e pude verificar se o inputevento não dispara em alterações programáticas (o que parece óbvio, mas é indiscutivelmente contrário a alguma linguagem confusa na página MDN relevante; consulte a parte inferior da página aqui para ver o que quero dizer: desenvolvedor .mozilla.org / pt-
BR
22

Eu modifiquei a resposta de lawwantsin assim e isso funciona para mim. Eu uso o evento keyup em vez de pressionar a tecla, o que funciona muito bem.

$('#editor').on('focus', function() {
  before = $(this).html();
}).on('blur keyup paste', function() { 
  if (before != $(this).html()) { $(this).trigger('change'); }
});

$('#editor').on('change', function() {alert('changed')});
Dennkster
fonte
22

resposta rápida e suja não jQuery :

function setChangeListener (div, listener) {

    div.addEventListener("blur", listener);
    div.addEventListener("keyup", listener);
    div.addEventListener("paste", listener);
    div.addEventListener("copy", listener);
    div.addEventListener("cut", listener);
    div.addEventListener("delete", listener);
    div.addEventListener("mouseup", listener);

}

var div = document.querySelector("someDiv");

setChangeListener(div, function(event){
    console.log(event);
});
Developia
fonte
3
o que é esse evento de exclusão?
James Cat
7

Duas opções:

1) Para navegadores modernos (sempre-verdes): o evento "input" atuaria como um evento "change" alternativo.

https://developer.mozilla.org/en-US/docs/Web/Events/input

document.querySelector('div').addEventListener('input', (e) => {
    // Do something with the "change"-like event
});

ou

<div oninput="someFunc(event)"></div>

ou (com jQuery)

$('div').on('click', function(e) {
    // Do something with the "change"-like event
});

2) Para considerar os navegadores IE11 e modernos (sempre-verdes): isso observa as alterações dos elementos e seu conteúdo dentro da div.

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

var div = document.querySelector('div');
var divMO = new window.MutationObserver(function(e) {
    // Do something on change
});
divMO.observe(div, { childList: true, subtree: true, characterData: true });
um pouco menos
fonte
7

const p = document.querySelector('p')
const result = document.querySelector('div')
const observer = new MutationObserver((mutationRecords) => {
  result.textContent = mutationRecords[0].target.data
  // result.textContent = p.textContent
})
observer.observe(p, {
  characterData: true,
  subtree: true,
})
<p contenteditable>abc</p>
<div />

superwf
fonte
2
Isso parece interessante. Alguém pode fornecer alguma explicação? W
WoIIe 23/02/19
3

Aqui está o que funcionou para mim:

   var clicked = {} 
   $("[contenteditable='true']").each(function(){       
        var id = $(this).attr("id");
        $(this).bind('focus', function() {
            // store the original value of element first time it gets focus
            if(!(id in clicked)){
                clicked[id] = $(this).html()
            }
        });
   });

   // then once the user clicks on save
   $("#save").click(function(){
            for(var id in clicked){
                var original = clicked[id];
                var current = $("#"+id).html();
                // check if value changed
                if(original != current) save(id,current);
            }
   });
Gregory Whiteside
fonte
3

Esse tópico foi muito útil enquanto eu investigava o assunto.

Modifiquei parte do código disponível aqui em um plug-in jQuery para que ele seja reutilizável, principalmente para satisfazer minhas necessidades, mas outros podem apreciar uma interface mais simples para iniciar o uso inicial de tags editáveis ​​por conteúdo.

https://gist.github.com/3410122

Atualizar:

Devido à sua crescente popularidade, o plugin foi adotado por Makesites.org

O desenvolvimento continuará daqui:

https://github.com/makesites/jquery-contenteditable

tracend
fonte
2

Aqui está a solução que acabei usando e funciona fabulosamente. Eu uso $ (this) .text () porque estou apenas usando uma div de uma linha com conteúdo editável. Mas você também pode usar .html () dessa maneira, não precisa se preocupar com o escopo de uma variável global / não global e o anterior é realmente anexado ao editor div.

$('body').delegate('#editor', 'focus', function(){
    $(this).data('before', $(this).html());
});
$('#client_tasks').delegate('.task_text', 'blur', function(){
    if($(this).data('before') != $(this).html()){
        /* do your stuff here - like ajax save */
        alert('I promise, I have changed!');
    }
});
user740433
fonte
1

Para evitar temporizadores e botões "salvar", você pode usar um evento de desfoque que é acionado quando o elemento perde o foco. mas, para ter certeza de que o elemento foi realmente alterado (não apenas focado e desfocado), seu conteúdo deve ser comparado à sua última versão. ou use o evento keydown para definir algum sinalizador "sujo" nesse elemento.

joox
fonte
1

Uma resposta simples no JQuery, eu apenas criei este código e achei que seria útil para outros também

    var cont;

    $("div [contenteditable=true]").focus(function() {
        cont=$(this).html();
    });

    $("div [contenteditable=true]").blur(function() {
        if ($(this).html()!=cont) {
           //Here you can write the code to run when the content change
        }           
    });
sarath
fonte
Observe que $("div [contenteditable=true]")irá selecionar todos os filhos de uma div, direta ou indiretamente, que sejam editáveis ​​por conteúdo.
Bruno Ferreira
1

Resposta não JQuery ...

function makeEditable(elem){
    elem.setAttribute('contenteditable', 'true');
    elem.addEventListener('blur', function (evt) {
        elem.removeAttribute('contenteditable');
        elem.removeEventListener('blur', evt.target);
    });
    elem.focus();
}

Para usá-lo, chame (digamos) um elemento de cabeçalho com id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Esse elemento agora será editável pelo usuário até perder o foco.

DrMcCleod
fonte
5
Ugh, por que o voto negativo sem explicação? Isso prejudica tanto a pessoa que escreveu a resposta quanto a todos que lêem a resposta mais tarde.
9788 Mike Willis
0

O evento onchange não é acionado quando um elemento com o atributo contentEditable é alterado. Uma abordagem sugerida pode ser adicionar um botão, "salvar" a edição.

Verifique este plugin que lida com o problema dessa maneira:

CMS
fonte
para a posteridade, daqui a dez anos e não há necessidade de um botão "salvar", verifique o jsfiddle da resposta aceita
revelt
0

Usar DOMCharacterDataModified em MutationEvents levará ao mesmo. O tempo limite está configurado para evitar o envio de valores incorretos (por exemplo, no Chrome, tive alguns problemas com a tecla de espaço)

var timeoutID;
$('[contenteditable]').bind('DOMCharacterDataModified', function() {
    clearTimeout(timeoutID);
    $that = $(this);
    timeoutID = setTimeout(function() {
        $that.trigger('change')
    }, 50)
});
$('[contentEditable]').bind('change', function() {
    console.log($(this).text());
})

Exemplo JSFIDDLE

janfabian
fonte
11
+1 - DOMCharacterDataModifiednão será acionado quando o usuário modificar o texto existente, por exemplo, aplicando negrito ou itálico. DOMSubtreeModifiedé mais apropriado neste caso. Além disso, as pessoas devem lembrar que os navegadores herdados não suportam esses eventos.
Andy E
3
Apenas observe que os Eventos de mutação são depreciados pelo w3c devido a problemas de desempenho. Mais pode ser encontrado nesta questão de estouro de pilha .
Jrullmann
0

Eu construí um plugin jQuery para fazer isso.

(function ($) {
    $.fn.wysiwygEvt = function () {
        return this.each(function () {
            var $this = $(this);
            var htmlold = $this.html();
            $this.bind('blur keyup paste copy cut mouseup', function () {
                var htmlnew = $this.html();
                if (htmlold !== htmlnew) {
                    $this.trigger('change')
                }
            })
        })
    }
})(jQuery);

Você pode simplesmente ligar $('.wysiwyg').wysiwygEvt();

Você também pode remover / adicionar eventos, se desejar

Blowsie
fonte
2
Isso ficará lento e lento à medida que o conteúdo editável for mais longo ( innerHTMLé caro). Eu recomendaria usar o inputevento onde ele existe e voltar a algo assim, mas com algum tipo de devolução.
Tim Baixo
0

Em Angular 2+

<div contentEditable (input)="type($event)">
   Value
</div>

@Component({
  ...
})
export class ContentEditableComponent {

 ...

 type(event) {
   console.log(event.data) // <-- The pressed key
   console.log(event.path[0].innerHTML) // <-- The content of the div 
 }
}

Dávid Konkoly
fonte
0

Por favor, siga a seguinte solução simples

Em .ts:

getContent(innerText){
  this.content = innerText;
}

em .html:

<div (blur)="getContent($event.target.innerHTML)" contenteditable [innerHTML]="content"></div>
Sahil Ralkar
fonte
-1

Confira essa ideia. http://pastie.org/1096892

Eu acho que está perto. O HTML 5 realmente precisa adicionar o evento de alteração às especificações. O único problema é que a função de retorno de chamada avalia se (antes == $ (this) .html ()) antes que o conteúdo seja realmente atualizado em $ (this) .html (). setTimeout não funciona, e é triste. Diz-me o que pensas.

Lawrence Whiteside
fonte
Tente digitar em vez de pressionar.
Aen Tan
-2

Com base na resposta de @ balupton:

$(document).on('focus', '[contenteditable]', e => {
	const self = $(e.target)
	self.data('before', self.html())
})
$(document).on('blur', '[contenteditable]', e => {
	const self = $(e.target)
	if (self.data('before') !== self.html()) {
	  self.trigger('change')
	}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Phillip Senn
fonte