jQuery Mobile - Listview

Este é um dos muitos exemplos de jQuery Mobile. por favor consulte os artigos de introdução ao jQuery Mobile e ao jQuery para informações introdutórias sobre os frameworks.

O código fonte deste artigo pode ser encontrado aqui: https://github.com/gomobile/sample-jqm-listview.

       

Este exemplo usa a API Rotten Tomatoes* para criar uma lista paginada dos DVDs disponíveis para locação. Ele demonstra como popular uma listview com dados vivos de uma API web, e como gerar páginas dinamicamente.

As Bases da API Web

Este é um exemplo de como utilizar a grande quantidade de dados interessantes de terceiros através de APIS open web. Ele utiliza a API Rotten Tomatoes para obter informações sobre os DVDs lançados atualmente. Como muitas outras APIs web, a API Rotten Tomatoes é um serviço RESTful que retorna os dados no formato JSON. A API especifica diversas URLs para diversas funcionalidades nos serviços. Por exemplo, dados dos DVDs lançados atualmente são fornecidos através desta URL:

http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/current_releases.json?parameters

Para invocar esta funcionalidade, um app deveria enviar uma solicitação HTTP GET para esta URL. Os parâmetros da função são especificados como parâmetros da URL de consulta. A API Rotten Tomatoes requer uma apikey para identificar a identidade da app que está acessando o serviço, para que eles possam analizar e controlar o acesso à API. Ela também aceita parâmetros para especificar quantos DVDs formam uma "página" de dados de resposta, e quantas páginas de DVD devem ser retornadas, para que ela possa particionar um conjunto grande de lançamentos em múltiplas páginas de resposta.

ParâmetroRequeridoValor PadrãoDescrição
apikeysimN/Achave para identificar o app acessando a API
page_limitnão16número de DVDs por página
pagenão1página atual do lançamento de DVDs
countrynãouspaís para localização

A API retorna dados em JSON em uma resposta HTTP, que contém o número total de DVDs disponíveis, e uma lista do tamanho da página com os detalhes do filme.

 
{ "total": 50, "movies": [{ "id": "771203390", "title": "Even the Rain (Meme La Pluie)", "year": 2011, "mpaa_rating": "Unrated", "runtime": 103, "ratings": { "critics_rating": "Certified Fresh", "critics_score": 89, : }, "synopsis": "...", "posters": { "thumbnail": "http://content8.flixster.com/.../11155586_mob.jpg", : }, "abridged_cast": [ {"name": "Gael Garcia Bernal"}, {"name": "Luis Tosar"}, {"name": "Najwa Nimri"} ], : }, ... , { ... }], : }

Chamando APIs Web via AJAX

Este exemplo chama a API Rotten Tomatoes usando uma solicitação AJAX. Comunicação assíncrona é utilizada para garantir que aplicação permaneça responsiva enquanto a solicitação está sendo processada. Em vez de ter somente uma thread JavaScript* na app aguardando por uma resposta do serviço remoto, a thread da aplicação pode somente especificar um callback para processamento desta resposta futura, e retornar da função de solicitação para fazer algum trabalho mais útil neste intervalo.

Adicionalmente, este exemplo explora um hack popular do JSONP para que o código possa trabalhar de form apropriada como um web app hospedo em navegadores, não somente como um web app empacotado em dispositivos. (Em geral, uma solução CORS HTML5 pode ser usado em vez de JSONP, mas somente com dispositivos clientes e provedores de serviços suportados.) Usar o JSONP permite que uma versão hospedada desta aplicação contorne a política de mesma origem dos navegadores e se comunique com o serviço do Rotten Tomatoes. Este exemplo utiliza um plugin JSONP do jQuery para abstrair a logística de implementação de uma solicitação AJAX baseada em JSONP. Sob o capô, a API Rotten Tomatoes é efetivamente chamada através da injeção de uma tag de script com a URL de solicitação como sua fonte dentro do DOM, e enganando o navegador para que ele acredite que isto seja apenas o download de um script de terceiros do domínio do Rotten Tomatoes. A API Rotten Tomatoes API então retorna os dados desejados como um parâmetro para uma função de callback simulada, especificada pelo parâmetro de callback na solicitação. O plugin JSONP do jQuery fornece o callback simulado, nomeado por padrão como _jqjsp, cujo principal propósito é fazer com que os dados de resposta sejam disponibilizados para as funções de callback reais do app.

 
/* código para enviar uma solicitação AJAX baseada no SONP */ $.jsonp({ /* URL de solicitação para obter os dados dos lançamentos atuais da API Rotten Tomatoes */ url: baseurl + 'lists/dvds/current_releases.json', /* parametros para ser enviados como parte da solicitação */ data: { page: page, page_limit: nItemsPerPage, apikey: key }, /* nome do parâmetro para complementar o nome do callback na solicitação */ callbackParameter: 'callback' })
 
