KISS - Keep it simple, stupid!

jshash – Implementando um Hash em JavaScript

postado por Gabriel Gilini em 21/04/2009 03:42:14
Tags: ,

Recentemente escrevi algum código javascript que realizava algumas requisições, e a fim de não buscar informações redundantes, armazenava um cache desses dados que chegavam. Simplificadamente, algo como:

var cache = {};
 
function buscaEProcessaDados(id){
	var dados;
	if(!cache[id]){
		// faz a requisição e retorna dados
		cache[id] = dados;
	}
	// Processa
}

E estava funcionando tudo bem. Mas pensando bem, isso não está muito seguro. Vamos supor que outro script na página execute o seguinte código:

Object.prototype.foo = function(){alert('te peguei!')};

e eu quisesse buscar algum dado que tenha id = foo.

buscaEProcessaDados('foo'); // :(

Mesmo não havendo nenhum dado armazenado na propriedade 'foo' daquele objeto cache que foi criado vazio mais acima, o teste

!!cache[id] //-> true

será interpretado como true, já que o JavaScript encontrará essa propriedade ao subir a cadeia de prototypes (escreverei sobre isso em breve) em busca de algo com nome 'foo'.

A maneira que encontrei para resolver este problema foi implementar um hash simples, com métodos de put, get e remove. O objeto que conterá as propriedades fica inacessível para código fora do objeto construído e as chaves são concatenadas a uma string gerada a partir do timestamp no momento da instanciação.

function Hash(){
    var _hash = {};
    var pre = '__' + (new Date()).getTime() + '__';
 
    function put(key, value){
        _hash[pre + key] = value;
        return this;
    }
 
    function get(key){
        return _hash[pre + key];
    }
 
    function remove(key){
        delete _hash[pre + key];
    }
 
    return {
        'put': put,
        'get': get,
        'remove': remove
    }
}

Utilizando o Hash, nosso código anterior fica assim:

var cache = new Hash();
 
function buscaEProcessaDados(id){
	var dados,
cached = cache.get(id);
	if(!cached){
		// faz a requisição e retorna dados
		cache.put(id, dados);
	}
	// Processa
}

e mesmo que a propriedade 'foo' esteja sendo herdada do Object.prototype, podemos ficar seguros que ela não interferirá em nossa hash:

typeof cache.foo == 'function' &&
typeof cache.get('foo') == 'undefined'; //-> true

É claro que esta Hash é bem primitiva e muitos métodos ainda podem ser implementados. Ainda assim ela funciona perfeitamente para propósitos simples como o demonstrado acima.

Como de costume, o código está hospedado no GitHub, e o download pode ser feito aqui.

Tags: ,
Topo

Sem comentários

WordDefinator e a criatividade na computação

postado por Fabrício Ferracioli em 13/03/2009 15:50:11

Trabalhos de faculdade normalmente são aquela coisa acadêmica, com embasamento em situações comuns e já documentadas e não muito desafiadores. Tudo bem diferente do “mundo empresarial”. Mas, como vale nota todo mundo acaba fazendo. Quando se tem a oportunidade e liberdade de escolher o que deseja fazer num trabalho de faculdade vemos idéias interessante surgirem de mentes sedentas por liberdade. Acredito que esse exercício de criatividade também é muito importante na formação acadêmica, mesmo na ciência da computação, que apesar de ser uma ciência exata necessita de criatividade, principalmente porque deveríamos ser guiados a uma formação para resolução de problemas.

Uma das oportunidades que tive durante o último ano foi na disciplina de Linguagens de Programação, onde deveríamos implementar qualquer coisa numa linguagem a nossa escolha. Sobre a linguagem escolhida deveriamos também apresentar nossas conclusões acerca do projeto da linguagem. A aplicação desenvolvida pelo grupo em que estava, chamada WordDefinator (by Organizações Tabajara), é um script em Javascript que ao ser acionado, com tecla F8, ignora palavras comuns e transforma as outras em links. Ao clicar numa delas será aberto um frame com a definição da palavra, sendo possível escolher como fonte a wikipedia (pt, en), dicionário michaelis e, se a palavra for da língua inglesa, será possível procurar sua definição no dictionary. Para desativar a aplicação e ter o site com seu conteúdo original, basta apertar ESC.
Não era uma aplicação incrível, mas foi interessante notar a reação das pessoas a esse trabalho e aos outros apresentados pelas outras equipes. Um dos grupos fez até um browser utilizando Qt 4. Todos que perguntavam realmente tinham interesse no assunto, ou tinham visto algo que se aplicava a situações que elas passavam.

