03/04
2010

Funções em JavaScript podem ser passadas como argumentos de outras funções. Esta prática é inclusive muito explorada pelos programadores. Se você ainda não esta familiarizado com o uso de funções, leia este post primeiro. Vamos então aos exemplos.

Funções como argumentos

1
2
3
4
5
6
7
8
9
10
function imprimir(fn) {
    var texto = fn();
    alert(texto);
}
 
function gerador() {
    return 'Sou um texto!';
}
 
imprimir(gerador);

Neste primeiro exemplo, declaramos uma função chamada “imprimir” que espera como argumento uma outra função. Ela executa esta outra função, pega o seu retorno e passa ele como argumento de “alert”.

É importante perceber que na linha 10 “gerador” é passado como argumento sem os parênteses “()” no final. Ou seja, ela não está sendo invocada, somente passada. A execução da função ocorre na linha 2. Uma variação do nosso exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
function imprimir(fn) {
    var texto = fn('por');
    alert(texto);
}
 
function gerador(lingua) {
    if (lingua === 'por') {
        return 'Sou um texto!';
    }
    return 'I´m a text!';
}
 
imprimir(gerador);

Agora “gerador” espera um parâmetro “lingua”. Este parâmetro é passado por “imprimir” no ato da execução, linha 2.

Funções anônimas

O nome pode parecer estranho mas o conceito é bem simples: funções anônimas são funções que não tem nome:

1
2
3
4
5
6
7
8
9
10
11
function imprimir(fn) {
    var texto = fn('por');
    alert(texto);
}
 
imprimir(function(lingua) {
    if (lingua === 'por') {
        return 'Sou um texto!';
    }
    return 'I´m a text!';
});

“Imprimir” continua sendo exatamente igual. Aguarda uma função como parâmetro. A diferença é que, ao invocarmos “imprimir”, declaramos uma função anônima ao mesmo tempo. Como ela não tem nome, não existe a possibilidade dela ser executada por uma outra função ou em outro momento do código. Somente “imprimir” a contém e pode executá-la.

E pra quê serve isso?

Funções como argumentos e funções anônimas são amplamente utilizadas como “callbacks”. Se você atrela um evento de click a um botão, algo deve acontecer quando este botão for clicado. Este “algo” é uma função. Ou então uma requisição Ajax. Vamos supor que temos uma requisição que busca se um determinado email já foi ou não cadastrado em um banco de dados. Quando a requisição finalmente termina, uma resposta é enviada ao nosso programa. Esta resposta é normalmente passada como argumento de uma função “callback”.

12/03
2010

Criei um plugin jQuery para criar selects no DOM a partir de um vetor de dados. O código do plugin abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function($){
	$.fn.hhSelectMaker = function(data, cfg) {
 
		var cfg = cfg || {};
		if ( !cfg.key) cfg.key = 'key';
		if ( !cfg.value) cfg.value = 'value';
		if ( !cfg.selected) cfg.selected = false;
 
		var snippet = '';
		for (var i = 0; i < data.length; i++) {
 
			snippet += "<option value='" + data[i][cfg.key]; 
 
			if (cfg.selected === data[i][cfg.key]) {
				snippet += " selected='selected' ";
			}
 
			snippet += "'>" + data[i][cfg.value] + '</option>';
		}
		return this.append(snippet);
	};
})(jQuery);

Como funciona?

Montei dois exemplos para ilustrar o funcionamento do plugin.
O plugin recebe dois argumentos. O primeiro, obrigatório, é a fonte de dados. O segundo, opcional, é a configuração da fonte de dados.

Caso somente o primeiro argumento seja passado, o plugin espera um estrutura de dados no seguinte formato:

var dados = [
    { key: 'Chave1', value: 'Valor1' },
    { key: 'Chave2', value: 'Valor2' },
    { key: 'Chave3', value: 'Valor3' }
    // etc...
];

O nome das chaves pode ser modificado utilizando-se o segundo argumento:

$('select').hhSelectMaker(dados, {
	key: 'id', value: 'nome', selected: '5'				
});

Configurando-se o plugin desta maneira a estrutura de dados esperada é a seguinte:

var dados = [
    { id: 'Chave1', nome: 'Valor1' },
    { id: 'Chave2', nome: 'Valor2' },
    { id: 'Chave3', nome: 'Valor3' }
    // etc...
];

Além disso, a chave selected permite que um determinado valor seja selecionado como padrão após o carregamento do plugin.

24/02
2010

Objetos: o básico

“No JavaScript, matrizes são objetos, funções são objetos, expressões regulares são objetos, e, claro, objetos são objetos” – O Melhor do JavaScript, pág. 17 – Douglas Crockford

Da mesma maneira que matrizes (ou vetores, ou, em inglês, arrays) objetos são listas. Nos vetores os elementos das listas são índices numéricos sequenciais. Nos objetos cada item tem um par nome/valor:

var computador = {
    monitor: 'LG',
    mouse: 'Logitech',
    teclado: 'Microsoft'
};
 
alert('Meu mouse é: ' + computador.mouse);

No exemplo acima um objeto foi armazenado em uma variável chamada “computador”. Este objeto possui três items. Para acessar um valor utilizamos um ponto e o nome do elemento a ser acessado logo após o nome da variável do objeto: computador.monitor, computador.mouse, computador.teclado.

É possível inicializar um objeto vazio e adicionar valores posteriormente:

var computador = {};
 
computador.monitor = 'LG';
computador.mouse = 'Microsoft';
computador.mouse = 'Logitech';
computador.teclado = 'Microsoft';
 
alert('Meu mouse é: ' + computador.mouse);

É também possível inicializar um objeto utilizando-se a seguinte sintaxe:

var computador = new Object();

Esta forma não é considerada uma boa prática. Prefira sempre { }.

Recuperando e inserindo valores

Há duas formas de recuperar ou inserir valores. Com ponto (notação já descrita acima) e com [ ]:

var computador = {
    'mouse-sem-fio': 'Microsoft',
    'caixa-de-som': 'Creative Labs'
};
 
computador['mouse-sem-fio'] = 'Logitech';
 
alert('Mouse: ' + computador['mouse-sem-fio']);

A notação [ ] permite recuperar valores com nomes que não são constantes ou palavras reservadas. A sintaxe abaixo é inválida:

computador.mouse-sem-fio = 'Logitech';

Não é possível utilizar -, _, espaços ou outros caracteres que não sejam letras e números quando utilizamos a notação de ponto. Neste caso é preciso utilizar [ ]. Da mesma forma, ao criarmos um objeto, se o nome possuir qualquer caracter especial, aspas (simples ou duplas) deverão ser utilizadas:

var computador = { mouse-sem-fio: 'Logitech' }; // Errado
var computador = { 'mouse-sem-fio': 'Logitech' }; // Correto

Além disso, se o nome do valor a ser recuperado vier de uma outra variáve é necessário utilizar [ ]:

var computador = {
    'nome': 'Meu Computador',
    monitor: 'LG',
    mouse: 'Logitech',
    teclado: 'Microsoft'
};
 
var nome = 'mouse';
 
alert('Meu mouse é: ' + computador[nome]);
alert('Meu computador chama: ' + computador.nome);

É muito importante entender o exemplo acima, que ilustra bem a diferença entre a notação de ponto e [ ].

Percorrendo um objeto

É possível varrer todos os valores de um objeto:

var computador = {
    monitor: 'LG',
    mouse: 'Logitech',
    teclado: 'Microsoft'
};
 
for (var nome in computador) {
    alert('Nome: ' + nome + ' / Valor: ' + computador[nome]);
}

É importante salientar que este tipo de loop for / in não garante que a ordem dos elementos seja sempre a mesma. Dependendo da execução do script a ordem pode variar. Há meios de se utilizar o loop for tradicional para percorrer objetos mas isso fica para uma outra ocasião.

Objetos dentro de objetos: estruturas mais complexas

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
43
44
45
46
47
48
49
50
51
52
53
54
55
var computador = {
    usuarios: [ 
        { login: 'sigmus', rank: 'admin' }, 
        { login: 'amigo', rank: 'guest' }
    ],
    hardware: {
        monitor: 'LG',
        mouse: 'Logitech',
        teclado: 'Microsoft',
        hd: {
            marca: 'Seagate',
            tamanho: 256
        }
    },
    software: {
        sistemaOperacional: {
            nome: 'Windows',
            versao: 7
        },
        editorPHP: 'Aptana'
    }
}; // Fim do objeto
 
// Início das funções
 
var hdPrecisaDeUpgrade = function(micro) {
    var tamanhoHd = micro.hardware.hd.tamanho;
    if (tamanhoHd < 512) {
        return true;
    }
    return false;
};
 
var usuarioEhAdmin = function(login, usuarios) {
    for (var i = 0; i < usuarios.length; i++) {
        var
        login = usuarios[i].login,
        rank = usuarios[i].rank;
 
        if (rank === 'admin') {
            return true;
        }
    }
    return false;
};
 
// Testando alguns dados
 
if (hdPrecisaDeUpgrade(computador)) {
    alert('Você precisa de um HD maior!');
}
 
if (usuarioEhAdmin('sigmus', computador.usuarios)) {
    alert('O usuário é admin!');
}

Este exemplo mostra como é possível compor estruturas de dados mais complexas com objetos. É possível inclusive embutir funções dentro de objetos, mas isso é assunto para outro post. Por enquanto é isso. O entendimento de objetos é crucial para um real entendimento do JavaScript. E é também uma das suas facetas mais fascinantes. Caso você tenha alguma dúvida ou sugestão, por favor, comente!

21/02
2010

