Dethe Elza ( [email protected] ), Arquiteto Técnico Sênior, Blast Radius
O Document Object Model (DOM) é uma das ferramentas mais comumente usadas para manipular dados XML e HTML, mas seu potencial raramente é totalmente explorado. Ao aproveitar o DOM e torná-lo mais fácil de usar, você obtém uma ferramenta poderosa para aplicativos XML, incluindo aplicativos Web dinâmicos.
Esta edição traz a colunista convidada, amiga e colega Dethe Elza. Dethe tem ampla experiência no desenvolvimento de aplicações Web usando XML e gostaria de agradecê-lo por me ajudar a introduzir a programação XML usando DOM e ECMAScript. Fique ligado nesta coluna para mais colunas de Dethe.
- David Mertz
DOM é uma das APIs padrão para processamento de XML e HTML. Muitas vezes é criticado por consumir muita memória, ser lento e detalhado. Ainda assim, é a melhor escolha para muitas aplicações e certamente é mais simples que SAX, outra API importante do XML. O DOM está aparecendo gradualmente em ferramentas como navegadores da web, navegadores SVG, OpenOffice e assim por diante.
O DOM é ótimo porque é um padrão amplamente implementado e incorporado a outros padrões. Como padrão, seu tratamento de dados é independente da linguagem de programação (o que pode ou não ser um ponto forte, mas pelo menos torna consistente a maneira como lidamos com os dados). O DOM agora não está apenas integrado aos navegadores da Web, mas também faz parte de muitas especificações baseadas em XML. Agora que faz parte do seu arsenal, e talvez você ainda o use ocasionalmente, acho que é hora de aproveitar ao máximo o que ele traz para a mesa.
Depois de trabalhar com o DOM por um tempo, você verá o desenvolvimento de padrões – coisas que você deseja fazer repetidamente. Os atalhos ajudam você a trabalhar com DOMs longos e a criar códigos elegantes e autoexplicativos. Aqui está uma coleção de dicas e truques que uso com frequência, junto com alguns exemplos de JavaScript.
O primeiro truque
para insertAfter e prependChild
é que "não há truque".O DOM possui dois métodos para adicionar um nó filho a um nó de contêiner (geralmente um Elemento, ou um Documento ou Fragmento de Documento): appendChild(node) e insertBefore(node, referenceNode). Parece que algo está faltando. E se eu quiser inserir ou acrescentar um nó filho após um nó de referência (tornando o novo nó o primeiro da lista)? Por muitos anos, minha solução foi escrever a seguinte função:
Listagem 1. Métodos errados para inserir e adicionar por
function insertAfter(pai, nó, referênciaNode) {
if(referenceNode.nextSibling) {
parent.insertBefore (nó, referenceNode.nextSibling);
} outro {
pai.appendChild(nó);
}
}
function prependChild(pai, nó) {
if (pai.primeiroFilho) {
parent.insertBefore(nó, parent.firstChild);
} outro {
pai.appendChild(nó);
}
}
Na verdade, como na Listagem 1, a função insertBefore() foi definida para retornar paraappendChild() quando o nó de referência está vazio. Portanto, em vez de usar os métodos acima, você pode usar os métodos da Listagem 2 ou ignorá-los e apenas usar as funções integradas:
Listagem 2. Maneira correta de inserir e adicionar de antes
function insertAfter(pai, nó, referênciaNode) {
parent.insertBefore (nó, referenceNode.nextSibling);
}
function prependChild(pai, nó) {
parent.insertBefore(nó, parent.firstChild);
}
Se você é novo na programação DOM, é importante ressaltar que, embora você possa ter vários ponteiros apontando para um nó, esse nó só pode existir em um local na árvore DOM. Portanto, se você quiser inseri-lo na árvore, não há necessidade de removê-lo primeiro da árvore, pois ele será removido automaticamente. Este mecanismo é conveniente ao reordenar nós, simplesmente inserindo-os em suas novas posições.
De acordo com esse mecanismo, se você quiser trocar as posições de dois nós adjacentes (chamados node1 e node2), poderá usar uma das seguintes soluções:
node1.parentNode.insertBefore(node2, node1);
ou
(
node1.nextSibling, node1);
DOM é amplamente utilizado em páginas da web. Se você visitar o site de bookmarklets (consulte Recursos), encontrará muitos scripts curtos e criativos que podem reorganizar páginas, extrair links, ocultar imagens ou anúncios em Flash e muito mais.
No entanto, como o Internet Explorer não define constantes de interface de nó que podem ser usadas para identificar tipos de nós, você deve garantir que, se omitir uma constante de interface, primeiro defina a constante de interface no script DOM para a Web.
Listagem 3. Certificando-se de que o nó está definido
if (!window['Nó']) {
janela.Node = new Object();
Nó.ELEMENT_NODE = 1;
Nó.ATTRIBUTE_NODE = 2;
Nó.TEXT_NODE = 3;
Nó.CDATA_SECTION_NODE = 4;
Nó.ENTITY_REFERENCE_NODE = 5;
Nó.ENTITY_NODE = 6;
Nó.PROCESSING_INSTRUCTION_NODE = 7;
Nó.COMMENT_NODE = 8;
Nó.DOCUMENT_NODE = 9;
Nó.DOCUMENT_TYPE_NODE = 10;
Nó.DOCUMENT_FRAGMENT_NODE = 11;
Nó.NOTATION_NODE = 12;
}
A Listagem 4 mostra como extrair todos os nós de texto contidos em um nó:
Listagem 4. Texto interno
função TextoInterior(nó) {
// este é um nó de texto ou CDATA?
if (node.nodeType == 3 || node.nodeType == 4) {
retornar node.data;
}
var eu;
var returnValue = [];
for (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
return returnValue.join('');
}
Atalhos
As pessoas costumam reclamar que o DOM é muito detalhado e que funções simples exigem muito código. Por exemplo, se você quiser criar um elemento <div> que contenha texto e responda a um clique de botão, o código poderá ser semelhante a:
Listagem 5. O "longo caminho" para criar um <div>
função handle_button() {
var pai = document.getElementById('myContainer');
var div = document.createElement('div');
div.className = 'minhaDivCSSClass';
div.id = 'meuDivId';
div.style.position = 'absoluto';
div.style.left = '300px';
div.style.top = '200px';
var text = "Este é o primeiro texto do restante deste código";
var textNode = document.createTextNode(texto);
div.appendChild(textNode);
parent.appendChild(div);
}
Se você cria nós dessa maneira com frequência, digitar todo esse código irá cansá-lo rapidamente. Tinha que haver uma solução melhor – e havia! Aqui está um utilitário que ajuda a criar elementos, definir propriedades e estilos de elementos e adicionar nós filhos de texto. Exceto o parâmetro name, todos os outros parâmetros são opcionais.
Listagem 6. Atalho da função elem()
function elem(nome, atributos, estilo, texto) {
var e = document.createElement(nome);
if (atributos) {
for (digite atributos) {
if (chave == 'classe') {
e.className = attrs[chave];
} else if (chave == 'id') {
e.id = atributos[chave];
} outro {
e.setAttribute(chave, atributos[chave]);
}
}
}
se (estilo) {
for (chave no estilo) {
e.estilo[chave] = estilo[chave];
}
}
se (texto) {
e.appendChild(document.createTextNode(texto));
}
retornar e;
}
Usando este atalho, você pode criar o elemento <div> na Listagem 5 de uma forma mais concisa. Observe que os parâmetros de atributos e estilo são fornecidos usando objetos de texto JavaScript.
Listagem 7. Uma maneira fácil de criar um <div>
função handle_button() {
var pai = document.getElementById('myContainer');
parent.appendChild(elem('div',
{class: 'myDivCSSClass', id: 'myDivId'}
{posição: 'absoluta', esquerda: '300px', parte superior: '200px'},
'Este é o primeiro texto do resto deste código'));
}
Este utilitário pode economizar muito tempo quando você deseja criar rapidamente um grande número de objetos DHTML complexos. O padrão aqui significa que se você tiver uma estrutura DOM específica que precisa ser criada com frequência, use um utilitário para criá-la. Isso não apenas reduz a quantidade de código que você escreve, mas também reduz o corte e colagem repetitivos de código (culpado de erros) e torna mais fácil pensar com clareza ao ler o código.
O que vem a seguir?
O DOM muitas vezes tem dificuldade em dizer qual é o próximo nó na ordem do documento. Aqui estão alguns utilitários para ajudá-lo a avançar e retroceder entre os nós:
Listagem 8. nextNode e prevNode
// retorna o próximo nó na ordem do documento
função próximoNode(nó) {
se (!node) retornar nulo;
if (node.firstChild){
retornar node.firstChild;
} outro {
retornar nextWide(nó);
}
}
//função auxiliar para nextNode()
função nextWide(nó) {
se (!node) retornar nulo;
if (node.nextSibling) {
retornar node.nextSibling;
} outro {
retornar nextWide(node.parentNode);
}
}
// retorna o nó anterior na ordem do documento
função anteriorNode(nó) {
se (!node) retornar nulo;
if (node.previousSibling) {
retornar anteriorDeep(node.previousSibling);
}
retornar node.parentNode;
}
//função auxiliar para prevNode()
função anteriorDeep(nó) {
se (!node) retornar nulo;
enquanto (node.childNodes.length) {
nó = node.lastChild;
}
nó de retorno;
}
Use DOM facilmente
Às vezes você pode querer iterar pelo DOM, chamando uma função em cada nó ou retornando um valor de cada nó. Na verdade, como essas ideias são tão gerais, o DOM Nível 2 já inclui uma extensão chamada DOM Traversal and Range (que define objetos e APIs para iterar todos os nós no DOM), que é usada para aplicar Function e selecionar um intervalo no DOM. . Como essas funções não estão definidas no Internet Explorer (pelo menos ainda não), você pode usar nextNode() para fazer algo semelhante.
Aqui, a ideia é criar algumas ferramentas simples e comuns e depois montá-las de diferentes maneiras para conseguir o efeito desejado. Se você estiver familiarizado com programação funcional, isso lhe parecerá familiar. A biblioteca Beyond JS (consulte Recursos) leva essa ideia adiante.
Listagem 9. Utilitários DOM funcionais
// retorna um Array de todos os nós, começando em startNode e
// continuando pelo resto da árvore DOM
function listaNodes(startNode) {
var lista = new Array();
var nó = startNode;
enquanto(nó) {
lista.push(nó);
nó = próximoNode(nó);
}
lista de retorno;
}
// O mesmo que listNodes(), mas funciona de trás para frente a partir de startNode.
// Observe que isso não é o mesmo que executar listNodes() e
// invertendo a lista.
function listNodesReversed(startNode) {
var lista = new Array();
var nó = startNode;
enquanto(nó) {
lista.push(nó);
nó = prevNode(nó);
}
lista de retorno;
}
// aplica func a cada nó em nodeList, retorna nova lista de resultados
mapa de função(lista, func) {
var lista_resultados = new Array();
for (var i = 0; i < lista.comprimento; i++) {
lista_resultados.push(func(lista[i]));
}
retornar lista_resultados;
}
// aplica o teste a cada nó, retorna uma nova lista de nós para os quais
//teste(nó) retorna verdadeiro
função filtro(lista, teste) {
var lista_resultados = new Array();
for (var i = 0; i < lista.comprimento; i++) {
if (teste(lista[i])) lista_resultado.push(lista[i]);
}
retornar lista_resultados;
}
A Listagem 9 contém quatro ferramentas básicas. As funções listNodes() e listNodesReversed() podem ser estendidas para um comprimento opcional, semelhante ao método slice() do Array. Deixo isso como um exercício para você. Outra coisa a notar é que as funções map() e filter() são completamente genéricas e podem ser usadas para trabalhar com qualquer lista (não apenas listas de nós). Agora mostro algumas maneiras de combiná-los.
Listagem 10. Usando utilitários funcionais
// Uma lista de todos os nomes de elementos na ordem do documento
função isElement(nó) {
retornar node.nodeType == Node.ELEMENT_NODE;
}
function nodeName(nó) {
retornar node.nodeName;
}
var elementNames = map(filter(listNodes(document),isElement), nodeName);
// Todo o texto do documento (ignora CDATA)
function isText(nó) {
retornar node.nodeType == Node.TEXT_NODE;
}
função nodeValue(nó) {
retornar node.nodeValue;
}
var allText = map(filter(listNodes(document), isText), nodeValue);
Você pode usar esses utilitários para extrair IDs, modificar estilos, encontrar determinados nós e removê-los e muito mais. Depois que as APIs DOM Traversal e Range forem amplamente implementadas, você poderá usá-las para modificar a árvore DOM sem primeiro construir a lista. Eles não são apenas poderosos, mas também funcionam de maneira semelhante ao que destaquei acima.
A zona de perigo do DOM
Observe que a API DOM principal não permite analisar dados XML em DOM ou serializar DOM em XML. Essas funções são definidas na extensão "Load and Save" do DOM Nível 3, mas ainda não estão totalmente implementadas, então não pense nelas agora. Cada plataforma (navegador ou outro aplicativo DOM profissional) possui seu próprio método de conversão entre DOM e XML, mas a conversão entre plataformas está além do escopo deste artigo.
DOM não é uma ferramenta muito segura - especialmente ao usar a API DOM para criar árvores que não podem ser serializadas como XML. Nunca misture APIs sem namespace DOM1 e APIs com reconhecimento de namespace DOM2 (por exemplo, createElement e createElementNS) no mesmo programa. Se você usar namespaces, tente declarar todos os namespaces na posição do elemento raiz e não substitua o prefixo do namespace, caso contrário as coisas ficarão muito confusas. De modo geral, desde que você siga sua rotina, você não desencadeará situações críticas que possam lhe trazer problemas.
Se você estiver usando innerText e innerHTML do Internet Explorer para análise, você pode tentar usar a função elem(). Ao construir utilitários semelhantes, você obtém mais conveniência e herda os benefícios do código multiplataforma. Misturar esses dois métodos é muito ruim.
Alguns caracteres Unicode não estão incluídos no XML. A implementação do DOM permite adicioná-los, mas a consequência é que eles não podem ser serializados. Esses caracteres incluem a maioria dos caracteres de controle e caracteres individuais em pares substitutos Unicode. Isso só acontece se você tentar incluir dados binários no documento, mas essa é outra situação complicada.
Conclusão
Abordei muito sobre o que o DOM pode fazer, mas há muito mais que o DOM (e o JavaScript) podem fazer. Estude e explore esses exemplos para ver como eles podem ser usados para resolver problemas que podem exigir scripts de cliente, modelos ou APIs especializadas.
O DOM tem suas limitações e deficiências, mas também tem muitas vantagens: está integrado em muitos aplicativos, funciona da mesma maneira, quer você use a tecnologia Java, Python ou JavaScript, é muito fácil usar o SAX; que é simples e poderoso de usar. Um número crescente de aplicativos está começando a suportar DOM, incluindo aplicativos baseados em Mozilla, OpenOffice e XMetaL do Blast Radius. Cada vez mais especificações exigem o DOM e o estendem (por exemplo, SVG), para que o DOM esteja sempre ao seu redor. Seria sensato usar esta ferramenta amplamente implantada.