22 Feb 2018
Procesador de eventos genérico para Google Tag Manager
Lectura: 19 mins.
|
Dificultad:

Procesador de eventos genérico para Google Tag Manager

Introducción

Una de las principales ventajas de Google Tag Manager es su seguimiento de eventos automático integrado.

No solamente podemos atar, mediante activadores, la ejecución de etiquetas a determinados momentos concretos distintos de la carga de página (clics, temporizadores…), sino que muchos de esos eventos ya se detectan y miden de forma automática desde GTM, permitiéndonos configurarlos como condiciones de activadores de forma sencilla y sin necesidad de incluir código adicional.

CHOOSE YOUR WEAPON

Aunque esto suele ser suficiente para cubrir la mayoría de casos comunes (clics en enlaces u otros elementos, envíos de formularios, tiempos de espera, cambios de historial…) y la lista va creciendo con el tiempo (vídeos, scroll, visibilidad…), tarde o temprano nos encontraremos con la necesidad de medir alguna interacción no cubierta o incompatible.

En esos casos, tendremos que depender de código externo o crear nuestros propios procesadores de Eventos (event listeners) personalizados: snippets de código Javascript, generalmente instalados en el sitio web directamente desde Tag Manager como etiquetas de HTML personalizado, que detectan las interacciones deseadas y generan eventos en la Data Layer o capa de datos —con o sin datos relevantes asociados—, y que a su vez podremos capturar y utilizar como activadores desde el lado de GTM.

Estos procesadores, que ya hemos visto como en algunos casos pueden ser bastante complejos por distintos factores (integración con aplicaciones web concretas, compatibilidad entre navegadores…), en otros pueden resultar sorprendentemente simples. ¿Por qué?

I see you…

Los navegadores web generan internamente múltiples eventos en forma de Eventos DOM, correspondientes a diversas interacciones con el DOM (Document Object Model o Modelo de Objetos del Documento): la representación estructurada del mismo y de sus elementos con la que podemos interactuar directamente via Javascript u otros lenguajes.

Y lo que es más importante… en navegadores actuales, todo esto se hace de forma (bueno, cada vez más 😅) estandarizada y fácilmente accesible mediante funciones comunes como addEventListener (que usaremos aquí).

Esto vamos a aprovecharlo para crear un procesador de eventos genérico, multiuso, que, con solo cambiar un par de líneas de configuración, nos sirva para cubrir múltiples tipos de casos en que una interacción no se pueda medir (aún) mediante eventos automáticos de GTM, pero sí mediante eventos DOM.

¿Suena bien? Pues adelante 🤓

La receta

Como lo normal es que muchos ya hayáis llegado hasta aquí con prisas y el Control+C a medio pulsar, vamos a empezar por lo importante. que es el código y las instrucciones para instalarlo, y luego ya si eso lo explicamos tranquilamente y vemos algunos ejemplos 😜.

Paso 1: Instalación

Crear una etiqueta de tipo HTML Personalizado y pegar el siguiente código:

<script>
(function() {
	var trkElements = '____'; // DOM Query de los elementos a medir, dejar en blanco para document
	var trkEvents = ['____','____','____'];  // Lista de eventos a medir
	var trkUseCapture = true;

	var trk = trkElements ? document.querySelectorAll(trkElements) : [document];
	for(i = 0, il = trk.length; i < il; i++) {
		for(j = 0, jl = trkEvents.length; j < jl; j++) {
			trk[i].addEventListener(trkEvents[j], function(e) {
				window.dataLayer = window.dataLayer || [];
				dataLayer.push({
					'event': e.type,
					'gtm.element': e.target,
					'gtm.elementClasses': e.target.className || '',
					'gtm.elementId': e.target.id || '',
					'gtm.elementTarget': e.target.target || '',
					'gtm.elementUrl': e.target.href || e.target.action || e.target.src || '',
					'domEvent': e
				});
				if({{Debug Mode}}) console.log(e);
			}, trkUseCapture);
		}
	}
})();
</script>

Dado que el script utiliza la variable integrada {{Debug Mode}}, asegúrate de tenerla activada (o de eliminar la línea de código correspondiente, tampoco es importante).

Paso 2: Configuración

Rellenar las variables del principio del script con los valores apropiados para lo que queramos medir:

  • En trkElements: la selección del elemento o elementos a observar, en formato de selector CSS Query: nombres de etiquetas, IDs (con #) y clases (con .) separados por comas, espacios para especificar descendientes, etc.

    También se puede dejar en blanco para lanzar el procesador de eventos directamente sobre el objeto global del documento, document. En algunos casos esto será necesario (porque solo funcione así, o no tenga sentido atar el procesador a elementos concretos); en otros, puede ser útil para medir todos los elementos de la página de forma global (normalmente, los listeners de un elemento se propagan a sus descendientes).

    ¡Ojo! Esto puede tener un gran impacto sobre el rendimiento de la página. Siempre que sea posible, es importante limitar el ámbito de los procesadores a las páginas, acciones y elementos concretos que necesitemos medir. En caso de duda, siempre se pueden crear procesadores “globales” durante el desarrollo y pruebas, y restringirlos después…
  • En trkEvents: la lista de nombres de eventos a medir, entre comillas y separados por comas. Podemos ver un listado de posibles eventos disponibles más abajo.
  • En trkUseCapture: según sea true o false cambia la forma en la que se procesan los eventos (muy básicamente, indica si lo hace “antes” o “después” durante la propagación del mismo).
    A efectos prácticos, podemos dejarlo en true para aumentar las posibilidades de que se registre sin problemas (al tomar precedencia sobre acciones que podrían cancelar el proceso), siempre que tengamos cuidado de comprobar que no ocurra a la inversa y sean nuestras etiquetas las que interfieran 😨.
Al activar useCapture, el evento salta en la fase “capture” en vez de en “bubbling”.

Paso 3: Activación

Asignar activador a la etiqueta (creándolo previamente, si fuera necesario) para que cargue junto con la página.

Normalmente, lo mejor será esperar al momento DOM preparado/DOM Ready para asegurarnos de que los elementos a procesar están disponibles, aunque también podremos probar a adelantarlo a Página vista/Page View si va directamente sobre document, o atrasarlo a Ventana cargada/Window Loaded si da problemas.

Si procede, será recomendable aprovechar para añadir condiciones adicionales basadas en {{Page URL}} y similares, para limitar la ejecución del procesador a las páginas o secciones del sitio web que nos interesen:

Ejemplo de trigger para procesador eventos DOM
Ejemplo de activador

¿Cómo funciona?

Este script coge una lista de elementos de la página (representado como una consulta en formato CSS query) y una lista de nombres de eventos, y crea los event listeners necesarios de forma que, cada vez que ocurre uno de los eventos a medir:

  • Se genera una entrada de dataLayer que propaga y hace saltar (con el mismo nombre) el evento correspondiente en GTM.
  • Se rellenan automáticamente las variables integradas asociadas al mismo con los datos del elemento objetivo: {{Click Element}} o {{Form Element}}, {{Click URL}}, {{Click Classes}}, etc.
Eventos DOM en la consola de debug GTM
Así irán apareciendo en la consola de depuración de GTM

Adicionalmente, se guarda un campo adicional domEvent con la referencia al evento DOM original completo. Esto se hace así para tener acceso completo desde la capa de datos a la estructura interna del evento DOM, que no es la misma en todos los casos y en algunos se registra información adicional potencialmente interesante en propiedades específicas (por ejemplo, la posición del ratón en los de tipo mouseover o códigos identificadores de botones en clics y pulsaciones de teclas).

Así, si queremos acceder a estos datos, solo necesitaremos crear variables de tipo Data Layer con los nombres apropiados.

Para ayudar a detectar e identificar estos datos, también se muestra la estructura del evento en la consola de depuración del navegador (F12 en Firefox y Chrome), si estamos en modo vista previa/depuración.

Así aparecerán en la consola. Revisándolos podremos ver fácilmente qué datos podemos extraer de cada uno

Ejemplos de eventos

La cantidad de interacciones que podemos medir automáticamente en forma de eventos DOM desde cualquier navegador actual es grande, variada, en continuo crecimiento y, sobre todo, bastante estandarizada a estas alturas: hasta hace relativamente poco, el uso de esta funcionalidad era limitado, complejo y/o inconsistente por incompatibilidades entre navegadores y versiones, pero creemos que la situación ya ha mejorado lo suficiente para que sea razonable ir dejando atrás lo antiguo y problemático…

No va por nadie en particular *cof* attachEvent *cof*, que va

Aquí va una lista de algunos de los ejemplos más interesantes:

load, abort, error

Al finalizar la carga de un elemento: una imagen, un iframe, un script externo, el documento completo… Corresponden a carga completa (incluido todo su contenido), carga abortada y error, respectivamente.

focus, blur

Cuando un elemento recibe o pierde el foco (ej., al entrar o salir de un campo de formulario).

change, input

Al cambiar el contenido de un campo de formulario u otro elemento editable; input se registra continuamente con cada cambio (ej., pulsación de tecla), mientras que change lo hace una vez al salir del campo (blur).

submit, reset

Al darle a enviar o a borrar datos de un formulario.
Muy útil este último para medir formularios diseñados en 1995 👴

Realmente, ni en 1995 se usaba. Y hablamos de tiempos oscuros de formularios simplones y pizza de masa gorda

click, dblclick, contextmenu, wheel

Al hacer clic, doble clic, clic derecho (menú) o mover la rueda del ratón sobre un elemento. Para seguimiento más avanzado de clics, también disponemos de mousedown y mouseup (pulsar/soltar cualquier botón).

mouseenter/mouseover, mousemove, mouseleave/mouseout

Al hacer “hover” o pasar el ratón por encima de un elemento: respectivamente, al entrar, mover (se registra repetidamente) y salir el puntero de su zona.

La diferencia entre mouseenter/mouseleave y mouseover/mouseout es que estos últimos saltan al entrar/salir al elemento seleccionado o a cualquiera de sus descendientes, con lo cual se pueden registrar múltiples veces dentro de un mismo elemento (al “cambiar” entre subelementos). En caso de duda, en la mayoría de casos preferiremos mouseenter/mouseleave, al ser su comportamiento más similar al de :hover en CSS.

dragstart, drag, drop

Al arrastrar, mantener arrastrando (se registra repetidamente) o soltar un elemento.

keydown/keypress, keyup

Al pulsar (o dejar pulsada, se registra repetidamente) o soltar una tecla. La diferencia entre keydown y keypress es que este último solo se registra para teclas que generan caracteres imprimibles, como letras, números o signos de puntuación, pero no teclas como Ctrl, Shift, cursores…

cut, copy, paste

Al cortar, copiar o pegar contenido.

play, pause, ended, playing, seeking, timeupdate…

Para medir interacciones con elementos de audio y vídeo.

scroll

Al hacer scroll el documento o alguno de sus elementos. Se registra repetidamente, así que necesitará procesado intermedio adicional para ser realmente útil —para eso disponemos de soluciones a medida tanto nativas como personalizadas—.

resize

Al cambiar el tamaño de la ventana del navegador. Ojo, este evento debe ir atado no al documento ni a ninguno de sus elementos sino directamente a la ventana (objeto window), con lo cual el código de su procesador de eventos debería ser algo similar a esto:

window.addEventListener('resize', function(e) {
	dataLayer.push({
		'event': ... // etc
	});
});

orientationchange

Al cambiar la pantalla de orientación. Como resize, debe ir atado a la ventana (window). Como este hay otros muchos y muy interesantes eventos orientados a dispositivos móviles (acciones táctiles, lectura de sensores…), pero se salen del tema del post.

fullscreenchange

Al entrar o salir de pantalla completa. Atar a document.

visibilitychange

Al cambiar el “estado de visibilidad” del documento, es decir, al ocultar o mostrar la pestaña del navegador correspondiente. Interesante, ¿verdad? 😀

Ejemplos prácticos

La mejor forma de entender cómo podemos crear procesadores personalizados para estos eventos es viendo ejemplos reales, así que vamos a ello.

Ejemplo práctico: Cambios en campos de formulario

Uno de los casos más básicos y comunes es detectar cuándo se rellenan campos individuales de un formulario y así medir con más detalle la experiencia del usuario entre la entrada y el envío (o no) del mismo: embudos, errores, abandonos…

Para ello, podremos utilizar eventos de cambio de foco (focus, blur) y de introducción de datos (input, change).

Por ejemplo, para detectar cuándo se ha elegido opción en un desplegable (etiqueta <select>) como el siguiente…

<select id="selector-importantisimo" name="tipo_tortilla" class="desplegable">
  <option selected="selected">Selecciona tortilla...</option>
  <option value="mal_gusto">Con cebolla</option>
  <option value="buen_gusto">Sin cebolla</option>
</select>

… podemos configurar un procesador a medida que monitorice su evento change.
La etiqueta HTML a crear podría tener este código:

<script>
(function() {
	var trkElements = '#selector-importantisimo'; // otras posibilidades serían '.desplegable', 'select.desplegable' o 'select[name="tipo_tortilla"]'
	var trkEvents = ['change'];  // Lista de eventos a medir
	var trkUseCapture = true;

	var trk = trkElements ? document.querySelectorAll(trkElements) : [document];
	for(i = 0, il = trk.length; i < il; i++) {
		for(j = 0, jl = trkEvents.length; j < jl; j++) {
			trk[i].addEventListener(trkEvents[j], function(e) {
				window.dataLayer = window.dataLayer || [];
				dataLayer.push({
					'event': e.type,
					'gtm.element': e.target,
					'gtm.elementClasses': e.target.className || '',
					'gtm.elementId': e.target.id || '',
					'gtm.elementTarget': e.target.target || '',
					'gtm.elementUrl': e.target.href || e.target.action || e.target.src || '',
					'domEvent': e
				});
				if({{Debug Mode}}) console.log(e);
			}, trkUseCapture);
		}
	}
})();
</script>

También podríamos configurar trkElements como ‘select’ para que se midan todos los <select> en general (o incluso ‘input,select,textarea‘ para varios tipos de controles de formulario) y distinguir los que nos interesen a nivel de activador.
En cualquier caso, un posible activador para el nuevo evento sería este:

Ejemplo de trigger change

Adicionalmente (BONUS TRACK! 👨🏼‍🏫), si queremos capturar el valor seleccionado para utilizarlo en alguna etiqueta o activador personalizado, podemos crear una variable {{Click Value}} como esta, que devuelva en tiempo real el valor (propiedad value) del elemento actual:

Variable GTM element value
Funcionará con casi todo tipo de campos <input> y <select>

Ejemplo práctico: Hover

Un ejemplo sencillo de los eventos de tipo mouseover. Supongamos que hemos añadido a algunas de nuestras páginas un pequeño popup tipo cajita de notificación, como este:

Ejemplo popup a medir
Cualquier parecido con la coincidencia es pura realidad

Por el motivo que sea (crear un embudo de conversión detallado, diagnosticar por qué no lo usa ni el tato, aburrimiento académico, yo que sé 😜), queremos medir cuando un usuario pasa el raton por encima de la notificación.

Para identificar el elemento, lo inspeccionamos en la consola del navegador (Click derecho > Inspeccionar o equivalente) y vemos que este es su código fuente:

<div id="modal-cansina" class="modal modal-bottom-right">
    <div class="modal-content">
        <div class="modal-close"></div>
        <div class="modal-content-wrapper">
            <div class="modal-content-body vendo-opel-corsa">
                <h4>¿Quieres recibir nuestros artículos sobre Analítica y Estrategia digital cada mes en tu buzón de correo?</h4>
                <div class="modal-content-cta"><button class="btn bbq ogt">¡Suscríbeme!</button></div>
            </div>
        </div>
    </div>
</div>

Como solo nos interesa el recuadro completo y no sus componentes, bastará con fijarnos en el contenedor exterior visible, que en este caso podremos identificar por sus clases (.modal.modal-bottom-right) o, mejor aún, si es el único que queremos observar, su ID único (#modal-cansina).

Como evento a medir nos servirá con mouseenter, ya que nos basta con medir la “entrada” en el mismo y nos da igual la interacción con elementos interiores (texto, botones…).

La configuración del procesador correspondiente, por tanto, sería:

var trkElements = '#modal-cansina';
var trkEvents = ['mouseenter'];
var useCapture = false;

Y el resto igual. Nótese que en este caso particular hemos desactivado useCapture —es para evitar que el evento se propague a (y se active en) sus descendientes—, pero realmente tampoco es importante, ya que en cualquier caso lo solucionamos a continuación.

Para el activador, de nuevo, bastará con crear uno para el evento personalizado mouseenter, junto con la correspondiente condición adicional recomendada para asegurarnos, de nuevo basada en Click ID:

Ejemplo de trigger mouseenter
De verdad necesitabas otro pantallazo? Si es igual que el ejemplo anterior…

Un detalle adicional a tener en cuenta: dependiendo de qué etiqueta queramos asociar al evento (¿un evento de Google Analytics, tal vez?) normalmente solo queremos que salte una vez por página, no cada vez que el puntero “sale y vuelve a entrar”.

Para solucionar esto hay varias posibles soluciones, pero la más sencilla será limitarlo directamente en la propia etiqueta, en su Configuración avanzada:

Configurar etiqueta: activar una vez por pagina

Ejemplo práctico: Pulsaciones de teclas

Para detectar pulsaciones de teclado, bastará con utilizar alguno de los eventos relacionados y —salvo que queramos limitarlo a alguna caja de texto u otro elemento concreto— atarlo globalmente a document.

En la mayoría de casos, lo habitual es utilizar keyup en lugar de keydown/keypress, para evitar de forma sencilla que el evento se registre repetidamente al mantener una tecla pulsada.

El procesador a crear, por tanto, se configuraría así:

var trkElements = '';
var trkEvents = ['keyup'];
(...resto igual...)

Como podremos ver en la consola de depuración, el evento DOM generado (KeyboardEvent) tendrá varias propiedades (code, keyCode, key…) que nos permitirán identificar la tecla concreta pulsada y así utilizar este dato en condiciones y etiquetas.

Utilizaremos key, que devuelve un código en “texto legible” de la tecla. Para tenerlo a mano, crearemos la siguiente variable GTM de tipo Capa de Datos:

Variable GTM - Key

Con esto, ya podremos crear activadores basados en pulsaciones de teclas. 🙂

Por ejemplo, para detectar que el usuario ha intentado recargar la página pulsando F5:

Trigger GTM - Tecla F5

O para detectar que ha intentado sacar un pantallazo pulsando Print Screen (aka PrntScr, Impr Pant o alguna otra abreviatura similarmente ilegible):

Trigger GTM - Tecla PrntScrn

Ejemplo práctico: Copiar texto

Para detectar una operación de copiar contenido (sea con Ctrl/Cmd+C, con Menú > Copiar o por otros medios), el evento será copy y el elemento puede ser alguno concreto o document para cualquier parte de la página:

var trkElements = '';
var trkEvents = ['copy'];
(...)

Y por tanto, el trigger correspondiente:

Si queremos capturar qué contenido es el que se ha copiado, la cosa se complica un poco. El objeto del evento DOM no incluye información sobre el contenido concreto que se ha copiado, ni generalmente podremos acceder directamente al portapapeles para recuperarlo.

Sin embargo, sí que tendremos acceso al elemento directamente afectado (el párrafo, enlace, caja de texto…) en las variables de evento automático, con lo que podremos utilizar, por ejemplo, {{Click Text}} para obtener el texto (o representación textual) y utilizarlo en activadores o etiquetas:

Ojo con ponerlo así sin más, que podría ser una parrafada…

Pero, ¿y si solo hemos seleccionado parte del texto? ¿Y si la selección incluye varios párrafos u otros elementos?

La respuesta es que la referencia será al primero de los elementos y que {{Click Text}} devolverá el texto completo del mismo, independientemente de lo seleccionado. Así que habrá que tenerlo en cuenta, aunque como dato aproximado quizá nos pueda valer.

Bueh, aceptamos barco

Recomendaciones

Para terminar, unos pequeños consejos generales:

  • Si no esta roto, no lo arregles. Si GTM ya tiene una solución nativa que podría valer, pruébala primero y asegúrate al 100% de que te sirva o no: habrá problemas e incompatibilidades comunes que ya se habrá encargado de solucionar. ¿No funciona un clic en enlace o un envío de formulario? Prueba a desactivar (o activar) Comprobar Validación u otras opciones, comprueba que no hay condiciones adicionales que son las que hacen que no se active, en caso de duda crea un activador genérico (sin condiciones adicionales ni etiquetas asociadas) y comprueba si el evento salta. ¡Haz todo esto antes de lanzarte a soluciones personalizadas!
  • Investigar y testear. ¿Me servirá esto para lo que necesito medir? Crear una copia temporal sin publicar del procesador genérico, configurada en global y con varios tipos de eventos que sospechemos nos puedan servir, y hacer pruebas en modo de vista previa/depuración revisando los datos que nos devuelvan ambas consolas (GTM y navegador) nos puede dar respuestas muy rápido.
  • Acotar y optimizar. Si solo necesitas medir una instancia concreta de un evento, restringirlo en la propia configuración del procesador será más eficiente para el rendimiento que hacerlo a nivel de activador. Un procesador mal configurado puede saturar GTM, Analytics o incluso el navegador del usuario con eventos innecesarios. En caso de duda, configurar inicialmente en global (document + DOM todas las páginas) durante el desarrollo y restringir a posteriori, justo antes de publicar, es una buena idea.
  • Ojo a incompatibilidades e inconsistencias. Algunos eventos aún están en fase experimental y puede que no funcionen en todos los navegadores (o que no devuelvan los mismos datos), que cambie su funcionamiento o estructura con el tiempo, o incluso que queden obsoletos. Ante la duda, testear siempre en varios navegadores, y monitorizar regularmente los casos de los que dependa funcionalidad crítica.

Enlaces de interés