Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:manipulacion_dinamica_elementos_navegador

¡Esta es una revisión vieja del documento!


Manipulación de elementos en el navegador - BOM y DOM

Módulo perteneciente al curso Programación avanzada con JavaScript y ECMAScript.

Introducción

Hasta ahora hemos aprendido mucho sobre JavaScript como lenguaje de programación. Pero JavaScript sólo tiene sentido trabajando dentro de un determinado entorno y con un determinado propósito. En el caso que nos ocupa en este curso, este entorno es el navegador web, y el propósito es dotar de interactividad a las páginas web.

Todo lo aprendido en los módulos anteriores es válido para cualquier entorno que use JavaScript, como por ejemplo Node.js, ya que se trataba del lenguaje, “puro y duro”. Ahora vamos a centrarnos en las particularidades de usar el lenguaje en un navegador de Internet, que es seguramente el entorno más común en el que vamos a emplearlo.

Por el camino ya hemos visto algunas cuestiones puntuales relacionadas con los navegadores y su interacción mediante JavaScript. En los siguientes temas aprenderemos mucho más sobre cómo interacciona el lenguaje con los elementos de una página y con los usuarios, dotándolo por fin del contexto y propósito necesarios.

Pero antes de comenzar es necesario conocer brevemente la historia de los navegadores web y cómo ésta nos impacta como desarrolladores.

DOM

Los orígenes del DOM - Un poco de historia

Durante la segunda mitad de la década de los '90 del siglo pasado se vivió la guerra de los navegadores (“browser wars”). Fue una guerra sin sangre pero cruenta y no exenta de víctimas, la principal de las cuales fue Netscape Navigator, el primer navegador comercial, que dominó la Internet de los primeros años.

Microsoft había menospreciado en gran medida el impacto que Internet en general y la web en particular iban a desempeñar en el mundo del futuro, lo cual abrió el camino para que Netscape floreciera. En pocos años se convirtió en el navegador por defecto de todo el mundo, utilizado por la práctica totalidad de los escasos millones de usuarios que tenía la entonces incipiente World Wide Web. Cuando Microsoft se quiso dar cuenta y contraatacar para no perder el tren de la Web tuvo que hacer uso de toda su artillería pesada.

Su principal baza fue incluir de serie su navegador, Internet Explorer, en sus sistemas operativos, de modo que los usuarios lo usasen por defecto y confiando en la resistencia al cambio de la mayoría de éstos. Funcionó. En pocos años superó a Netscape, obligándolos a regalar el producto, y con el tiempo acabó por barrerlo del mercado. Esta estrategia le pasó factura también a Microsoft, que sufrió varios procesos de investigación por abuso de poder monopolista, tanto en EEUU como en Europa, tuvo que pagar cuantiosas multas y a punto estuvo de ser obligada a dividirse en varias empresas más pequeñas.

Esta guerra no se acabó con el fin de Netscape, sino que se ha venido repitiendo en oleadas de distinta intensidad desde entonces. Primero con Mozilla Firefox, un proyecto derivado del Netscape original que casi logra barrer de nuevo a Internet Explorer del mercado, y en los últimos años con la entrada de Google Chrome que ha ido acaparando poco a poco el mayor número de usuarios de escritorio y que ahora es el líder indiscutible.

Algo parecido se vive también en el ámbito de los dispositivos móviles (tabletas y teléfonos). Lo bueno de estas guerras más recientes es que son positivas para los usuarios, pues todos persiguen tener el mejor producto, el más rápido y el más compatible con los estándares. Sin embargo, en la primera guerra de los navegadores la cosa fue justo al contrario, y eso lo estamos pagando los desarrolladores desde entonces.

Es un tema muy interesante y si tienes interés puedes leer un artículo bastante detallado en la Wikipedia. Lo que a nosotros nos interesa ahora es cómo estas guerras influyeron en los modelos que actualmente nos permiten programar para los navegadores.

El BOM o DOM Level 0

BOM

Cuando Netscape lanzó JavaScript en septiembre de 1995 necesitaba alguna forma de permitir que el lenguaje interactuase con los elementos de las páginas. Para ello creó un modelo de objetos relacionados que representaban los elementos más importantes del navegador: la ventana, los enlaces, los controles de un formulario, etc… así como un mecanismo de reacción ante eventos para detectar desde el código las acciones de los usuarios.

Microsoft lanzó casi un año más tarde la versión 3 de su Internet Explorer (agosto de 1996). Dado que para entonces miles de Webs estaban utilizando el modelo de objetos de Netscape, se vieron obligados a incorporarlo también para conseguir la mayor compatibilidad, así que estaba también soportado por este navegador (si bien introdujeron su propia versión de JavaScript denominada JScript y un lenguaje ya desaparecido en la práctica pero aún soportado por IE, denominado VBScript).

Este modelo de objetos original es lo que se conoce como Browser Object Model o BOM, y lo que algunos denominan también DOM Level 0.

Sigue siendo útil en cierta medida, y todos los navegadores modernos lo soportan. Como es sencillo de utilizar podemos sacarle partido muy rápidamente y lo usaremos a veces, sobre todo para interactuar con formularios web.

El BOM no está estandarizado ya que no existe ninguna especificación oficial al respecto (aunque está descrito parcialmente en la especificación de HTML 4), pero es tan sencillo que las implementaciones existentes en todos los navegadores funcionan bien y sobre todo de manera consistente, así que no debemos menospreciarlo a pesar de que sea tan antiguo.

Los DOMs intermedios y el DOM Level 1

Cuando en verano de 1997 aparecieron en el mercado las versiones 4.0 de los dos navegadores, la tendencia hacia la interactividad en la web era imparable. La palabra de moda por aquel entonces era DHTML (Dynamic HTML) que no era más que el uso conjunto de HTML, CSS y JavaScript para dotar de interactividad a las páginas.

En ese momento ya no era suficiente con el modelo sencillo de objetos que existía en las versiones anteriores. Ahora era necesario, además, crear capas de contenidos que pudieran moverse, así como acceder y modificar cualquier elemento de una página, no solo los enlaces, las imágenes y los formularios.

Ambas empresas, en un afán de traer usuarios hacia sus plataformas, implementaron esa funcionalidad de manera diferente e incompatible. Netscape ofrecía acceso a las capas mediante la propiedad document.layers y Microsoft con document.all. A partir de ahí las diferencias eran también grandes.

En este momento había que elegir si desarrollabas con el modelo de uno, del otro o –como sucedía casi siempre- hacías el doble de trabajo generando una versión de la página que funcionase en ambos. Un infierno para los desarrolladores y algo muy malo para la Web, que veía como se rompía la unicidad de cómo generar contenidos y se le añadía una complejidad innecesaria.

Mientras tanto el World Wide Web Consortium (W3C) trabajaba en la definición de un modelo unificado para la Web, tanto para JavaScript (que resultó en el estándar ECMAScript) como para el acceso a los contenidos de los documentos. A este último se le denominó DOM o Document Object Model.

Inicialmente estaba pensado para trabajar con documentos XML, y si había otra palabra de moda por aquella época ésta era sin duda eXtensible Markup Language. Así que tanto Netscape como Microsoft incorporaron el DOM a sus siguientes versiones del navegador, a finales de los '90. Netscape, en un movimiento valiente, abandonó por completo su modelo anterior basado en document.layers, mientras que Microsoft soportó su modelo intermedio hasta la última versión de Internet Explorer, por razones de compatibilidad, pero eso es cosa del pasado 😉

Como los documentos HTML pueden considerarse, con ciertas salvedades, también documentos XML, este primer DOM Level 1 está soportado para todas las páginas Web.

Proporciona una representación jerárquica de todos los elementos que forman parte de una página, y facilita a través de JavaScript métodos para poder navegar por esa jerarquía y acceder a cualquier elemento y modificarlo.

Los DOM Level 2, Level 3 y Level 4

A finales del año 2000 la W3C presentó una nueva versión del DOM, la Level 2, que revisaba, corregía y ampliaba al nivel 1. Sus principales novedades fueron la posibilidad de buscar elementos directamente sin tener que navegar por la jerarquía paso a paso, un modelo no intrusivo para los eventos de la página y el soporte de espacios de nombres XML.

Posteriormente, en abril de 2004, apareció el nivel 3 del DOM que añadía soporte para gestionar eventos del teclado, y varias características para trabajar con XML desde JavaScript.

A finales de 2015 se publicó el nivel 4 del DOM que amplió los niveles 2 y 3, pero se centró principalmente en la interacción táctil con los documentos web en lugar de la estructura o las relaciones de los nodos. Un añadido significativo fue la descripción de la recolección de basura de elementos no utilizados, que anteriormente se dejaba para la implementación de intérpretes de JavaScript, pero que nos influye como desarrolladores a la hora de programar.

HTML 5

HTML5, aunque no está directamente vinculado a los niveles de DOM, se refiere a la quinta revisión importante del estándar HTML, aparecida en octubre de 2014. Trajo cambios significativos a las capacidades de la web, incluido un mejor soporte para multimedia, gráficos y formularios, así como un mejor manejo de errores y marcado semántico. Si bien el DOM en sí evoluciona de forma independiente, HTML5 a menudo requiere actualizaciones del DOM para aprovechar al máximo sus nuevas características.

En este módulo vamos a estudiar cómo acceder a los elementos de una página a través del BOM y del DOM, explicando las diferencias existentes entre navegadores y centrándonos en la funcionalidad más extendida y los casos más comunes, para poder programar aplicaciones que funcionen en todos los navegadores.

Trabajo con el BOM o DOM Level 0

Como hemos visto en la breve reseña histórica anterior, existe un común denominador a todos los navegadores con el que podemos trabajar: el BOM o DOM Level 0.

Si bien este modelo de objetos del navegador es bastante limitado, y ha sido superado con creces por los diferentes niveles del DOM especificados por el W3C, posee varias ventajas que hacen que sea recomendable utilizarlo en muchas ocasiones:

  • Está ampliamente soportado. Todos los navegadores del mercado lo implementan de manera coherente, por lo que si lo utilizamos en nuestro código podemos estar seguros de que va a funcionar bien en cualquier navegador, incluso en los más antiguos.
  • Permite el acceso directo y rápido a los formularios que rellenan los usuarios, por lo que gracias a él podemos ir más ágiles en ciertas validaciones y manipulaciones de este tipo de elementos.
  • Nos da acceso a objetos intrínsecos del navegador y su entorno, como la propia ventana de éste, la resolución de la pantalla o el historial de navegación.

Es un modelo jerárquico muy sencillo que se ve representado en la figura siguiente.

BOM-Navegador

Aquellas ramas de la figura cuyo nombre termina con letra 's', se refieren a colecciones de elementos que contienen a su vez referencias a otros objetos. Así por ejemplo, la colección forms contiene referencias a objetos que representa a los formularios que posee el documento.

A continuación vamos a estudiar todos estos objetos para ver cómo podemos sacarles partido. Dado que todos ellos poseen una serie de propiedades y métodos que vamos a tener que enumerar por completitud, vamos a utilizar contenido fundamentalmente textual para hablar de ellos.

El objeto window

Esta clase representa cualquier ventana abierta por el navegador y más en concreto se refiere, por regla general, a la ventana actual en la que se ejecuta nuestro script (a veces nos referimos a la ventana actual como el objeto global). Representa también a aquellas ventanas independientes que se abren para contener a otras páginas, ventanas contenidas en marcos y ventanas de diálogo.

A través de los objetos de esta clase se puede acceder a todas las propiedades y eventos que afectan a una ventana concreta del navegador. Se puede conocer su estado, la ubicación de Internet que tiene abierta (la de la ventana propia) u obtener acceso al documento que está contenido en su interior. También se puede usar para mostrar diálogos en ventanas aparte, aunque no está recomendado hacerlo.

Cuando se abre una nueva instancia del navegador se está creando un nuevo objeto window. Si el documento que se muestra en esta ventana define marcos internos (que a su vez contendrán a otros documentos), éstos también serán ventanas.

