ProgrammableWeb – A referência sobre mashups

Um dos grandes benefícios da colaboração e troca de conteúdos que a web 2.0 nos proporciona são os mashups.
Para quem não sabe, mashup é o ato de misturar dados de serviços diferentes através das APIs disponibilizadas, criando um outro serviço. Um exemplo clássico é tern uma lista de endereços de um sistema próprio, e integrá-lo com o Google Maps (veja um exemplo de como usar a API do Google Maps).

O mundo dos mashups vêm crescendo muito, e descobri um site muito bacana, que tem por objetivo listar tanto APIs quanto mashups já prontos.
No ProgramamableWeb são quase mil APIs e mais de 3400 mashups. Dá pra fazer buscas abertas, ou por áreas e ainda ver quais as mais acessadas.


Crescimento da quantidade de mashups no site

Vale, e muito, como referência para tanto ter novas idéias quanto estudar, e ver o que o pessoal está fazendo por aí.

Usando a Prototype – passo 1 – escondendo selects debaixo de uma popup

Vamos lá, falar de usos práticos da biblioteca Prototype. Okey, aqui no And After há alguns posts a respeito já, mas estou pensando em criar uma série deles.
Nesse primeiro, amos ver como criar uma função que esconde determinados elementos do html se outro estiver por cima.

Não entendeu? Vamos lá, vou explicar de onde veio a idéia – acho que fica mais fácil.
Uma coisa que aparece em qualquer projeto de web ultimamente é a necessidade de se fazer caixas (normalmente divs) que abrem por cima de outras. Normalmente para mostrar conteúdos oriundos de requisições ajax (ok, sei que o termo não está correto, mas não consegui pensar em outro).
Aí, o que normalmente se faz? Tem-se uma div com display:none e no retorno da requisição (callback), dá-se um display:block nela mostrando o conteúdo da forma adequada.
O grande problema é quando essa div abre por cima de um select ou um flash, por exemplo. O IE6 mostra o select ou o swf por cima da div (não ocorre no Firefox nem IE7), acabando com o layout e dificultando tudo. Para isso fiz essa função aqui, genérica, que recebe dois parâmetros: o id do seu elemento html que estará por cima, e o id de um elemento html que contém elementos do tipo select.

Usando o objeto Position (veja a documentação aqui) da Prototype, é possível acessar o método cumulativeOffset(element) que retorna um array com as posições das coordenadas x e y do elemento, independente do local no código em que esteja (posição relativa dos pais, absoluta, floats, etc…)
Com isso, o que é feito: pega-se a posição da div de popup e suas dimensões, e se varre a lista de selects presentes no outro elemento. A cada select que é encontrado, pega-se também as coordenadas desse select e suas dimensões, e através de uma lógica simples faz a comparação nos eixos x e y se a popup está por cima do select. Se sim, deixa o select como invisível.
Segue o código:

function escondeNoOver(id_popup, id_area) {
        //define qual a popup
        var popup = $(id_popup);
        //pega todas os selects de um determinado elemento
        var selects = $(id_area).getElementsByTagName("select");
        //pega coordenadas X/Y da popup (topo do lado esquerdo) e dimensões da mesma
        var posX = Position.cumulativeOffset(popup)[0];
        var posY = Position.cumulativeOffset(popup)[1];
        var width = popup.clientWidth;
        var height = popup.clientHeight;
       
        //percorre a lista de selects
        for(i=0; i<selects.length; i++) {
           
            //pega coordenadas X/Y do select (topo do lado esquerdo) e dimensões do mesmo
            var selX = Position.cumulativeOffset(selects[i])[0];
            var selY = Position.cumulativeOffset(selects[i])[1];
            var widthS = selects[i].clientWidth;
            var heightS = selects[i].clientHeight;
           
            var onX = false;
            //verifica se no eixo X (horizontal), a popup está entre o início e o final do select
            if(posX<selX+widthS) {
                if(posX+width>selX)
                    onX = true;
            } else {
                if(selX+widthS>posX+width)
                    onX = true;
            }
           
           
            //verifica se no eixo Y (vertical), a popup está entre o início e o final do select
            var onY = false;
            if(posY<selY+heightS) {
           
                if(posY+height>selY)
                    onY = true;
            } else {
                if(selY+heightS>posY+height)
                    onY = true;
            }
           
            //tem que estar dentro de X e Y para esconder a popup
            if(onX && onY){
                selects[i].style.visibility = "hidden";
            }
        }
    }