Uma importante novidade do jQuery 1.4 foi a melhoria do método index. Este método retorna um número inteiro que indica a posição de um determinado elemento do DOM em relação aos seus irmãos. A primeira posição é 0. Caso o elemento não seja encontrado -1 é retornado. A documentação completa deste método pode ser encontrada aqui.

Ele é muito útil porque geralmente determinados objetos de uma página possuem algum tipo de relação que os agrupa. Vamos imaginar a seguinte estrutura:

1
2
3
4
5
6
7
8
9
10
11
	<ul id="menu">
	    <li><a href="#">Capítulo 1</a></li>
	    <li><a href="#">Capítulo 2</a></li>
	    <li><a href="#">Capítulo 3</a></li>
	</ul>
 
	<div id="capitulos">
	    <div>Conteúdo capítulo 1</div>
	    <div>Conteúdo capítulo 2</div>
	    <div>Conteúdo capítulo 3</div>
	</div>

Um item de menu, ao ser clicado, faz com que seu respectivo conteúdo seja mostrado na tela. Todas as divs de conteúdo estão inicialmente escondidas. Se um item de menu for clicado novamente, todas as divs são escondidas e a div de conteúdo correspondente é revelada. Uma situação muito comum, trivial e extremamente recorrente. O código abaixo implementa a situação:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var gerenciarCapitulos = function(){
	$('#menu li a').click(function(e){
		e.preventDefault();
		var indice = $(this).index('#menu li a');
		mostrarConteudo(indice);
	});
};
 
var mostrarConteudo = function(indiceCapitulo){
	var 
	capitulos = $('#capitulos div'),
	divCapitulo = $(capitulos[indiceCapitulo]);
 
	capitulos.hide();
	divCapitulo.show();
};
 
$(function(){
	gerenciarCapitulos();
});

Index é utilizado na linha 4:

4
		var indice = $(this).index('#menu li a');

Neste caso index recebe como argumento um seletor que indica o contexto que deve ser utilizado para descobrir o índice em relação a this (ou seja, o elemento clicado). Como os items de menu estão na mesma sequência das divs de capítulos o índice acaba sendo o mesmo. O valor do índice é passado para a função mostrarConteudo que recupera do vetor capitulos a div que deve ser mostrada:

12
	divCapitulo = $(capitulos[indiceCapitulo]);

Aqui você pode encontrar o exemplo funcionando.

Um exemplo ruim que não utiliza o método index

É possível programar a mesma funcionalidade de várias outras maneiras sem a utilização de index. Antes eu contava com “ids” que tivessem números em comum. Entretanto, isso implicava em mais código html:

1
2
3
4
5
6
7
8
9
10
11
	<ul id="menu">
	    <li id="link-1"><a href="#">Capítulo 1</a></li>
	    <li id="link-2"><a href="#">Capítulo 2</a></li>
	    <li id="link-3"><a href="#">Capítulo 3</a></li>
	</ul>
 
	<div id="capitulos">
	    <div id="capitulo-1">Conteúdo capítulo 1</div>
	    <div id="capitulo-2">Conteúdo capítulo 2</div>
	    <div id="capitulo-3">Conteúdo capítulo 3</div>
	</div>

Percebam que agora todos as lis de menu e todas as divs de capítulo possuem ids. O código JavaScript correspondente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var gerenciarCapitulos = function(){
	$('#menu li a').click(function(e){
		e.preventDefault();
		var indice = $(this).parent().attr('id').split('-')[1];
		mostrarConteudo(indice);
	});
};
 
var mostrarConteudo = function(indiceCapitulo){
	var 
	capitulos = $('#capitulos div'),
	divCapitulo = $('#capitulo-' + indiceCapitulo);
 
	capitulos.hide();
	divCapitulo.show();
};
 
$(function(){
	gerenciarCapitulos();
});

Somente as linhas 4 e 12 foram modificadas. Na linha 4 utilizamos um split para obter o índice. Na 12 um seletor é montado dinamicamente utilizando este índice. É um código fraco e pouquíssimo elegante. Exemplo ruim pode ser encontrado aqui.

31/01
2010

Vetores (arrays): o básico

Vetores (ou arrays em inglês) são listas de informações. Pode ser uma lista de números, de strings, de outros vetores, de objetos ou de funções. Vetores servem para agrupar dados similares

Imagine que tenhamos que guardar o nome de três carros que vieram de um formulário. É necessário exibir um alerta se um dos carros possuir o nome Civic.

1
2
3
4
5
6
7
8
var
c1 = 'Fiesta',
c2 = 'Palio',
c3 = 'Civic';
 
if (c1 === 'Civic' || c2 === 'Civic' || c3 === 'Civic') {
    alert('Parabéns, você tem um Civic!');
}

Após um tempo em produção, formulário precisa agora ter 4 campos para de entrada. Atualizado, o novo código seria o seguinte:

1
2
3
4
5
6
7
8
9
var
c1 = 'Fiesta',
c2 = 'Palio',
c3 = 'Civic',
c4 = 'Fox';
 