Não era esse o final pretendido para esse post, mas esse exemplo só confirma o que o Ricardo postou essa semana sobre educação em seu blog. Também na computação, criatividade é tão importante quanto saber programar ou descobrir a complexidade de um algoritmo de ordenação. E vocês, tem a mesma opinião?

Obs.: Como o Lucas reclamou enquanto eu estava escrevendo o post, ele também fazia parte do grupo que implementou o WordDefinator, e ele diz que foi o que “mais implementou”…

Tags: , ,
Topo

1 Comentário

Arrastando elementos com JavaScript (ou JavaScript Drag)

postado por Gabriel Gilini em 27/02/2009 20:21:10
Tags: , ,

Na minha incessante busca por aprender mais sobre JavaScript e DOM, escrevi meu próprio código para arrastar elementos pela página – ou drag se preferir. Escolhi justamente o drag porque, além precisar lidar bastante com as inconsistências da implementação do DOM nos diferentes navegadores, é uma funçãozinha muito utilizada e que me fazia carregar uma lib gigantesca pra usar só essa funcionalidade.

Durante o desenvolvimento desse script aprendi a lidar com o problema de perda de escopo em funções de callback (vale a leitura), algo que eu nunca tinha focado minha atenção por utilizar facilidades existentes nas bibliotecas famosas.

Minhas principais fontes de pesquisa foram o newsgroup comp.lang.javascript e seu respectivo FAQ. Tem um pessoal por lá que sabe do que tá falando, tipo um tal de Douglas Crockford :).

Como funciona

Vou pular as funções de utilidades que estão no objeto Drag, como evtObserve, callback, entre outras; e vou explicar só o funcionamento do drag propriamente dito.

function Drag(elm, handler){
    this.isDragging = false;
    this.draggable = (typeof elm == 'string')?
        document.getElementById(elm):
        elm;
    this.handler = (typeof handler == 'undefined')?
        this.draggable:
        (typeof handler == 'string')?
            document.getElementById(handler):
            handler;

A propriedade ‘isDragging’ é quem vai ser responsável pelo flow control do script, ela que vai dizer quando a função _drag deve parar de ser chamada. O resto é auto-explicativo, se passar uma string eu uso gEBI para encontrá-lo, senão é bom que você tenha passado um elemento! A mesma coisa pro handler, que é o elemento que vai iniciar o evento de drag. Com a diferença de que se você não especificar um, o elemento arrastável vai fazer esse papel.

this.initDrag = this.callback(function(){
        this.isDragging = true;
        this.lastMouseCoords = [
            this.mouseCoords[0],
            this.mouseCoords[1]
        ];
        if(!this.draggable.style.left || !this.draggable.style.top){
            var offsets = this.getOffsets();
            this.draggable.style.left = offsets[0] + 'px';
            this.draggable.style.top = offsets[1] + 'px';
        }
        this.draggable.style.zIndex = this.handler.style.zIndex = '1000';
        this._drag();
    }, {bind: this});

Essa é a função que é chamada quando você clica no handler, ela vai preparar o gramado pra função que vai mover o elemento pela tela, iniciando propriedades como a posição atual do mouse e do elemento arrastável na tela. A função callback coloca a initDrag em uma closure para que ‘this’ signifique o objeto instanciado a partir do construtor Drag.

this._drag = this.callback(function(){
        this.newLeft = parseInt(this.draggable.style.left, 10) -
            (this.lastMouseCoords[0] - this.mouseCoords[0]);
        this.newTop = parseInt(this.draggable.style.top, 10) -
            (this.lastMouseCoords[1] - this.mouseCoords[1]);
 
        if(this.newLeft < 0)
            this.draggable.style.left = 0 + 'px';
        else
            this.draggable.style.left = this.newLeft + 'px';
 
        if(this.newTop < 0)
            this.draggable.style.top = 0 + 'px';
        else
            this.draggable.style.top = this.newTop + 'px';
 
        this.lastMouseCoords = this.mouseCoords;
 
        if(this.isDragging){
            setTimeout(this._drag, 10);
        }
    }, {bind: this});

Esse cara que realiza a mágica, mas é um truque bem simples. A cada vez que esta função é chamada, ela pega as últimas coordenadas do mouse armazenadas no objeto, e subtrai das coordenadas atuais, para descobrir quanto ela vai ter que mover o objeto na página. Depois disso, só armazena as coordenadas atuais como últimas lidas e, se a propriedade isDragging ainda for verdadeira, chama a si própria depois de 0,01 segundos. “Mas por que você não coloca um evento de mousemove?”, você deve estar se perguntando. Simples: performance. Em um evento de mousemove, a função de callback é chamada a cada pixel que o ponteiro se move, o que deixa o movimento de arrasto bem travado se você mover bastante o mouse. Você pode brincar com o valor desse Timeout e ver o que funciona melhor no seu caso, mas 10ms deixa com uma movimentação bem suave.

this.endDrag = this.callback(function(){
        this.isDragging = false;
        this.draggable.style.zIndex = this.handler.style.zIndex = '';
    }, {bind: this});

E esta é a função chamada quando você solta o botão do mouse. Com a propriedade isDragging setada para false, o _drag não é mais chamado e a animação pára.

Como podem ver, o conceito é muito simples, o que complica pra variar é o DOM. Olhando o código todo você pode encontrar todas os malabarismos necessários pra achar a dimensão da tela, do elemento e mais algumas coisinhas místicas das implementações de DOM soltas por aí.

O código todo se encontra disponível no GitHub, e você pode conferir também o script em funcionamento.

Tags: , ,
Topo

Sem comentários

Construindo uma select box com options condicionais utilizando cakephp e ajax

postado por Fabrício Ferracioli em 13/02/2009 16:53:50

Ao acompanhar a lista de discussão CakePHP Tuga percebi que existe uma dúvida que sempre dá as caras por lá: preencher as opções de um select box baseado na escolha de um select box preenchido anteriormente. O maior exemplo disso é o famoso “select cidade estado” ou “combo box cidade estado”.

Essa dúvida retornou novamente, mas através de um e-mail enviado para o pessoal da empresa por um amigo de faculdade com a mesma dúvida. No e-mail ele ainda referenciava duas fontes [1] [2] em que havia tentado, mas não obtia sucesso. Talvez a dificuldade seja o idioma, então decidi fazer um exemplo e disponibilizar para a comunidade. Este exemplo utiliza a famosa combinação de seleção do estado e depois da cidade, sendo que o select box de cidades é preenchido a partir da escolha do estado.

Criei mais coisas que o necessário pensando que esses arquivos poderão ser utilizados para outros projetos, um exemplo disso é o modelo de cidades, que não possui basicamente nada, mas pode ser aumentado para inserção de cidades em um determinado estado.

Atualmente tudo deve ser feito diretamente no banco de dados. Vamos iniciar com sua criação:

DROP TABLE IF EXISTS `cidades`;
CREATE TABLE IF NOT EXISTS `cidades` (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `estado_id` int(10) UNSIGNED NOT NULL,
  `cidade` varchar(50) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;
 
DROP TABLE IF EXISTS `estados`;
CREATE TABLE IF NOT EXISTS `estados` (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `sigla` char(2) character SET utf8 NOT NULL,
  `estado` varchar(30) character SET utf8 NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;

Logo depois criamos os modelos:

class Estado extends AppModel
{
    var $name = 'Estado';
    var $hasMany = array(
        'Cidade' => array(
            'dependent' => true
        )
    );
}
class Cidade extends AppModel
{
    var $name = 'Cidade';
    var $belongsTo = 'Estado';
}

Até esse ponto nenhuma novidade, agora vamos aos controllers. O controller de cidades está quase vazio, contém somente sua declaração.
Vou colocar apenas o de estados.

class EstadosController extends AppController
{
    var $name = 'Estados';
    var $helpers = array('Html', 'Form', 'Javascript', 'Ajax');
    var $components =  array('RequestHandler');
    function lista_cidades()
    {
        $estados = $this->Estado->find('list',
            array(
                'fields' => array('Estado.id', 'Estado.estado'),
            )
        );
        $this->set('estados', $estados);
    }
    function cidades_estado()
    {
        $cidades = $this->Estado->Cidade->find('list',
            array(
                'conditions' => array('Cidade.estado_id' => $this->data['Estado']['estado']),
                'fields' => array('Cidade.id', 'Cidade.cidade'),
                'order' => array('Cidade.cidade ASC')
           )
        );
        $this->set('cidades', $cidades);
    }
}

Aqui começa a ficar interessante, observem que foi necessário carregar os helpers Ajax e Javascript além do component RequestHandler. Esses são os responsáveis pela mágica. O primeiro método é a página que possui as combo box de estado e cidade. O segundo, é responsável por trazer as cidades referentes a um estado.

Vejam agora a primeira view:

<?php
    echo $javascript->link('prototype', false);
    echo $javascript->link('http://cidades-estados-js.googlecode.com/files/cidades-estados-1.0.js', false);
    echo $javascript->link('cidade_estado', false);
?>
    <fieldset>
        <legend>CakePHP Way</legend>
<?php
    echo $form->input('Estado.estado', array('options' => $estados));
    echo $form->input('Estado.cidades', array('options' => array()));
    echo $ajax->observeField('EstadoEstado',
        array(
            'update' => 'EstadoCidades',
            'url' => array('controller' => 'estados', 'action' => 'cidades_estado')
        )
    );
?>
    </fieldset>
    <fieldset>
        <legend>Cidades-Estado</legend>
<?php
    echo $form->input('estado', array('type' => 'select'));
    echo $form->input('cidade', array('type' => 'select'));
?>
    </fieldset>

Nesta view chamados o prototype, necessário para que o cake consiga realizar as requisições ajax, preencher o segundo select box, etc. Lembrando que ele deverá estar na pasta webroot/js da sua aplicação. No primeiro fieldset declaramos dois campos select, sendo um já preenchido com os estados encontrados no banco e trazidos pelo controller, o segundo deverá ter seus valores preenchidos com as cidades de um estado selecionado. O método observeField do helper ajax é utilizado no select com id EstadoEstado, que chama a action cidades_estado do controller estado. Essa action é responsável por trazer a lista de cidades do estado selecionado. Ao finalizar a requisição, o select box com id EstadosCidade deverá ser atualizado com o resultado retornado pela action.
Esse resultado deverá ser um conjunto de options com as cidades, gerado na view abaixo:

if(!empty($cidades))
{
    foreach ($cidades as $id => $cidade)
    {
        echo '<option value="'.$id.'">'.$cidade.'</option>';
    }
}

Pronto, já temos um select de estados > cidade condicional em ajax funcionando. Lembrando que esse exemplo pode ser abstraído para qualquer outra necessidade semelhante.

Aproveitando que esse tipo de necessidade é bastate comum, aproveitei para mostrar um pequeno script que já faz todo o trabalho para você.
Trata-se de um projeto hospedado no googlecode, que faz todo o trabalho sujo para você chamado cidades-estado-js. A única coisa necessária é adicionar o fonte e dizer onde ele deverá funcionar.
Neste exemplo ele é o segundo script adicionado. O terceiro script é quem diz onde deverão ser colocadas as cidades e estados. Veja seu fonte, que utiliza prototype.

$(document).observe('dom:loaded', function(){
    new dgCidadesEstados({
        estado: $('estado'),
        cidade: $('cidade')
    });
});

Pronto, com poucas linhas de código já se tem toda a funcionalidade. Agora basta escolher a versão em CakePHP ou a cidades-estados-js.
Para quem deseja muita agilidade, utilize a cidades-estado-js. Para quem quer velocidade e flexibilidade total, mãos a obra com CakePHP.

Acredito que esse post do pinceladas web também possa interessar a quem está lendo até esse ponto. Ele contém um arquivo sql com todas as cidades e estados do Brasil.

[1]http://www.devmoz.com/blog/2007/04/04/cakephp-update-a-select-box-using-ajax/
[2]http://www.jamesfairhurst.co.uk/posts/view/using_ajax_to_populate_a_select_box_in_cakephp/

Tags: , , , , ,
Topo

32 Comentários

Sobre JavaScript e Simplicidade

postado por Gabriel Gilini em 10/11/2008 15:39:38

Assistindo à apresentação de Christian HeilmannMaintainable JavaScript — me dei conta da quantidade de código JavaScript que escrevi inutilmente ao longo dos últimos meses. Não é segredo que gosto muito da linguagem, e sempre penso em soluções mirabolantes para resolver todo tipo de problema com algumas linhas de código. Porém percebi como o advento das bibliotecas JavaScript tem feito com que os desenvolvedores pensem menos.

Explico. Suponhamos que você tenha vários elementos que gostaria de esconder em sua interface. Se você é um purista, não utiliza nenhuma biblioteca, teremos algo como (código adaptado da apresentação do Heilmann):

Quatorze linhas de código. Se todos os browsers utilizados suportassem getElementsByClassName seriam apenas 5, um dia chegamos lá ;)

Se você utiliza Prototype, jQuery, ou outra das inúmeras bibliotecas existentes, o código fica bem menor para o mesmo resultado final.

Simples, não é? Se você é designer, só escreve JavaScript aqui e ali, vai perceber que é a solução para todos seus problemas. Mas se você é desenvolvedor, deveria parar para pensar em quantas chamadas de função vão acontecer para que você consiga esconder alguns elementos, uma das tarefas mais triviais em JavaScript. “Ah, mas os computadores conseguem processar isso sem maiores problemas hoje em dia, eles são rápidos”. Se você pensou nisso, devo citar uma frase que vi no comp.lang.javascript há alguns dias.

“Computers are fast” so it is okay to waste all of their
resources. Who are you now, Bill Gates?
– David Mark

E quer saber? Ele está certo. Não podemos tomar nossas próprias máquinas como parâmetro de recurso, devemos escrever código que dê uma ótima experiência de uso a todos e não apenas aos que têm “computadores rápidos”. Mas escrever 14 linhas de JavaScript para esconder alguns elementos? SIM, é isso que devemos fazer, mas não nesse caso. Aqui devemos nos lembrar que o CSS deve cuidar do visual. Tudo o que foi feito até agora, com esses três pedaços de código, foi setar a propriedade “display” como “none” para todos os elementos que tenham a classe “esconder”. Lembrando que a letra “C” de CSS significa Cascading, podemos atingir o mesmo resultado com uma solução muito mais elegante e rápida.

Para encerrar gostaria de enfatizar que não adianta dominar completamente JavaScript, saber o ECMA-262 de cabo a rabo, se você não analisa o requisito antes de sair utilizando todas as facilidades da sua biblioteca favorita. Se você precisa cavar um balde de areia não faz sentido usar uma retro escavadeira.

Abaixo você pode assistir as duas partes da apresentação do Christian Heilmann no Fronteers Conference ‘08


Presentation: Christian Heilmann: Maintainable JavaScript, part 1 from Bachelor-ict.nl on Vimeo.


Presentation: Christian Heilmann: Maintainable JavaScript, part 2 from Bachelor-ict.nl on Vimeo.

Tags: ,
Topo

2 Comentários

Creative Commons License
Sou Ágil: KISS em http://kiss.souagil.com.br está licenciado sobre
Creative Commons Attribution-Share Alike 2.5 Brazil License.

souÁgil