JavaScript Orientado a Objetos – Parte 1

O JavaScript, apesar de ser usado há muito tempo quase que exclusivamente como linguagem de programação estruturada, também é uma linguagem de programação orientada a objetos poderosa. Nessa série – remodelada – vou mostrar um pouco sobre o assunto.

Depois de muito tempo, resolvi editar essa série e repostá-la aqui. Aprendi um pouco mais sobre o assunto através da prática, corrigi alguns erros – muito criticados na época – e adicionei algumas informações que achei pertinentes. Como dá pra ver, o permalink continua o mesmo, e a versão antiga da série já não está mais no ar. Espero que as correções e adições possam esclarecer bem o assunto.

Alguns Conceitos

Antes de começarmos a colocar as mãos em código, vamos aprender um pouco de teoria. O JavaScript não é uma linguagem orientada a objetos completamente desenvolvida, e não implementa alguns conceitos nativamente, como herança e variáveis públicas e privadas, dependendo de “gambiarras” para sua implementação.

A base da programação orientada a objetos é, logicamente, objetos. Um objeto é uma abstração de algo real (uma mesa, um carro, a janela do navegador, um elemento HTML…). Esse objeto possui sua própria estrutura de dados, encapsulada em seu próprio escopo. Esses dados podem ser tipos primitivos (Strings, Arrays, Numbers, Booleans…), funções (as quais chamaremos de métodos, quando dentro dos objetos) e até outros objetos.

Para criarmos um objeto, precisamos da definição de sua estrutura de dados, criada através de uma matriz. Essa matriz é chamada de classe, ou construtor no JavaScript.

De começo creio que é o suficiente. Quando for necessário, explicarei outros conceitos, como encapsulamento, polimorfismo, herança…

Criando Construtores

Em nosso querido JavaScript, um construtor é apenas uma função comum, que é instanciada em uma variável. Essa instância se faz através da palavra-chave new, indicando a criação de um novo objeto.

function Person() {}
	var me = new Person();

A palavra-chave new, entretanto, só é necessária caso quando a função não retorna o objeto recém-criado. Para retorná-lo, usamos a palavra-chave this.

function Person() { return this; }
	var me = Person();

O this, apesar de estar no construtor, refere-se ao próprio objeto. Ele será bem usado mais a frente.

Definindo Propriedades

Propriedades, ou atributos, são os dados que ficam encapsulados no escopo do objeto. Como dito mais acima, essas propriedades podem ser tipos primitivos, funções ou outros objetos.

Seguindo a idéia de algo real, toda pessoa tem um nome. Vamos dar um nome à pessoa que “criamos”.

var me = new Person();
	me.name = "Julio Greff";

No código acima, definimos um nome apenas ao objeto me. Outros objetos criados a partir de Person não terão a mesma propriedade. Isso é útil em nosso caso, já que somente eu me chamo “Julio Greff”, e só você possui seu nome.

A mesma técnica não é tão útil quando precisamos definir uma propriedade para todos os objetos criados a partir de determinado construtor. Já pensou se tivéssemos, por exemplo, que definir o número de olhos para cada pessoa que criarmos? Para evitar retrabalho, definimos as propriedades comuns a todos objetos dentro do construtor.

function Person() {
		this.eyes = 2;
	}

No exemplo, usei this para me referir ao próprio objeto, ou seja, todo objeto criado terá a propriedade eyes igual a 2.

Mesmo definindo uma propriedade comum a todos objetos, podemos modificar alguma propriedade de um objeto em particular sem que os outros sejam afetados. Por exemplo, no caso de um cíclope, caolho ou alguma anomalia genética grave.

var me = new Person();
	var cyclop = new Person();
	cyclop.eyes = 1;
	alert(cyclop.eyes); // 1
	alert(me.eyes); // 2, inalterado

Como você deve ter notado – ou pelo menos devia – o acesso às propriedades é feito através do . (ponto), assim como é feita a sua definição.

Desde quando a humanidade é a humanidade, toda pessoa recebe um nome quando nasce. Também pode receber um apelido, uma roupa, ou qualquer outra coisa. Com os objetos acontece algo parecido. Na “concepção” de cada objeto, podemos passar parâmetros para a função construtora, assim como faríamos como outra função qualquer. Esses parâmetros podem, mais tarde, vir a se tornarem propriedades do objeto.

function Person(name) {
		this.name = name;
	}

Dessa maneira, quando criamos o objeto, já podemos dar um nome à pessoa, sem a necessidade de fazê-lo depois da criação do objeto.

Definindo Métodos

Assim como características (propriedades), pessoas também têm comportamento: andar, falar, comer… Objetos também têm comportamento, e esses comportamentos são chamados de métodos. Métodos nada mais são do que funções que são executadas no escopo de algum objeto.