if (c1 === 'Civic' || c2 === 'Civic' || c3 === 'Civic' || c4 == 'Civic') {
    alert('Parabéns, você tem um Civic!');
}

De cara dá para perceber que este código não é nem um pouco prático de ser atualizado. Uitilizando vetores, é possível agrupar os carros em uma única variável. Além disso, as informações armazenadas em um vetor são facilmente acessadas:

1
2
3
4
5
6
7
var carros = ['Fiesta', 'Palio', 'Civic', 'Fox']
 
for (indice in carros) {
    if(carros[indice] === 'Civic') {
        alert('Parabéns, você tem um Civic!');
    }
}

Acessando os elementos de um vetor

Além de strings, os vetores podem conter números:

1
2
3
var lista = [5, 25, 40];
 
alert(lista[1]);

Para acessarmos os valores do vetor lista precisamos utilizar um índice. O primeiro índice de um vetor é sempre 0. Por isso o resultado deste código é 25. O índice 1 é na verdade o segundo índice do vetor.

Podemos também misturar tipos diferentes de dados em um mesmo vetor:

1
2
3
var lista = ['bananas', 10];
 
alert(lista[0]);

Para inicializarmos uma lista como vazia e depois adicionar um elemento:

1
2
3
4
5
var lista = [];
lista.push('abacaxi');
lista.push('goiaba');
 
alert(lista[1]);

É também possível adicionar valores fora de sequência:

1
2
3
4
5
var lista = [];
lista[5] = 'melancia';
 
alert(lista[5]);
alert(lista[0]);

A tentativa de acesso ao índice 0 retorna undefined neste caso, pois o único índice existente é 5. Vetores podem também ser declarados utilizando-se a palavra reservada Array:

1
2
3
var lista = new Array(30, 60, 90);
 
alert(lista[2]);

A utilização de [ ] é mais prática e considerada boa prática, entretanto.

Vetores com variáveis e com outros vetores dentro

Elementos de vetores podem, obviamente, ser variáveis:

1
2
3
4
var meuCarro = 'Fiesta';
var lista = ['Sandero', meuCarro, 'Fox'];
 
alert(lista[1]);

Um vetor dentro de outro:

1
2
3
4
5
// exemplo-7
var numeros = [40, 45, 50]
var lista = [10, 30, numeros];
 
alert(lista[2][1]);

O nível de profundidade dos vetores em JavaScript não é definido e é dado apenas pelo limite da memória (e pelo seu bom senso, claro).

Quantidade de elementos de um vetor

1
2
3
var lista = [12, 24, 48];
 
alert(lista.length);

A propriedade length nos retorna um número indicando quantos elementos um vetor tem. Isso é muito importante e útil, pois nos permite “passear” pelo vetor:

1
2
3
4
5
var lista = ['primeiro', 'segundo', 'terceiro'];
 
for (var i = 0; i < lista.length; i++) {
    alert('Índice: ' + i + ' valor: ' + lista[i]);
}

Para finalizar, uma brincadeira inútil:

1
2
3
4
5
6
7
8
9
10
11
var lista = [
    'motocicleta',
    [30, function() {
        alert('Olá')
        }
     ]
];
 
alert(lista[0,0]);
 
lista[1][1]();
23/01
2010

O Google AJAX Libraries API é um serviço que possibilita o carregamento remoto de frameworks JavaScript a partir de seus servidores. Com ele você pode, por exemplo, fazer com que o seu site carrege o código fonte do jQuery diretamente dos servidores do Google. O serviço está em operação deste 2007.

O framework JavaScript do Yahoo!,YUI, sempre recomendou que seus códigos fossem carregados remotamente. Este post do Ajaxian elenca uma série de vantagens. Destaco algumas delas:

  • caching é feito corretamente; desenvolvedores não precisam mais se preocupar com isso
  • gzip funciona
  • a CDN do Google, distribuida em vários lugares do mundo, possibilita que os arquivos hospedados estejam “próximos” do usuário
  • se uma quantidade significativa de websites utilizar a mesma URL para carregar um determinado framework, quando alguém entrar no seu site pode ser que este arquivo já tenha sido carregado

Faz bastante sentido para mim. Claro que se por um acaso os servidores do Google ficarem indisponíveis, ou o serviço descontinuado, você terá graves problemas. Além disso, os mais paranóicos podem afirmar que este é apenas mais um método desenvolvido para colher informações de usuários. Mas isso com certeza não impede que as pessoas continuem utilizando Gmail ou Analytics.

Outro potencial problema é que você fica dependente de uma conexão com a internet para desenvolver. A vantagem é que a maioria dos desenvolvedores não consegue mesmo trabalhar offline. Nosso trabalho já depende demais ferramentas online para isso ser uma preocupação genuína.

Como funciona?

