Na primeira parte deste artigo, começamos um passo-a-passo para desenvolver um app para Windows Phone 7 em HTML 5 usando o Phone Gap.
Criando a Aplicação Property Search
Seguindo os princípios do Unobtrusive JavaScript, onde as apresentações são separadas da lógica, movi a lógica do aplicativo para um arquivo propertySearch.js (acrescentando isso ao GapSourceDictionary.xml, naturalmente). O HTML tem o seguinte markup, uma forma simples que pode ser usada para introduzir a string para procura:
<h1 id="welcomeMsg">Find A Home</h1>
<form id="form" action="#">
<input type="text" id="searchText"/>
<input type="submit" id="searchButton" value="Go" />
<input type="button" id="getLocationButton" value="My Location"/>
</form>
Os handlers dos eventos estão ligados assim:
$(document).ready(function () {
document.addEventListener("deviceready", onDeviceReady, false);
});
// phonegap is initialised
function onDeviceReady() {
// verify that the phone is ready!
$("#welcomeMsg").append("...");
$("#form").submit(searchProperties);
$("#getLocationButton").click(getGeolocation);
}
Note que também adicionei jQuery ao projeto. O código que acrescenta uma elipse (…) ao título é uma indicação sutil que o evento deviceready foi disparado. Usando o emulador, descobri que o código JavaScript dentro da página frequentemente não estava sendo executado, e isso parece não ter nada a ver com o código JavaScript do PhoneGap. Felizmente, o código é sempre executado quando implantado em um dispositivo real.
A função search properties é um simples chamado AJAX às APIs do Nestoria, usando a função jQuery ajax(), que administra o processo JSONP de adição dinâmica de um scriptag ao DOM, que busca os dados requeridos.
var propertyTemplate = $("#propertyTemplate").template();
// searches for properties based on the current search text
function searchProperties() {
var query = "http://api.nestoria.co.uk/api?" +
"country=uk&pretty=1&action=search_listings&encoding=json&listing_type=buy" +
"&place_name=" + $("#searchText").val();
$.ajax({
dataType: "jsonp",
url: query,
success: function (result) {
var resultsContainer = $("#resultsContainer");
resultsContainer.empty();
$.each(result.response.listings, function (index, property) {
var $itemNode = $.tmpl(propertyTemplate, property).data("propertyData", property);
$itemNode.appendTo(resultsContainer);
});
}
});
return false;
}
NOTA: O aplicativo usa as APIs do Nestoria UK, de forma que se você estiver fazendo testes com seu emulador, configure sua locação para algum lugar no UK!
Estou usando um template do jQuery para renderizar cada item. Você pode aplicar renderizar uma array de itens via função tmpl(), contudo, aqui faço a renderização de cada um individualmente, para que possa associar os dados brutos do JSON para cada propriedade com o elemento gerado através da função data() do jQuery. Isso é como definir o DataContext para um Silverlight FrameworkElement.
script id="propertyTemplate" type="text/x-jquery-tmpl">
<li class="property" onClick="propertyClick(this); return false;">
<div class="propertyContainer">
<div class="thumbnailColumn">
<img src="${thumb_url}" class="thumbnail"/>
</div>
<div class="detailsColumn">
<div class="title">${title}</div>
<div class="price">${price_formatted}</div>
</div>
</div>
</li>
</script>
Quando uma propriedade é clicada, a função seguinte é chamada, locando o elemento DOM clicado, e, então, extrai os detalhes da propriedade do JSON via função data() descrita acima. Isso é usado juntamente com outro template para renderizar os detalhes das propriedades.
// handle clicks on properties
function propertyClick(propertyNode) {
// get the property
var property = $(propertyNode).data("propertyData");
// render the template
$("#detailsContainer").empty();
$.tmpl(propertyDetailsTemplate, property).appendTo("#detailsContainer");
// switch pages
showDetailsPage();
}
O template é mostrado aqui:
<script id="propertyDetailsTemplate" type="text/x-jquery-tmpl">
<div>
<h1>${price_formatted}</h1>
<img src="${img_url}" class="propertyImage"/>
<h2>${title}</h2>
<div class="bedrooms">bedrooms: ${bedroom_number}</div>
<div class="bathrooms">bathrooms: ${bathroom_number}</div>
<div class="summary">${summary}</div>
</div>
</script>
:
A navegação entre páginas é obtida tendo o markup para ambas as páginas presentes no DOM:
<body>
<div class="page searchPage">
...
</div>
<div class="page detailsPage">
...
</div>
</body>
As páginas são definidas para usar o posicionamento absoluto.
div.page
{
position: absolute;
width: 480px;
}
Podemos, então, mostrar/ esconder essas páginas simplesmente animando a sua propriedade “left” do CSS:
function showSearchPage() {
$(".detailsPage").animate({ left: 480 }, 300, function () {
$(this).hide();
});
$(".searchPage").show().animate({ left: 0 }, 300);
};
function showDetailsPage() {
$(".detailsPage").show().animate({ left: 0 }, 300);
$(".searchPage").animate({ left: -480 }, 300, function () {
$(this).hide();
});
};
Vale a pena notar que se o IE9 suportasse transições CSS3, o código acima poderia ter sido feito com o CSS, mas infelizmente, animações não se encontram entre as características com suporte. Não há nada de errado com as animações jQuery, elas têm uma sintaxe bastante simples e concisa. Contudo, a animação CSS3 dá ao browser a opção de usar a aceleração GPU, melhorando muito a performance. Os navegadores que usam webkit no Android e iOS suportam esta característica (usando o webkit prefix nas propriedades CSS requeridas).
A localização atual é identificada via navigator.geolocation.getCurrentPosition, usando as APIs do Bing Maps REST para geocodificar a partir de uma coordenada lat/long para um código postal:
// gets the current phone location
function getGeolocation() {
navigator.geolocation.getCurrentPosition(function (position) {
// geocode via Bing Maps
var apiKey = "Ai9-KNy6Al-r_ueyLuLXFYB_GlPl-c-_iYtu16byW86qBx9uGbsdJpwvrP4ZUdgD";
var query = "http://dev.virtualearth.net/REST/v1/Locations/" + position.coords.latitude +
"," + position.coords.longitude + "?jsonp=onGeocode&key=" + apiKey
$.ajax({
dataType: "jsonp",
url: query
});
});
}
// handle the geocode results
function onGeocode(result) {
// extract the 'outward' part of the postcode
var postalCode = result.resourceSets[0].resources[0].address.postalCode;
var codeSplit = postalCode.split(" ");
if (codeSplit.length > 0) {
$("#searchText").val(codeSplit[0]);
}
}
Metro-style
O estilo padrão para controles HTML não parecem tão bacanas, contudo, com um pouco de CSS, é possível recriar alguma coisa parecida com o estilo WP7 Metro. Copiei aqui algumas das propriedades presentes nos dicionários de recursos do Silverlight.
body, input, div
{
font-size: 22.667px; /* PhoneFontSizeMedium */
}
h2
{
font-weight: normal;
font-size: 32px; /* PhoneFontSizeLarge */
}
h1
{
font-weight: normal;
font-size: 42.667px; /* PhoneFontSizeExtraLarge */
}
input[type="button"], input[type="submit"]
{
background: black;
color: white;
border-color: white;
border-style: solid;
padding: 4px 10px;
border-width: 3px; /* PhoneBorderThickness */
font-size: 25.333px; /* PhoneFontSizeMediumLarge */
}
input[type="button"]:active, input[type="submit"]:active
{
background: white;
color: black;
}
input[type="text"]
{
width: 150px;
padding: 4px;
}
ProgressBar
Por diversão, pensei em criar um HTML equivalente ao ‘trailing dots’ da ProgressBar do WP7. Com um pouquinho de markup/ CSS:
<div class="progress">
<div class="pip"></div>
<div class="pip"></div>
<div class="pip"></div>
<div class="pip"></div>
<div class="pip"></div>
</div>
.progress div
{
width: 5px;
height: 5px;
overflow: hidden;
position: absolute;
background: green;
}
.progress
{
position: relative;
height:10px;
}
E algumas animações jQuery adicionais, desta vez usando o easing plugin:
function onDeviceReady() {
...
// create an animation loop for the progress bar;
startAnimation();
var tid = setInterval(startAnimation, 3500);
}
function startAnimation() {
var delay = 200;
$(".pip").each(function () {
animatePip($(this), delay);
delay += 200;
});
}
function animatePip(element, delay) {
element.css("left", 0)
.hide()
.delay(delay)
.show()
.animate({ left: 240 }, { duration: 1000, easing: "easeOutSine" })
.animate({ left: 480 }, { duration: 1000, easing: "easeInSine" });
}
Conseguimos algo que se aproxima do progressBar do WP7:
(Note que algumas vezes a animação fica meio maluca. Tente atualizar seu navegador para resetá-la!)
Novamente, meus comentários com respeito às animações CSS3 e aceleração GPU aplicam-se aqui também.
Morte de Processos
Gerenciar a morte de um processo é uma tarefa complicada para desenvolvedores de WP7. Quando seu aplicativo é fechado pelo sistema, devido ao consumo de memória do dispositivo, você pode salvar o estado do aplicativo e o estado da página – o framework guarda a situação de suas URIs. É de sua responsabilidade reiniciar o seu aplicativo no mesmo estado. E ao contrário do que alguns desenvolvedores com quem conversei pensam, o Mango não remove a necessidade de matar aplicativos quando há um estouro de memória. Ao invés disso, ele apenas sabe se virar melhor e faz com que isso aconteça menos, já que ele possui um estado suspenso em que aplicações ficam estacionadas quando não estão sendo utilizadas.
Então, como a morte de um processo funciona com o aplicativo Phone-Gap HTML5? Boa pergunta! A implantação da morte de um processo provavelmente requerirá alguma comunicação customizada fora das APIs do PhoneGap, o que permite ao aplicativo JavaScript fornecer seu estado atual ao aplicativo Silverlight que o hospeda. A respeito disso, tenho grande interesse em saber de alguém que tenha resolvido esse problema!
O Processo de Desenvolvimento
A tentativa de desenvolver aplicativos com o navegador no emulador WP7 não é um processo agradável. Qualquer erro JavaScript, inclusive alguns erros simples de análise, tipicamente resultam no HTML sendo renderizado, mas com os scripts ignorados. Uma abordagem muito melhor é rodar seu HTML/JavaScript em um browser, que lhe dá acesso às ferramentas usuais de desenvolvimento, como Firebug, ou à interna do Chrome/ ferramentas IE.
Contudo, para fazer isso, você precisa simular as APIs do PhoneGap. Para meu aplicativo, descobri que o esquema abaixo realiza o truque:
document.addEventListener = function (evt, handler, capture) {
$("body").bind(evt, handler);
};
$(document).ready(function () {
setTimeout(function () {
$("body").trigger("deviceready");
}, 100);
});
OK, sei que você está pensando, e o código de geolocalização? Onde está o mock para as APIs do PhoneGap? A resposta é que muitas APIs do PhoneGap são projetadas para combinar perfeitamente com a especificação HTML correspondente. Assim, por exemplo, API de geolocalização do PhoneGap é exatamente o mesmo das APIs de geolocalização do HTML5. Para telefones, como o WP7, em que o browser suporta esta característica HTML5, o PhoneGap não faz nada. Para telefones que não suportam, o PhoneGap fornece uma implementação (usando APIs nativas).
Se o mock do PhoneGap é tão simples, você deve estar se perguntando para que usar o PhoneGap? Bem, o PhoneGap ainda nos dá um mecanismo para empacotar arquivos em um arquivo XAP de forma que possam ser renderizados pelo browser.
Quão multiplataforma é esta abordagem?
Eu diria que esta abordagem é provavelmente a respeito de quão multiplataforma ela é, tanto quanto qualquer outro aplicativo baseado em browsers HTML5/ JavaScript. Sempre haverá diferenças de multiplataformas a serem suplantadas. Como teste, rodei este código em um iPod Touch sem qualquer modificação. Os resultados foram muito bons, embora haja alguns probleminhas visíveis.
Quão viáveis são aplicativos HTML5?
Está clara a crescente tendência em direção a aplicativos multiplataforma para HTML5, e a Microsoft parece estar dando suporte a este conceito (embora os aplicativos JavaScript / HTML5 Metro no Win8 não sejam multiplataforma!). A Microsoft trabalhou com o Nitobi para criar o PhoneGap para WP7, e também anunciou a conferência //build/, na qual a Microsoft estará trabalhando com jQuery Mobile para criar o tema Metro para seus controles mobile.
Atualmente, o PhoneGap para WP7 está em beta, e certamente precisa de polimento. Com certeza, isso melhorará com o tempo. A grande lista de aplicativos escritos com PhoneGap certamente indica que esta é uma solução viável para desenvolvimento de aplicativos.
Um obstáculo para os aplicativos WP7 com HTML5 é o IE9, que roda no telefone Mango. Ainda que tenha uma lista impressionante de características HTML5 suportadas, há algumas características/ problemas que não consigo resolver. Elas fazem com que fique óbvio a qualquer usuário que se trata um aplicativo do browser, estragando completamente a ‘ilusão’.
- user-scalable=no – esta configuração atualmente parece ser ignorada. Isso significa que o usuário pode fazer um pinch em seu aplicativo, o que simula uma web-page. UPDATE O leitor Roy assinalou que os parâmetros viewport deveriam ser separados por vírgulas. Isso quase resolve o problema. O usuário ainda pode fazer um pinch na view, o que faz com que haja zoom, mas quando o pinch termina, a viewport retorna à escala original. Não é perfeito, mas é melhor que nada.
- Parece não ter jeito de desligar o retângulo sombreado que aparece em cima dos links ao serem clicados. Com aplicativos que tem conteúdo dinâmico, isso pode parecer bastante ridículo, com um retângulo cinza permanecendo na tela enquanto o conteúdo da página muda por baixo. Nos browsers Android/ iPhone isso pode ser desligado no CSS via webkit-tap-highlight-color.
Esperamos que essas limitações sejam resolvidas. Quando isso acontecer, tenho certeza que o HTML5 será uma tecnologia viável para criar aplicativos de qualidade para telefones.
Você pode fazer o download do projeto aqui.
***
Texto original disponível em http://www.scottlogic.co.uk/blog/colin/2011/09/developing-windows-phone-7-html5-apps-with-phonegap/