Tratamento de eventos

Este tutorial mostra como usar a biblioteca closure para detectar e responder a eventos JavaScript. Você verá como os eventos funcionam em geral e aprenderá técnicas específicas de JavaScript para tornar o código escalonável e de fácil manutenção.

Eventos em JavaScript e Biblioteca de fechamento

Quando os usuários digitam ou usam o mouse em programas JavaScript, o JavaScript gera objetos de evento para representar as ações do usuário. Esses eventos são distribuídos para objetos DOM. Para fazer com que os objetos DOM respondam a eventos, anexe listeners de eventos a eles, que são simplesmente funções JavaScript. No programa, você especifica os listeners de eventos e os eventos que eles vão processar. Por exemplo, quando o usuário clica em um link, um evento de clique é enviado ao listener de clique do objeto DOM que representa o link.

O modelo de evento faz parte do JavaScript, mas vários navegadores implementam eventos de maneiras diferentes. Por exemplo, a maioria dos navegadores transmite um objeto Evento como um parâmetro para funções de listener, mas o Internet Explorer armazena um objeto Evento em uma propriedade Janela em vez de passá-lo como parâmetro.

Essa biblioteca oferece um modelo consistente de eventos que funciona da mesma forma em todos os navegadores. A closure Library implementa o próprio modelo de evento que oferece comportamento uniforme ocultando as variações dos modelos de eventos de diferentes navegadores. Portanto, você só precisa considerar um conjunto de comportamentos ao programar seu programa. Além disso, o modelo de evento da biblioteca Frontend corrige problemas em navegadores, como possíveis vazamentos de memória em funções aninhadas no Internet Explorer.

Como os eventos funcionam

O processo de manipulação de eventos da biblioteca closure começa quando ocorre uma ação que gera um evento, como o clique do usuário em um link. Um objeto de evento de clique é criado para representar o evento, e o destino do evento é determinado. Nesse caso, o destino é o link em que o usuário clicou.

As propriedades do objeto do evento contêm informações relevantes sobre o evento. O objeto de evento é transmitido como um argumento quando listeners de eventos são chamados. A biblioteca Encerramento representa eventos de navegador com objetos de evento de classe goog.events.BrowserEvent.

Os eventos são enviados em duas fases: primeiro, a fase de captura, e depois a de balão. Na fase de captura, o evento é enviado primeiro para o elemento raiz do DOM e depois para a hierarquia DOM até que o destino seja atingido. Todos os elementos que estão ouvindo eventos de fase de captura terão seus listeners chamados na ordem de hierarquia DOM durante essa fase.

Depois que a fase de captura for concluída, a fase de balão será realizada. Na fase de bolha, o evento é enviado ao destino do evento e, em seguida, para cima na hierarquia do DOM até o elemento raiz. Nesta fase, os listeners que ouvem eventos de fase de balão e listeners sem uma fase atribuída são chamados.

Na Biblioteca outro fechamento, você usa goog.events.listen() para atribuir um listener de eventos a um objeto. A assinatura completa do listen() é:

  goog.events.listen(eventSource, eventType, listener, [capturePhase, [handler]]);

  • eventSource é o objeto DOM a que você quer que o listener de eventos seja anexado.
  • eventType é uma string ou matriz de strings que definem os tipos de eventos que acionam o listener.
  • listener é o próprio listener de eventos, ou seja, a função que será chamada quando o evento especificado for enviado ao objeto.
  • capturePhase é um parâmetro opcional que precisa ser definido como true se você quiser que o listener seja chamado apenas durante a fase de captura.
  • handler é um parâmetro opcional que permite especificar qual objeto é representado por this na função do listener.

Processar eventos

Nesta seção, vamos criar um aplicativo de bloco de notas de exemplo que abre um texto para edição quando o usuário clica no texto. O app será baseado no tutorial anterior. Os arquivos de origem deste tutorial estão disponíveis aqui:

Nosso exemplo de bloco de notas mostra texto como elementos do documento. Para tornar o texto editável, queremos revelar um elemento <textarea> para editar o texto quando o usuário clicar nele. Para fazer isso, vamos anexar um listener de eventos ao elemento de conteúdo de texto quando criarmos o elemento. Um listener de eventos é simplesmente uma função que é chamada quando ocorre um evento especificado.

A biblioteca Encerramento tem sua própria estrutura de eventos que resolve as incompatibilidades nos modelos de evento de diferentes navegadores. Para usar esse framework, chame goog.events.listen() para anexar uma função de listener ao elemento.

Por exemplo, esta chamada para goog.events.listen() garante que um clique em uma nota acione uma chamada para openEditor():

goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor);

O primeiro argumento é o elemento em que queremos detectar eventos. O segundo argumento é o tipo de evento que estamos ouvindo. O terceiro argumento é a função a ser chamada quando ocorre o evento especificado.