Existem dois jeitos de se utilizar o serviço. O primeiro, é mais “estático” e simples. O processo é igual ao que você já faz: para incluir um framework, como MooTools, Dojo, jQuery, você deve incluir script src=”" em seu html. Basta fazer com que o atributo src desta tag aponte para uma URL do serviço: http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js. Este link leva diretamente para a versão minificada do jQuery 1.4 (que hoje é a mais atual).

O segundo jeito, mais “dinâmico”, consiste em incluir utilizando script src=”" o código fonte da API Ajax do Google: http://www.google.com/jsapi. Com este código disponível, você pode carregar os frameworks. Vamos incluir novamente o jQuery utilizando o método estático:

google.load("jquery", "1"); // obtém a última versão da família 1 (que hoje é a 1.4)
google.setOnLoadCallback(function() {
 
    $(function(){
        alert('Olá');
    });
 
});

A vantagem desta forma é que, com ela, você pode definir, como no exemplo acima, que quer pegar a versão mais atual do jQuery família de versão 1. É possível ser mais específico:

// obtém versão 1.3.2 (que é a última da família 1.3)
google.load("jquery", "1.3"); 
 
// obtém especificamente a versão 1.2.6 sem compressão
google.load("jquery", "1.2.6", {uncompressed:true});

É uma funcionalidade um pouco mais difícil de implementar mas sem dúvida interessante, pois permite que o seu código sempre utilize a versão mais atual de uma determinada família.

17/01
2010

Se você é fresco como eu ou como a Dani Murai e gosta de codificar suas páginas utilizando doctype strict já sabe que target=”_blank” não valida. Particularmente eu sou contra _blank. É consenso entre os gurus de UX deixar a critério do usuário abrir ou não um link em uma nova guia. Mas como muitos clientes não entendem as implicações de forçar este tipo de prática guela abaixo do usuáro, e você é fresco e continua querendo usar strict, a solução é JavaScript:

1
2
3
4
5
$('a').click(function(){
    var href = $(this).attr('href');
    window.open(href);
    return false;
});

Este exemplo em jQuery, se executado, fará com que todos os links desta página sejam abertos em uma nova guia. O return false na linha 4 serve para barrar o evento padrão do link. Se o return for omitido, o destino do link vai ser aberto em uma nova guia e também na página atual.

preventDefault: uma alternativa mais elegante

Descobri recentemente o método preventDefault que acaba sendo muito mais prático e menos sujo do que o return false:

1
2
3
4
5
$('a').click(function(event){
    event.preventDefault();
    var href = $(this).attr('href');
    window.open(href);
});

Além de fazer muito mais sentido do que o return, preventDefault pode ser colocado em qualquer parte do evento, não interrompendo o fluxo da função.

16/01
2010

No final do ano passado o Google Analytics disponibilizou um novo método para a insercão do seu objeto de tracking:

 
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
 
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
})();

Diferentemente do método tradicional, este código deve ser colocado dentro da tag head ou no início da tag body. A documentação completa pode ser lida aqui: http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html.

O método assíncrono permite que o impacto do script na experiência do usuário seja mínimo (maior performance). Além disso, se o usuário sair da página antes dela ter sido completamente carregada a probabilidade de registro pelo tracking é muito maior deste jeito.

Wordpress plugin: Google Analyticator

Aqui no blog eu utilizo o Google Analyticator, um excelente plugin para Wordpress. Duas funcionalidades são importantes para mim: filtrar as visitas do admin (eu) e tracking automático de eventos para links que levam pra fora do blog. Este plugin já utiliza o método assíncrono.

11/01
2010

Sessões em JavaScript

É possível fazer sessões em JavaScript? Mais ou menos. Eu sei, isso cheira a gambiarra. Fiquei sabendo desta técnica através deste post. O objeto window tem uma propriedade chamada name. Esta propriedade é raramente utilizada. O interessante é que ela sobrevive à mudanças de páginas:

window.name = 'testando';

Se você rodar este exemplo, for para outra página e inspecionar o conteúdo da propriedade window.name vai perceber que o valor continua sendo testando. Ou seja, esta informação continua guardada, simulando uma sessão.

Infelizmente window.name consegue armazenar somente strings. Em um outro post pretendo demonstrar como podemos serializar os dados nesta propriedade. Serializando os dados seria possível armazenar qualquer tipo de vetor, objeto ou função. A maioria dos navegadores consegue armazenar até 10Mb de informações na propriedade.

O ideal seria utillizar uma sessão genuína via back-end, utilizando PHP, C#, Java, etc. Mas como todos nós sabemos, às vezes a realizade exige soluções um tanto quanto “criativas”.

04/01
2010

Por algum motivo idiota, sempre que eu precisava referenciar um elemento do DOM eu buscava o sujeito repetindo o seletor:

$('#post-238 h2 a').css('color','white');
 
if ($('#post-238 h2 a').text() === 'Colocando objetos jQuery em variáveis') {
    alert('Entramos na condição. Texto alterado para branco.');
}

Quando na verdade, o código abaixo é perfeitamente possível e muitíssimo mais limpo:

var conteudo = $('#post-238 h2 a');
 
conteudo.css('color','yellow');
 
if (conteudo.text() === 'Colocando objetos jQuery em variáveis') {
    alert('Entramos na condição. Texto alterado para amarelo.');
}

Vantagens

É boa prática em programação evitar a duplicação de código (no caso a string que define o seletor). Isso evita bugs e facilita o entendimento e a manutenção do código. Além disso, imagino que a performance seja maior.

Sou só eu???

Quando o Felipe Pupo começou a me ensinar MooTools, ele me mostrou que adquirir o objeto do DOM uma única vez era uma das grandes vantagens em relação ao jQuery. Sempre achei que a repetição do $ e dos seletores o tempo todo era algo realmente pouco prático. Não sei se isso é algo novo no jQuery ou se é algo que eu nunca havia reparado.

O fato é que não sou só eu que faço isso. Uma grande parte dos programadores jQuery não armazena os objetos já adquiridos em variáveis. Será que existe algum efeito colateral?

29/12
2009

Todos estes anos trabalhando com desenvolvimento web e eu nunca havia implementado um autocomplete. Após uma rápida busca o candidato óbvio foi o jQuery Autocomplete plugin. A implementação foi tranquila. O autocomplete em questão puxa uma lista de aeroportos de um arquivo JSON.

O problema da acentuação

Infelizmente o plugin é rígido ao tratar caracteres. O comportamente desejado era que os mesmos resultados fossem devolvidos digitando-se, por exemplo, “são” ou “sao” (São Paulo e outros resultados apareceriam neste caso). Isso não acontecia. Ao digitar sao, “São Paulo” não era um dos resultados. Entretanto, percebi que o plugin não diferenciava caixa alta e baixa: SÃO e são retornam o mesmo resultado.

A gambiarra

Modificar plugins é uma péssima prática por vários motivos:

  • um bug pode ser introduzido (e só percebido depois)
  • se uma nova versão do plugin for lançada, a modificação tem que ser refeita
  • pior: a modificação pode não funcionar numa nova versão

Plugins bem feitos geralmente fornecem formas de personalização do comportamento através de objetos de configuração. Em algumas situações, como no nosso caso, isso não é possível. Vasculhei o código do plugin pelo método toLowerCase (conversão de caixa alta para caixa baixa):

177
178
179
180
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
    result = data[i];
    break;
}
255
256
if (!options.matchCase)
    currentValue = currentValue.toLowerCase();

Continuei buscando e encontrei ocorrências da conversão em mais cinco ou seis lugares. Então criei um método prototype para o objeto String chamado filterData:

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
String.prototype.filterData = function() { 
 
    var
    str = this.toLowerCase(),
    specialChars = [
        {val:'a', let:'áàãâä'},
	{val:'e', let:'éèêë'},
	{val:'i', let:'íìîï'},
	{val:'o', let:'óòõôö'},
	{val:'u', let:'úùûü'},
	{val:'c', let:'ç'},
    ],
    regex;
 
    for (var i in  specialChars) {
	regex = new RegExp('[' + specialChars[i].let + ']', 'g');
	str = str.replace(regex, specialChars[i].val);
	regex = null;
    }
 
    return str;
};
 
// Exemplo de uso
alert('Isto é um teste. NÃO podemos ter acentos nem CAIXA ALTA!'.filterData());

Finalmente, para encerrar a gambiarra, substitui no código do plugin todas as ocorrências de toLowerCase para filterData:

177
178
179
180
if( data[i].result.filterData() == q.filterData() ) {
    result = data[i];
    break;
}
255
256
257
258
if (!options.matchCase)
    currentValue = currentValue.filterData();
 
// e em todo o resto do plugin

E funciona! Vou tentar entrar em contato com o autor do plugin sugerindo esta funcionalidade para a próxima versão.

Observação: utilize UTF-8

Para esta modificação funcionar a página HTML deve ter o seu charset declarado como UTF-8. O charset ISO-8859-1 não funciona. Sei disso porque a página original teve que ser alterada para UTF-8. Com ISO-8859-1 buscas com acentos não retornavam resultado algum. Mistério, não tenho idéia porque isso ocorre.

21/12
2009

Função é uma porção de código de um programa maior que desempenha uma tarefa específica e é relativamente independente do resto do código. Geralmente uma função tem um nome, recebe argumentos, realiza sua tarefa e finalmente retorna um resultado.

1
2
3
4
// exemplo-1
function a() {
    alert('Olá, sou a função.');
}

O fragmento de código acima não realiza nenhuma tarefa. A função de nome a foi apenas declarada e está pronta para ser utilizada. Para que algo aconteca precisamos invocar, chamar a função. Isso por ser feito da seguinte maneira:

5
a();

Se adicionarmos a linha acima ao nosso código, um alerta aparecerá no navegador exibindo a mensagem “Olá, sou uma função”.

E para que servem as funções? (argumentos)

