Tag: jquery

Curso de Bootstrap, jQuery e Web Standards na Fatec Sorocaba

Nos últimos dois sábados (11 e 18 de Abril de 2015) ministrei o curso Bootstrap, jQuery e Web Standards, uma breve introdução às tecnologias e boas práticas envolvidas no desenvolvimento do front end de páginas web, na Faculdade de Tecnologia de Sorocaba.

curso-bootstrap

Para este curso não preparei uma apostila ou apresentação teórica, mas uma série de exercícios práticos onde cada participante pôde construir sua própria página e seus próprios scripts, que vão desde eventos simples até manipulação do HTML e animações.

Ainda estou aprendendo a ministrar aulas e cursos. Há quem diga que para ensinar basta ter disposição. Concordo, mas estou aprendendo que existe uma grande diferença entre simplesmente transmitir conhecimento e ser um mestre, no sentido de realmente transmitir os princípios do ofício. Como sempre, apliquei uma pesquisa rápida e anônima para obter feedback. Consegui melhorar um pouco em relação a pesquisas anteriores. 😀

O guia de atividades junto com a resolução de todos os exercícios está disponível no meu Google Drive.

Fique à vontade para usar o material como desejar sob a mesma licença Creative Commons deste blog e de todos os meus materiais. 🙂

Jericho Selector, ou jQuery para Java

CSS3

É com grande satisfação que venho anunciar a publicação da primeira versão estável (stable release) da biblioteca Jericho Selector.

O Jericho Selector é uma extensão para a conhecida biblioteca de leitura e manipulação de HTML Jericho HTML Parser que permite selecionar elementos de um documento HTML ao estilo do jQuery, ou seja, utilizando seletores CSS.

Por quê Jericho? Diferente do jsoup, por exemplo, ele permite alterar documents HTML mantendo a formatação original. Outras bibliotecas reescrevem o documento inteiro. De qualquer forma, eu gosto de ter opções! 😉

Jericho Selector é totalmente livre, sob a licença MIT.

Como usar

O Jericho Selector está disponível no Repositório Central do Maven, então basta configurar o seu projeto com a seguinte dependência:

<dependency>
    <groupId>br.com.starcode.jerichoselector</groupId>
    <artifactId>jericho-selector</artifactId>
    <version>1.0.1-RELEASE</version>
</dependency>

Depois, faça a importação do método estático $ que pode ser usado como ponto de entrada para uso do Jericho Selector:

import static br.com.starcode.jerichoselector.jerQuery.$;

Então você poderá realizar consultas da seguinte forma:

$(html, "p.my-text")

O que foi feito

Antes de iniciar a implementação do Jericho Selector, tive que implementar um interpretador (parser) completo de seletores CSS. Para isso, implementei outra biblioteca que chamei de parCCSer. Ela foi baseada na especificação oficial do W3C para CSS3 e cobre praticamente todos os aspectos da especificação, exceto alguns detalhes que se aplicariam apenas no contexto de um navegador e também algo relacionado a suporte de caracteres UTF-8. Ela também está sob a licença MIT.

O Jericho Selector então utiliza a árvore de objetos gerada pelo parCCser, assim como a API do Jericho HTML Parser para consultar os elementos de um documento HTML com base em um dado seletor.

Todas as implementações possuem cobertura em testes unitários de mais de 90% do código, sem contar os casos excepcionais que o plugin de cobertura não consegue analisar.

O que precisa ser feito

Nas próximas semanas pretendo adicionar ao Jericho Selector funcionalidades de uma API fluente, análogas ao jQuery, para que seja possível realizar operações com os elementos recuperados usando lambdas e outros recursos. Métodos como closest, parentsUntil, find, each são meus primeiros alvos.

Outro ponto a melhorar é o desempenho da biblioteca. Alguns seletores específicos podem ter sua execução otimizada utilizando cache ou métodos específicos do Jericho HTML Parser como getAllElementsByClass.

O que você pode fazer

Reporte qualquer problema e sugira novas funcionalidades!

Código-fonte

Confira o código e maiores detalhes na minha página do GitHub:

https://github.com/utluiz/jericho-selector/

Entenda como Zebrar uma tabela com CSS

Colorir alternadamente as linhas de uma tabela é um requisito comum. A abordagem mais comum é usar um código personalizado na view para adicionar estilos alternados. No entanto, é possível fazer isso sem o uso de uma linguagem de back-end como Java, PHP ou .Net.

CSS 3

Podemos usar estilos CSS selecionando os elementos pares e ímpares, como no seguinte exemplo:

/* linhas pares (even) */
.tabela tbody tr:nth-child(even) {
    background-color: #CCC;
}
/* linhas ímpares (odd) */
.tabela tbody tr:nth-child(odd) {
    background-color: #FFF;
}

Continuando com o exemplo, agora só precisamos adicionar a classe tabela ao elemento <table> no HTML:

<table class="tabela">
    <thead>
        ....
    </thead>
    <tbody>
        ....
    </tbody>
</table>

Veja um exemplo funcional no Jsfiddle!

Que bruxaria é essa?

Para quem não conhece a sintaxe do CSS, ou nem sabe que tipo de tecnologia é essa, trata-se de um tipo de linguagem para aplicar primariamente estilos visuais nos elementos de uma página web.

Considere o seguinte exemplo:

seletor {
    atributo1: valor1;
    atributo2: valor2;
}

Esta é a estrutura de uma regra (rule) do CSS. Regras são compostas pelas seguintes partes:

  • Seletor: define quais elementos serão afetados pela regra.
  • Atributo: especifica qual atributo será afetado. Podem haver vários atributos por regra.
  • Valor: o respectivo valor de cada atributo.

Um seletor que começa com um ponto (.), como em .tabela, chama-se seletor de classes. Ele diz ao navegador para aplicar a regra aos elementos que possuem o atributo class com a respectiva classe, como em class="tabela". Um elemento pode conter várias classes, cujos nomes devem ser separados por espaços em branco, como em class="tabela outra-classe".

Voltemos agora ao exemplo do tópico anterior. O seletor.tabela tbody tr:nth-child(even) primeiramente seleciona elementos que contém o atributo class="tabela".

Em seguida, encontramos o trecho tbody. Este é um seletor de tag, isto é, ele seleciona as tags <tbody>, que define o corpo da tabela, de forma que não zebramos o título da mesma. Como isso vem depois de .tabela e é separado por um espaço em branco, incluiremos todos os elementos <tbody> filhos do elemento com class="tabela". O próximo trecho é tr, que seleciona todos os elementos <tr> filhos do elemento <tbody>. Note que, se houver uma tabela dentro de outra, as linhas da tabela mais interna também serão afetadas. Se quiséssemos especificar a seleção apenas dos filhos diretos, poderíamos usar o caractere maior (>), como em .tabela > tbody > tr.

O seletor tr é seguido de um caractere de dois pontos (:). Este é um pseudo-seletor. Ele altera o seletor anterior. Nesse caso o pseudo-seletor nth-child() permite especificar quais elementos do conjunto total de tags <tr> serão realmente incluídos. Dentro do parêntesis, poderíamos especificar um índice numérico. Por exemplo, tr:nth-child(5) iria selecionar apenas a quinta linha da tabela. Porém, usamos os valores especiais odd e even para definir índices ímpares e pares, respectivamente.

Finalmente, aplicamos a cor #CCC (um cinza claro) ao atributo background-color (cor de fundo) às linhas pares. Depois, aplicamos a cor #FFF (branco, em hexadecimal) como cor de fundo das linhas ímpares. Note que o valor #FFF é uma abreviação para #FFFFFF. Na versão com 6 letras, cada dupla de bytes representam uma cor do RGB (Red, Green, Blue).

Ufa! Entendeu? 😉

Se você lê Inglês e quer se aprofundar, recomendo a referência da fundação Mozilla.

Compatibilidade com navegadores antigos

A solução em CSS é muito legal, mas o seletor nth-child não vai funcionar no Internet Explorer 6, 7 e 8. Se precisar manter a compatibilidade com essas versões do navegador, uma alternativa é usar jQuery. O jQuery simula seletores mais novos mesmo em navegadores antigos através de código Javascript.

O seguinte trecho de código aplica a coloração em linhas ímpares e pares logo após o carregamento da página:

$(document).ready(function() {
    //linhas pares, iniciando em zero
    $('.tabela tbody tr:even').css('background-color', '#FFF'); 
    //linhas ímpares iniciando em zero
    $('.tabela tbody tr:odd').css('background-color', '#CCC'); 
});

Note que inverti odd e even. Isso é porque a versão CSS do seletor usa índices baseados em 1 (1, 2, 3, …, N), enquanto a versão jQuery usa índices de vetores Javascript, que são baseados em 0 (0, 1, 2, …, N – 1).

Veja o exemplo funcional da versão em Javascript no Jsfiddle!


Este artigo foi baseado na minha resposta no StackOverflow em Português!

HTML e Javascript: trocando dois elementos de posição (swap)

Você tem dois elementos quaisquer na estrutura do documento da sua página (DOM). Como invertê-los, isto é, colocar um no lugar do outro?

Abordagens para solução

O problema não é tão simples quanto pode parecer a princípio, pois ao movermos um dos elementos, perdemos sua posição original e não é tão fácil armazenar a localização de um elemento em Javascript.

Vamos analisar algumas abordagens nos tópicos abaixo.

Variável auxiliar

Podemos usar o princípio básico da troca de valores entre variáveis. Por exemplo, dadas as variáveis A e B, podemos trocar os valores usando uma variável auxiliar AUX:

AUX = A
A = B
B = AUX

Simples, não? Mas estamos aqui falando da posição de elementos na estrutura HTML. O que seria nossa variável auxiliar? A resposta é: um elemento criado dinamicamente.

Colocando isso numa solução reutilizável em forma de um plugin para jQuery:

(function ($) {
    $.fn.swap = function(anotherElement) {
        var a = $(this).get(0);
        var b = $(anotherElement).get(0);
        var swap = document.createElement('span');
        a.parentNode.insertBefore(swap, a);
        b.parentNode.insertBefore(a, b);
        swap.parentNode.insertBefore(b, swap);
        swap.remove();
    }
}(jQuery));

Demo no jsfiddle

A utilização é muito simples:

$(seletorElemento).swap(seletorOutroElemento);

Puff! Os elementos foram trocados.

Armazenando a posição do elemento

Outra abordagem, que não envolve a criação de um elemento adicional, é armazenar a posição de pelo menos um dos elementos. Na verdade, só precisamos armazenar a posição do elemento que será movido primeiro.

O problema está em como representar a posição de um elemento, pois a sua inserção será feita relativamente a algum outro. Se usarmos o anterior como referência, teremos problemas se ele for o primeiro. Se usarmos o posterior, teremos problemas se ele for o último. Pior, os dois elementos a serem trocados podem estar próximos um do outro, em sequência!

Não vou descrever minuciosamente as nuances do algoritmo que desenvolvi porque não creio ser interessante, mas considere a seguinte implementação:

(function ($) {

    $.fn.swap = function(anotherElement) {

        var sameParentStrategy = function(one, another) {
            var oneIndex = one.index();
            var anotherIndex = another.index();
            var swapFunction = function(first, second, firstIndex, secondIndex) {
                if (firstIndex == secondIndex - 1) {
                    first.insertAfter(second);
                } else {
                    var secondPrevious = second.prev();
                    second.insertAfter(first);
                    first.insertAfter(secondPrevious);
                }
            }
            if (oneIndex < anotherIndex) {
                swapFunction(one, another, oneIndex, anotherIndex);
            } else {
                swapFunction(another, one, anotherIndex, oneIndex);
            }
        };

        var differentParentsStrategy = function(one, another) {
            var positionStrategy = function(e) {
                var previous = e.prev();
                var next = e.next();
                var parent = e.parent();
                if (previous.length > 0) {
                    return function(e) {
                        e.insertAfter(previous);
                    };
                } else if (next.length > 0) {
                    return function(e) {
                        e.insertBefore(next);
                    };
                } else {
                    return function(e) {
                        parent.append(e);
                    };
                }
            }
            var oneStrategy = positionStrategy(one);
            var anotherStrategy = positionStrategy(another);
            oneStrategy(another);
            anotherStrategy(one);
            return this;
        };

        //check better strategy
        var one = $(this);
        var another = $(anotherElement);
        if (one.parent().get(0) == another.parent().get(0)) {
            sameParentStrategy(one, another);
        } else {
            differentParentsStrategy(one, another);
        }

    };

}(jQuery));

Note as diferentes estratégias de troca, dependendo dos elementos serem ou não filhos de um mesmo “pai”.

Demo no jsfiddle.

Clonagem dos elementos

Uma terceira alternativa, um tanto simplista na verdade, é clonar os dois elementos e substituir os originais um pelo outro. Vejamos o código:

(function ($) {
    $.fn.swap = function(anotherElement) {
        // cache elements
        var $div1 = $(this),
            $div2 = $(anotherElement);
        // clone elements and their contents
        var $div1Clone = $div1.clone(),
            $div2Clone = $div2.clone();
        // switch places
        $div1.replaceWith($div2Clone);
        $div2.replaceWith($div1Clone);
    }
}(jQuery));

Análise das soluções

Cada abordagem parece ter seu ponto fraco. Criar um elemento auxiliar deixa qualquer um com um pé atrás por causa da criação e inserção de um elemento “inútil” ao DOM. Porém, a complexidade do algoritmo que calcula a posição dos elementos e as várias funções usadas da API do jQuery chega a dar calafrios na espinha. E quanto a clonar os elementos? Isso não pode ser algo do bem!

Note que a primeira abordagem, por ser simples e direta, pode facilmente ser implementada sem usar a API do jQuery. Usei propositalmente a API nativa do Javascript para gerar um ganho de desempenho, pois, como veremos a seguir, isso faz muita diferença. Já as demais abordagens exigiriam alterações nada triviais para depender apenas da API nativa.

Ao invés conjecturar, vamos logo ao teste de desempenho. Preparei oito cenários com os algoritmos de troca descritos acima. Para cada algoritmo, o desempenho para troca de nós que estão no mesmo nível e o desempenho para troca de nós que estão em locais diferentes do HTML foram medidos separadamente. Além disso, o algoritmo que usa a “variável auxiliar” foi desmembrado em dois: o primeiro usando a API do jQuery e o segundo, como descrito no respectivo tópico, usando a API nativa.

Considere o seguinte gráfico:

Desempenho de algoritmos de swap

A legenda para relacionar cor e algoritmo:

  • Azul escuro: nós no mesmo nível armazenando a posição do elemento.
  • Vermelho: nós em níveis diferentes armazenando a posição do elemento.
  • Amarelo: nós no mesmo nível com variável auxiliar e a API do jQuery.
  • Verde escuro: nós em níveis diferentes com variável auxiliar e a API do jQuery.
  • Roxo: nós no mesmo nível com variável auxiliar e API nativa, sem jQuery.
  • Azul claro: nós em níveis diferentes com variável auxiliar e API nativa, sem jQuery.
  • Pink: nós no mesmo nível usando clonagem.
  • Verde claro: nós de níveis diferentes usando clonagem.

Teste no jsperf

Conclusões

Os resultados confirmaram algumas das suspeitas:

  • A clonagem é muito lenta.
  • Usando a API do jQuery, armazenar o local da variável é mais rápido do que criar e inserir um elemento auxiliar.
  • Porém, usar a API nativa do Javascript gerou um ganho em torno de 10x no desempenho. Mesmo com a alteração no DOM gerada pera inserção da variável auxiliar, o desempenho desta solução superou todas as demais.

Não pretendo aqui tirar nenhum mérito de uma excelente biblioteca como o jQuery, pois em muitos casos ela nos permite obter um desempenho muito superior a uma “solução caseira”. Porém, não devemos tomar qualquer biblioteca por uma solução definitiva para todos os problemas.

Muitos cenários exigirão código específico e usar uma API nativa, de modo a remover camadas de abstração, muitas vezes é o melhor caminho.

Nós, desenvolvedores, devemos ser flexíveis o suficiente para trabalhar em diferentes níveis de abstração. Devemos saber quando “baixar” o nível… 😉

Este artigo foi baseado na minha resposta no StackOverflow em Português e nas respostas de outros excelentes desenvolvedores como Marcelo Gibson e Zuul!

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.