JS Lazyload for Windows Mobile 6.5 widgets
It took me the better part of the afternoon, but I tamed it.
I am creating a widget that has to run on different devices. I implemented a lazy loader to loadmost of the javascript files and it worked on all my targets, except Windows Mobile 6.5. I wanted to be notified when all the files were loaded to proceed to my main app init code, but it was not working on the WM6.5 widget context. On the browser it worked, but when packaged as a widget it didn’t.
I thought I had hit a brick wall and had to resort to something (more) hackish, but in the end the perserverance paid off, with a little luck.
First, for some reason, the attachEvent method does not exist on a script element when running as a widget, although it exists on Mobile IE, which was why it worked.
Poking at a newly created script object, I noticed a onreadystatechange property. Experimenting with it on a live widget I managed to make it call my callback function once, but when I tried to put it to use on my code it didn’t.
After much debugging, in which Bluetooth File Transfer.app didn’t help (on 10.6.2 it crashes 80% of the time when transferring a file to a WM device), I figured it out.
I was creating the new script object, setting all the properties and then adding it to the DOM. This seemed to be a sensible solution, but it didn’t work in this case. Then I tried to add the script element to the DOM, immediately after its creation. Bingo! It worked. I only set the “src” property in the end, to prevent loading the script before I have the chance to set the onreadystatechange callback and everything else. I don’t know if this is really needed, but after wasting lots of time on this today I’ll wait until tomorrow to find out.
I don’t know if this code is still working on the other platforms, but I don’t see any reason not to. Then again, I better check.
Here is the relevant code. I’m posting it here in case some poor soul stumps on the same problem. “cb” is the callback function.
var script = document.createElement("script");
document.getElementsByTagName("head")[0].appendChild(script);
if (script.addEventListener) {
script.addEventListener("load", cb, false);
} else if (script.attachEvent) {
script.attachEvent("onreadystatechange", function() {
if (script.readyState == "loaded") {
cb();
}
});
} else {
script.onreadystatechange = function () {
cb();
};
}
script.type = "text/javascript";
script.src = name;
Mental note, I need some wordpress plugin for posting code.
CSS animations
Some days ago I decided to play with the animations using CSS3. Seemed a little weird at first, but is actually very easy.
No time for long (or short) explanations, please RTFM for that, but here is a little example: the devious <blink> tag. Old is new again, I guess…
For webkit only, replace those -webkits with -moz to see with a recent Firefox.
<style type="text/css">
.blink {
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-name: blink;
-webkit-animation-duration: .5s;
-webkit-animation-direction: alternate;
}
@-webkit-keyframes blink {
0% { opacity: 1; }
100% { opacity: .2; }
}
</style>
<p class="blink">Hello blinking world.</p>
iPhone
Já falei aqui do que aconteceu quando comprei um iPhone à Vodafone.
Não aceitei que o telefone fosse novamente reparado. Não fizeram nada relativamente aos defeitos que relatei quando o entreguei para a reparação, ainda veio com uns pequenos riscos, pelo que não tenho confiança que sendo entregue para nova reparação as coisas não piorem ainda mais.
Como já disse, os defeitos que o telefone tem são pequenos, mas são defeitos. Se quisesse tolerar defeitos, não teria pago o que custa um iPhone e tinha poupado dinheiro e comprava outra coisa qualquer.
Recorri à Deco, que foi inútil. Recorri ao CACCL, que me marcou uma audiência, onde fui. Duvidava que me resolvessem o caso, mas o “não” eu já tinha, não podia piorar…Fui. Perdi quase 2h à espera e 10 euros em taxis para cada lado.
Entrei na sala na expectativa do que poderia sair de lá, e demorei a atingir, mas assim que o douto juiz começou a falar veio-me logo cabeça as sábias aqui da Jonas assim que o viu sobre o seu caso com a Ensitel.
Falava de forma agressiva, como se estivesse irritado. Devia ser jurista, a representante da Vodafone também, pelo que me senti como carne para canhão. Os meus argumentos de que já havia sido reparado não serviam de nada: teria que ser reparado novamente e mais nada.
A representante da Vodafone disse que normalmente os telefones que vendem podem ser devolvidos/trocados nos 14 dias após a compra, excepto o iPhone (wtf?). Isto não está na lei, a lei não estabelece este tipo de prazos, a não ser em caso de avaria, mas como estratégia comercial a maior parte dos vendedores optam por dar um prazo adicional como aliciante, mas não são obrigadas a isso.
Qualquer réstia de esperança que eu tinha caiu por terra assim que o excelso senhor disse que era contra este tipo de coisas. Trocar era mau, mas devolver o dinheiro era coisa do demo, e era definitivamente contra uma pessoa “mudar de ideias”. Aí calei-me e deixei as coisas terminarem. Queria era sair dali e voltar ao trabalho que se fazia tarde. Ficou em acta a minha resposta “vou pensar nisso”, tal e qual, à sentença que deveria deixar reparar novamente o telefone.
Pensei na opção que me deram, e não me apetece ficar novamente sem telefone durante uma semana, ficar igual e arriscar a que fique com mais danos dos que já tem. Se ganhar algum dano, pelo menos que seja eu a colocá-lo.
Outra coisa que aprendi, foi a não comprar NADA sem que tenha 30 dias para devolver, ainda que o excelentíssimo senhor não goste ou que tenha que pagar mais por isso. Nem que compre à distância: aí a lei prevê um prazo de troca “no questions asked”.
Naquele dia a TVI estava fazer uma reportagem sobre o CCACL, e foi uma pena não terem filmado a nossa sessão (a Vodafone não autorizou). Gostava de ter ficado com um recuerdo.
H1N1 no metro
Agora que a paranoia da gripe A passou, será que o Metro de Lisboa pode substituir os paineis de informação por outros mais úteis?
A meu ver, há pelo menos 4 coisas que podiam ser aconselhadas aos utentes:
- Não passar o passe dentro da mala/carteira. As pessoas atrás de si agradecem por não ter que esperar enquanto está ali freneticamente a esfregar a mala no sensor até que seja reconhecido.
- Não é preciso esperar que a porta feche para passar o seu passe. A porta mantém-se aberta na maior parte das vezes.
- Ao entrar na carruagem, deixe espaço para que quem quer sair.
- Nas escadas rolantes, se preferir ficar quieto, encoste-se a um dos lados (direito, normalmente), para não ficar no caminho de quem quiser subir os degraus.
kthxbai.
On gay marriage
Gay marriage being one of the hot topics here in Portugal at the moment, I decided to write something on the matter.
I don’t care if two people that decide to marry have the same sex. I really don’t – it’s their life and I have no right to meddle with it. Besides, having gay marriage available for those who want it does not force me to have one myself.
And that thing about allowing something like a “marriage”, but not called as such is ridiculous. If it’s the same thing as a marriage as its proponents say, then why invent new words?
On adoption by these couples, I have some slight doubts, but I’m pretty sure it’s harmless. At least, should be on a par with every other hetero couple out there, as good or as bad as it can get. There are kids living their whole youth in institutions, and I can’t imagine it being better than having 2 dads or 2 moms who love them. Oh, the horror!
Just my 2c.
Camping for geeks
Like all good things, this year’s Codebits has come to an end. Some people in denial still hang to their wristband, but I took mine off when I got home as it was a starting to become too tight.
Loved it again, and what can I say? It’s hard not to like an event such as this that touches all the soft spots in a geek.
I won one of the prizes with my own crazy project, lent a hand in another that also won, did my first overnighter in years, got a mild cold and got sick of pizza again, though in a few days I’m sure I’ll be hitting some pizza joint when the time for lunch comes.
Here is an event, where people will not leave for most of day (if they leave at all), where they’ll be doing exactly the same things as they do in their working hours of the rest of the year, often with the very same people. I can’t speak for everyone, but I think we’re very fortunate to be working on something we love so much.
Now let’s archive this year’s project, mark one line of the Taskpaper Ideas file as “done”, and patiently wait for Codebits X (MMX?).
Meanwhile, the project number tag still glued to my laptop’s back can remain there…
Demoscene at Codebits 2009
Last year at Codebits we had a great demo project by my fellow teammate Bitcoder, called Nagalhães. This year we’ll have a presentation by PS about the demoscene.
In spirit, Codebits has always been a demo party to me, so these little pieces of demoscene creeping into it seem very appropriate.
Now, where did I put those demos I downloaded from the Starport BBS?
Codebits Challenge 4
Já acabou o quiz 4 do Codebits, e poucos quizzes me deram tanto gozo fazer como este, pelo que acho que seria interesante fazer um post sobre o assunto. Esta foi a minha participação, mas incorpora várias sugestões de colegas do trabalho que estão perto de mim e que felizmente percebem mais disto que eu. Kudos!
A ideia era simples: implementar uma função em Javascript para listar os programas que dão num canal com um nome que contém uma determinada substring, menores que um determinado número de minutos. Isto no menor número de caracteres possível. Não vou documentar o processo todo, que o quiz foi há duas semanas e já não me lembro de tudo, mas deve dar para perceber o que se tem que fazer neste casos.
Vendo a página de serviços do SAPO, vemos que temos que aceder a dois serviços. Um para determinar a lista de canais para saber a sigla dos que tenham no nome a substring desejada, e outro para pedir a programação destes canais para o dia desejado. A filtragem pela duração será feita localmente.
Aqui temos o primeiro problema: aceder aos serviços do SAPO a partir do HTML do Codebits. Como estão em dois domínios diferentes, não podemos usar as técnicas de Ajax normais, já que se viola a regra de que um site apenas pode aceder ao mesmo domínio (e subdomínios). É uma restrição que faz sentido, mas quse pode contornar facimente com o script tag hack, em escrevemos para o header da página uma nova tag de SCRIPT em que o src é o URL do pedido que pretendemos fazer. Em Firefox e Safari, podemos despoletar um evento que ocorre quando termina o carregamento dos dados. Uma função deste tipo pode ser a seguinte:
function loadJSON(url, callback) {
var tag = document.createElement("script");
tag.src = url;
tag.type="text/javascript";
tag.onload = callback;
document.getElementsByTagName("head")[0].appendChild(tag);
}
Duas notas sobre os pedidos ao serviço: vamos pedir os dados em JSON, que é mais fácil de processar que XML, e vamos pedir que os dados sejam atribuídos a uma variável em javascript (com o parâmetro jsontag), de modo a ser mais fácil o acesso.
Nesta função, o parâmetro callback é invocado quando termina o carregamento do JSON, tal como o evento onload normal. Podemos passar uma função anónima neste parâmetro.
Para o primeiro request, e usando a função anterior, podemos fazer algo assim:
loadJSON("http://services.sapo.pt/EPG/GetChannelListjson?jsontag=Canais", function() {
// init com array vazio
var chn = [];
// caminho para os canais (Canais é o nome da varável que o JSON devolvido no request nos criou)
var chns = Canais.GetChannelListResponse.GetChannelListResult.Channel;
// iterar sobre os canais, acrescentando ao array os que tenham "TV" no nome.
for (i in chns) {
// usei uma RegExp, mas podiamos usar simplesmente um indexOf
var r = new RegExp("TV", "i");
if (r.test(chns[i].Name))
chn.push(escape(chns[i].Sigla));
}
// terminado! aqui já temos na variável "chn" um array com as siglas dos canais que tenham "p1" no nome.
// parte 2 aqui!!!
});
Para o segundo request, as coisas são parecidas. Fazer request (passando as siglas dos canais obtidos no passo anterior) e para cada canal ver todos os programas que tenham menos que X minutos. Ordenar pela hora de início, e mostrar or resultado.
// ...
loadJSON("http://services.sapo.pt/EPG/GetChannelListByDateIntervaljson?channelSiglas="+chn.join(",")+"&startDate="+p2+"+00%3A00%3A00&endDate=01-01-2001+23%3A59%3A59&jsontag=Programacao", function() {
// inicializar a vazio o array com os programas
var progs = [];
// caminho para os canais
var chns = Programacao.GetChannelListByDateIntervalResponse.GetChannelListByDateIntervalResult.Channel;
// para cada canal...
for (j in chns) {
// ...e para cada programa deste canal
var prog = chns[j].Programs.Program;
for (i in prog) {
p = prog[i];
// se duracao menor que 31 minutos (a duração obtida vem em segundos)
if (p.Duration < 31 * 60) {
progs.push(p);
}
}
}
// ordenar os vários programas por hora de início (sort() recebe um parâmetro que é o comparador para determinar a ordenação entre 2 elementos)
progs.sort(function(a, b) {
return a.StartTime < b.StartTime ? -1 : 1;
});
// mostrar os programas, invocado a função o() fornecida.
for (i in progs) {
p = progs[i];
o(p.ChannelName + " at " + p.StartTime + ": " + p.Title + " (" + (p.Duration/60) + ")");
}
});
// ...
Como podemos ver, não é complicado. É moroso, talvez, especialmente para quem tenha pouca experiência em Javascript, mas é tudo uma questão de tentar.
Este quiz foi para mim como uma montanha russa. Até aqui estivemos a subir, a partir daqui é que vem a parte gira.
Este código que aqui meti foi uma versão inicial. Já com algumas optimizações que não consegui evitar fazer, mas ainda há muito a encurtar. Um conselho: nem todas as optimizações originam código menor. É preciso medir bem a alteração que se faz. Coisas como eficiência, validação de código, legibilidade, boas práticas: esqueçam tudo. Um piloto da Red Bull Air Race não se chateia de não ter refeições a bordo do avião.
Há muitas micro-optimizações que podemos fazer, mas posso enumerar o princípio:
- Esqueçam packers/minifiers de Javascript: aqui funcionam mal, porque a escala é muito menor do habitual, e perceberão melhor o código se fizerem tudo à mão.
- Omitir tudo o que não for mesmo necessário. Quebras de linha, pontos e vírgula, “var” na declaração de uma variável, espaços a mais nas atribuições, comparações, etc.
- Inicialização de variáveis, só mesmo se imprescindível (como no caso dos arrays)
- Nomes longos de variáveis e funções, como o “loadJSON”? Um luxo impensável: aqui pode ser ficar apenas como a função “J”. Têm 26*2 variáveis/funções possíveis, são suficientes
- Usar defaults. A tag de script tem por omissão o type “text/javacsript”. Apagar a linha em que se faz “src.type =”.
- Façam a composição de nomes longos com fragmentos.
Para este último ponto é melhor explicar em detalhe. Por exemplo, os dois serviços começam por http://services.sapo.pt/.
var u = "http://services.sapo.pt/"; var a = u + "EPG/GetChannelListjson..."; var b = u + "EPG/GetChannelListByDateIntervaljson...";
Podemos ir mais além, uma vez que vemos que continam a ter partes em comum.
var u = "http://services.sapo.pt/EPG/GetChannelList"; var a = u + "json..."; var b = u + "ByDateIntervaljson...";
Nada mau. Outra coisa que temos ainda em tamanho XXL são os caminhos para as propriedades do resultado do pedido JSON.
var chns = Programacao.GetChannelListByDateIntervalResponse.GetChannelListByDateIntervalResult.Channel;
Isto pode ser muito reduzido. Primeiro, em vez de fazer jsontag=Programacao, fazemos jsontag=B. Depois, em Javascript é igual usar a sintaxe de array ([]) para aceder aos membros de um objecto, pelo que esta linha é equivalente:
var chns = B["GetChannelListByDateIntervalResponse"]["GetChannelListByDateIntervalResult"]["Channel"];
Isto é útil porque agora temos strings nos índices. O que que significa que tal como para o URL dos serviçøs, podemos fazer algo assim:
var g = "GetChannelListByDateInterval"; var chns = B[g + "Response"][g + "Result"]["Channel"];
Muito melhor. Mas o Response/Result é usado noutro local no código, ainda podemos fazer melhor.
var g = "GetChannelListByDateInterval"; var u = "Response"; var v = "Result" var chns = B[g + u][g + v]["Channel"];
Mas o “Channel” também é usado noutros sítios…
var c = "Channel"; var g = "Get" + c + "ListByDateInterval"; var u = "Response"; var v = "Result" var chns = B[g + u][g + v][c];
Esta última optimização já não é muito útil. Que com a nova variáveil e a concatenação acabamos quase por ter o mesmo número de caracteres, mas aqui é só um exemplo e sabemos que o “Channel” é usado noutros sítios, pelo que temos lucro. Estes exemplos também são escritos com vista a serem claros enquanto explico. No meu código, seria algo como isto:
c="Channel",g="Get"+c+"ListByDateInterval",u="Response",v=" Result",chns=B[g+u][g+v][c];
Muito mais denso. Tendo todo o código podemos ver o que podemos reutilizar e sempre se poupam uns caracteres. Chega a uma altura em que é muito difícil espremer mais, mas tal como uma bisnaga de uma pasta de dentes, há sempre um pouquinho mais.
O serviço de EPG devolve os programas que comecem até 20 minutos antes da hora especificada, pelo que isto causava problemas de ter programas do dia anterior. Em vez de meter no código uma condição para lidar com a situação, faço o segundo pedido para programas que comecem às 00:20 do dia. Problema resolvido no mesmo número de caracteres.
Algures no tempo tive a participação mais curta, mas depois perdi o comboio e não consegui apanhá-lo mais. Tentei a técnica dos caracteres “chineses” que acabou por ganhar, mas uns bugs no meu encode/decoder não me deixaram avançar. A ideia em si é simples: o form do quiz contabilizava caracteres, e não bytes. Se um caracter UTF-8 é multi-byte, podemos empacotar vários (acho que no máximo 4) caracteres ASCII (8 bits) no mesmo caracter UTF-8. Há sempre o overhead de um decoder, mas como se viu é irrisório comparado com o que se poupa no empactamento.
Sem mais delongas, esta foi uma das minhas últimas versões. Esta tem 677 caracteres, mas a final acho que tinha menos, mas já desapareceu da intranet do Codebits. Oops.. É o que dá confiar na cloud.
function epg(a,b,d){T="StartTime",E="Channel",D="Duration",L="Get"+E+"List",G=L+"ByDateInterval",O=document,R="Response",U="Result",Z="json?jsontag="
function J(u,k){s=O.createElement("script");s.src="/services.sapo.pt/EPG/"+u;s.onload=k;O.body.appendChild(s)}J(L+Z+"A",function(){c="";z=A[L+R][L+U][E]
for(i in z)with(z[i])if(Name.match(a))c+=Sigla+","
J(G+Z+"B&channelSiglas="+c+"&startDate="+b+"+00:20:00&endDate="+b+"+23:59:59",function(){m=[];c=B[G+R][G+U][E]
for(j in c){g=c[j].Programs.Program;for(i in g)if(g[i][D]<d*60)m.push(g[i])}for(i in m.sort(function(a,b){return a[T]<b[T]?-1:1})){p=m[i];o(p[E+"Name"]+" at "+p[T]+": "+p.Title+" ("+(p[D]/60)+" minutes)\n")}})})}
Só garanto que funcione esta última versão. Como as outras foram quase refeitas enquanto escrevia o post, pode-me ter escapado algo, mas não interessa muito, desde que percebam o funcionamento.
Enjoy!
Chrome OS
A Google anunciou o Chrome OS, um sistema operativo simples apenas para aceder à web.
A ideia agrada-me. Como dizem num dos vídeos, em alguns casos as pessoas apenas usam o browser, e se não tiverem internet nem sequer ligam o computador. Isto é o padrão de uso de muitas pessoas que conheço. Única excepção: Office, mas porque não conhecem o Google Docs, porque o que fazem seria perfeitamente comportável pelo Google Docs. Outra excepção: anti-vírus, anti-spyware, firewall e os spyware e worms, mas aqui ficamos a ganhar.
A ideia agrada-me. É o computador como electrodoméstico a alimentar-se apenas da web. Zero de manutenção, muito pouco de configuração. O pouco que se destina a fazer, deve fazê-lo bem. E pelo facto de correr Webkit, é um grande win. Tudo o que tire market share ao IE é positivo, mesmo que seja belzebu a lançar um browser novo.
Para quem quer correr a tralha toda habitual, procure noutro lado. Não é para isso que se destina este OS. As pessoas não têm pouca necessidade de apps locais se as versões web funcionarem bem. Ver o caso do email: conheço pessoas que mesmo conhecendo clientes de email locais, preferem o interface do GMail. Idem para leitores de RSS. Com o Youtube deixou de ser tão necessário ter player locais, mais a confusão de codecs, para ver um vídeo recebido por email. A vida das pessoas que não vivem para os computadores centra-se na web há muito tempo, e este OS posiciona-se para ser o motor de um segundo computador. Para muitos funcioará bem também como computador primário. Que interessa ter as fotos catalogadas no computador, com backups, se servem apenas para alimentar o Facebook?
A questão de ter o Google a lançar isto é também positiva. É uma marca forte, em que as pessoas confiam e de que gostam. Ao contrário do que tentam fazer Gnome e KDE, não tentam imitar o Windows ou o OSX, evitando o problema do uncanny valley.
Penso que a Microsoft tem motivos para ter medo. A Apple tem alguns, mas menos: posicionam-se num segmento de mercado mais alto, além de já terem algo semelhante (o iPhone OS). Será que a haver resposta da Apple, será o afamado iTablet?
Me likes.
Administração local e o email, essa nova tecnologia
Há duas semanas enviei um email para a Junta de Freguesia de Sacavém um email sobre a recolha de lixos no sítio onde vivo. Enviei o mesmo email para o SMAS de Loures, assim como para o todos os sítios que me pareceram estar relacionadas com o assunto.
Basicamente, o lixo está a ser recolhido poucas vezes, o que deixa os contentores e ecopontos muitas vezes a transbordar. Já de si é mau, mas piora quando há vento e espalha algum do lixo pelas redondezas. Há umas semanas, a equipa de jardinagem que lá andava a tratar das zonas verdes apanhou todo o lixo espalhado e depositou-o perto dos contentores. Kudos para os jardineiros, mas parece que este trabalho foi em vão porque passaram semanas e o lixo continua no mesmo monte, no chão. O lixo mais leve já se voltou a espalhar, e se não for recolhido em breve voltará tudo à estaca zero.
Só hoje, 2 semanas depois de os ter enviado, tive algum feedback, e foi apenas um forward do email enviado para a JF para o SMAS. Do SMAS e da câmara, nickles!
Como aconteceu noutras situações em que tive que contactas com estas entidades, quando a resposta vier daqui a 2 meses, será num bonito documento de Word em anexo no email, embrulhado em legalês, indecifrável sem intérprete.
Entretanto, o problema deixará de existir até que os jardineiros lá voltem para repetir o trabalho.
Se calhar isto faz parte do plano de Natal. Nada como passar o Natal e ver os plátanos, palmeiras e outras árvores alegremente enfeitados com fraldas usadas, frascos de detergente, iogurtes com bifidus e espinhas de peixe.