Estos marcos aparecerán reflejados en la matriz (o, más bien, colección) del BOM denominada frames, perteneciente a la ventana actual. Como un marco no es más que una ventana contenida dentro de otra, se podrá acceder a ellos desde JavaScript a partir de otros objetos window. La matriz frames es, por tanto, una colección de objetos window y se puede recorrer para acceder a las propiedades y métodos de sus elementos. Dado que frames se puede utilizar como una matriz de JavaScript, para saber cuántos marcos hay contenidos dentro de la ventana actual basta con escribir:

console.log(window.frames.length);

Cuando desde un script se desea hacer uso de alguna de las características de la ventana que contiene al documento actual (aquél donde reside nuestro código) no es necesario tener una referencia a dicha ventana y basta con escribir el nombre de la propiedad o método, pues se considera la ventana actual como el objeto predeterminado en el código (el objeto global). De esta forma, algunos de los métodos de JavaScript vistos en anteriores módulos no son tales métodos del lenguaje, sino que se trata en realidad de métodos del objeto window, que al ser el objeto predeterminado no es necesario especificar.

Ejemplos de estas funciones son setTimeout, clearTimeout, alert, confirm y prompt que se han utilizado directamente aun siendo métodos de la ventana que contiene al documento. Una sintaxis más concreta es, por ejemplo:

var id = window.setTimeout("miFuncion()", 2000)
window.alert("Esto es un mensaje de información");

También hemos utilizado de forma directa, en algunas ocasiones, el objeto document (sin mencionarlo explícitamente, ahora lo estudiaremos), que en realidad depende de la ventana en la que está contenido (ver figura de la lección anterior). Por la razón expuesta, se puede acceder a él simplemente escribiendo su nombre, cuando en realidad es posible escribir, por ejemplo:

window.document.write("<strong>Hola</strong>");

Propiedades del objeto window

Las propiedades de una ventana no son demasiado importantes, salvo aquellas que se refieren a otros objetos o a colecciones de objetos, estando dichas colecciones reflejadas en el árbol de la figura del BOM de la lección anterior.

Algunos navegadores (especialmente Firefox) han añadido propiedades especiales a las ventanas, pero no son útiles más que en casos muy particulares en los que queramos sacar partido a ese navegador en concreto. Así que estudiaremos el modelo básico común a todos ellos, que nos asegura un funcionamiento estable siempre.

Estas propiedades básicas del objeto window son muy sencillas y se pasan a enumerar a continuación:

  • name: sirve para averiguar o asignar el nombre de una ventana determinada. Se suele utilizar principalmente para distinguir unos marcos de otros dentro de la colección de marcos (frames) o para identificar ventanas abiertas mediante código.
  • closed: indica si una ventana está o no cerrada.
  • length: informa sobre la cantidad de ventanas hijas (o marcos) que contiene la ventana actual. Es equivalente a escribir: window.frames.length.
  • self: devuelve una referencia a la propia ventana.
  • parent: devuelve una referencia a la ventana que contiene a la ventana actual, es decir, a la ventana situada en el nivel inmediatamente superior de la jerarquía de ventanas del navegador. Si se está trabajando desde un marco, esta propiedad tendrá una referencia a la ventana superior que contiene a ese marco (la que contiene la etiqueta <frameset>).
  • opener: devuelve una referencia a la ventana desde la cuál se ha abierto la que contiene a nuestro script, normalmente tras utilizar el método open que veremos un poco más adelante.
  • top: devuelve una referencia a la ventana de orden superior del navegador, es decir, la que contiene a todas las demás ventanas. A partir de ésta se puede acceder a todas las demás recorriendo su colección frames, en caso de que no sea la única ventana de nuestra página. La mejor manera de averiguar si la ventana donde reside nuestro Script es la que está por encima de todas las demás (arriba de todo en la jerarquía de ventanas) consiste en comprobar que se trata de la ventana de orden superior en la jerarquía:
function esLaSuperior()
{
 return top == window;
}

Esto se usa a veces para forzar que nuestra ventana se abra siempre en el marco superior, y nunca dentro de un marco interno de otra página (aunque lo mas recomendable, si no queremos que esto ocurra, es enviar desde el servidor una cabecera especial, X-FRAME-OPTIONS, que indica precisamente eso).

  • status: refleja el contenido actual de la barra de estado de la ventana en caso de que el navegador la tenga. En la actualidad su uso es muy reducido ya que por defecto la mayor parte de los navegadores no dejan cambiar el contenido de esta barra o, directamente, ni siquiera la muestran. Es lo que tiene haber abusado tanto de ella en su momento, a finales de los '90 😉
  • location: esta propiedad devuelve una referencia al objeto del mismo nombre, que permite conocer y fijar la URL de las páginas, con limitaciones por cuestiones de privacidad. Como tiene sus propias propiedades y métodos la estudiaremos en una lección independiente dentro de este módulo.
  • frameElement: devuelve una referencia a un objeto frame o iframe que contiene a la página actual (en caso de haberlo). Al contrario que parent no devuelve una referencia a la ventana en la que está contenida, sino al propio objeto del DOM que la contiene, por lo que podremos cambiar por ejemplo su propiedad src para modificar el contenido. No tiene usos muy interesantes y puedes obviarla.
  • frames: es la colección de todos los objetos window que estén contenidos en la ventana actual, es decir, marcos (<frameset>) y marcos internos (<iframe>).
  • innerHeight: devuelve la altura actual del área visible de la ventana, es decir, del área que se utiliza para mostrar contenidos, la cual no incluye la barra de direcciones, pestañas, etc… sino sólo donde se visualiza la página web.
  • innerWidth: ídem que la anterior pero para el ancho.
  • outerHeight: devuelve o ajusta la altura de la ventana, incluyendo las posibles barras de herramientas y barras de desplazamiento que tenga.
  • outerWidth: lee o establece el ancho. Análoga a la anterior.
  • pageXOffset / scrollX: devuelve el número de píxeles que el contenido de la página está desplazado hacia la izquierda (el scroll horizontal del contenido). Muy útil para mover objetos y dejarlos siempre visibles. Es de sólo lectura.En navegadores modernos (no IE) existe la propiedad sinónima scrollX, que es totalmente equivalente pero más corta y más fácil de recordar.
  • pageYOffset / scrollY: ídem pero para el desplazamiento vertical. Su equivalente moderno es scrollY.
  • screenX / screenLeft: devuelven la posición horizontal de la esquina superior izquierda de la ventana.
  • screenY / screenTop: ídem que la anterior pero para la posición vertical.

Acceso a marcos de ventanas

Dado que el objeto window que representa a la ventana actual hace las veces del objeto global de JavaScript, es muy fácil aprovechar este hecho para acceder a las variables globales y funciones de una ventana desde el código de otra ventana del mismo dominio (esto último es muy importante). En el caso de páginas contenidas dentro de marcos de otras ventanas, el acceso es muy fácil, puesto que es posible referenciar a las ventanas mediante su nombre directamente. Por ejemplo, consideremos esta estructura de marcos (ya totalmente en desuso, pero para tener un ejemplo sencillo):

<frameset cols="220px,*">
   <frame src="01.htm" name="Izquierdo">
   <frame src="02.htm" name="Principal">
</frameset>
</html>
 
Si la página ''01.htm'' contiene por ejemplo una variable global denominada ''TotalIzquierda'' y una función ''MostrarTotal'', podemos hacer uso de ellas desde cualquier marco o desde la propia página que los contiene escribiendo sentencias como esta:
 
<code javascript>
top.Izquierdo.TotalIzquierda++;
top.Izquierdo.MostrarTotal();

Es decir, que basta con escribir el nombre asignado al marco para obtener una referencia a su ventana. A partir de ahí podemos usar cualquier método o variable globales definidas en el código de la página web que contiene.

Alternativamente podríamos haber accedido mediante la colección frames de la ventana superior, bien por nombre:

top.frames["Izquierdo"].TotalIzquierda++;

Bien por posición:

top.frames[0].TotalIzquierda++;

Si en lugar de <frameset> hubiésemos utilizado marcos internos con la etiqueta <iframe> el código sería el mismo.

IMPORTANTE: por cuestiones de seguridad y privacidad los navegadores impiden que el código de Script de una página se comunique o acceda a los elementos de otra página ubicada en un dominio diferente. Esto incluye las variables y funciones de JavaScript. No sólo debe coincidir el dominio, sino también el puerto y el protocolo. De otro modo, páginas malintencionadas podrían acceder a los datos de otras pestañas o abrir en un marco páginas privadas (como la del banco) haciéndonos creer que es la única que está abierta y robar nuestra información o automatizar esas ventanas.

Si ejecutamos este ejemplo desde el disco, y no colgando las páginas en un servidor Web aunque sea local, la mayor parte de los navegadores nos darán un error de seguridad y no lo ejecutarán, al no utilizar explícitamente el mismo protocolo para las URL de las páginas dentro de los marcos. El protocolo file:// no considera que las páginas que abrimos desde disco pertenezcan al mismo dominio.

Métodos del objeto window

Los métodos alert, setTimeout, setInterval, clearTimeout, prompt y confirm ya han sido estudiados anteriormente por lo que no volveremos sobre ellos, pero existen otros específicos del objeto window. A continuación veremos los más importantes, dejando fuera los específicos de ciertos navegadores (incompatibles con los demás), algunos avanzados que se salen del ámbito de este curso (como postMessage para comunicación entre ventanas o getSelection para gestionar selecciones del usuario) y los relativos a los eventos (que se tratarán en el módulo correspondiente):

  • blur: hace que la ventana pierda el foco.
  • focus: hace que la ventana obtenga el foco (o intente obtenerlo).
  • close: cierra la ventana actual. No te permitirá hacerlo con ninguna ventana que no haya sido abierta como resultado de una interacción del usuario.
  • find: busca el texto indicado en el documento actual y destaca los fragmentos encontrados. Es un método no estándar pero la realidad es que todos los navegadores modernos lo soportan.
  • getComputedStyle: devuelve un objeto del DOM de tipo CSSStyleDeclaration que mediante sus propiedades nos permite conocer los estilos CSS que tiene aplicados el elemento indicado como argumento. Es muy interesante para determinar los valores finales de las propiedades CSS de cualquier elemento, al que se le pueden haber aplicado muchas reglas CSS con diferentes órdenes de especificidad.
  • moveBy: mueve la ventana del navegador un determinado número de píxeles en horizontal y en vertical, según lo especificado en sus dos parámetros. Este movimiento es relativo a la posición actual, es decir, que si escribimos window.moveBy(100, 50); se moverá 100 píxeles en horizontal hacia la derecha y 50 en vertical hacia abajo desde su posición actual. No se suele utilizar ya que solo funcionará con ventanas que hayas abierto previamente mediante código y que no contengan pestañas.
  • moveTo: es idéntico al anterior pero especifica la posición absoluta a la que se moverá la ventana. Las coordenadas se refieren a la posición de la esquina superior izquierda, incluyendo las barras de navegación y otros elementos que hubiese. Por ejemplo, para colocar la ventana actual arriba del todo de la pantalla, a la izquierda, escribiríamos: window.moveTo(0, 0);. Al igual que el anterior, solo funciona en ventanas propias sin pestañas.
  • open: crea una nueva ventana. Lo vamos a ver con detalle enseguida.
  • print: imprime los contenidos de la ventana.
  • resizeBy: cambia el tamaño de la ventana para aumentarla o reducirla (si los parámetros son negativos) el número indicado de píxeles en horizontal y en vertical. El tamaño se aumenta o reduce respecto al tamaño actual de la ventana. Solo funciona en ventanas abiertas con open y especificando la propiedad resizable.
  • resizeTo: ídem que la anterior pero especificando el tamaño absoluto.
  • scroll: este método se ocupa de deslizar el documento contenido en la ventana hasta la posición especificada por dos coordenadas 'X' e 'Y', que indican el grado de deslizamiento horizontal y vertical en píxeles respectivamente. Ambos argumentos están referidos a la esquina superior izquierda de la ventana actual. Escribiendo: window.Scroll(0, 200); se conseguirá que la ventana actual muestre el documento totalmente en el margen izquierdo y el píxel 200 situado en la parte superior de la parte cliente de ésta. Las coordenadas se miden desde la esquina superior izquierda, siendo las coordenadas horizontales positivas hacia la derecha, y las verticales positivas hacia abajo.
  • scrollBy: es como el método anterior pero el scroll que se realiza es relativo al scroll actual.
  • scrollTo: este método es idéntico al método scroll. Todos estos métodos de scroll pueden tomar como parámetro un objeto ScrollTooptions que permite especificar algunas cuestiones más aparte de las cantidades, pero raramente se utiliza.
  • showModalDialog: muestra una ventana modal que podemos usar para pedir información al usuario y devolver algún dato a través de la propiedad returnValue. Se considera obsoleto y no deberíamos usarlo. La mayor parte de los navegadores ya no lo soportan pero lo pongo aquí para que al menos te suene.