Para criarmos métodos, procedemos da mesma forma com que criamos propriedades. Simples, rápido e indolor:

function Person() {
		this.speak = function(message) {
			alert(message);
		}
	}

Para executarmos métodos também procedemos da mesma forma com que acessamos propriedades, com a diferença de que estamos chamando uma função.

me.speak("Olá!"); // "Olá!"

Essa não é a única maneira de criar métodos, mas é a mais simples, mais organizada e mais eficiente. As outras formas usam propriedades apontando para uma função criada normalmente, dentro ou fora do objeto.

Para a parte 1 é o suficiente. Tenha uma boa digestão e logo volto com mais! Até lá!

18 de fevereiro, 2008

Mootools – Chain

Cookies!

Executar funções em determinada ordem, como filas de requisições, através de uma classe. Resumindo, é isso o que faz a classe Chain, da Mootools, e nesse post veremos como funciona e também como implementar essa funcionalidade em suas próprias classes.

Seqüenciando Funções

Para criar uma seqüência de funções, basta instanciar a classe Chain. Após isso, já podemos adicionar funções à seqüência para podermos chamá-la quando necessário.

Adicionamos funções à uma seqüência através do método Chain.chain, passando como parâmetro a função a ser adicionada à seqüência.

var myChain = new Chain();
myChain.chain(function() {
	alert("Primeira Função");
});
myChain.chain(function() {
	alert("Segunda Função");
});

O método Chain.callChain é quem chama a próxima função da seqüência. Quando o método é executado, a função é executada e então removida da seqüência.

myChain.callChain(); // "Primeira Função"
myChain.callChain(); // "Segunda Função"

Caso necessário, podemos limpar todas as funções de nossa seqüência através do método Chain.clearChain.

Implementando em Classes

Obviamente, Chain é muito mais útil quando usado dentro de uma função. Classes não deixam de ser uma função, e possuem muito mais poder. É nelas onde a utilização de Chain acontece na própria Mootools.

Após a implementação de Chain em alguma classe, podemos utilizar seus métodos.

var myClass = new Class({
	"initialize": function() {},
	"execute": function() {
		this.callChain();
		if(this.chains.length > 0) this.execute();
	}
});
myClass.implement(new Chain);
var myChain = new myClass();
myChain.chain(function() {
	alert("Primeira Função");
});
myChain.chain(function() {
	alert("Segunda Função");
});
myChain.execute();

Como no exemplo acima, também podemos utilizar a variável Chain.chains, que é o array que contém todas as funções na seqüência. Não é necessário se preocupar muito com ele, já que a classe faz todo o necessário. O utilizei acima somente para não criar um loop infinito.

Essa classe também já é implementada na utilização de efeitos, para encadear vários efeitos subseqüentes. Também muito útil para se criar uma fila de requisições Ajax, ou algo do gênero. Vou ficando por aqui, até!

10 de fevereiro, 2008

Firefox, Event Listeners e Return

Quem já não perdeu alguns fios de cabelo usando return false em um Event Listener no Firefox? Por mais que se tente, o evento continua com seu comportamento padrão. Nesse post vou mostrar a “grande” solução.

De acordo com a especificação do ECMAScript, Event Listeners (definidos através da famosa addEvent, para execvent, para executar funções no disparo de eventos) não possuem valores de retorno, ou seja, return false não faz efeito algum (pelo menos no Firefox, que segue esse padrão).

A solução é simples: a função não possui valor de retorno, mas recebe como parâmetro um objeto Event. Através dele, podemos usar Event.preventDefault para impedir que o comportamento padrão seja executado. Veja o código:

addEvent(myForm, "submit", function(e) {
		if(e && e.preventDefault) e.preventDefault();
		else return false; // JavaScript à moda IE
	})

Simples, não? Espero que possa ajudar, já que também tive esse pequeno problema. Até o próximo post!

07 de fevereiro, 2008

Cookies em JSON

Cookies!

Através da serialização de dados com JSON, podemos tornar muito mais fácil a utilização de cookies em JavaScript. Além de tornar mais fácil a leitura/gravação dos dados, podemos eliminar a necessidade de vários cookies para o mesmo site.

Com essa idéia na cabeça há tempos, e aproveitando que continuo bem posicionado no Google em relação a Cookies em JavaScript, iniciei minha implementação, que terminou no código que vou mostrar.

Antes de começar…

Antes de começar, será necessário que utilizemos um parser JSON. Você pode codificar sua própria solução, ou utilizar a biblioteca JSON do JSON.org. Eu optei pela segunda opção. Isso não quer dizer que é necessário você fazer o mesmo, podendo até utilizar outra biblioteca como base.

