Será que o onclick é do mal? As várias formas de escutar eventos em uma página

Nesse post, perguntaremos se o usuário realmente quer sair do nosso site quando ele clicar no botão de sair. Precisamos chegar num resultado igual a esta imagem:

onclickOuAEL–confirmSair

Como teremos que fechar a janela do site caso o usuário confirme que quer sair, usar apenas css e html não seria o suficiente pra essa funcionalidade. Principalmente porque queremos mexer no conteúdo da página depois que a página já carregou, mais especificamente, depois de um clique do usuário. Em casos como esse precisamos usar javascript e com certeza mexer com eventos. Vamos lá!

Nosso botão é esse aqui:

<button id="botaoSair">
	Sair
</button>

O balão/popup com uma mensagem bonitinha e botões de “Ok” e “Cancelar” será criado com a função confirm lá no javascript. Precisamos que a função seja executada quando o usuário clicar no botão. É comum dizer que queremos escutar o evento de clique. Podemos fazer isso das seguintes maneiras:

Atributos do HTML que lidam com eventos (event handler attributes)

Atributo da tag no html

<button id="botaoSair" onclick="confirmaSaida()">
	Sair
</button>
<script>
	function confirmaSaida(){
		confirm('Tem certeza de que quer sair?')
	}
</script>

Atributo da tag no javascript

botaoSair.onclick = confirmaSaida

function confirmaSaida(){
	confirm('Tem certeza de que quer sair?')
}

Usando o addEventListener do DOM

botaoSair.addEventListener('click', confirmaSaida)

function confirmaSaida(){
	confirm('Tem certeza de que quer sair?')
}

E a melhor maneira de escutar eventos é…

As duas maneiras tiveram o mesmo resultado pro usuário. Porém, é muito provável que você já tenha ouvido que usar os atributos do html é a maneira "errada" de escutar um evento. Tratamos de javascript e eventos nos cursos de front da Caelum. Tanto o curso introdutório quanto o avançado sempre geram boas discussões sobre esse assunto. Nas aulas e neste post não quero convencer você de nada. O que realmente quero é mostrar as perguntas que você sempre deve se fazer para escolher uma ou outra com plena consciência. Aqui vão as perguntas:

1. O evento que eu preciso escutar existe em todas as abordagens?

Os atributos do html e os tipos de eventos do DOM vêm de especificações diferentes da W3C. Temos partes em diferentes especificações que falam quais eventos devem existir nos navegadores:

Parte na spec do HTML5

Ela fala basicamente que todas as tags devem ter certos atributos para lidar com eventos. Esses atributos podem ser colocados tanto via javascript quanto no html mesmo. Os eventos que cada tag deve conseguir lidar estão relacionados nessa seção. Um browser compilante com HTML5 deve implementar tudo o que consta nessa tabela.

Parte na spec “UI Events”

A lista dos tipos de eventos é um rascunho no qual eles ainda estão trabalhando (Working Draft). Isso quer dizer que não há muitas garantias de que algum evento não será adicionado, modificado ou removido.

Parte na spec “Pointer Events”

Eventos como o mouseover assumem que todos os usuários navegam apenas com mouse. Essa spec criou novos eventos um pouco mais abstratos que padronizam os eventos que qualquer dispositivo com propósito de apontar pra algum ponto da página podem emitir.

Parte na spec do DOM

Essa parte trata de como os eventos devem ser ouvidos(addEventListener), disparados e manipulados. Porém, em nenhum momento definem quais tipos de eventos devem existir. O que ela diz é que qualquer um pode criar um tipo de evento que quiser, são o que chamamos de eventos customizados – no nosso curso de web apps nós vemos eles a fundo

E o que essas especificações querem dizer?

Nem todos os atributos que você pode escutar com addEventListener terão um atributo onalgumacoisa no html. Um exemplo são os Pointer Events que não têm nenhum atributo na spec do hmtl e também qualquer evento customizado que você tenha inventado no seu site. Todos esses eventos só poderão ser escutados com addEventListener

Para o nosso botão, podemos optar por qualquer uma das opções já que o evento de click pode ser usado das duas formas.

2. Eu estou separando as responsabilidades?

No front, o que eu quero dizer com separação de responsabilidades é, da maneira mais rasa possível, separar nosso código html do código javascript. O contrário do que fazemos quando utilizamos um atributo como o onclick.

Um argumento a favor de sempre separar os dois diz que código javascript dentro do html é um código mais difícil de compreender, e por consequência, de manter. Vou dar um exemplo.

No seguinte html, alguns botões quando clicados devem incrementar o valor de uma variável numeroDeClicks.

<header>
	<button>
		Login
	</button>
</header>
<main>
	... conteúdo da página
	<button onclick="numeroDeClicks++">
		Like
	</button>
	... conteúdo da página
</main>
<footer>
		<button onclick="numeroDeClicks++">
			Share
		</button>
</footer>
<script src="analytics.js"></script>

Perceba que temos um script analytics.js no final. A função dele é enviar várias informações e estatísticas para nossos servidores. Uma dessas estatísticas é o valor que está na variável numeroDeClicks.

O desenvolvedor que só faz o código de analytics.js não faz ideia de quais botões da página mudam o valor de numeroDeClicks. Se ele precisar remover ou adicionar algum desses botões às estatísticas de clicks ele vai ter que abrir o html, caçar o botão e tirar ou colocar o onclick.

Se você não quer caçar código javascript dentro do html, vá de addEventListener.

Cuidado, separar js de html nem sempre é separação de responsabilidades