Generación de ventanas propias

El método open del objeto window visto en la lista anterior, está orientado a la creación de nuevas ventanas e instancias personalizadas del navegador. Su sintaxis es la siguiente:

windowID = window.open(URL , nombre , [características]);

El parámetro URL define la dirección de la página a mostrar en la nueva ventana. Si se especifica una cadena vacía se abrirá una página en blanco, lo cual a veces puede ser útil para crear contenidos directamente a través de JavaScript en la nueva ventana.

El segundo parámetro hace referencia al nombre de la ventana que se está creando. Si ya existe una ventana con ese nombre, en lugar de crearse una ventana nueva se abrirá el documento especificado en la ya existente. Si el nombre no existe, se abrirá una nueva ventana del navegador. Por último, si existe algún marco en alguna de las instancias del navegador que tenga como nombre el dado, el documento se abrirá en éste. Esto es especialmente importante ya que permite controlar mediante código la navegación por las páginas y restringirla al marco que se desee.

Existen una serie de nombres de ventana especiales que podemos utilizar para controlar en qué ventana se abrirá el nuevo documento. Así, "_blank" siempre fuerza la creación de una ventana nueva, independientemente de que ya hayamos abierto otra con ese mismo nombre; "_parent" se refiere a la ventana padre de la actual o a la propia ventana si no tiene padre; "_self" es la propia ventana actual, y "_top" la ventana padre de todas las demás en la jerarquía actual en caso de que haya marcos internos.

Las características de la nueva ventana (tercer parámetro) se especifican con alguno o varios de los parámetros de la tabla siguiente, que controlan aspectos, funcionalidades, etc. En caso de especificar más de uno se deben separar por comas. En MDN tienes una lista exhaustiva de valores.

Parámetro Descripción
toolbar Controla si mostrará (yes) o no la barra de herramientas del navegador. En caso de valer no se ocultarán incluso los botones de navegación.
location Si vale yes se mostrará la barra de direcciones del navegador. Si vale no, ésta no será visible.
status Especifica si se debe mostrar la barra de estado.
menubar Permite controlar si se mostrará la barra de menús o no.
scrollbars Especifica si se deben mostrar las barras de desplazamiento para moverse por el contenido de la ventana.
resizable Controla si se permitirá al usuario cambiar el tamaño de la ventana o no. No funciona en navegadores modernos para no restringir la libertad de los usuarios
fullscreen Especifica si la ventana se abrirá a pantalla completa o no.
left Posición horizontal de la parte izquierda de la ventana en píxeles referidos a la parte izquierda de la pantalla
top Posición vertical de la parte superior de la ventana en píxeles referidos a la parte superior de la pantalla
width Ancho de la ventana en píxeles. Alternativamente se puede especificar con innerWidth.
height Alto de la ventana en píxeles. Alternativamente se puede especificar con innerHeight.

Con estos parámetros se pueden controlar diversos aspectos de la nueva ventana, pudiendo crear ventanas emergentes con información dinámica o nuevas instancias del navegador.

La realidad es que el uso del método open está muy restringido por los navegadores para evitar el abuso por parte de ciertas páginas de las ventanas emergentes con publicidad (conocidas como pop-ups). Por ello sólo permitirán generalmente el uso de open cuando éste se realice como consecuencia directa de una acción del usuario (por ejemplo pulsar un botón o un enlace), cancelando la apertura en otros casos a través del bloqueador de pop-ups que incorporan todos los navegadores. Esto es aprovechado también por páginas poco recomendables para lanzar los famosos pop-under que podemos ver en ocasiones. Al pulsar en un enlace de descarga o de navegación, muestran una ventana con publicidad que no recibe el foco. No las vemos hasta que cerramos el navegador y nos encontramos con unas cuantas de esas abiertas que ni siquiera habíamos visto. En general no conviene abusar de la apertura de ventanas pues lo más probable es que las bloquee el navegador debido a los abusos que se dan por parte de algunos. No obstante, conviene al menos conocer cómo funciona.

El valor devuelto por la función open es muy importante puesto que es una referencia a la nueva ventana (un objeto window) y podrá ser usado en el código de script para acceder a sus métodos y propiedades.

Gracias a esta referencia podemos cambiar sobre la marcha algunos aspectos de la misma (como su posición o tamaño) usando los métodos y propiedades que hemos visto, podemos cerrarla, ponerle el foco para hacerla activa o verificar si el usuario la ha cerrado o no con la propiedad closed. También accederemos a todos sus elementos usando el BOM o el DOM.

Desde la ventana recién creada se puede acceder a la que contiene el script que la ha abierto mediante la propiedad opener de sí misma.

DEMO: ventana siempre en marco superior

ATENCIÓN: en este ejemplo se utiliza window.top.location.href para cambiar la ubicación de la página. Esto ya no funciona. La forma de hacerlo es llamar al método replace() del mismo objeto, es decir, escribir: window.top.location.replace(window.location.href);. Todo lo demás es válido. No obstante esto es solo una manera de experimentar un poco con este objeto, sin más trascedencia en la práctica. La manera correcta de evitar que una página se pueda usar dentro de un marco es devolviendo desde el servidor una cabecera X-Frame-Options y es algo de Backend no de Front-End.

Partimos de la siguiente página web:

<!DOCTYPE HTML>
<html lang="en-US">
<head>
    <meta charset="UTF-8">
    <title>Marco</title>
</head>
<body>
    <iframe style="width:80%; height:500px; border:2px solid black;" src="siempreEncima.htm"></iframe>
</body>
</html>

Y esta es siempreEncima.htm:

<!DOCTYPE HTML>
<html lang="en-US">
    <head>
        <meta charset="UTF-8">
	<title>Siempre por encima</title>
    </head>
    <body>
	<h1>¡Esto siempre está por encima!</h1>
	<p><span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius, nam, quas, reiciendis dolorem quia architecto vitae praesentium consequatur eaque eveniet amet earum velit sunt ipsam harum. Odit, tempore, nostrum, et quis quidem perferendis odio consectetur dolore ipsam sapiente nemo earum eaque! Temporibus, odit, fugit asperiores iste officia facere ex dolorem totam nobis illum nulla veniam perspiciatis eum ratione harum culpa laudantium non et tempora unde inventore assumenda atque laboriosam eos dolor omnis aliquam fugiat corporis officiis dolore modi in rerum voluptatum est molestias consequatur nemo voluptates nam repellendus magni a voluptas delectus corrupti minus sapiente cum! Laboriosam, ex, incidunt, necessitatibus quo iste vel ea aliquam maiores voluptatibus quae earum at deleniti aut aliquid laborum repellat suscipit sequi cupiditate harum accusantium dolorum cumque rerum ipsam pariatur sapiente blanditiis fugiat aperiam esse commodi explicabo et nulla hic labore sit soluta fugit fuga. Enim, impedit, reprehenderit consectetur facilis commodi est excepturi optio eius consequatur debitis atque doloremque ducimus rerum? Facilis, itaque, nostrum, nulla quasi quod enim similique tenetur sequi aliquam et facere delectus praesentium in quam soluta asperiores eligendi nam. Dolorem, corporis, tempora, saepe repellendus consequatur magnam recusandae aspernatur molestiae minus mollitia sequi hic magni ipsa alias omnis labore error autem quos voluptates ab temporibus adipisci velit quod obcaecati veritatis? Quos id et temporibus quibusdam quis fugiat animi nihil nostrum! Laudantium, vero, iure perspiciatis iste tenetur error quod rerum ipsam quisquam illum possimus ea cumque delectus labore ipsa soluta facere sunt natus perferendis quia consequuntur consequatur quas hic quaerat sed cum eos harum?</span>
	<span>Amet, illo, qui, dolor odio eius illum nam possimus minus laborum ipsam sint nemo. Facere, tempora minima saepe incidunt totam atque tempore optio odio sed nam neque impedit a praesentium distinctio magnam nesciunt rerum aliquam quam commodi ea. Nemo, dolor, repellendus. Cum, et, incidunt expedita perspiciatis tempore explicabo modi tenetur quisquam odio placeat officiis error iusto voluptates amet aliquid hic quidem aperiam obcaecati architecto non vel nemo laboriosam odit numquam nobis deserunt tempora excepturi eum veniam illo adipisci iste quaerat sapiente? Porro non dolor repudiandae quidem impedit reiciendis sequi quas aspernatur. Magnam, cupiditate, quos libero aut ducimus unde voluptatibus reiciendis doloremque consectetur nobis! Odit, aliquam, voluptates saepe soluta nulla dicta ducimus quo facilis cum optio corrupti provident distinctio corporis iste in quis velit temporibus voluptatum officia accusantium cumque exercitationem numquam deleniti omnis officiis autem quas quam illo ab. Repellat, vitae, corporis similique quae accusamus laboriosam adipisci non natus dignissimos vel suscipit quas tenetur quos labore tempora aperiam earum obcaecati quis iure quod. Natus, assumenda, excepturi, aliquid, laudantium similique quae facilis voluptates omnis perferendis perspiciatis ad quisquam aut explicabo voluptatibus accusantium delectus esse sequi distinctio magnam officia maxime laboriosam ea. Quo, voluptatem, et a blanditiis quam soluta iure labore vel doloremque praesentium molestiae nisi ex odio doloribus sint error possimus nesciunt accusamus eveniet dolorum magnam eius repellat voluptate tempora iste velit enim sit rem perspiciatis rerum quisquam voluptates incidunt laboriosam. Illo, veritatis, aperiam, iusto minus dolore fuga quo quidem obcaecati eveniet autem accusamus et ab recusandae voluptatibus repellat? Aut, veritatis, minima.</span>
	<span>Asperiores, eveniet, eius velit ad impedit deserunt voluptates incidunt est temporibus omnis molestiae magni nostrum facilis provident nemo dicta reiciendis dolores sint neque non atque doloribus accusantium. Magni, facilis, totam asperiores mollitia quidem sequi odit deleniti tenetur earum rem fugiat magnam laudantium pariatur ullam ipsum tempore consequatur repudiandae voluptatibus officia ab voluptatem cum iure soluta sint ipsam! Vero, quas itaque ipsum a aperiam quibusdam eaque ab eius libero consequuntur voluptatem neque perferendis ratione nam nesciunt earum rerum! Totam, minus, sequi optio harum nemo rerum quibusdam architecto accusantium voluptatibus unde incidunt blanditiis neque perferendis itaque laudantium eum cupiditate tempore debitis modi suscipit excepturi magni cum! Nam, officiis, deleniti, sequi, in obcaecati reprehenderit magnam laborum iste accusamus nisi esse corporis voluptate possimus ex odio veniam tenetur veritatis mollitia ut atque enim beatae animi omnis tempore explicabo asperiores doloremque optio dolores ratione quibusdam ipsa commodi ducimus nobis impedit odit cum eaque? Commodi, doloribus, nemo, asperiores exercitationem architecto magnam dolorem ullam eos ad ab voluptatem repudiandae ipsam laboriosam tempore facere. Sapiente, ratione, aspernatur, nisi, consequatur quasi architecto doloremque officia praesentium placeat quidem molestias possimus nihil tempore neque doloribus aliquid ea fuga magnam quaerat unde nam consequuntur libero voluptatum illum veritatis ad eos itaque sunt sit saepe similique a ex est. Quae, recusandae, dolores vitae laborum culpa rerum architecto similique accusamus velit mollitia sint dolorem eaque excepturi deleniti sunt aliquid adipisci asperiores reiciendis perspiciatis numquam. Quia, pariatur, et, enim soluta quam accusantium harum nihil beatae dolorum quae repudiandae eius quis nam libero sapiente. Optio, ab!</span>
	<span>Quibusdam adipisci accusamus fuga aperiam vero vel harum ipsam non similique provident. Excepturi, repellendus, voluptates, laborum rerum velit fugit quia asperiores laboriosam id labore similique ut? Accusantium, nemo magni sapiente reprehenderit quasi officiis qui voluptates saepe minima suscipit praesentium optio tempora necessitatibus dicta doloribus quisquam consequatur fugit quos autem nulla laborum. Molestiae, architecto, cupiditate, aperiam, sed perspiciatis veniam accusantium eaque suscipit numquam aut maxime mollitia dolorum rem magnam odit autem nihil recusandae odio magni iure facere itaque atque debitis quo temporibus repellat facilis corrupti rerum tenetur voluptatem tempore commodi doloribus nostrum. Blanditiis, velit inventore tempore odit reiciendis a deleniti voluptatum quod molestiae atque quo fuga libero deserunt corporis reprehenderit amet explicabo sint tenetur ducimus officiis aspernatur quia in quas quaerat iure numquam laudantium hic voluptatem nam sapiente vitae quam autem ex sit ipsum omnis suscipit sunt facere nisi pariatur nulla natus. Obcaecati, perferendis, iure ipsa quod iste aspernatur aliquid inventore eveniet officia esse deserunt enim adipisci quis provident laborum impedit quisquam quasi veritatis accusamus maxime vero quas cum sapiente neque minus optio nulla ex ab veniam vitae! Voluptas, blanditiis, nisi neque quo inventore voluptatem illo ad sit unde earum at quibusdam suscipit expedita omnis laboriosam possimus beatae! Doloribus, ipsam distinctio porro eum eos dolore at praesentium illum cumque commodi! Rerum, inventore, velit maiores nobis voluptatem sit veniam tenetur facere eligendi optio nulla explicabo. Sapiente, ratione id eius placeat minima velit architecto? Unde, ratione, amet, nulla odio deleniti ipsa officiis voluptate veritatis eos quia ipsam sint. Officiis libero quaerat distinctio saepe.</span>
	<span>Culpa, optio, dolore, asperiores, rem sint perferendis impedit illo quo officia minus est sunt vero maxime architecto maiores deleniti dicta. Esse, accusantium, obcaecati, corrupti iusto incidunt ullam rerum perspiciatis sint cumque voluptatum ea dolorem provident porro quibusdam perferendis laboriosam fuga quis voluptas asperiores magni tenetur dolor odit tempora possimus illo pariatur voluptates non officiis reiciendis repellendus praesentium molestias fugit quisquam alias at sunt vitae. Modi, rerum repellat quidem beatae minima alias explicabo consequuntur dignissimos fuga accusamus neque similique consectetur commodi cum non placeat ducimus deserunt. Incidunt, vel, asperiores, debitis expedita ea blanditiis dolorum ducimus placeat nesciunt corporis nulla laboriosam ullam voluptatem perspiciatis excepturi saepe repellat quis soluta minima amet nihil reprehenderit alias ipsa consectetur non quisquam consequuntur corrupti! Totam, id, numquam, obcaecati qui non inventore ex aut molestiae accusamus quia velit nobis tempore labore earum mollitia unde a similique ea exercitationem eligendi eius quidem itaque animi ipsum natus soluta corrupti cum possimus libero eos! Deserunt, error, voluptatem, ad quod nam distinctio sapiente unde ipsam iure non molestiae perspiciatis possimus repellat! Nemo ullam enim veritatis est quasi. Sint, qui voluptatum et aut voluptatibus ex ullam quo asperiores. Dignissimos, possimus quis dolor explicabo ullam eligendi incidunt iure hic voluptatem neque. Dolorem, accusantium, officia, odit, cum architecto quaerat rem natus aperiam doloremque fugit amet mollitia minima ad corporis ut nobis commodi sapiente beatae quo reiciendis maxime facere in asperiores impedit veniam ea autem. Et, placeat, quod, quisquam, vel ipsam exercitationem accusamus cumque ducimus eius voluptatem ad id omnis modi quibusdam pariatur maiores minus!</span></p>
    </body>
