Erro de duplicação de função de edição de elementos DOM em JavaScript
Suponha que temos uma lista:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Vamos obter a própria lista e seus itens em variáveis separadas:
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
Vamos fazer com que os itens da nossa lista possam ser editados por um input que aparece:
for (let li of lis) {
li.addEventListener('click', function func() {
let input = document.createElement('input');
input.value = list.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
});
}
Suponha agora que queremos que novos itens possam ser adicionados à lista. Suponha que para isso tenhamos um input correspondente abaixo da lista:
<input id="adder">
Vamos obter a referência para este input em uma variável:
let adder = document.querySelector('#adder');
Vamos fazer com que, ao perder o foco, o input adicione um novo item à lista com o texto obtido do nosso input:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});
Suponha agora que queremos que os itens recém-adicionados também possam ser editados. Por si só, a edição não funcionará para eles, pois quando anexamos o manipulador de clique aos itens da lista, esses itens ainda não existiam.
Vamos ver as possíveis variantes de solução para este problema.
Solução um
A solução mais simples é duplicar
o código da função func, vinculando-a
também para os itens recém-criados:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function func() {
// aqui duplicamos o código
});
ul.append(li);
});
Claro, nesta solução vemos imediatamente a desvantagem - duplicar código não é correto.
Solução dois
Para resolver o problema da duplicação,
é lógico colocar a função func
para fora, tornando-a uma Function Declaration:
function func() {
let input = document.createElement('input');
input.value = list.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
}
Aqui é onde o problema nos espera.
O fato é que nossa função usava
a variável li, obtida
do escopo externo.
Mas depois de colocar a função para fora, essa
variável agora não é visível!
Para resolver o problema, vamos passar
nossa li como parâmetro:
function func(li) {
let input = document.createElement('input');
input.value = list.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
li.addEventListener('click', func);
});
li.removeEventListener('click', func);
}
E aqui nossa solução gera mais um problema. O fato é que não se pode simplesmente passar um parâmetro para um manipulador de evento:
for (let li in lis) {
li.addEventListener('click', func(li)); // não funciona!
}
Para resolver este problema, basta chamar nossa função dentro de um manipulador anônimo:
for (let li of lis) {
li.addEventListener('click', function() {
func(li);
});
}
E agir de forma análoga ao criar um novo item de lista:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
li.addEventListener('click', function() {
func(li);
});
ul.append(li);
});
Solução três
Existe uma solução mais elegante. Podemos simplesmente usar a delegação de eventos. Neste caso, o problema com os novos itens de lista simplesmente não surgirá:
ul.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // capturamos exatamente o clique no li, não no input
let li = event.target;
let input = document.createElement('input');
input.value = li.textContent;
li.textContent = '';
li.append(input);
input.addEventListener('blur', function() {
li.textContent = this.value;
});
}
});
Neste caso, o loop pelos itens da lista nem será necessário, e o código para criar um novo item de lista se reduzirá a isto:
adder.addEventListener('blur', function() {
let li = document.createElement('li');
li.textContent = this.value;
ul.append(li);
});