Aqui na Caelum nós temos alguns casos em que gostamos bastante de usar atributos do html para lidar com eventos. O que mais gosto é o do logo do nosso site, lá no cabeçalho:

<img src="/imagens/caelum-logo.svg" alt="Caelum - Ensino e Inovação" onerror="this.src=this.src.replace('.svg', '.png'); this.onerror=null">

Nosso logo vem por padrão como um SVG. Browsers que não suportam SVG disparam um evento error na tag <img>. Ouvimos esse evento com um onerror, nele mudamos o src da imagem para um PNG.

Nosso onerror não mexe com nenhum outro código javascript. Apenas ajuda a definir o conteúdo do próprio elemento, o que com certeza é responsabilidade do html. Separar os dois não seria separação de responsabilidades e sim separação de linguagens (duas coisas diferentes).

E o nosso caso?

É bem provável que mais código, além do confirm(), seja executado dependendo da opção (“Cancelar” ou “Ok”) que o usuário escolher. Nesse caso, com o onclick poderíamos estar separando as responsabilidades e seria melhor usarmos o addEventListener

3. Quantas vezes vou usar a função confirmaSaida?

Geralmente, quando damos nome a uma função que está solta no código, estamos dizendo que ela pode e deve ser chamada diversas vezes em vários pontos do código. Basta qualquer parte do codigo executar confirmaSaida() que teremos o mesmo resultado de um clique no botão.

Deixar confirmaSaida disponível para qualquer um usar não é bem o que preciso. Se eu só vou usar essa função quando o usuário clicar no botão, posso passar a função como um parâmetro – dentro dos parênteses – do addEventListener:

botaoSair.addEventListener('click', function confirmaSaida(){
	confirm('Tem certeza de que quer sair?')
})

A função será criada apenas para ser passada para o addEventListener, ninguém além dele consegue executar confirmaSaida(). Proteger nossa função de ser chamada por outros códigos só é possível com addEventListener!

E por que não com o `onclick`?

Não é possível porque atributos como o onclick recebem um valor, não são uma função que você chama e passa parâmetros. Para exemplificar, podemos fazer qualquer um dos seguintes exemplos:

Atributo da tag no html

<button id="botaoSair" onclick="confirm('Tem certeza de que quer sair?')">
	Sair
</button>

Atributo da tag no javascript

botaoSair.onclick = function confirmaSaida(){
	confirm('Tem certeza de que quer sair?')
}

Nesses exemplos, nós estamos apenas dando um outro nome para a função confirmaSaida. Para chamar ela basta o seguinte em qualquer outro código:

botaoSair.onclick()

Se toda tag da página é acessível, seus atributos também são. Assim, confirmaSaida ficou acessível pra qualquer um. Se você não quer isso, vá de addEventListener

4. Alguma outra coisa precisa acontecer nesse evento?

Uma nova funcionalidade entrou no jogo. Agora, além de mostrar a mensagem uma outra equipe desenvolveu uma super função que vai mandar várias informações e estatísticas para nossos servidores. O código é grande e temos a função superEstatistica só pra ela.

botaoSair.onclick = function confirmaSaida(){
	confirm('Tem certeza de que quer sair?')
}

botaoSair.onclick = function superEstatistica(){
	//super código gigante
}

Nesse momento nosso usuário não vê mais a mensagem de confirmação quando clica no botão

botaoSair.onclick é uma propriedade que só pode ter um valor de cada vez. No nosso caso, o valor que ela tem é a função superEstatistica.

Se você precisa colocar mais de um comportamento num evento, acabou o sonho, vá de addEventListener:

botaoSair.addEventListener('click', function confirmaSaida(){
	confirm('Tem certeza de que quer sair?')
})

botaoSair.addEventListener('click', function superEstatistica(){
	//super código gigante
})

Moral da história

No fim, não existe uma solução absolutamente melhor. Em muitos casos as respostas dessas perguntas apoiarão o uso de addEventListener, só que isso não quer dizer que as respostas serão sempre as mesmas. No seu próximo projeto faça as 4 perguntas e com certeza você tomará uma decisão bem pensada. Se você lembrar de mais alguma pergunta/motivo pra escolher um dos métodos de escutar eventos, diga aí nos comentários!

6 Comentários

  1. Roberto Lima 04/04/2016 at 16:22 #

    Muito esclarecedor. Caramba, aposto que muita gente não conhecia varias dessas formas.

  2. Filipe 06/04/2016 at 23:14 #

    e no caso do angularJS, que possui a diretiva ‘ng-click’ (lembrando que o slogan do angular é ‘botar mais js no html, transformando sites em apps’), seria uma boa prática deixar de declarar esse callback no html com ng-click e passar a usar addEventListener nos controllers?

  3. M. França 12/04/2016 at 19:26 #

    Post “básico”, porém muito interessante e didático.
    Parabéns! =)

  4. Alan 01/04/2017 at 22:06 #

    Excelente explicação! Me ajudou de forma profunda a respeito do assunto em questão, criando também uma grande clareza em minha mente. Parabéns!

  5. Everton da Rosa 15/05/2017 at 08:48 #

    Dá sim pra fazer só com HTML e CSS, sem Javascript.

    http://codepen.io/everton3x/pen/YVvEow

  6. Artur Diniz Adam 19/05/2017 at 18:14 #

    Oi Everton,

    Dá pra fazer a confirmação mesmo! Já até editei o post =) Não escrevi, mas estava pensando na funcionalidade de fechar a janela do browser, com window.close(), que é uma instrução do JS.

    Dá pra incrementar seu codepen e colocar o link assim:

    <a href="javascript:window.close()" rel="nofollow">Sim</a>
    

Deixe uma resposta