Reputation: 169
I'm developing a Java Web Application using JSF2 and Primefaces as WebFrameworks. My application is aimed at managing cameras. All the data concerning the cameras are stored into a PostgreSQL database.
I have a JSF view in which I display a dynamic map using Leaflet's Javascript API.
You can see examples of such maps through the following link : Leaflet example.
To sum up, my map is aimed at displaying cameras' markers. The markers' positions are generated using the cameras' attributes.
I've create a composite component and its Backing Bean to handle the map. Here is the view's source code :
<h:body>
<!-- INTERFACE -->
<composite:interface componentType="mapComponent">
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="#{resource['css:leaflet.css']}" />
<!--[if lte IE 8]><link rel="stylesheet" href="#{resource['css:leaflet.ie.css']}" /><![endif]-->
<script
src="#{resource['js:leaflet/Leaflet-v0.4.4-0/dist/leaflet-src.js']}"></script>
<script
src="#{resource['js:leaflet/Leaflet-v0.4.4-0/src/leafclusterer.js']}"></script>
<div id="#{cc.mapId}" style="width: 520px; height: 580px" />
<script type="text/javascript">
//<![CDATA[
var clusterPopup;
function onClustererClick(cluster, coordinates) {
var markers = cluster.getMarkers;
var popupContent = "<p>";
for (var i = 0; i < cluster.getCluster().getMarkers().length; i++) {
var currentMarker = cluster.getCluster().getMarkers()[i];
popupContent += currentMarker.marker._popup._content
popupContent += "<br/>";
}
popupContent += "</p>";
clusterPopup = new L.Popup();
clusterPopup.setLatLng(coordinates);
clusterPopup.setContent(popupContent);
map.openPopup(clusterPopup);
};
var cameraIcon = L.icon({
iconUrl : "#{resource['img:leaflet/camera.png']}",
iconSize : [ 48, 48 ],
shadowSize : [ 48, 48 ],
iconAnchor : [ 30, 30 ],
popupAnchor : [ 0, -20 ]
});
var marker;
function onMapClick(e) {
marker = new L.marker(e.latlng, {
draggable : true,
icon : cameraIcon
});
map.addLayer(marker);
marker.bindPopup("<b>Hello world!</b><br />I am a popup.");
map.off('click');
};
var osmUrl = '#{cc.osmUrl}';
var osmAttrib = '#{cc.osmAttrib}';
var basic = new L.tileLayer(osmUrl, {
minZoom : #{cc.minZoom},
maxZoom : #{cc.maxZoom},
attribution : osmAttrib
});
var map = new L.Map('#{cc.mapId}', {
center : new L.LatLng(#{cc.mapX}, #{cc.mapY}),
zoom : #{cc.mapDefaultZoom},
layers : [ basic ]
});
var baseMaps = {
"#{cc.mapBasicLayerName}" : basic
};
if(#{cc.mapEnableOnClick}) {
map.on('click', onMapClick);
}
var clusterer = new LeafClusterer(map);
//]]>
</script>
<composite:insertChildren />
</composite:implementation>
</h:body>
</html>
And here is the code of my backing bean :
@FacesComponent(value = "mapComponent")
public class MapComponent extends UINamingContainer {
private static final String MAP_ID_PROP = "map_id";
private static final String OSM_URL_PROP = "osm_url";
private static final String OSM_ATTRIB_PROP = "osm_attrib";
private static final String MIN_ZOOM_PROP = "min_zoom";
private static final String MAX_ZOOM_PROP = "max_zoom";
private static final String MAP_X_PROP = "map_x";
private static final String MAP_Y_PROP = "map_y";
private static final String MAP_DEFAULT_ZOOM_PROP = "map_default_zoom";
private static final String MAP_BASIC_LAYER_NAME_PROP = "map_basic_layer_name";
private static final String MAP_ENABLE_ON_CLICK_PROP = "map_enable_on_click";
private String mapId;
private String osmUrl;
private String osmAttrib;
private Integer minZoom;
private Integer maxZoom;
private Double mapX;
private Double mapY;
private Integer mapDefaultZoom;
private String mapBasicLayerName;
private Boolean mapEnableOnClick;
/**
* Instanties un nouveau map component.
*/
public MapComponent() {
super();
mapId = (String) AppUtil.getProperty(MAP_ID_PROP);
osmUrl = (String) AppUtil.getProperty(OSM_URL_PROP);
osmAttrib = (String) AppUtil.getProperty(OSM_ATTRIB_PROP);
minZoom = Integer.parseInt((String) AppUtil.getProperty(MIN_ZOOM_PROP));
maxZoom = Integer.parseInt((String) AppUtil.getProperty(MAX_ZOOM_PROP));
mapX = Double.parseDouble((String) AppUtil.getProperty(MAP_X_PROP));
mapY = Double.parseDouble((String) AppUtil.getProperty(MAP_Y_PROP));
mapDefaultZoom = Integer.parseInt((String) AppUtil.getProperty(MAP_DEFAULT_ZOOM_PROP));
mapBasicLayerName = (String) AppUtil.getProperty(MAP_BASIC_LAYER_NAME_PROP);
setMapEnableOnClick(Boolean.parseBoolean((String) AppUtil.getProperty(MAP_ENABLE_ON_CLICK_PROP)));
}
/**
* Getter : retourne le map id.
*
* @return le map id
*/
public String getMapId() {
return mapId;
}
/**
* Getter : retourne le osm url.
*
* @return le osm url
*/
public String getOsmUrl() {
return osmUrl;
}
/**
* Getter : retourne le osm attrib.
*
* @return le osm attrib
*/
public String getOsmAttrib() {
return osmAttrib;
}
/**
* Getter : retourne le min zoom.
*
* @return le min zoom
*/
public Integer getMinZoom() {
return minZoom;
}
/**
* Getter : retourne le max zoom.
*
* @return le max zoom
*/
public Integer getMaxZoom() {
return maxZoom;
}
/**
* Getter : retourne le map x.
*
* @return le map x
*/
public Double getMapX() {
return mapX;
}
/**
* Getter : retourne le map y.
*
* @return le map y
*/
public Double getMapY() {
return mapY;
}
/**
* Getter : retourne le map default zoom.
*
* @return le map default zoom
*/
public Integer getMapDefaultZoom() {
return mapDefaultZoom;
}
/**
* Getter : retourne le map basic layer name.
*
* @return le map basic layer name
*/
public String getMapBasicLayerName() {
return mapBasicLayerName;
}
/**
* Setter : affecte le map id.
*
* @param mapId le map id
*/
public void setMapId(String mapId) {
this.mapId = mapId;
}
/**
* Setter : affecte le osm url.
*
* @param osmUrl le osm url
*/
public void setOsmUrl(String osmUrl) {
this.osmUrl = osmUrl;
}
/**
* Setter : affecte le osm attrib.
*
* @param osmAttrib le osm attrib
*/
public void setOsmAttrib(String osmAttrib) {
this.osmAttrib = osmAttrib;
}
/**
* Setter : affecte le min zoom.
*
* @param minZoom le min zoom
*/
public void setMinZoom(Integer minZoom) {
this.minZoom = minZoom;
}
/**
* Setter : affecte le max zoom.
*
* @param maxZoom le max zoom
*/
public void setMaxZoom(Integer maxZoom) {
this.maxZoom = maxZoom;
}
/**
* Setter : affecte le map x.
*
* @param mapX le map x
*/
public void setMapX(Double mapX) {
this.mapX = mapX;
}
/**
* Setter : affecte le map y.
*
* @param mapY le map y
*/
public void setMapY(Double mapY) {
this.mapY = mapY;
}
/**
* Setter : affecte le map default zoom.
*
* @param mapDefaultZoom le map default zoom
*/
public void setMapDefaultZoom(Integer mapDefaultZoom) {
this.mapDefaultZoom = mapDefaultZoom;
}
/**
* Setter : affecte le map basic layer name.
*
* @param mapBasicLayerName le map basic layer name
*/
public void setMapBasicLayerName(String mapBasicLayerName) {
this.mapBasicLayerName = mapBasicLayerName;
}
/**
* Getter : retourne le map enable on click.
*
* @return le map enable on click
*/
public Boolean getMapEnableOnClick() {
return mapEnableOnClick;
}
/**
* Setter : affecte le map enable on click.
*
* @param mapEnableOnClick le map enable on click
*/
public void setMapEnableOnClick(Boolean mapEnableOnClick) {
this.mapEnableOnClick = mapEnableOnClick;
}
}
To manage my components, I've also created a backing bean. Here is the xhtml source :
<h:body>
<!-- INTERFACE -->
<composite:interface componentType="mapMarker">
<composite:attribute name="longitude" type="java.lang.Float" />
<composite:attribute name="latitude" type="java.lang.Float" />
<composite:attribute name="padding" type="java.lang.Integer" />
<composite:attribute name="popUpContent" type="java.lang.String" />
<composite:attribute name="maxZoom" type="java.lang.Integer" />
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<script type="text/javascript">
//
function onPopupClick(padding, lat, lng) {
map.closePopup(clusterPopup);
if(map.getZoom() < #{cc.maxZoom}) {
var pos = map.latLngToLayerPoint(new L.LatLng(#{cc.latitude}, #{cc.longitude}));
var sw = new L.point(pos.x - padding, pos.y + padding);
sw = map.layerPointToLatLng(sw);
var ne = new L.point(pos.x + padding, pos.y - padding);
ne = map.layerPointToLatLng(ne);
map.setView(new L.LatLng(lat, lng), #{cc.maxZoom});
}
};
marker = new L.marker([#{cc.latitude}, #{cc.longitude}], {
draggable : true,
icon : cameraIcon
});
var popUpContent = '<span style="cursor:pointer;" id="#{cc.id}Link" onclick="onPopupClick(#{cc.padding},' + #{cc.latitude} + ',' + #{cc.longitude} + ')">#{cc.popUpContent}</span>';
marker.bindPopup(popUpContent);
clusterer.addMarker(marker);
</script>
</composite:implementation>
</h:body>
And here is the source code of my Backing Bean :
@FacesComponent(value = "mapMarker")
public class MapMarker extends UINamingContainer {
private static final String PADDING_PROP = "padding";
private static final String MAX_ZOOM_MARKER_PROP = "max_zoom_marker";
private Float longitude;
private Float latitude;
private String popUpContent;
private Integer padding;
private Integer maxZoom;
public MapMarker() {
padding = Integer.parseInt((String) AppUtil.getProperty(PADDING_PROP));
maxZoom = Integer.parseInt((String) AppUtil.getProperty(MAX_ZOOM_MARKER_PROP));
}
public Float getLongitude() {
return longitude;
}
public void setLongitude(Float longitude) {
this.longitude = longitude;
}
public Float getLatitude() {
return latitude;
}
public void setLatitude(Float latitude) {
this.latitude = latitude;
}
public String getPopUpContent() {
return popUpContent;
}
public void setPopUpContent(String popUpContent) {
this.popUpContent = popUpContent;
}
public Integer getPadding() {
return padding;
}
public void setPadding(Integer padding) {
this.padding = padding;
}
public Integer getMaxZoom() {
return maxZoom;
}
public void setMaxZoom(Integer maxZoom) {
this.maxZoom = maxZoom;
}
}
The components sets their attributes by reading a properties file containing default values. Here is the content of the file :
# Cartographie OpenStreetMap
map_id=map
osm_attrib=test
min_zoom=1
max_zoom=13
map_x=48.227
map_y=6.611
map_default_zoom=8
map_basic_layer_name=Basique
map_enable_on_click=true
# Cartographie OpenStreetMap
# Marqueur de caméra
padding=20
max_zoom_marker=11
# Marqueur de caméra
I have a bean "ConsultationBean" that handles the view containing the map. The bean uses a service to store in an ArrayList, all the cameras contained in the database. In the view, I call the init method via a prerenderView evet, that creates all the MapMarker by using the cameras list. Here is the xhtml source code :
<ui:composition template="/xhtml/common/layout.xhtml">
<ui:define name="headTitle">
<h:outputText value="#Cartographie#" />
</ui:define>
<ui:define name="header">
<ui:include src="/xhtml/common/header.xhtml">
<ui:param name="headerClass" value="banner-thin" />
</ui:include>
</ui:define>
<ui:define name="content">
<f:metadata>
<f:event type="preRenderView" listener="#{consultationBean.init}"
update="content,mapPanelGroup"></f:event>
</f:metadata>
<div id="content" class="content-home ui-corner-bottom">
<h2 class="main-title content-title light-bg light-bordered-top">
<h:outputText value="#Cartographie#" />
</h2>
<h:panelGroup id="mapPanelGroup">
<util:map id="mapContainer">
<ui:repeat var="marker" value="#{consultationBean.markers}">
<util:mapMarker/>
</ui:repeat>
</util:map>
</h:panelGroup>
</div>
</ui:define>
</ui:composition>
</html>
Here is the source code of the backing bean :
/**
* Le Class ConsultationBean.
*/
@Controller
@Scope("view")
@SuppressWarnings("serial")
public class ConsultationBean implements Serializable {
/** La constante LOGGER. */
private static final Logger LOGGER = Logger.getLogger(ConsultationBean.class);
/** Le cameras. */
private List<Camera> cameras;
/** Le map. */
private UINamingContainer map;
/** Le markers. */
private List<MapMarker> markers;
/** Le camera service. */
@Autowired
private CameraService cameraService;
/**
* Initialise le.
*/
public void init() {
cameras = cameraService.findAll();
map = (UINamingContainer) FacesUtils.findComponentById(FacesContext.getCurrentInstance(), "mapContainer");
initMarkers();
}
/**
* Initialise le markers.
*/
public void initMarkers() {
markers = new ArrayList<MapMarker>();
for (Camera camera : cameras) {
MapMarker mapMarker = new MapMarker();
mapMarker.setLatitude(camera.getY());
mapMarker.setLongitude(camera.getX());
mapMarker.setPopUpContent(camera.toString());
markers.add(mapMarker);
map.getChildren().add(mapMarker);
map.getChildren().add(new HtmlInputText());
}
}
/**
* Getter : retourne le markers.
*
* @return le markers
*/
public List<MapMarker> getMarkers() {
return markers;
}
/**
* Setter : affecte le markers.
*
* @param markers le markers
*/
public void setMarkers(List<MapMarker> markers) {
this.markers = markers;
}
}
My problem is that I need to set my attributes into the MapMarker's default constructor. For instance, by using the follogwing default constructor, my markers will display on the map at the exact same point :
public MapMarker() {
padding = Integer.parseInt((String) AppUtil.getProperty(PADDING_PROP));
maxZoom = Integer.parseInt((String) AppUtil.getProperty(MAX_ZOOM_MARKER_PROP));
latitude = (float) 47.67876;
longitude = (float) 6.97061;
}
What I want to do is making my "init" method from the "ConsultationBean" efficient in a sense that it initializes the marker's attributes like the default constructor. I must use the "init" method because it's where I use my list of cameras.
Maybe I should rerender the view and that's the reason why I've used an update attribute of the preRender event, but it doesn't work.
If someone has an idea to solve this problem...
Thanks in advance.
Update 1 :
The "init" method from "ConsultationBean" is unefficient. Indeed, I create "MapMarker" and add them to the map, but they never present in the view. However, the markers that are contained in the view are those generated by the view and vi the "ui:repeat" tag :
<ui:repeat var="marker" value="#{consultationBean.markers}">
<util:mapMarker/>
</ui:repeat>
I've noticed that my MapMarkers are constructed before the "ConsultationBean". As a consequence, my cameras list and markers list are empty, but the number of markers generated is the same as the number of cameras stored in database.
I've tried to put an id to the markers with "#{camera.code}# as a value, with the following code :
<ui:repeat var="camera" value="#{consultationBean.cameras}">
<util:mapMarker id="#{camera.code}"/>
</ui:repeat>
Unfortunately it failed, because the list is empty. I'm really stuck with the manner of generating my markers.
Upvotes: 1
Views: 3337
Reputation: 169
I've finally found the solution. What I've done is that I've added an additionnal component to the view named "MapMarkersContainer". This component contains the list of cameras and this list is filled before the MapMarker's "encodeBegin" is called. I've modified the "encodeBegin" of "MapMarker" in order to get all the cameras infos through "MapMarkersContainer"'s list.
<h:body>
<!-- INTERFACE -->
<composite:interface componentType="mapMarkersContainer">
<composite:attribute name="cameras" type="java.util.List" />
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
</composite:implementation>
</h:body>
</html>
Here is the source code of the Backing Bean :
@FacesComponent(value = "mapMarkersContainer")
public class MapMarkersContainer extends UINamingContainer {
private List<Camera> cameras;
private Integer currentIndex = 0;
public List<Camera> getCameras() {
currentIndex++;
return cameras;
}
public void setCameras(List<Camera> cameras) {
this.cameras = cameras;
}
public Integer getCurrentIndex() {
return currentIndex;
}
public void setCurrentIndex(Integer currentIndex) {
this.currentIndex = currentIndex;
}
}
Here is the modification that occurs in "MapMarker" :
@FacesComponent(value = "mapMarker")
public class MapMarker extends UINamingContainer {
private static final String PADDING_PROP = "padding";
private static final String MAX_ZOOM_MARKER_PROP = "max_zoom_marker";
private Float longitude;
private Float latitude;
private String popUpContent;
private Integer padding;
private Integer maxZoom;
private Camera camera;
public MapMarker() {
padding = Integer.parseInt((String) AppUtil.getProperty(PADDING_PROP));
maxZoom = Integer.parseInt((String) AppUtil.getProperty(MAX_ZOOM_MARKER_PROP));
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
padding = Integer.parseInt((String) AppUtil.getProperty(PADDING_PROP));
maxZoom = Integer.parseInt((String) AppUtil.getProperty(MAX_ZOOM_MARKER_PROP));
MapMarkersContainer mapMarkersContainer = (MapMarkersContainer) FacesUtils.findComponentById(context,
"mapMarkersContainer");
Integer cameraIndex = mapMarkersContainer.getCurrentIndex();
Camera camera = mapMarkersContainer.getCameras().get(cameraIndex);
latitude = camera.getY();
longitude = camera.getX();
popUpContent = camera.toString();
setId(camera.getNom());
super.encodeBegin(context);
}
public Float getLongitude() {
return longitude;
}
public void setLongitude(Float longitude) {
this.longitude = longitude;
}
public Float getLatitude() {
return latitude;
}
public void setLatitude(Float latitude) {
this.latitude = latitude;
}
public String getPopUpContent() {
return popUpContent;
}
public void setPopUpContent(String popUpContent) {
this.popUpContent = popUpContent;
}
public Integer getPadding() {
return padding;
}
public void setPadding(Integer padding) {
this.padding = padding;
}
public Integer getMaxZoom() {
return maxZoom;
}
public void setMaxZoom(Integer maxZoom) {
this.maxZoom = maxZoom;
}
public Camera getCamera() {
return camera;
}
public void setCamera(Camera camera) {
this.camera = camera;
}
}
And here is what changed in "ConsultationBean" view :
<ui:composition template="/xhtml/common/layout.xhtml">
<ui:define name="headTitle">
<h:outputText value="#Cartographie#" />
</ui:define>
<ui:define name="header">
<ui:include src="/xhtml/common/header.xhtml">
<ui:param name="headerClass" value="banner-thin" />
</ui:include>
</ui:define>
<ui:define name="content">
<util:mapMarkersContainer id="mapMarkersContainer"/>
<div id="content" class="content-home ui-corner-bottom">
<h2 class="main-title content-title light-bg light-bordered-top">
<h:outputText value="#Cartographie#" />
</h2>
<h:panelGroup id="mapPanelGroup">
<util:map id="mapContainer">
<ui:repeat id="repeat" var="camera" value="#{consultationBean.cameras}">
<util:mapMarker/>
</ui:repeat>
</util:map>
</h:panelGroup>
</div>
</ui:define>
</ui:composition>
</html>
And here is the Java source code of "ConsultationBean" :
/**
* Le Class ConsultationBean.
*/
@Controller
@Scope("view")
@SuppressWarnings("serial")
public class ConsultationBean implements Serializable {
/** Le cameras. */
private List<Camera> cameras;
/** Le markers. */
private List<MapMarker> markers;
private MapMarkersContainer mapMarkersContainer;
/** Le camera service. */
@Autowired
private CameraService cameraService;
/**
* Initialise le.
*
* @throws IOException
*/
@PostConstruct
public void init() throws IOException {
cameras = cameraService.findAll();
mapMarkersContainer = (MapMarkersContainer) FacesUtils.findComponentById(FacesContext.getCurrentInstance(),
"mapMarkersContainer");
mapMarkersContainer.setCameras(cameras);
}
/**
* Getter : retourne le markers.
*
* @return le markers
*/
public List<MapMarker> getMarkers() {
return markers;
}
/**
* Setter : affecte le markers.
*
* @param markers le markers
*/
public void setMarkers(List<MapMarker> markers) {
this.markers = markers;
}
public List<Camera> getCameras() {
return cameras;
}
public void setCameras(List<Camera> cameras) {
this.cameras = cameras;
}
}
Now, each marker has is own ID and its own correct value. A counter is updated each time you use the getter methode of the cameras list, so that you can access the correct Camera at the correct index.
Thanks to Guilherme Torres Castro for the answer and the discovery of the "encodeBegin" method.
Upvotes: 1
Reputation: 15350
Try this:
@Override
public void encodeBegin(FacesContext context) throws IOException{
padding = Integer.parseInt((String) AppUtil.getProperty(PADDING_PROP));
maxZoom = Integer.parseInt((String) AppUtil.getProperty(MAX_ZOOM_MARKER_PROP));
latitude = (float) 47.67876;
longitude = (float) 6.97061;
super.encodeBegin(context);
}
Upvotes: 0