<!-- tag de script gerada e injetada no DOM pelo código acima --> <!-- (asumindo page = 1, nItemsPerPage = 12, key = KEY) --> <script async src="http://api.rottentomatoes.com/api/public/v1.0/lists/dvd/ current_releases.json?page=1&page_limit=12&apikey=KEY&callback=_jqjsp></script>
 
/* dados retornados pela API, ocultados como um parâmetro do script de callback simulado */ _jqjsp( { "total": 50, "movies": [ {...} ], ... } );

Este exemplo especifica os callbacks atuais para o tratamento dos dados de resposta da solicitação AJAX usando a interface Promise do jQuery. Solicitações AJAX baseadas no jQuery-based implementam a interface Promise para permitir que múltiplos callbacks possam ser encadeados à solicitação original. A interface Promise fornece ganchos especiais de callback para o tratamento dos diversos estados de compleitude — done() para tratar a resposta para uma solicitação com sucesso, fail() para tratar erros e always() para tratar a resposta independente do status. Note que a implementação AJAX padrão do jQuery falha silenciosamente quando usa JSONP, o que é a motivação pela qual usamos o plugin JSONP. A interface Promise também fornece um gancho de callback pipe(), que pode ser usado para processar os dados de resposta antes de passar o valor filtrado dos dados como parte de uma nova Promise para o callback subsequente na cadeia. Este exemplo registra um callback pipe para limpar a formatação dos dados, fazer um cache de dados para consultas futuras, e retornar uma matriz formatada de movies baseada em uma porção dos dados originais. Ele então registra um callback done para gerar a interface gráfica apropriada para mostrar os dados do pipe (ver a próxima seção).

 
$.jsonp( /* enviar uma solicitaçao ajax */ ) .pipe(function(data) { /* limpar os dados de uma fonte remota para aplicar o formato de dados específico do app */ cleanupData(data); /* fazer o cache dos resultados para consultas futuras */ cachedData[page] = data.movies; /* retornar os dados de filmes */ return data.movies; }) .done(function(data) { /* gerar a UI para apresentar os dados processados */ });

Note que o encadeamento de Promise através do pipe possibilita modularidade. No código real, os dois callbacks residem em funções de dois componentes diferentes, dataSource.getMovies()viewController.createPrimaryPage(), para criar uma separação clara das preocupações entre gerenciamento de dados e gerenciamento de interface de usuário (UI).

Gerando um Listview Dinamicamente

