Pretendo com este post iniciar uma série de posts de best-practices de desenvolvimento em Actionscript 3.
Começo com um dos maiores erros que costumo ver, especialmente em antigos programadores Actionscript 2: a amaldiçoada utilização do parent (o antigo _parent) ou do stage (o antigo _root).
Este post aplica-se tanto a Flash CS3 como a Flex. O código está puramente em Actionscript 3, mas o raciocínio é exactamente o mesmo para MXML.
O caso
Imaginemos a seguinte imagem:

Temos o Stage. Dentro do Stage, temos um menu e duas secções: seccaoA e seccaoB. Dentro do menu temos dois botões: opcaoA e opcaoB. Ou seja:
- Stage
- Stage.menu
- Stage.menu.opcaoA
- Stage.menu.opcaoB
- Stage.seccaoA
- Stage.seccaoB
Queremos que quando o utilizador clique na opcaoA, a seccaoA seja mostrada, e que aconteça o mesmo para a opcaoB e seccaoB.
O código, para os adeptos do “parent”, parece óbvio:
(Dentro do Menu):
// Construtor
public function Menu()
{
opcaoA.addEventListener(MouseEvent.CLICK, opcaoAClickHandler);
opcaoB.addEventListener(MouseEvent.CLICK, opcaoBClickHandler);
}
function opcaoAClickHander(ev:MouseEvent):void
{
parent.seccaoA.visible=true;
parent.seccaoB.visible=false;
}
function opcaoBClickHander(ev:MouseEvent):void
{
parent.seccaoA.visible=false;
parent.seccaoB.visible=true;
}
Aparentemente isto resolveria o problema de uma assentada só. Porém…
Os problemas do parent
Infelizmente está 100% incorrecto utilizar o parent. A regra é simples: é *proibido* usá-lo. Surgiu um problema que obriga à utilização do parent? Repense! Leia este post, e veja como se resolve.
E obviamente perguntará: porque é que está incorrecto?
Problema 1: Reutilização
O seu menu está um espectáculo: é um dos preferidos na sua empresa tanto que o quer utilizar em vários projectos.
Entretanto aparece um novo projecto onde vai usar o menu. Porém foi outro elemento da equipa que fez a seccaoA e seccaoB, mas…. deu-lhes outros nomes. Chamou-lhes “seccao1″ e “seccao2″. E agora, que faz?
Primeira opção: mudar os nomes dados pelo seu colega (seccao1 e seccao2) para seccaoA e seccaoB. Big mistake: infelizmente o outro elemento da equipa tinha feito imenso código que usava intensivamente os nomes “seccao1″ e “seccao2″. E agora vai fazer o quê? Mexer no código dele? Não me parece…
Segunda opção: mudar no seu código onde está parent.seccaoA para parent.seccao1. Parece uma boa ideia dado o exemplo que fizemos aqui, mas se se tratasse um menu altamente complexo que usasse imensas vezes “parent.seccaoA”, seria lógico (e rápido) estar a mudar em todo o lado para seccao1 ?
Ok, é teimoso e estaria disposto a mudar o seu código todo… Porém, ao fazer isto, em vez de ter um menu que está a reutilizar entre projectos, na realidade acaba por estar a criar vários menus diferentes. Isto porque para cada projecto terá que mudar o código de cada menu de acordo com os nomes das secções. Ou seja, no fundo é um menu diferente com código diferente por projecto…
Problema 2: Refactoring
O seu projecto está feito e parece terminado. Porém, agora aparece o chefe a dizer “Ah e tal, vocês fizeram isto mal. Não quero a seccaoA dentro do Stage, mas sim dentro de um MovieClip chamado SectionContainer que tem uma borda e uns efeitos giros”. E lá vão vocês criar o SectionContainer, e colocar as secções lá dentro. Entretanto compilam e… Erro!
O código antigo do menu referia-se a parent.seccaoA. Porém, com a alteração, a seccaoA neste momento pode ser acedida assim: parent.sectionContainer.seccaoA. E com isto lá ter que ir mudar o seu código (e rezar para que o menu não seja complexo ao ponto de ter que mudar 23423423 linhas…).
A solução: componentes fracamente acoplados
A solução é relativamente simples, e passa por se cumprir uma regra também ela simples:
« Um componente/”MovieClip” nunca pode saber nada sobre quem o usa. Um componente/”MovieClip” só é responsável por si próprio »
Por outras palavras, se dentro do Menu está a fazer “parent.seccaoA”, quer dizer que o menu sabe que existe uma secção fora de si chamada seccaoA - sabe mais do que deveria saber.
Então como posso fazer: “quanto se clica na opcaoA, quero mostrar a seccaoA” se o menu não pode saber que há uma seccaoA ? Ora, o menu não tem que mudar para a seccaoA mas sim avisar quem o está a usar que está na altura de mudar para a seccaoA. Avisar como? Através de eventos!
Lançar e apanhar os eventos
O raciocínio passará a ser então:
- O utilizador clica no botão, o que despoleta um click e respectiva chamada da função opcaoAClickHander
- O opcaoAClickHandler deverá avisar o componente externo que está na altura de mudar para a seccaoA, lançando um evento chamado por exemplo “opcaoAClick”
- O Stage deverá estar a escutar eventos do tipo “opcaoAClick”, e quando os apanha chama a função menuOpcaoAClickHandler
- O menuOpcaoAClickHandler deverá conter o código necessário para mostrar a secção correspondente e esconder as outras.
Assim, o código correspondente:
(Dentro do Menu):
// Construtor
public function Menu()
{
opcaoA.addEventListener(MouseEvent.CLICK, opcaoAClickHandler);
opcaoB.addEventListener(MouseEvent.CLICK, opcaoBClickHandler);
}
function opcaoAClickHander(ev:MouseEvent):void
{
dispatchEvent(new Event("opcaoAClick"));
}
function opcaoBClickHander(ev:MouseEvent):void
{
dispatchEvent(new Event("opcaoBClick"));
}
(Dentro do Stage, que está associado a uma classe chamada Index):
// Construtor
public function Index()
{
menu.addEventListener("opcaoAClick", menuOpcaoAClickHandler);
menu.addEventListener("opcaoBClick", menuOpcaoBClickHandler);
}
function menuOpcaoAClickHandler(ev:MouseEvent):void
{
seccaoA.visible=true;
seccaoB.visible=false;
}
function menuOpcaoBClickHandler(ev:MouseEvent):void
{
seccaoA.visible=false;
seccaoB.visible=true;
}
Ou seja, se só o Stage é que sabe que existem secções, só o Stage é que pode mudar de secções. Então o menu simplesmente lança um aviso “olha, está na altura de mudar para a seccaoA”. O Stage apanha o aviso, e faz então o que é necessário para mostrar a secção correspondente.
Mais correcto ainda, seria criar um Custom Event, que possuiria uma String como propriedade pública onde seria colocado o nome da secção. Porém, isso fica para uma posterior lição.
Conclusões
- Qualquer componente ou MovieClip nunca pode saber *nada* sobre quem o está a usar.
- Não usar o parent.
- Não usar o parent.
- Não usar o parent.
- Sempre que surge um caso de “aqui ficava mesmo bem um parent”, usamos o dispatchEvent como descrito acima.
Desta forma, o nosso menu já não sofre de problemas de reutilização nem de refactoring:
- se quisermos usar o menu noutro projecto, é só colocá-lo noutro projecto: não é preciso mudar o código do menu, pois este é completamente independente do projecto onde é usado;
- se o projecto sofrer alterações de estrutura, é só alterar o projecto sem mexermos no menu: não é preciso mudar o código do menu, pois este é completamente independente do projecto onde é usado.
É de realçar que este raciocínio não deve ser aplicado só a menus, mas a TUDO. Vai usar o parent? Esqueça. Use o dispatchEvent!