Lo que hay debajo

La mayoría de los frameworks modernos utilizan algún tipo de atributo añadido a los elementos para indicar como dibujar o que controlador asignar a dicho elemento. Por ejemplo, en jQueryMobile y en bootstrap se utilizan los atributos “data-“ para indicar roles, estilos, etc.; de la misma manera, en AngularJS se asignan atributos con el prefijo “ng-“ para asignar, por ejemplo, un controlador a un <div> concreto. No es la única manera de “dopar” la página; la mayoría de las librerías tienen métodos para aplicar un behaviour concreto a un objeto de nuestra página.

Pero volviendo al sistema “cómodo”… ¿cómo funciona este mecanismo que traduce los atributos de un elemento en estilos, directivas, controladores…? Cada librería utiliza su propio sistema, lógicamente. Por ejemplo, en AngularJS cada elemento al que se aplica una directiva o controlador tiene su propio contexto ($scope).

Aquí vamos a imitar el comportamiento de estas librerías creando un atributo para aplicar estilos y otro para aplicar un evento click. Sin utilizar ninguna librería: el objetivo de este ejercicio es aprender cómo funcionan, a un nivel muy básico, los mecanismos que utilizan atributos para extiende la funcionalidad o el estilo de una página web. Todo se reduce a seleccionar elementos del DOM y a leer y modificar atributos de los mismos.

Empecemos con un poco de HTML. En algunos elementos se ha añadido un atributo tag-role. En nuestra imaginaria librería este atributo simplemente aplica un estilo al elemento.

<html>
<head>
<title>Tag lib sample</title>
<body>
    <div id="one" tag-role="header">
	<h1>Test</h1>
    </div>

    <div tag-role="content">
	<button id="first" tag-click="btnAlert">Click Me</button>
	<button id="second" tag-click="btnAlert">Click Me Too</button>
    </div>
</body>
</html>

Aplicando estilos

En un proyecto normal tendríamos los estilos en su propio archivo .css y, por supuesto, no sería tan simple como añadir un único estilo al elemento. Pero, por simplificar, es precisamente esto lo que haremos: añadir un elemento <style> en la cabecera de la página y un elemento <script> al final del archivo en el que iremos añadiendo el Javascript que compone nuestra “librería”

function applyStyles () {
    var arrElements = document.getElementsByTagName('div'),
	numElements = arrElements.length,
	strRole, i;

    for(i = 0; i < numElements; i++) {
	strRole = arrElements[i].getAttribute('tag-role');
	if(strRole) {
		arrElements[i].setAttribute('class', 'tag-' + strRole);
	}
    }
}

Este método empieza por buscar en el documento todos los elementos con el tag <div>. A continuación recorre la lista de elementos (no es un Array, por eso no se puede utilizar el método forEach) y, para cada elemento, se intenta leer el atributo tag-role. Si dicho atributo existe, se añade un atributo class con el nombre del role y el prefijo ‘tag-‘. Muy simple. En un framework real, por supuesto, habría más cosas que tener en cuenta, otras modificaciones, quizás añadir más elementos para rodear el original como hace, por ejemplo, jQuery Mobile.

La clave en este método y el siguiente son los métodos getElementsByTagName() y getAttribute(). El primero devuelve todos los elementos del DOM que tienen el tag (div, p, h1, input…) que se pasa como parámetro, o todos si el parámetro es *; si se necesitan sólo determinados tag, se pasa cada tag en un parámetro: getElementsByTagName(‘div’, ‘img’, ‘button’). El segundo método devuelve el valor del atributo que se pasa como parámetro si dicho atributo está definido para el elemento. Una librería utilizará mecanismos más sofisticados para navegar por el DOM y recuperar los elementos que se quieren extender, pero en el fondo todos esos sistemas recurren a getElementsByTagName() o alguno de los métodos para selección de elementos de DOM que todos los navegadores tienen.

Asignando un controlador

El segundo atributo que hemos inventado es tag-click; añade un listener para el evento click que ejecuta la función indicada en el valor del atributo.

function createClickListener (strController, objItem) {
    objItem.addEventListener('click', 
			function () { 
				window[strController](objItem);
			},
			false);
}

function applyControllers () {
    var arrElements = document.getElementsByTagName('*'),
	numElements = arrElements.length,
	strController,
	i;

    for(i = 0; i < numElements; i++) {
	strController = arrElements[i].getAttribute('tag-click');
	if(strController) {
		createClickListener(strController, arrElements[i]);
	}
    }
}

El método applyControllers() busca todos los elementos del documento dado que queremos aplicar el controlador a cualquier tipo de tag: divs, buttons, etc. A continuación busca en cada elemento el atributo tag-click y usa el valor del contenido del atributo como nombre de una función global que se ejecutará cada vez que se haga clic sobre el elemento. En el caso que nos ocupa se está usando el mismo método global en los dos casos, btnAlert(). Nuestra librería le envía el elemento sobre el que el usuario ha hecho clic.

function btnAlert (objItem) {
    alert(objItem.id);
}

Nuevamente, una librería real hace muchas más cosas. Por ejemplo, al aplicar un controlador a un elemento, AngularJS crea un contexto para dicho elemento, que pasa al controlador.

Concluyendo

Para completar el ejemplo habría que encapsular todos los métodos de la “librería” y añadir a la página el código necesario para que se apliquen las modificaciones al código. En el archivo adjunto está el ejemplo listo para funcionar. No hemos creado el siguiente gran framework. La intención es únicamente ayudar a entender que es lo que hacen por debajo las librerías y frameworks que utiliza prácticamente todo el mundo. No es preciso saber como funcionan dichos frameworks línea a línea. Pero siempre es útil conocer las bases.

El código completado y operativo está disponible en este enlace.