Dethe Elza ( [email protected] ), arquitecto técnico sénior, Blast Radius
El modelo de objetos de documento (DOM) es una de las herramientas más utilizadas para manipular datos XML y HTML, pero su potencial rara vez se aprovecha al máximo. Al aprovechar el DOM y hacerlo más fácil de usar, obtiene una poderosa herramienta para aplicaciones XML, incluidas las aplicaciones web dinámicas.
Esta edición presenta a la columnista invitada, amiga y colega Dethe Elza. Dethe tiene una amplia experiencia en el desarrollo de aplicaciones web utilizando XML y me gustaría agradecerle por ayudarme a introducir la programación XML utilizando DOM y ECMAScript. Estén atentos a esta columna para conocer más columnas de Dethe.
- David Mertz
DOM es una de las API estándar para procesar XML y HTML. A menudo se le critica por requerir mucha memoria, ser lento y detallado. Aún así, es la mejor opción para muchas aplicaciones y ciertamente es más simple que SAX, la otra API importante de XML. DOM está apareciendo gradualmente en herramientas como navegadores web, navegadores SVG, OpenOffice, etc.
DOM es excelente porque es un estándar y está ampliamente implementado e integrado en otros estándares. Como estándar, su manejo de datos es independiente del lenguaje de programación (lo que puede ser una fortaleza o no, pero al menos hace que la forma en que manejamos los datos sea consistente). El DOM ahora no sólo está integrado en los navegadores web, sino que también forma parte de muchas especificaciones basadas en XML. Ahora que es parte de tu arsenal, y tal vez todavía lo uses ocasionalmente, supongo que es hora de aprovechar al máximo lo que aporta.
Después de trabajar con el DOM por un tiempo, verá que se desarrollan patrones: cosas que desea hacer una y otra vez. Los atajos le ayudan a trabajar con DOM extensos y a crear código elegante y que se explica por sí mismo. Aquí hay una colección de consejos y trucos que uso con frecuencia, junto con algunos ejemplos de JavaScript.
El primer truco
para insertarDespués y anteponerChild
es que "no hay truco".El DOM tiene dos métodos para agregar un nodo secundario a un nodo contenedor (generalmente un elemento, un documento o un fragmento de documento): appendChild(nodo) e insertBefore(nodo, nodo de referencia). Parece que falta algo. ¿Qué sucede si quiero insertar o anteponer un nodo secundario después de un nodo de referencia (haciendo que el nuevo nodo sea el primero en la lista)? Durante muchos años, mi solución fue escribir la siguiente función:
Listado 1. Métodos incorrectos para insertar y agregar mediante
función insertarDespués (padre, nodo, nodo de referencia) {
if(referenceNode.nextSibling) {
parent.insertBefore(nodo, referenceNode.nextSibling);
} demás {
padre.appendChild(nodo);
}
}
función anteponerChild(padre, nodo) {
si (padre.primerhijo) {
parent.insertBefore(nodo, parent.firstChild);
} demás {
padre.appendChild(nodo);
}
}
De hecho, al igual que el Listado 1, la función insertBefore() se ha definido para volver a appendChild() cuando el nodo de referencia está vacío. Entonces, en lugar de usar los métodos anteriores, puede usar los métodos del Listado 2, o omitirlos y simplemente usar las funciones integradas:
Listado 2. Forma correcta de insertar y agregar desde antes
función insertarDespués (padre, nodo, nodo de referencia) {
parent.insertBefore(nodo, referenceNode.nextSibling);
}
función anteponerChild(padre, nodo) {
parent.insertBefore(nodo, parent.firstChild);
}
Si es nuevo en la programación DOM, es importante señalar que, aunque puede tener varios punteros apuntando a un nodo, ese nodo sólo puede existir en una ubicación en el árbol DOM. Entonces, si desea insertarlo en el árbol, no es necesario eliminarlo primero del árbol, ya que se eliminará automáticamente. Este mecanismo resulta útil cuando se reordenan los nodos simplemente insertándolos en sus nuevas posiciones.
Según este mecanismo, si desea intercambiar las posiciones de dos nodos adyacentes (llamados nodo1 y nodo2), puede utilizar una de las siguientes soluciones:
node1.parentNode.insertBefore(node2, node1);
o
node1.parentNode.insertBefore(node1.nextSibling, node1);
¿Qué más puedes hacer con DOM?
DOM se usa ampliamente en páginas web. Si visita el sitio de bookmarklets (ver Recursos), encontrará muchos scripts cortos creativos que pueden reorganizar páginas, extraer enlaces, ocultar imágenes o anuncios Flash, y más.
Sin embargo, debido a que Internet Explorer no define constantes de interfaz de nodo que puedan usarse para identificar tipos de nodos, debe asegurarse de que si omite una constante de interfaz, primero defina la constante de interfaz en el script DOM para la Web.
Listado 3. Asegurarse de que el nodo esté definido
si (!ventana['Nodo']) {
ventana.Nodo = nuevo Objeto();
Nodo.ELEMENT_NODE = 1;
Nodo.ATTRIBUTE_NODE = 2;
Nodo.TEXT_NODE = 3;
Nodo.CDATA_SECTION_NODE = 4;
Nodo.ENTITY_REFERENCE_NODE = 5;
Nodo.ENTITY_NODE = 6;
Nodo.PROCESSING_INSTRUCTION_NODE = 7;
Nodo.COMMENT_NODE = 8;
Nodo.DOCUMENT_NODE = 9;
Nodo.DOCUMENT_TYPE_NODE = 10;
Nodo.DOCUMENT_FRAGMENT_NODE = 11;
Nodo.NOTATION_NODE = 12;
}
El Listado 4 muestra cómo extraer todos los nodos de texto contenidos en un nodo:
Listado 4. Texto interno
función Texto interno (nodo) {
// ¿Es este un nodo de texto o CDATA?
if (nodo.nodeType == 3 || nodo.nodeType == 4) {
devolver nodo.datos;
}
var i;
var valorretorno = [];
para (i = 0; i < nodo.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
retorno returnValue.join('');
}
Atajos
La gente suele quejarse de que el DOM es demasiado detallado y que las funciones simples requieren mucho código. Por ejemplo, si desea crear un elemento <div> que contenga texto y responda a un clic en un botón, el código podría verse así:
Listado 5. El "largo camino" para crear un <div>
función handle_button() {
var padre = document.getElementById('myContainer');
var div = document.createElement('div');
div.className = 'myDivCSSClass';
div.id = 'miDivId';
div.style.position = 'absoluto';
div.style.left = '300px';
div.style.top = '200px';
var text = "Este es el primer texto del resto de este código";
var textNode = document.createTextNode(texto);
div.appendChild(textNode);
padre.appendChild(div);
}
Si crea nodos de esta manera con frecuencia, escribir todo este código lo cansará rápidamente. Tenía que haber una solución mejor, ¡y la hubo! Aquí hay una utilidad que lo ayuda a crear elementos, establecer propiedades y estilos de elementos y agregar nodos secundarios de texto. Excepto el parámetro de nombre, todos los demás parámetros son opcionales.
Listado 6. Atajo de función elem()
elemento de función (nombre, atributos, estilo, texto) {
var e = document.createElement(nombre);
si (atributos) {
para (ingrese atributos) {
si (clave == 'clase') {
e.className = atributos[clave];
} más si (clave == 'id') {
e.id = atributos[clave];
} demás {
e.setAttribute(clave, atributos[clave]);
}
}
}
si (estilo) {
para (tecla de estilo) {
e.style[clave] = estilo[clave];
}
}
si (texto) {
e.appendChild(document.createTextNode(texto));
}
devolver e;
}
Usando este atajo, puede crear el elemento <div> en el Listado 5 de una manera más concisa. Tenga en cuenta que los atributos y los parámetros de estilo se proporcionan mediante objetos de texto JavaScript.
Listado 7. Una manera fácil de crear un <div>
función handle_button() {
var padre = document.getElementById('myContainer');
padre.appendChild(elem('div',
{clase: 'myDivCSSClass', id: 'myDivId'}
{posición: 'absoluta', izquierda: '300px', arriba: '200px'},
'Este es el primer texto del resto de este código'));
}
Esta utilidad puede ahorrarle mucho tiempo cuando desee crear rápidamente una gran cantidad de objetos DHTML complejos. El patrón aquí significa que si tiene una estructura DOM específica que debe crearse con frecuencia, use una utilidad para crearla. Esto no solo reduce la cantidad de código que escribe, sino que también reduce el corte y pegado repetitivo de código (un culpable de los errores) y hace que sea más fácil pensar con claridad al leer el código.
¿Qué sigue?
El DOM a menudo tiene dificultades para indicarle cuál es el siguiente nodo en el orden del documento. A continuación se muestran algunas utilidades que le ayudarán a avanzar y retroceder entre nodos:
Listado 8. nextNode y prevNode
// devuelve el siguiente nodo en el orden del documento
función siguienteNodo(nodo) {
si (!nodo) devuelve nulo;
si (nodo.primerChild){
devolver nodo.firstChild;
} demás {
devolver nextWide(nodo);
}
}
// función auxiliar para nextNode()
función nextWide(nodo) {
si (!nodo) devuelve nulo;
si (nodo.nextSibling) {
devolver nodo.nextSibling;
} demás {
devolver nextWide(nodo.parentNode);
}
}
// devuelve el nodo anterior en el orden del documento
función nodoprev(nodo) {
si (!nodo) devuelve nulo;
if (nodo.anteriorSibling) {
devolver anteriorDeep(nodo.previousSibling);
}
devolver nodo.parentNode;
}
// función auxiliar para prevNode()
función anteriorDeep(nodo) {
si (!nodo) devuelve nulo;
mientras (nodo.childNodes.length) {
nodo = nodo.lastChild;
}
nodo de retorno;
}
Utilice DOM fácilmente
A veces es posible que desees iterar a través del DOM, llamando a una función en cada nodo o devolviendo un valor de cada nodo. De hecho, debido a que estas ideas son tan generales, DOM Nivel 2 ya incluye una extensión llamada DOM Traversal and Range (que define objetos y API para iterar todos los nodos en el DOM), que se usa para aplicar funciones y seleccionar un rango en el DOM. . Debido a que estas funciones no están definidas en Internet Explorer (al menos no todavía), puedes usar nextNode() para hacer algo similar.
Aquí, la idea es crear algunas herramientas simples y comunes y luego ensamblarlas de diferentes maneras para lograr el efecto deseado. Si está familiarizado con la programación funcional, esto le resultará familiar. La biblioteca Beyond JS (ver Recursos) lleva adelante esta idea.
Listado 9. Utilidades DOM funcionales
// devuelve una matriz de todos los nodos, comenzando en startNode y
// continuando por el resto del árbol DOM
función listaNodos(inicioNodo) {
lista var = nueva matriz();
var nodo = inicioNodo;
mientras (nodo) {
lista.push(nodo);
nodo = siguienteNodo(nodo);
}
lista de devolución;
}
// Lo mismo que listNodes(), pero funciona al revés desde startNode.
// Tenga en cuenta que esto no es lo mismo que ejecutar listNodes() y
// invirtiendo la lista.
función listaNodosReversed(startNode) {
lista var = nueva matriz();
var nodo = inicioNodo;
mientras (nodo) {
lista.push(nodo);
nodo = nodoanterior(nodo);
}
lista de devolución;
}
// aplica la función a cada nodo en nodeList, devuelve una nueva lista de resultados
mapa de funciones (lista, func) {
var lista_resultados = nueva matriz();
for (var i = 0; i < lista.longitud; i++) {
result_list.push(func(lista[i]));
}
devolver lista_resultados;
}
// aplica la prueba a cada nodo, devuelve una nueva lista de nodos para los cuales
// prueba(nodo) devuelve verdadero
filtro de función (lista, prueba) {
var lista_resultados = nueva matriz();
for (var i = 0; i < lista.longitud; i++) {
if (prueba(lista[i])) result_list.push(lista[i]);
}
devolver lista_resultados;
}
El Listado 9 contiene cuatro herramientas básicas. Las funciones listNodes() y listNodesReversed() se pueden extender a una longitud opcional, similar al método slice() del Array. Dejo esto como ejercicio para usted. Otra cosa a tener en cuenta es que las funciones map() y filter() son completamente genéricas y pueden usarse para trabajar con cualquier lista (no solo listas de nodos). Ahora te muestro algunas formas en que se pueden combinar.
Listado 10. Usando utilidades funcionales
// Una lista de todos los nombres de elementos en el orden del documento
función esElemento(nodo) {
devolver node.nodeType == Node.ELEMENT_NODE;
}
función nombre de nodo (nodo) {
devolver nodo.nombrenodo;
}
var elementNames = map(filter(listNodes(documento),isElement), nodeName);
// Todo el texto del documento (ignora CDATA)
la función esTexto(nodo) {
devolver nodo.nodeType == Nodo.TEXT_NODE;
}
función valornodo(nodo) {
devolver nodo.nodeValue;
}
var allText = map(filter(listNodes(documento), isText), nodeValue);
Puede utilizar estas utilidades para extraer ID, modificar estilos, encontrar ciertos nodos y eliminarlos, y más. Una vez que las API DOM Traversal y Range estén ampliamente implementadas, puede usarlas para modificar el árbol DOM sin crear primero la lista. No sólo son poderosos, sino que también funcionan de manera similar a lo que destaqué anteriormente.
La zona de peligro del DOM
Tenga en cuenta que la API DOM principal no le permite analizar datos XML en DOM ni serializar DOM en XML. Estas funciones están definidas en la extensión DOM Nivel 3 "Cargar y guardar", pero aún no están completamente implementadas, así que no piense en ellas ahora. Cada plataforma (navegador u otra aplicación DOM profesional) tiene su propio método de conversión entre DOM y XML, pero la conversión multiplataforma está fuera del alcance de este artículo.
DOM no es una herramienta muy segura, especialmente cuando se utiliza la API DOM para crear árboles que no se pueden serializar como XML. Nunca mezcle API que no sean de espacio de nombres DOM1 y API que tengan en cuenta el espacio de nombres DOM2 (por ejemplo, createElement y createElementNS) en el mismo programa. Si usa espacios de nombres, intente declarar todos los espacios de nombres en la posición del elemento raíz y no anule el prefijo del espacio de nombres; de lo contrario, las cosas se volverán muy confusas. En términos generales, siempre que sigas tu rutina, no desencadenarás situaciones críticas que puedan meterte en problemas.
Si ha estado utilizando el texto interno y el HTML interno de Internet Explorer para el análisis, puede intentar usar la función elem(). Al crear utilidades similares, obtiene más comodidad y hereda los beneficios del código multiplataforma. Mezclar estos dos métodos es muy malo.
Algunos caracteres Unicode no están incluidos en XML. La implementación del DOM permite agregarlos, pero la consecuencia es que no se pueden serializar. Estos caracteres incluyen la mayoría de los caracteres de control y caracteres individuales en pares sustitutos Unicode. Esto sólo sucede si intentas incluir datos binarios en el documento, pero esa es otra situación complicada.
Conclusión
He cubierto mucho de lo que el DOM puede hacer, pero hay mucho más que el DOM (y JavaScript) pueden hacer. Estudie y explore estos ejemplos para ver cómo se pueden utilizar para resolver problemas que pueden requerir scripts de cliente, plantillas o API especializadas.
El DOM tiene sus limitaciones y desventajas, pero también tiene muchas ventajas: está integrado en muchas aplicaciones; funciona de la misma manera ya sea que use tecnología Java, Python o JavaScript; es muy fácil de usar SAX; use las plantillas mencionadas anteriormente; que es a la vez simple y potente de usar. Un número cada vez mayor de aplicaciones están comenzando a admitir DOM, incluidas las aplicaciones basadas en Mozilla, OpenOffice y XMetaL de Blast Radius. Cada vez más especificaciones requieren el DOM y lo amplían (por ejemplo, SVG), por lo que el DOM siempre está a su alrededor. Sería prudente utilizar esta herramienta ampliamente implementada.