</html>

Lo que queremos es que no se permita usar siempreEncima.htm en un <iframe>, así que haremos la siguiente modificación en siempreEncima.htm:

<!DOCTYPE HTML>
<html lang="en-US">
    <head>
	<meta charset="UTF-8">
	<title>Siempre por encima</title>
	<script type="text/javascript">	
            if (window.top != window) {
	        window.top.location.href = window.location.href;
            }
	</script>
</head>
<body>
(...)
  • window: representa la página actual
  • window.top: representa a la página que es la página superior de la jerarquía de páginas actual.
  • window.location.href: ubicación de la página actual.

El objeto history

Cuando se utiliza un navegador y vamos cambiando de página al pulsar sobre los enlaces, se va guardando un ordenado historial de las páginas que hemos visitado. De esta manera es posible volver rápidamente a cualquiera de ellas sin tener que escribir de nuevo el URL correspondiente. Podemos movernos por el historial hacia atrás o hacia adelante (si previamente nos hemos movido ya hacia atrás).

Para ello pulsaremos las flechas del historial de navegación o bien usaremos ALT + o ALT + como se observa en la figura siguiente, en la que se ha pulsado con el botón secundario del ratón sobre la flecha atrás del historial para ver las páginas anteriores:

Historial

Desde JavaScript es posible moverse por el contenido del historial simulando la pulsación de esos botones sin intervención por parte del usuario. Este objeto presenta una interfaz común muy sencilla con solamente una propiedad y tres métodos.

A través de su propiedad length podemos saber el número de entradas que existen en el historial actualmente.

Con los métodos back y forward nos movemos una posición hacia atrás o una hacia delante respectivamente. Es equivalente a pulsar los correspondientes botones del navegador y no necesitan por tanto ningún argumento.

Por ejemplo, para colocar un enlace en una página que, tras ser pulsado, devuelva al usuario a la página en la que se encontraba anteriormente basta con escribir:

<a href="#" onClick="history.back();">Volver</a>

El tercer y último método es go, que toma como argumento un número entero que indica el número de posiciones que se deben recorrer en el historial. Si este número es 1 equivale al método forward. Si es –1 equivale al método back.

Si al utilizar alguno de estos métodos se intenta acceder a un elemento fuera de la matriz (por ejemplo, hay tres URL en la historia y estando en el primero se escribe history.go(5);) no se produce error alguno sino que el comando simplemente no tiene efecto.

La razón de que este objeto tenga una funcionalidad tan limitada es la privacidad del usuario. Si se permitiese conocer mediante el uso de scripts cualquier detalle sobre los sitios que hemos visitado antes de la página actual, las empresas podrían efectuar interesantes estudios de mercado en función de estos datos. Sólo tendrían que recabar esta información cuando visitásemos su página para conocer los gustos y aficiones de sus visitantes. Así, la utilidad del historial es, en general, muy baja.

De cara a facilitar la vida a las aplicaciones de tipo Single Page Application (que solo tienen una página y van cambiando sus contenidos dinámicamente) se introdujeron un par de métodos y un evento adicionales en este objeto para poder gestionar la historía y simular navegación en páginas únicas, pudiendo mostrar una cosa u otra en la barra de navegación sin realizar realmente la navegación. Esto se sale del ámbito de este curso, pero si te interesa la MDN tiene una referencia y un ejemplo.

Demo: crear un navegador dentro del navegador

Utilización del objeto history.

Partimos de esta página:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
	<title>Navegador dentro de un navegador</title>
	<link type="text/css" rel="stylesheet" href="navegador.css"></link>
	<script type="text/javascript">
		var ifrNavegador = null;
 
		function navegar(){
			var destino = document.getElementById("direccion").value;
			if (ifrNavegador == null)
				ifrNavegador = document.getElementById("navegador");
 
			ifrNavegador.contentWindow.location.href = destino;
		}
	</script>
    </head>
    <body>
	<div id="contenedor">
		<div id="controles">
			<span id="atras" onclick="history.back();"></span>
			<span id="adelante" onclick="history.forward();"></span>
			<input id="direccion" type="text" onkeypress="if (event.keyCode == 13) navegar();" value="http://www.campusmvp.es/recursos"></input>
			<span id="refrescar" onclick="navegar();"></span>
		</div>
		<iframe id="navegador" src="http://www.campusmvp.es/recursos"></iframe>
	</div>
    </body>
</html>

El objeto screen

Este objeto existe con el solo objetivo de proporcionar información sobre la resolución de la pantalla del usuario. Puede ser útil para adaptar el flujo y diseño de la página a diferentes resoluciones, aunque raramente se usan en la actualidad, ya que lo correcto es utilizar media queries de CSS3 para conseguir diseños fluidos que usen estilos diferentes en función de las dimensiones del navegador, no de la pantalla.

Sus propiedades son:

  • width: devuelve el ancho de la pantalla en píxeles.
  • height: devuelve el alto de la pantalla en píxeles.
  • availHeight: el alto de la pantalla disponible para colocar ventanas (no tiene porqué coincidir con el tamaño total si hay barras del sistema que ocupan su propio espacio).
  • availWidth: ídem para la anchura disponible.
  • orientation: devuelve un objeto con información sobre la orientación de la pantalla. El objeto tiene dos propiedades: -angle que indica el ángulo de giro de la pantalla actual y -type que devuelve una cadena de texto con la descripción de la orientación (sus posibles valores son: "portrait-primary", "portrait-secondary", "landscape-primary", o "landscape-secondary").
  • colorDepth / pixelDepth: indica el número de bits utilizados para proporcionar el color en la pantalla del sistema donde se ejecuta el navegador. Por ejemplo, si vale 4 es que se permite la visualización de 16 colores al mismo tiempo, si vale 16 es color de alta densidad, 24 es color real (16,1 millones de colores simultáneamente) aunque también podría ser de 32 o incluso de 48 bits por píxel. La realidad es que, en la actualidad, todos los navegadores devuelven 24 en estas propiedades, por lo que su utilidad es nula. Antiguamente, sobre todo en sistemas UNIX, podían valer para algo puesto que las pantallas antiguas permitían diversas combinaciones de colores y podía ser diferente la del sistema a la del monitor. Hoy no tiene utilidad alguna y la he mencionado solo por completitud.

El objeto document del BOM

Como ya hemos visto en varios ejemplos previos, este objeto representa al propio documento que se está mostrando en la ventana del navegador. Se utiliza para acceder a sus propiedades así como a los elementos que lo constituyen.

Este objeto siempre está disponible para cualquier ventana.

Sus propiedades y métodos en el BOM son muy sencillos y la mayoría de ellos -los que son comunes a todos los navegadores- los tienes resumidos en la siguiente tabla:

Propiedad Descripción
title Título del documento. Se puede leer o cambiar.
bgColor Devuelve o establece el color de fondo para el documento.
fgColor Especifica el color para el texto del documento.
linkColor Indica el color con el que se resaltarán los hiperenlaces de la página
aLinkColor Sirve para ajustar el color con el que se resaltarán los hiperenlaces activos de la página.
vLinkColor Especifica el color con el que se resaltarán los hiperenlaces de la página una vez que ya se han visitado y están en la caché de archivos de Internet.
lastModified Devuelve la fecha de última modificación del documento actual.
URL Es un atajo para la propiedad location.href por lo que podemos usarla para leer o cambiar la dirección de la página actual.
characterSet Devuelve una cadena con la codificación utilizada para el documento actual (UTF-8, Windows-1252…, repasa el tema sobre cadenas de texto). Internet Explorer sólo lo soporta a partir de la versión 9.
compatMode Indica si la página se ha renderizado en modo de compatibilidad (con navegadores antiguos) o en modo estándar.
referrer Especifica la URL de la página a partir de la cual el usuario ha llegado a la actual (normalmente pulsando sobre un enlace). Si se ha llegado de otra manera, es decir, sin proceder de otra página (se ha ido directamente) se obtendrá una cadena en blanco. También se obtendrá este valor si la página anterior procedía de un sitio de Internet declarado como seguro. Es muy útil para saber qué ruta ha seguido el usuario actual para llegar a nuestro sitio, aunque sea desde una página en otro dominio.