Abaixo segue o código funcional de um html com essa função, onde ao clicar no link do final da página sera chamada a função e ela verificará se há campos do tipo select por baixo daquela div de popup que possui um fundo acinzentado.
Vale notar que essa função pode ser alterada para verificar outros tipos de objetos e não precisa ser usada apenas para essa questão de retorno de requisições ajax – muitas vezes precisamos esconder algumas coisas, independente do motivo, não? 🙂
 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Untitled Document</title>
<style type="text/css">
* {margin:0; padding:0; border:0;}
body {font-family:Verdana, Arial, Helvetica, sans-serif;}
input, select, textarea {border:1px solid #000000;}
form {margin:50px;}
p {margin:10px;}
div#popup{padding:20px; border:3px solid #000000; position:absolute; left:80px; top:350px; background-color:#F0F0F0; width:200px;}
</style>
<script type="text/javascript" src="http://www.prototypejs.org/assets/2008/1/25/prototype-1.6.0.2.js"></script>
<script type="text/javascript">
    function escondeNoOver(id_popup, id_area) {
        //define qual a popup
        var popup = $(id_popup);
        //pega todas os selects de um determinado elemento
        var selects = $(id_area).getElementsByTagName("select");
        //pega coordenadas X/Y da popup (topo do lado esquerdo) e dimensões da mesma
        var posX = Position.cumulativeOffset(popup)[0];
        var posY = Position.cumulativeOffset(popup)[1];
        var width = popup.clientWidth;
        var height = popup.clientHeight;
        //percorre a lista de selects
        for(i=0; i<selects.length; i++) {
            //pega coordenadas X/Y do select (topo do lado esquerdo) e dimensões do mesmo
            var selX = Position.cumulativeOffset(selects[i])[0];
            var selY = Position.cumulativeOffset(selects[i])[1];
            var widthS = selects[i].clientWidth;
            var heightS = selects[i].clientHeight;
            var onX = false;
            //verifica se no eixo X (horizontal), a popup está entre o início e o final do select
            if(posX<selX+widthS) {
                if(posX+width>selX)
                    onX = true;
            } else {
                if(selX+widthS>posX+width)
                    onX = true;
            }
            //verifica se no eixo Y (vertical), a popup está entre o início e o final do select
            var onY = false;
            if(posY<selY+heightS) {
           
                if(posY+height>selY)
                    onY = true;
            } else {
                if(selY+heightS>posY+height)
                    onY = true;
            }
            //tem que estar dentro de X e Y para esconder a popup
            if(onX && onY){
                selects[i].style.visibility = "hidden";
            }
        }
    }
</script>
</head>
<body>
<div id="popup">sua div de popup</div>
<form action="entreemcontato.php" method="post" id="form">
<div>
    <p>Nome:</p>
    <p><input type="text" name="nome" value="Seu Nome"/></p>
    <p>Comentários:</p>
    <p><textarea name="comentários" rows="5" cols="20">
    Seus comentários
    </textarea></p>
    <p>Você é:</p>
    <p><input type="radio" name="vocee" value="homem" checked="checked"/>
    Homem
    </p>
    <p><input type="radio" name="vocee" value="mulher"/>
    Mulher
    </p>
    <p><input type="checkbox" name="cefet" checked="checked"/>Estudo no Cefet</p>
    <p>O que você achou desse minicurso?</p>
    <select>
    <option value="bom">Bom</option>
    <option value="regular" selected="selected">Regular</option>
    <option value="ruim">Ruim</option>
    </select>
    <p>Você repetiria esse curso?</p>
    <select>
    <option value="bom">Sim</option>
    <option value="regular" selected="selected">Sempre</option>
    </select>
    <p><input type="submit" value="Enviar"/></p>
    <p><input type="reset" value="Limpar"/></p>
</div>
</form>
<a href="#" onclick="escondeNoOver(´popup´, ´form´);" title="checar popup">checar popup</a>
</body>
</html>
 

Em breve, outros exemplos usando a Prototype 🙂

Criar classes em javascript – usando a Prototype

Orientação a objetos no javascript? É possível, sim. E muito necessário, ainda mais quando lidamos com projetos de grande porte.
Okey, javascript não é uma linguagem OO (abreviação para orientação a objetos). Nem tudo da orientação a objetos em javascript é possível ser implementada; alguns dos (famosos) paradigmas da OO são impossíveis de serem atingidos por limitações técnicas (por exemplo, não existe o conceito de interface, ou métodos que possam ser sobreescritos). O que não inviabiliza o uso da OO no javascript.

Se você não conhece muito de orientação a objetos, sugiro que você procure na web ou na literatura apropriada os conceitos. Esse post falará sobre o uso da (biblioteca) Prototype na ajuda a implementar a OO.

Quem já implementou do zero uma classe em javascript sabe que a dificuldade não é grande, mas que a sintaxe é meio estranha. Por exemplo, vamos criar uma classe que abstrai um objeto Pessoa:
[update]exemplo retirado do blog do Rodrigo, nesse post aqui[/update]

<script language="javascript">
function Pessoa() {
    var nome;
    var idade;
    var email;
    this.getNome = getNome;
    this.getIdade = getIdade;
    this.getEmail = getEmail;
    this.setNome = setNome;
    this.setIdade = setIdade;
    this.setEmail = setEmail;
    this.mostraValores = mostraValores;
    function getNome() {
        return nome;
    }
    function getIdade() {
        return idade;
    }
    function getEmail() {
        return email;
    }
    function setNome(_nome) {
        nome = _nome;
    }
    function setIdade(_idade) {
        idade = _idade;
    }
    function setEmail(_email) {
        email = _email;
    }
    function mostraValores() {
        return ´Nome: ´ + nome +´ Idade: ´+ idade +´ anos Email: ´+ email;
    }
}
</script>

Para consumir essa classe:

<script language="javascript">
    var pessoa = new Pessoa();
    pessoa.setNome("Rodrigo Lazoti");
    pessoa.setIdade(26);
    pessoa.setEmail("[email protected]");
    alert(pessoa.mostraValores());
</script>

Veja que essa classe mais parece um monte de codificação estruturada agrupada, nem não há um construtor para ela.

Vou direto para a codificação de como ficaria tal classe usando a Prototype:

referenciando a prototype:

<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js" type="text/javascript"/>
var Pessoa = Class.create();
Pessoa.prototype = {
    //construtor padrão
    initialize: function(nome, idade, email) {
        this.setNome(nome);
        this.setIdade(idade);
        this.setEmail(email);
    },
    setNome: function(nome) {
        this.nome = nome;
    },
    setIdade: function(idade) {
        this.idade = idade;
    },
    setEmail: function(email) {
        this.email = email;
    },
    mostraValores: function() {
        return "Nome: " + this.nome + " Idade: "+ this.idade + " anos - Email: "+ this.email;
    }
}

Quem está acostumado com OO percebe logo de cara que, dessa forma, a semântica usada é muito mais direta e clara; você sempre se refencia aos atributos da classe usando this, e você sempre é obrigado a ter um método initialize que funciona como o construtor.
Para esclarecer o código, na primeira linha é usado um objeto Class da própria prototype e chama-se o seu método create(); a partir do retorno desse método tem-se um objeto que pode ser trabalhado como uma classe da Prorotype. Vale a pena lembrar que o código usado para consumir a classe pode ser exatamente o mesmo enquanto a interface (resumidamente, assinatura dos métodos) das mesmas forem iguais.

Agora, uma coisa bacana (peguei o exemplo lá na API da Prototype) é como extender uma classe. Imagine que você quer criar a partir da classe Pessoa, uma classe Pirata; digamos que o seu Pirata é do passado, e não tem e-mail. Mas tem um navio.
O primeiro passo é extender a classe Pessoa, da seguinte forma:

var Pirata = Class.create(Pessoa, {});

Vamos ver como ficaria o código para sobreescrever o método setEmail() e adicionar um método novo:

var Pirata = Class.create(Pessoa, {
    // sobreescreve o método que define o e-mail
    setEmail: function() {
        this.email = null;
    },
    //cria um método para salvar o nome do navio dele
    setNavio: function(navio) {
        this.navio = navio;
    }
   
});

Sugiro para quem está afim de estudar mais, primeiro olhar a documentação da API da Prototype e procurar exemplos por ae na net, e depois pegar algo que você já fez e funciona e tentar reescrever, do zero, usando orientação a objetos. É uma curva de aprendizado não muito complexa, mas é uma forma bem diferente de se programar para quem não está acostumado!

[update]e é claro que eu esqueci apenas de de falar que é necessário incluir a biblioteca prototype no seu javascript. Você pode fazer download ou referenciar a mesma que é hospedada pelo Google Code[/update]

Google Maps – endereços próximos

Nesse post do começo de Maio, falei sobre como usar a API do Google Maps para colocar numa página um mapa que traça uma rota entre dois endereços.

Dando continuidade, usei aquele html como base para exemplificar como usar a API se você necessitar encontrar o endereço mais próximo de uma lista de endereços; no caso abaixo, o que fiz foi plotar na tela 3 endereços (usei como exemplo os endereços de 3 SESCs aqui da capital paulista) e, quando o usuário digitar um endereço e clicar no botão, um listener para o evento onclick do mesmo busca o endereço pela API do Google Maps, faz a comparação entre as coordenadas da latitude e longitude desse endereço com os 3 dos SESCs e, aquele que for mais perto, é usado para traçar um ponto-a-ponto.

Segue, abaixo, o código html totalmente funcional. Ele está todo comentado, e fala por si só; o funcionamento é simples. *aconselho uma lida no post ao qual me referi no começo do post, caso você não esteja familiarizado com a API do Google Maps

[update]parece que havia um problema em alguns browsers dependendo da configuração dele, com o charset utilizado (os endereços com acentuação não eram localizados). Alterei para UTF-8 e upei o html para download[/update]

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Google Maps - encontrando o endereço mais próximo</title>
<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAM3tYiYBfOKmhuRiSYzXlxRSolT9dJHhqkNrIgFq9Znypr0iOlhQeBdhVvDQuPJy4hxdG187GklHLbA" type="text/javascript"></script>
<script type="text/javascript">
    var geocoder = new GClientGeocoder();
    var map;
    var directionsPanel;
    var directions;
    var gdir = new GDirections();
    var addr = new Array(3);
    var distances = new Array(3);
   
    window.onload = function() {
        //seta o país que será usado pela API
        geocoder.setBaseCountryCode("pt_BR");
        map = new GMap2(document.getElementById(´map´), { size: new GSize(710,480) })
        map.addControl(new GLargeMapControl());
        map.setCenter(new GLatLng(-23.547779, -46.639366), 12); //centro da cidade de São Paulo
       
        //mostra o endereço de 3 Sescs no mapa
        showAddress("Sesc Paulista - São Paulo/SP", 1);
        showAddress("Rua Amador Bueno, 505 - São Paulo/SP", 2); //Sesc Santo Amaro
        showAddress("Rua Clélia, 93 - São Paulo/SP", 3); //Sesc Pompéia
        //ao clicar no botão do formulário, faz a busca
        document.getElementById("trace-route").onclick = function() {
            //tenta encontrar o endereço digitado pelo usuário
            geocoder.getLocations(
                document.getElementById("from").value,
                    function(point) {
                        //marca no mapa o endereço citado
                        add_marker(point);
                        //se existir o endereço que o usuário digitou...
                        if(point.Placemark) {
                            //cria objeto GLatLng e grava a latitude/longitude do endereço digitado pelo usuário, para uso futuro
                            var b = new GLatLng(point.Placemark[0].Point.coordinates[1], point.Placemark[0].Point.coordinates[0]);
                            /*
                            variáveis que serão usadas para comparar distâncias entre coordenadas
                            de latitude e longitude
                            */
                            var smallestDist;
                            var smallestDistId;
                            //percorre lista dos 3 endereços do SESCs
                            for(var i=0; i<3; i++) {
                                var a = new GLatLng(addr[i][1], addr[i][0]);
                                /*
                                usa método da classe GLatLng() que retorna a distância, em metros, entre
                                duas coordenadas de latitude/longitude
                                */
                                var x = a.distanceFrom(b);
                                //lógica para marcar qual a menor distância entre o endereço digitado e os 3 endereços dados
                                if(x<smallestDist || i==0) {
                                    smallestDist = x;
                                    smallestDistId = i;
                                }
                            }
                            /*
                            cria um array de dois objetos GLatLng, com as coordenadas do endereço digitado pelo
                            usuário e o endereço do ponto mais próximo a esse endereço
                            */
                            var pointsArray = [new GLatLng(addr[smallestDistId][1], addr[smallestDistId][0]), b]
                            directions = new GDirections(map); //instancia objetos
                            //usa o array de pontos para traçar a rota entre os dois endereços (ou melhor, entre as duas coordenadas)
                            directions.loadFromWaypoints(pointsArray, {"locale":"pt_BR"});
                        }
                    }
              );
        }
       
    }
   
   
    function showAddress(address, k) {
      geocoder.getLocations(
        address,
        function(point) {
            add_marker(point, k);
        }
      );
    }
   
   
   
    function add_marker(response, k) {
        if(!response.Placemark) {
            return;
        }
        place = response.Placemark[0];
        if(k) {
            //grava a latitude/longitude do endereço
            addr[k-1] = place.Point.coordinates;
        }
        point = new GLatLng(place.Point.coordinates[1],
                            place.Point.coordinates[0]);
        var marker = createMarker(point, place.address, k);
        map.addOverlay(marker);
    }
   
    function createMarker(point,html, k) {
        var marker = new GMarker(point, false);
        return marker;
    }
   
</script>
</head>
<body>
<div id="map"></div>
<form action="#" method="POST">
    <fieldset>
        <label for="from">ponto de partida:</label>
        <input type="text" name="from" id="from" />
    </fieldset>
    <input type="button" class="button" id="trace-route" value="achar SESC mais próximo" />
</form>
</body>
</html>

Ajax Libraries API

Mais uma vez, está lá o Google oferecendo serviços para facilitar a vida dos desenvolvedores.

Dessa vez, lançaram um serviço no qual hospedam algumas famosas bibliotecas javascript no servidor e que você pode usá-las de lá. Nesse momento, as bibliotecas disponíveis são: jQuery, prototype, script.aculo.us, MooTools e Dojo.



A forma de se utilizar é simples: primeiro você referencia a biblioteca javascript do Google:

<script src="http://www.google.com/jsapi"></script>

Depois, você precisa (1) carregar a biblioteca quer utilizar e, (2) dentro da função de callback dela, poderá utilizar a sua biblioteca normalmente, como se tivesse carregado da forma tradicional.

No exemplo abaixo, eu criei uma página que usa as bibliotecas prototype e script.aculo.us e faz uma div ser arrastável pela tela – um exemplo simples de como começar o uso da AJAX Libraries API do Google:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Ajax Libraries API</title>
<script src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
	//1 - carrega bibliotecas prototype e scriptaculous
	google.load("prototype", "1.6");
	google.load("scriptaculous", "1.8.1");
	//2 - na função de callback, crio uma div que pode ser arrastada usando um objeto da script.aculo.us
	google.setOnLoadCallback(function() {
		new Draggable($("drag"));
	});
</script>
<style type="text/css">
	div#drag {
		width:100px; height:50px; cursor:pointer; border:1px solid #FF0000;
	}
</style>
</head>
<body>
<div id="drag">drag me</div>
</body>
</html>

 

Qual a vantagem?

Você não precisa se preocupar em estar sempre com a última versão de cada biblioteca – o Google hospedará sempre a última versão estável, e caso você não especifique qual você quer usar, ele disponibilizará essa -, terá sempre acesso às últimas (e mais bacanas) bibliotecas javascript que estão disponíveis na web e não precisar se preocupar em setar cabeçalhos de cachê, entre outras vantagens.

No site do AJAX Libraries API, há explicações de como carregar todas as bibliotecas, e referências mais específicas. Vale a pena 🙂

Fonte: http://googlesystem.blogspot.com/2008/05/google-hosts-popular-javascript.html