Veja um exemplo de função de listener que oculta o elemento de conteúdo e exibe o elemento do editor:

tutorial.notepad.Note.prototype.openEditor = function(e) {
  var elt = e.target;

  // Get the current contents of the note text Element, so we can put it into
  // the editor field.
  var content = goog.dom.getTextContent(elt);

  // Given the way we've built our DOM structure, the editor div
  // will be the next Element after the note text Element.
  var editorContainer = goog.dom.getNextElementSibling(elt);
  var editor = goog.dom.getFirstElementChild(editorContainer);

  // Put the note contents into the editor field.
  editor.innerHTML = content;

  // Hide the note text Element and show the editor.
  elt.style.display = "none";
  editorContainer.style.display = "inline";
};

O arquivo notepad2_1.js contém a versão atualizada do nosso exemplo.

Como usar métodos de instância

Na função openEditor, precisamos encontrar o conteúdo e os elementos do editor sempre que a função for chamada. Encontramos esses elementos examinando a estrutura DOM. Mas já armazenamos referências a esses elementos como campos de instância em nossos objetos Note quando os criamos:

tutorial.notepad.Note.prototype.makeNoteDom = function() {
  // Create DOM structure to represent the note.
  this.headerElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.title);
  this.contentElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.content);

  // Create the editor text area and save button.
  this.editorElement = goog.dom.createDom(goog.dom.TagName.TEXTAREA, null, '');
  ...
}

Por que não podemos usar essas referências no corpo da openEditor()? Por padrão, um listener de eventos é executado no contexto do destino do evento. Em outras palavras, quando o usuário clica no elemento de nota, this se refere ao elemento no corpo da openEditor(), mesmo que a openEditor() seja um método da Note.

A Biblioteca Encerramento fornece uma solução para esse problema. Para permitir o uso de métodos de objeto como listeners de eventos, a closure Library permite um objeto de contexto como um parâmetro opcional para goog.events.listen(). Por exemplo, a chamada a seguir anexa o listener openEditor() de forma que this se refira ao objeto Note no corpo da openEditor():

goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor, false, this);

O quinto argumento fornece o objeto para ser exibido como this no corpo do listener.

É possível anexar nossos listeners ao mesmo método makeNoteDom() em que criamos os elementos. No exemplo, substitua tutorial.notepad.Note.prototype.makeNoteDom() pelo seguinte método:

tutorial.notepad.Note.prototype.makeNoteDom = function() {
  // Create DOM structure to represent the note.
  this.headerElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.title);
  this.contentElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.content);

  // Create the editor text area and save button.
  this.editorElement = goog.dom.createDom(goog.dom.TagName.TEXTAREA, null, '');
  var saveBtn = goog.dom.createDom(goog.dom.TagName.INPUT,
      {'type': 'button', 'value': 'Save'}, '');
  this.editorContainer = goog.dom.createDom(goog.dom.TagName.DIV, {'style': 'display:none;'},
      this.editorElement, saveBtn);

  this.contentContainer = goog.dom.createDom(goog.dom.TagName.DIV, null,
      this.contentElement, this.editorContainer);

  // Wrap the editor and the content div in a single parent so they can
  // be toggled in unison.
  var newNote = goog.dom.createDom(goog.dom.TagName.DIV, null,
      this.headerElement, this.contentContainer);

  // Add the note's DOM structure to the document.
  this.parent.appendChild(newNote);

  // Attach the event listener that opens the editor.
  // CHANGED: We need to preserve the meaning of 'this' when the listener is called.
  goog.events.listen(this.contentElement, goog.events.EventType.CLICK,
      this.openEditor, false, this);

  // NEW:
  goog.events.listen(saveBtn, goog.events.EventType.CLICK,
      this.save, false, this);

  // Attach the Zippy behavior.
  this.zippy =  new goog.ui.Zippy(this.headerElement, this.contentContainer);
};

Como a biblioteca closure permite preservar o significado de this em nossos listeners, podemos torná-los muito simples. Adicione os seguintes métodos abaixo de makeNoteDom():

tutorial.notepad.Note.prototype.save = function(e) {
  this.content = this.editorElement.value;
  this.closeEditor();
};

tutorial.notepad.Note.prototype.closeEditor = function() {
  this.contentElement.innerHTML = this.content;
  this.contentElement.style.display = "inline";
  this.editorContainer.style.display = "none";
};

tutorial.notepad.Note.prototype.openEditor = function(e) {
  this.editorElement.value = this.content;
  this.contentElement.style.display = "none";
  this.editorContainer.style.display = "inline";
};

Nosso exemplo de bloco de notas agora pode editar e salvar notas. Seus arquivos vão ficar assim:

  • notepad2_2.js (link em inglês)
  • notepad2_2.html (não modificamos o arquivo .html neste tutorial) Para sua comodidade, ela foi revinculada aqui.