Todas las propiedades anteriores que se refieren a atributos de aspecto están obsoletas, pues deberíamos usar CSS para eso. Las más útiles serían referrer, characterSet y title. Pero su uso es limitado en cualquier caso.

Métodos de document

Hay algunos métodos pertenecientes al BOM y existentes desde las primeras versiones de los navegadores que pueden ser útiles a veces, aunque han sido ampliamente superados por los métodos del DOM (que veremos en breve).

Por ejemplo, mediante los métodos write y writeln se puede escribir texto en el documento mientras lo cargamos o al final del mismo. Si como argumento se especifica texto que representa HTML, éste se interpretará para mostrarse adecuadamente. Por ejemplo, si se escribe:

document.write('<span style="font-size:x-sma ll;font-weight:bold;">
Este documento se ha actualizado el ' + (document.lastModified) + '</span>');

Se insertará e interpretará ese HTML en la página, mostrando un texto con tamaño pequeño y en negrita con la fecha en la que se ha actualizado el archivo del documento.

El método writeln actúa exactamente igual que write pero además, añade automáticamente un retorno de carro, al final de la línea especificada, en el código fuente. Esto sólo tiene efecto visible en la página si se escribe entre las etiquetas <pre></pre> que sí consideran este tipo de caracteres, pues en HTML un retorno de carro se expresa de otra manera (con <br/>). A efectos prácticos en la mayoría de los casos ambos métodos son equivalentes.

Se pueden utilizar los métodos open y close para definir bloques de escritura de texto en el documento. El contenido de un bloque no se visualiza en documento hasta que se cierra, bien expresamente, o bien, porque se ha terminado de procesar la página. De esta manera para el código:

document.Open();
document.writeln("Línea 1");
document.writeln("Línea 2");
document.writeln("Línea 3");
document.close();

escribiría de una sola vez el texto “Línea 1 Línea 2 Línea 3”, mientras que si no se utilizaran open ni close se escribirían las líneas en sucesión. Además close obliga al navegador a mostrar el texto que se envíe al documento con write. En algunos navegadores si la página ya está cargada y acabada de procesar, aunque usemos write o writeln el texto no se mostrará a menos que lo encerremos entre llamadas a open y close.

El objeto document tiene muchos más métodos y propiedades, pero estos son los que forman parte del nivel 0 del DOM, es decir, los que son soportados incluso por los navegadores más antiguos. Más adelante vamos a ver cómo manejar los objetos contenidos en un documento usando los niveles reales del DOM, por lo que volveremos sobre document y sus diferentes miembros. De momento quédate con esta información porque para hacer cosas sencillas te puede servir y siempre funcionará, incluso en navegadores de hace varias décadas. En la práctica casi no se utiliza, pero lo he incluido por completitud.

Colecciones del objeto document en el BOM

Como se vio al inicio del tema, en la primera figura, el objeto document posee unas propiedades que son matrices o colecciones de objetos (su nombre termina en “s” pues es un plural para indicar que pueden ser varios objetos). Estas colecciones nos permiten acceder en la página a los elementos del tipo que indica su nombre. Por ejemplo, a través de links podremos acceder a los enlaces de la página.

A continuación, se comentan las diferentes colecciones que ofrece el objeto document así como las propiedades más relevantes de los objetos que contienen éstas.

  • anchors: es una matriz que contiene el nombre de todos los enlaces internos <a></a> que hay en la página a los que se les haya dotado de nombre (atributo name) o identificador (atributo id). Si no se cubre su atributo href entonces no actúan como enlaces, sino sólo como anclas/secciones, y están en esta colección.
  • links: similar a la colección anterior, contiene todos los enlaces a sitios web que existen en la página. Es decir, contiene todas las referencias a objetos <a></a> que tienen su atributo href con algún contenido (es decir, apuntan a alguna página). Estos objetos son de tipo anchor, y sus propiedades son exactamente las mismas que las del objeto location. Si un enlace con nombre tiene el atributo href fijado aparecerá tanto en la colección anchors como en esta de links. Dentro de esta colección también se incluyen los elementos <area></area> (para mapas de imágenes) que posean el atributo href.

Para obtener una lista con todos los enlaces de una página deberemos escribir:

    var enlaces = "";
    for(i=0; i<document.links.length; i++)
    {
        enlaces += (i+1) + " - " + document.links[i].href + "\n";
    }
 
    alert(enlaces);</span>
  • images: obtenemos referencias a todos los objetos image (etiquetas <img>) de nuestra página. Mediante la propiedad src de cada image podemos leer la ruta a la imagen o establecer una ruta diferente.
  • applets: obtiene información sobre los programas Java que se estén usando en la página. Hoy en día está obsoleta (¡por suerte!) ya que quitando algunas páginas gubernamentales 🤦‍♂️ pocos applets Java se ven por la Red puesto que hace años que están declarados como obsoletos. Las propiedades de cada applet se pueden usar a través del objeto applet correspondiente dentro de esta colección.
  • embeds: devuelve todos los elementos ''<embed></embed>'' que posee la página. Al igual que los anteriores, se han abandonado casi por completo y sería difícil encontrarlos en una página moderna. Estos elementos se solían utilizar para incrustar documentos de todo tipo en las páginas.
  • forms: esta colección devuelve referencias a todos formularios que tengamos en la página, y a partir de éstos a sus controles. Esta propiedad sí que es bastante útil, como veremos enseguida.

Habitualmente se utiliza el acceso a los formularios para poder validar sus campos, comprobando que se han rellenado los que son obligatorios, que tienen la longitud apropiada, que se ha usado el formato correcto, etc… No vamos a entrar en esta tarea en concreto ahora mismo por dos motivos:

  • Porque es muy fácil de hacer con lo aprendido hasta ahora, pero necesitamos conocer el manejo de eventos.
  • Porque en la vida real generalmente no lo harás nunca así, sino que utilizarás bibliotecas especializadas como jQuery Validation u otras.

En un próximo módulo dedicado a los eventos veremos varios ejemplos de acceso a elementos de formularios para llevar a cabo cosas más útiles que la validación de contenidos.

Acceso a los elementos de formularios en el BOM

El objeto document posee una propiedad llamada forms que devuelve una colección de objetos que representan los formularios que haya en la página actual. A partir de esta matriz, podemos acceder a cada formulario y leer o escribir los valores que haya dentro.

<!DOCTYPE html>
<html>
    <head>
        <title>Formulario de ejemplo</title>
        <style type="text/css">
	body 
	{
		font-family:"Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
		font-size:12px;
	}
	.cabecera
	{
		text-align:center;
		font-weight:bold;
		font-size:2.5em;
		color:blue;
	}
	.Encabezado
	{
		font-weight:bold;
		font-size:1.5em;
	}
	.Obligatorio
	{
		color:red;
	}
        </style>
 
<script type="text/javascript">
// La siguiente función mostrará todos los formularios 
// de la página, así como sus campos y valores
function mostrarDatosFormulario(){
    var info = "";
    for(var i=0; i<document.forms.length; i++)
    {
	var f = document.forms[i];
	info += "- " + f.name + " (" + f.method + "): " + f.action;
	info += "<blockquote>";
	for (var j = 0; j<document.forms[i].elements.length; j++)
	{
		var e = document.forms[i].elements[j];
		info += "·" + e.name + " (" + e.type + "): " + e.value + "<br/>";
	}
	info += "</blockquote>";
    }
    infoFormulario.innerHTML =info;
}
</script>
 
 
    </head>
    <body>
 
<h1 class="cabecera">Env&iacute;o de datos personales</h1>
 
<form name="frmDatos" action="/api/User" method="POST">
<label for="txtNombre" class="Encabezado">Nombre<span class="Obligatorio">*</span>: 
	  <input type="text" name="txtNombre" size="20" value="Pepe">
</label>
<br/>
<label for="txtApellidos" class="Encabezado">Apellidos<span class="Obligatorio">*</span>: 
	  <input type="text" name="txtApellidos" size="40">
</label>
<br/>
<label for="txtDir" class="Encabezado">Dirección<span class="Obligatorio">*</span>: 
	  <input type="text" name="txtDir" size="60"> 
</label>
<label for="txtCiudad" class="Encabezado"> Localidad: 
	<input type="text" name="txtCiudad" size="25">
</label>
<label for="txtCP" class="Encabezado"> C.P.: 
  <input type="text" name="txtCP" size="5">
</label>
<br/>
<label for="txtTlfn" class="Encabezado"> Teléfono<span class="Obligatorio">*</span>: 
	<input type="text" name="txtTlfn" size="15">
</label>
<br/>
<label for="txtEMail" class="Encabezado"> eMail<span class="Obligatorio">*</span>: 
	<input type="text" name="txtEMail" size="30">
</label>
<br/>
<p> 
  <input type="checkbox" name="chkInfo" value="checkbox" checked>
  <label for="chkInfo" class="Encabezado">Deseo recibir informaci&oacute;n sobre sus productos</label>
</p>
<p> 
  <input type="button" name="Enviar" onclick="mostrarDatosFormulario();" value="Enviar datos personales">
  <input type="reset" name="Reset" value="Volver a rellenar datos">
</p>
</form>
<div id="infoFormulario"></div>
</body>
</html>

La propiedad elements es una colección de elementos que están dentro del formulario (campos, botones, checkboxes, etc).

Otra forma de acceder a cierto campo:

document.forms["frmDatos"].elements["txtApellidos"].value

Y otra más cómoda que es equivalente a la anterior:

document.forms.frmDatos.elements.txtApellidos.value

Finalmente, más sencilla y popular, con el nombre del formulario y campo al que queremos acceder:

document.frmDatos.txtApellidos.value

Trabajo con el DOM

Lo que hemos visto hasta ahora es funcionalidad básica incluida en todos los navegadores del mercado, incluso los más antiguos. He incluido este BOM, aparte de por motivos históricos, porque a través de él tendremos acceso a los enlaces y a los controles de los formularios, que en su momento eran los elementos más importantes de una página web. Esto era suficiente hace muchos años donde lo máximo a lo que aspiraba un programador era a cambiar las ruta de los enlaces, hacer un efecto bonito en una imagen o a validar los datos que se introducían en un formulario. Conviene conocerlo en cualquier caso.

La realidad actual es muy diferente. Las modernas aplicaciones web necesitan poder cambiar por completo los contenidos de una página y actuar sobre cualquier elemento que exista dentro de éstas. Esto permite una potencia sin precedentes que ha hecho que muchas aplicaciones tradicionales de escritorio se hayan migrado paulatinamente a la nube y se ejecuten en un navegador. Por ejemplo, los modernos WebMail como GMail o Outlook.com no tienen nada que envidiar a la mayor parte de los clientes de correo de escritorio, que de hecho en muchos casos están desapareciendo y siendo sustituidas por aplicaciones web. Este tipo de aplicaciones son posibles gracias a la capacidad de los navegadores para modificar dinámicamente sus contenidos.

Los nodos del DOM

El DOM en realidad es más sencillo de manejar incluso que el BOM. Solamente hay que tener unas cuantas cosas claras.

Lo primero que hay que conocer es cómo se exponen los elementos de una página para ser accedidos desde JavaScript.

Consideremos una página simple con este código HTML:

<!DOCTYPE html>
<html>
   <head>
      <title>Ejemplo de DOM</title>
      <script>
      </script>
   </head>
<body>
   <h1>Ejemplo de DOM</h1>
   <ul>
      <li>Elemento 1</li>
      <li>Elemento 2</li>
   </ul>
</body>
</html>

Como vemos, el cuerpo consta únicamente de una cabecera <h1> así como de una lista desordenada. Todo ello está envuelto, claro está, en una etiqueta <html> y precedido por el doctype correspondiente a HTML5.

Todos y cada uno de estos elementos, incluyendo el doctype o el script vacío de la cabecera, son accesibles a través del DOM. El navegador expone el documento completo a JavaScript a partir de una organización jerárquica donde unos elementos están contenidos dentro de otros:

arbol-DOM

Como podemos observar esta jerarquía incluye a todos los elementos del código (incluso los espacios en blanco) y los relaciona entre sí a través de enlaces entre padres e hijos.

Así el doctype y el nodo <html> están en la raíz de este documento. Del nodo <html> cuelgan a su vez dos nodos; <head> para la cabecera y <body> para el cuerpo. De estos dos a su vez dependen otros nuevos nodos: el título y el script en el caso de la cabecera, y un encabezado de tipo <h1> y una lista no ordenada (además de algunos espacios en blanco) en el caso del cuerpo de la página. Y así sucesivamente. Cada nodo contiene a otros nodos, siguiendo fielmente el código HTML de la página, que no deja de ser un documento XML de naturaleza inherentemente jerárquica.

Incluso los comentarios HTML que tengamos en el código aparecerán como nodos en la representación jerárquica del DOM.

DEMO: el árbol del DOM

El DOM podemos verlo en las herramientas para desarrolladores. Por ejemplo, en Mozilla Firefox, desde la pestaña Inspector. En Google Chrome, sería en la pestaña Elements.

Recorriendo la estructura jerárquica del DOM

Para recorrer el DOM debemos partir de algún elemento y luego ir moviéndonos a través de sus hijos o de sus padres.

Existen dos puntos de partida habituales para navegar el DOM:

  • La raíz del documento: se refiere al nodo <html> y se puede obtener una referencia al mismo mediante la propiedad documentElement del documento.
  • El cuerpo del documento: es el nodo equivalente a la etiqueta <body>, por lo que queda más abajo que el anterior y deja en su mismo nivel a la cabecera y por encima a la raíz del documento. Se accede mediante la propiedad body del documento.

Podemos acceder al nodo doctype usando la propiedad homónima document.doctype, aunque por regla general no nos resultará útil.

Todos los nodos del DOM poseen una serie de propiedades que nos permiten navegar por la jerarquía y acceder a los nodos restantes. La jerarquía se puede recorrer tanto hacia abajo como hacia arriba o a los lados.

Para acceder a los elementos inferiores/descendientes, cada nodo dispone de las siguientes propiedades:

  • childNodes: esta colección nos permite acceder a los nodos hijos directos del nodo actual, sean estos del tipo que sean. Se incluyen únicamente los hijos de primer nivel, es decir, que para ahondar en niveles inferiores de la jerarquía hay que usar recursivamente esta función con cada nodo.
  • children: colección similar a la anterior pero que únicamente incluye nodos que sean elementos HTML de la página, por lo que se excluyen los nodos de texto y los comentarios (excepto en versiones viejas de Internet Explorer, que incluían también los comentarios).
  • firstChild: devuelve una referencia al primer nodo hijo del nodo actual. Es equivalente a escribir nodo.childNodes[0].
  • lastChild: devuelve una referencia al último nodo hijo del nodo actual. Es equivalente a escribir nodo.childNodes[nodo.childNodes.length-1].

Para movernos hacia arriba, hacia elementos ascendientes/padres de uno dado, la propiedad de la que disponemos en cada nodo es parentNode. Nos devuelve una referencia al nodo que es el padre inmediato del nodo actual. Por ejemplo, el padre de la cabecera (<head>) es siempre el nodo raíz <html>.

Existe también una propiedad parentElement que devuelve una referencia al elemento que es nodo padre del que estamos consultando. La diferencia con parentNode es que si un nodo del DOM no tiene como padre a otro nodo del DOM, devuelve un nulo. Por ejemplo, document.documentElement devuelve un nulo para parentElement, ya que su “padre” no es un elemento del DOM (está arriba del todo), pero que si consultas su propiedad parentNode devuelve una referencia a document. En general la que usaremos es parentNode.

Disponemos de un método especial para movernos hacia arriba en el DOM: closest(). Este método (que no propiedad, ojo) nos permite obtener el primer elemento por encima del actual en la jerarquía que cumpla con el selector CSS que le pasemos como argumento.

Por ejemplo, si tenemos seleccionado un elemento en la variable elemento y escribimos:

console.log(elemento.closest('div.claseUno'));

Lo que veremos por consola es el primer elemento de tipo <div> que contenga a nuestro elemento y que además tenga aplicada la clase .claseUno. Si no hubiera ninguno arriba de la jerarquía obtendríamos un null. Si le pasásemos un selector CSS no válido como parámetro obtendríamos un error de sintaxis.

En realidad closest() comienza la búsqueda a partir del propio elemento sobre el que lo aplicamos, por lo que si este mismo elemento ya cumple con el selector que le pasamos como argumento, se detendría y devolvería el propio elemento como resultado. Es importante tenerlo en cuenta. Se puede ver ese caso concreto en una de las líneas de ejemplo del archivo DOMClosest.html de las descargas.

Por último podemos obtener referencias a los elementos inmediatamente contiguos en el nivel de la jerarquía usando las propiedades previousSibling y nextSibling, para obtener respectivamente referencias al nodo anterior y siguiente dentro del mismo nivel.

Existen un par de métodos especiales del DOM que nos permiten recorrer los elementos filtrándolos previamente. Se trata de createNodeIterator y de createTreeWalker. Ambos permiten iterar por los elementos del DOM especificando previamente qué nodos nos interesan. Se trata de métodos muy especializados, que dan muy poco rendimiento y que raramente se utilizan. Los incluyo por completitud únicamente.

DEMO: Pintando el DOM recursivamente

Con el siguiente código, se mostrará una página pintando el DOM:

<!DOCTYPE html>
<html>
<head>
<title>Ejemplo de DOM</title>
    <script type="text/javascript">
 
        //SOLAMENTE LOS NODOS QUE SON ELEMENTOS HTML
        function mostrarDOMApartirDe(elto)
        {
            //Metemos el nodo actual
            var arbol = "<ul><li>" + elto.nodeName + "</li>";
            //recorremos los nodos hijos llamando a esta misma función recursivamente
            if (elto.children) {
                for (var i = 0; i < elto.children.length; i++) {
                    arbol += mostrarDOMApartirDe(elto.children[i]);
                }
            }
            arbol += "</ul>";
            //devolvemos el resultado final
            return arbol;
        }
 
        function pintarDOM()
        {
            document.getElementById("dom").innerHTML = "<h2>DOM del documento actual:</h2>" + mostrarDOMApartirDe(document.documentElement);
        }
 
    </script>
</head>
<!-- Al cargar la página, se llamará a la función 'pintarDOM()' -->
<body onload="pintarDOM();">
    <!-- Comentario HTML -->
    <h1>Ejemplo de DOM</h1>
    <ul>
        <li>Elemento 1</li>
        <li>Elemento 2</li>
    </ul>
    <div id="dom" style="font-size:small; border:1px dashed; background-color:rgb(250, 250, 250);"></div>
</body>
</html>

document.documentElement es el elemento que representa la etiqueta <html> dentro del DOM, y aquí lo utilizamos para sacar todo el árbol a partir de él.

Podemos hacer una variante que en lugar de utilizar la colección children, usemos la colección childNodes:

<!DOCTYPE html>
<html>
<head>
<title>Ejemplo de DOM</title>
    <script type="text/javascript">
 
        //TODOS LOS ELEMENTOS
        function mostrarDOMApartirDe(elto)
        {
            //Metemos el nodo actual
            var arbol = "<ul><li>" + elto.nodeName + "</li>";
            //recorremos los nodos hijos llamando a esta misma función recursivamente
            if (elto.childNodes) {
                for (var i = 0; i < elto.childNodes.length; i++) {
                    arbol += mostrarDOMApartirDe(elto.childNodes[i]);
                }
            }
            arbol += "</ul>";
            //devolvemos el resultado final
            return arbol;
        }
 
        function pintarDOM()
        {
            document.getElementById("dom").innerHTML = "<h2>DOM del documento actual:</h2>" + mostrarDOMApartirDe(document.documentElement);
        }
 
    </script>
</head>
<body onload="pintarDOM();">
    <!-- Comentario HTML -->
    <h1>Ejemplo de DOM</h1>
    <ul>
        <li>Elemento 1</li>
        <li>Elemento 2</li>
    </ul>
    <div id="dom" style="font-size:small; border:1px dashed; background-color:rgb(250, 250, 250);"></div>
</body>
</html>

La ventaja de usar childNodes es que nos salen más nodos: comentarios, saltos de línea…

De todos modos, lo habitual es usar children.

Localizando elementos en la página

Lo que acabamos de ver es útil para recorrer de manera sistemática todos los elementos de una página y hacer algo con ellos, como mostrar el árbol jerárquico de nuestro ejemplo. Sin embargo, en el uso diario es muy poco práctico y lioso. Lo que necesitaremos es disponer de una manera de obtener acceso directo a nodos concretos de la página, sin tener que recorrer la jerarquía entera para encontrarlos. Eso es lo que nos proporciona el DOM Level 2 gracias a varios métodos importantes del objeto document, que utilizaremos con mucha frecuencia.

A través de su ID

El más común de ellos es getElementById. Se le pasa como parámetro una cadena con el identificador del elemento que queremos localizar en la página, y devuelve una referencia al nodo correspondiente o un null en caso de que no exista ningún elemento con ese id.

Por ejemplo, si tenemos en la página un fragmento HTML como éste:

<h1 class="rojo">Ejemplo de DOM</h1>
<ul id="lista">
   <li>Elemento 1</li>
   <li>Elemento 2</li>
</ul>

Podemos obtener una referencia a la lista (y por tanto a todos sus elementos hijo) escribiendo esto en nuestro código JavaScript:

var miLista = document.getElementById("lista");

El identificador del elemento es dependiente de mayúsculas y minúsculas, así que deberemos escribirlo exactamente igual a como esté en el atributo id en el código fuente.

Los id de los elementos de una página deberían ser siempre únicos. Por supuesto nadie nos impide poner el mismo id a más de una etiqueta, pero eso no hará más que dificultarnos el acceso a los mismos. En caso de ocurrir esto, getElementById nos devolverá el primer elemento que tenga ese identificador. De todos modos, el comportamiento podría variar de un navegador a otro ya que el estándar no define qué hacer en este caso. Como norma deberíamos ser muy cuidadosos y no duplicar los identificadores en las páginas.

Todos los navegadores modernos crean de manera automática en el objeto global (window) referenciando a los elementos que tienen asignado un id, variables cuyo nombre es el propio id. Gracias a ello, el uso de la función anterior se puede evitar y escribir directamente:

var miLista = window.lista;

O incluso, al tratarse del objeto global, algo más simple todavía:

var miLista = lista;

Consiguiendo el mismo resultado que con getElementById. Sin embargo, a pesar de que es más directa, no es recomendable utilizar esta sintaxis, y se aconseja usar getElementById por los efectos que pueda tener en el código si declaramos otras variables globales con ese mismo nombre. Además, explicitar el uso de la función de búsqueda nos ayuda a conseguir un código más claro y fácil de entender.

También se puede hacer lo mismo con los controles de los formularios, sin necesidad de ponerles un identificador, usando la función getElementsByName. Ésta funciona exactamente igual pero localiza los campos a través de su atributo name.

Las etiquetas de un tipo

La función getElementsByTagName permite obtener todos los elementos de la página representados por la etiqueta que le indiquemos. Por ejemplo, en el fragmento anterior podemos localizar todos los elementos de la lista escribiendo:

var eltosLista = document.getElementsByTagName("li");

Ahora podemos recorrer la colección de elementos con un bucle y actuar sobre todos ellos:

for (var i = 0; i < eltosLista.length; i++)
{
   alert(eltosLista[i].innerHTML);
}

En este caso simplemente mostramos el contenido de cada elemento de lista que hay en la página.

El nombre de la etiqueta no debe contener los símbolos < y > que siempre se usan, y no depende de mayúsculas o minúsculas por lo que es lo mismo usar “li”, “LI”, etc…

Una cuestión adicional interesante que ofrece getElementsByTagName es que es un método aplicable a cualquier nodo del DOM, no sólo a document. De este modo podemos restringir la búsqueda de elementos a los hijos de un determinado nodo, en lugar de sacar todos los elementos de la página:

var miLista = document.getElementById("lista");
alert(miLista.getElementsByTagName("li")[0].innerHTML);

En este ejemplo restringimos la búsqueda de elementos <li> a los contenidos dentro del elemento miLista.

A través de un nombre de clase

Otro método de localización directa de elementos es getElementsByClassName. Devuelve una colección de elementos que tienen aplicada una determinada clase CSS a través del atributo class:

var rojos = document.getElementsByClassName("rojo");
for (var i = 0; i <rojos.length; i++) {
   rojos[i].className = "verde";
}

Este fragmento localiza todos los elementos que tienen aplicada la clase .rojo y la sustituye por otra llamada .verde. El método localiza los elementos que tengan aplicada esa clase aunque tengan varias clases aplicadas a la vez. Al igual que el anterior, funciona no sólo con document, sino con los nodos que pueden contener a otros.

También podríamos haber escrito el código de esta otra manera más adecuada:

var rojos = document.getElementsByClassName("rojo");
for (var i = 0; i <rojos.length; i++) {
   rojos[i].classList.remove("rojo");
   rojos[i].classList.add("verde");
}

que utiliza un objeto DOMTokenList obtenido de la propiedad classList de cualquier nodo. Esta propiedad nos permite gestionar las clases de un elemento mediante una colección, en lugar de simplemente establecerlas todas de golpe con className. Así, el método add nos permite añadir una clase y remove eliminarla. De esta forma, si el elemento tiene varias clases aplicadas, podremos actuar solamente sobre las que nos interesen, en lugar de gestionarlas todas a la vez con una cadena. classList es la propiedad que se suele usar para esto.

A través de un selector CSS

El método más potente para localizar elementos en una página es querySelectorAll. Nos permite utilizar selectores CSS (incluyendo pseudoclases) para localizar cualquier elemento o conjunto de elementos de la página.

Como único argumento de este método le pasaremos una cadena con el selector CSS que queremos utilizar para localizar el elemento, obteniendo una colección de nodos que cumplen con ese selector. Por ejemplo, en el HTML anterior podemos escribir:

var ultimosDeLaLista = document.querySelectorAll("ul > li:last-child");

Con esta instrucción obtendremos los últimos elementos (pseudoclase :last-child) de tipo <li> contenidos en elementos de tipo <ul>, o sea, todos los últimos elementos de todas las listas no ordenadas que haya en la página.

También podemos combinar selectores, como en CSS, para cumplir varias condiciones al mismo tiempo:

var indicadores = document.querySelectorAll("div.verde, div.rojo");

Este selector nos devolverá todos los contenedores de tipo <div> que tengan aplicada la clase “verde” o la clase “rojo”.

Si solo nos interesa el primer elemento que cumpla con el selector CSS indicado, podemos emplear la variante querySelector (sin el “All” al final) que se limita a devolver el primero de la colección.

Estos dos últimos métodos funcionan en todos los navegadores, y en el caso de Internet Explorer a partir de la versión 8.0.

Si has trabajado alguna vez con jQuery u otra biblioteca similar encontrarás este método querySelectorAll muy similar. De hecho lo es y en el caso concreto de jQuery usa por debajo este método cuando lo considera apropiado. Entonces, ¿por qué usar jQuery y no directamente el DOM? Las principales diferencias estriban básicamente en que el código es compatible con todos los navegadores y las colecciones devueltas por jQuery, al ser objetos jQuery, nos permiten una mayor flexibilidad para hacer cosas.

DEMO: Localizar elementos en el DOM

En el siguiente código HTML tenemos varios ejemplos para localizar elementos en el DOM utilizando:

  • getElementById
  • getElementsByTagName
  • getElementsByClassName
  • querySelector
<!DOCTYPE html>
<html>
    <head>
        <title> </title>
        <style type="text/css">
            .rojo {
                color: red;
            }
            .verde {
                color: green;
            }
        </style>
    </head>
    <body>
        <h1 class="rojo">Ejemplo de DOM</h1>
        <ul id="lista">
            <li>Elemento 1</li>
            <li>Elemento 2</li>
        </ul>
 
        <script type="text/javascript">
        // Localizar por id
        alert(document.getElementById("lista").nodeName);
 
        // Localizar por etiqueta 
        var eltosLista = document.getElementsByTagName("li");
        for (var i = 0; i < eltosLista.length; i++)
        {
            alert(eltosLista[i].innerHTML);
        }
 
        // Localizar por etiqueta limitando la región
        var miLista = document.getElementById("lista");
        alert(miLista.getElementsByTagName("li")[0].innerHTML);
 
        // Localizar por nombre de clase
        var rojos = document.getElementsByClassName("rojo");
        for (var i = 0; i <rojos.length; i++) {
            rojos[i].className = "verde";
        }
 
        // Localizar por selectores CSS
        var ultimoDeLaLista = document.querySelector("ul > li:last-child");
        alert(ultimoDeLaLista.innerHTML);
 
        </script>
    </body>
</html>

Colecciones "vivas" y estáticas

Las colecciones resultantes de todos estos métodos de selección que devuelven resultados múltiples tienen la característica de que están “vivas” (los anglosajones les llaman live NodeLists).

Esto quiere decir que, si tras haber obtenido una colección de elementos con cualquiera de estos métodos, se introduce o se retira un nodo en la página que coincida con los resultados de búsqueda, dichos cambios se verán reflejados en la colección automáticamente y el nodo aparecerá o desaparecerá también de la misma. No es necesario volver a hacer la búsqueda. Esto las dota de gran flexibilidad y puede resultar útil en ciertas ocasiones.

La única excepción a esto es querySelectorAll. Este método devuelve colecciones estáticas de nodos y precisamente este es uno de los motivos de que sea mucho más lento que los restantes métodos.

Es decir, todos los métodos getElementBy* devuelven colecciones vivas, mientras que querySelectorAll devuelve colecciones estáticas. Debemos tener presente esta distinción al trabajar con estas colecciones, ya que suponen una gran diferencia si hacemos uso intensivo de cambios dinámicos en el árbol del DOM.

Como es evidente, todas estas colecciones no son matrices normales de JavaScript sino un tipo especial de colección denominada HTMLCollection. También es posible que sean colecciones que implementan la interfaz NodeList definida por el W3C. Pero, en cualquier caso, ambas se utilizan de igual manera desde JavaScript, y a efectos prácticos, podemos considerarlas como listas de nodos sin más.

Al no tratarse de matrices de JavaScript no podremos emplear los métodos habituales de éstas, como pop, push o join, por ejemplo. Digamos que lo único que conservan de las matrices nativas es la propiedad length y la forma de acceder a sus elementos por posición. En el resto no se asemejan en nada.

Por el contrario, las colecciones devueltas por los selectores de jQuery sí que son matrices (ampliadas con las capacidades de jQuery), ya que antes de devolver los elementos los copia a una matriz. Es interesante conocer esta distinción.

Principales propiedades base de los nodos

Ahora que ya sabemos acceder a los nodos del DOM de diversas maneras, debemos aprender a hacer cosas con ellos. Pero antes, es necesario conocer algunas propiedades base que cualquier elemento del DOM posee y que nos permitirán averiguar su tipo, su valor o conocer y modificar el HTML que contienen. Algunas las hemos visto ya implícitamente en ejemplos anteriores, pero ahora las explicaremos.

En el ejemplo de recursividad visto antes hemos utilizado la propiedad nodeName. Ésta devuelve una cadena de texto con el nombre en mayúsculas de la etiqueta utilizada para crear dicho elemento. Por ejemplo, para el cuerpo del documento devolvería la cadena “BODY”:

alert(document.body.nodeName); //BODY

Existe otra propiedad similar denominada tagName que devuelve exactamente lo mismo. La diferencia entre ambas estriba en que, como hemos visto, existen nodos en un documento HTML que no se corresponden con etiquetas en su código fuente, como por ejemplo los nodos de texto. En el caso de estos nodos, tagName devolverá undefined, ya que no hay etiqueta asociada alguna, mientras que nodeName devolverá siempre alguna descripción apropiada. En el caso de nodos que no son elementos HTML devolverá una etiqueta que empieza con una almohadilla # (#text, #comment…).

En general emplearemos nodeName en lugar de tagName debido a esto.

La propiedad nodeType nos proporciona un número a través del cual sabremos el tipo de nodo con el que estamos trabajando. Existen 12 tipos diferentes de nodos definidos en el DOM Level 1 (ver tabla a continuación). Sin embargo, por regla general, solamente trabajaremos con los tipos 1 y 3 (elementos HTML y textos sueltos). El resto raramente se utilizan y muchos se corresponden con nodos de documentos XML genéricos, no con código HTML.

Nombre del tipo de nodo Valor
ELEMENT_NODE 1
ATTRIBUTE_NODE 2
TEXT_NODE 3
CDATA_SECTION_NODE 4
ENTITY_REFERENCE_NODE 5
ENTITY_NODE 6
PROCESSING_INSTRUCTION_NODE 7
COMMENT_NODE 8
DOCUMENT_NODE 9
DOCUMENT_TYPE_NODE 10
DOCUMENT_FRAGMENT_NODE 11
NOTATION_NODE 12

Lo que querremos hacer con los nodos en muchas ocasiones es acceder a ver sus contenidos o incluso modificarlos. Para ello el DOM nos ofrece dos propiedades con este propósito.

La propiedad InnerHTML nos otorga acceso al HTML contenido dentro de una etiqueta, en aquellas que tiene sentido. La mayor parte las etiquetas HTML tienen la capacidad de contener a otras etiquetas. Por ejemplo, consideremos este fragmento:

<ul id="lista">
   <li>Elemento 1</li>
   <li>Elemento 2</li>
</ul>

Si escribimos esta instrucción:

alert(getElementById("lista").innerHTML);

Veremos aparecer por pantalla los elementos <li> contenidos en ese nodo:

lista-elementos

Un fallo habitual de principiantes es intentar acceder a los elementos de una página desde el código JavaScript que se ejecuta desde la cabecera al cargar la página. Dado que los navegadores interpretan el código JavaScript y HTML a medida que lo van leyendo, si ejecutamos un código en la cabecera que actúe sobre los elementos del cuerpo de la página obtendremos un error. El motivo es que, en el momento de procesar ese JavaScript, el procesador del navegador aún no ha llegado al cuerpo y por lo tanto todavía no existe elemento alguno en la página a efectos del script. Debemos asegurarnos de que el código que accede a los nodos de un documento HTML se ejecuta una vez que la página ya se ha cargado por completo, o bien que el fragmento lo colocamos en el código por debajo de los elementos sobre los que queremos actuar. En el módulo de eventos veremos cómo lograrlo.

Del mismo modo podemos cambiar el HTML de un nodo asignando un valor a esta propiedad. Esta es sin duda la operación más importante e interesante que permite hacer el DOM. Gracias a la sustitución dinámica del HTML incluido dentro de un nodo del DOM es posible la existencia de aplicaciones avanzadas (como los mencionados clientes de correo como GMail y Outlook.com). Todo este tipo de aplicaciones se basan en mostrar y ocultar elementos y en sustituir los contenidos HTML de ciertos nodos para mostrar diferentes informaciones.

En realidad, las bibliotecas avanzadas de gestión de elementos de interfaz de usuario, como las usadas en aplicaciones web complejas de tipo Single Page Application, suelen utilizar lo que se denomina un DOM Virtual, en lugar de trabajar directamente sobre el DOM real. Este DOM Virtual en memoria permite realizar todo tipo de operaciones que no inciden en la página y que, por lo tanto, no se deben renderizar y ofrecen un rendimiento mucho mayor. Una vez renderizado en el DOM virtual se pueden trasladar los resultados al DOM real para su visualización. De hecho, asignar valores a innerHTML no suele ser buena idea en aplicaciones que realizan muchos cambios y a menudo en una página si nos importa el rendimiento de ésta, ya que no es muy eficiente. Para aplicaciones convencionales donde esa variación de rendimiento es inapreciable, sí que se puede utilizar sin problema.

En nuestro ejemplo anterior, para mostrar el árbol generado con la función mostrarDOMApartirDe se insertaba la cadena generada por ésta dentro de un <div> de la página usando esta instrucción:

document.getElementById("dom").innerHTML =
    "<h2>DOM del documento actual:</h2>" +
    mostrarDOMApartirDe(document.documentElement);

El HTML introducido escribiendo en esta propiedad es interpretado por el navegador y se renderiza en el contexto de la página, es decir, teniendo en cuenta los estilos y el flujo existentes de antemano.

Hay que tener especial cuidado con no modificar el HTML interno de etiquetas que NO pueden contener a otras etiquetas. Por ejemplo las imágenes (etiquetas <img>) o los cambios de línea (<br>) no pueden contener a otros elementos. Lo más habitual es modificar los contenidos de etiquetas <div> y <span>, pero cualquier otra etiqueta contenedora nos servirá.

Otra típica cuestión que suele pasarse por alto y puede generar efectos no deseados es el hecho de usar el operador += con la propiedad innerHTML. Es perfectamente válido escribir código como este:

status.innerHTML += "<img src='conectado.gif'>";
status.innerHTML += " - Nombre de usuario";

De hecho este código funcionará. Sin embargo hay un efecto secundario resultante de cómo actúa esto por debajo. Cuando usamos el operador += para concatenar una cadena al HTML existente en un elemento, lo que ocurre es que se lee el valor actual (que es una cadena), se concatena la cadena extra que vamos a añadir, y se vuelve a asignar a la propiedad innerHTML. Esto provoca que se interprete nuevamente el HTML completo. Es decir, no se limita a añadir un fragmento e interpretar solo éste, sino que de cada vez se añade todo el HTML y se interpreta completo.

Esto, además de poco eficiente, puede provocar otros efectos secundarios como parpadeos, descargas innecesarias de recursos que se solicitan una y otra vez, etc… Es mucho más recomendable generar todo el HTML en una variable de texto y luego asignarla un sola vez.

La propiedad innerHTML sólo sirve para nodos del DOM correspondientes con elementos HTML. Pero como hemos visto hay otros tipos de nodos que no lo son, como los nodos de texto. Podemos modificar el contenido de este tipo de nodos gracias a la propiedad nodeValue. De todos modos, por regla general, si queremos cambiar por ejemplo un texto contenido en la página, en lugar de localizar su nodo en el DOM y usar nodeValue, lo que haremos será obtener una referencia a su padre (un div, span o párrafo, fáciles de localizar) y modificar su contenido con innerHTML.

Si solo nos interesa el contenido textual de un nodo, sin todo el HTML que lo compone, otra propiedad interesante que debemos conocer es innerText, que sirve tanto para leer como para cambiar el contenido de texto de cualquier nodo. Al leer esta propiedad obtenemos lo mismo que si el usuario hubiese seleccionado el contenido del nodo en el navegador y lo hubiese copiado al portapapales, es decir, incluyendo posibles cambios de línea y otros elementos textuales. También existe la propiedad textContent, que es muy parecida a la anterior pero que se limita a concatenar los contenidos textuales de los nodos hijos del actual.

Aunque ambos métodos pueden parecer idénticos en realidad no lo son. innerText tienen en cuenta el aspecto final del texto tal y como lo vería un usuario, mientras que textContent no. Así, por ejemplo, si el nodo al que le aplicamos estos métodos contiene una etiqueta <script> con código JavaScript dentro, innerText no lo considerará mientras que textContent sí. Por otro lado, como innerText tiene en cuenta cómo se ven los elementos, si uno de ellos está oculto mediante CSS no saldrá en los resultados tampoco (y además leer la propiedad innerText provoca un reflow (un reprocesado) de la página y puede ser costoso en páginas muy complejas).

Atributos y propiedades específicas de los nodos

Los elementos HTML de una página poseen diversos atributos según su tipo. Por ejemplo, los enlaces tienen atributos href y title mientras que las imágenes tienen atributos específicos como src o hspace.

Desde un nodo del DOM, que representa un elemento de nuestra página, podemos modificar cualquiera de los atributos de dicho elemento. Esto nos proporciona una potencia fenomenal para poder hacer cualquier cosa con un elemento de la página, leyendo o modificando cualquiera de sus propiedades.

Los atributos de un nodo HTML pueden leerse y modificarse de diversas maneras.

En primer lugar existen una serie de métodos específicos para acceder a los atributos:

  • getAttribute: obtiene el valor del atributo que se le especifique como argumento. Por ejemplo: elto.getAttribute("src");.
  • setAttribute: ajusta el atributo especificado como primer argumento con el valor especificado como segundo argumento. Ej: elto.setAttribute("src", "https://www.campusmvp.es");.
  • removeAttribute: retira del nodo el atributo especificado, dejándolo sin efecto. Por ejemplo, si retiramos el atributo alt a una imagen, ésta dejará de mostrar el tooltip correspondiente cuando le pasemos por encima con el cursor.
  • hasAttribute: comprueba si el atributo especificado está o no ajustado explícitamente para el nodo. No sirven los valores por defecto para los atributos. Sólo devolverá true si el atributo especificado se ha ajustado, bien en el código HTML, o bien por JavaScript.

Con estos métodos podemos acceder y modificar cualquier atributo de un objeto HTML.

Además los nodos ofrecen una colección denominada attributes que nos permite el acceso directo por nombre a cualquier atributo del mismo. Podemos escribir código como este:

img.attributes["src"].value = "https://www.campusmvp.es/images/logo.png";
alert(img.attributes["src"].value);

accediendo por nombre a los atributos.

Cada objeto de la colección tiene la propiedad value, que contiene el valor del atributo como refleja el ejemplo, y la propiedad name, que es el nombre del atributo.

Por ejemplo, para enumerar todos los atributos que tiene establecidos un elemento HTML (en este caso una imagen) escribiremos un código similar a este:

var img = document.getElementsByTagName("img")[0];
var atribs = "";
for (var i = 0; i < img.attributes.length; i++)
{
   atribs += img.attributes[i].name + ": " +
   img.attributes[i].value + "\n";
}
console.log(atribs);

La colección sólo contiene los atributos establecidos explícitamente, no todos los atributos posibles. Si queremos establecer un atributo no inicializado deberemos utilizar el método setAttribute.

Atributos como propiedades

La mayor parte de los atributos de un elemento HTML se generan automáticamente como propiedades del nodo correspondiente con el mismo nombre, y podemos leerlos o cambiarlos de manera directa y sencilla sin necesidad de nada de lo anterior. Por ejemplo:

var img = document.getElementsByTagName("img")[0];
alert(img.src);

Es decir, la mayor parte de los atributos de un elemento se corresponden con una propiedad homónima en el nodo del DOM correspondiente. El nombre de esta propiedad se construye usando la notación camelCase. Esto implica que los atributos que constan de una sola palabra (como src o title) se escriben totalmente en minúsculas. Sin embargo, los que están formados por más de una palabra se escriben con la primera letra de la segunda palabra en mayúsculas (por ejemplo cellPadding o hSpace).

Sabiendo esto, es muy sencillo cambiar mediante una simple propiedad el valor de casi cualquier atributo de un elemento HTML, como acabamos de ver.

Una excepción a esta norma de nombramiento de atributos/propiedades es la propiedad que nos permite trabajar con la clase CSS de un elemento. El atributo HTML correspondiente es class (por ejemplo <h1 class="cabecera">), así que la correspondiente propiedad debería ser también class. Sin embargo, el nombre de la propiedad que nos permite acceder a la clase de un elemento es className.

Es relevante señalar que no siempre existe una relación bidireccional entre atributos de una etiqueta HTML y las propiedades del nodo correspondiente en el DOM. De hecho, algunas propiedades no tienen atributos correspondientes en HTML y algunos atributos no tienen propiedades relacionadas y a veces ¡no van sincronizados!. Más detalles en este artículo: La relación entre atributos HTML y propiedades JavaScript.

Otra propiedad especial es la que nos permite jugar con el estilo CSS de los elementos. Cuando especificamos estilos en línea (inline) para un elemento el atributo **style contiene diversas propiedades escritas en una misma cadena:

<a style="text-decoration:dashed;font-size:1.2em;" ... >

Para acceder a estos estilos inline de un elemento utilizaremos la propiedad style del nodo correspondiente. Ésta devuelve un objeto que contiene tantas propiedades como posibles atributos CSS se pueden utilizar para ese nodo. Los nombres de las propiedades siguen también notación Camel Case, con la salvedad de que los guiones del nombre se eliminan.

Así por ejemplo la propiedad CSS border se convierte en la propiedad border del estilo, y la propiedad font-decoration se convierte sin embargo en fontDecoration (se elimina el guion y se aplica el Camel Casing). Por ejemplo:

var enlace = document.getElementsByTagName("a")[0];
enlace.style.border = "2px dashed red";
enlace.style.padding = "15px";
enlace.style.fontSize = "12pt";

Como podemos observar los valores asignados son los mismos que pondríamos en el CSS.

Para trabajar de este modo con los nodos que representan elementos HTML conviene saberse muy bien los posibles atributos y estilos que se pueden modificar para cada uno. Como es casi imposible sabérselos todos de memoria conviene tener a mano una buena guía on-line que nos proporcione esa información, como por ejemplo la MDN (mucho mejor siempre en inglés, que es más completa y actualizada).

DEMO: Manejo de atributos de nodos

<!DOCTYPE html>
<html>
    <head>
        <title> </title>
        <style type="text/css">
            .rojo {
                color: red;
            }
            .verde {
                color: green;
            }
        </style>
    </head>
    <body>
        <h1 class="rojo">Ejemplo de DOM</h1>
        <ul id="lista">
            <li>Elemento 1</li>
            <li>Elemento 2</li>
        </ul>
        <a href="http://www.campusmvp.com" title="Visita la web de campusMVP">campusMVP</a>
        <img src="http://www.campusmvp.com/Logo/logo.png" alt="Logo campusMVP" />
 
        <script type="text/javascript">
            var img = document.getElementsByTagName("img")[0];
            //por métodos de atributos 
            alert(img.getAttribute("src"));
            //por colección de atributos
            alert(img.attributes["src"].value);
            //por propiedad
            alert(img.src);
            //verificar si posee un atributo
            alert(img.hasAttribute("alt")); //true
            alert(img.hasAttribute("hspace")); //false
 
            //mostrar todos los atributos de la imagen
            var atribs = "";
            for (var i = 0; i < img.attributes.length; i++)
            {
                atribs += img.attributes[i].name + ": " + img.attributes[i].value + "\n";
            }
            alert(atribs);
 
            //Cambiar los rojos que haya a verdes
            var rojos = document.getElementsByClassName("rojo");
            for (var i = 0; i < rojos.length; i++) {
                rojos[i].className = "verde";
            }
 
            //Acceso a los estilos
            var enlace = document.getElementsByTagName("a")[0];
            enlace.style.border = "2px dashed red";
            enlace.style.padding = "15px";
            enlace.style.fontSize = "12pt";
        </script>
    </body>
</html>
informatica/programacion/cursos/programacion_avanzada_javascript/manipulacion_dinamica_elementos_navegador.1728472137.txt.gz · Última modificación: por tempwin