Quando um programa de alguma complexidade maior é desenvolvido, problemas recorrentes tornam-se evidentes. Tarefas repetitivas que podem ser identificadas, isoladas e codificadas em funções. Vamos alterar nosso código para o seguinte:

1
2
3
4
// exemplo-2
function a(texto) {
    alert(texto);
}

A função a agora recebe o que é chamado de argumento. Este argumento tem um nome: texto. Novamente, nosso código é inútil enquando não for acionado.

5
a('Sou um exemplo de texto.');

Mais uma vez um alerta pipoca na tela no navegador, desta vez com a mensagem “Sou um exemplo de texto”. Perceba como isso é importante. Nossa função agora comporta-se de maneira diferente dependendo do argumento que lhe é passado. Nessa altura nossa função não é nem um pouco útil. Ela nada mais é do que uma espécie de “atalho” para um outra função: alert. Ambas desempenham a mesma tarefa. Vamos então modificar a função para que ela realize algo um pouco diferente:

1
2
3
4
5
// exemplo-3
function a(texto) {
    alert('O argumento passado foi: ' + texto);
}
a('um exemplo de texto.');

O resultado do exemplo 3 é: O argumento passado foi um exemplo de texto. A função continua utilizando alert, mas agora ela realiza uma tarefa um pouco mais complexa.

É possível afirmar então que funções são pedaços de código que desempenham tarefas muito parecidas, mas de maneira um pouco diferente dependendo do jeito que as acionamos. O que faz a função se comportar de forma diferente é o contexto. O contexto, nos exemplos acima, é dado pelo argumento que é pasado no momento da invocação da função.

Sobre os retornos de funções

Como já foi dito, funções podem ter nomes, receber argumentos, desempenhar uma tarefa e retornar algum resultado.

1
2
3
4
5
// exemplo-4
function a(texto) {
    return 'O argumento passado foi: ' + texto;
}
alert(a('um exemplo de texto.'));

Este exemplo se comporta de maneira igual ao anterior. Alert é chamada e recebe como argumento o retorno da função “a”. Como “a” está dentro de “alert” ela é executada primeiro. Desta forma, quando alert é chamada já temos o resultado de “a” disponível, que é passado como argumento para “alert”.

É possível pensar na sequência de operações da seguinte forma: “alert” é invocada, invoca “a”, que retorna um resultado, que é passado como argumento o argumento de “alert”.

É muito importante entender que o retorno não somente retorna um resultado. Ele também pára a execução da função.

1
2
3
4
5
6
// exemplo-5
function a(texto) {
    return;
    alert('O argumento passado foi: ');
}
a('um exemplo de texto.');

Neste caso nada acontece no navegador. Na linha 3 nossa função, invocada na linha 5, não retorna nenhum valor. Ou seja, ela para de ser executada. Esta função é inútil porque ele não desempenha nenhuma tarefa. Em nenhuma circunstância chegaremos na linha 4. É um exemplo besta mas serve para ilustrar que retornos podem ou não retornar um valor, mas sempre interrompem a execução da função.

13/12
2009

Manipulação e validação de datas é um assunto chato em qualquer linguagem de programação. Em JavaScript temos ao nosso dispor o objeto Date. Tarefas repetitivas como a validação de datas ou verificação de idade acabam se tornando extremamente maçantes. Esta foi a motivação que me levou a criar o eDate (Easy Date).

Download do eDate

Você pode baixar o eDate aqui. Não sei usar direito o GitHub ainda, então se vocês tiverem qualquer dificuldade é só avisar. Feito o download, é só incluir o arquivo na sua página utilizando uma tag script e começar a usar. eDate não é um plugin de nenhuma biblioteca funcionando com JavaScript puro.

Como funciona?

eDate funciona através de métodos estáticos. Praticamente todos os métodos recebem um objeto Date como argumento. As exceções são os métodos isValid e getNew. Vamos começar por eles.

isValid

Este método serve para determinar se uma data é ou não válida (retornando true / false). Recebe como argumento um objeto que será utilizado para validar a data.

1
2
3
4
5
6
var result = eDate.isValid({
    day: 29,
    month: 2,
    year: 2009   
});
alert(result);

A data não é válida porque 2009 não é um ano bissexto. Notem que utilizamos 2 para fevereiro. Uma das inconveniências do objeto date é a sua bizarra atribuição de números aos meses. Janeiro é 0, fevereiro 1, etc. eDate “conserta” isso, fazendo com que janeiro seja 1, fevereiro 2, etc. isValid pode receber parâmetros de outra forma:

1
2
3
4
var result = eDate.isValid({
    'dd/mm/yyyy': '29/02/2009'
});
alert(result);

Foi por isso que optei em utilizar um objeto como argumento para isValid. No exemplo acima utilizamos a máscara dd/mm/yyyy. É possível também utilizar mm/dd/yyyy e yyyy/mm/dd.

