Lista dinâmica com DOM

10/01, 2006 17:53 por Fellipe

Usar Javascript para manipular DOM torna-se uma tarefa muito interessante após um tempinho de experiência. Para continuar com o laboratório de DOM, fiz um pequeno script que lê o conteúdo de um documento HTML (ou XML) e identifica seu título principal (H1) e subtítulos (H2) de modo que possamos criar uma lista dinâmica (via JS) de links para os subtítulos abaixo do título principal e links para o título principal abaixo de todos os subtítulos.

Para visualizar o script em ação, veja o exemplo aqui.

Observe o exemplo e em seguida abra seu código fonte. Dentro do seção <body> não existe lista (<ul>) ou âncora (<a>) alguma, tudo é construído dinamicamente através da função que chamei de dynamicHeaderList(), localizado no topo do código.

Esta idéia, em particular, começou a partir de um objetivo incomum: fazer uma versão de brincadeira para este blog aparecer em uma tela de celular. No meio da idéia, resolvi dissecar um artigo que fiz tempos atrás sobre blockquote chamado DOM na prática e aproveitar algumas funcionabilidades que aquele script tinha.

O código

Primeiro, a função principal, chamada dynamicHeaderList(). Esta função tem como propósito ler o documento por inteiro, gerar array dos cabeçalhos e posicionar os itens criados…

dynamicHeaderList()

[code lang=”javascript”]
function dynamicHeaderList() {
if (!document.getElementsByTagName || !document.createElement || !document.appendChild) return;
var father = document.getElementsByTagName(”h1″);
father[0].setAttribute(”id”, geraHeaderId(father[0].firstChild.nodeValue));
var headers = document.getElementsByTagName(”h2″);

var buildList = document.createElement(”ul”);

for (var i=0; i < headers.length; i++) {
headers[i].setAttribute("id", geraHeaderId(headers[i].firstChild.nodeValue));

var listItem = document.createElement("li");
var listLink = document.createElement("a");
listLink.setAttribute("href", "#" + headers[i].getAttribute("id"));
listLink.appendChild(document.createTextNode(headers[i].firstChild.nodeValue));

listItem.appendChild(listLink);
buildList.appendChild(listItem);

var link = document.createElement("a");
link.setAttribute("title","Go to TOP");
link.setAttribute("href","#"+father[0].getAttribute("id"));
link.appendChild(document.createTextNode("Top"));
var para = document.createElement("p");
para.appendChild(link);
para.className = "anchor";

headers[i].parentNode.insertBefore(para, headers[i].nextSibling);
}

father[0].parentNode.insertBefore(buildList, father[0].nextSibling);
}
[/code]

  1. A primeira coisa a fazer é capturar o primeiro e mais importante nó para o trabalho sujo, ou seja, o título principal, que é marcado sempre pela primeira (e única) tag H1.
    father = document.getElementsByTagName("h1"); e como ele é virgem de identificação, ou seja, não tem um id, precisamos usar o método getElementsByTagName e presumir que ele seja o primeiro item do array de elementos H1, denominado posteriormente por father[0].
  2. Para resolver o impasse da falta de um id próprio, criei outra pequena função chamada geraHeaderId(), ainda tosca, que gera um id a partir do próprio conteúdo escrito no título principal. Em uma tacada só, gero o tal id e associo como atributo do nosso título principal, através do método setAttribute.
    father[0].setAttribute("id", geraHeaderId(father[0].firstChild.nodeValue));
  3. Repito a operação de captura, mas agora com os subtítulos.
    var headers = document.getElementsByTagName("h2"); Tenho outro objetivo para eles…
  4. E então crio meu primeiro elemento no DOM e armazeno na variável buildList
    var buildList = document.createElement("ul"); mas ainda não faço nada com ele.
  5. E como era de se esperar, precisamos criar um loop para navegar na coleção de H2 gerada em passo anterior.
    for (var i=0; i < headers.length; i++)
  6. Agora, dentro do loop, gero um novo id para cada H2 diferente, enquanto vou navegando na coleção.
    headers[i].setAttribute("id", geraHeaderId(headers[i].firstChild.nodeValue));
  7. Dentro do loop, crio mais dois objetos,
    var listItem = document.createElement("li");
    var listLink = document.createElement("a");
    um li (ou list item) e um a (ou anchor), nessa seqüência.
  8. Na âncora que criei dentro da variável listItem adiciono o atributo href e associo o valor referente ao id gerado para o mesmo headers[i] na primeira linha deste loop, criando assim a primeira âncora dinâmica.
    listLink.setAttribute("href", "#" + headers[i].getAttribute("id"));
  9. Aqui existe mais uma mágica, em createTextNode é associado automaticamente um texto com o valor do H2 em questão (headers[i]) ao listLink.
    listLink.appendChild(document.createTextNode(headers[i].firstChild.nodeValue));
  10. E finalmente, o listLink é colocado como filho de listItem, e listItem como filho de buildList.
    listItem.appendChild(listLink);
    buildList.appendChild(listItem);
    Tudo isso serve para formar a nossa lista de links (ul) conforme o loop for identificando e navegando por novos subtítulos (H2).
  11. E para dar uma simplificada na explicação, um combo de instruções. Abaixo, criamos na primeira linha um objeto do tipo âncora e a armazenamos na variável link. Adicionamos o atributo title com o valor Go to TOP no objeto link. Adicionamos outro atributo, o href, cujo valor é o id do título principal. Criamos novamente uma ramificação de texto dentro do nosso link, deixando escrito top para o usuário. Criamos outro elemento, desta vez um parágrafo, e falamos que o nosso objeto link é filho do nosso parágrafo chamado para. Por fim, falamos que o objeto para pertence a classe (de CSS) chamada anchor.
    var link = document.createElement("a");
    link.setAttribute("title","Go to TOP");
    link.setAttribute("href","#"+father[0].getAttribute("id"));
    link.appendChild(document.createTextNode("Top"));
    var para = document.createElement("p");
    para.appendChild(link);
    para.className = "anchor";
  12. Como último passo do loop e penúltimo do script, inserimos o objeto-parágrafo (para) que contém o link para o topo logo depois de cada subtítulo da página em questão.
    headers[i].parentNode.insertBefore(para, headers[i].nextSibling); E repetimos o loop, caso a condição ainda seja verdadeira, ou seja, caso ainda existam H2 na página.
  13. Caso os subtítulos (H2) tenham acabado, então o loop termina e finalmente podemos chegar ao último passo:
    father[0].parentNode.insertBefore(buildList, father[0].nextSibling); Inserimos o buildList, agora completo, logo abaixo do título principal (H1). Veja a finalização aqui e observe o fonte dentro do body.
  14. geraHeaderId()

    function geraHeaderId(headerNode){
        var headerId = headerNode.split(" ");
        var temp="";
        for(var j=0; j<headerId.length; j++) {
            temp += (headerId[j].substring(0,1));
        }
        headerId = temp.toLowerCase();
        return headerId;
    }

    O geraHeaderId() recebe um parâmetro, o headerNode. Este parâmetro deve ser uma string nua e crua, pois essa função divide esta string em vários pedaços (delimitados por seus espaços em branco) e faz junção de suas iniciais… mais ou menos para transformar isso: Título de alguma notícia, nisso: tdan. Útil para gerar ids sempre que necessário.

    O que aprendi com isso tudo

    • getElementsByTagName: este método, quando usado, sempre retorna um array de objetos de determinado tipo. Tive que usar em duas situações distintas, uma quando precisei recuperar um objeto que não tinha id porém sempre era o primeiro da lista, e duas quando tive que navegar por todos h2 da página.
    • createElement: usei diversas vezes para criar nós dinâmicos no HTML.
    • appendChild: usei também muitas vezes para ordenar uma estrutura hierárquica entre todos elementos que já havia criado.
    • setAttribute e getAttribute: o primeiro associa um atributo a algum elemento enquanto o segundo captura o atributo, caso ele exista. Esses métodos são cruciais para o case de estudo acima.
    • insertBefore: Apesar de um pouco confuso ainda, este elemento permitiu que eu colocasse um nó após outro já existente, e não dentro como o appendChild sugere.
    • nextSibling: Graças a esta propriedade eu pude usar o método insertBefore. O nextSibling indica o próximo elemento após o elemento em questão.

    Fontes de estudo

    Apesar deste artigo estar grande e até bastante confuso para quem não sabe do que se trata, espero que possa ajudar bastante quem está se iniciando no mundo de DOM.

    Para estudar DOM recomendo muitas visitas ao Google, DevGuru e W3 Schools. Lá, além de um índice completo, existem exemplos muito bons. Não posso esquecer de citar a fonte oficial, o DOM no W3C. Apesar de achar a fonte oficial um paraíso, ele ainda parece muito restrito aos que estão extremamente ligados com os padrões promovidos por eles próprios, e é justamente por isso que eu julgo necessário a existência de blogs que façam a digestão daquele conteúdo! Salvem os techblogs.

    Este artigo foi publicado Tuesday, 10 de January de 2006 às 17:53 e foi categorizado como Cookbook, DOM Lab. Você pode acompanhar os comentários deste post assinando o comment RSS (RSS 2.0 [?]). Você também pode comentar ou atrelar um trackback [?] daqui no seu site.

5 comentários para “Lista dinâmica com DOM”

  1. Renan falou assim:

    Muito bom, cara
    parabéns

  2. Bruno Dulcetti falou assim:

    daki a pouco o html vai ficar praticamente nulo… hehehehehe… estaria o html entrando em extinção? nem tanto… :D muito bom cara, parabéns… ;)

  3. CosmeWeb falou assim:

    Tá ficando bom hein cara. :)

    O semantic-blog vai ficar show com tuas incríveis técnicas com client-side. ;)

  4. Cris Zimermann falou assim:

    Ufa, cheguei!

    E, preciso admitir q vc está de mesmo de parabéns!

    Passa lá no OPORTUNIDADE DE NEGÓCIOS BRASIL, vc ñ vai precisar de tanta carona :)

    []s

  5. Renata falou assim:

    Muitissímo bem escrito e explicado até msm para quem não teve contato com DOM. Parabéns! ;)
    Bjin

Deixe seu parecer sobre o artigo

XHTML: Você pode usar: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

contract expand