A nova API do Dados Abertos entrega dados em XML e JSON de forma paginada. Neste tutorial, você pode saber mais sobre como funciona o mecanismo de paginação, e ver um exemplo em HTML e JavaScript com uma forma de juntar os resultados de diferentes requisições em um só conjunto de dados.
A nova interface de programação de aplicações (API) do serviço Dados Abertos da Câmara foi projetada para fazer jus ao nome: sua finalidade principal é fornecer conjuntos reduzidos de dados, selecionados por meio de diversos parâmetros de busca, para aplicações leves que exibam ou processem esses dados de maneiras particulares. É um tipo de trabalho diferente do que é feito com os arquivos de dados para download, que contêm todas as informações sobre todos os itens de suas respectivas entidades — e por isso exigem disponibilidade de comunicação ("largura de banda") para o download de vários megabytes de uma vez, grandes espaços de memória RAM e processamento intenso na seleção e cruzamento dos dados.
Para manter os conjuntos de dados selecionados relativamente pequenos e a transferência deles mais veloz, a nova API segue um padrão muito comum em outras interfaces que seguem a arquitetura REST: as URLs (ou, mais corretamente, os recursos) que fornecem resultados em listas dividem o resultado em páginas.
Todas as respostas válidas do novo Dados Abertos, tanto em JSON quanto em XML, são entregues em uma estrutura padronizada de organização. Há uma seção com o nome dados
, contendo os resultados
da requisição, e uma seção com o nome links
. O conteúdo de dados
, nos recursos coletivos, é uma lista ordenada de itens que correspondam aos parâmetros de busca utilizados.
<xml> <dados> <partido_> ... </partido_> <partido_> ... </partido_> ... </dados> <links> <link> <rel>self</rel> <href>"..."</href> </link> <link> <rel>next</rel> <href>"..."</href> </link> <link> <rel>previous</rel> <href>"..."</href> </link> <link> <rel>first</rel> <href>"..."</href> </link> <link> <rel>last</rel> <href>"..."</href> </link> </links> </xml>
{ "dados": [ { ... }, { ... }, ... ], "links": [ { "rel": "self", "href": "http://...", }, { "rel": "previous", "href": "http://...", }, { "rel": "next", "href": "http://...", }, { "rel": "first", "href": "http://...", }, { "rel": "last", "href": "http://...", } ] }
Cada elemento link
tem dois campos: href
traz uma URL e rel
é um termo que descreve a relação que a URL tem com o documento ou recurso retornado. Esses termos usados com rel
são de certa forma padronizados, e por serem divulgados no site da Internet Assigned Numbers Authority (IANA) são conhecidos popularmente
como "relações IANA".
Nem sempre todos os links mostrados nos exemplos estão presentes, e não precisam aparecer no resultado nesta mesma ordem. O valor do campo rel
indica os seguintes significados para as URLs:
O uso dos links, e principalmente de suas descrições semânticas dos campos rel
, são recomendações fundamentais da arquitetura REST e da chamada "Web 3.0". É que está na essência de conceitos como linked data e HATEOAS (Hypermedia As The Engine Of Application State, ou "hipermídia como o motor de estado da aplicação").
Na API do Dados Abertos, todos os recursos coletivos (isto é, que fornecem listagens) que têm suporte a paginação usam os mesmos parâmetros de query string para configurá-la:
Para se obter, por exemplo, uma lista de dados de 50 deputados de cada vez, a URL inicial seria .../deputados?itens=50
. Na resposta dessa primeira requisição, haveria um link, identificado com o parâmetro rel
com o valor next
, com a URL já configurada como .../deputados?itens=50&pagina=2
, para a obtenção dos dados dos 50 deputados seguintes.
O correto uso dos elementos link
permite que você obtenha todos os itens de um recurso paginado, de qualquer tamanho total, usando um conceito simples: enquanto cada resposta tiver um link cujo campo rel
tenha o valor next
, é só fazer uma nova requisição usando a URL que esse link traz como valor do campo href
.
É fácil imaginar isso como um loop, mas em linguagens como JavaScript as requisições para obter dados via rede são feitas de maneira assíncrona: logo que a requisição é feita, o fluxo do programa continua
sem esperar a resposta, e é preciso definir o que vai acontecer quando enfim essa resposta chegar. Essa característica exige uma abordagem um pouco diferente daquela de loop WHILE
que se poderia imaginar
para o problema.
Neste tutorial, é criada uma simples página HTML, sem qualquer formatação visual, que monta uma lista com todos os deputados em exercício, usando a API do Dados Abertos, e exibe os nomes desses deputados em um menu tipo combobox. Ao escolher qualquer dos nomes, é exibida na página a foto do parlamentar.
A abordagem dessa aplicação para montar uma só lista com várias requisições (que não pretende ser apresentada como a melhor, é importante observar) é centrada na variável global listaDeps
. É definida
uma função a ser executada quando chegar a resposta, e a cada vez que é chamada essa função acrescenta os dados retornados à lista. No jargão técnico, esse tipo de função, que é invocada como reação de resposta a um evento,
é chamada de callback.
Além de acrescentar itens a listaDeps
, a função verifica a existência de um link cujo rel
seja next
na resposta à requisição. Isso é feito em um loop FOR
que
verifica o conteúdo do campo rel
em todos os nós ou objetos link
que são retornados como itens da lista links
. Há também, comentada, uma versão que usa o método .forEach()
da variável.
Assim que é encontrado um link de rel
igual a next
, a URL que está nesse link, em seu campo href
, é usada como argumento para chamar a função principal que cria uma nova requisição,
buscarListaDeps
. A cada vez que é invocada, buscarListaDeps
cria uma nova callback para a chegada da resposta — e todo o processo se repete até que não haja mais links next
a obter.
<doctype html>
<html lang="pt-br">
<head>
<title>Quem é quem dos deputados</title>
<meta charset="UTF-8"/>
<script>
"use strict";
var listaDeps = new Array();
/* buscarListaDeps
Carrega 'listaDeps' com os dados obtidos do recurso paginado,
em chamadas sucessivas
*/
function buscarListaDeps (urlInicio) {
var corpoResposta;
var req = new XMLHttpRequest();
var dados;
req.open ("GET", urlInicio);
req.onreadystatechange = function (evt) {
if (req.readyState === req.DONE &&
req.status >= 200 && req.status < 300) {
// A requisição foi respondida com sucesso.
corpoResposta = JSON.parse(req.responseText);
listaDeps = listaDeps.concat(corpoResposta.dados);
// Se houver um link de rel="next" na resposta, chamar a função de busca
// outra vez usando esse link
// VERSÃO COM LOOP FOR
for (var i = 0; i < corpoResposta.links.length; i++) {
if (corpoResposta.links[i].rel === "next") {
buscarListaDeps(corpoResposta.links[i].href);
return;
}
}
/* VERSÃO USANDO FOREACH
corpoResposta.links.forEach (
function (val, idx, arr) {
if (val.rel === "next") {
buscarListaDeps (val.href);
return;
}
}
);
*/
menuCarregarOpcoes();
} // FIM DO "IF"
} // FIM DE onreadystatechange
req.setRequestHeader ("Accept", "application/json");
req.send();
}
buscarListaDeps("https://dadosabertos.camara.leg.br/api/v2/deputados?itens=100");
/* menuCarregarOpcoes
Configura as opções de nomes de deputados no menu
*/
function menuCarregarOpcoes() {
var i=0;
var menuwdg = document.getElementById("menudeps");
var opt;
// Criar o primeiro item sem o nome de um parlamentar...
opt = document.createElement("option");
opt.text = "Escolha um parlamentar..."
menuwdg.add(opt);
while (listaDeps[i]) {
opt = document.createElement("option");
opt.text = listaDeps[i].nome;
menuwdg.add(opt);
i++;
}
}
/* menuOpcaoEscolhida
Chamada quando o usuário escolhe outro nome no menu, executa
as alterações na foto e no quadro de informações.
*/
function menuOpcaoEscolhida() {
var escolhido;
var menuwdg = document.getElementById("menudeps");
escolhido = menuwdg.value;
for (var i = 0; i < listaDeps.length; i++) {
if (listaDeps[i].nome === escolhido) {
mostrarDeputado (listaDeps[i]);
}
}
}
/*
mostrarDeputado - recebe um item da lista de deputados,
contendo os dados de um parlamentar, e os insere na
exibição do HTML no navegador
*/
function mostrarDeputado (dep) {
var wdgFoto = document.getElementById("fotodep");
var wdgNome = document.getElementById("nomedep");
var wdgPartEst = document.getElementById("part-est");
// A URL da foto é colocada como valor do atributo "src"
// do elemento <img> identificado com o id "fotodep"
wdgFoto.setAttribute("src", dep.urlFoto);
// O nome é inserido como conteúdo do elemento com id "nome"
wdgNome.innerHTML = dep.nome;
wdgPartEst.innerHTML = dep.siglaPartido + "-" + dep.siglaUf;
}
</script>
</head>
<body>
<!-- Aqui é criado o controle de menu -->
<select id="menudeps" onchange="menuOpcaoEscolhida()"></select><br/>
<!-- A foto é exibida neste elemento "img"... -->
<br/>
<img id="fotodep" src="../../img/howtouse/avatar-anonimo.png" width="200"/>
<br/>
<!-- Nome e estado -->
<h3 id="nomedep"></h3>
<h4 id="part-est"></h4>
</body>
</html>