/**
* Copyright (c) 2008-2010 The Open Source Geospatial Foundation
*
* Published under the BSD license.
* See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
* of the license.
*/
Ext.namespace("GeoExt.tree");
/** private: constructor
* .. class:: LayerNodeUI
*
* Place in a separate file if this should be documented.
*/
GeoExt.tree.LayerNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
/** private: method[constructor]
*/
constructor: function(config) {
GeoExt.tree.LayerNodeUI.superclass.constructor.apply(this, arguments);
},
/** private: method[render]
* :param bulkRender: ``Boolean``
*/
render: function(bulkRender) {
var a = this.node.attributes;
if (a.checked === undefined) {
a.checked = this.node.layer.getVisibility();
}
GeoExt.tree.LayerNodeUI.superclass.render.apply(this, arguments);
var cb = this.checkbox;
if(a.checkedGroup) {
// replace the checkbox with a radio button
var radio = Ext.DomHelper.insertAfter(cb,
[''].join(""));
radio.defaultChecked = cb.defaultChecked;
Ext.get(cb).remove();
this.checkbox = radio;
}
this.enforceOneVisible();
},
/** private: method[onClick]
* :param e: ``Object``
*/
onClick: function(e) {
if(e.getTarget('.x-tree-node-cb', 1)) {
this.toggleCheck(this.isChecked());
} else {
GeoExt.tree.LayerNodeUI.superclass.onClick.apply(this, arguments);
}
},
/** private: method[toggleCheck]
* :param value: ``Boolean``
*/
toggleCheck: function(value) {
value = (value === undefined ? !this.isChecked() : value);
GeoExt.tree.LayerNodeUI.superclass.toggleCheck.call(this, value);
this.enforceOneVisible();
},
/** private: method[enforceOneVisible]
*
* Makes sure that only one layer is visible if checkedGroup is set.
*/
enforceOneVisible: function() {
var attributes = this.node.attributes;
var group = attributes.checkedGroup;
// If we are in the baselayer group, the map will take care of
// enforcing visibility.
if(group && group !== "gx_baselayer") {
var layer = this.node.layer;
var checkedNodes = this.node.getOwnerTree().getChecked();
var checkedCount = 0;
// enforce "not more than one visible"
Ext.each(checkedNodes, function(n){
var l = n.layer
if(!n.hidden && n.attributes.checkedGroup === group) {
checkedCount++;
if(l != layer && attributes.checked) {
l.setVisibility(false);
}
}
});
// enforce "at least one visible"
if(checkedCount === 0 && attributes.checked == false) {
layer.setVisibility(true);
}
}
},
/** private: method[appendDDGhost]
* :param ghostNode ``DOMElement``
*
* For radio buttons, makes sure that we do not use the option group of
* the original, otherwise only the original or the clone can be checked
*/
appendDDGhost : function(ghostNode){
var n = this.elNode.cloneNode(true);
var radio = Ext.DomQuery.select("input[type='radio']", n);
Ext.each(radio, function(r) {
r.name = r.name + "_clone";
});
ghostNode.appendChild(n);
}
});
/** api: (define)
* module = GeoExt.tree
* class = LayerNode
* base_link = `Ext.tree.TreeNode `_
*/
/** api: constructor
* .. class:: LayerNode(config)
*
* A subclass of ``Ext.tree.TreeNode`` that is connected to an
* ``OpenLayers.Layer`` by setting the node's layer property. Checking or
* unchecking the checkbox of this node will directly affect the layer and
* vice versa. The default iconCls for this node's icon is
* "gx-tree-layer-icon", unless it has children.
*
* Setting the node's layer property to a layer name instead of an object
* will also work. As soon as a layer is found, it will be stored as layer
* property in the attributes hash.
*
* The node's text property defaults to the layer name.
*
* If the node has a checkedGroup attribute configured, it will be
* rendered with a radio button instead of the checkbox. The value of
* the checkedGroup attribute is a string, identifying the options group
* for the node.
*
* To use this node type in a ``TreePanel`` config, set ``nodeType`` to
* "gx_layer".
*/
GeoExt.tree.LayerNode = Ext.extend(Ext.tree.AsyncTreeNode, {
/** api: config[layer]
* ``OpenLayers.Layer or String``
* The layer that this layer node will
* be bound to, or the name of the layer (has to match the layer's
* name property). If a layer name is provided, ``layerStore`` also has
* to be provided.
*/
/** api: property[layer]
* ``OpenLayers.Layer``
* The layer this node is bound to.
*/
layer: null,
/** api: config[layerStore]
* :class:`GeoExt.data.LayerStore` ``or "auto"``
* The layer store containing the layer that this node represents. If set
* to "auto", the node will query the ComponentManager for a
* :class:`GeoExt.MapPanel`, take the first one it finds and take its layer
* store. This property is only required if ``layer`` is provided as a
* string.
*/
layerStore: null,
/** api: config[loader]
* ``Ext.tree.TreeLoader|Object`` If provided, subnodes will be added to
* this LayerNode. Obviously, only loaders that process an
* ``OpenLayers.Layer`` or :class:`GeoExt.data.LayerRecord` (like
* :class:`GeoExt.tree.LayerParamsLoader`) will actually generate child
* nodes here. If provided as ``Object``, a
* :class:`GeoExt.tree.LayerParamLoader` instance will be created, with
* the provided object as configuration.
*/
/** private: method[constructor]
* Private constructor override.
*/
constructor: function(config) {
config.leaf = config.leaf || !(config.children || config.loader);
if(!config.iconCls && !config.children) {
config.iconCls = "gx-tree-layer-icon";
}
if(config.loader && !(config.loader instanceof Ext.tree.TreeLoader)) {
config.loader = new GeoExt.tree.LayerParamLoader(config.loader);
}
this.defaultUI = this.defaultUI || GeoExt.tree.LayerNodeUI;
Ext.apply(this, {
layer: config.layer,
layerStore: config.layerStore
});
if (config.text) {
this.fixedText = true;
}
GeoExt.tree.LayerNode.superclass.constructor.apply(this, arguments);
},
/** private: method[render]
* :param bulkRender: ``Boolean``
*/
render: function(bulkRender) {
var layer = this.layer instanceof OpenLayers.Layer && this.layer;
if(!layer) {
// guess the store if not provided
if(!this.layerStore || this.layerStore == "auto") {
this.layerStore = GeoExt.MapPanel.guess().layers;
}
// now we try to find the layer by its name in the layer store
var i = this.layerStore.findBy(function(o) {
return o.get("title") == this.layer;
}, this);
if(i != -1) {
// if we found the layer, we can assign it and everything
// will be fine
layer = this.layerStore.getAt(i).getLayer();
}
}
if (!this.rendered || !layer) {
var ui = this.getUI();
if(layer) {
this.layer = layer;
// no DD and radio buttons for base layers
if(layer.isBaseLayer) {
this.draggable = false;
Ext.applyIf(this.attributes, {
checkedGroup: "gx_baselayer"
});
}
if(!this.text) {
this.text = layer.name;
}
ui.show();
this.addVisibilityEventHandlers();
} else {
ui.hide();
}
if(this.layerStore instanceof GeoExt.data.LayerStore) {
this.addStoreEventHandlers(layer);
}
}
GeoExt.tree.LayerNode.superclass.render.apply(this, arguments);
},
/** private: method[addVisibilityHandlers]
* Adds handlers that sync the checkbox state with the layer's visibility
* state
*/
addVisibilityEventHandlers: function() {
this.layer.events.on({
"visibilitychanged": this.onLayerVisibilityChanged,
scope: this
});
this.on({
"checkchange": this.onCheckChange,
scope: this
});
},
/** private: method[onLayerVisiilityChanged
* handler for visibilitychanged events on the layer
*/
onLayerVisibilityChanged: function() {
if(!this._visibilityChanging) {
this.getUI().toggleCheck(this.layer.getVisibility());
}
},
/** private: method[onCheckChange]
* :param node: ``GeoExt.tree.LayerNode``
* :param checked: ``Boolean``
*
* handler for checkchange events
*/
onCheckChange: function(node, checked) {
if(checked != this.layer.getVisibility()) {
this._visibilityChanging = true;
var layer = this.layer;
if(checked && layer.isBaseLayer && layer.map) {
layer.map.setBaseLayer(layer);
} else {
layer.setVisibility(checked);
}
delete this._visibilityChanging;
}
},
/** private: method[addStoreEventHandlers]
* Adds handlers that make sure the node disappeares when the layer is
* removed from the store, and appears when it is re-added.
*/
addStoreEventHandlers: function() {
this.layerStore.on({
"add": this.onStoreAdd,
"remove": this.onStoreRemove,
"update": this.onStoreUpdate,
scope: this
});
},
/** private: method[onStoreAdd]
* :param store: ``Ext.data.Store``
* :param records: ``Array(Ext.data.Record)``
* :param index: ``Number``
*
* handler for add events on the store
*/
onStoreAdd: function(store, records, index) {
var l;
for(var i=0; i