<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>log</title>
	<atom:link href="http://log.pt/feed/" rel="self" type="application/rss+xml" />
	<link>http://log.pt</link>
	<description>smart software development. great user experience</description>
	<lastBuildDate>Mon, 25 Feb 2013 15:52:48 +0000</lastBuildDate>
	<language>pt-PT</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Focus Group aplicado a um portal económico</title>
		<link>http://log.pt/blog/2013/02/focus-group-portal-web/</link>
		<comments>http://log.pt/blog/2013/02/focus-group-portal-web/#comments</comments>
		<pubDate>Mon, 25 Feb 2013 13:06:24 +0000</pubDate>
		<dc:creator>Ux Team</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[Focus Groups]]></category>
		<category><![CDATA[Usabilidade]]></category>
		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://log.pt/?p=21561</guid>
		<description><![CDATA[Este artigo é uma partilha de um projeto em Usabilidade para validação de um portal económico web desenvolvido pela equipa do nosso cliente. Para a realização deste projeto a log realizou um Focus Group. Vantagens e alguns cuidados a ter na realização de um Focus Group A atividade seleccionada foi o Focus Group. Um Focus [...]]]></description>
				<content:encoded><![CDATA[<p>Este artigo é uma partilha de um projeto em Usabilidade para validação de um portal económico web desenvolvido pela equipa do nosso cliente. Para a realização deste projeto a log realizou um Focus Group.</p>
<h3>Vantagens e alguns cuidados a ter na realização de um Focus Group</h3>
<p>A atividade seleccionada foi o <b>Focus Group</b>.</p>
<p>Um Focus Group (FG) é uma reunião informal de utilizadores em que são pedidas as suas opiniões sobre um tópico específico. O objetivo é saber quais as <strong>perceções</strong>, <strong>sentimentos</strong>, <strong>atitudes</strong> e <strong>ideias</strong> dos participantes sobre esse tópico.</p>
<p>Os FG foram concebidos para obter as opiniões das pessoas e não para determinar a força da sua opinião. Os resultados obtidos não são quantitativos nem representativos da população alvo. Estes resultados podem ser usados para fazer a geração de hipóteses para futuras avaliações usando métodos quantitativos e qualitativos (criação de inquéritos ou entrevistas).</p>
<p>Os FG são úteis para identificar os vários pontos de vista existentes, bem como para permitir que os participantes aprendam uns com os outros gerando um sentimento de coesão social. Algumas das <strong>vantagens</strong> associadas são:</p>
<ul>
<li><i>Feedback </i>rápido por parte dos utilizadores;</li>
<li>A equipa de desenvolvimento fica com uma ideia das prioridades dos utilizadores;</li>
<li>Pode surgir a possibilidade de características em que ninguém havia pensado e que até façam sentido.</li>
</ul>
<p><strong>No entanto:</strong></p>
<ul>
<li>Os participantes podem influenciar-se mutuamente, caindo a discussão no comum pensamento de grupo (<i>GroupThink</i>). Uma tendência social para concordar com a opinião mais popular do grupo, em vez de manter a sua verdadeira posição; e</li>
<li>Falar sobre algo não é o mesmo que fazer ou usar.</li>
</ul>
<p>Aconselhamos a leitura do <a href="http://www.globalhealthcommunication.org/tools/60" target="_blank">Handbook for Excellence in Focus Group Research</a>. Um livro prático e explicativo de como preparar e aplicar esta técnica.</p>
<h3>O Projeto</h3>
<h4>Objetivos do Focus Group</h4>
<p>Foram estipulados três objetivos para este FG:</p>
<ol start="1">
<li>Obter e analisar as opiniões sobre questões de usabilidade, funcionalidade e nomenclatura do Portal;</li>
<li>Obter e analisar as opiniões sobre conteúdos de algumas áreas do Portal;</li>
<li>Obter e analisar as primeiras impressões após a apresentação do Portal.</li>
</ol>
<h4>Equipa log e participantes</h4>
<p>A equipa que preparou e realizou o FG era composta por dois moderadores e um observador.</p>
<p>O grupo deste FG era de dimensão mini, composto por 6 participantes, com idades variáveis e habilitações académicas e profissionais variadas, caracterizando este grupo como <strong>heterogéneo</strong>. O grupo não estava a par do projecto que deu origem ao Portal, mas todos os participantes sabiam que iam para um FG.</p>
<p>O Focus Group foi realizado numa sala preparada para o efeito, com o material de apresentação e registo audio-visual, paredes disponíveis para atividades no decorrer do FG e uma mesa comprida onde todos os participantes conseguiam ter contacto visual entre si.</p>
<h4>A sessão de Focus Group</h4>
<p>De entre as abordagens possíveis, a metodologia de moderação selecionada para o FG em questão foi baseada na <strong>técnica de questionário</strong> que se caracteriza por <strong>questões abertas</strong>. Desta forma, permite-se a honestidade dos sentimentos que emergem, minimizando a influência do moderador e ajudando a eliminar confusões sobre o que foi dito.</p>
<p>A log optou por um <strong>FG estruturado</strong>, com um estilo de <strong>perguntas não-diretivas</strong>, embora com um <strong>guia de moderação previamente definido e estruturado</strong>.</p>
<p>A esta combinação designa-se por <span style="text-decoration: underline;"><strong>Método da Discussão de Grupo Focada</strong></span>.</p>
<p>O livro <a title="Focus Group :: A Pratical Guide for Applied Research" href="http://www.amazon.co.uk/gp/product/0761920714" target="_blank">Focus Group :: A Pratical Guide for Applied Research</a> é uma boa fonte de informação no momento de planear as dinâmicas e as questões a aplicar. Aconselhamos a sua leitura para um aprofundamento deste tema.</p>
<h4>Observação e Registo</h4>
<p>Estes passos estavam a cargo do observador que ia registando os resultados numa <strong>check list</strong> preparada previamente para o efeito.</p>
<p>O FG foi também <strong>filmado</strong> para posterior facilitação do trabalho de análise, com autorização expressa dos seus participantes.</p>
<h3>Resultados</h3>
<p>Os resultados do FG foram apresentados num <strong>relatório escrito</strong>.</p>
<p>Ao longo do relatório transcreveram-se algumas <strong>citações</strong> que descreviam bem as respostas dos participantes e que, no seu discurso direto, poderiam ter mais impacto na percepção do leitor relativamente à opinião, sentimentos ou percepções dos participantes.</p>
<p>Os resultados foram também acompanhados de <strong>fotografias das dinâmicas</strong> realizadas e dos seus outputs.</p>
<p style="text-align: center;"><a href="http://log.pt/blog/2013/02/focus-group-portal-web/attachment/2/" rel="attachment wp-att-21601"><img class="size-large wp-image-21601 aligncenter" title="Resultados Focus Group Portal Economico" alt="Focus Group Portal Economico" src="http://log.pt/wp-content/uploads/2013/02/2-600x398.png?4c9b33" width="600" height="398" /></a> <a href="http://log.pt/blog/2013/02/focus-group-portal-web/attachment/3/" rel="attachment wp-att-21611"><img class="size-large wp-image-21611 aligncenter" title="Resultados Focus Group Portal Economico" alt="Focus Group Portal Economico" src="http://log.pt/wp-content/uploads/2013/02/3-600x287.png?4c9b33" width="600" height="287" /></a></p>
<p><strong>Exemplo</strong> de resultado obtido numa das dinâmicas realizadas;</p>
<p style="text-align: center;"><a href="http://log.pt/blog/2013/02/focus-group-portal-web/attachment/screen-shot-2013-02-25-at-12-46-14-pm/" rel="attachment wp-att-21972"><img class="aligncenter  wp-image-21972" title="Resultados Focus Group Portal Economico" alt="Focus Group Portal Economico" src="http://log.pt/wp-content/uploads/2013/02/Screen-Shot-2013-02-25-at-12.46.14-PM.png?4c9b33" width="641" height="483" /></a></p>
<p>A finalizar o Relatório foi apresentada uma análise sobre as <strong>principais conclusões</strong> retiradas do FG e a validar em outras atividades como <strong>Testes com Utilizadores</strong> com e sem utilização de <strong>Eyetracking</strong> e <strong>Avaliação Heurística</strong>.</p>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2013/02/focus-group-portal-web/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Scrum :: Vamos lá ser ágeis!</title>
		<link>http://log.pt/blog/2013/02/scrum-vamos-la-ser-ageis/</link>
		<comments>http://log.pt/blog/2013/02/scrum-vamos-la-ser-ageis/#comments</comments>
		<pubDate>Tue, 05 Feb 2013 11:36:26 +0000</pubDate>
		<dc:creator>Tiago Jacob</dc:creator>
				<category><![CDATA[blog]]></category>

		<guid isPermaLink="false">http://log.pt/?p=21212</guid>
		<description><![CDATA[Adotar uma metodologia de desenvolvimento ágil, como o Scrum, não se faz de forma leviana, pois é preciso que toda a equipa ou empresa compreenda e aceite a nova cultura. Para nós o Scrum veio como uma lufada de ar fresco. Na log foram duas ou três pessoas que começaram a &#8220;evangelizar&#8221; os princípios do [...]]]></description>
				<content:encoded><![CDATA[<p>Adotar uma metodologia de desenvolvimento ágil, como o <strong>Scrum</strong>, não se faz de forma leviana, pois é preciso que toda a equipa ou empresa compreenda e aceite a nova cultura.</p>
<p>Para nós o <strong>Scrum</strong> veio como uma lufada de ar fresco. Na log foram duas ou três pessoas que começaram a &#8220;evangelizar&#8221; os princípios do <strong>Scrum</strong> e a reclamar por uma nova forma de encarar os projetos. Tanto reclamaram que conseguiram! De facto, a gestão de projeto tradicional já não nos permitia evoluir na direção que pretendíamos, pois faltava-nos a agilidade para reagir à mudança.</p>
<p>Não se tratou do <strong>Scrum</strong> ser melhor ou pior que o modelo waterfall que usámos durante alguns anos. Tratou-se de constatar que para um melhor desempenho e sucesso da nossa atividade, a metodologia que melhor servia os nossos objetivos era, sem dúvida, o <strong>Scrum</strong>.</p>
<h2>Scrum 101</h2>
<p>Começámos pelo básico. Perceber os conceitos, os papéis, as responsabilidades, os rituais. Para nós foi muito importante entender os valores e os princípios desta metodologia.</p>
<p>O <strong>Scrum</strong> baseia-se e advoga os valores do <a title="Agile manifesto" href="http://agilemanifesto.org/" target="_blank"><em>Agile Manifesto</em></a> que descrevemos aqui:</p>
<p style="text-align: center;"><strong>Indivíduos e interações</strong> <em>em vez de</em> <strong>Processos e ferramentas</strong></p>
<p style="text-align: center;"><strong>Funcionalidades completas</strong> <em>em vez de</em> <strong>Documentação extensa</strong></p>
<p style="text-align: center;"><strong>Colaboração com o Cliente</strong> <em>em vez de</em> <strong>Negociação de contrato</strong></p>
<p style="text-align: center;"><strong>Resposta à mudança</strong> <em><em>em vez de</em></em> <strong>Seguir um plano à risca</strong></p>
<p>Ou seja, os itens à direita têm valor claro, mas os itens da esquerda têm mais importância. E foram os itens da esquerda que inspiraram a nossa mudança. Hoje podemos afirmar que o sucesso do <strong>Scrum</strong> assenta na compreensão destes valores e de outros que apoiam processos ágeis. Se acreditarem (com muita força) que estes princípios vos vão ajudar a alcançar melhor resultados, o sucesso está (quase) garantido. Se tiverem dúvidas sobre estes princípios, mais vale despenderem o tempo necessário para debaterem se o <strong>Scrum</strong> é ou não a metodologa que melhor se adapta à vossa empresa.</p>
<p>Outro desafio foi o assumir novos papéis. Não vale a pena querer iniciar o <strong>Scrum</strong> na sua empresa se não estiver disposto a mudar os papéis. Ao princípio estranha-se mas depois, já toda a equipa está familiarizada com os seus novos <em>roles</em>. São eles:</p>
<ul>
<li><strong>Product Owner</strong>: responsável pelo projeto, ou como costumamos lembrar à nossa equipa&#8230; é quem tem o pescoço a prémio! :)</li>
</ul>
<ul>
<li><strong>Scrum Master</strong>: garante que a equipa é funcional e produtiva. E acreditem, um bom Scrum Master faz o vosso projeto correr sobre rodas!</li>
</ul>
<ul>
<li><strong>Development Team</strong>: organiza-se para realizar as suas tarefas. Estes são os talentos! Uns mais experientes, outros menos, mas todos fazem a &#8220;magia&#8221; acontecer.</li>
</ul>
<ul>
<li><strong>Stakeholders</strong>: todos aqueles que beneficiam ou promovem o desenvolvimento do produto. É para eles que criamos e desenvolvemos as nossas soluções.</li>
</ul>
<p><a href="http://log.pt/blog/2013/02/scrum-vamos-la-ser-ageis/attachment/quickstart-guide-16-4/" rel="attachment wp-att-21422"><img class="size-large wp-image-21422 aligncenter" alt="QuickStart Guide - 16" src="http://log.pt/wp-content/uploads/2013/02/QuickStart-Guide-163-384x480.jpg?4c9b33" width="384" height="480" /></a></p>
<p>No próximo artigo vamos falar de <strong>Cerimónias</strong> e <strong>Artefatos</strong>.</p>
<p>Até breve!</p>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2013/02/scrum-vamos-la-ser-ageis/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Scrum :: Os nossos primeiros passos</title>
		<link>http://log.pt/blog/2013/01/scrum-os-nossos-primeiros-passos/</link>
		<comments>http://log.pt/blog/2013/01/scrum-os-nossos-primeiros-passos/#comments</comments>
		<pubDate>Thu, 17 Jan 2013 10:39:48 +0000</pubDate>
		<dc:creator>Scrum Team</dc:creator>
				<category><![CDATA[blog]]></category>

		<guid isPermaLink="false">http://log.pt/?p=20432</guid>
		<description><![CDATA[Vamos dar início a uma série de artigos sobre Scrum, partilhando as nossas experiências e aprendizagens sobre diversas temáticas desta framework. O Scrum entrou na log há 3 anos e, num misto de entusiasmo e inexperiência, entrámos a fundo na prática do “ScrumBut”. Conhecem o conceito? Uma prática que não cumpre todos os requisitos da [...]]]></description>
				<content:encoded><![CDATA[<p>Vamos dar início a uma série de artigos sobre Scrum, partilhando as nossas experiências e aprendizagens sobre diversas temáticas desta framework.</p>
<p>O <strong>Scrum</strong> entrou na log há 3 anos e, num misto de entusiasmo e inexperiência, entrámos a fundo na prática do “<em>ScrumBut</em>”.
Conhecem o conceito? Uma prática que não cumpre todos os requisitos da framework e que a longo prazo, não permite atingir os benefícios esperados. Olhando para trás, não restam dúvidas: foi um “<em>But</em>” com todas as letras!</p>
<p>Para perceberem melhor do que falamos, no primeiro projeto da log desenvolvido em <strong>Scrum</strong> a equipa era constituída por um Developer e um Scrum Master. Esperem! Falta um pormenor nesta história: o Developer e o Scrum Master eram a mesma pessoa!</p>
<p>Felizmente, cedo nos apercebemos que é preciso avaliar constantemente o nosso desempenho e saber identificar os erros que foram cometidos e ultrapassá-los. Sempre.
Os <em>Sprint Retrospective</em> tiveram, para isso, um papel fundamental.</p>
<p>O nosso caminho tem sido caracterizado por uma aprendizagem que nos tem levado a optimizações e alinhamentos constantes, especialmente nas nossas equipas mas também nos nossos clientes.
Agora, passados 3 anos temos equipas totalmente alinhadas aos processos e rituais do <strong>Scrum</strong> e, mais importante, aos resultados que esta metodologia permite obter em projetos de desenvolvimento de software.</p>
<p>E como o <strong>Scrum</strong> é feito (acima de tudo) de experiências, vamos partilhar as nossas aqui no blog. Vamos falar das falhas que cometemos e de como as resolvemos, as nossas aprendizagens passadas e seguramente, as que ainda virão.</p>
<p>E sim&#8230;é possível passar de um “<em>ScrumBut</em>” para simplesmente&#8230;<strong>Scrum</strong>.</p>
<p><img class="alignnone size-large wp-image-20442" title="ScrumBut" alt="" src="http://log.pt/wp-content/uploads/2012/12/Scrumbut-600x461.jpg?4c9b33" width="600" height="461" /></p>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2013/01/scrum-os-nossos-primeiros-passos/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Acessibilidade em iOS</title>
		<link>http://log.pt/blog/2012/12/acessibilidade-em-ios/</link>
		<comments>http://log.pt/blog/2012/12/acessibilidade-em-ios/#comments</comments>
		<pubDate>Tue, 04 Dec 2012 12:16:24 +0000</pubDate>
		<dc:creator>Luís Rodrigues</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[accessibility]]></category>
		<category><![CDATA[acessibilidade]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[VoiceOver]]></category>

		<guid isPermaLink="false">http://log.pt/?p=19832</guid>
		<description><![CDATA[Pode parecer uma ideia bizarra, mas qualquer pessoa pode tirar pleno partido de um iPod Touch, iPhone ou iPad sem olhar para ele. Como? Estes dispositivos, que lançaram a moda dos ecrãs tácteis, hoje ubíquos, fizeram o impensável e, graças à qualidade das suas interfaces e da experiência do utilizador, dispensaram a necessidade de teclados [...]]]></description>
				<content:encoded><![CDATA[<p>Pode parecer uma ideia bizarra, mas qualquer pessoa pode tirar pleno partido de um iPod Touch, iPhone ou iPad sem olhar para ele.</p>
<p>Como? Estes dispositivos, que lançaram a moda dos ecrãs tácteis, hoje ubíquos, fizeram o impensável e, graças à qualidade das suas interfaces e da experiência do utilizador, dispensaram a necessidade de teclados físicos e da maior parte dos botões. Tudo muito bonito para quem <em>vê bem</em>, mas estes dispositivos não oferecem qualquer resposta táctil para além do motor de vibração no iPhone.</p>
<p>O iOS, o sistema operativo que corre em quase todos os dispositivos móveis da Apple, permite definições de contraste, tamanho de letra e <em>zoom</em>, certamente úteis para quem tem algumas dificuldades visuais, mas que nada servem a uma pessoa com problemas mais sérios, incluindo quem não vê absolutamente <em>nada</em>.</p>
<p>Como, então?</p>
<h3 id="voiceover">VoiceOver</h3>
<p>O segredo está na tecnologia VoiceOver, migrada do OS X para prestar <em>feedback</em> auditivo aos utilizadores de iOS com problemas de visão. Graças ao VoiceOver, e agora também à assistente pessoal digital Siri, até <a href="http://thenextweb.com/apple/2011/09/15/stevie-wonder-sings-steve-jobs-praises-for-ios-accessibility/">pessoas completamente cegas</a> têm acesso facilitado aos dispositivos da Apple e a um grande leque de aplicações.</p>
<p>O VoiceOver não é um produto adquirido à parte. Está integrado em todos os dispositivos iOS e pode ser activado abrindo a aplicação de <em>Settings</em>, e escolhendo <em>General &gt; Accessibility &gt; VoiceOver &gt; On</em>. Também é possível configurar o VoiceOver para ser activado com três toques no botão de Home do dispositivo.</p>
<p>Com o VoiceOver ligado, constatamos de imediato que a interface adquire um comportamento estranho e se torna algo frustrante de usar. Um toque em qualquer parte do ecrã apenas selecciona o elemento tocado, despoletando uma descrição auditiva, sendo que para o activar é preciso um duplo toque no ecrã. E fazer subir ou descer os conteúdos no ecrã obriga a arrastar com <em>três</em> dedos. Mas também nos apercebemos, como que numa revelação, que passamos a poder usar o iOS literalmente de olhos fechados.</p>
<p>As restantes diferenças não são assim tantas: um utilizador do VoiceOver apoia-se na mesma memória muscular e espacial da maioria, e, tal como a maioria, é capaz de varrer rapidamente o ecrã com os dedos para encontrar o que procura, saltando por cima de tudo o que não lhe interessa.</p>
<p>O VoiceOver dá ainda acesso ao “rotor,” um controlo activado pela rotação de dois dedos no ecrã para ajustar a granularidade com que o VoiceOver percorre os elementos da interface, possibilitando saltar entre letras, palavras ou frases, <em>links</em>, campos de formulário ou secções de conteúdo.</p>
<p>No vídeo abaixo, o programador Robin Christopherson sobe ao palco para demonstrar como usa o iPad com VoiceOver no seu dia-a-dia.</p>
<p><iframe width="500" height="281" src="http://www.youtube.com/embed/fw6SjfRTfbc?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<h3 id="atributosdeacessibilidade">Atributos de acessibilidade</h3>
<p>Todos os controlos e <em>views</em> do UIKit no iOS implementam protocolos de acessibilidade com atributos que podem ser facilmente definidos a partir do Interface Builder ou no código da aplicação. Estes atributos são:</p>
<p><strong>Label:</strong> Rótulo curto que identifica o elemento, idealmente descrevendo a sua função;</p>
<p><strong>Traits:</strong> Uma ou mais características pré-definidas descrevendo o estado ou comportamento do elemento (se é um botão ou uma imagem, se está seleccionado ou inactivo, etc.);</p>
<p><strong>Hint:</strong> Texto opcional para esclarecer, de forma sucinta, o resultado de activar o elemento;</p>
<p><strong>Value:</strong> O valor actual do elemento, caso não se encontre já descrito pelo rótulo;</p>
<p><strong>Frame:</strong> A moldura do elemento consiste num par de coordenadas que especificam a posição e dimensões do elemento;</p>
<p><strong>Language:</strong> O idioma do elemento, caso a descrição precise de aparecer num idioma que não o configurado.</p>
<p>No vídeo abaixo, podemos observar a utilização da nossa aplicação <a href="http://eatoutapp.com/">EatOut</a> com o VoiceOver ligado. Aqui, a acessibilidade do botão para marcação telefónica em iPhone foi optimizada com descrições da <em>label</em> e da <em>hint</em>.</p>
<p><iframe width="500" height="281" src="http://www.youtube.com/embed/hPxMid_9hwg?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<h3 id="paraquestetrabalhotodo">Para quê este trabalho todo?</h3>
<p>Na verdade, tornar uma aplicação acessível implica <em>muito pouco esforço</em>. Isto deve-se em parte à qualidade da API do VoiceOver, que por omissão torna acessíveis todos os controlos do UIKit, sendo que nos restantes casos não é preciso mais do que encontrar descritivos apropriados para os atributos de cada um dos elementos da interface.</p>
<p>No caso do EatOut, não foi preciso fazer <em>nada</em> para optimizar a pesquisa de restaurantes para utilizadores com necessidades especiais. A acessibilidade do campo de texto e do teclado foi completamente garantida pela <em>framework</em> sem obrigar à escrita de uma linha de código ou definição de um parâmetro que fosse.</p>
<p>Assim sendo, por que não tornar uma aplicação acessível? Ao cuidar da acessibilidade das nossas aplicações, estamos a levá-las a um público maior. Estamos a oferecer a mesma experiência de utilização a toda a gente, não obstante as limitações e necessidades físicas de cada um. Satisfazemos ainda requisitos de acessibilidade que organismos governamentais por vezes impõem. E, ao mesmo tempo, estamos a tornar uma boa aplicação numa aplicação ainda melhor.</p>
<p>Além disso, uma boa acção não devia ser motivo suficiente?</p>
<h3 id="boasprticas">Boas práticas</h3>
<p>Como disse, é muito fácil tornar uma aplicação minimamente acessível, e só costuma falhar quem não se preocupou sequer com o assunto. É frequente aplicações adoptarem controlos alternativos para personalizar aspectos da interface que o UIKit não permite alterar, e quando isso acontece a questão da acessibilidade é por vezes esquecida.</p>
<p>Controlos alternativos que não sejam baseados em UIKit, e por isso não herdem atributos de acessibilidade por omissão, podem implementar os protocolos <a href="http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAccessibility_Protocol/Introduction/Introduction.html"><code>UIAccessibility</code></a> e/ou <a href="http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAccessibilityContainer_Protocol/Introduction/Introduction.html"><code>UIAccessibilityContainer</code></a> para gozar de todos os benefícios de acessibilidade de um controlo oficial.</p>
<p>Independentemente dos controlos usados, as boas práticas de acessibilidade centram-se na qualidade das descrições das <em>labels</em> e <em>hints</em>, e na correcta definição dos <em>traits</em> dos elementos.</p>
<p>As descrições preferem-se concisas: da mesma forma que muitos não olham atentamente para as mensagens dos alertas e para os rótulos de botões, também é provável que um utilizador com VoiceOver não vá ficar a ouvir longas descrições de elementos.</p>
<p>A <em>label</em> deve descrever a função do elemento, de preferência um verbo como “Pesquisar” ou “Fechar”, e não o elemento propriamente dito. “Botão de pesquisa” já é informação em excesso, até porque o <em>trait</em> pode já indicar tratar-se de um botão, o que levaria o VoiceOver a descrevê-lo como “Botão de pesquisa. Botão.”</p>
<p>A <em>hint</em> deve ser usada com parcimónia. Por exemplo, quando a <em>label</em> não é suficientemente explícita, a <em>hint</em> pode esclarecer o resultado de activar o elemento. Por exemplo: “Filtros. Botão. Abre o painel de opções de pesquisa.”</p>
<p>Para além da definição destes atributos, que suprem as necessidades da grande maioria das aplicações, também é possível ajustar a interface para responder ao estado do VoiceOver e à sua eventual activação. Detectar o estado do VoiceOver é particularmente útil quando pretendemos aumentar a perceptibilidade de certos elementos da nossa interface. Por exemplo, uma mensagem de erro que apareça por breves segundos pode e deve ser lida ao utilizador se este tiver VoiceOver.</p>
<h3 id="detectaroestadodovoiceover">Detectar o estado do VoiceOver</h3>
<p>No EatOut, oferecemos uma funcionalidade de <em>pull-to-refresh</em> em todas as tabelas com conteúdos dinâmicos. Embora a funcionalidade em si seja acessível, usando três dedos para deslocar o conteúdo para lá do limite superior, o VoiceOver torna a sua existência pouco perceptível e o acesso moroso.</p>
<p>Assim, decidimos que, ao activar o VoiceOver, o utilizador deva ser presenteado com um atalho de refrescamento no canto superior direito.</p>
<p><iframe width="500" height="281" src="http://www.youtube.com/embed/nT35oG1r5-s?feature=oembed" frameborder="0" allowfullscreen></iframe></p>
<p>A detecção é feita no código recorrendo à classe <a href="https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/nsnotificationcenter_Class/Reference/Reference.html"><code>NSNotificationCenter</code></a> do iOS, sendo suficiente associar um método (<code>didChangeVoiceOverStatus:</code> neste exemplo) à notificação <code>UIAccessibilityVoiceOverStatusChanged</code>.</p>
<p>&nbsp;</p>
<p>Podemos obter o estado do VoiceOver invocando a função <a href="http://developer.apple.com/library/ios/documentation/uikit/reference/UIKitFunctionReference/Reference/reference.html#//apple_ref/c/func/UIAccessibilityIsVoiceOverRunning"><code>UIAccessibilityIsVoiceOverRunning()</code></a>, que retorna <code>YES</code> ou <code>NO</code> caso esteja ou não activo.</p>
<p>Convém lembrarmo-nos de retirar o observador da notificação quando já não precisarmos dela, para evitar falhas e fugas de memória:</p>
<p>&nbsp;</p>
<h3 id="concluses">Conclusões</h3>
<p>Tendo em conta a rapidez e facilidade com que a <em>framework</em> do iOS pode garantir o acesso a pessoas com deficiências visuais, torna-se difícil justificar o motivo de tantas aplicações de primeira linha negligenciarem este aspecto da utilização.</p>
<p>Como vimos, a simples definição de um ou dois atributos de acessibilidade em cada controlo é quanto basta para tornar uma aplicação usável e acessível por todos. Mesmo funcionalidades avançadas, dependentes da detecção do estado do VoiceOver, não são particularmente onerosas de implementar.</p>
<p>Para mais informações sobre estes tópicos, recomendamos a leitura da documentação no <em>site</em> da Apple, indicada abaixo.</p>
<h3 id="referncias">Referências</h3>
<ul>
<li><a href="http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html">Accessibility Programming Guide for iOS</a>, Apple</li>
<li><a href="http://mattgemmell.com/2010/12/19/accessibility-for-iphone-and-ipad-apps/">Accessibility for iPhone and iPad apps</a>, por Matt Legend Gemmell</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/12/acessibilidade-em-ios/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>10 dicas de SEO e Usabilidade</title>
		<link>http://log.pt/blog/2012/11/10-dicas-de-seo-e-usabilidade/</link>
		<comments>http://log.pt/blog/2012/11/10-dicas-de-seo-e-usabilidade/#comments</comments>
		<pubDate>Mon, 19 Nov 2012 17:52:20 +0000</pubDate>
		<dc:creator>Filipe Serrazina</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[SEO]]></category>
		<category><![CDATA[Usabilidade]]></category>

		<guid isPermaLink="false">http://log.pt/?p=14102</guid>
		<description><![CDATA[A importância do SEO (Search Engine Optimization) é indiscutivel. Mas é igualmente importante a usabilidade do site. &#160; Proporcionar uma experiência única e ter um site com um design apelativo são fatores que irão garantir que o utilizador realiza o &#8220;call to action&#8221; tão ambicionado por quem desenhou a arquitetura de informação do site. Atrair [...]]]></description>
				<content:encoded><![CDATA[<h2>A importância do SEO (<em>Search Engine Optimization</em>) é indiscutivel. Mas é igualmente importante a usabilidade do site.</h2>
<p>&nbsp;</p>
<p>Proporcionar uma experiência única e ter um site com um design apelativo são fatores que irão garantir que o utilizador realiza o &#8220;<em>call to action</em>&#8221; tão ambicionado por quem desenhou a arquitetura de informação do site.</p>
<p>Atrair muito tráfego para um website não pode ser entendido como uma estratégia isolada. É igualmente necessário garantir uma excelente experiência de utilização aos seus visitantes.</p>
<p>Qual o propósito de obter muito tráfego, se depois, devido a uma má experiência de utilização, os utilizadores abandonam rapidamente o site, sem que consigam cumprir os objetivos que inicialmente tinham em mente quando entraram no site?</p>
<p>Todos ambicionam que o seu site atraia o maior número de pessoas, uma vez que mais pessoas, pode ser sinónimo de crescimento e de novas oportunidades. É isso que faz com que muitos olhem para o SEO como uma resposta às suas necessidades. Mas na ânsia de subir no <em>ranking</em> dos motores de busca, tende-se a ignorar a importância da usabilidade como um fator que também melhora a qualidade do tráfego do site.</p>
<p>Uma vez que os motores de busca estão cada vez mais inteligentes e conhecem cada vez melhor a nossa maneira de pensar, a optimização de um website torna-se um processo mais refinado, permitindo melhorar a usabilidade e o SEO.</p>
<p>De seguida são apresentados 10 pontos importantes para melhorar o SEO e a usabilidade de um site.</p>
<h3>1. Tag &lt;head&gt;</h3>
<p style="text-align: center;"><img class="aligncenter" title="Head Tag" src="http://farm3.staticflickr.com/2090/2180932252_19c9babef1.jpg" alt="" width="430" height="346" /></p>
<p>Para o SEO esta é a secção mais importante da estrutura do HTML.</p>
<p>Nesta tag são inseridas a tag &lt;title&gt;, as &lt;meta&gt; descrições e ainda as meta tags e a tag rel=&#8221;canonical&#8221;.</p>
<p>Do ponto de vista da usabilidade, esta tag pode não parecer muito importante, uma vez que esta secção não vai afectar o aspecto visual do site (excepção à tag title). No entanto, este é o conteúdo que irá aparecer, na maioria das vezes, no <em>Search Engine Results Page</em> (SERP). Se o conteúdo desta secção parecer pouco profissional, os utilizadores não se irão sentir tentados a clicar para aceder ao site.</p>
<p>Os motores de busca utilizam o conteúdo das meta tags na secçãode forma a decidirem que informação mostram. Por isso, é necessário torná-la o mais claro e usável possível.</p>
<h3>2. Tag &lt;title&gt;</h3>
<p>A tag &lt;title&gt; é utilizada pelos motores de busca para determinar o assunto da página. É esta tag que é mostrada na página de resultados (SERP) e é onde os utilizadores podem clicar para aceder ao site.</p>
<p>O conteúdo da tag &lt;title&gt; deve refletir o conteúdo da página. Com esta regra em mente, colocar as palavras chave no início da tag &lt;title&gt; dá-nos um título que é amigo dos motores de busca.</p>
<p>Após efetuar uma pesquisa e obter como resultado uma lista de sites, o utilizador vai ler o texto colocado nesta tag &lt;title&gt; para melhor identificar os sites que lhe são apresentados nesta lista de resultados.</p>
<p>Por outro lado, os web browsers usam o texto desta tag para nomear a tab onde o site se encontra, o que permite ao utilizador identificar rapidamente a tab que contém o site que pretende consultar.</p>
<p>Por norma, os browsers atribuem o conteúdo do título aos favoritos, pelo que os utilizadores também irão procurar pelo título os sites que colocaram nos favoritos.</p>
<h3>3. &lt;Meta Name=&#8221;Description&#8221;</h3>
<p>Os motores de busca utilizam o conteúdo da tag &lt;Meta Name=&#8221;Description&#8221; para mostrar a descrição do site no SERP. Os motores de busca têm um limite de caracteres para mostrar no SERP, não devendo exceder os 160 caracteres.</p>
<p>Embora o <a href="http://googlewebmastercentral.blogspot.com/2009/09/google-does-not-use-keywords-meta-tag.html">Google não utilize o conteúdo das meta tags para elaborar o ranking</a>, a presença de palavras-chave na meta descrition pode gerar tráfego uma vez que pode captar a atenção dos utilizadores.</p>
<p>Do ponto de vista do utilizador, uma <em>meta description</em>, para cada uma das páginas, que corresponda ao conteúdo dessa página e que seja única para cada página, pode fazer toda a diferença em termos de SEO. Pois se os utilizadores, ao verem o mesmo conteúdo da <em>meta description</em> no SERP para todas as páginas, podem ter problemas em identificar qual o link a clicar.</p>
<p>Pelo contrário, se os utilizadores acederem ao site através do SERP, e se o conteúdo da descrição for apelativo mas o resultado da página for diferente da sua expectativa, então isto irá ter um efeito muito negativo na experiência de utilização.</p>
<h3>4. Rel=&#8221;Canonical&#8221;</h3>
<p>Uma página canonical é a forma de “comunicar” que o <a href="http://support.google.com/webmasters/bin/answer.py?hl=en&amp;answer=139394">conteúdo da página é o original</a>. Se existirem várias páginas cujo conteúdo é bastante parecido, apenas com pequenas variações, definir um URL como canonical pode resolver o problema do conteúdo duplicado. Conteúdo duplicado é extremamente penalizado pelos motores de busca.</p>
<p>Para os utilizadores, terem páginas com conteúdo idêntico é um factor que os confunde. Se o conteúdo duplicado estiver presente em links ou na navegação do site, irá ser muito difícil para os utilizadores escolherem qual o link a clicar. Isto pode também ocorrer se o mesmo conteúdo é acedido através de links com nomes diferentes, uma vez que na mente dos utilizadores o conteúdo deveria ser diferente.</p>
<h3>5. &lt;A Href=&#8221;</h3>
<p style="text-align: center;"><img class="aligncenter" title="Anchor" src="http://farm1.staticflickr.com/57/162298564_6672bdd9ff.jpg" alt="" width="500" height="375" /></p>
<p>Os links são essenciais para os motores de pesquisa. Juntamente com os sitemaps XML, eles representam a única forma dos crawlers localizarem e indexarem as páginas. O Google utiliza o <em><a href="http://thekeywordacademy.com/link-juice-explained">link juice</a></em>, para determinar o &#8220;valor&#8221; dos links para o ranking.</p>
<p>Para que os links sejam &#8220;amigos&#8221; dos motores de busca, precisam de ser descritivos e conter as palavras-chave corretas.</p>
<p>Para os utilizadores, existirem links usáveis significa que podem mais facilmente atingir o objectivo com o qual entraram num site (seja com o propósito de fazer uma pesquisa informativa ou uma compra de produtos ou serviços). O facto de o utilizador conseguir atingir o objectivo afeta positivamente a experiência de utilização, aumentando a probabilidade do utilizador voltar ao site ou recomendá-lo a outras pessoas.</p>
<h3>6. &lt;Link Rel=&#8221;Stylesheet&#8221;</h3>
<p>Os motores de busca penalizam websites que são lentos a carregar.
Quando o JavaScript e as CSS estão nos ficheiros HTML, estes ficheiros ficam com um tamanho maior, o que faz com que o carregamento do ficheiro seja mais lento.</p>
<p>Uma boa solução é colocar o código do JavaScript e das CSS em ficheiros externos, e chamá-los só quando são necessários. Ainda melhor, será comprimir os ficheiros de forma a que tenham um tamanho mais reduzido.</p>
<p>Minimizar o número de pedidos ao servidor, encontrando o equilíbrio entre o número de ficheiros e o seu tamanho também pode melhorar a velocidade do site.</p>
<p>Os utilizadores <a href="http://www.useit.com/alertbox/page-abandonment-time.html">passam entre 10 a 20 segundos</a> numa página web a não ser que o conteúdo seja relevante para eles. Podemos imaginar o quão rápido os utilizadores abandonariam um site se a página demorasse uma eternidade a carregar. Um <a href="http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html">estudo elaborado pela Google</a> que os &#8220;<em>sites rápidos fazem com que os utilizadores fiquem agradados com a experiência</em>&#8221; e que &#8220;<em>quando um site é lento a responder, os visitantes passam menos tempo nele</em>&#8220;.</p>
<h3>7. Microdata</h3>
<p><img class="aligncenter size-large wp-image-14192" title="HTML-Guidelines-Usability-SEO-Microdata" src="http://log.pt/wp-content/uploads/2012/05/HTML-Guidelines-Usability-SEO-Microdata-600x305.jpg?4c9b33" alt="" width="600" height="305" /></p>
<p>A informação contida na microdata é uma das <a href="#log_anchor_linguagens">três linguagens utilizadas (1)</a> para dar informação do conteúdo aos motores de busca. O formato da microdata, <a href="http://schema.org">schema.org</a>, foi adotado pela Google, Bing e Yahoo!. A microdata fornece informação contextual aos motores de busca e a forma como o site deve ser indexado.</p>
<p>Adicionalmente, também cria &#8220;<em>rich snippets</em>&#8220;, que fornecem mais informação no SERP do que as listas tradicionais.</p>
<p>Como a microdata fornece aos motores de busca informação mais detalhada do site, estes podem apresentar resultados com mais informação no SERP. Implementar a microdata é benéfico para um início positivo na experiência de utilização mesmo antes de acedermos ao site.</p>
<h3>8. Densidade do conteúdo</h3>
<p>O conteúdo de um site deve ser cativante e permitir que os utilizadores atinjam os objectivos com que acederam a um site. Embora pareça uma tarefa muito complicada, tudo isto deve ser feito de uma maneira curta e sucinta. O que nos leva ao conceito de densidade do conteúdo.</p>
<p>A densidade do conteúdo é o rácio de conteúdo de uma página em relação ao tamanho dessa página. Páginas com uma grande densidade tendem a ter um melhor ranking nos motores de busca. Por exemplo, se a página A tem 200 palavras e tem 10Kb de tamanho, é provável que tenha um melhor rank em comparação com a página B que tem 200 palavras mas pesa 20Kb.</p>
<p>Do ponto de vista da usabilidade, o conteúdo de uma página deve ser claro e objectivo. Se uma página contém muitos parágrafos, irá perder os seus leitores. A maioria dos utilizadores percorrem uma página à procura da informação. Se não a conseguirem localizar facilmente, desistem e abandonam a página.</p>
<p>A densidade do conteúdo é importante para o SEO e por isso, os sites que se preocupam com este ponto normalmente possuem mais conteúdo que contém  palavras-chave e que é fácil de compreender. Disponibilizar as palavras-chave corretas e disponibilizar um conteúdo que seja de fácil leitura, ajuda a obter melhores rankings nos motores de busca.</p>
<p>Os sites que têm demasiado conteúdo podem dificultar o trabalho aos motores de busca, e sites que contêm demasiadas palavras-chave (<em><a href="http://support.google.com/webmasters/bin/answer.py?hl=en&amp;answer=66358">Keyword Stuffing</a></em>) oferecem, muito provavelmente, uma experiência de utilização negativa e, por isso, descem no ranking dos motores de busca.</p>
<p>Para os utilizadores, um site que tem as principais palavras-chave de forma moderada é bom para a usabilidade desse site. Os utilizadores podem facilmente identificar se o conteúdo é relevante, e continuar a ler, se for o caso.</p>
<p>Resumidamente, quando os utilizadores visitam um site, estão à procura de conteúdo relevante. Quanto mais tempo eles demorarem a encontrar e a compreender o conteúdo, menos usável se torna o site.</p>
<h3>9. Utilização dos Breadcrumbs</h3>
<p>A navegação por breadcrumbs é bastante útil nos sites e constitui uma oportunidade de SEO para tornar o site mais amigável para os motores de busca.</p>
<p>Jakob Nielsen <a href="http://www.useit.com/alertbox/breadcrumbs.html">concluiu em 2007,</a> que os <em>breadcrumbs</em> têm aumentado a sua utilidade deste 1995.</p>
<p>Do ponto de vista da usabilidade, os <em>breadcrumbs</em>:</p>
<ul>
<li>Permitem ao utilizador ter um rasto da sua navegação, podendo voltar atrás, ou mesmo ao ponto de partida da navegação;</li>
<li>Permitem que os níveis superiores do site fiquem à distância de um clique;</li>
<li>Diminuem a <a href="http://www.smashingmagazine.com/2009/03/17/breadcrumbs-in-web-design-examples-and-best-practices-2;sa=D&amp;;sntz=1&amp;;usg=AFQjCNFG3lPtt3ufwx-AIMuIEH1X6AtfQQ">taxa de rejeição</a>.</li>
<li>São facilmente entendidos pelos utilizadores.</li>
</ul>
<p>Do ponto de vista do SEO, os <em>breadcrumbs</em>:</p>
<ul>
<li>Constituem uma forma natural de colocar palavras-chave na página, o que ajuda na densidade de palavras-chave;</li>
<li>Informam e definem, para os motores de busca, qual o conteúdo das páginas do site;</li>
<li>São bastante relevantes para o Google.</li>
</ul>
<h3>10. Links Internos</h3>
<p>Uma interligação de links efectiva e uma utilização de links com significado torna um site usável, o que melhora o ranking nos motores de busca. Investir algum tempo para criar bons links no início de um projecto, irá, ao longo dos tempos, trazer resultados muitos positivos.</p>
<p>Sendo o objectivo de um site atrair utilizadores, quando eles chegam ao site, se não encontram forma de prosseguir e obter a informação que procuram, a experiência de utilização é má e o mais provável é o utilizador não voltar a aceder a esse site.</p>
<p>Criar links que levem os utilizadores a clicar é, todavia, discutível.
Alguns autores argumentam que os links devem conter o texto da âncora, o que contribui enormemente para o SEO. Outros, dizem que o texto deve apenas conter um &#8220;<em>Call to Action</em>&#8220;. Ambos os argumento são válidos, mas o volume de tráfego e os cliques em conteúdo relevante é, em última análise, o interesse máximo de quem construiu o site.</p>
<p>De forma a manter os links atualizados, devemos criar links que se integrem no conteúdo da página. Os links devem dar uma pista aos utilizadores para onde se destinam. Qualquer link que seja ambíguo provavelmente nunca será clicado. Devemos também utilizar o maior número de palavras ou frases para indicar para onde será redirecionado o utilizador, quando clicar no link.</p>
<hr />
<div style="clear: both;"></div>
<pre id="log_anchor_linguagens">(1) As três linguagens são: Microdata, Microformato e RDFA</pre>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/11/10-dicas-de-seo-e-usabilidade/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Internacionalização em WordPress (i18n)</title>
		<link>http://log.pt/blog/2012/11/wordpress-i18n/</link>
		<comments>http://log.pt/blog/2012/11/wordpress-i18n/#comments</comments>
		<pubDate>Tue, 06 Nov 2012 15:14:35 +0000</pubDate>
		<dc:creator>Luís Rodrigues</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://log.pt/?p=15172</guid>
		<description><![CDATA[Num artigo anterior, aflorámos o tema da internacionalização em WordPress, e salientámos a importância de todo o código produzido estar preparado para suportar tradução, mesmo que à partida tal não pareça necessário. A internacionalização (também conhecida pela abreviatura i18n) consiste em assinalar determinadas cadeias de caracteres — ou strings — apresentadas ao utilizador de modo [...]]]></description>
				<content:encoded><![CDATA[<p>Num <a href="http://log.pt/blog/2012/08/wordpress-tutorial-plugins/">artigo anterior</a>, aflorámos o tema da internacionalização em WordPress, e salientámos a importância de todo o código produzido estar preparado para suportar tradução, mesmo que à partida tal não pareça necessário.</p>
<p>A internacionalização (também conhecida pela abreviatura <em>i18n</em>) consiste em assinalar determinadas cadeias de caracteres — ou <em>strings</em> — apresentadas ao utilizador de modo a que estas possam ser traduzidas por um mecanismo transversal à solução. No caso do WordPress, esse mecanismo está presente na biblioteca livre <em>open source</em> do <a href="http://www.gnu.org/software/gettext/">GNU gettext</a>.</p>
<p><strong>A internacionalização deve ser uma consideração a ter desde o primeiro instante do desenvolvimento.</strong> Nunca é demais repeti-lo. Uma equipa minimamente interessada em entregar uma solução localizável — seja para melhorar a integração com o eco-sistema aplicacional, aproximar-se dos clientes que já tem ou alcançar novos mercados — não pode encarar a tarefa de internacionalização como uma preocupação secundária.</p>
<p>Primeiro, porque o tempo e o custo de integrar mecanismos de tradução num projecto já iniciado ficam consideravelmente maiores, que mais não seja pelo esforço de localizar no código do projecto todas as <em>strings</em> passíveis de tradução, mas também pelo impacto que quaisquer revisões arquitecturais possam ter.</p>
<p>Segundo, porque o esforço de deixar uma <em>string</em> preparada para tradução, mesmo que depois não se faça nada com ela, é mínimo. Aqui basta a disciplina de envolver o texto a traduzir com uma das várias funções disponibilizadas para o efeito pelo WordPress.</p>
<h3 id="funesdetraduo">Funções de tradução do WordPress</h3>
<p>O WordPress oferece um conjunto de funções para processamento de texto, das quais as mais recorrentes são <a href="http://codex.wordpress.org/Function_Reference/_2"><code>__()</code></a> e <a href="http://codex.wordpress.org/Function_Reference/_e"><code>_e()</code></a>. A diferença entre as duas é simples: <code>__()</code> retorna a <em>string</em> traduzida para processamento posterior, ao passo que <code>_e()</code> apresenta imediatamente a tradução.</p>
<p>Assim, onde antes faríamos <code>echo</code>, devemos passar a usar <code>_e()</code>:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
echo 'Hello, world!'; // Antes
_e( 'Hello, world!' ); // Depois
?&gt;
</pre>
<p>De modo semelhante, devemos transformar as atribuições de todas as <em>strings</em> traduzíveis com <code>__()</code>:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
$greeting = 'Hello, world!'; // Antes
$greeting = __( 'Hello, world!' ); // Depois
?&gt;
</pre>
<p>Como se pode constatar, não só o impacto na legibilidade é mínimo, como agora sabemos exactamente que texto do nosso projecto está a ser traduzido, bastando efectuar uma pesquisa por “__” ou “_e”. Adiante veremos que existem ferramentas concebidas para varrer o código do projecto e recolher de forma automática todas as <em>strings</em> passíveis de tradução.</p>
<p>As funções de tradução também não se ficam por estes dois exemplos. Outras há, menos comuns mas nem por isso menos úteis, que iremos abordar ao longo do artigo.</p>
<h3 id="domniosdetexto">Domínios de texto</h3>
<p>As funções acima apresentadas aceitam um segundo parâmetro que diz respeito ao identificador do domínio textual. Por omissão, a plataforma assume o identificador <em>“default”</em>, mas recomenda-se que <em>plugins</em> e temas declarem domínios de texto próprios para evitar colisões com as traduções de outros componentes.</p>
<p>As funções que declaram o domínio textual são também as usadas para carregar os ficheiros de tradução no WordPress: <a href="http://codex.wordpress.org/Function_Reference/load_plugin_textdomain"><code>load_plugin_textdomain()</code></a> para os <em>plugins</em> e <a href="http://codex.wordpress.org/Function_Reference/load_theme_textdomain"><code>load_theme_textdomain()</code></a> para os temas.</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
load_plugin_textdomain( 'my_plugin', false, $relpath );
_e( 'Hello, world!', 'my_plugin' );
?&gt;
</pre>
<p>O primeiro parâmetro é o identificador do domínio, que se quer único para toda a solução, e deverá ser usado em todas as funções de tradução do <em>plugin</em>. O parâmetro do meio não é usado, mantendo-se a posição apenas por questões de retrocompatibilidade. Por fim, a variável <code>$relpath</code> é o caminho, relativo a <code>WP_PLUGIN_DIR</code>, da directoria que contém os ficheiros de tradução com extensão MO.</p>
<p>A função <code>load_plugin_textdomain()</code> carrega todos os ficheiros aí encontrados que correspondam ao formato <em>&lt;textdomain&gt;-&lt;locale&gt;.mo</em>, em que <em>&lt;textdomain&gt;</em> é o domínio declarado acima e <em>&lt;locale&gt;</em> o identificador de cada idioma, composto pelo código <a href="http://www.mathguide.de/info/tools/languagecode.html">ISO 639–1</a> da língua, seguido por um <em>underscore</em> (_) e pelo código regional <a href="http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm">ISO 3166–1</a> em maiúsculas. Por exemplo, um <em>plugin</em> com o domínio de tradução “my_plugin” teria um ficheiro de tradução chamado <em>my_plugin-pt_PT.mo</em> para português de Portugal e outro <em>my_plugin-pt_BR.mo</em> para português do Brasil.</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
load_theme_textdomain( 'my_theme', $abspath );
_e( 'Hello, world!', 'my_theme' );
?&gt;
</pre>
<p>Com <code>load_theme_textdomain()</code>, indicamos à mesma o identificador de domínio, e o caminho absoluto para os ficheiros de tradução do tema. Recomenda-se que esta função seja invocada no contexto da <a href="http://log.pt/blog/2012/01/wordpress-tutorial-actions/">acção</a> <code>after_setup_theme</code>.</p>
<p>Ao contrário do que sucede com os <em>plugins</em>, os ficheiros com as traduções dos temas não podem conter o domínio textual no nome. Aqui, independentemente do domínio, os ficheiros devem chamar-se <em>pt_PT.mo</em> para português de Portugal, <em>pt_BR.mo</em> para português do Brasil ou <em>de_DE.mo</em> para alemão.</p>
<p>Ainda a propósito dos domínios, Geert De Deckere apresenta no seu blogue algumas sugestões interessantes sobre como tornar um <em>plugin</em> <a href="http://www.geertdedeckere.be/article/loading-wordpress-language-files-the-right-way">mais tolerante a traduções personalizadas</a>.</p>
<h3 id="umdoistrs">Um, dois, três…</h3>
<p>A necessidade de ter descrições separadas para formas singulares e plurais da apresentação de quantidades é um aborrecimento que qualquer programador já conhece.</p>
<p>Por vezes implica codificar uma simples estrutura de decisão, que mais não seja recorrendo a operadores ternários, mas que diminui a legibilidade do código. Outros sacrificam a atenção ao pormenor usando 1 <em>string(s)</em> genérica(s) para todas as quantidades.</p>
<p>O desafio é complicado ainda pela necessidade de as traduzir, em particular se pensarmos que há idiomas com regras específicas e mais do que duas flexões de número (como o russo ou o árabe).</p>
<p>Para endereçar este problema, o WordPress tem a função <a href="http://codex.wordpress.org/Function_Reference/_n"><code>_n()</code></a>, que retorna uma <em>string</em> traduzida em concordância de número com o valor que lhe for passado:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php _n( $singular, $plural, $number, $domain ); ?&gt;
</pre>
<p>Não obstante a existência de idiomas com mais do que duas flexões de número, a função apenas aceita parâmetros para singular e plural. Não há qualquer problema, apesar de tudo. O próprio gettext consegue desmultiplicar todas flexões necessárias a partir daqui, ao passo que o WordPress limita-se a assumir que a codificação das <em>strings</em> originais é feita num idioma com regras gramaticais próximas das do inglês.</p>
<h3 id="variveis">Variáveis</h3>
<p>O primeiro desafio da utilização de <em>strings</em> de tradução surge quando se tenta traduzir texto contendo uma ou mais variáveis. Cedo descobrimos que interpolação…</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php _e( &quot;I love the $language language.&quot; ); ?&gt;
</pre>
<p>…não funciona. O tradutor recebe <em>“I love the $language language.”</em> para traduzir, mas a variável não é substituída.</p>
<p>Posto isto, o primeiro impulso do programador pode ser tentar uma resolução por concatenação:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php _e( 'I love the ' . $language . ' language.' ); ?&gt;
</pre>
<p>Mas o resultado seria igualmente desastroso.</p>
<p>Quer estejam interpoladas ou concatenadas, não se devem traduzir <em>strings</em> que contenham variáveis. O <em>software</em> que recolhe as <em>strings</em> para tradução não tem capacidade para interpretar o seu valor. As funções de tradução esperam receber <em>strings</em> literais e não suportam a tradução de valores guardados em variáveis ou constantes.</p>
<p>O programador pode tentar ainda separar a frase onde quer que ocorram as variáveis, assim:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php _e( 'I love the ' ); echo $language; _e( ' language.' ); ?&gt;
</pre>
<p>E estaria absolutamente incorrecto. Porquê?</p>
<h3 id="parabonsentendedores">Para bons entendedores</h3>
<p>Porque, no capítulo das boas práticas, recomenda-se que as <em>strings</em> de tradução consistam em <em>frases completas</em> e que se evitem orações ou palavras isoladas. A fragmentação das frases é um obstáculo para que muitas sejam convenientemente traduzidas. Veja-se o (mau) exemplo anterior:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php _e( 'I love the ' ); echo $language; _e( ' language.' ); ?&gt;
</pre>
<p>O primeiro problema é que os tradutores se irão deparar com dois fragmentos isolados e descontextualizados (<em>“I love the”</em> e <em>“language.”</em>), encontrando natural dificuldade em interpretá-los.</p>
<p>Em segundo lugar, o idioma de destino não tem de responder às mesmas regras gramaticais que o de origem. Não cabe na cabeça de ninguém traduzir <em>“I love the”</em> por “Adoro a língua” e depois substituir <em>“language.”</em> por um ponto final, até porque estes fragmentos podem traduzir-se de outra forma noutros contextos.</p>
<p>Um terceiro problema decorre de ter espaços no início ou no final das <em>strings</em>. Estes são difíceis de detectar pelos tradutores, aumentando a probabilidade de erros escusados no produto final.</p>
<p>O procedimento correcto nestas situações é usar as funções <a href="http://php.net/manual/en/function.printf.php"><code>printf()</code></a> ou <a href="http://php.net/manual/en/function.sprintf.php"><code>sprintf()</code></a> com <em>tokens</em> de substituição para incluir variáveis:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
_e( &quot;I love the $language language.&quot; ); // Incorrecto…
_e( 'I love the ' . $language . ' language.' ); // Incorrecto…
_e( 'I love the ' ); echo $language; _e( ' language.' ); // Incorrecto…

printf( __( 'I love the %s language.' ), $language ); // CORRECTO!
?&gt;
</pre>
<p>Aqui, uma possível tradução portuguesa seria “Adoro a língua %s.” havendo uma posterior substituição do <em>token</em> <code>%s</code> pelo conteúdo da variável.</p>
<h3 id="contextualizaodetraduesambguas">Contextualização de traduções ambíguas</h3>
<p>No seguimento do exemplo acima, vamos supor que a variável <code>$language</code> é preenchida com o valor obtido de um formulário contendo as seguintes opções (traduzidas):</p>
<ul>
<li><code>__( 'English' )</code></li>
<li><code>__( 'Portuguese' )</code></li>
<li><code>__( 'French' )</code></li>
</ul>
<p>Embora se trate de uma substituição directa em inglês, ocorrem várias dúvidas na hora de traduzir estas palavras. <em>“Portuguese”</em> representa um substantivo ou um adjectivo? Refere-se ao género masculino ou ao feminino? É singular ou plural?</p>
<p>É essencial contextualizar estas <em>strings</em> e, para isso, o WordPress tem definida a função <a href="http://codex.wordpress.org/Function_Reference/_x"><code>_x()</code></a>. Livres de ambiguidades, as <em>strings</em> traduzidas com <code>_x()</code> teriam o seguinte aspecto:</p>
<ul>
<li><code>_x( 'English', 'I love the X language' )</code></li>
<li><code>_x( 'Portuguese', 'I love the X language' )</code></li>
<li><code>_x( 'French', 'I love the X language' )</code></li>
</ul>
<p>A descrição contextual do segundo parâmetro deve evitar referências à gramática. Indicar, em vez do contexto, que se trata de um “adjectivo feminino singular” não significa nada para certos tradutores e pode induzir outros em erro. Há línguas em que o conceito de adjectivo não existe. Noutras, a tradução do adjectivo pode ser uniforme, não ter género ou nem sequer ser um adjectivo. E, como já vimos, pode haver diferentes regras para as flexões numéricas.</p>
<p>Estas não são as únicas funções de tradução que o WordPress deixa ao dispor das equipas de desenvolvimento. Existem variações que combinam a funcionalidade das anteriores: <a href="http://codex.wordpress.org/Function_Reference/_ex"><code>_ex()</code></a>, por exemplo, imprime uma tradução contextual, e <a href="http://codex.wordpress.org/Function_Reference/_nx"><code>_nx()</code></a> retorna o texto de uma tradução contextual com flexão numérica. Como é hábito, recomendamos a consulta do <a href="http://codex.wordpress.org/">Codex</a> ao programador que melhor queira conhecer as ferramentas ao seu alcance.</p>
<h3 id="traduodehtml">Tradução de HTML</h3>
<p>A inclusão de estruturas HTML no texto a traduzir deve sempre ser ponderada. Embora seja prático e até recomendável incluir <em>tags</em> com semântica relevante, como <code>&lt;strong&gt;</code> ou <code>&lt;em&gt;</code>, nem todo o HTML deve ser exposto aos esforços dos tradutores.</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php _e( '&lt;div&gt;Hello &lt;em&gt;again&lt;/em&gt;, World!&lt;/div&gt;' ); ?&gt;
&lt;div&gt;&lt;?php _e( 'Hello &lt;em&gt;again&lt;/em&gt;, World!' ); ?&gt;&lt;/div&gt;
</pre>
<p>Na primeira linha do código acima, a inclusão da <em>tag</em> <code>&lt;div&gt;</code> no texto a traduzir é claramente desnecessária, mas faz todo o sentido deixar explícita para o tradutor a ênfase dada à palavra <em>“again”</em>.</p>
<p>Regra geral, <em>tags</em> que englobem a totalidade da <em>string</em> devem ser deixadas fora da tradução.</p>
<h3 id="traduodejavascript">Tradução de JavaScript</h3>
<p>A biblioteca gettext usada pelo WordPress contempla apenas a tradução de ficheiros PHP. Na eventualidade de ser necessário traduzir ficheiros JavaScript, o WordPress oferece a função <a href="http://codex.wordpress.org/Function_Reference/wp_localize_script"><code>wp_localize_script()</code></a>. Infelizmente, não é uma solução que preze a elegância dado que injecta código JavaScript no cabeçalho das páginas, violando os princípios do <a href="http://en.wikipedia.org/wiki/Unobtrusive_JavaScript">JavaScript não obstrutivo</a>.</p>
<p>Eis um exemplo de utilização:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// O script a traduzir deve ser incluído antes de chamar wp_localize_script():
wp_enqueue_script( 'my_script_handle' );

wp_localize_script(
    'my_script_handle',
    'MyTranslationObject',
    array(
        'greeting' =&gt; __( 'Hello, World!' ),
    )
);
?&gt;
</pre>
<p><code>wp_localize_script()</code> recebe o nome identificativo do <em>script</em> a traduzir (previamente registado e incluído na página), o nome do objecto JavaScript que irá conter o texto traduzido e um <em>array</em> associativo com uma correspondência entre chaves e texto traduzido com a função <code>__()</code>.</p>
<p>A diferença é que, aqui, as <em>strings</em> propriamente ditas não podem ocorrer no JavaScript, ou não serão traduzidas. O <em>script</em> deve antes referir-se ao objecto de tradução declarado (neste caso, <code>MyTranslationObject</code>) e ao nome da variável que corresponde à <em>string</em>. Assim:</p>
<pre class="brush: jscript; title: ; notranslate">
alert( 'Hello, World!' ); // Antes
alert( MyTranslationObject.greeting ); // Depois
</pre>
<h3 id="oprocessodetraduo">O processo de tradução</h3>
<p>Embora possamos ter todo o nosso projecto preparado para internacionalização, a tradução do texto não é automática. Aqui intervêm os tradutores que, sem acesso ao código e sem conhecimentos técnicos profundos, devem verter as <em>strings</em> do projecto para os vários idiomas de destino.</p>
<p>Antes de mais, será preciso compilar as <em>strings</em> do projecto através de um comando como o <em>xgettext</em>, embora a Automattic ofereça ao público <a href="http://i18n.svn.wordpress.org/tools/trunk/">ferramentas mais adequadas ao WordPress</a> no seu repositório SVN.<a class="footnote" id="fnref:1" title="see footnote" href="#fn:1">[1]</a> Usando estes últimos, em particular o <em>script makepot.php</em>, as <em>strings</em> de um <em>plugin</em> podem ser obtidas com um simples:</p>
<pre class="brush: plain; title: ; notranslate">
php makepot.php wp-plugin &lt;directoria do plugin&gt;
</pre>
<p>(No caso de um tema, devemos substituir “wp-plugin” na linha acima por “wp-theme”.)</p>
<p>Independentemente da ferramenta usada, o que todas fazem é percorrer os ficheiros do projecto e identificar o conteúdo textual associado a funções de tradução. O fruto deste processo de recolha é um ficheiro modelo com a extensão POT <em>(Portable Object Template)</em>, que deve ser entregue aos tradutores da solução para que estes possam gerar as versões localizadas.</p>
<p>A tradução é feita manualmente<a class="footnote" id="fnref:2" title="see footnote" href="#fn:2">[2]</a> usando uma aplicação como o <a href="http://www.poedit.net/">Poedit</a>, compatível com os principais sistemas operativos, ou através de uma solução baseada na <em>web</em> como o <a href="http://wordpress.glotpress.com/">GlotPress</a>.</p>
<p>Este processo implica pegar no POT gerado anteriormente para criar ficheiros localizáveis com a extensão PO (<em>Portable Object</em>, editável) e MO (<em>Machine Object</em>, versão compilada para utilização pelo gettext). Uma vez traduzidos, estes novos ficheiros devem ser enviados de volta à equipa de desenvolvimento para inclusão no projecto.</p>
<p>Quando o texto de um projecto sofre alterações, basta voltar a gerar o POT como de início, e importá-lo no <em>software</em> de tradução de modo a que as novidades sejam propagadas para os ficheiros PO e MO de cada idioma.</p>
<h3 id="referncias">Referências</h3>
<p>Para informações adicionais sobre internacionalização em WordPress, recomendamos as seguintes leituras e recursos:</p>
<ul>
<li><a href="http://codex.wordpress.org/I18n_for_WordPress_Developers">“i18n for Developers”</a> <em>(WordPress Codex)</em></li>
<li><a href="http://www.slideshare.net/jfontainhas/lost-in-translation-wordpress-and-i18n">“Lost in Translation”</a>, por Zé Fontainhas</li>
<li><a href="http://ottopress.com/2012/internationalization-youre-probably-doing-it-wrong/">“Internationalization: You’re probably doing it wrong”</a>, por Samuel “Otto” Wood</li>
<li><a href="http://wppolyglots.wordpress.com/">WP Polyglots</a></li>
</ul>
<p><em>Ça va?</em> Para quaisquer dúvidas ou sugestões, <a href="https://www.facebook.com/logOpenSourceConsulting">a nossa página no Facebook</a> está, como sempre, aberta à participação dos leitores.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">Para os <a href="http://wordpress.org/extend/plugins/"><em>plugins</em> publicados no WordPress.org</a>, a recolha das <em>strings</em> de tradução é automática e o ficheiro modelo pode ser obtido através da área de administração. <a class="reversefootnote" title="return to article" href="#fnref:1"> ↩</a></li>
<li id="fn:2">Pode, no entanto, haver recurso a memórias de tradução ou APIs externas para semi-automatizar o processo. <a class="reversefootnote" title="return to article" href="#fnref:2"> ↩</a></li>
</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/11/wordpress-i18n/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How Visual Mode changed my life</title>
		<link>http://log.pt/blog/2012/10/how-visual-mode-changed-my-life/</link>
		<comments>http://log.pt/blog/2012/10/how-visual-mode-changed-my-life/#comments</comments>
		<pubDate>Mon, 22 Oct 2012 12:06:41 +0000</pubDate>
		<dc:creator>Susana Salgado</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[Communication]]></category>
		<category><![CDATA[Sketching]]></category>
		<category><![CDATA[Visual]]></category>

		<guid isPermaLink="false">http://log.pt/?p=16562</guid>
		<description><![CDATA[Visual thinking, visual communication, visual note taking, sketching. How many times have we heard this jargon before? All around us, friends and colleagues are actually using these techniques and improving their professional or personal lives. In my case, the bug hit me a few months ago, when I started reading Gamestorming. My goal was simple: [...]]]></description>
				<content:encoded><![CDATA[<p><em>Visual thinking</em>, <em>visual communication</em>, <em>visual note taking</em>, <em>sketching</em>.
How many times have we heard this jargon before? All around us, friends and colleagues
are actually using these techniques and improving their
professional or personal lives.</p>
<p>In my case, the bug hit me a few months ago, when I started reading
<a title="Gamestorming" href="http://www.gogamestorm.com/" target="_blank"><em>Gamestorming</em></a>. My goal was simple: to learn new dynamics in order to develop
business and teams. Yet, somehow, I was stuck on visual and entered in what I
like to call <strong>visual mode</strong>. And now, I&#8217;m hooked!</p>
<p>Don&#8217;t get me wrong: I&#8217;m not an expert in this field! I&#8217;m just having fun learning more about it and I want to share my personal experience on how this process started working
for me.</p>
<p>I hope I can inspire some of you, if only just a bit, to start looking at the
world in a different and funny way!</p>
<p><strong>1. Start Sketching… not drawing</strong></p>
<p>Sketch everything you want, even if it looks ridiculous. Were you successful in
conveying the message? Great! You did it! Not really? Well, keep trying!
But don&#8217;t try to achieve artistic perfection except, of course, if you are an
accomplished artist. You won&#8217;t succeed and you&#8217;ll feel frustrated.
In order to sketch you don&#8217;t need to know how to draw. Trust me. I can’t REALLY
draw, dogs look like crocodiles!</p>
<p><img class="alignnone  wp-image-16622" title="#1" src="http://log.pt/wp-content/uploads/2012/10/IMG_00631-e1349912923785-600x453.jpg?4c9b33" alt="" width="480" height="362" /><strong></strong></p>
<p><strong>2. Transforme the way you work.</strong></p>
<p>I got rid of my to-do list. But wait&#8230; I still have a bunch of things I need to do. I&#8217;ve just
transformed it in something more appealing and easy to ready. It works for me.</p>
<p>It&#8217;s a small white cardboard with the things I need to do or think about, in different colored and shaped post-it notes.</p>
<p><img class="alignnone  wp-image-16652" title="#2" src="http://log.pt/wp-content/uploads/2012/10/IMG_0064-e1349913053427-600x449.jpg?4c9b33" alt="" width="480" height="359" /></p>
<p><em>OTB means Outside the Box.</em></p>
<p><strong>3. Get up and head to the whiteboard.</strong></p>
<p>Don&#8217;t just sit there craving for it. Get up and go. Sketch, write, underline, draw a
box, or whatever comes through your mind. Expose yourself.</p>
<p><img class="alignnone size-large wp-image-16662" title="#3" src="http://log.pt/wp-content/uploads/2012/10/IMG_0065-e1349913255847-600x403.jpg?4c9b33" alt="" width="600" height="403" /></p>
<p><strong>4. Pratice. Pratice. Pratice.</strong></p>
<p>Your cell phone drawings will look less and less like bricks.</p>
<p><img class="alignnone  wp-image-16672" title="#4" src="http://log.pt/wp-content/uploads/2012/10/IMG_0066-e1349913322311-600x282.jpg?4c9b33" alt="" width="480" height="226" /></p>
<p><strong>5. Embrace digital.</strong></p>
<p>iPad, tablets.
I use <a title="Noteshelf app" href="http://www.fluidtouch.biz/noteshelf/" target="_blank">Noteshelf</a>, <a title="Showme app" href="http://www.showme.com/" target="_blank">Showme</a>, <a title="Inkflow app" href="http://www.qrayon.com/home/inkflow/" target="_blank">Inkflow</a>.
And a <a title="Bamboo pen" href="http://www.amazon.co.uk/Wacom-Bamboo-Solo-Stylus-Black/dp/B0050OFF42/ref=sr_1_2?s=computers&amp;ie=UTF8&amp;qid=1349907140&amp;sr=1-2" target="_blank">Bamboo pen</a>.</p>
<p><img class="alignnone  wp-image-16682" title="#5" src="http://log.pt/wp-content/uploads/2012/10/IMG_0067-e1349913394748-600x425.jpg?4c9b33" alt="" width="480" height="340" /></p>
<p><strong>6. …but don&#8217;t forget paper!</strong></p>
<p>Paper is visual mode’s best friend.
I use a <a title="Micron Sakura" href="http://www.sakuraofamerica.com/Pen-Archival" target="_blank">Micron Sakura</a> 0.45mm black pen and a few <a title="Tombow pens" href="http://www.tomboweurope.com/abt-dual-brush-pen-en.html?category=painting-and-drawing" target="_blank">Tombow ABT Dual Brush</a> colour pens, for my thinking process. What am I talking about? Well, for instance, when I go to a
meeting, I draw some sketches, write notes, etc. For this, I will only use a black pen.
Afterwards, I will colour my notes during my information workflow recap. It&#8217;s a
way to absorb or consolidate the whole data.</p>
<p>Forget pencils. Remember: the goal is not to draw perfectly.</p>
<p><img class="alignnone  wp-image-16702" title="#6" src="http://log.pt/wp-content/uploads/2012/10/IMG_0068-e1349913497624-507x480.jpg?4c9b33" alt="" width="456" height="432" /></p>
<p><strong>7. Transform the meetings you&#8217;re in.</strong></p>
<p>Whiteboards, post its, markers, colour papers, whatever. Lose the laptop.</p>
<p><img class="alignnone  wp-image-16722" title="#7" src="http://log.pt/wp-content/uploads/2012/10/IMG_0069-e1349913604930-600x321.jpg?4c9b33" alt="" width="480" height="257" /></p>
<p><strong>8. Start right away.</strong></p>
<p>You don&#8217;t need to read 10 books, 20 articles, or watch 5 films to get started. You just
need to be inspired. Start small and at your own pace.
Grab a pen, some paper and go for it!</p>
<p><img class="alignnone  wp-image-16732" title="#8" src="http://log.pt/wp-content/uploads/2012/10/IMG_0070-e1349913674574-427x480.jpg?4c9b33" alt="" width="384" height="432" /></p>
<p><strong>9. Watch out for the overwhelmed syndrome.</strong></p>
<p>This is a tricky one, at least for me! When I start reading or learning about something I
really like, I tend to embark in a long arduous search for books, articles and videos that cover the subject. What happens when I do that? I get so overloaded with information that, at some point, I don&#8217;t really know where to start or even what to do!</p>
<p>Don&#8217;t overexplore it. Go with the flow. You have to have your own rhythm.</p>
<p><img class="alignnone  wp-image-16742" title="#9" src="http://log.pt/wp-content/uploads/2012/10/IMG_0071-e1349913733700-346x480.jpg?4c9b33" alt="" width="277" height="384" /></p>
<p><strong>10. Have fun!</strong></p>
<p>Indeed. If you&#8217;re not having fun, forget it! Focus on what you really love
and what brings you joy.</p>
<p><img class="alignnone  wp-image-16762" title="#10" src="http://log.pt/wp-content/uploads/2012/10/IMG_00721-e1349913889331-502x480.jpg?4c9b33" alt="" width="402" height="384" /></p>
<p>Remember, this is a way of thinking. It will take time. However, when you
least expect it you&#8217;ll start thinking visually. And this is all you need to do in
order to enter the visual mode and becoming addicted. I certainly did!</p>
<p><strong>Wanna go visual?</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/10/how-visual-mode-changed-my-life/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Desenvolvimento sobre WordPress (5) — Plugins</title>
		<link>http://log.pt/blog/2012/10/wordpress-tutorial-plugins/</link>
		<comments>http://log.pt/blog/2012/10/wordpress-tutorial-plugins/#comments</comments>
		<pubDate>Fri, 12 Oct 2012 17:09:04 +0000</pubDate>
		<dc:creator>Luís Rodrigues</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://log.pt/?p=14952</guid>
		<description><![CDATA[Nas primeiras quatro partes desta série de artigos focados no desenvolvimento sobre WordPress, introduzimos um conjunto de APIs e mecanismos internos da plataforma (acções, filtros, shortcodes e widgets) que qualquer programador PHP pode alavancar para melhorar e complementar as funcionalidades base do WordPress. Todavia, os desenvolvimentos foram apresentados no contexto da alteração de um tema, [...]]]></description>
				<content:encoded><![CDATA[<p>Nas primeiras quatro partes desta série de artigos focados no desenvolvimento sobre WordPress, introduzimos um conjunto de APIs e mecanismos internos da plataforma (<a href="http://log.pt/blog/2012/01/wordpress-tutorial-actions/">acções</a>, <a href="http://log.pt/blog/2012/01/wordpress-tutorial-filters/">filtros</a>, <a href="http://log.pt/blog/2012/01/wordpress-tutorial-shortcodes/"><em>shortcodes</em></a> e <a href="http://log.pt/blog/2012/01/wordpress-tutorial-widgets/"><em>widgets</em></a>) que qualquer programador PHP pode alavancar para melhorar e complementar as funcionalidades base do WordPress.</p>
<p>Todavia, os desenvolvimentos foram apresentados no contexto da alteração de um tema, escrevendo código no seu ficheiro <em>functions.php</em>. Embora haja várias situações em que esta utilização é perfeitamente legítima e até ideal, a lógica da plataforma deve estar tão separada quanto possível das camadas de apresentação. Pretendemos que o código desenvolvido seja independente do tema, que possa ser reutilizado por outros e que o <em>site</em> não perca funcionalidade se por acaso o tema mudar.</p>
<h3 id="omeuprimeiroplugin">O primeiro <em>plugin</em></h3>
<p>Felizmente, é muito fácil migrar código existente no tema para um <em>plugin</em>. Basta criar uma pasta na directoria <em>wp-content/plugins/</em> e, lá dentro, um ficheiro PHP contendo um cabeçalho especial. O código, tal como surge no <em>functions.php</em>, pode ser movido directamente para este ficheiro sem que nada deixe de funcionar (desde que se lembrem de activar o novo <em>plugin</em> na área de administração!)</p>
<p>Por exemplo, colocando o seguinte num novo ficheiro <em>wp-content/plugins/primeiro-plugin/primeiro-plugin.php</em>:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/*
Plugin Name: O meu primeiro plugin
Description: Este é o meu primeiro plugin.
Version: 1.0
Author: O meu nome
Author URI: http://log.pt/
*/

// Aqui vai o código do meu plugin...

?&gt;
</pre>
<p></code></pre>
<p>O resultado pode ser consultado na página de administração de <em>plugins</em>.</p>
<p><div id="attachment_14972" class="wp-caption aligncenter" style="width: 520px"><img src="http://log.pt/wp-content/uploads/2012/07/007-01-my-first-plugin.png?4c9b33" alt="O meu primeiro plugin" title="O meu primeiro plugin" width="510" height="72" class="size-full wp-image-14972" /><p class="wp-caption-text">O plugin na página de administração do WordPress.</p></div></p>
<h3 id="osmeusfavoritos"><em>Plugin</em> de favoritos</h3>
<p>Naturalmente, o nosso primeiro <em>plugin</em> não fará nada se não contiver código. Para consolidar o que aprendemos nos artigos anteriores, bem como introduzir alguns conceitos novos, como o carregamento de ficheiros JavaScript e CSS, ou a invocação de pedidos AJAX, vamos explicar como desenvolver um <em>plugin</em> que permita aos visitantes assinalar artigos e páginas do site como favoritas.</p>
<p>Para este <em>plugin</em>, iremos desenvolver as seguintes componentes:</p>
<ul>
<li>Um <em>shortcode</em> com um botão para adicionar ou retirar uma página dos favoritos;</li>
<li>Um <em>shortcode</em> que permita mostrar, em qualquer página, todos os favoritos do visitante;</li>
<li>Uma <em>widget</em> que permita mostrar um número configurável de páginas favoritas.</li>
</ul>
<h3 id="aprimeirapedra">A primeira pedra</h3>
<p>Lancemos pois as fundações sobre as quais o nosso <em>plugin</em> será construído. Pegando no exemplo anterior, adicionamos o seguinte código:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/*
Plugin Name: My Favorites
Plugin URI: http://log.pt/
Description: My favorite posts plugin.
Version: 1.0
Author: log.OSCON
Author URI: http://log.pt/
*/

define( 'MY_FAVORITES_VERSION', '1.0' );

if (!class_exists( 'MyFavoritesPlugin' )) :

class MyFavoritesPlugin {

    public static function activate ()
    {
        // Método para activação do plugin
    }

    public static function init ()
    {
        // Método para inicialização do plugin

        load_plugin_textdomain( 'my-favorites', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
    }
}

endif;

if (class_exists( 'MyFavoritesPlugin' ))
{
    /* Register hooks */
    register_activation_hook( __FILE__, array( 'MyFavoritesPlugin', 'activate' ) );

    /* Init actions */
    add_action( 'init', array( 'MyFavoritesPlugin', 'init' ) );
}
</pre>
<p></code></pre>
<p>Aqui apenas registarmos dois <em>hooks</em>, o novo <a href="http://codex.wordpress.org/Function_Reference/register_activation_hook"><code>register_activation_hook()</code></a>, para nos permitir executar código aquando da activação do <em>plugin</em>, e o já conhecido <a href="http://codex.wordpress.org/Function_Reference/add_action"><code>add_action()</code></a> que associará o nosso método <code>MyFavoritesPlugin::init()</code> ao passo de inicialização da plataforma.</p>
<p>De notar que não estamos a usar funções como fizemos até agora, mas sim uma classe com vários métodos estáticos. Fazemos isto por causa de uma limitação histórica da linguagem PHP, que até há bem pouco tempo não suportava <em>namespaces</em>, fazendo com que todas as funções existissem no mesmo escopo global. Definir novas funções num <em>plugin</em> é, por isso, um exercício arriscado, uma vez que nomes demasiado comuns podem facilmente colidir com as funções já definidas noutros pontos da plataforma, ocasionando erros no servidor.</p>
<p>A função <code>add_action()</code> (e outras semelhantes) que aceitavam uma <em>string</em> com o nome da função de <em>callback</em> aceitam também um <em>array</em> com a estrutura <code>array( '&lt;nome ou instância da classe&gt;', '&lt;nome do método&gt;' )</code>.</p>
<p>No método de inicialização estamos já a fazer uma chamada a <a href="http://codex.wordpress.org/Function_Reference/load_plugin_textdomain"><code>load_plugin_textdomain()</code></a>. Esta função declara o domínio de tradução &#8220;my-favorites&#8221; para as várias <em>strings</em> do <em>plugin</em>. Não nos iremos demorar aqui<a href="#fn:1" id="fnref:1" title="see footnote" class="footnote">[1]</a>, excepto para dizer que é boa prática deixar o nosso código preparado para internacionalização.</p>
<h3 id="javascriptecss">JavaScript e CSS</h3>
<p>Tanto temas como <em>plugins</em> podem injectar o seu próprio JavaScript e estilos CSS nas páginas geradas pelo WordPress. Tal é feito com recurso aos pares de funções <a href="http://codex.wordpress.org/Function_Reference/wp_register_script"><code>wp_register_script()</code></a>/<a href="http://codex.wordpress.org/Function_Reference/wp_register_script"><code>wp_register_script()</code></a> e <a href="http://codex.wordpress.org/Function_Reference/wp_register_style"><code>wp_register_style()</code></a>/<a href="http://codex.wordpress.org/Function_Reference/wp_register_style"><code>wp_register_style()</code></a>. Acrescentemos o seguinte código ao final do nosso método <code>MyFavoritesPlugin::init()</code>:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
wp_register_style(
    'my-favorites',
    plugins_url( 'css/default.css', __FILE__ ),
    false,
    MY_FAVORITES_VERSION,
    'screen'
);

wp_enqueue_style( 'my-favorites' );

wp_register_script(
    'my-favorites',
    plugins_url( 'js/default.js', __FILE__ ),
    array( 'jquery' ),
    MY_FAVORITES_VERSION,
    false
);

wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'my-favorites' );
</pre>
<p></code></pre>
<p><code>wp_register_script()</code> indica ao WordPress que existe um novo <em>script</em> JavaScript que pode ser incluídos nas páginas usando <code>wp_enqueue_script()</code>. Este <em>script</em> deve ter um nome e um ficheiro (os dois primeiros parâmetros da função). Podemos opcionalmente indicar dependências (no nosso caso, o ficheiro JavaScript depende do jQuery, que vem incluído na plataforma, pelo que não é preciso declará-lo). O quarto parâmetro indica a versão do <em>script</em>, que não sendo estritamente necessária, é útil para evitar que a <em>cache</em> dos <em>browsers</em> carreguem uma versão antiga do ficheiro quando este muda. Por fim, podemos indicar através de um valor booleano se o <em>script</em> é carregado no cabeçalho (<em>false</em>) ou no rodapé (<em>true</em>) da página.</p>
<p>No fim, incluímos tanto o jQuery como o nosso <em>script</em> na página gerada pelo WordPress usando <code>wp_enqueue_script()</code>.</p>
<p>O processo para os estilos é em tudo semelhante ao anterior, a diferença é que o último parâmetro indica, não a área de carregamento, mas o tipo de CSS a incluir (por exemplo, &#8220;screen&#8221; ou &#8220;print&#8221;).</p>
<p>Em ambos os casos, usamos a função do WordPress <a href="http://codex.wordpress.org/Function_Reference/plugins_url"><code>plugins_url()</code></a> para nos indicar o URL dos ficheiros.</p>
<p>Os ficheiros JavaScript e CSS usados neste projecto são fornecidos abaixo no arquivo ZIP com o código do <em>plugin</em>. Estes controlam a apresentação dos botões e listas de favoritos, bem como a funcionalidade AJAX que serve para assinalar determinada página como favorita.</p>
<h3 id="favoritosdoutilizador">Favoritos do utilizador</h3>
<p>Concentremo-nos agora nos métodos para escrita e obtenção da lista de favoritos de um dado utilizador. Há aqui um cuidado especial a ter porque este utilizador pode ou não estar autenticado, e temos de garantir um mínimo de persistência dos dados em qualquer dos casos.</p>
<p>Para um utilizador não-autenticado, não há outra forma de guardar a lista de favoritos sem ser em <em>cookie</em>. Aos outros, contudo, podemos associar dados através de um conjunto de funções oferecidas pelo WordPress, entre as quais se contam <a href="http://codex.wordpress.org/Function_Reference/get_user_meta"><code>get_user_meta()</code></a> e <a href="http://codex.wordpress.org/Function_Reference/update_user_meta"><code>update_user_meta()</code></a>.</p>
<p>Começando pelo nosso método de guardar a lista de favoritos do utilizador que recebe um nome identificativo da lista (neste projecto iremos usar apenas &#8220;my-favorites&#8221;), o identificador do utilizador e a lista com os IDs dos artigos e páginas favoritas, teremos:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
private static function set_user_post_list ( $listname, $user_id = null, $list = array() ) {
    if (!$list) $list = array();
    /* Garantir que os identificadores são todos numéricos: */
    $list = array_map( 'intval', $list );

    if ($user_id)
    {
        /* O utilizador está autenticado, guardar lista em base de dados: */
        update_user_meta( $user_id, $listname, $list );
        /* Limpar a cache do utilizador para garantir a actualidade dos dados apresentados: */
        wp_cache_delete( $user_id, 'users' );    
        wp_cache_delete( $user_id, 'usermeta' );
    }
    else
    {
        /* O utilizador não está autenticado, guardar lista em cookie: */
        setcookie( $listname, serialize( $list ), time() + 365 * 24 * 60 * 60, COOKIEPATH, COOKIE_DOMAIN, false );
    }
}
</pre>
<p></code></pre>
<p>Codificamos agora a operação inversa, de obter uma lista de artigos com base no seu nome e, opcionalmente, no identificador do utilizador:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
private static function get_user_post_list ( $listname, $user_id = null ) {
    $list = ($user_id) ? get_user_meta( $user_id, $listname, true )
                       : unserialize( $_COOKIE[$listname] );
    if (!$list) $list = array();
    return array_map( 'intval', $list );
}
</pre>
<p></code></pre>
<p>Por fim, vamos desenvolver um método que obtenha a lista de favoritos do visitante actual e imprima o HTML necessário à sua apresentação. Este método pode ser invocado directamente, mas também será útil para as funções de <em>shortcode</em> e AJAX que iremos desenvolver de seguida:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
public static function favorite_list ( $atts ) {
    global $user_ID;

    $atts = shortcode_atts( array(
        'paged'          =&gt; 1,
        'posts_per_page' =&gt; 10,
        'order'          =&gt; 'ASC',
        'orderby'        =&gt; 'title',
    ), $atts );

    extract( $atts );

    // Validação básica:
    $paged = (int) $paged;
    $posts_per_page = (int) $posts_per_page;

    $favorites = array_map( 'intval', self::get_user_post_list( 'my_favorites', $user_ID ) );

    if (empty( $favorites ))
    {
        _e( 'You have no favorite posts.', 'my-favorites' );
    }
    else
    {
        $query = new WP_Query( array(
            'post_type'      =&gt; get_post_types( array( 'public' =&gt; true ) ),
            'post__in'       =&gt; $favorites,
            'paged'          =&gt; $paged,
            'posts_per_page' =&gt; $posts_per_page,
            'post_status'    =&gt; 'publish',
            'order'          =&gt; $order,
            'orderby'        =&gt; $orderby,
        ) );

        if ($query-&gt;have_posts()) : ?&gt;          
            &lt;ul class=&quot;fav-list&quot;&gt;
            &lt;?php while ($query-&gt;have_posts()) : $query-&gt;the_post(); ?&gt;
                &lt;li&gt;
                    &lt;?php my_favorite_toggle_button(); ?&gt;
                    &lt;a href=&quot;&lt;?php the_permalink() ?&gt;&quot; rel=&quot;bookmark&quot; class=&quot;favorite-link&quot; title=&quot;&lt;?php the_title(); ?&gt;&quot;&gt;&lt;?php the_title(); ?&gt;&lt;/a&gt;
                &lt;/li&gt;
            &lt;?php endwhile; ?&gt;
            &lt;/ul&gt;
        &lt;?php endif;
    }
}
</pre>
<p></code></pre>
<h3 id="inclusodecdigophp">Inclusão de código PHP</h3>
<p>O código PHP de um <em>plugin</em> não tem de estar todo no mesmo ficheiro. Para não complicar a leitura e a localização de código, vamos incluir uma série de ficheiros adicionais com uma ajuda da função <a href="http://codex.wordpress.org/Function_Reference/plugin_dir_path"><code>plugin_dir_path()</code></a> para obter o caminho.</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
if (class_exists( 'MyFavoritesPlugin' ))
{
    /* Register hooks */
    register_activation_hook( __FILE__, array( 'MyFavoritesPlugin', 'activate' ) );

    /* Init actions */
    add_action( 'init', array( 'MyFavoritesPlugin', 'init' ) );

    require_once( plugin_dir_path( __FILE__ ) . 'includes/functions.php' );
    require_once( plugin_dir_path( __FILE__ ) . 'includes/shortcodes.php' );
    require_once( plugin_dir_path( __FILE__ ) . 'includes/widgets.php' );
}
</pre>
<p></code></pre>
<h3 id="funes">Funções</h3>
<p>Feito isto, podemos agora criar um ficheiro em <em>includes/functions.php</em> contendo a declaração de funções para imprimir o botão de favoritos e a lista de artigos.</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
    &lt;?php /* File: includes/functions.php */

function my_favorite_toggle_button ( $post_id = null, $atts = array() )
{
    global $user_ID;

    $atts = extract( shortcode_atts( array(
        'ajax'           =&gt; false,
    ), $atts ) );

    if (!isset( $post_id )) {
        $post_id = get_the_ID();
    }

    if (!$post_id) {
        error_log( __FUNCTION__ . ' must be called within a post loop.' );
        return;
    }

    $favorites  = MyFavoritesPlugin::get_user_post_list( 'my_favorites', $user_ID );

    $tooltip    = (in_array( $post_id, $favorites ))
                    ? __( 'Remove from favorites', 'my-favorites' )
                    : __( 'Add to favorites', 'my-favorites' );

    $class      = (in_array( $post_id, $favorites )) ? ' favorite-post-on' : ' favorite-post-off';

    if (!is_user_logged_in())
    {
        $class  .= ' guest';
    }

    if (!$ajax &amp;&amp; (!is_user_logged_in() || defined( WP_CACHE ) &amp;&amp; WP_CACHE))
    {
        // Update button following AJAX call:
        $class  .= ' cache-workaround';
    }

    $class .= &quot; size-24&quot;;

    echo '&lt;div class=&quot;my-favorite&quot;&gt;';

    echo '&lt;a href=&quot;#&quot; class=&quot;favorite-post-toggle ' . $class . ' my-favorite-post-' . $post_id . '&quot;'
        . ' id=&quot;my-favorite-post-' . $post_id . '&quot; title=&quot;' . esc_attr( $tooltip ) . '&quot;&gt;';

    echo '&lt;span class=&quot;favorite-post-on-button&quot;&gt;';
    echo '&lt;span class=&quot;label&quot;&gt;';
    _e( 'Remove from favorites', 'my-favorites' );
    echo '&lt;/span&gt;';
    echo '&lt;/span&gt;';

    echo '&lt;span class=&quot;favorite-post-off-button&quot;&gt;';
    echo '&lt;span class=&quot;label&quot;&gt;';
    _e( 'Add to favorites', 'my-favorites' );
    echo '&lt;/span&gt;';
    echo '&lt;/span&gt;';

    echo '&lt;/a&gt;';

    echo '&lt;/div&gt;';
}

function my_favorite_list ( $atts ) {
    $atts = shortcode_atts( array(
        'paged'          =&gt; 1,
        'posts_per_page' =&gt; 10,
        'order'          =&gt; 'ASC',
        'orderby'        =&gt; 'post__in',
        'ajax'           =&gt; false,
    ), $atts );

    $class      = &quot;&quot;;
    $data_attr  = &quot;&quot;;

    if (!is_user_logged_in() || (defined( WP_CACHE ) &amp;&amp; WP_CACHE)) {
        $class .= &quot; cache-workaround&quot;;
    }

    foreach ($atts as $k =&gt; $v) {
        $data_attr .= ' data-' . preg_replace( '/[^A-Za-z0-9]/', '-', $k ) . '=&quot;' . esc_attr( $v ) . '&quot;' ;
    }

    echo '&lt;div class=&quot;my-favorite-post-list' . $class . '&quot;' . $data_attr . '&gt;';

    if (!is_user_logged_in() || (defined( WP_CACHE ) &amp;&amp; WP_CACHE)) {
        // Insert AJAX list placeholder:
        _e( 'Fetching favorites...', 'my-favorites' );
    } else {
        // No cache, so print list directly:
        MyFavoritesPlugin::favorite_list( $atts );
    }

    echo '&lt;/div&gt;';
}

?&gt;
</pre>
<p></code></pre>
<p>Apesar das referências a AJAX no código, ainda não estamos a fazer nada com resultados práticos. Queremos apenas definir um conjunto de classes HTML que irão servir de fundação para a posterior associação de eventos e actualização de conteúdos através de JavaScript.</p>
<p>Estas referências a AJAX aparecem associadas a verificações do estado da <em>cache</em> da plataforma. Isto porque queremos evitar que a apresentação das listas de favoritos de um determinado utilizador fique armazenada em <em>cache</em> pelo WordPress, evitando também que os dados sejam indevidamente apresentados a terceiros. Ao apresentar estes dados preenchidos em resultado de um pedido AJAX, estamos a garantir que a <em>cache</em> do sistema não os afecta (o que não significa que os pedidos AJAX não possam ficar guardados em <em>cache</em>, se necessário!).</p>
<h3 id="shortcodes">Shortcodes</h3>
<p>Dado que vamos reaproveitar o código das funções que acabámos de definir, o registo dos <em>shortcodes</em> torna-se muito mais simplificado:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
&lt;?php /* File: includes/shortcodes.php */

function my_favorite_toggle_button_shortcode ( $atts ) {
    extract( shortcode_atts( array(
        'post_id' =&gt; get_the_ID(),
    ), $atts ) );

    ob_start();
    my_favorite_toggle_button( $post_id );
    $output = ob_get_clean();

    return $output;
}

add_shortcode( 'my_favorite_button', 'my_favorite_toggle_button_shortcode' );

function my_favorite_list_shortcode ( $atts ) {
    ob_start();
    my_favorite_list( $atts );
    $output = ob_get_clean();

    return $output;
}

add_shortcode( 'my_favorite_list', 'my_favorite_list_shortcode' );

?&gt;
</pre>
<p></code></pre>
<p>…ficando assim com os <em>shortcodes</em> <code>[my_favorite_button]</code>, que acrescenta um botão à página, e <code>[my_favorite_list]</code>, para exibir a lista de favoritos do utilizador.</p>
<h3 id="widgets">Widgets</h3>
<p>O registo da <em>widget</em> de favoritos em nada varia do que aprendemos no artigo anterior desta série:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
 &lt;?php /* File: includes/widgets.php */

class MyFavorite_Posts_Widget extends WP_Widget {

    public function MyFavorite_Posts_Widget () {
        parent::__construct(
            'my_favorite_posts', // Identificador
            'My Favorite Posts', // Nome
            array( 'description' =&gt; __( 'Displays a list of posts favorited by the user.' ), ) // Argumentos
        );
    }

    public function form ( $instance ) {
        $title    = isset( $instance['title'] )    ? esc_attr( $instance['title'] )  : '';
        $number   = isset( $instance['number'] )   ? absint( $instance['number'] )   : 5;
        ?&gt;
        &lt;p&gt;
            &lt;label for=&quot;&lt;?php echo $this-&gt;get_field_id( 'title' ); ?&gt;&quot;&gt;&lt;?php _e( 'Title:' ); ?&gt;&lt;/label&gt; 
            &lt;input class=&quot;widefat&quot; id=&quot;&lt;?php echo $this-&gt;get_field_id( 'title' ); ?&gt;&quot; name=&quot;&lt;?php echo $this-&gt;get_field_name( 'title' ); ?&gt;&quot; type=&quot;text&quot; value=&quot;&lt;?php echo esc_attr( $title ); ?&gt;&quot; /&gt;
        &lt;/p&gt;
        &lt;p&gt;
            &lt;label for=&quot;&lt;?php echo $this-&gt;get_field_id( 'title' ); ?&gt;&quot;&gt;&lt;?php _e( 'Number of posts to show:' ); ?&gt;&lt;/label&gt; 
            &lt;input id=&quot;&lt;?php echo $this-&gt;get_field_id( 'number' ); ?&gt;&quot; name=&quot;&lt;?php echo $this-&gt;get_field_name( 'number' ); ?&gt;&quot; type=&quot;text&quot; value=&quot;&lt;?php echo esc_attr( $number ); ?&gt;&quot; size=&quot;3&quot; /&gt;
        &lt;/p&gt;
        &lt;?php 
    }

    public function update ( $new_instance, $old_instance ) {
        $instance = (array) $old_instance;

        $instance['title']    = strip_tags( $new_instance['title'] );
        $instance['number']   = absint( $new_instance['number'] );

        return $instance;
    }

    public function widget ( $args, $instance ) {
        extract( $args );

        $number   = absint( $instance['number'] );
        $title    = apply_filters( 'widget_title', empty( $instance['title'] )
                  ? __( 'Favorite Posts', 'my-favorites' )
                  : $instance['title'], $instance, $this-&gt;id_base );

        if (!$number)
            $number = 5;

        ob_start();
        my_favorite_list( array( 'posts_per_page' =&gt; $number ) );
        $output = ob_get_clean();

        if (!empty( $output )) {
            echo $before_widget;
            if ($title)
                echo $before_title . $title . $after_title;
            echo $output;
            echo $after_widget;
        }
    }
}

add_action( 'widgets_init', create_function( '', 'register_widget( &quot;MyFavorite_Posts_Widget&quot; );' ) );
?&gt;
</pre>
<p></code></pre>
<h3 id="cachingeajax">Caching e AJAX</h3>
<p>Com o código anterior, temos um <em>plugin</em> quase funcional, faltando um elemento crucial: apesar de dispor de um botão, o utilizador não consegue ainda adicionar ou retirar um artigo da sua lista de favoritos. O botão irá depender de uma invocação AJAX para informar o WordPress da acção sem obrigar ao recarregamento da página.</p>
<p>Para isso já temos, no JavaScript fornecido com este artigo, </p>
<pre><code>
<pre class="brush: jscript; title: ; notranslate">
jQuery.post(
    FavoritesAjax.ajaxurl, // URL do WordPress
    {
        action:     'my_toggle_favorite', // Acção a executar
        post_id:    post_id
    },
    function (response) {
        if (response) {
            // Processamento da resposta e actualização do estado do botão
        }
    },
    'json'
);
</pre>
<p></code></pre>
<p>Há aqui dois elementos a ter em atenção.</p>
<p>O primeiro é o objecto <code>FavoritesAjax</code> que contém o URL do pedido. Este pode ser registado no final do nosso método <code>MyFavoritesPlugin::init</code>, logo a seguir ao registo de <em>scripts</em> com o WordPress, usando a função <a href="http://codex.wordpress.org/Function_Reference/wp_localize_script"><code>wp_localize_script()</code></a>.</p>
<p>Esta função serve para traduzir <em>strings</em> que ocorram num <em>script</em>, mas também para injectar outros valores JavaScript na página. Neste caso, queremos que o JavaScript conheça o URL usado pelo WordPress para todos os pedidos AJAX, presente em <em>wp-admin/admin_ajax.php</em> (apesar da localização do ficheiro, este não se limita ao uso por administradores):</p>
<pre><code>
<pre class="brush: plain; title: ; notranslate">
wp_localize_script(
    'my-favorites',
    'FavoritesAjax',
    array( 'ajaxurl' =&gt; admin_url( 'admin-ajax.php' ) )
);
</pre>
<p></code></pre>
<p>O segundo elemento é o <code>my_toggle_favorite</code> que é passado como valor do parâmetro <code>action</code> do pedido AJAX. É este que vai permitir ao WordPress relacionar o pedido com uma acção específica previamente registada na plataforma, no passo de inicialização do nosso <em>plugin</em>:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
add_action( 'wp_ajax_my_toggle_favorite'        , array( 'MyFavoritesPlugin', 'ajax_toggle_favorite' ) );
add_action( 'wp_ajax_nopriv_my_toggle_favorite' , array( 'MyFavoritesPlugin', 'ajax_toggle_favorite' ) );
</pre>
<p></code></pre>
<p>A função <code>add_action()</code> é já nossa conhecida, mas temos uma novidade na medida em que esta suporta dois tipos especiais para processamento de pedidos AJAX determinados pelo prefixo no nome da acção: <code>wp_ajax_</code> (para utilizadores autenticados) e <code>wp_ajax_nopriv_</code> (para utilizadores sem previlégios). O restante nome da acção é o valor do parâmetro <code>action</code> passado no pedido AJAX.</p>
<p>Aqui, definimos o <em>callback</em> da acção como <code>MyFavoritesPlugin::ajax_toggle_favorite()</code>. Vamos pois adicioná-lo à classe do <em>plugin</em>:</p>
<pre><code>
<pre class="brush: plain; title: ; notranslate">
public static function ajax_toggle_favorite ()
{
    global $user_ID;

    $favorites  = self::get_user_post_list( 'my_favorites', $user_ID );

    $output     = false;
    $post_id    = (int) $_POST['post_id'];
    $key        = array_search( $post_id, $favorites );

    if ($key === false or !isset( $key ))
    {
        // Add to favorites:
        array_unshift( $favorites, $post_id );
        $favorites = array_unique( $favorites );
        $output = $post_id;
    }
    else
    {
        // Remove from favorites:
        unset( $favorites[$key] ); 
        $output = 0;
    }

    self::set_user_post_list( 'my_favorites', $user_ID, $favorites );

    header( 'Content-Type: text/x-json' );
    echo json_encode( $output );
    die();
}
</pre>
<p></code></pre>
<p>Com isto, podemos finalmente observar o <em>plugin</em> em acção.</p>
<p><div id="attachment_14982" class="wp-caption aligncenter" style="width: 610px"><img src="http://log.pt/wp-content/uploads/2012/07/007-02-my-favorites-plugin-600x176.png?4c9b33" alt="Botão de favoritos no artigo e widget na barra lateral." title="Plugin de favoritos em acção" width="600" height="176" class="size-large wp-image-14982" /><p class="wp-caption-text">Botão de favoritos no artigo e widget na barra lateral.</p></div></p>
<p>Se a plataforma estiver a utilizar uma solução de <em>caching</em> como o <a href="http://wordpress.org/extend/plugins/wp-super-cache/">WP Super Cache</a> ou o <a href="http://wordpress.org/extend/plugins/w3-total-cache/">W3 Total Cache</a>, logo constatamos que apenas funciona convenientemente quando o utilizador se encontra autenticado. Para os restantes casos, a <em>cache</em> tende a guardar as páginas apresentadas, recusando-se a actualizar a lista de favoritos, pelo que precisamos de um método para contornar este comportamento. Aqui voltamos a recorrer ao AJAX para que, em circunstâncias em que a <em>cache</em> esteja activa, possamos continuar a obter conteúdos &#8220;frescos&#8221;.</p>
<p>No nosso método de inicialização, acrescentamos as seguintes linhas:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
add_action( 'wp_ajax_my_favorite_list'          , array( 'MyFavoritesPlugin', 'ajax_favorite_list' ) );
add_action( 'wp_ajax_nopriv_my_favorite_list'   , array( 'MyFavoritesPlugin', 'ajax_favorite_list' ) );
</pre>
<p></code></pre>
<p>E, na classe do <em>plugin</em>, definimos o método a invocar para as novas acções:</p>
<pre><code>
<pre class="brush: php; title: ; notranslate">
public static function ajax_favorite_list ()
{
    $paged          = (int) $_POST['paged'];
    $posts_per_page = (int) $_POST['posts_per_page'];
    $order          = $_POST['order'];
    $orderby        = $_POST['orderby'];

    $atts = array(
        'paged'             =&gt; $paged,
        'posts_per_page'    =&gt; $posts_per_page,
        'order'             =&gt; $order,
        'orderby'           =&gt; $orderby,
        'ajax'              =&gt; true,
    );

    header( 'Content-Type: text/html' );
    self::favorite_list( $atts );
    die();
}
</pre>
<p></code></pre>
<p>O princípio é semelhante ao que já vimos para a acção <code>my_toggle_favorite</code>, com a diferença que o pedido AJAX irá retornar todo o HTML da lista de favoritos para ser injectado na página por JavaScript, assim:</p>
<pre><code>
<pre class="brush: jscript; title: ; notranslate">
jQuery.post(
    FavoritesAjax.ajaxurl,
    {
        action:         'my_favorite_list',
        paged:          paged,
        posts_per_page: posts_per_page,
        order:          order,
        orderby:        orderby
    },
    function (response) {
        jQuery(el).html(response);
    },
    'html'
);
</pre>
<p></code></pre>
<h3 id="cdigodoplugin">Código do <em>plugin</em></h3>
<p>Por uma questão de brevidade, há uma série de dinâmicas que não se encontram aqui descritas, como a actualização dos eventos associados a certas hiperligações numa lista de favoritos injectada via AJAX, ou a capacidade de mostrar a lista dos favoritos pela ordem em que foram adicionados. Recomendamos a consulta do código fonte, disponível abaixo e pronto para ser utilizado em qualquer <em>site</em> WordPress.</p>
<ul>
<li><a href="http://log.pt/wp-content/uploads/2012/07/007-my-favorites-1.0.zip?4c9b33">Descarregar <em>My Favorites</em> 1.0</a> (ZIP, 31 KB)</li>
</ul>
<p>No próximo artigo desta série, continuaremos a desenvolver o <em>plugin</em> de favoritos para incluir páginas de configuração no painel de administração do WordPress. Como sempre, estamos disponíveis para dar resposta às questões e sugestões dos nossos leitores <a href="https://www.facebook.com/logOpenSourceConsulting">na nossa página no Facebook</a>.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Até porque pretendemos aprofundar o assunto num artigo futuro. <a href="#fnref:1" title="return to article" class="reversefootnote">&#160;&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/10/wordpress-tutorial-plugins/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Boas práticas de usabilidade no desenho de carrinhos de compras</title>
		<link>http://log.pt/blog/2012/07/boas-praticas-usabilidade-carrinhos-compras/</link>
		<comments>http://log.pt/blog/2012/07/boas-praticas-usabilidade-carrinhos-compras/#comments</comments>
		<pubDate>Thu, 05 Jul 2012 13:49:04 +0000</pubDate>
		<dc:creator>Filipe Serrazina</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[eCommerce]]></category>
		<category><![CDATA[Shopping]]></category>
		<category><![CDATA[Usabilidade]]></category>

		<guid isPermaLink="false">http://log.pt/?p=13712</guid>
		<description><![CDATA[Um bom processo de compra online permite aos utilizadores fazerem as suas compras de forma rápida e fácil, sem que estes se sintam confusos ou sobrecarregados com esta tarefa. No entanto, é comum verificar taxas de abandono superiores a 50% em muitos sites de comercio electrónico. Um dos primeiros passos que se deve adotar para [...]]]></description>
				<content:encoded><![CDATA[<p>Um bom processo de compra online permite aos utilizadores fazerem as suas compras de forma rápida e fácil, sem que estes se sintam confusos ou sobrecarregados com esta tarefa. No entanto, é comum verificar taxas de abandono superiores a 50% em muitos sites de comercio electrónico.</p>
<p>Um dos primeiros passos que se deve adotar para reduzir a taxa de abandono é a implementação de um processo de compra de apenas uma página, embora existam outros fatores que podem desmotivar e afastar os utilizadores do processo de compra final.</p>
<h2>O processo deve ser linear</h2>
<p>Criar um vasto número de passos a efetuar ou mesmo criar passos dentro de passos, confunde e intimida os utilizadores, uma vez que quebra o modelo mental do processo de compra. Isto é crítico uma vez que, no processo mental dos utilizadores, o processo de compra deve ser linear e sequencial.</p>
<p>Um exemplo desta não linearidade é pedir ao utilizador dados da morada de envio numa nova página e depois fazer um redireccionamento para o passo anterior. Ver a mesma página duas vezes, faz com que a maioria dos utilizadores pense que ocorreu um erro no processo, uma vez que é este o comportamento no momento da validação de formulários.</p>
<h2>Evitar termos como “Continuar”</h2>
<p>Usar termos como “Continuar” são ambíguos e tendem a confundir os utilizadores.</p>
<p><img class="aligncenter size-large wp-image-13792" title="exemplo-continuar-bt" src="http://log.pt/wp-content/uploads/2012/03/exemplo-continuar-bt-600x125.png?4c9b33" alt="Imagem com exemplo de Botão Continuar" width="600" height="125" /></p>
<p>Dependendo do momento, um botão “Continuar” no processo de compra pode significar uma de duas coisas:</p>
<ol>
<li>Continuar a comprar, ou seja, o utilizador pode continuar a procurar mais produtos;</li>
<li>Continuar para o processo de pagamento, ou seja, quando o utilizador já tem todos os produtos e quer pagar.</li>
</ol>
<p>Outro exemplo é o botão “Voltar”. Quererá esta ação significar voltar à página anterior ou voltar à página dos resultados?</p>
<p>Estes são exemplos de termos que podem ter diferentes interpretações dependendo do contexto (qual a página onde o utilizador se encontra) e também da interpretação dos utilizadores. A solução neste caso é substituir estes termos ambíguos, por palavras que não deixam dúvidas sobre a ação que está implícita, como “Efetuar Pagamento” ou “Continuar a Comprar”.</p>
<h2>Reforçar visualmente todos os campos sensíveis na página de pagamento</h2>
<p>O momento da introdução dos dados de pagamento é sempre o momento mais desconfortável para os utilizadores. Principalmente se o pagamento for via cartão de crédito e se a informação relativa aos dados do cartão não parecer segura. Se assim for, os utilizadores vão hesitar em finalizar a compra.</p>
<p><div id="attachment_13802" class="wp-caption aligncenter" style="width: 481px"><img class="size-full wp-image-13802" title="credit-card-info" src="http://log.pt/wp-content/uploads/2012/03/credit-card-info.png?4c9b33" alt="credit-card-info" width="471" height="166" /><p class="wp-caption-text">Exemplo de informação clara e destacada sobre o método de pagamento e segurança</p></div></p>
<p style="text-align: left;" align="CENTER">Colocar na página ícones e texto relativos à segurança do processo, levam a que a página seja percecionada como sendo segura, enquanto que as páginas que não contemplem esses elementos inspiram menos confiança.</p>
<p>Tecnicamente não existem diferenças entre estes dois tipos de página. No entanto, os utilizadores não sabem como funciona um site e a única coisa que sabem é o que o seu instinto lhes diz.</p>
<p>Adicionar pistas visuais ao design (cor de fundo, um contorno e ícones relativos a segurança) dos campos relativos aos dados do cartão de crédito, faz aumentar a percepção de segurança e assim, encaminhar os utilizadores para a finalização do processo de compra</p>
<h2>Adicionar descrições aos campos dos formulários</h2>
<p>Quando os campos dos formulários não contemplam descrições estes podem tornar-se ambíguos. Esta ausência de explicação potencia que alguns utilizadores não saibam que tipo de informação se está a ser pedida.</p>
<p>Estes problemas podem ser resolvidos adicionando pequenas descrições e exemplos junto às etiquetas.</p>
<p><img title="descrições dos campos " src="http://log.pt/wp-content/uploads/2012/03/Screen-Shot-2012-03-26-at-17.09.02-600x104.png?4c9b33" alt="exemplo de descrições dos campos " width="600" height="104" /></p>
<p>Uma vez que nem todos os utilizadores precisam deste tipo de explicação, pode-se esconder a informação por detrás de um link “Saiba mais” ou reduzir o tamanho da fonte.</p>
<p>Esta sugestão é extensível para campos que em princípio, não levantam dúvidas, como o campo do <strong>Email</strong>. Pode aproveitar-se para informar ao utilizador o que irá ser feito com a informação. Muitos utilizadores podem querer saber o que irá ser feito com os endereços de email.</p>
<h2>Não usar um botão de “Aplicar” no formulário</h2>
<p>Os utilizadores desconhecem o significado que um botão “Aplicar” tem, para diferentes secções da página.</p>
<p>Em vez de usar o termo &#8220;Aplicar&#8221; dever-se-á usar termos mais específicos.</p>
<p><div id="attachment_13812" class="wp-caption aligncenter" style="width: 610px"><img class="size-large wp-image-13812" title="applyButton" src="http://log.pt/wp-content/uploads/2012/03/applyButton-600x114.png?4c9b33" alt="Exemplo de um botão para aplicar" width="600" height="114" /><p class="wp-caption-text">Em vez de usar o termo &#8220;Aplicar&#8221; devemos usar termos mais específicos</p></div></p>
<p>Usar esta terminologia em ações como a confirmação de um método de envio, pode fazer com que o botão nunca seja clicado, pois o utilizador pode confundi-lo com a ação de submeter o formulário.</p>
<p>Neste caso, quando o utilizador confunde o botão de “Aplicar” com o botão de submeter o formulário e ao efetuar a ação é redirecionado para a mesma página, a consequência é a frustação das suas expectativas sobre o passo seguinte da compra.</p>
<p>Aconselha-se também a redução da utilização de botões “Aplicar”, recorrendo neste caso ao AJAX para validação e atualização de um valor.</p>
<h2>Usar apenas uma coluna para os campos do formulário</h2>
<p>É muito difícil para os utilizadores relacionarem os campos em formulários quando estes estão distribuídos por duas colunas.</p>
<p>Normalmente podem acontecer dois tipo de erros:</p>
<ol>
<li>Uma das colunas é ignorada ou não é relacionada com o formulário e o utilizador não preenche esses dados.</li>
<li>Campos não relacionados são preenchidos e submetidos, causando erros de validação.</li>
</ol>
<h2>Usar indicações de erro de forma evidente</h2>
<p>A maioria dos utilizadores tem grandes dificuldades em encontrar e entender as mensagens de erro.</p>
<p>Quando um utilizador tem um problema no preenchimento de um formulário, a probabilidade de abandono do formulário aumenta significativamente.</p>
<p>As mensagens de erro devem ser colocadas junto dos campos que contêm erros ou corre-se o risco de estas serem ignoradas pelos utilizadores. Sem esta proximidade pode tornar-se difícil compreender as mensagens. Nestes casos, os utilizadores tendem não notar nada de errado com os campos, tentando submeter os formulários.</p>
<p>Se os utilizadores não compreenderem as mensagens de erro, não conseguirão resolver os problemas ou prosseguir para processo de compra. Nestes casos, o abandono é inevitável. É assim muito importante, empenhar bastante esforço para desenhar e comunicar eficazmente as mensagens de erro.</p>
<p><div id="attachment_13822" class="wp-caption aligncenter" style="width: 610px"><img class="size-large wp-image-13822" title="mensagens-erro" src="http://log.pt/wp-content/uploads/2012/03/mensagens-erro-600x228.png?4c9b33" alt="Mensagens de Erro" width="600" height="228" /><p class="wp-caption-text">Um bom exemplo de como apresentar as mensagens de erro</p></div></p>
<p>Devemos assegurar que as mensagens de erro:</p>
<ul>
<li>Estão contextualizadas (não estão apenas no topo da página, mas também junto ao campo que contém o erro);</li>
<li>São claras e concisas;</li>
<li>Distinguem-se do resto do <em>layout</em> (grande contraste e o uso de setas ou outros elementos visuais).</li>
</ul>
<h2>Incluir sempre um mini-carrinho de compras</h2>
<p>Normalmente os carrinhos de compras são apresentados de duas formas: um carrinho de compras completo e funcional na página e um “mini” carrinho de compras, normalmente localizado na barra lateral. A melhor prática é incluir os dois.</p>
<p><div id="attachment_13832" class="wp-caption aligncenter" style="width: 296px"><img class="size-full wp-image-13832" title="mini-cart" src="http://log.pt/wp-content/uploads/2012/03/mini-cart.png?4c9b33" alt="Mini Carrinho de Compras" width="286" height="231" /><p class="wp-caption-text">O site da Amazon disponibiliza sempre um mini-carrinho</p></div></p>
<p>Com o carrinho de compras completo, podemos incluir mais informação e opções. Por exemplo podemos mostrar informação acerca do produto, remover/editar produtos, montante das taxas e opções de envio.</p>
<p>Os mini-carrinhos mostram informação dentro de um área pequena que não provoca ruído com o restante <em>layout</em> da página.</p>
<h2>Destacar os botões de pagamento e de adicionar</h2>
<p>A distinção entre botões com ações distintas como pagar ou adicionar mais produtos é de extrema importância. Para tal, é aconselhável usar botões que se distingam claramente dos restantes elementos da página.</p>
<p><div id="attachment_13842" class="wp-caption aligncenter" style="width: 446px"><img class="size-full wp-image-13842" title="botoes-de-adicionar" src="http://log.pt/wp-content/uploads/2012/03/botoes-de-adicionar.png?4c9b33" alt="Botões de Adicionar" width="436" height="250" /><p class="wp-caption-text">Esta falta de distinção pode fazer com que os utilizadores não encontrem os botões para finalizar a compra e assim, nunca finalizarem a comprar!</p></div></p>
<h2>Usar tabelas para comparação de produtos</h2>
<p>É boa prática utilizar uma estrutura em tabela quando desenhamos uma página do carrinho de compras. A página deve disponibilizar a informação de forma eficiente e sem quebras na leitura. Devem ser utilizadas fontes standard e evitar usar fundos complexos.</p>
<p>Devem-se sempre usar contornos para separar as várias células. A informação no carrinho de compras deve ser de fácil leitura e sem quaisquer elementos que possam distrair ou aumentar a complexidade.</p>
<p>Usar a imagem do produto na página do carrinho de compras ajuda também o utilizador a relacionar os produtos que está a comprar.</p>
<h2>Evitar usar demasiados campos</h2>
<p>Para os utilizadores o preenchimento de formulários é uma tarefa frustrante, principalmente quando obriga ao preenchimento de campos que não são essenciais para o processo da compra.</p>
<p>Assim, deve-se desenhar o processo de compra da maneira mais simples possível, para que os utilizadores considerem o processo conveniente, ou será quase certo que não irão realizar uma compra através do site.</p>
<p>Deve-se restringir o número de campos ao mínimo, agrupar campos que se assemelhem e providenciar cabeçalhos para cada uma das secções. O uso do espaço branco também ajuda a tornar o formulário mais apelativo e organizado.</p>
<h2>Outras dicas que optimizam o processo de compras online</h2>
<ul>
<li>Usar imagens dos produtos com qualidade;</li>
<li>Usar botões de “Adicionar ao Carrinho” e “Finalizar Compra” grandes para poderem ser usados em dispositivos móveis;</li>
<li>Evitar o uso de elementos flash que tornem o carregamento lento;</li>
<li>Assegurar que se fornece toda a informação ao utilizador, como por exemplo, em que passo ele se encontra e quantos passos faltam para terminar o processo.</li>
</ul>
<h2>Exemplos de boas práticas de carrinhos de compras</h2>
<p><strong><a href="http://www.cafepress.com/">Cafepress</a></strong></p>
<ul>
<li>Possibilidade de guardar o estado do carrinho;</li>
<li>Integração com o Paypal;</li>
<li>Possibilidade de alterar a quantidade;</li>
<li>Múltiplos métodos de envio.</li>
</ul>
<p><div id="attachment_13722" class="wp-caption aligncenter" style="width: 570px"><img class="size-full wp-image-13722" title="www.cafepress.com" src="http://log.pt/wp-content/uploads/2012/03/www.cafepress.jpeg?4c9b33" alt="Cafepress" width="560" height="356" /><p class="wp-caption-text">Cafepress processo de compra</p></div></p>
<p><strong><a href="http://store.nike.com/index.jsp?country=US&amp;lang_locale=en_US">Nike Store</a></strong></p>
<ul>
<li>Integração com Paypal;</li>
<li>Envio grátis para compras acima de um determinado montante;</li>
<li>Recomendações “Também pode gostar”;</li>
<li>Múltiplos métodos de envio.</li>
</ul>
<p><div id="attachment_13732" class="wp-caption aligncenter" style="width: 570px"><img class="size-full wp-image-13732" title="nike-store" src="http://log.pt/wp-content/uploads/2012/03/nike-store.jpeg?4c9b33" alt="Nike Store Checkout" width="560" height="320" /><p class="wp-caption-text">Processo de compra da Nike Store</p></div></p>
<p><strong><a href="http://www.bose.com/">Bose</a></strong></p>
<ul>
<li>Acessórios recomendados;</li>
<li>Opções de pagamento e utilização dos logos dos cartões;</li>
<li>Opção de pagar depois “Bill me later”;</li>
<li>Envio gratuito;</li>
<li>Processo de compra bastante explícito (4 passos).</li>
</ul>
<p><div id="attachment_13742" class="wp-caption aligncenter" style="width: 570px"><img class="size-full wp-image-13742 " title="bose" src="http://log.pt/wp-content/uploads/2012/03/bose.png?4c9b33" alt="Bose Checkout" width="560" height="397" /><p class="wp-caption-text">Processo de compra do site da Bose</p></div></p>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/07/boas-praticas-usabilidade-carrinhos-compras/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Desenvolvimento sobre WordPress (4) — Widgets</title>
		<link>http://log.pt/blog/2012/07/wordpress-tutorial-widgets/</link>
		<comments>http://log.pt/blog/2012/07/wordpress-tutorial-widgets/#comments</comments>
		<pubDate>Mon, 02 Jul 2012 11:08:07 +0000</pubDate>
		<dc:creator>Luís Rodrigues</dc:creator>
				<category><![CDATA[blog]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://log.pt/?p=14252</guid>
		<description><![CDATA[Nas primeiras duas partes da nossa série de artigos introdutórios sobre desenvolvimento em WordPress, centrámo-nos na API de hooks, que serve para invocar funções que executem acções ou filtrem dados em resposta a acontecimentos na plataforma. Na terceira, foi a vez de nos focarmos na criação de shortcodes que podem ser utilizados no conteúdo das [...]]]></description>
				<content:encoded><![CDATA[<p>Nas <a href="http://log.pt/blog/2012/01/wordpress-tutorial-actions/">primeiras</a> <a href="http://log.pt/blog/2012/02/wordpress-tutorial-filters/">duas partes</a> da nossa série de artigos introdutórios sobre desenvolvimento em WordPress, centrámo-nos na API de <em>hooks</em>, que serve para invocar funções que executem acções ou filtrem dados em resposta a acontecimentos na plataforma. Na <a href="http://log.pt/blog/2012/03/wordpress-tutorial-shortcodes/">terceira</a>, foi a vez de nos focarmos na criação de <em>shortcodes</em> que podem ser utilizados no conteúdo das páginas e dos artigos de um <em>site</em>.</p>
<p>No presente artigo, iremos abordar os elementos de conteúdo e navegação que povoam as <em>sidebars</em> de muitos <em>sites</em> WordPress, e aprenderemos a desenvolver uma nova <em>widget</em> de raíz.</p>
<h3 id="sidebarsoureasdewidgets"><em>Sidebars</em> (ou áreas de <em>widgets</em>)</h3>
<p><div id="attachment_14452" class="wp-caption alignright" style="width: 205px"><img class="size-medium wp-image-14452" title="Configuração das sidebars no painel de administração" src="http://log.pt/wp-content/uploads/2012/05/006-01-widget-areas-195x300.png?4c9b33" alt="Configuração das sidebars no painel de administração" width="195" height="300" /><p class="wp-caption-text">Figura 1: Sidebars no painel de administração.</p></div></p>
<p>As <em>widgets</em> do WordPress são agrupadas e apresentadas em <em>sidebars</em>, áreas pré-definidas nos modelos de cada tema que podem ser configuradas pelo utilizador no painel de administração da plataforma. Um tema pode suportar qualquer número de <em>sidebars</em>. A configuração é feita mediante uma interface <em>drag and drop</em>, bastando ao utilizador arrastar para a <em>sidebar</em> desejada as <em>widgets</em> que pretende aí incluir, podendo ordená-las como desejar.</p>
<p>É possível registar novas <em>sidebars</em> usando a função <a href="http://codex.wordpress.org/Function_Reference/register_sidebar"><code>register_sidebar()</code></a>, passando-lhe um <em>array</em> associativo de argumentos, tais como o seu nome, identificadores ou a “moldura” HTML pretendida. Por exemplo:</p>
<pre class="brush: php; title: ; notranslate">
register_sidebar( array(
    'name' =&gt; 'Nova Sidebar',
    'id'   =&gt; 'nova-sidebar'
) );
</pre>
<p>Existem outros parâmetros que podem ser passados à função <code>register_sidebar()</code> para personalizar o HTML produzido. Recomendamos a leitura da documentação da função para todos os pormenores.</p>
<p>Convém salientar que este registo, só por si, não garante que a <em>sidebar</em> vai aparecer no tema. Para isso é preciso invocá-la no modelo ou <em>template</em> do tema onde se pretende que ela seja apresentada. Esta invocação é feita chamando a função <a href="http://codex.wordpress.org/Function_Reference/dynamic_sidebar"><code>dynamic_sidebar()</code></a> com o nome ou identificador da <em>sidebar</em> criada. Para o caso acima, isto seria:</p>
<pre class="brush: php; title: ; notranslate">
dynamic_sidebar( 'nova-sidebar' );
</pre>
<p>A <em>sidebar</em> em questão precisa de ter pelo menos uma <em>widget</em> activa para ficar visível.</p>
<h3 id="aminhaprimeirawidget">A minha primeira <em>widget</em></h3>
<p>Introduzidos os conceitos básicos das <em>sidebars</em>, voltamo-nos para o desenvolvimento das <em>widgets</em> propriamente ditas.</p>
<p>Ao contrário dos <em>hooks</em> e <em>shortcodes</em> que conhecemos até aqui, as <em>widgets</em> não se definem à custa de funções mas sim com classes PHP que herdam da superclasse <code>WP_Widget</code>. Esta classe deve implementar o construtor da classe e três outros métodos importantes:</p>
<ul>
<li><strong><code>form()</code></strong>: Método que imprime o formulário de configuração da <em>widget</em> no painel de administração;</li>
<li><strong><code>update()</code></strong>: Método que regista as opções da <em>widget</em> configuradas no painel de administração;</li>
<li><strong><code>widget()</code></strong>: Método que imprime o conteúdo da <em>widget</em>, tal como será apresentado ao visitante do site.</li>
</ul>
<p>Assim uma nova <em>widget</em> respeita o seguinte esqueleto de código:</p>
<pre class="brush: php; title: ; notranslate">
class Latest_Category_Posts_Widget extends WP_Widget {

    public function Latest_Category_Posts_Widget () {
        // Método constructor da widget
    }

    public function form ( $instance ) {
        // Formulário de configuração
    }

    public function update ( $new_instance, $old_instance ) {
        // Processamento da configuração da widget
    }

    public function widget ( $args, $instance ) {
        // Apresentação da widget
    }

}
</pre>
<p>Após declarar a classe que implementa a <em>widget</em>, é necessário registá-la com a função <a href="http://codex.wordpress.org/Function_Reference/register_widget"><code>register_widget()</code></a>, deste modo:</p>
<pre class="brush: php; title: ; notranslate">
register_widget( 'Latest_Category_Posts_Widget' );
</pre>
<p>É, no entanto, mais correcto registar a <em>widget</em> através de um <em>hook</em> de acção, no passo reservado à inicialização de <em>widgets</em>. Convém por isso alterar a linha acima para:</p>
<pre class="brush: php; title: ; notranslate">
add_action( 'widgets_init',
    create_function( '', 'register_widget( &quot;Latest_Category_Posts_Widget&quot; );' ) );
</pre>
<p>Na inicialização da classe, através do seu método construtor, podemos definir atributos básicos como o nome e descrição da nossa <em>widget</em>. Vamos, pois, redefinir o nosso construtor da seguinte forma:</p>
<pre class="brush: php; title: ; notranslate">
public function Latest_Category_Posts_Widget () {
    parent::__construct(
        'recent_category_posts', // Identificador
        'Recent Category Posts', // Nome
        array( 'description' =&gt; __( 'Latest posts in a category.' ), ) // Argumentos
    );
}
</pre>
<p><div id="attachment_14462" class="wp-caption aligncenter" style="width: 310px"><img class="size-medium wp-image-14462" title="A primeira widget" src="http://log.pt/wp-content/uploads/2012/05/006-02-recent-category-widget-300x146.png?4c9b33" alt="" width="300" height="146" /><p class="wp-caption-text">Figura 2: A primeira widget à espera de ser incluída.</p></div></p>
<p><div id="attachment_14472" class="wp-caption alignright" style="width: 208px"><img class="size-medium wp-image-14472" title="Formulário de configuração em branco" src="http://log.pt/wp-content/uploads/2012/05/006-03-latest-category-widget-empty-198x300.png?4c9b33" alt="" width="198" height="300" /><p class="wp-caption-text">Figura 3: Formulário de configuração em branco.</p></div></p>
<p>O resultado destas linhas é o apresentado na <em>screenshot</em> acima. No entanto, ao arrastar a <em>widget</em> para uma das <em>sidebars</em> do WordPress, constatamos que está vazia. O mesmo se passa na face pública do <em>site</em>. Trataremos agora de remediar essa situação, apresentando primeiro aos administradores da plataforma um formulário que permita configurar a <em>widget</em>, e de seguida o HTML apresentado aos visitantes com base nas opções seleccionadas.</p>
<h3 id="formulriodeconfigurao">Formulário de configuração</h3>
<p>Pretendemos desenvolver uma <em>widget</em> que mostre um número determinado de artigos de uma categoria escolhida pelo administrador. Precisamos de pelo menos dois campos para isto: um onde o administrador selecciona a categoria, e outro para indicar o número de artigos a apresentar. Incluiremos também um campo de texto para o título da <em>widget</em>.</p>
<p>A <em>dropdown</em> para selecção da categoria será feita à custa da função [<code>wp_dropdown_categories()</code>][WPCodex:wp_dropdown_categories], o que nos poupa algum trabalho de construção da interface. O método que desenha o formulário toma portanto este aspecto:</p>
<pre class="brush: php; title: ; notranslate">
public function form ( $instance ) {
    $title    = isset( $instance['title'] )    ? esc_attr( $instance['title'] )  : '';
    $category = isset( $instance['category'] ) ? absint( $instance['category'] ) : 0;
    $number   = isset( $instance['number'] )   ? absint( $instance['number'] )   : 5;
    ?&gt;

&lt;label for=&quot;&lt;?php echo $this-&gt;get_field_id( 'title' ); ?&gt;&quot;&gt;&lt;?php _e( 'Title:' ); ?&gt;&lt;/label&gt;
        &lt;input id=&quot;&lt;?php echo $this-&gt;get_field_id( 'title' ); ?&gt;&quot; class=&quot;widefat&quot; type=&quot;text&quot; name=&quot;&lt;?php echo $this-&gt;get_field_name( 'title' ); ?&gt;&quot; value=&quot;&lt;?php echo esc_attr( $title ); ?&gt;&quot; /&gt;

        &lt;?php wp_dropdown_categories( array(
            'show_option_all' =&gt; __( 'All' ),
            'orderby'         =&gt; 'name',
            'hide_empty'      =&gt; 0,
            'hierarchical'    =&gt; 1,
            'selected'        =&gt; $category,
            'id'              =&gt; $this-&gt;get_field_id( 'category' ),
            'name'            =&gt; $this-&gt;get_field_name( 'category' ),
            'class'           =&gt; 'widefat',
        ) ); ?&gt;

&lt;label for=&quot;&lt;?php echo $this-&gt;get_field_id( 'title' ); ?&gt;&quot;&gt;&lt;?php _e( 'Number of posts to show:' ); ?&gt;&lt;/label&gt;
        &lt;input id=&quot;&lt;?php echo $this-&gt;get_field_id( 'number' ); ?&gt;&quot; type=&quot;text&quot; name=&quot;&lt;?php echo $this-&gt;get_field_name( 'number' ); ?&gt;&quot; value=&quot;&lt;?php echo esc_attr( $number ); ?&gt;&quot; size=&quot;3&quot; /&gt;

    &lt;?php
}
</pre>
<p><div id="attachment_14482" class="wp-caption alignright" style="width: 295px"><img class="size-full wp-image-14482" title="Formulário de configuração" src="http://log.pt/wp-content/uploads/2012/05/006-04-latest-category-widget-form.png?4c9b33" alt="" width="285" height="254" /><p class="wp-caption-text">Figura 4: Formulário de configuração.</p></div></p>
<p>Refrescando a página de administração de <em>widgets</em>, podemos constatar que as coisas têm melhor aspecto. Contudo, os valores introduzidos no formulário ainda não ficam guardados, é preciso agora definir o método <code>update()</code>. O processo é tão simples quanto definir as chaves de um <em>array</em> associativo a partir de um outro <em>array</em> com os dados de entrada:</p>
<pre class="brush: php; title: ; notranslate">
public function update ( $new_instance, $old_instance ) {
    $instance = (array) $old_instance;

    $instance['title']    = strip_tags( $new_instance['title'] );
    $instance['category'] = absint( $new_instance['category'] );
    $instance['number']   = absint( $new_instance['number'] );

    return $instance;
}
</pre>
<h3 id="apresentaodawidget">Apresentação da <em>widget</em></h3>
<p>O passo final da criação desta <em>widget</em> consiste em apresentar ao utilizador, numa <em>sidebar</em>, a nossa lista de <em>n</em> artigos dentro da categoria pretendida.</p>
<p>Munidos do identificador da categoria e do número de artigos a apresentar, podemos obter a lista com que iremos preencher a <em>widget</em>. O título da <em>widget</em> é opcional: se o utilizador não definiu um no formulário de configuração, este será preenchido com o texto “Recent posts in %s”, onde o <em>token</em> <em>%s</em> é substituído pelo nome da categoria escolhido.</p>
<p>A lista é obtida com o objecto <a href="http://codex.wordpress.org/Class_Reference/WP_Query"><code>WP_Query</code></a>, indicando os parâmetros <code>posts_per_page</code> para o limite de artigos apresentados, <code>cat</code> para o identificador da categoria, <code>post_status</code> para garantir que apenas mostramos artigos publicados e <code>ignore_sticky_posts</code> para que não surjam artigos de destaque, apenas os mais recentes. <code>no_found_rows</code> indica que não precisamos de saber quantos artigos existem no <em>site</em>, interessa-nos apenas uma lista dos mais recentes. Também não é preciso indicar explicitamente que pretendemos uma ordenação cronológica inversa porque <code>WP_Query</code> assume essa preferência por omissão.</p>
<pre class="brush: php; title: ; notranslate">
public function widget ( $args, $instance ) {
    extract( $args );

    $category = absint( $instance['category'] );
    $number   = absint( $instance['number'] );
    $title    = apply_filters( 'widget_title', empty( $instance['title'] )
              ? sprintf( __( 'Recent posts in %s' ), get_the_category_by_ID( $category ) )
              : $instance['title'], $instance, $this-&gt;id_base );

    if (!$number)
        $number = 5;

    $q = new WP_Query( array(
        'posts_per_page'        =&gt; $number,
        'cat'                   =&gt; $category,
        'no_found_rows'         =&gt; true,
        'post_status'           =&gt; 'publish',
        'ignore_sticky_posts'   =&gt; true
    ) );

    $out = '';

    while ($q-&gt;have_posts()) {
        $q-&gt;the_post();
$out .= '&lt;/pre&gt;
&lt;ul&gt;
	&lt;li&gt;};

 if (!empty( $out )) {
 echo $before_widget;

 if ($title)
 echo $before_title . $title . $after_title;

echo '
&lt;ul&gt;' . $out . '&lt;/ul&gt;
';

 echo $after_widget;
 }
}
</pre>
<p>Esta <em>widget</em> pode ainda ser melhorada. Por exemplo, a lista compilada pode ser armazenada em <em>cache</em> do WordPress para que a sua apresentação não pese tanto. No entanto, o resultado final deste exemplo ilustrativo já pode ser observado na <em>sidebar</em> do <em>site</em>.</p>
<p><div id="attachment_14492" class="wp-caption aligncenter" style="width: 262px"><img class="size-full wp-image-14492" title="Voilà!" src="http://log.pt/wp-content/uploads/2012/05/006-05-presto.png?4c9b33" alt="" width="252" height="157" /><p class="wp-caption-text">Figura 5: Voilà!</p></div></p>
<h3 id="boasprticas">Boas práticas</h3>
<p>No capítulo das melhores práticas de desenvolvimento, recomendamos que os interessados em desenvolver novas <em>widgets</em> se baseiem no <a href="https://github.com/tommcfarlin/WordPress-Widget-Boilerplate">WordPress Widget Boilerplate</a> de Tom McFarlin, um modelo de projecto que adianta algum trabalho ao mesmo tempo que procura incutir bons hábitos na codificação de <em>widgets</em> para a plataforma. Recomendamos ainda o artigo que inspirou o projecto, <a href="http://wp.tutsplus.com/tutorials/widgets/writing-maintainable-wordpress-widgets-part-1-of-2/">Writing Maintainable WordPress Widgets</a>, pelo mesmo autor.</p>
<p>A consulta da <a href="http://codex.wordpress.org/Widgets_API">documentação da API no Codex</a> é, claro, fundamental.</p>
<hr />
<p>No próximo artigo, explicaremos como integrar tudo o que aprendemos até agora num <em>plugin</em> WordPress. Até lá, aguardamos questões e sugestões dos nossos leitores <a href="https://www.facebook.com/logOpenSourceConsulting">na nossa página no Facebook</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://log.pt/blog/2012/07/wordpress-tutorial-widgets/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using apc
Database Caching using apc
Object Caching 1028/1193 objects using apc

 Served from: log.pt @ 2013-05-18 19:00:32 by W3 Total Cache -->