13 de novembro de 2012

PrimeFaces 3.4.1: GMap

Olá!
Depois de algum tempo, aqui estou eu com mais uma postagem sobre PrimeFaces, desta vez estou usando a versão 3.4.1. Peço desculpas se por acaso eu perdi a didática de escrever aqui, é que faz muito tempo que não escrevo mais.
Mas vamos lá... Hoje a postagem é sobre o GMap, pra quem ainda não sabe o PrimeFaces já tem um componente com a biblioteca GMap, pra usá-la é bem simples.
Primeiramente vamos baixar a biblioteca do Prime, basta clicar aqui.
Para este projeto estarei usando JPA, para quem ainda não sabe como usar: é só ver os primeiros links da página Nível: Intermediário.

Meu projeto ficou com essa estrutura:

Começando... adicione a biblioteca do PrimeFaces às bibliotecas do projeto. E na página index:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true" />
        <title>GMap</title>
    </h:head>
    <h:body>
        <p:gmap center="-15.565986,-54.309782" zoom="15" type="HYBRID" style="width:600px;height:400px" />  
    </h:body>
</html>

Vamos aos pontos importantes do código acima:
Linha 3: lembre-se de adicionar a taglib do prime na página
Linha 5: Ponto importante para o funcionamento do p:gmap, que é a biblioteca do Google para o maps funcionar.
Linha 9: Este código é suficiente para aparecer o mapa, e você já poderá navegar pelo mundo! Mas vamos aos detalhes dessa linha: a propriedade center se refere à latitude e longitude, esta propriedade indica qual será o ponto onde o mapa deverá abrir inicialmente; a propriedade zoom se refere ao zoom do mapa, que pelos meus testes pode ir de 0 até 20 (talvez em alguns lugares possa ir a mais ou menos que isso); a propriedade type trata da forma de visualizar o mapa, que pode ser HYBRID (mapa e satélite), ROADMAP (apenas mapa), TERRAIN (terreno: uma opção que fica abaixo do mapa) ou SATELLITE (visão do satélite, mas vi que ele habilita a opção mapa também, mas se escolher ela, o mapa não aparece, uma solução para usar apenas o satélite seria usar a propriedade mapTypeControl="false").
Resultado:

Capturar Latitude e Longitude

Seguindo ainda a mesma base usado anteriormente, irei usar para este exemplo minha classe LocalBean (vide imagem da estrutura do meu projeto lá em cima), nela faça o seguinte:
@javax.faces.bean.ManagedBean
@javax.faces.bean.ViewScoped
public class LocalBean {
    
    public void pontoSelecionado(PointSelectEvent event) {  
        LatLng latituteLongitude = event.getLatLng();  
          
        FacesContext.getCurrentInstance().addMessage(
            null,
            new FacesMessage(
                FacesMessage.SEVERITY_INFO, 
                "Ponto selecionado", 
                "Lat:" + latituteLongitude.getLat() + ", Long:" + latituteLongitude.getLng()
            )
        );  
    }  
}
Detalhes do código
Linha 5 e 6: as classes PointSelectEvent e LatLng devem ser importadas da biblioteca do PrimeFaces.


E a index deve ser editada para:
<h:form>
    <p:growl id="mensagem"  showDetail="true" />

    <p:gmap center="-15.565986,-54.309782" zoom="15" type="HYBRID" style="width:600px;height:400px">
        <p:ajax event="pointSelect" listener="#{localBean.pontoSelecionado}" update="mensagem" />
    </p:gmap>
</h:form>
Adicionamos tudo dentro de um h:form por conta do p:growl, então na linha 2 adicionamos o p:growl.
Linha 5: é adicionado a tag p:ajax, onde temos a propriedade event, listener que chama o método da nossa classe LocalBean, e por fim atualizamos o growl.
Resultando em:


Adicionando e carregando marcadores de um banco de dados
Para este exemplo estaremos gravando os marcadores em um banco de dados, e como disse anteriormente, estarei usando JPA para agilizar este desenvolvimento, então, para começo de conversa vamos fazer nossa classe model.Local:
@Entity
@Table(name="local")
public class Local implements Serializable{
    
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="codigo", nullable=false, unique=true)
    private Long codigo;
    @Column(name="descricao")
    private String descricao;
    @Column(name="latitude")
    private double latitude;
    @Column(name="longitude")
    private double longitude;

    /*
     * Getters e Setters
     */
}

Agora vamos fazer nossa classe dao.LocalDAO:
@Stateless
public class LocalDAO {
    
    @PersistenceContext(unitName="PrimeGmapPU")
    private EntityManager em;
    
    public void gravar(Local local){
        try {
            em.persist(local);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public List<Local> listar(){
        List<Local> locais = new ArrayList<Local>();
        try {
            Query query = em.createQuery("SELECT l FROM Local l");
            locais = query.getResultList();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return locais;
    }
    
}

Lembrando que na Linha 4, o unitName, é da minha unidade de persistência, o que pode mudar de acordo como você criou sua conexão.
Agora vamos para nossa classe controller.LocalBean, eu refiz ela, ou seja tirei do projeto aquela questão do growl, ela ficou dessa forma:

@javax.faces.bean.ManagedBean
@javax.faces.bean.ViewScoped
public class LocalBean implements Serializable{

    @EJB
    private LocalDAO localDAO;
    private Local local;
    private MapModel locais;

    public void novo(PointSelectEvent event) {
        local = new Local();
        LatLng coord = event.getLatLng();
        local.setLatitude(coord.getLat());
        local.setLongitude(coord.getLng());
    }

    public void gravar() {
        localDAO.gravar(local);
        carregarLocais();
    }

    private void carregarLocais() {
        locais = new DefaultMapModel();

        List<Local> locaisList = localDAO.listar();
        for (Local l1 : locaisList) {
            locais.addOverlay(
                    new Marker(
                    new LatLng(l1.getLatitude(), l1.getLongitude()),
                    l1.getDescricao()));
        }
    }

    /*
     * Getters e Setters
     */
    public MapModel getLocais() {
        if (locais == null) {
            carregarLocais();
        }
        return locais;
    }

}

Linha 8: a classe MapModel deve ser importada da biblioteca do Primefaces;
Linha 10: método novo, responsável por pegar as coordenadas geográficas e setar no meu objeto Local;
Linha 17: método gravar, responsável por chamar o DAO e gravar o método no banco de dados;
Linha 22: método carregarLocais, este método é responsável por converter uma lista de locais em um MapModel;
Linha 37: neste caso estou mostrando apenas esse get, por ter um diferencial que é carregar os locais caso o mesmo esteja null.

E por fim nossa página index.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true" />
        <title>GMap</title>
    </h:head>
    <h:body style="font-size: 12px">
        <p:gmap id="mapa" center="-15.565986,-54.309782" zoom="15" model="#{localBean.locais}" onPointClick="dlgLocal.show()" type="HYBRID" style="width:600px;height:400px">
            <p:ajax event="pointSelect" listener="#{localBean.novo}" update="formLocal" />
        </p:gmap>

        <p:dialog widgetVar="dlgLocal" width="280" height="100">  
            <h:form id="formLocal">  
                <p:focus for="desc" />
                <h:panelGrid columns="2" style="width: 100%">
                    <h:outputLabel for="desc" value="Descrição:" />  
                    <p:inputText id="desc" value="#{localBean.local.descricao}" /> 
                </h:panelGrid>
                <p:toolbar>
                    <p:toolbarGroup align="right">
                        <p:commandButton value="Gravar" actionListener="#{localBean.gravar()}" oncomplete="dlgLocal.hide()" update=":mapa" />
                    </p:toolbarGroup>
                </p:toolbar>
            </h:form>  
        </p:dialog>
    </h:body>
</html>

Agora vamos entender o que está acontecendo nesta página:
Linha 9:  temos o gmap, com a propriedade model, que está relacionada ao MapModel do nosso controller, e a propriedade onPointClick, que abre o dialog de cadastro de um novo marcador;
Linha 10: chama o método novo quando é clicado no mapa;
Linha 13: temos o dialog que faz o cadastro de um novo marcador.
Ficando dessa forma:


Este projeto foi apenas para dar uma base de como carregar e adicionar marcadores no gmaps com o PrimeFaces. Também não tratei o dialog, ou seja, precisaria de um "trato especial" no caso dele não fechar quando o campo descrição estiver vazio. Bom eu tenho o código que faz isso, mas para não deixar essa postagem mais extensa, não coloquei aqui, provavelmente eu deva criar uma postagem sobre como controlar o dialog, até mesmo como abrir ou fechar ele pelo controller.

Até mais! :)