Tableless - Desenvolvimento inteligente com Padrões Web

14/09/2010
Artigos

Anatomia de um plugin jQuery

jQuery é um framework JavaScript, o mais sexy do pedaço, que transformou essa linguagem em uma das mais importantes ferramentas presentes no set de um webdesigner ou um desenvolvedor frontend. O que antes era chato e complicado, passou a ser extremamente dinâmico e elegante.

Por


Um dos grandes trunfos do jQuery é a sua vasta gama de plugins. Praticamente qualquer efeito, ação ou manipulação que você consiga imaginar já deve possuir um plugin. Caso contrário, quem sabe você mesmo não desenvolve um?

Neste artigo, você confere técnicas de como criar o seu próprio plugin. Vamos ver dois exemplos. O primeiro plugin adiciona classes ao primeiro e último ítem de uma lista, tabela, div etc. e o segundo simula a busca por textos do navegador Safari (aquele highlight bacana, como um marcador de textos, no termo procurado).

Vale lembrar ainda a importáncia de conhecer pelo menos um pouco de JavaScript antes de começar a trabalhar a fundo com jQuery.

O que é e como funciona um plugin jQuery?

Existem dois tipos de plugins: as funções e os métodos públicos. A função é anexada diretamente ao objeto jQuery e fica sem acesso a qualquer atributo/elemento encadeado.

1
2
3
4
5
6
7
     jQuery.titulo = function(campeonato) {
         if($('div#flamengo').html()) {
             $('div#flamengo').append(campeonato);
         } else {
             alert(campeonato);
         }
     };

A função criada acima recebe um parâmetro campeonato e busca a existência de uma div com o id “flamengo” (o método .html() retorna null quando o elemento não está presente, logo é uma ótima maneira de validar sua existência). Se encontrar a div, ele adiciona o parâmetro passado ao seu conteúdo. Caso não encontre, nossa função utiliza o bom e velho alert().

Para ser executada, a função titulo precisaria ser chamada assim:

1
    $.titulo('Mundial 81');

O tipo de plugin acima deve ser desenvolvido apenas quando o mesmo for um utilitário, ou seja, que não necessite herdar propriedades do seletor. Um bom exemplo é o método isArray da API do jQuery.

Já plugins desenvolvidos como método público herdam toda a cadeia de propriedades e elementos disponíveis. Isso porque ele é chamado diretamente em um seletor jQuery. Dentro desse método, o termo this representa o objeto jQuery selecionado. É dessa forma que, provavelmente, você vai desenvolver a maioria dos seus plugins e é assim também que vamos desenvolver nossos plugins nesse tutorial.

Como ficaria nossa função titulo transformada em um método público do jQuery?

1
2
3
4
     jQuery.fn.titulo = function(campeonato){
        this.append(campeonato);
        return this;
     };

A nova versão do plugin seria executada da seguinte maneira:

1
     $('#flamengo').titulo('Libertadores 2011');

Muito parecido com os plugins de jQuery que você já implementou em seus sites, certo? Note que utilizamos o nome jQuery por extenso. Poderíamos ter utilizado seu apelido: $.fn.titulo = function([...]. No entanto, quando você precisar do jQuery no modo no conflict (com outros frameworks), é importante utilizar o nome por extenso para o plugin funcionar corretamente. Outra observação importante é nunca esquecer o ponto e vírgula ao final da função, senão o plugin não funciona quando o código for compactado. E, por último, é interessante que seu plugin sempre retorne o objeto jQuery (this), possibilitando assim o encadeamento com outros métodos.

O problema em utilizar a palavra jQuery e não o apelido ($) é que você precisaria utilizar sempre a palavra dentro do seu método para referenciar o framework. Para resolver isso, vamos utilizar uma função anônima, recurso muito legal do JavaScript, encapsulando nosso plugin. A função anônima recebe como parâmetro o objeto jQuery. Dentro dela, uma outra função engloba o plugin “convertendo” o objeto para seu apelido normal. O cifrão ($), nesse caso, pode ser qualquer apelido que você queira trabalhar dentro do seu plugin.

1
2
3
4
5
6
7
8
     (function($) {
        // $ é o jQuery no escopo do plugin
        // Poderia ser qualquer apelido
        $.fn.titulo= function(campeonato){
            this.append(campeonato);
            return this;
         };
     })(jQuery);

Acima temos um modelo quase ideal para nossos plugins (ainda faltam as opções, veremos mais adiante).

Agora que vimos a estrutura básica, vamos estabelecer algumas metas para nossos plugins:

  • Reutilizável: o principal objetivo de um plugin é que ele venha a ser utilizado por mais de um projeto, mais de uma pessoa. Não adianta desenvolver um plugin muito específico;
  • Leve & rápido: tamanho é sempre documento no caso de JavaScript e arquivos web em geral. Aqui quanto menor, melhor. O plugin também deve ser otimizado para trabalhar com 1 ou 100 elementos;
  • Flexível: fácil de adaptar, com diversas opções. Tenha isso em mente quando for desenvolver um plugin. Evite utilizar IDs de elementos, nomes, classes e cores fixas.

Meu primeiro plugin: jquery.fila()

Não existe melhor maneira para aprender do que na prática, vamos então desenvolver nosso primeiro plugin. Para todo elemento que possua “filhos”, por exemplo uma lista ou uma tabela, o plugin vai adicionar uma classe ao primeiro e ao último ítem. Nosso plugin vai receber como opções os nomes das classes a serem adicionadas.

Vamos chamar nosso plugin de fila (FIrst-LAst). Ele terá um conjunto de opções básicas e, utilizando a função jQuery .extend, sobreescreverá o conjunto com opções passadas na chamada do plugin. As classes ‘first’ e ‘last’ serão adicionadas caso o plugin seja executado sem nenhum parâmetro.

1
2
3
4
5
6
7
8
9
10
11
12
13
(function($){
    $.fn.fila = function(opcoes) {

     // opções padrão
     var defaults = {
         first: 'first',
         last: 'last'
     };

     opcoes = $.extend(defaults, opcoes);

    };
})(jQuery);

Agora só precisamos varrer o seletor em busca de elementos que possuam “filhos”. Quando existir uma classe definida nas opções, o plugin adicionará a mesma aos elementos encontrados. Note que estamos utilizando os seletores ‘:first-child’ e ‘:last-child’ para buscar o primeiro e o último filho e adicionar as classes especificadas nas opções.

1
2
3
4
     return this.each(function(){
         if(opcoes.first) $(this).find(':first-child').addClass(opcoes.first);
         if(opcoes.last) $(this).find(':last-child').addClass(opcoes.last);
     });
jQuery.fila.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(function($){
    $.fn.fila = function(opcoes) {

     // opções padrão
     var defaults = {
         first: 'first',
         last: 'last'
     };

     opcoes = $.extend(defaults, opcoes);

     return this.each(function(){
         if(opcoes.first) $(this).find(':first-child').addClass(opcoes.first);
         if(opcoes.last) $(this).find(':last-child').addClass(opcoes.last);
     });

    };
})(jQuery);

Exemplos de uso do plugin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
     // define ambas as classes
     $('ul').fila({
        first: 'primeiro',
        last: 'ultimo'
     });

     // opções padrão
     $('#resultado tbody').fila();

     // marca apenas o último filho
     $('div').fila({
        first: null,
        last: 'omega'
     });

Marcando textos com estilo

Nosso segundo plugin de exemplo vai fazer o seguinte: pesquisar por um termo em um texto e destacar os resultados. Começamos com um pouco de CSS, que vai servir para a nossa marcação:

1
2
3
4
5
6
7
8
     span.marcaTexto {
        position: relative;
        z-index: 180!important;
        -moz-box-shadow: 0 0 1em #889;
        -webkit-box-shadow: 0 0 1em #888;
        box-shadow: 0 0 1em #889;
        padding: 4px;
    }

A ideia é adicionar o texto ao span marcaTexto, que também será o nome do plugin, mas você poderá utilizar a classe que quiser (essa opção estará presente na configuração). Uma das diferenças desse plugin em relação ao primeiro exemplo é que vamos aceitar tanto uma palavra como um conjunto de opções, dessa forma a chamada pode ser feita só com o termo a ser pesquisado ou com todas as opções disponíveis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     // opções padrão
     var defaults = {
         termo: '',
         corTexto: '#000',
         corFundo: '#ffff00',
         classe: 'marcaTexto'
     };

     // se o parâmetro passado for um array, carrega as opções
     if( $.isArray( opcoes ) )
     {
         opcoes = $.extend(defaults, opcoes);
     }
     // caso seja string, o usuário passou apenas o termo a ser marcado
     else
     {
         defaults.termo = opcoes;
         opcoes = defaults;
     }

Utilzamos a função jQuery.isArray para identificar se o parâmetro passado é um array ou uma string e assim definimos como tratar as opções. Além do termo a ser pesquisado, o plugin também pode receber as cores do texto e do fundo do span, além da classe CSS que define a estrutura básica da marcação.

Uma das dificuldades deste plugin é tratar tags HTML. Pra não complicar muito o exemplo, vamos simplesmente remover qualquer tag presente no texto utilizando a função text(). Para a pesquisa, um pouquinho de expressões regulares – precisamos verificar se o termo a ser pesquisado existe no seletor. Caso exista, o plugin entra em ação e marca o texto com o span e suas opções definidas anteriormente.

jquery.marcaTexto.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
(function($){
    $.fn.marcaTexto = function(opcoes) {
     // opções padrão
     var defaults = {
         termo: '',
         corTexto: '#000',
         corFundo: '#ffff00',
         classe: 'marcaTexto'
     };

     // se o parâmetro passado for um array, carrega as opções
     if( $.isArray( opcoes ) )
     {
         opcoes = $.extend(defaults, opcoes);
     }
     // caso seja string, o usuário passou apenas o termo a ser marcado
     else
     {
         defaults.termo = opcoes;
         opcoes = defaults;
     }

     // varre o seletor passado
     return this.each(function(){
         // armazena o texto do elemento
         var content = $(this).text();
         // pesquisa por termo no texto
         var re = new RegExp( opcoes.termo, 'i' );
         var result = content.match( re );
         // caso tenha encontrado ocorrências do texto...
         if( result )
         {
             // busca novamente, só que agora adicionando a marcação ao(s) termo(s)
             re = new RegExp( opcoes.termo, 'gi' );
             content = content.replace( re, '<span class="' + opcoes.classe + '" style="color: ' + opcoes.cortexto + ';background-color: ' + opcoes.corfundo + '">' + opcoes.termo + '</span>');
             // altera o HTML do elemento original
             $(this).html( content );
         }
     });

    };
})(jQuery);

Esse plugin tem diversas “falhas” que vão ficar de dever de casa. Por exemplo, atualmente ele ignora maiúsculas e minúsculas. Poderíamos ter uma opção definindo que tipo de busca utilizar, case sensitive ou insenstive. Outra falha é que ele busca qualquer elemento, mesmo um que não tenha conteúdo. Para esses casos, como um table, ele deveria buscar o conteúdo em seus filhos. E, conforme mencionado anteriormente, é necessário fazer com que o plugin preserve as tags HTML encontradas no texto.

Compartilhe seus plugins!

No início pode ser um pouco difícil saber identificar o que é transformado em um plugin e o que continua como um simples trecho de código. O que eu costumo fazer, até para praticar, é olhar elementos em interfaces de aplicativos: filtros, pesquisas, animações, botões. Aplicativos desktop são uma ótima inspiração para o desenvolvimento de plugins jQuery.

Espero que esse tutorial tenha explicado bem a arte de desenvolver plugins para jQuery e espero ainda mais que vocês possam criar e compartilhar novos plugins, a comunidade web brasileira precisa disso.

E, não esqueça: se você já tiver desenvolvido um plugin, deixe o link nos comentários.

Por Davi Ferreira

Programador apaixonado pelo que faz. Responsável pelo desenvolvimento de sistemas e interfaces para Globo.com, Confederação Brasileira de Vôlei, Ricoh Brasil, Embratel entre outros.

http://www.daviferreira.com/blog

Veja os outros posts de

  • http://twitter.com/charlesrockbass Charles

    Nossa, que post útil!

    Finalmente consegui entender o que era aquele (function($))…(jQuery) dos plugins!

    Agora me sinto mais confiante pra generalizar as tarefas JS por aqui!

    Muito obrigado!

  • http://www.marcelaraujo.com.br Marcel Araujo

    Muito bom brother

    Parabéns

  • http://duodra.co Duodraco

    Artigo muito útil para quem está começando a estender a jQuery.

    Só um comentário a fazer:
    a função .html(),citada no começo do artigo, retorna uma string vazia (“”) se o elemento existir, mas estiver vazio. Ao testar “” como booleano(if) ele retorna como false(assim como o null), não sendo uma boa maneira para testar se o elemento existe.

    O melhor seria usar a propriedade length, retornando o numero de elementos selecionados· Se de fato div#flamengo não existir, isso retornará 0, que é falso.

  • http://www.daviferreira.com Davi Ferreira

    Fala, Duo!

    Tem razão, cara, vacilo meu. Me expressei mal :)

    Um != null resolve lá, será?

    Valeu!

  • http://duodra.co Duodraco

    Opa Davi.
    Resolver, resolve, mas ainda não é a melhor opção.

    Seriam 2 alternativas:
    if($(‘div#flamengo’).html() != null) { …
    ou
    if($(‘div#flamengo’).length) {…

    Mas ainda prefiro a segunda, pois é exatamente o que queremos testar (a existência, ou não, do elemento) e é menos custosa: length é um atributo e já terá nossa resposta enquanto que o html() é um método, que realizará uma série de outras chamadas a métodos da jQuery até retornar nosso resultado.

  • http://www.ederprado.com Eder Prado

    Muito bom o texto, está de parabéns mesmo.

    Fica uma dica aqui como referência para quem quer sempre manter o JQuery atualizado e padronizado.

    http://jqueryui.com/

    Esse site é dos próprios desenvolvedores do framework, depois que comecei a usá-lo não consegui parar.

    #ficadica

    Abraço!

  • Luiz Carlos

    Nossa, muito… mas muito legal mesmo esse POST.

    Eu também não entendia o que era, como você chamou de, “Métodos Anônimos”, quando os vias em alguns plugins, e nem que poderia fazer uma expressão regular, como uma instância de em um objeto, e muito menos, entendia da anatomia de um plugin jquery.

    Meus parabéns, e muito obrigado.

    A paz do Senhor.

  • Bernardo

    Exelente artigo

    parabéns

    http://twitter.com/jquerybrasil/

  • http://tutorial-city.net/ Tutorial City

    Outro grande artigo sobre este mesmo assunto: http://www.learningjquery.com/2007/10/a-plugin-development-pattern

  • Pingback: Links – Pego no Twitter « Welguri's Blog

  • Paulo Rafael

    Parabéns pelo post, tenho certeza que esclareceu as dúvidas de muita gente!

  • Marcius

    Muito bom o artigo, parabéns e obrigado por está disseminando tanta informação válida.

  • Pingback: Templates e jQuery – parte 2 | Boas práticas de Desenvolvimento com Padrões Web

  • http://www.brimoveisindustriais.com Ben

    Muito bom o artigo. Finalmente consegui começar entender toda essa “mágica” que rola por tras do Javascript.

  • Pingback: Habilitando tags em fotos com jQuery | Tableless - Desenvolvimento com Padrões Web

  • Pingback: Anatomia de um plugin jQuery « Theodozio

  • http://giovaniarduini.com/jquery/ Giovani Arduin

    Segui seu tutorial, e fiz um puglin de galeria de imagem, pois no meu serviço, sempre temos que fazer uma e pensei pq não fazer uma que contenha varios exemplos e se quiser mudar alguma coisa é só, mudar o css:

    http://giovaniarduini.com/jquery/

    Só detectei 2 incoveniente:
    1º se eu colocar o mesmo efeito com legenda em duas ou mais galeria, dai da erro nas legendas.
    2º No ie 7 quando não tem nada escrito na tag alt das legendas, ele não esconde a div como deveria.

    Obrigado !

  • Pedro Felipe

    Olá Davi! Primeiramente, excelente post! Parabens meesmo!!

    Mas estou com uma pequena duvida:
    A linha ” defaults.termo = opcoes; ” é realmente necessaria??

    Realizei alguns testes copiando exatamente igual e não tinha funcionado. So depois que tirei esta linha que o script rodou certim!

  • Pedro Felipe

    So mais uma coisa, Davi:
    no replace as opções estao com letras minusculas e no “defaults” ela esta com uma letra em maiuscula.

  • http://www.daviferreira.com/blog Davi Ferreira

    Fala, Pedro!
    Não ententi muito bem seu segundo comentário. Sobre o primeiro, é necessário somente quando você passa as opções como string e não como array. Manda aí um exemplo pra eu ver o erro direitinho. Valeu! :)

  • http://pluginjquery.com.br/ Plugins Jquery

    Muito boa sua lista ja conhecia alguns inclusive tenho post no meu blog de alguns deles, irei aproveitar e fazer posts dos que acho que vale a pena de como implementa-los. abrass

  • Pingback: jQuery: dicas de otimização e performance | WebPatterns - Seu site nos padrões WEB