Início > Programação > Como Alterar o Tema da Aplicação Dinamicamente com JSF

Como Alterar o Tema da Aplicação Dinamicamente com JSF

É possível fazer uma aplicação JSF trocar seu arquivo CSS por outro dinamicamente segundo algum critério, mas sem encher o código fonte de “ifs” e “switches”, o que claramente seria uma afronta a um bom design orientado a objetos. Esse arquivo CSS é o skin ou tema do nosso site.

Vamos descrever as configurações necessárias através de um exemplo. Suponha que um mesmo site responda em duas URLs diferentes:

1. https://marketing.atitudereflexiva.com.br

2. https://financeiro.atitudereflexiva.com.br

Quando o site for o do Marketing, o background será alterado para azul e quando o site for o do Financeiro o background será amarelo como nas imagens abaixo:

marketing

Figura 1 – Página do Marketing

(https://marketing.atitudereflexiva.com.br)

financeiro

Figura 2 – Página do Financeiro

(https://financeiro.atitudereflexiva.com.br)

Note que poderíamos utilizar outros critérios como pré-condição para a troca do tema – uma página específica, por exemplo. Utilizando o JSF 2.1.20, o pom.xml fica assim:

<dependency>
   <groupId>com.sun.faces</groupId>
   <artifactId>jsf-api</artifactId>
   <version>2.1.20</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>com.sun.faces</groupId>
   <artifactId>jsf-impl</artifactId>
   <version>2.1.20</version>
   <scope>provided</scope>
</dependency>

Dentro do diretório padrão de resources, definimos um diretório chamado estilo e dentro dele estão os diretórios que armazenam os CSS do tema de cada site:

webapp
...resources
......estilo
.........financeiro
............css
...............style.css
.........marketing
............css
...............style.css

O CSS do tema do Marketing, cujo background é azul, fica assim:

body {
    background-color: blue;
}

O CSS do tema do Financeiro, cujo background é amarelo, fica assim:

body {
    background-color: yellow;
}

Sugiro utilizar uma enum para facilitar o acesso ao nome definido para a estrutura criada para cada tema:

public enum Skin {

   FINANCEIRO("financeiro"),
   MARKETING("marketing");

   private String nome;

   private Skin(String nome){
      this.nome = nome;
   }

   public Skin procurarPorNome(String nome){
      Skin skin = null;
      for(Skin s : values()){
         if(s.nome.equals(nome)){
            skin = s; 
         }
      }
      return skin;
   }
}

Precisamos criar uma classe que responda de forma diferenciada a uma solicitação de recurso. Para isso, devemos estender a classe ResourceWrapper:

public class CustomResource extends ResourceWrapper {
   
   private Resource resource;
   
   public CustomResource(Resource resource) {
      this.resource = resource;
   }

   @Override
   public Resource getWrapped() {
      return this.resource;
   }

   @Override
   public String getContentType() {
      return getWrapped().getContentType();
   }

   @Override
   public String getLibraryName() {
      return getWrapped().getLibraryName();
   }

   @Override
   public String getResourceName() {
      return getWrapped().getResourceName();
   }

   @Override
   public void setContentType(String contentType) {
      getWrapped().setContentType(contentType);
   }

   @Override
   public void setLibraryName(String libraryName) {
      getWrapped().setLibraryName(libraryName);
   }

   @Override
   public void setResourceName(String resourceName) {
      getWrapped().setResourceName(resourceName);
   }

   @Override
   public String toString() {
      return getWrapped().toString();
   }

   @Override
   public String getRequestPath() {
      String requestPath = resource.getRequestPath();
      String versao = // versao do pacote;
      String data = // data de construcao do pacote;
      if (requestPath.contains("?")) {
         requestPath = requestPath + "&v=" + versao + "&dcp=" + data;
      } else {
         requestPath = requestPath + "?v=" + versao + "&dcp=" + data;
      }
      return requestPath;
   }
}

Toda essa parametrização do método getRequestPath() é para evitar cache do navegador. Bem, teclar [CTRL]+[L] no navegador força o refresh, mas o usuário não vai fazer isso.

Para utilizar nosso CustomResource, precisamos implementar um ResourceHandlerWrapper para completar a customização de nosso recurso. É nessa classe que vamos especificar o pacote base de nossos temas e que chamamos de “estilo”:

public class CustomResourceHandler extends ResourceHandlerWrapper {

   public static final String LIBRARY = "estilo";

   private ResourceHandler wrapped;

   public CustomResourceHandler(ResourceHandler wrapped) {
      this.wrapped = wrapped;
   }

   @Override
   public ResourceHandler getWrapped() {
      return this.wrapped;
   }

   @Override
   public Resource createResource(String resourceName, 
      String libraryName) {
      Resource resource = super.createResource(resourceName, libraryName);
      if (resource != null && libraryName != null && 
         libraryName.equalsIgnoreCase(LIBRARY)) {
         return new CustomResource (resource);
      } else {
         return resource;
      }
   }
}

Agora, vamos definir um managed bean de sessão para armazenar o skin (tema) em uso. Poderia ser um bean qualquer, mas o ideal é que esse objeto fique na sessão para que esteja disponível entre as requests:

@ManagedBean(name = "skinMB")
@SessionScoped
public class SkinMB {

   private Skin skin;

   public SkinMB() {
      super();
   }

   public void setSkin(Skin skin){
      this.skin = skin;   
   }

   public Skin getSkin(){
      return this.skin;
   
}

Esse managed bean será atualizado por um PhaseListener que iremos implementar à seguir. Um PhaseListener permite que interceptemos uma requisição de recurso em algum momento de seu ciclo de vida. Faremos isso no RESTORE_VIEW:

public class SkinHandler implements PhaseListener {

   public SkinHandler () {
   }

   @Override
   public void afterPhase(PhaseEvent event) {
   }

   @Override
   public void beforePhase(PhaseEvent event) {
      String serverName = getServerName();
      Skin skin = Skin.procurarPorNome(serverName);
      SkinMB skinMB = // recuperar o bean de sessao
      skinMB.setSkin(skin);
   }

   @Override
   public PhaseId getPhaseId() {
      return PhaseId.RESTORE_VIEW;
   }

   public String getServerName() {
      ExternalContext ext = FacesContext.getCurrentInstance()
         .getExternalContext();
      HttpServletRequest request = 
         (HttpServletRequest) ext.getRequest();
      return request.getServerName();
   }
}

Agora vamos adicionar o Handler e o Listener criados ao faces-config.xml:

<faces-config (..)>
   <application>
      <resource-handler>br.com.CustomResourceHandler</resource-handler>
   </application>
   <lifecycle>
      <phase-listener>br.com.SkinHandler</phase-listener>
   </lifecycle>
</faces-config>

E finalmente, vamos adicionar nossa biblioteca “estilo” à nossa página XHTML:

<h:head>
   <c:set var="libResources" value="estilo"/>
   <h:outputStylesheet name="#{skinMB}/css/style.css" 
      library="#{libResources}" />
</h:head>
<body>
   <h1>Bem vindo!</h1>
</body>

Assim, nosso PhaseListener customizado se encarregará de decidir que tema será utilizado de acordo com a URL acessada.

Anúncios
  1. Nenhum comentário ainda.
  1. No trackbacks yet.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: