/*
* Copyright (c) 2013 Bundesamt für Kartographie und Geodäsie.
* See license.txt in the BKG WebMap distribution or repository for the
* full text of the license.
*
* Author: Dirk Thalheim
*/
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/BaseTypes/Element.js
* @requires OpenLayers/Util.js
* @requires OpenLayers/Control.js
* @requires OpenLayers/Events.js
* @requires BKGWebMap/Control.js
* @requires BKGWebMap/Util.js
* @requires BKGWebMap/Util/Toggler.js
* @requires BKGWebMap/Control/SidePanel.js
*/
//noinspection JSUnusedLocalSymbols
/**
* @classdesc Eigenes Control-Element zur Anzeige eines Layerswitchers
*
* @constructor BKGWebMap.Control.LayerSwitcher
* @param {object} options - Optionen für das Controlelement
*/
BKGWebMap.Control.LayerSwitcher = OpenLayers.Class(BKGWebMap.Control.SidePanel, {
/**
* Eventhandler
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {OpenLayers.Events}
*/
events: null,
/**
* Setzt die Reihenfolge der Layer im Layerswitcher.
* In Abhängigkeit von hinzufügen in Map auf- oder absteigend
* sortiert.
*
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {boolean}
*/
ascending: true,
/**
* Eine Kopie der Stati der Layer der Map zum Zeitpunkt der letzten
* Aktualisierung des LayerSwitchers.
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {object[]}
*/
layerStates: null,
/**
* Texte für Labels
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {object.<string,string>}
*/
labels: {
baseLayer: 'Hintergrundkarte',
overlays: 'Overlays'
},
/**
* Tooltip für Toggle-Button
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {string}
*/
title: 'Layerswitcher ein-/ausblenden',
/**
* CSS-Klasse für content
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {string}
*/
style: 'wmLayerSwitcherPanel',
/**
* Breite für Layerswitcher
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {int}
*/
size: 175,
/**
* LayerBaum für die Darstellung
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {object}
*/
layerTree: null,
/**
* Nach Initialisierung werden automatisch alle Gruppen ab dieser Ebene im Layerbaum eingeklappt. Standart ist
* <code>-1</code>. Dies klappt alle Ebenen auf.
* @memberOf BKGWebMap.Control.LayerSwitcher
* @type {int}
*/
closeLevels: -1,
initialize: function(options) {
BKGWebMap.Control.SidePanel.prototype.initialize.apply(this, [options]);
this.layerStates = [];
this.layerEntries = [];
this.outsiteViewport = this.div != null;
this.layerTree = {
baselayers: { name: this.labels.baseLayer, layers: [], groups: {}},
overlays: { name: this.labels.overlays, layers: [], groups: {}}
};
},
destroy: function() {
if(this.events) {
this.events.destroy();
this.events = null;
}
//clear out layers info and unregister their events
this.clearLayers();
this.layerEntries = null;
this.map.events.un({
addlayer: this.redraw,
changelayer: this.redraw,
removelayer: this.redraw,
changebaselayer: this.redraw,
scope: this
});
OpenLayers.Control.prototype.destroy.apply(this, arguments);
},
/**
* Registriert Control für Layer-Map-Events
* @param {OpenLayers.Map} map
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
setMap: function(map) {
OpenLayers.Control.prototype.setMap.apply(this, arguments);
this.map.events.on({
addlayer: this.redraw,
changelayer: this.redraw,
removelayer: this.redraw,
changebaselayer: this.redraw,
scope: this
});
},
/**
* Erstellt die HTML-Elemente des LayerSwitcher
*
* @return {HTMLElement} Eine Referenz zum DOMElement welches die Legende beinhaltet.
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
draw: function() {
var div = this.outsiteViewport ? this.div : BKGWebMap.Control.SidePanel.prototype.draw.apply(this);
this.loadContents(this.outsiteViewport ? div : this.content);
// populate div with current info
this.redraw();
return div;
},
/**
* Checks if the layer state has changed since the last redraw() call.
*
* @return {boolean} <code>true</code> wenn sich der Status seit dem letzten Rendern geändert hat
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
checkRedraw: function() {
if ( !this.layerStates.length || (this.map.layers.length != this.layerStates.length) ) {
return true;
}
for (var i=0, len=this.layerStates.length; i<len; i++) {
var layerState = this.layerStates[i];
var layer = this.map.layers[i];
if ( (layerState.name != layer.name) ||
(layerState.inRange != layer.inRange) ||
(layerState.id != layer.id) ||
(layerState.visibility != layer.visibility) ||
(layerState.group != layer.group) ) {
return true;
}
}
return false;
},
/**
* Ermittelt die aktuellen Layerstati.
* @return {object[]}
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
getCurrentLayerStates: function() {
var len = this.map.layers.length;
var layerStates = new Array(len);
for (var i=0; i < len; i++) {
var layer = this.map.layers[i];
layerStates[i] = {
'name': layer.name,
'visibility': layer.visibility,
'inRange': layer.inRange,
'id': layer.id,
'group': layer.group
};
}
return layerStates;
},
/**
* Ermittelt den aktuellen Status der Kartenlayer und baut daraus den Layerswitcher neu.
*
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
redraw: function() {
// Sofern sich der Status nicht geändert hat ist nichts zu tun.
if (!this.checkRedraw()) {
return;
}
// Anzeige der Layer im Layerswitcher zurücksetzen
this.clearLayers();
// Status wird vor redraw gespeichert, da während des
// redraws Änderungen vorgenommen werden, die sonst eine
// Endlosschleife verursachen könnten.
this.layerStates = this.getCurrentLayerStates();
var layers = this.map.layers.slice();
// if (!this.ascending) { layers.reverse(); }
var baselayers = BKGWebMap.Util.grep(layers, function(layer) { return layer.isBaseLayer; });
var overlays = BKGWebMap.Util.grep(layers, function(layer) { return !layer.isBaseLayer && layer.displayInLayerSwitcher; });
if (!this.ascending) { overlays.reverse(); }
// layerTree aufbauen. Dabei Baselayer und Overviews getrennt behandeln.
this.updateLayerTree(this.layerTree.baselayers, baselayers);
this.updateLayerTree(this.layerTree.overlays, overlays);
// leere Gruppen ab zweiter Hierarchieebene entfernen
this.removeEmptyGroups(this.layerTree.overlays);
// layerTree rendern
for(var id in this.layerTree) {
//noinspection JSUnfilteredForInLoop
var group = this.layerTree[id];
if(BKGWebMap.Util.isEmpty(group.groups) && BKGWebMap.Util.isEmpty(group.layers)) continue;
this.renderLayerTree(group, 0);
this.toc.appendChild(group.titleDiv);
this.toc.appendChild(group.contentDiv);
}
},
/**
* Aktualisiert den LayerTree
* @param layerGroup {object} aktueller Knoten im Layerbaum
* @param layers {array} Liste der Layer die dem Baum hinzugefügt werden
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
updateLayerTree: function(layerGroup, layers) {
for(var i= 0; i < layers.length; i++) {
var layer = layers[i];
var group;
if (!layer.group) {
group = layerGroup;
} else {
// TODO: weitere Untergruppen?
if(layerGroup.groups[layer.group] === undefined) {
layerGroup.groups[layer.group] = {name: layer.group, layers: [], groups: {}};
}
group = layerGroup.groups[layer.group];
}
group.layers.push(layer);
}
},
/**
* Entfernt alle leeren Layergruppen aus dem Baum
* @param tree {object} Der aktuelle Knoten im Layerbaum
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
removeEmptyGroups: function(tree) {
for(var i=tree.groups.length-1; i >= 0; i++) {
var group = tree.groups[i];
this.removeEmptyGroups(group);
if(!group.groups && !group.layers) {
group.groups.splice(i, 1);
}
}
},
/**
* Erstellt und arrangiert alle HTML-Elemente im Layertree
* @param tree {object} der Layerbaum
* @param level {int} aktuelle Ebenennummer im Gesamtbaum
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
renderLayerTree: function(tree, level) {
// Überschrift- und Inhaltsdiv für Gruppe
if(!tree.titleDiv) {
tree.titleDiv = document.createElement('dt');
tree.titleDiv.innerHTML = tree.name;
tree.contentDiv = document.createElement('dd');
OpenLayers.Element.addClass(tree.contentDiv, tree.name.toLowerCase());
tree.toggler = new BKGWebMap.Util.Toggler(
tree.titleDiv, tree.contentDiv,
{ closed: this.closeLevels >= 0 && !(level < this.closeLevels) }
);
}
// Alle Untergruppen
if(tree.groups) {
var grouplist = document.createElement('dl');
for (var key in tree.groups) {
//noinspection JSUnfilteredForInLoop
var group = tree.groups[key];
this.renderLayerTree(group, level + 1);
grouplist.appendChild(group.titleDiv);
grouplist.appendChild(group.contentDiv);
}
tree.contentDiv.appendChild(grouplist);
}
// Alle direkten Layer in der Gruppe
for(var i=0; i<tree.layers.length; i++) {
this.renderLayerEntry(tree.layers[i], tree.contentDiv);
}
},
/**
* Erzeugt die HTML-Darstellung für einen Layer-Eintrag
* @param {OpenLayers.Layer} layer - Der aktuelle Layer
* @param {HTMLElement} groupDiv - HTML-Element, in das Layereintrag angehängt werden soll
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
renderLayerEntry: function(layer, groupDiv) {
var layerEntry = this.getLayerRenderer(layer, this);
layerEntry = layerEntry || new BKGWebMap.Control.LayerSwitcher.LayerEntry(layer, this);
var div = layerEntry.draw();
if(div != null) {
groupDiv.appendChild(div);
}
this.layerEntries.push(layerEntry);
},
/**
* Platzhalter zur Bereitstellung eigener Renderer für Layereinträge im LayerSwitcher
* @param {OpenLayers.Layer} layer - Der aktuelle Layer
* @param {BKGWebMap.Control.LayerSwitcher} parent - der Referenz auf diesen LayerSwitcher
* @return {OpenLayers.Class<BKGWebMap.Control.LayerSwitcher.LayerEntry>}
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
getLayerRenderer: function(layer, parent) {
return null;
},
/**
* Löscht Visualisierung der Layer.
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
clearLayers: function() {
while (this.toc.firstChild) {
this.toc.removeChild(this.toc.firstChild);
}
// layertree leeren
this.clearLayerTree(this.layerTree.baselayers);
this.clearLayerTree(this.layerTree.overlays);
if(this.layerEntries) {
BKGWebMap.Util.each(this.layerEntries, function(index, entry) { entry.destroy(); });
}
this.layerEntries = [];
},
/**
* Löscht die Visualisierung des Layerbaums
* @memberOf BKGWebMap.Control.LayerSwitcher
* @param tree {object} aktueller Knoten im Baum
*/
clearLayerTree: function(tree) {
// HTML Inhalt zurücksetzen
if(tree.contentDiv) {
while (tree.contentDiv.firstChild) {
tree.contentDiv.removeChild(tree.contentDiv.firstChild);
}
}
// Layer zurücksetzen
tree.layers = [];
// Gruppen zurücksetzen
if(tree.groups) {
for(var key in tree.groups) {
//noinspection JSUnfilteredForInLoop
this.clearLayerTree(tree.groups[key]);
}
}
},
/**
* Setzt Layout-Divs und Labels für den LayerSwitcher
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
loadContents: function(div) {
// TOC
this.toc = document.createElement("dl");
OpenLayers.Element.addClass(this.toc, 'toc');
div.appendChild(this.toc);
// Pseudo-Events registrieren, um Interaktion mit LayerSwitcher zu erlauben
this.events = new OpenLayers.Events(this, div, null, true);
this.events.on({
"mousedown": this.onmousedown,
"mousemove": this.onmousemove,
"mouseup": this.onmouseup,
"mouseout": this.onmouseout,
"click": BKGWebMap.Util.stopEvent,
"dblclick": BKGWebMap.Util.stopEvent,
"touchstart": BKGWebMap.Util.stopEvent,
scope: this
});
},
/**
* @param {Event} evt
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
onmousedown: function (evt) {
this.mousedown = true;
OpenLayers.Event.stop(evt, true);
},
/**
* @param {Event} evt
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
onmousemove: function (evt) {
if (this.mousedown) {
OpenLayers.Event.stop(evt, true);
}
},
/**
* @param {Event} evt
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
onmouseup: function (evt) {
if (this.mousedown) {
this.mousedown = false;
OpenLayers.Event.stop(evt, true);
}
},
/**
* @memberOf BKGWebMap.Control.LayerSwitcher
*/
onmouseout: function () {
this.mousedown = false;
},
CLASS_NAME: "BKGWebMap.Control.LayerSwitcher"
});
/**
* Factory-Funktion zur Generierung eines LayerSwitcher Steuerelement.
* @param {Array<OpenLayers.Control>} controls - Liste der Steuerelemente, in die die neue erzeugten Steuerelemente
* eingefügt werden sollen.
* @param {object} config - Konfiguration für Steuerelement (s. Konstruktor BKGWebMap.Control.LayerSwitcher).
*/
BKGWebMap.Control.FACTORIES['layerSwitcher'] = function(controls, config) {
if (!config) return;
config = (typeof config === 'boolean') ? {} : config;
var sidePanel = BKGWebMap.Util.grep(controls, function(c) {return c.CLASS_NAME === 'BKGWebMap.Control.SidePanel'});
config.div = config.div || null;
if (!config.div && sidePanel.length > 0) {
// Layerswitcher in SidePanel einfügen
var title = document.createElement('h3');
title.innerHTML = 'Ebenenauswahl';
sidePanel[0].content.appendChild(title);
config.div = document.createElement('div');
OpenLayers.Element.addClass(config.div, 'layerSwitcher');
sidePanel[0].content.appendChild(config.div);
}
controls.push(new BKGWebMap.Control.LayerSwitcher(config));
};