O Código

Download JSON Cookie

Você pode fazer o download do JSON Cookie em sua página de testes, ou clicando no botão abaixo. Você também encontra a opção de incluir ou não o parser JSON, caso você deseje utilizar uma solução externa.

Utilização

Esse novo código utiliza uma sintaxe bem parecida com o antigo JGCookie, modifiquei apenas os atributos data (logicamente, para poder tratar objetos) e expires, e adicionei path.

Para definir um novo cookie, utilizamos o método JsonCookie.set. É necessário definir o nome do cookie (primeiro parâmetro) e seu conteúdo (um objeto JSON, como segundo parâmetro).

var userData = { "user": "JulioGreff", "password": "abcdefghi" };
	JsonCookie.set("userData", userData); // Gravando o Cookie
	

Também é possível definir a data de expiração e caminho (path) do cookie:

JsonCookie.set("userData", userData, new Date(2008,11,30), "/admin");
	

Para ler o cookie, basta chamar o método JsonCookie.get com o nome do cookie.

alert(JsonCookie.get("userData").user); // "JulioGreff"
	

Corrigindo um bug do JGCookie, agora também é possível apagar os cookies, através do método JsonCookie.unset, com o nome do cookie.

JsonCookie.unset("userData");
	alert(JsonCookie.get("userData")); // false
	

Caso você tenha alguma dúvida, sugestão ou correção, comente!

27 de janeiro, 2008

Mootools – Options

Através dos conceitos de reutilização de código, as funções JavaScript de hoje contém opções e mais opções para o usuário definir (ou não). Verificar cada uma dessas opções manualmente dá muito trabalho, mas a Mootools nos ajuda. Vamos ver o suporte dessa incrível biblioteca, através da classe Options.

O que faz?

A classe Options define as opções passadas pelo usuário para alguma classe. Essa definição é feita sobrescrevendo os valores padrão (caso esses sejam definidos pelo usuário) e os mantendo quando nenhuma opção correspondente é passada.

Lógico, nada muito espetacular. Isso poderia ser feito através da função $merge, da própria Mootools. Mas há mais que isso. Além de definir opções, a classe Options também vasculha-as em busca de opções que comecem com “on”, e automaticamente adicionam-as como um evento, caso a classe Events esteja implementada. É mais que apenas um $merge disfarçado…

Implementando

Mais uma vez, antes de podermos utilizar essa classe extra, devemos implementá-la em nossa classe. Novamente, utilizamos o método Class.implement.

Caso você queira utilizar o suporte a eventos, também implemente a classe Events, na mesma declaração.

myClass.implement(new Options, new Events);

Definindo Opções

Antes de tudo, é necessário definir as opções que serão utilizadas por padrão. Essas opções padrão devem estar em this.options, sendo this a nossa classe.

var SlideShow = new Class({
	"options": {
		"slides": [],
		"startSlide": 0
	},
	"initialize": function() {}
});

Para fazer a “escolha” das opções, basta utilizar o método Options.setOptions, levando como parâmetro as opções que devem sobrescrever as opções padrão. Após a chamada ao método, todas as opções estarão disponíveis em this.options. Se o usuário definir opções que não estão definidas como padrão, elas também serão adicionadas.

var SlideShow = new Class({
	"options": {
		"slides": [],
		"startSlide": 0
	},
	"initialize": function(options) {
		this.setOptions(options);
	}
});
var mySlideShow = new SlideShow({
	"slides": [$("slide-1"), $("slide-2")],
	"wrap": true
});
mySlideShow.options["startSlide"]; // 0
mySlideShow.options["wrap"]; // true, também foi adicionada

Definindo Eventos

Não há diferença quanto a adicionar opções comuns ou eventos, os dois são adicionados da mesma maneira.

var mySlideShow = new SlideShow({
	"slides": [$("slide-1"), $("slide-2")],
	"wrap": true,
	"onStart": function() {}
});

No caso de eventos, não é necessário definir uma opção padrão, já que os eventos são chamados apenas se estiverem definidos, não causando erros. Caso você considere necessário, pode-se usar Class.empty como padrão. Class.empty é apenas uma função vazia, apenas para não precisarmos nos preocupar em definir algo a ser executado.

var SlideShow = new Class({
	"options": {
		"slides": [],
		"startSlide": 0,
		"onStart": Class.empty
	},
	"initialize": function(options) {
		this.setOptions(options);
		this.fireEvent("onStart", this.options.startSlide, 10);
	}
});

Assim como as outras classes, a própria biblioteca faz grande uso de Options. É o espírito da reutilização, tanto na própria biblioteca quanto para os desenvolvedores. Até a próxima!

20 de janeiro, 2008