Este exemplo utiliza uma listview do jQuery Mobile para apresentar a matriz de filmes retornada pelo callback the pipe acima. Ele define um modelo em HTML para o listview, delineando os vários components do listview baseado no formato esperado dos dados JSON. Ele utiliza a sintaxe de modelo Mustache para interagir com a matriz JSON e puxar todos os campos relevantes de cada elemento da matriz. Uma tag Mustache é delimitada por colchetes duplos ( { { } } ), e é dinamicamente substituída por um valor baseado nos dados JSON disponibilizados. Uma seção Mustache começa com um prefixo de cerquilha (#) e acaba com o prefixo de barra de divisão (/), e delinea um modelo que pode ser replicado para cada item na matriz JSON nomeada; matrizes não nomeadas são referenciadas como ponto (.). O modelo é embutido dentro de um código principal HTML como um script com tag desconhecida "text/html". Isto permite que o modelo seja formatado e mantido com o resto do código HTML, enquanto permanece invisível ao runtime do HTML5 e acessível ao código JavaScript.

 
<!-- modelo de listview (oculto na tag de tipo desconhecido) --> <script type="text/html" id="primaryPageContentTmpl"> <ul data-role="listview" class="movies"> <!-- seção do mustache --> {{#.}} <!-- modelo para cada item em um dado conjunto de dados json --> <li><a href="#secondaryPage" class="movie" data-index={{index}}> <!-- imagem thumbnail do filme --> <img src={{posters.thumbnail}} /> <!-- título do filme --> <h3>{{title}}</h3> <!-- avaliação dos críticos do Rotten Tomatoes, númerico com ícone correspondente --> <p> TOMATOMETER<sup>&reg;</sup> {{ratings.critics_score}} <i class="icon tiny {{&ratings.critics_rating}}"></i> </p> </a></li> {{/.}} </ul> </script>

O callback done da solicitação AJAX copia o modelo do DOM, avisa a biblioteca Mustache para renderizar o modelo com os dados recebidos, e insere o listview renderizado dentro do DOM. Então, ele chama o método listview() para informar ao jQuery Mobile a existência do listview, para que o widget do listview possa ser inicializado com os estilos e comportamentos apropriados do jQuery Mobile.

 
.done(function(data) { /* obter o modelo para criar o listview */ var tmpl = $('#primaryPageContentTmpl').html(); /* renderizar o modelo do listview com os dados */ $(Mustache.render(tmpl, data)) /* inserir na página do DOM */ .prependTo($page.find(':jqmData(role=content)')) /* avisar o jQuery Mobile para processar o widget listview recém inserido */ .listview(); });

Gerando Dinamicamente as Páginas Primárias

Este exemplo é composto por diversas páginas "primárias", cada uma contendo um listview de seus filmes correspondentes, e vinculadas a uma página anterior e uma posterior através de botões navbar no seu rodapé. (Estas páginas foram chamadas de "primárias" porque elas estão no primeiro nível de navegação do app.) No seu início, este exemplo consulta a API Rotten Tomatoes pelo número total de filmes para calcular o número de páginas primárias. Então ele cria o esqueleto para esta quantidade de páginas primárias baseados no modelo HTML pré definido.

 
<!-- modelo para o esqueleto de página primária --> <script type="text/html" id="primaryPageSkeletonTmpl"> <div data-role="page" class="primaryPage"> <!-- cabeçalho --> <div data-role="header" data-id="app-header"> : </div> <!-- conteúdo --> <!-- para conter o listview criado dinamicamente com dados da API web --> <div data-role="content"> </div> <!-- rodapé --> <!-- navbar para vincular às páginas de filmes anteriores e posteriores --> <div data-role="footer" data-theme="a"> <div data-role="navbar"> <ul> <li><a href="#" class="prev">Previous</a></li> <li><a href="#" class="next">Next</a></li> </ul> </div> </div> </div> </script>

Para cada página, o modelo precisa ser personalizado com o número da página como o id e o atributo de navegação data-url do jQuery Mobile, bem como com os links corretos para as páginas anterior e posterior. O botão de navegação prévia (próxima) para a primeira (última) páginas é desabilitado aplicando a classe ui-disabled do jQuery Mobile.

 
/* cria um esqueleto de página em html de um nó do DOM */ var $page = $($('#primaryPageSkeletonTmpl').html()) .attr('id', pagenum) .attr('data-url', '#' + pagenum); /* link para a página anterior (se existir) */ if (pagenum == 1) { $page.find('.prev').addClass('ui-disabled'); } else { $page.find('.prev').attr('href', '#' + (pagenum - 1)); } /* link para a próxima página (se existir) */ if (pagenum == N) { $page.find('.next').addClass('ui-disabled'); } else { $page.find('.next').attr('href', '#' + (pagenum + 1)) }

Este exemplo cria um objeto jQuery, $pages, para armazenar todos os esqueletos de páginas recém criados, para que els possam ser inseridos no DOM todos de uma só vez. No geral, é melhor minimizar o número de inserção de nós no DOM para obter um desempenho melhor. Cada inserção usualmente dispara um caro reflow, no qual o runtime web recalcula o posicionamento de todos os elementos afetados, e renderiza novamente parte ou toda a aplicação. As novas páginas são inseridas antes das páginas existentes, de forma que a primeira página primária se torne a página mais alta no documento, mas seja reconhecida pelo jQuery Mobile como a primeira página a ser carregada pelo app.

 
/* cria páginas e insere no DOM em bloco */ var $pages = $(); for (var pagenum = 1; pagenum <= N; pagenum++) { $pages = $pages.add( /* created $page (see above) */ ); } $pages.insertBefore( /* primeira das páginas existentes */ );

Este exemplo também registra um handler para evento na carga inicial de todas as páginas primárias, para que o conteúdo do listview para cada página seja gerado sob demanda.

 
/* criar as páginas sob demanda */ $(document).on('pageinit', '.primaryPage', /* generate listview content */);

Finalmente, $.mobile.initializePage() é chamado para informar ao jQuery Mobile para que ele que processe todas as páginas e carregue a primeira página.

 
/* avisa ao jQuery Mobile para processar as páginas */ $.mobile.initializePage();

Gerando Dinamicamente as Páginas de Detalhes Secundários

Selecionar um filme na primeira página faz com que esta aplicação de exemplo navegue até uma página de detalhes "secundária", que contém informações adicionais sobre aquele filme. Cada página de detalhes secondários é criada sob demanda. Existe apenas uma página de detalhes secundários no DOM durante todo o tempo. O esqueleto da página secundária é definido no código HTML inicial. Cada solicitação pode detalhes de um filme fará com que o conteúdo e o título daquela página sejam complementados dinamicamente.

 
<!-- uma cópia do esqueleto da página secundária, conteúdo e título serão preenchidossob demanda --> <div data-role="page" id="secondaryPage"> : </div>

Para permitir a criação de páginas sob demanda, este exemplo ouve todos os eventos de clique em um item filnho filme (<li>) de cada listview (class = "movies") de cada página primária. Utilizando um seletor hierárquico do jQuery, este exemplo pode obter o índice do filme selecionado em relação ao listview de seu pai, que poden então ser usaado com o número da página atual para identificar o filme.

 
/* cria uma página secundária sob demanda */ $(document).on('click', '.movies > li', createSecondaryPage);

O conteúdo da página secundária é dinamicamente criado usando um modelo HTML baseado no Mustache, renderizado com os dados do cache da solicitação inicial que gerou a página primária correspondente.

 
<!-- modelo para o conteúdo das páginas secundárias --> <script type="text/html" id="secondaryPageContentTmpl"> : </script>
 
/* atualizar a página secundária (sobrescrever os detalhes do conteúdo com  detalhes de um filme específico */ createSecondaryPage() { /* obter a página atual */ var pagenum = $.mobile.activePage.attr('id'); /* obter os dados detalhados do cache para o filme selecionado */ var data = getMovie(pagenum, $(this).index()); $('#secondaryPage') /* atualizar a barra de ferramentas de cabeçalho com o título do filme */ .find('#title').html(data.title).end() /* renderizar os modelo dos detalhes do filme com os dados e inseri-lo  dentro do DOM como conteúdo */ .find(':jqmData(role=content)') .html($(Mustache.render($('#secondaryPageContentTmpl').html(), data))); }

Atualizando os Dados

Este exemplo fornece um botão de atualização no topo das páginas primárias para forçar o app a solicitar os dados mais atuais à API web da Rotten Tomatoes. A funcionalidade de atualização essencialmente limpa o conteúdo de todas as páginas primárias, limpa o cache de dados e recarrega o app da primeira página primária. Todas as páginas primárias criadas anteriormente são rastreadas, como se fosse disparado manualmente o evento pageinit revisitando cada uma delas, para que os listviews correspondentes sejam dinamicamentes gerados novamente.Se o número total de páginas tiver sido alterado, páginas esqueleto adicionais poderão ser criadas e os botões de navegação de páginas afetados são atualizados.

Tela Inicial de Login

A primeira página mostrada neste exemplo é uma página de login para que seja inserida a sua chave de desenvolvedor para acessar a API web do Rotten Tomatoes. Se você quiser testar este app de exemplo, você pode se registrar para uma conta de usuário Mashery, e então solicitar uma chave. Este exemplo irá testar a validade da chave digitada, fazendo uma solicitação única à API do Rotten Tomatoes API e analisando o status da resposta. Com uma chave válida, o código fonte principal do evento inicia a criação de todos os esqueletos de páginas primárias e carrega a primeira página primária. Caso contrário, uma mensagem de erro é apresentada, voltando à página de login para que uma nova tentativa seja realizada. A página de login utiliza um formulário do  jQuery Mobile para solicitar a sua chave de desenvolvedor, e páginas de diálogo do  jQuery Mobile para mostrar mensages com informações.

Diversos

Para emular ou fazer a pré visualização deste exemplo usando o Intel HTML5 Development Environment, você precisa iniciar o Chrome com a flag --allow-running-insecure-content. Isto é causado pois a IDE é executada em um ambiente seguro com https, enquanto a API do Rotten Tomatoes suporta apenas tráfego http.


Dispositivos Testados:

  • Samsung Galaxy S* II smartphone (Google Android* 2.3.5, 480 x 800 pixels, hdpi)
  • Lava Xolo X900* smartphone (Android 2.3.7, 600 x 1024 pixels, hdpi)
  • Motorola Droid* Razr M smartphone (Android 4.0.4, 540 x 960 pixels, hdpi)
  • Asus Google Nexus* 7 tablet (Android 4.1.1; display: 7 inches, 1280 x 800 pixels, tvdpi)
  • Amazon Kindle Fire* 2 tablet (v.10.1.3 based on Android 4.0; display: 7 inches, 1024 x 600 pixels, mdpi)
  • Apple iPod Touch* 4th gen mobile device (Apple iOS* 4.3.1, 640 x 960 pixels, retina)
  • Apple iPod Touch 4th gen mobile device (iOS 6.0, 640 x 960 pixels, retina)
  • Apple iPad* 2 tablet (iOS 5.1.1, 1024 x 768 pixels, non-retina)
Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.