A grande vantagem das máscaras é que, muitas vezes, as datas que manipulamos em páginas estão em um único campo input de um formulário. Pretendo adicionar mais máscaras utilizando expressões regulares no futuro.

getNew

Este método retorna um novo objeto date. A vantagem de utilizar getNew ao invés de new Date é que podemos utilizar as máscaras. Além disso, não precisamos nos preocupar em subtrair o mês.

1
2
3
4
5
6
7
var 
d1 = new Date(2009, 11, 13),
d2 = eDate.getNew({'dd/mm/yyyy': '13/12/2009'});
 
if (d1.toString() === d2.toString()) {
    alert(d2.toString());
}

getToday

getToday retorna a data atual do seu computador mas com uma diferença: o horário vem zerado (meia-noite):

1
2
var t = eDate.getToday();
alert(t.toString());

Isso é diferente de:

1
2
var t = new Date();
alert(t.toString());

addDays

Este método serve para adicionar ou subtrair dias de um objeto date:

1
2
3
var d = eDate.getNew({'dd/mm/yyyy': '1/1/2010'});
eDate.addDays(d, -1);
alert(d.toString());

Criamos um objeto date através de eDate.getNew(). Depois utilizamos o método addDays para subtrair um dia do objeto. Percebam que o objeto em si foi manipulado por referência.

diffDays

Caso você precise saber a diferença em dias de uma data para outra:

1
2
3
4
5
var diff = eDate.diffDays(
    eDate.getNew({'dd/mm/yyyy': '03/03/2010'}),
    eDate.getNew({'dd/mm/yyyy': '20/02/2010'})
);
alert(diff);

Neste caso a primeira data está 11 dias no futuro em relação a segunda. Se invertéssemos a ordem das datas, o resultado seria -11. Se as datas fossem iguais o resultado seria 0.

isOverAge

Este método permite verificar, por exemplo, se alguém é ou não maior de idade:

1
2
3
4
5
var result = eDate.isOverAge(
    eDate.getNew({'dd/mm/yyyy': '20/10/1985'}),
    18
);
alert(result);

O primeiro argumento é um objeto date, o segundo o número de anos que deve ser utilizado para a comparação. Uma gama de websites exige que o usuário informe sua idade antes de liberar o acesso. É uma política um tanto quanto duvidosa porque o usuário pode mentir a idade ou alterar a data do seu computador, já que as datas geradas pelo JavaScript dependem do horário do computador do usuário.

Sugestões e melhorias

Por enquanto é isso que a o eDate faz. Penso em adicionar métodos e resolver bugs conforme a necessidade surgir. Aguardo comentários e sugestões.

09/12
2009

A palavra reservada var em JavaScript define a variável como sendo local ao bloco de escopo onde está sendo declarada:

1
2
3
4
5
6
7
8
9
10
11
12
var x = 1;
 
function fn1() {
    var x = 2;
    alert(x);
}
function fn2() {
    alert(x);
}
 
fn1();
fn2();

O resultado é: 2 e depois 1. Neste exemplo, uma outra variável x (que por acaso tem o mesmo nome) foi definida dentro da função fn1. Por isso, ao executarmos a função fn2 o valor original da variável declarada na linha 1 permanece.

Para que o resultado fosse 2 e 2:

1
2
3
4
5
6
7
8
9
10
11
12
var x = 1;
 
function fn1() {
    x = 2; // atribuição de valor sem var
    alert(x);
}
function fn2() {
    alert(x);
}
 
fn1();
fn2();

A modificação da linha 4 altera o valor da variável declarada na linha 1 e temos, consequentemente, o mesmo resultado nos dois alerts (trata-se da mesma variável com o mesmo nome).

Note que na linha 1 dos dois exemplos a variável foi declarada utilizando-se var. Entretanto, a remoção de var não implica em nenhuma mudança de comportamento. Isso acontece porque neste caso a declaração ocorre no escopo mais “exterior” do nosso código. Com ou sem var ela pode ser considerada global.

E porque isso é importante?

O uso de variáveis globais é uma péssima prática. A variável pode ser utilizada e modificada por qualquer parte do código. Isso é fonte de inúmeros bugs. A não ser que seja realmente necessário, sempre declare as variáveis como locais. É possível (e recomendável) declarar várias variáveis locais de uma vez:

1
2
3
4
5
6
    var x = 1, y, z; // boa prática
 
    var x = 1; // não tão boa
    var y;
    var z;
}
06/12
2009

Outro dia perdi um bom tempo tentando utilizar a função json_decode do PHP para parsear um código JSON que eu havia criado. O problema é que eu estava utilizando aspas simples ao invés de duplas:

{
    'prop1': 'valor1'
}

O código válido seria o seguinte:

{
    "prop1": "valor1"
}

Vale lembrar que o exemplo abaixo não é um JSON válido:

{
    prop1: "valor1"
}

Apesar deste código ser um objeto JavaScript válido, a sintaxe do JSON exige que as propriedades sejam declaradas entre aspas duplas.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes