[Commits] r1505 - in sandbox/ahocevar/playground/trunk/geoext: . build examples examples/data lib lib/GeoExt lib/GeoExt/data lib/GeoExt/widgets lib/GeoExt/widgets/form lib/GeoExt/widgets/grid lib/GeoExt/widgets/tips lib/GeoExt/widgets/tree lib/overrides resources/css resources/images resources/images/default resources/images/gray resources/images/slate tests tests/lib tests/lib/GeoExt tests/lib/GeoExt/data tests/lib/GeoExt/widgets tests/lib/GeoExt/widgets/form tests/lib/GeoExt/widgets/grid tests/lib/GeoExt/widgets/tree tests/lib/overrides

commits at geoext.org commits at geoext.org
Fri Nov 27 10:20:18 CET 2009


Author: ahocevar
Date: 2009-11-27 10:20:18 +0100 (Fri, 27 Nov 2009)
New Revision: 1505

Added:
   sandbox/ahocevar/playground/trunk/geoext/build/Makefile
   sandbox/ahocevar/playground/trunk/geoext/build/geoext-license.js
   sandbox/ahocevar/playground/trunk/geoext/examples/attributes.html
   sandbox/ahocevar/playground/trunk/geoext/examples/attributes.js
   sandbox/ahocevar/playground/trunk/geoext/examples/data/describe_feature_type.xml
   sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.html
   sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.js
   sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.html
   sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.js
   sandbox/ahocevar/playground/trunk/geoext/examples/renderer.html
   sandbox/ahocevar/playground/trunk/geoext/examples/renderer.js
   sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.html
   sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.js
   sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.html
   sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMCReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Action.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/FeatureRenderer.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LayerOpacitySlider.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/ZoomSlider.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerLoader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamLoader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamNode.js
   sandbox/ahocevar/playground/trunk/geoext/lib/overrides/
   sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js
   sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst
   sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-slate.css
   sandbox/ahocevar/playground/trunk/geoext/resources/images/slate/
   sandbox/ahocevar/playground/trunk/geoext/resources/images/slate/anchor.png
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/adapter/
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.js
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.js
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.js
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.js
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Action.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/FeatureRenderer.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LayerOpacitySlider.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendImage.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendWMS.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/ZoomSlider.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/BaseLayerContainer.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerLoader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamLoader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamNode.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html
Removed:
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js
   sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js
   sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst
   sandbox/ahocevar/playground/trunk/geoext/resources/css/example.css
   sandbox/ahocevar/playground/trunk/geoext/resources/images/slate/anchor.png
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html
Modified:
   sandbox/ahocevar/playground/trunk/geoext/build/full.cfg
   sandbox/ahocevar/playground/trunk/geoext/build/readme.txt
   sandbox/ahocevar/playground/trunk/geoext/examples/data/wmscap.xml
   sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.html
   sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.js
   sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.html
   sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.js
   sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.html
   sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.js
   sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.html
   sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.js
   sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.html
   sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.js
   sandbox/ahocevar/playground/trunk/geoext/examples/popup.html
   sandbox/ahocevar/playground/trunk/geoext/examples/popup.js
   sandbox/ahocevar/playground/trunk/geoext/examples/search-form.html
   sandbox/ahocevar/playground/trunk/geoext/examples/search-form.js
   sandbox/ahocevar/playground/trunk/geoext/examples/tree.html
   sandbox/ahocevar/playground/trunk/geoext/examples/tree.js
   sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.html
   sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.js
   sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.html
   sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/SingleFile.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureRecord.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerRecord.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ProtocolProxy.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ScaleStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesReader.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesStore.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendImage.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendPanel.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendWMS.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/MapPanel.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Popup.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/BasicForm.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/FormPanel.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/SearchAction.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/BaseLayerContainer.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerContainer.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerNode.js
   sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/OverlayLayerContainer.js
   sandbox/ahocevar/playground/trunk/geoext/license.txt
   sandbox/ahocevar/playground/trunk/geoext/resources/css/geoext-all-debug.css
   sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-gray.css
   sandbox/ahocevar/playground/trunk/geoext/resources/css/popup.css
   sandbox/ahocevar/playground/trunk/geoext/resources/images/default/anchor.png
   sandbox/ahocevar/playground/trunk/geoext/resources/images/gray/anchor.png
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/FeatureStore.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerReader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerRecord.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerStore.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ProtocolProxy.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ScaleStore.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.js
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendPanel.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/MapPanel.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Popup.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/form/SearchAction.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerContainer.html
   sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerNode.html
   sandbox/ahocevar/playground/trunk/geoext/tests/list-tests.html
Log:
merging r1504 from trunk

Copied: sandbox/ahocevar/playground/trunk/geoext/build/Makefile (from rev 1504, core/trunk/geoext/build/Makefile)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/build/Makefile	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/build/Makefile	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,71 @@
+# Makefile for GeoExt
+
+VERSION = r$(shell echo '$$Revision$$' | tr -d "[:alpha:]$$ :")
+LIB_NAME = GeoExt
+ZIP_NAME = $(LIB_NAME)-$(VERSION).zip
+
+CSS_DIR = $(LIB_NAME)/resources/css
+CSS_IMPORTS = $(shell cat ../resources/css/geoext-all-debug.css | grep '@import' | sed 's/^@import "\(.*\)";/\1/')
+ALL_CSS = $(CSS_DIR)/geoext-all.css
+
+.PHONEY: help clean ext examples lib zip dist
+
+help:
+	@echo "Please use 'make <target>' where <target> is one or more of"
+	@echo "  clean    to clean up after building"
+	@echo "  ext      to make a custom Ext JS build. See readme.txt for requirements"
+	@echo "  examples to make the examples dir"
+	@echo "  lib      to make a standalone dir of lib resources"
+	@echo "  zip      to make a zip archive of a standalone lib"
+	@echo "  dist     to make a zip archive of a standalone lib with examples"
+	@echo
+	@echo "Example use:"
+	@echo "  make lib"
+	@echo "  make lib LIB_NAME=MyLib"
+	@echo "  make zip"
+	@echo "  make zip ZIP_NAME=MyZip.zip"
+	@echo "  make dist VERSION=0.5-rc1"
+
+clean:
+	-rm -rf $(LIB_NAME)
+	-rm -f $(ZIP_NAME)
+
+ext:
+	@echo "Building ext.js."
+	mkdir -p $(LIB_NAME)/script
+	jsbuild full.cfg -v -o $(LIB_NAME)/script/ -j ext.js
+
+examples:
+	mkdir -p $(LIB_NAME)/examples 
+	rsync -au --exclude=.svn `pwd`/../examples `pwd`/$(LIB_NAME)
+	for file in `find $(LIB_NAME)/examples/ -name "*.html"`; do \
+	    sed -i "s/\.\.\/lib\/GeoExt\.js/\.\.\/script\/GeoExt\.js/g" $${file} ; \
+	    sed -i "s/geoext-all-debug\.css/geoext-all\.css/g" $${file} ; \
+	done ; 	
+
+lib:
+	@echo "Building the library."
+	mkdir -p $(LIB_NAME)/script $(LIB_NAME)/resources/css
+	
+	jsbuild full.cfg -v -o $(LIB_NAME)/script/ -j GeoExt.js
+	echo "GeoExt.VERSION_NUMBER='$(VERSION)';" >> $(LIB_NAME)/script/GeoExt.js 
+	
+	rsync -au --exclude=.svn `pwd`/../resources `pwd`/$(LIB_NAME)
+	
+	for file in `find $(CSS_DIR) -name "*.css"`; do \
+	    csstidy $${file} --template=highest $${file} ; \
+	done ;
+	echo "" > $(ALL_CSS)
+	for filename in $(CSS_IMPORTS); do \
+	    cat $(CSS_DIR)/$${filename} >> $(ALL_CSS) ; \
+	done ;
+	-rm $(CSS_DIR)/geoext-all-debug.css
+
+	cp ../license.txt $(LIB_NAME)
+
+zip: lib
+	@echo "Archiving the library."
+	-rm -f $(ZIP_NAME)
+	zip $(ZIP_NAME) -r $(LIB_NAME)
+
+dist: examples zip

Modified: sandbox/ahocevar/playground/trunk/geoext/build/full.cfg
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/build/full.cfg	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/build/full.cfg	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,5 +1,6 @@
 [GeoExt.js]
 root = ../lib
+license = geoext-license.js
 exclude =
     GeoExt.js
     GeoExt/SingleFile.js

Copied: sandbox/ahocevar/playground/trunk/geoext/build/geoext-license.js (from rev 1504, core/trunk/geoext/build/geoext-license.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/build/geoext-license.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/build/geoext-license.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,30 @@
+/*
+
+Copyright (c) 2008-2009, The Open Source Geospatial Foundation
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the Open Source Geospatial Foundation nor the names
+      of its contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+*/

Modified: sandbox/ahocevar/playground/trunk/geoext/build/readme.txt
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/build/readme.txt	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/build/readme.txt	2009-11-27 09:20:18 UTC (rev 1505)
@@ -2,23 +2,54 @@
 ===============
 
 This directory contains configuration files necessary for building GeoExt
-(and ExtJS).  The build configuration is intended for use with the jsbuild
+(and Ext JS).  The build configuration is intended for use with the jsbuild
 utility included in JSTools (http://projects.opengeo.org/jstools).
 
 Brief instructions
 ------------------
 
-Install JSTools.
+This build dir contains a Makefile, which can be used to build GeoExt. The
+build process requires a bash-like shell, make, sed, find and rsync. All this
+should be available on well equipped development *nix and OS X boxes. In
+addition, JSTools and csstidy are required. The latter is available as csstidy
+package on debian-style linux systems.
 
+    $ sudo aptitude install csstidy
+
+To install JSTools, python-setuptools is required. This is available as
+python-setuptools package on debian-style linux systems.
+
+    $ sudo aptitude install python-setuptools
+    
+Now you can easily install JSTools.
+
     $ easy_install http://svn.opengeo.org/jstools/trunk/
 
-Change into the core/trunk/build directory.
+Change into the core/trunk/build directory (the one containing the readme.txt
+file you are reading right now), if you are not already here.
 
-    $ cd core/trunk/geoext/build
+    $ cd geoext/build
 
-Run jsbuild.
+From here, you can build the library.
 
-    $ jsbuild full.cfg
+    $ make zip
     
-For more complete instructions on building GeoExt, see the documentation
-on the project website: http://www.geoext.org/trac/geoext/wiki/builds.
+Now you can take the resulting GeoExt.zip file and unpack it on your web
+server. The library itself resides in the script folder, the resources folder
+contains css files and images.
+
+For more complete instructions on building GeoExt with jsbuild, see the
+documentation on the project website:
+http://www.geoext.org/trac/geoext/wiki/builds.
+
+The Makefile also contains a target for building Ext JS. This requires Ext JS
+to be installed in ../../ext (relative to this build dir). Make sure to
+provide a license file matching your Ext JS installation and replace the
+provided ext-license.js file with it. To build the library with ext.js in the
+script folder, run make with the ext and zip targets.
+
+    $ make ext zip
+    
+The Makefile has even more targets. Invoke make help to see them all.
+
+    $ make help

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/attributes.html (from rev 1504, core/trunk/geoext/examples/attributes.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/attributes.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/attributes.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,23 @@
+<html>
+    <head>
+        <title>GeoExt AttributeReader and AttributeStore</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+        <script type="text/javascript" src="attributes.js"></script>
+
+    </head>
+    <body>
+        <h1>AttributeReader and AttributeStore</h1>
+        <p>This is example that shows how create an AttributeStore with
+        records read from a WFS DescribeFeatureType response.</p>
+
+        <p>Note that the js is not minified so it is readable.
+        See <a href="attributes.js">attributes.js</a>.</p>
+
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/attributes.js (from rev 1504, core/trunk/geoext/examples/attributes.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/attributes.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/attributes.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[attributes]
+ *  Attribute Store & Reader
+ *  ------------------------
+ *  Create records with attribute types and values with an AttributeStore.
+ */
+
+var store;
+Ext.onReady(function() {
+    
+    // create a new attributes store
+    store = new GeoExt.data.AttributeStore({
+        url: "data/describe_feature_type.xml"
+    });
+    store.load();
+
+    // create a grid to display records from the store
+    var grid = new Ext.grid.GridPanel({
+        title: "Feature Attributes",
+        store: store,
+        cm: new Ext.grid.ColumnModel([
+            {id: "name", header: "Name", dataIndex: "name", sortable: true},
+            {id: "type", header: "Type", dataIndex: "type", sortable: true}
+        ]),
+        sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
+        autoExpandColumn: "name",
+        renderTo: document.body,
+        height: 300,
+        width: 350
+    });    
+
+});

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/data/describe_feature_type.xml (from rev 1504, core/trunk/geoext/examples/data/describe_feature_type.xml)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/data/describe_feature_type.xml	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/data/describe_feature_type.xml	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><xs:schema  targetNamespace="http://www.openplans.org/topp"  xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0"><xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://localhost:8080/geoserver/schemas/gml/2.1.2.1/feature.xsd"/><xs:complexType xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/2001/XMLSchema" name="states_Type"><xs:complexContent><xs:extension base="gml:AbstractFeatureType"><xs:sequence><xs:element name="the_geom" minOccurs="0" nillable="true" type="gml:MultiPolygonPropertyType"/><xs:element name="STATE_NAME" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="2147483647"/></xs:restriction></xs:simpleType></xs:element><xs:element name="STATE_FIPS" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="2147483647"/></xs:restriction></xs:simpleType></xs:element><xs:element name="SUB_REGION" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="2147483647"/></xs:restriction></xs:simpleType></xs:element><xs:element name="STATE_ABBR" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="2147483647"/></xs:restriction></xs:simpleType></xs:element><xs:element name="LAND_KM" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="WATER_KM" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="PERSONS" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="FAMILIES" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="HOUSHOLD" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="MALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="FEMALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="WORKERS" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="DRVALONE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="CARPOOL" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="PUBTRANS" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="EMPLOYED" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="UNEMPLOY" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="SERVICE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="MANUAL" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="P_MALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="P_FEMALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="SAMP_POP" minOccurs="0" nillable="true" type="xs:double"/></xs:sequence></xs:extension></xs:complexContent></xs:complexType><xs:element name="states" type="topp:states_Type" substitutionGroup="gml:_Feature"/></xs:schema>
\ No newline at end of file

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/data/wmscap.xml
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/data/wmscap.xml	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/data/wmscap.xml	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://demo.opengeo.org:80/geoserver/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd">
-<WMT_MS_Capabilities version="1.1.1" updateSequence="70">
+<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://demo.opengeo.org/geoserver/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd">
+<WMT_MS_Capabilities version="1.1.1" updateSequence="146">
   <Service>
     <Name>OGC:WMS</Name>
     <Title>GeoServer Web Map Service</Title>
@@ -10,7 +10,7 @@
       <Keyword>WMS</Keyword>
       <Keyword>GEOSERVER</Keyword>
     </KeywordList>
-    <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms"/>
+    <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms"/>
     <ContactInformation>
       <ContactPersonPrimary>
         <ContactPerson>Claudius Ptolomaeus</ContactPerson>
@@ -39,10 +39,10 @@
         <DCPType>
           <HTTP>
             <Get>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Get>
             <Post>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Post>
           </HTTP>
         </DCPType>
@@ -79,7 +79,7 @@
         <DCPType>
           <HTTP>
             <Get>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Get>
           </HTTP>
         </DCPType>
@@ -91,10 +91,10 @@
         <DCPType>
           <HTTP>
             <Get>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Get>
             <Post>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Post>
           </HTTP>
         </DCPType>
@@ -104,7 +104,7 @@
         <DCPType>
           <HTTP>
             <Get>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Get>
           </HTTP>
         </DCPType>
@@ -116,7 +116,7 @@
         <DCPType>
           <HTTP>
             <Get>
-              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
             </Get>
           </HTTP>
         </DCPType>
@@ -4042,8 +4042,188 @@
       <SRS>EPSG:42305</SRS>
       <SRS>EPSG:42304</SRS>
       <SRS>EPSG:42303</SRS>
-      <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+      <LatLonBoundingBox minx="-180.0" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
       <Layer queryable="1">
+        <Name>og:bugsites</Name>
+        <Title/>
+        <Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract>
+        <KeywordList>
+          <Keyword>spearfish</Keyword>
+          <Keyword>sfBugsites</Keyword>
+          <Keyword>insects</Keyword>
+          <Keyword>bugsites</Keyword>
+          <Keyword>tiger_beetles</Keyword>
+        </KeywordList>
+        <SRS>EPSG:26713</SRS>
+        <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N", 
+  GEOGCS["NAD27", 
+    DATUM["North American Datum 1927", 
+      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
+      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
+      AUTHORITY["EPSG","6267"]], 
+    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+    UNIT["degree", 0.017453292519943295], 
+    AXIS["Geodetic longitude", EAST], 
+    AXIS["Geodetic latitude", NORTH], 
+    AUTHORITY["EPSG","4267"]], 
+  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
+  PARAMETER["central_meridian", -105.0], 
+  PARAMETER["latitude_of_origin", 0.0], 
+  PARAMETER["scale_factor", 0.9996], 
+  PARAMETER["false_easting", 500000.0], 
+  PARAMETER["false_northing", 0.0], 
+  UNIT["m", 1.0], 
+  AXIS["Easting", EAST], 
+  AXIS["Northing", NORTH], 
+  AUTHORITY["EPSG","26713"]]-->
+        <LatLonBoundingBox minx="-103.8678559295459" miny="44.3738305876282" maxx="-103.63763045271456" maxy="44.434080121216205"/>
+        <BoundingBox SRS="EPSG:26713" minx="590232.0" miny="4914096.0" maxx="608471.0" maxy="4920512.0"/>
+        <Style>
+          <Name>capitals</Name>
+          <Title>Capital cities</Title>
+          <Abstract/>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:bugsites"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
+        <Name>og:restricted</Name>
+        <Title/>
+        <Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract>
+        <KeywordList>
+          <Keyword>spearfish</Keyword>
+          <Keyword>restricted</Keyword>
+          <Keyword>areas</Keyword>
+          <Keyword>sfRestricted</Keyword>
+        </KeywordList>
+        <SRS>EPSG:26713</SRS>
+        <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N", 
+  GEOGCS["NAD27", 
+    DATUM["North American Datum 1927", 
+      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
+      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
+      AUTHORITY["EPSG","6267"]], 
+    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+    UNIT["degree", 0.017453292519943295], 
+    AXIS["Geodetic longitude", EAST], 
+    AXIS["Geodetic latitude", NORTH], 
+    AUTHORITY["EPSG","4267"]], 
+  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
+  PARAMETER["central_meridian", -105.0], 
+  PARAMETER["latitude_of_origin", 0.0], 
+  PARAMETER["scale_factor", 0.9996], 
+  PARAMETER["false_easting", 500000.0], 
+  PARAMETER["false_northing", 0.0], 
+  UNIT["m", 1.0], 
+  AXIS["Easting", EAST], 
+  AXIS["Northing", NORTH], 
+  AUTHORITY["EPSG","26713"]]-->
+        <LatLonBoundingBox minx="-104.35148148738409" miny="44.25607459294179" maxx="-103.0750295549078" maxy="44.621871797688264"/>
+        <BoundingBox SRS="EPSG:26713" minx="551796.8125" miny="4901896.0" maxx="652788.5625" maxy="4940954.0"/>
+        <Style>
+          <Name>restricted</Name>
+          <Title>Red, translucent style</Title>
+          <Abstract>A sample style that just prints out a transparent red interior with a red outline</Abstract>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:restricted"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
+        <Name>og:archsites</Name>
+        <Title/>
+        <Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract>
+        <KeywordList>
+          <Keyword>archsites</Keyword>
+          <Keyword>spearfish</Keyword>
+          <Keyword>sfArchsites</Keyword>
+          <Keyword>archeology</Keyword>
+        </KeywordList>
+        <SRS>EPSG:26713</SRS>
+        <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N", 
+  GEOGCS["NAD27", 
+    DATUM["North American Datum 1927", 
+      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
+      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
+      AUTHORITY["EPSG","6267"]], 
+    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+    UNIT["degree", 0.017453292519943295], 
+    AXIS["Geodetic longitude", EAST], 
+    AXIS["Geodetic latitude", NORTH], 
+    AUTHORITY["EPSG","4267"]], 
+  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
+  PARAMETER["central_meridian", -105.0], 
+  PARAMETER["latitude_of_origin", 0.0], 
+  PARAMETER["scale_factor", 0.9996], 
+  PARAMETER["false_easting", 500000.0], 
+  PARAMETER["false_northing", 0.0], 
+  UNIT["m", 1.0], 
+  AXIS["Easting", EAST], 
+  AXIS["Northing", NORTH], 
+  AUTHORITY["EPSG","26713"]]-->
+        <LatLonBoundingBox minx="-103.8724583832911" miny="44.37729507653392" maxx="-103.63783694485966" maxy="44.48793465340441"/>
+        <BoundingBox SRS="EPSG:26713" minx="589860.0" miny="4914479.0" maxx="608355.0" maxy="4926490.0"/>
+        <Style>
+          <Name>point</Name>
+          <Title>Default point</Title>
+          <Abstract>A sample style that just prints out a 6px wide red square</Abstract>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:archsites"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
+        <Name>og:streams</Name>
+        <Title/>
+        <Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract>
+        <KeywordList>
+          <Keyword>spearfish</Keyword>
+          <Keyword>sfStreams</Keyword>
+          <Keyword>streams</Keyword>
+        </KeywordList>
+        <SRS>EPSG:26713</SRS>
+        <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N", 
+  GEOGCS["NAD27", 
+    DATUM["North American Datum 1927", 
+      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
+      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
+      AUTHORITY["EPSG","6267"]], 
+    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+    UNIT["degree", 0.017453292519943295], 
+    AXIS["Geodetic longitude", EAST], 
+    AXIS["Geodetic latitude", NORTH], 
+    AUTHORITY["EPSG","4267"]], 
+  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
+  PARAMETER["central_meridian", -105.0], 
+  PARAMETER["latitude_of_origin", 0.0], 
+  PARAMETER["scale_factor", 0.9996], 
+  PARAMETER["false_easting", 500000.0], 
+  PARAMETER["false_northing", 0.0], 
+  UNIT["m", 1.0], 
+  AXIS["Easting", EAST], 
+  AXIS["Northing", NORTH], 
+  AUTHORITY["EPSG","26713"]]-->
+        <LatLonBoundingBox minx="-103.87778561486992" miny="44.37222288070277" maxx="-103.62277295981083" maxy="44.50211347714936"/>
+        <BoundingBox SRS="EPSG:26713" minx="589443.0" miny="4913935.0" maxx="609526.75" maxy="4928059.5"/>
+        <Style>
+          <Name>simple_streams</Name>
+          <Title>Default Styler for streams segments</Title>
+          <Abstract>Blue lines, 2px wide</Abstract>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:streams"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
         <Name>tiger:poly_landmarks</Name>
         <Title>Manhattan (NY) landmarks</Title>
         <Abstract>Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs</Abstract>
@@ -4072,7 +4252,7 @@
           <Abstract/>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poly_landmarks"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poly_landmarks"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4105,7 +4285,7 @@
           <Abstract>Manhattan points of interest</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poi"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poi"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4137,11 +4317,150 @@
           <Abstract/>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:tiger_roads"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:tiger_roads"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
+        <Name>za:za_natural</Name>
+        <Title>Natural Landmarks in South Africa</Title>
+        <Abstract>This layer describes natural features of South Africa such as forests and lakes.</Abstract>
+        <KeywordList>
+          <Keyword>forests</Keyword>
+          <Keyword>water</Keyword>
+          <Keyword>landmarks</Keyword>
+          <Keyword>Africa</Keyword>
+          <Keyword>South</Keyword>
+          <Keyword>natural</Keyword>
+        </KeywordList>
+        <SRS>EPSG:4326</SRS>
+        <!--WKT definition of this CRS:
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="16.935359954834" miny="-34.3737831115723" maxx="32.5472412109375" maxy="-22.7736549377441"/>
+        <BoundingBox SRS="EPSG:4326" minx="16.935359954834" miny="-34.3737831115723" maxx="32.5472412109375" maxy="-22.7736549377441"/>
+        <Style>
+          <Name>za_natural</Name>
+          <Title>Default Styler</Title>
+          <Abstract/>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_natural"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
+        <Name>za:za_points</Name>
+        <Title>Points of Interest in South Africa</Title>
+        <Abstract>Noteworthy locations such as hotels and tourist attractions in South Africa.</Abstract>
+        <KeywordList>
+          <Keyword>of</Keyword>
+          <Keyword>tourist</Keyword>
+          <Keyword>zoo</Keyword>
+          <Keyword>landmarks</Keyword>
+          <Keyword>cities</Keyword>
+          <Keyword>interest</Keyword>
+          <Keyword>attractions</Keyword>
+          <Keyword>museum</Keyword>
+          <Keyword>hotel</Keyword>
+          <Keyword>points</Keyword>
+          <Keyword>picnic</Keyword>
+        </KeywordList>
+        <SRS>EPSG:4326</SRS>
+        <!--WKT definition of this CRS:
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="16.4827766418457" miny="-46.9045600891113" maxx="37.9386329650879" maxy="-22.2347373962402"/>
+        <BoundingBox SRS="EPSG:4326" minx="16.4827766418457" miny="-46.9045600891113" maxx="37.9386329650879" maxy="-22.2347373962402"/>
+        <Style>
+          <Name>za_points</Name>
+          <Title>Default Styler</Title>
+          <Abstract/>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_points"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
+        <Name>za:za_roads</Name>
+        <Title>South African Roads</Title>
+        <Abstract>This layer describes roads in South Africa.</Abstract>
+        <KeywordList>
+          <Keyword>south</Keyword>
+          <Keyword>africa</Keyword>
+          <Keyword>roads</Keyword>
+        </KeywordList>
+        <SRS>EPSG:4326</SRS>
+        <!--WKT definition of this CRS:
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="16.4580821990967" miny="-34.8331336975098" maxx="32.8781242370605" maxy="-22.1271991729736"/>
+        <BoundingBox SRS="EPSG:4326" minx="16.4580821990967" miny="-34.8331336975098" maxx="32.8781242370605" maxy="-22.1271991729736"/>
+        <Style>
+          <Name>za_roads</Name>
+          <Title>Default Styler</Title>
+          <Abstract/>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_roads"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
+        <Name>za:za_vegetation</Name>
+        <Title>South African Vegetation</Title>
+        <Abstract>This layer describes vegetated areas in South Africa, categorized by biome.</Abstract>
+        <KeywordList>
+          <Keyword>south</Keyword>
+          <Keyword>africa</Keyword>
+          <Keyword>vegetation</Keyword>
+        </KeywordList>
+        <SRS>EPSG:4326</SRS>
+        <!--WKT definition of this CRS:
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="16.4691715240479" miny="-34.8336486816406" maxx="32.8940010070801" maxy="-22.123929977417"/>
+        <BoundingBox SRS="EPSG:4326" minx="16.4691715240479" miny="-34.8336486816406" maxx="32.8940010070801" maxy="-22.123929977417"/>
+        <Style>
+          <Name>za_vegetation</Name>
+          <Title>Default Styler</Title>
+          <Abstract/>
+          <LegendURL width="20" height="20">
+            <Format>image/png</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_vegetation"/>
+          </LegendURL>
+        </Style>
+      </Layer>
+      <Layer queryable="1">
         <Name>topp:tasmania_cities</Name>
         <Title>Tasmania cities</Title>
         <Abstract>Cities in Tasmania (actually, just the capital)</Abstract>
@@ -4168,7 +4487,7 @@
           <Abstract/>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_cities"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_cities"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4199,7 +4518,7 @@
           <Abstract>Light red line, 2px wide</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_roads"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_roads"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4231,7 +4550,7 @@
           <Abstract>Green fill with black outline</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_state_boundaries"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_state_boundaries"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4265,7 +4584,7 @@
           <Abstract>A blue fill, solid black outline style</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_water_bodies"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_water_bodies"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4291,7 +4610,7 @@
   AXIS["Geodetic longitude", EAST], 
   AXIS["Geodetic latitude", NORTH], 
   AUTHORITY["EPSG","4326"]]-->
-        <LatLonBoundingBox minx="-125.30903773" miny="7.705448770000002" maxx="-66.39223326999999" maxy="66.62225323"/>
+        <LatLonBoundingBox minx="-124.731422" miny="24.955967" maxx="-66.969849" maxy="49.371735"/>
         <BoundingBox SRS="EPSG:4326" minx="-124.73142200000001" miny="24.955967" maxx="-66.969849" maxy="49.371735"/>
         <Style>
           <Name>population</Name>
@@ -4300,17 +4619,17 @@
         categories of population, drawn in different colors</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:states"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:states"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
-        <Name>tiger:giant_polygon</Name>
-        <Title>World rectangle</Title>
-        <Abstract>A simple rectangular polygon covering most of the world, it's only used for the purpose of providing a background (WMS bgcolor could be used instead)</Abstract>
+        <Name>tike:waterways</Name>
+        <Title>Waterways</Title>
+        <Abstract>Waterways in Finland.</Abstract>
         <KeywordList>
-          <Keyword>DS_giant_polygon</Keyword>
-          <Keyword>giant_polygon</Keyword>
+          <Keyword>Finland</Keyword>
+          <Keyword>waterways</Keyword>
         </KeywordList>
         <SRS>EPSG:4326</SRS>
         <!--WKT definition of this CRS:
@@ -4323,148 +4642,81 @@
   AXIS["Geodetic longitude", EAST], 
   AXIS["Geodetic latitude", NORTH], 
   AUTHORITY["EPSG","4326"]]-->
-        <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
-        <BoundingBox SRS="EPSG:4326" minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+        <LatLonBoundingBox minx="19.649055480957" miny="59.9357719421387" maxx="31.5377140045166" maxy="69.9118957519531"/>
+        <BoundingBox SRS="EPSG:4326" minx="19.649055480957" miny="59.9357719421387" maxx="31.5377140045166" maxy="69.9118957519531"/>
         <Style>
-          <Name>giant_polygon</Name>
-          <Title>Border-less gray fill</Title>
-          <Abstract>Light gray polygon fill without a border</Abstract>
+          <Name>line</Name>
+          <Title>1 px blue line</Title>
+          <Abstract>Default line style, 1 pixel wide blue</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:giant_polygon"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:waterways"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
-        <Name>og:archsites</Name>
-        <Title>archsites_Type</Title>
-        <Abstract>Generated from sf_reset</Abstract>
+        <Name>tike:railways</Name>
+        <Title>roads_Type</Title>
+        <Abstract>Generated from tike</Abstract>
         <KeywordList>
-          <Keyword>archsites</Keyword>
-          <Keyword>sf_reset</Keyword>
+          <Keyword>tike</Keyword>
+          <Keyword>roads</Keyword>
         </KeywordList>
-        <SRS>EPSG:26713</SRS>
+        <SRS>EPSG:4326</SRS>
         <!--WKT definition of this CRS:
-PROJCS["NAD27 / UTM zone 13N", 
-  GEOGCS["NAD27", 
-    DATUM["North American Datum 1927", 
-      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
-      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
-      AUTHORITY["EPSG","6267"]], 
-    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
-    UNIT["degree", 0.017453292519943295], 
-    AXIS["Geodetic longitude", EAST], 
-    AXIS["Geodetic latitude", NORTH], 
-    AUTHORITY["EPSG","4267"]], 
-  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
-  PARAMETER["central_meridian", -105.0], 
-  PARAMETER["latitude_of_origin", 0.0], 
-  PARAMETER["scale_factor", 0.9996], 
-  PARAMETER["false_easting", 500000.0], 
-  PARAMETER["false_northing", 0.0], 
-  UNIT["m", 1.0], 
-  AXIS["Easting", EAST], 
-  AXIS["Northing", NORTH], 
-  AUTHORITY["EPSG","26713"]]-->
-        <LatLonBoundingBox minx="-103.8724583832911" miny="44.37729507653392" maxx="-103.63783694485966" maxy="44.48793465340441"/>
-        <BoundingBox SRS="EPSG:26713" minx="579284.662479525" miny="4907611.177401545" maxx="618930.337520475" maxy="4933357.822598455"/>
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
+        <BoundingBox SRS="EPSG:4326" minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
         <Style>
-          <Name>point</Name>
-          <Title>Default point</Title>
-          <Abstract>A sample style that just prints out a 6px wide red square</Abstract>
+          <Name>line</Name>
+          <Title>1 px blue line</Title>
+          <Abstract>Default line style, 1 pixel wide blue</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:archsites"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:railways"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
-        <Name>og:bugsites</Name>
-        <Title>bugsites_Type</Title>
-        <Abstract>Generated from sf_reset</Abstract>
+        <Name>tike:roads</Name>
+        <Title>roads_Type</Title>
+        <Abstract>Generated from tike</Abstract>
         <KeywordList>
-          <Keyword>bugsites</Keyword>
-          <Keyword>sf_reset</Keyword>
+          <Keyword>tike</Keyword>
+          <Keyword>roads</Keyword>
         </KeywordList>
-        <SRS>EPSG:26713</SRS>
+        <SRS>EPSG:4326</SRS>
         <!--WKT definition of this CRS:
-PROJCS["NAD27 / UTM zone 13N", 
-  GEOGCS["NAD27", 
-    DATUM["North American Datum 1927", 
-      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
-      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
-      AUTHORITY["EPSG","6267"]], 
-    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
-    UNIT["degree", 0.017453292519943295], 
-    AXIS["Geodetic longitude", EAST], 
-    AXIS["Geodetic latitude", NORTH], 
-    AUTHORITY["EPSG","4267"]], 
-  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
-  PARAMETER["central_meridian", -105.0], 
-  PARAMETER["latitude_of_origin", 0.0], 
-  PARAMETER["scale_factor", 0.9996], 
-  PARAMETER["false_easting", 500000.0], 
-  PARAMETER["false_northing", 0.0], 
-  UNIT["m", 1.0], 
-  AXIS["Easting", EAST], 
-  AXIS["Northing", NORTH], 
-  AUTHORITY["EPSG","26713"]]-->
-        <LatLonBoundingBox minx="-103.8678559295459" miny="44.3738305876282" maxx="-103.63763045271456" maxy="44.434080121216205"/>
-        <BoundingBox SRS="EPSG:26713" minx="579803.0418472051" miny="4910427.367097522" maxx="618899.9581527949" maxy="4924180.632902478"/>
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
+        <BoundingBox SRS="EPSG:4326" minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
         <Style>
-          <Name>point</Name>
-          <Title>Default point</Title>
-          <Abstract>A sample style that just prints out a 6px wide red square</Abstract>
+          <Name>line</Name>
+          <Title>1 px blue line</Title>
+          <Abstract>Default line style, 1 pixel wide blue</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:bugsites"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:roads"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
-        <Name>og:restricted</Name>
-        <Title>restricted_Type</Title>
-        <Abstract>Generated from sf_reset</Abstract>
-        <KeywordList>
-          <Keyword>restricted</Keyword>
-          <Keyword>sf_reset</Keyword>
-        </KeywordList>
-        <SRS>EPSG:26713</SRS>
-        <!--WKT definition of this CRS:
-PROJCS["NAD27 / UTM zone 13N", 
-  GEOGCS["NAD27", 
-    DATUM["North American Datum 1927", 
-      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
-      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
-      AUTHORITY["EPSG","6267"]], 
-    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
-    UNIT["degree", 0.017453292519943295], 
-    AXIS["Geodetic longitude", EAST], 
-    AXIS["Geodetic latitude", NORTH], 
-    AUTHORITY["EPSG","4267"]], 
-  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
-  PARAMETER["central_meridian", -105.0], 
-  PARAMETER["latitude_of_origin", 0.0], 
-  PARAMETER["scale_factor", 0.9996], 
-  PARAMETER["false_easting", 500000.0], 
-  PARAMETER["false_northing", 0.0], 
-  UNIT["m", 1.0], 
-  AXIS["Easting", EAST], 
-  AXIS["Northing", NORTH], 
-  AUTHORITY["EPSG","26713"]]-->
-        <LatLonBoundingBox minx="-103.85046714477757" miny="44.39425322606261" maxx="-103.74730938570416" maxy="44.482051617995175"/>
-        <BoundingBox SRS="EPSG:26713" minx="586973.4138631009" miny="4910714.68911342" maxx="604271.7736368991" maxy="4931370.31088658"/>
-        <Style>
-          <Name>polygon</Name>
-          <Title>Default polygon style</Title>
-          <Abstract>A sample style that just draws out a solid gray interior with a black 1px outline</Abstract>
-          <LegendURL width="20" height="20">
-            <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:restricted"/>
-          </LegendURL>
-        </Style>
-      </Layer>
-      <Layer queryable="1">
         <Name>og:roads</Name>
         <Title>roads_Type</Title>
         <Abstract>Generated from sf_reset</Abstract>
@@ -4495,58 +4747,46 @@
   AXIS["Easting", EAST], 
   AXIS["Northing", NORTH], 
   AUTHORITY["EPSG","26713"]]-->
-        <LatLonBoundingBox minx="-103.87787616044085" miny="44.37286182959436" maxx="-103.62276582457976" maxy="44.50215049516538"/>
-        <BoundingBox SRS="EPSG:26713" minx="577946.0691546879" miny="4905968.000151713" maxx="621015.9933453121" maxy="4936101.499848287"/>
+        <LatLonBoundingBox minx="-103.87787616044085" miny="44.37286182959436" maxx="-103.62269938718721" maxy="44.50497230654195"/>
+        <BoundingBox SRS="EPSG:26713" minx="589434.8125" miny="4914006.0" maxx="609527.25" maxy="4928377.0"/>
         <Style>
-          <Name>line</Name>
-          <Title>1 px blue line</Title>
-          <Abstract>Default line style, 1 pixel wide blue</Abstract>
+          <Name>simple_roads</Name>
+          <Title>Default Styler for simple road segments</Title>
+          <Abstract>Light red line, 2px wide</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:roads"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:roads"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
-        <Name>og:streams</Name>
-        <Title>streams_Type</Title>
-        <Abstract>Generated from sf_reset</Abstract>
+        <Name>tike:points</Name>
+        <Title>roads_Type</Title>
+        <Abstract>Generated from tike</Abstract>
         <KeywordList>
-          <Keyword>streams</Keyword>
-          <Keyword>sf_reset</Keyword>
+          <Keyword>tike</Keyword>
+          <Keyword>roads</Keyword>
         </KeywordList>
-        <SRS>EPSG:26713</SRS>
+        <SRS>EPSG:4326</SRS>
         <!--WKT definition of this CRS:
-PROJCS["NAD27 / UTM zone 13N", 
-  GEOGCS["NAD27", 
-    DATUM["North American Datum 1927", 
-      SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], 
-      TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], 
-      AUTHORITY["EPSG","6267"]], 
-    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
-    UNIT["degree", 0.017453292519943295], 
-    AXIS["Geodetic longitude", EAST], 
-    AXIS["Geodetic latitude", NORTH], 
-    AUTHORITY["EPSG","4267"]], 
-  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
-  PARAMETER["central_meridian", -105.0], 
-  PARAMETER["latitude_of_origin", 0.0], 
-  PARAMETER["scale_factor", 0.9996], 
-  PARAMETER["false_easting", 500000.0], 
-  PARAMETER["false_northing", 0.0], 
-  UNIT["m", 1.0], 
-  AXIS["Easting", EAST], 
-  AXIS["Northing", NORTH], 
-  AUTHORITY["EPSG","26713"]]-->
-        <LatLonBoundingBox minx="-103.87778561486992" miny="44.37222288070277" maxx="-103.62277295981083" maxy="44.50211347714936"/>
-        <BoundingBox SRS="EPSG:26713" minx="577959.2241185814" miny="4905858.689926578" maxx="621010.5258814186" maxy="4936135.810073422"/>
+GEOGCS["WGS 84", 
+  DATUM["World Geodetic System 1984", 
+    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
+    AUTHORITY["EPSG","6326"]], 
+  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
+  UNIT["degree", 0.017453292519943295], 
+  AXIS["Geodetic longitude", EAST], 
+  AXIS["Geodetic latitude", NORTH], 
+  AUTHORITY["EPSG","4326"]]-->
+        <LatLonBoundingBox minx="19.8481521606445" miny="59.8213005065918" maxx="31.2861518859863" maxy="70.0596923828125"/>
+        <BoundingBox SRS="EPSG:4326" minx="19.8481521606445" miny="59.8213005065918" maxx="31.2861518859863" maxy="70.0596923828125"/>
         <Style>
           <Name>line</Name>
           <Title>1 px blue line</Title>
           <Abstract>Default line style, 1 pixel wide blue</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:streams"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:points"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4578,7 +4818,7 @@
           <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Arc_Sample"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Arc_Sample"/>
           </LegendURL>
         </Style>
       </Layer>
@@ -4610,93 +4850,21 @@
           <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Img_Sample"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Img_Sample"/>
           </LegendURL>
         </Style>
       </Layer>
       <Layer queryable="1">
-        <Name>nurc:mosaic</Name>
-        <Title>Sample PNG mosaic</Title>
-        <Abstract>Subsampled satellite imagery loaded as a mosaic of PNG images</Abstract>
-        <KeywordList>
-          <Keyword>WCS</Keyword>
-          <Keyword>mosaic</Keyword>
-          <Keyword>mosaic</Keyword>
-        </KeywordList>
-        <!--WKT definition of this CRS:
-GEOGCS["WGS 84", 
-  DATUM["World Geodetic System 1984", 
-    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
-    AUTHORITY["EPSG","6326"]], 
-  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
-  UNIT["degree", 0.017453292519943295], 
-  AXIS["Geodetic longitude", EAST], 
-  AXIS["Geodetic latitude", NORTH], 
-  AUTHORITY["EPSG","4326"]]-->
-        <SRS>EPSG:4326</SRS>
-        <LatLonBoundingBox minx="6.34617490847439" miny="36.4917718219401" maxx="20.8296831527815" maxy="46.5907669751351"/>
-        <BoundingBox SRS="EPSG:4326" minx="6.34617490847439" miny="36.4917718219401" maxx="20.8296831527815" maxy="46.5907669751351"/>
-        <Style>
-          <Name>raster</Name>
-          <Title>Raster</Title>
-          <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
-          <LegendURL width="20" height="20">
-            <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:mosaic"/>
-          </LegendURL>
-        </Style>
-      </Layer>
-      <Layer queryable="1">
-        <Name>nurc:Pk50095</Name>
-        <Title>Sample scanned and georerenced map</Title>
-        <Abstract>This is a sample for the world image format (wld + prj + tiff)</Abstract>
-        <KeywordList>
-          <Keyword>WCS</Keyword>
-          <Keyword>img_sample2</Keyword>
-          <Keyword>Pk50095</Keyword>
-        </KeywordList>
-        <!--WKT definition of this CRS:
-PROJCS["WGS 84 / UTM zone 33N", 
-  GEOGCS["WGS 84", 
-    DATUM["World Geodetic System 1984", 
-      SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
-      AUTHORITY["EPSG","6326"]], 
-    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
-    UNIT["degree", 0.017453292519943295], 
-    AXIS["Geodetic longitude", EAST], 
-    AXIS["Geodetic latitude", NORTH], 
-    AUTHORITY["EPSG","4326"]], 
-  PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], 
-  PARAMETER["central_meridian", 15.0], 
-  PARAMETER["latitude_of_origin", 0.0], 
-  PARAMETER["scale_factor", 0.9996], 
-  PARAMETER["false_easting", 500000.0], 
-  PARAMETER["false_northing", 0.0], 
-  UNIT["m", 1.0], 
-  AXIS["Easting", EAST], 
-  AXIS["Northing", NORTH], 
-  AUTHORITY["EPSG","32633"]]-->
-        <SRS>EPSG:32633</SRS>
-        <LatLonBoundingBox minx="12.999446822650462" miny="46.722110379286" maxx="13.308182612644663" maxy="46.91359611878293"/>
-        <BoundingBox SRS="EPSG:32633" minx="347649.93086859107" miny="5176214.082539256" maxx="370725.976428591" maxy="5196961.352859256"/>
-        <Style>
-          <Name>raster</Name>
-          <Title>Raster</Title>
-          <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
-          <LegendURL width="20" height="20">
-            <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Pk50095"/>
-          </LegendURL>
-        </Style>
-      </Layer>
-      <Layer queryable="1">
         <Name>sf:sfdem</Name>
-        <Title>sfdem is a Tagged Image File Format with Geographic information</Title>
-        <Abstract>Generated from sfdem</Abstract>
+        <Title>Spearfish DEM</Title>
+        <Abstract>Digital Elevation Model data for Spearfish, South Dakota</Abstract>
         <KeywordList>
           <Keyword>WCS</Keyword>
-          <Keyword>sfdem</Keyword>
-          <Keyword>sfdem</Keyword>
+          <Keyword>sf</Keyword>
+          <Keyword>dem</Keyword>
+          <Keyword>digital</Keyword>
+          <Keyword>elevation</Keyword>
+          <Keyword>model</Keyword>
         </KeywordList>
         <!--WKT definition of this CRS:
 PROJCS["NAD27 / UTM zone 13N", 
@@ -4729,7 +4897,7 @@
           <Abstract>Classic elevation color progression</Abstract>
           <LegendURL width="20" height="20">
             <Format>image/png</Format>
-            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:sfdem"/>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:sfdem"/>
           </LegendURL>
         </Style>
       </Layer>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,8 +4,8 @@
 
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
         
         <script type="text/javascript" src="feature-grid.js"></script>
@@ -18,8 +18,9 @@
         GeoJSON document (data/summits.json).</p>
         
         <p>Because the layer and the store are bound to each other, the
-        features loaded into the store are automatically added to the
-        layer.</p>
+        features loaded into the store are automatically added to the layer. A 
+        GeoExt feature selection model is also used so that selecting rows in 
+        the grid selects features in the layer, and vice-versa.</p> 
 
         <p>See <a href=feature-grid.js>feature-grid.js</a>.</p>
 

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/feature-grid.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,5 +1,17 @@
-Ext.BLANK_IMAGE_URL = "../../../../ext/resources/images/default/s.gif"
+/**
+ * Copyright (c) 2008-2009 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.
+ */
 
+/** api: example[feature-grid]
+ *  Grid with Features
+ *  ------------------
+ *  Synchronize selection of features between a grid and a layer.
+ */
+
 var mapPanel, store, gridPanel, mainPanel;
 
 Ext.onReady(function() {
@@ -56,7 +68,8 @@
             header: "Elevation",
             width: 100,
             dataIndex: "elevation"
-        }]
+        }],
+        sm: new GeoExt.grid.FeatureSelectionModel() 
     });
 
     // create a panel and add the map panel and grid panel

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,8 +4,8 @@
 
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
 
         <script type="text/javascript" src="layercontainer.js"></script>
@@ -16,7 +16,9 @@
         <p>This is example that shows how create a TreePanel with layers
         from a map.  GeoExt does not provide a LayerTree component.  Instead,
         to create a tree with nodes that represent layers, create a tree with
-        a LayerContainer at the root, or add LayerNodes directly.</p>
+        a LayerContainer at the root, or add LayerNodes directly. See also the
+        <a href="tree.html">tree</a> example for a more complex tree
+        configuration</p>
 
         <p>Note that the js is not minified so it is readable.
         See <a href="layercontainer.js">layercontainer.js</a>.</p>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/layercontainer.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,3 +1,17 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[layercontainer]
+ *  Layer Tree
+ *  ----------
+ *  Create a layer tree with a LayerContainer.
+ */
+
 var store, tree, panel;
 Ext.onReady(function() {
     

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.html (from rev 1504, core/trunk/geoext/examples/layeropacityslider.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,44 @@
+<html>
+    <head>
+        <title>GeoExt LayerOpacitySlider</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+        <style type="text/css">
+            .x-tree-node-leaf .gx-tree-layer-icon {
+                width: 0px;
+            }
+        </style>
+
+        <script type="text/javascript" src="layeropacityslider.js"></script>
+    </head>
+    <body>
+        <h1>GeoExt.LayerOpacitySlider</h1>
+
+        <p>The LayerOpacitySlider allows control of a layer opacity using an
+        Ext.Slider.  It is also possible to add a special tooltip plugin,
+        LayerOpacitySliderTip, which will show the opacity value while dragging
+        the slider (the content is configurable).<p>
+
+        <p>In this example, the slider in below the map is in aggressive mode: the
+        opacity is changed as soon as the slider is moved. The slider inside
+        Map 1 is not aggressive: the opacity is changed when the slider is
+        released.</p>
+
+        <p>In Map 2 we have a fading effect between two layers. The slider is configured
+        with changeVisibility:true and a complementaryLayer. This avoids downloading images
+        when layer opacity is 0 or when complementaryLayer is fully covered by layer.
+        The effect on layer visibility is shown in the tree (checkboxes).</p>
+
+        <p>The js is not minified so it is readable. See <a
+        href="layeropacityslider.js">layeropacityslider.js</a>.</p>
+
+            <div id="map1-container" style="float:left"></div>
+            <div id="map2-container" style="float:right"></div>
+            <div id="tree" style="float:right"></div>
+            <div id="slider" style="clear:both"></div>
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.js (from rev 1504, core/trunk/geoext/examples/layeropacityslider.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/layeropacityslider.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[layeropacityslider]
+ *  Layer Opacity Slider
+ *  --------------------
+ *  Use a slider to control layer opacity.
+ */
+
+var panel1, panel2, wms, slider;
+
+Ext.onReady(function() {
+    
+    wms = new OpenLayers.Layer.WMS(
+        "Global Imagery",
+        "http://maps.opengeo.org/geowebcache/service/wms",
+        {layers: "bluemarble"}
+    );
+
+    // create a map panel with an embedded slider
+    panel1 = new GeoExt.MapPanel({
+        title: "Map 1",
+        renderTo: "map1-container",
+        height: 300,
+        width: 400,
+        map: {
+            controls: [new OpenLayers.Control.Navigation()]
+        },
+        layers: [wms],
+        extent: [-5, 35, 15, 55],
+        items: [{
+            xtype: "gx_opacityslider",
+            layer: wms,
+            vertical: true,
+            height: 120,
+            x: 10,
+            y: 10,
+            plugins: new GeoExt.LayerOpacitySliderTip()
+        }]
+    });
+    // create a separate slider bound to the map but displayed elsewhere
+    slider = new GeoExt.LayerOpacitySlider({
+        layer: wms,
+        aggressive: true, 
+        width: 200,
+        isFormField: true,
+        fieldLabel: "opacity",
+        renderTo: "slider"
+    });
+        
+    var clone = wms.clone();
+    var wms2 = new OpenLayers.Layer.WMS(
+        "OpenLayers WMS",
+        "http://labs.metacarta.com/wms/vmap0",
+        {layers: 'basic'}
+    );
+    panel2 = new GeoExt.MapPanel({
+        title: "Map 2",
+        renderTo: "map2-container",
+        height: 300,
+        width: 400,
+        map: {
+            controls: [new OpenLayers.Control.Navigation()]
+        },
+        layers: [wms2, clone],
+        extent: [-5, 35, 15, 55],
+        items: [{
+            xtype: "gx_opacityslider",
+            layer: clone,
+            complementaryLayer: wms2,
+            changeVisibility: true,
+            aggressive: true,
+            vertical: true,
+            height: 120,
+            x: 10,
+            y: 10,
+            plugins: new GeoExt.LayerOpacitySliderTip()
+        }]
+    });
+    
+    var tree = new Ext.tree.TreePanel({
+        width: 145,
+        height: 300,
+        renderTo: "tree",
+        root: new GeoExt.tree.LayerContainer({
+            layerStore: panel2.layers,
+            expanded: true
+        })
+    });
+
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,10 +1,10 @@
 <html>
     <head>
-        <script type="text/javascript" src="http://dev.geoext.org/trunk/ext/adapter/ext/ext-base.js"></script>
-        <script type="text/javascript" src="http://dev.geoext.org/trunk/ext/ext-all.js"></script>
+        <script type="text/javascript" src="http://extjs.cachefly.net/ext-2.2.1/adapter/ext/ext-base.js"></script>
+        <script type="text/javascript" src="http://extjs.cachefly.net/ext-2.2.1/ext-all.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
 
         <script type="text/javascript" src="legendpanel.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/legendpanel.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,17 +1,31 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
 
-var mapPanel;
+/** api: example[legendpanel]
+ *  Legend Panel
+ *  ------------
+ *  Display a layer legend in a panel.
+ */
 
+
+var mapPanel, legendPanel;
+
 Ext.onReady(function() {
     var map = new OpenLayers.Map({allOverlays: true});
     map.addLayers([
         new OpenLayers.Layer.WMS(
             "Tasmania",
-            "http://publicus.opengeo.org/geoserver/wms?",
+            "http://demo.opengeo.org/geoserver/wms?",
             {layers: 'topp:tasmania_state_boundaries', format: 'image/png', transparent: true},
             {singleTile: true}),
         new OpenLayers.Layer.WMS(
             "Cities and Roads",
-            "http://publicus.opengeo.org/geoserver/wms?",
+            "http://demo.opengeo.org/geoserver/wms?",
             {layers: 'topp:tasmania_cities,topp:tasmania_roads', format: 'image/png', transparent: true},
             {singleTile: true}),
         new OpenLayers.Layer.Vector('Polygons', {styleMap: new OpenLayers.StyleMap({
@@ -26,7 +40,7 @@
 
     var addLayer = function() {
         var wmslayer = new OpenLayers.Layer.WMS("Bodies of Water",
-            "http://publicus.opengeo.org/geoserver/wms?",
+            "http://demo.opengeo.org/geoserver/wms?",
             {layers: 'topp:tasmania_water_bodies', format: 'image/png', transparent: true},
             {singleTile: true});
         mapPanel.map.addLayer(wmslayer);
@@ -61,7 +75,7 @@
         zoom: 7
     });
 
-    var legendPanel = new GeoExt.LegendPanel({
+    legendPanel = new GeoExt.LegendPanel({
         labelCls: 'mylabel',
         bodyStyle: 'padding:5px',
         width: 350,

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,8 +4,8 @@
 
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
 
         <script type="text/javascript" src="mappanel-div.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-div.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,12 +1,25 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
 
+/** api: example[mappanel-div]
+ *  Map Panel
+ *  ---------
+ *  Render a map panel in any block level page element.
+ */
+
 var mapPanel;
 
 Ext.onReady(function() {
     var map = new OpenLayers.Map();
     var layer = new OpenLayers.Layer.WMS(
-        "bluemarble",
-        "http://sigma.openplans.org/geoserver/wms?",
-        {layers: 'bluemarble'}
+        "Global Imagery",
+        "http://maps.opengeo.org/geowebcache/service/wms",
+        {layers: "bluemarble"}
     );
     map.addLayer(layer);
 

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.html (from rev 1504, core/trunk/geoext/examples/mappanel-viewport.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,34 @@
+<html>
+    <head>
+        <title>GeoExt MapPanel in an Ext Viewport</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <!-- Google Maps API for "localhost" -->
+        <!--
+        <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+        -->
+        <!-- Google Maps API for "dev.geoext.org" -->
+        <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA_5ak-hsiH4j5bQQn7-k66xTWxvN8zH6Ta_pgIhhU0TB7bG8iAhS99ituPif4lG-2CHXoZ3qenLnK1g'></script>
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+        
+        <script type="text/javascript" src="mappanel-viewport.js"></script>
+
+    </head>
+    <body>
+        <div id="title">
+            <h1>GeoExt.MapPanel in an Ext.Viewport</h1>
+        </div>
+
+        <div id="description">
+            <p>This example shows how to place a MapPanel as a region in a
+            container using a border layout, the container is a Viewport in
+            this example.</p>
+            
+            <p>The js is not minified so it is readable. See <a
+            href="mappanel-viewport.js">mappanel-viewport.js</a>.</p>
+        </div>
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.js (from rev 1504, core/trunk/geoext/examples/mappanel-viewport.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-viewport.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[mappanel-viewport]
+ *  Map Panel (in a Viewport)
+ *  -------------------------
+ *  Render a map panel in a viewport.
+ */
+
+var mapPanel;
+
+Ext.onReady(function() {
+
+    // if true a google layer is used, if false
+    // the bluemarble WMS layer is used
+    var google = false;
+
+    var options, layer;
+    var extent = new OpenLayers.Bounds(-5, 35, 15, 55);
+
+    if (google) {
+
+        options = {
+            projection: new OpenLayers.Projection("EPSG:900913"),
+            units: "m",
+            numZoomLevels: 18,
+            maxResolution: 156543.0339,
+            maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+                                             20037508, 20037508.34)
+        };
+
+        layer = new OpenLayers.Layer.Google(
+            "Google Satellite",
+            {type: G_SATELLITE_MAP, sphericalMercator: true}
+        );
+
+        extent.transform(
+            new OpenLayers.Projection("EPSG:4326"), options.projection
+        );
+
+    } else {
+        layer = new OpenLayers.Layer.WMS(
+            "Global Imagery",
+            "http://maps.opengeo.org/geowebcache/service/wms",
+            {layers: "bluemarble"},
+            {isBaseLayer: true}
+        );
+    }
+
+    var map = new OpenLayers.Map(options);
+
+    new Ext.Viewport({
+        layout: "border",
+        items: [{
+            region: "north",
+            contentEl: "title",
+            height: 50
+        }, {
+            region: "center",
+            id: "mappanel",
+            title: "Map",
+            xtype: "gx_mappanel",
+            map: map,
+            layers: [layer],
+            extent: extent,
+            split: true
+        }, {
+            region: "east",
+            title: "Description",
+            contentEl: "description",
+            width: 200,
+            split: true
+        }]
+    });
+
+    mapPanel = Ext.getCmp("mappanel");
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,8 +4,8 @@
 
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
         
         <script type="text/javascript" src="mappanel-window.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/mappanel-window.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,3 +1,17 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[mappanel-window]
+ *  Map Panel (in a Window)
+ *  -------------------------
+ *  Render a map panel in a Window.
+ */
+
 var mapPanel;
 
 Ext.onReady(function() {
@@ -10,9 +24,9 @@
             xtype: "gx_mappanel",
             id: "mappanel",
             layers: [new OpenLayers.Layer.WMS(
-                "bluemarble",
-                "http://sigma.openplans.org/geoserver/wms?",
-                {layers: 'bluemarble'}
+                "Global Imagery",
+                "http://maps.opengeo.org/geowebcache/service/wms",
+                {layers: "bluemarble"}
             )],
             extent: "-5,35,15,55"
         }]

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/popup.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/popup.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/popup.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,9 +4,9 @@
 
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
         <link rel="stylesheet" type="text/css" href="../resources/css/geoext-all-debug.css" />
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
         
         <script type="text/javascript" src="popup.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/popup.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/popup.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/popup.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,6 +1,17 @@
-// make the references to the map panel and the popup 
-// global, this is useful for looking at their states
-// from the console
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[popup]
+ *  Feature Popup
+ *  -------------
+ *  Display a popup with feature information.
+ */
+
 var mapPanel, popup;
 
 Ext.onReady(function() {
@@ -36,7 +47,7 @@
                 }
             }
         });
-        mapPanel.add(popup);
+        popup.show();
     }
 
     // create popup on "featureselected"

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/renderer.html (from rev 1504, core/trunk/geoext/examples/renderer.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/renderer.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/renderer.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,97 @@
+<html>
+<head>
+    <title>GeoExt Feature Renderer</title>
+    <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+    <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+    <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+    <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
+    <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+    <script src="renderer.js"></script>
+    <style type="text/css">
+        .x-window-body {
+            background-color: white;
+        }
+        #swatches {
+            padding: 1em;
+        }
+        #swatches td {
+            border: 1px solid #ccc;
+            text-align: center;
+            margin: 0 auto;
+            padding: 0.5em;
+        }
+        #swatches td div div {
+            text-align: left; /* IE centers VML in root otherwise */
+        }
+        #wkt {
+            width: 400px;
+            height: 100px;
+        }
+        #symbolizers {
+            width: 400px;
+            height: 150px;
+        }
+    </style>
+</head>
+<body>
+    <div id="panel"></div>
+    <h1>GeoExt.FeatureRenderer</h1>
+    <p>The FeatureRenderer renderers arbitrary OpenLayers.Feature.Vector objects
+    given a list of symbolizers.  The FeatureRenderer component can be used 
+    anywhere a box component is used.</p>
+
+    <p>The js is not minified so it is readable. See <a href="renderer.js">renderer.js</a>.</p>
+
+    <h2>Symbol Types</h2>
+    <p>If the feature renderer is not given a feature, it can be configured to
+    render a default feature based on geometry type alone.</p>
+    <table id="swatches">
+        <tbody>
+            <tr><td>&nbsp;</td><td>point</td><td>line</td><td>polygon</td></tr>
+            <tr>
+                <td>default</td>
+                <td id="point_default"></td>
+                <td id="line_default"></td>
+                <td id="poly_default"></td>
+            </tr><tr>
+                <td>blue</td>
+                <td id="point_blue"></td>
+                <td id="line_blue"></td>
+                <td id="poly_blue"></td>
+            </tr><tr>
+                <td>custom</td>
+                <td id="point_custom"></td>
+                <td id="line_custom"></td>
+                <td id="poly_custom"></td>
+            </tr>
+            </tr><tr>
+                <td>stacked</td>
+                <td id="point_stacked"></td>
+                <td id="line_stacked"></td>
+                <td id="poly_stacked"></td>
+            </tr>
+        </tbody>
+    </table>
+    
+    <h2>Custom Feature Rendering</h2>
+    <p>You can render any OpenLayers.Feature.Vector object with a FeatureRenderer.
+    Use the inputs below to modify the well-known text and symbolizer.  These
+    will be used to render a feature in a new window.</p><br>
+    <label for="wkt">Geometry Well-Known Text</label><br>
+    <textarea id="wkt">
+POLYGON((1 30, -33 10, -39 -21, 1 -41, 23 -22, 27 15, 1 30), (0 10, -14 0, -4 -24, 12 -8, 0 10))
+    </textarea><br><br>
+    <label for="symbolizers">Feature Symbolizers</label><br>
+    <textarea id="symbolizers">
+[{
+    fillColor: "yellow",
+    fillOpacity: 1,
+    strokeColor: "blue",
+    strokeWidth: 1,
+    pointRadius: 5
+}]
+    </textarea><br>
+    <button id="render">render</button>
+</body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/renderer.js (from rev 1504, core/trunk/geoext/examples/renderer.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/renderer.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/renderer.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[renderer]
+ *  Feature Renderer
+ *  ----------------
+ *  Render a vector feature with multiple symbolizers in a box component.
+ */
+
+var blue = {
+    fillColor: "blue",
+    fillOpacity: 0.25,
+    strokeColor: "blue",
+    strokeWidth: 2,
+    pointRadius: 5
+};
+
+var custom = {
+    point: {
+        graphicName: "star",
+        pointRadius: 8,
+        fillColor: "yellow",
+        strokeColor: "red",
+        strokeWidth: 1
+    },
+    line: {
+        strokeColor: "#669900",
+        strokeWidth: 3
+    },
+    poly: {
+        fillColor: "olive",
+        fillOpacity: 0.25,
+        strokeColor: "#666666",
+        strokeWidth: 2,
+        strokeDashstyle: "dot"
+    }
+};
+
+var stacked = {
+    point: [{
+        pointRadius: 8,
+        fillColor: "white",
+        strokeColor: "red",
+        strokeWidth: 2
+    }, {
+        graphicName: "star",
+        pointRadius: 5,
+        fillColor: "red"
+    }],
+    line: [{
+        strokeColor: "red",
+        strokeWidth: 5
+    }, {
+        strokeColor: "#ff9933",
+        strokeWidth: 2
+    }],
+    poly: [{
+        strokeWidth: 3,
+        fillColor: "white",
+        strokeColor: "#669900"
+    }, {
+        strokeWidth: 2,
+        fillOpacity: 0,
+        strokeColor: "red",
+        strokeDashstyle: "dot"
+    }]
+};
+
+var configs = [{
+    symbolType: "Point",
+    renderTo: "point_default"
+}, {
+    symbolType: "Line",
+    renderTo: "line_default"
+}, {
+    symbolType: "Polygon",
+    renderTo: "poly_default"
+}, {
+    symbolType: "Point",
+    symbolizers: [blue],
+    renderTo: "point_blue"
+}, {
+    symbolType: "Line",
+    symbolizers: [blue],
+    renderTo: "line_blue"
+}, {
+    symbolType: "Polygon",
+    symbolizers: [blue],
+    renderTo: "poly_blue"
+}, {
+    symbolType: "Point",
+    symbolizers: [custom.point],
+    renderTo: "point_custom"
+}, {
+    symbolType: "Line",
+    symbolizers: [custom.line],
+    renderTo: "line_custom"
+}, {
+    symbolType: "Polygon",
+    symbolizers: [custom.poly],
+    renderTo: "poly_custom"
+}, {
+    symbolType: "Point",
+    symbolizers: stacked.point,
+    renderTo: "point_stacked"
+}, {
+    symbolType: "Line",
+    symbolizers: stacked.line,
+    renderTo: "line_stacked"
+}, {
+    symbolType: "Polygon",
+    symbolizers: stacked.poly,
+    renderTo: "poly_stacked"
+}];
+
+Ext.onReady(function() {        
+    for(var i=0; i<configs.length; ++i) {
+        new GeoExt.FeatureRenderer(configs[i]);
+    }
+    $("render").onclick = render;
+});
+
+var format = new OpenLayers.Format.WKT();
+var renderer, win;
+function render() {
+    var wkt = $("wkt").value;
+    var feature;
+    try {
+        feature = format.read(wkt)
+    } catch(err) {
+        $("wkt").value = "Bad WKT: " + err;
+    }
+    var symbolizers;
+    try {
+        var value = $("symbolizers").value;
+        symbolizers = eval("(" + value + ")");
+        if (!symbolizers || symbolizers.constructor !== Array) {
+            throw "Must be an array literal";
+        }
+    } catch(err) {
+        $("symbolizers").value = "Bad symbolizers: " + err + "\n\n" + value;
+        symbolizers = null;
+    }
+    if(feature && symbolizers) {
+        if(!win) {
+            renderer = new GeoExt.FeatureRenderer({
+                feature: feature,
+                symbolizers: symbolizers,
+                width: 150,
+                style: {margin: 4}
+            });
+            win = new Ext.Window({
+                closeAction: "hide",
+                layout: "fit",
+                width: 175,
+                items: [renderer]
+            });
+        } else {
+            renderer.update({
+                feature: feature,
+                symbolizers: symbolizers
+            });
+        }
+        win.show();
+    }
+}
+

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/search-form.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/search-form.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/search-form.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -2,8 +2,8 @@
     <head>
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
 
         <script type="text/javascript" src="search-form.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/search-form.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/search-form.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/search-form.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,4 +1,17 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
 
+/** api: example[search-form]
+ *  Filter Form Panel
+ *  -----------------
+ *  Use a form to build an OpenLayers filter.
+ */
+
 var formPanel;
 
 Ext.onReady(function() {

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.html (from rev 1504, core/trunk/geoext/examples/toolbar.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,42 @@
+<html>
+    <head>
+        <title>GeoExt Toolbar Example</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+        <script type="text/javascript" src="toolbar.js"></script>
+
+        <style type="text/css">
+            /* work around an Ext bug that makes the rendering
+               of menu items not as one would expect */
+            .ext-ie .x-menu-item-icon {
+                left: -24px;
+            }
+            .ext-strict .x-menu-item-icon {
+                left: 3px;
+            }
+            .ext-ie6 .x-menu-item-icon {
+                left: -24px;
+            }
+            .ext-ie7 .x-menu-item-icon {
+                left: -24px;
+            }
+        </style>
+    </head>
+    <body>
+        <h1>OpenLayers controls in an Ext toolbar</h1>
+
+        <p>This example shows how to add OpenLayers controls in an Ext toolbar.
+        GeoExt provides the GeoExt.Action class for adapating a control to an
+        object that can be inserted in a toolbar or in a menu.</p>
+
+        <p>The js is not minified so it is readable. See <a
+        href="toolbar.js">toolbar.js</a>.</p>
+
+        <div id="mappanel"></div>
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.js (from rev 1504, core/trunk/geoext/examples/toolbar.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/toolbar.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[toolbar]
+ *  Toolbar with Actions
+ *  --------------------
+ *  Create a toolbar with GeoExt Actions.
+ */
+
+Ext.onReady(function() {
+    Ext.QuickTips.init();
+
+    var map = new OpenLayers.Map();
+    var wms = new OpenLayers.Layer.WMS(
+        "Global Imagery",
+        "http://maps.opengeo.org/geowebcache/service/wms",
+        {layers: "bluemarble"}
+    );
+    var vector = new OpenLayers.Layer.Vector("vector");
+    map.addLayers([wms, vector]);
+    
+    var ctrl, toolbarItems = [], action, actions = {};
+
+    // ZoomToMaxExtent control, a "button" control
+    action = new GeoExt.Action({
+        control: new OpenLayers.Control.ZoomToMaxExtent(),
+        map: map,
+        text: "max extent",
+        tooltip: "zoom to max extent"
+    });
+    actions["max_extent"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("-");
+
+    // Navigation control and DrawFeature controls
+    // in the same toggle group
+    action = new GeoExt.Action({
+        text: "nav",
+        control: new OpenLayers.Control.Navigation(),
+        map: map,
+        // button options
+        toggleGroup: "draw",
+        allowDepress: false,
+        pressed: true,
+        tooltip: "navigate",
+        // check item options
+        group: "draw",
+        checked: true
+    });
+    actions["nav"] = action;
+    toolbarItems.push(action);
+
+    action = new GeoExt.Action({
+        text: "draw poly",
+        control: new OpenLayers.Control.DrawFeature(
+            vector, OpenLayers.Handler.Polygon
+        ),
+        map: map,
+        // button options
+        toggleGroup: "draw",
+        allowDepress: false,
+        tooltip: "draw polygon",
+        // check item options
+        group: "draw"
+    });
+    actions["draw_poly"] = action;
+    toolbarItems.push(action);
+
+    action = new GeoExt.Action({
+        text: "draw line",
+        control: new OpenLayers.Control.DrawFeature(
+            vector, OpenLayers.Handler.Path
+        ),
+        map: map,
+        // button options
+        toggleGroup: "draw",
+        allowDepress: false,
+        tooltip: "draw line",
+        // check item options
+        group: "draw"
+    });
+    actions["draw_line"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("-");
+
+    // SelectFeature control, a "toggle" control
+    action = new GeoExt.Action({
+        text: "select",
+        control: new OpenLayers.Control.SelectFeature(vector, {
+            type: OpenLayers.Control.TYPE_TOGGLE,
+            hover: true
+        }),
+        map: map,
+        // button options
+        enableToggle: true,
+        tooltip: "select feature"
+    });
+    actions["select"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("-");
+
+    // Navigation history - two "button" controls
+    ctrl = new OpenLayers.Control.NavigationHistory();
+    map.addControl(ctrl);
+
+    action = new GeoExt.Action({
+        text: "previous",
+        control: ctrl.previous,
+        disabled: true,
+        tooltip: "previous in history"
+    });
+    actions["previous"] = action;
+    toolbarItems.push(action);
+
+    action = new GeoExt.Action({
+        text: "next",
+        control: ctrl.next,
+        disabled: true,
+        tooltip: "next in history"
+    });
+    actions["next"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("->");
+
+    // Reuse the GeoExt.Action objects created above
+    // as menu items
+    toolbarItems.push({
+        text: "menu",
+        menu: new Ext.menu.Menu({
+            items: [
+                // ZoomToMaxExtent
+                actions["max_extent"],
+                // Nav
+                new Ext.menu.CheckItem(actions["nav"]),
+                // Draw poly
+                new Ext.menu.CheckItem(actions["draw_poly"]),
+                // Draw line
+                new Ext.menu.CheckItem(actions["draw_line"]),
+                // Select control
+                new Ext.menu.CheckItem(actions["select"]),
+                // Navigation history control
+                actions["previous"],
+                actions["next"]
+            ]
+        })
+    });
+
+    var mapPanel = new GeoExt.MapPanel({
+        renderTo: "mappanel",
+        height: 400,
+        width: 600,
+        map: map,
+        center: new OpenLayers.LonLat(5, 45),
+        zoom: 4,
+        tbar: toolbarItems
+    });
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/tree.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/tree.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/tree.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,8 +4,8 @@
         
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
 
         <script type="text/javascript" src="tree.js"></script>
@@ -14,7 +14,7 @@
     <body>
         <div id="desc">
             <h1>GeoExt.tree Components</h1>
-            <p>This example shows how to work with layer tree.  The basic
+            <p>This example shows how to work with a layer tree.  The basic
             component for building layer trees is the LayerNode, and there are
             different types of containers for automatically adding a map's
             layers to the tree. The tree configuration of this example is pure
@@ -22,6 +22,8 @@
             button below the layers panel.<p>
             <p>The js is not minified so it is readable. See
             <a href="tree.js">tree.js</a>.</p>
+            <p>Also see the <a href="layercontainer.html">Layer Container</a>
+            example for drag-n-drop support.</p>
         </div>
     </body>
 </html>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/tree.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/tree.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/tree.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,21 +1,21 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[tree]
+ *  Tree Nodes
+ *  ----------
+ *  Create all kinds of tree nodes.
+ */
+
 var mapPanel;
 Ext.onReady(function() {
-    // using OpenLayers.Format.JSON to create a nice formatted string of the
-    // configuration for editing it in the UI
-    var treeConfig = new OpenLayers.Format.JSON().write([{
-        nodeType: "gx_baselayercontainer"
-    }, {
-        nodeType: "gx_overlaylayercontainer",
-        // render the nodes inside this container with a radio button,
-        // and assign them the group "foo"
-        defaults: {
-            radioGroup: "foo"
-        }
-    }, {
-        nodeType: "gx_layer",
-        layer: "Tasmania Roads"
-    }], true);
-
+    // create a map panel with some layers that we will show in our layer tree
+    // below.
     mapPanel = new GeoExt.MapPanel({
         border: true,
         region: "center",
@@ -24,19 +24,21 @@
         center: [146.1569825, -41.6109735],
         zoom: 6,
         layers: [
-            new OpenLayers.Layer.WMS("Blue Marble",
-                "http://sigma.openplans.org/geoserver/wms", {
+            new OpenLayers.Layer.WMS("Global Imagery",
+                "http://maps.opengeo.org/geowebcache/service/wms", {
                     layers: "bluemarble"
                 }, {
                     buffer: 0,
                     visibility: false
-                }),
+                }
+            ),
             new OpenLayers.Layer.WMS("Tasmania State Boundaries",
                 "http://demo.opengeo.org/geoserver/wms", {
                     layers: "topp:tasmania_state_boundaries"
                 }, {
                     buffer: 0
-               }),
+                }
+            ),
             new OpenLayers.Layer.WMS("Water",
                 "http://demo.opengeo.org/geoserver/wms", {
                     layers: "topp:tasmania_water_bodies",
@@ -45,7 +47,8 @@
                 }, {
                     isBaseLayer: false,
                     buffer: 0
-                }),
+                }
+            ),
             new OpenLayers.Layer.WMS("Cities",
                 "http://demo.opengeo.org/geoserver/wms", {
                     layers: "topp:tasmania_cities",
@@ -54,7 +57,8 @@
                 }, {
                     isBaseLayer: false,
                     buffer: 0
-                }),
+                }
+            ),
             new OpenLayers.Layer.WMS("Tasmania Roads",
                 "http://demo.opengeo.org/geoserver/wms", {
                     layers: "topp:tasmania_roads",
@@ -62,13 +66,105 @@
                     format: "image/gif"
                 }, {
                     isBaseLayer: false,
+                    buffer: 0
+                }
+            ),
+            // create a group layer (with several layers in the "layers" param)
+            // to show how the LayerParamLoader works
+            new OpenLayers.Layer.WMS("Tasmania (Group Layer)",
+                "http://demo.opengeo.org/geoserver/wms", {
+                    layers: [
+                        "topp:tasmania_state_boundaries",
+                        "topp:tasmania_water_bodies",
+                        "topp:tasmania_cities",
+                        "topp:tasmania_roads"
+                    ],
+                    transparent: true,
+                    format: "image/gif"
+                }, {
+                    isBaseLayer: false,
                     buffer: 0,
                     // exclude this layer from layer container nodes
-                    displayInLayerSwitcher: false
-                })
+                    displayInLayerSwitcher: false,
+                    visibility: false
+                }
+            )
         ]
     });
+        
+    // using OpenLayers.Format.JSON to create a nice formatted string of the
+    // configuration for editing it in the UI
+    var treeConfig = new OpenLayers.Format.JSON().write([{
+        nodeType: "gx_baselayercontainer"
+    }, {
+        nodeType: "gx_overlaylayercontainer",
+        expanded: true,
+        // render the nodes inside this container with a radio button,
+        // and assign them the group "foo". See the registerRadio function
+        // in the code that we use as handlers for the tree's insert and
+        // append events to make these radio buttons change the active layer.
+        loader: {
+            baseAttrs: {radioGroup: "foo"}
+        }
+    }, {
+        nodeType: "gx_layer",
+        layer: "Tasmania (Group Layer)",
+        isLeaf: false,
+        // create subnodes for the layers in the LAYERS param. If we assign
+        // a loader to a LayerNode and do not provide a loader class, a
+        // LayerParamLoader will be assumed.
+        loader: {
+            param: "LAYERS"
+        }
+    }], true);
+
+    // the layer node's radio button with its radiochange event can be used
+    // to set an active layer.
+    var registerRadio = function(node){
+        if(!node.hasListener("radiochange")) {
+            node.on("radiochange", function(node){
+                alert(node.layer.name + " is now the the active layer.");
+            });
+        }
+    }
     
+    // create the tree with the configuration from above
+    var tree = new Ext.tree.TreePanel({
+        border: true,
+        region: "west",
+        title: "Layers",
+        width: 200,
+        split: true,
+        collapsible: true,
+        collapseMode: "mini",
+        autoScroll: true,
+        loader: new Ext.tree.TreeLoader({
+            // applyLoader has to be set to false to not interfer with loaders
+            // of nodes further down the tree hierarchy
+            applyLoader: false
+        }),
+        root: {
+            nodeType: "async",
+            // the children property of an Ext.tree.AsyncTreeNode is used to
+            // provide an initial set of layer nodes. We use the treeConfig
+            // from above, that we created with OpenLayers.Format.JSON.write.
+            children: Ext.decode(treeConfig)
+        },
+        listeners: {
+            "insert": registerRadio,
+            "append": registerRadio
+        },
+        rootVisible: false,
+        lines: false,
+        bbar: [{
+            text: "Show/Edit Tree Config",
+            handler: function() {
+                treeConfigWin.show();
+                Ext.getCmp("treeconfig").setValue(treeConfig);
+            }
+        }]
+    });
+
     // dialog for editing the tree configuration
     var treeConfigWin = new Ext.Window({
         layout: "fit",
@@ -108,52 +204,6 @@
         }]
     });
     
-    var toolbar = new Ext.Toolbar({
-        items: [{
-            text: "Show/Edit Tree Config",
-            handler: function() {
-                treeConfigWin.show();
-                Ext.getCmp("treeconfig").setValue(treeConfig);
-            }
-        }]
-    });
-    
-    var tree = new Ext.tree.TreePanel({
-        border: true,
-        region: "west",
-        title: "Layers",
-        width: 200,
-        split: true,
-        collapsible: true,
-        collapseMode: "mini",
-        autoScroll: true,
-        loader: new Ext.tree.TreeLoader({
-            clearOnLoad: true
-        }),
-        root: {
-            nodeType: "async",
-            children: Ext.decode(treeConfig)
-        },
-        rootVisible: false,
-        lines: false,
-        bbar: toolbar
-    });
-    
-    // the layer node's radio button with its radiochange event can be used
-    // to set an active layer.
-    var registerRadio = function(node){
-        if(!node.hasListener("radiochange")) {
-            node.on("radiochange", function(node){
-                alert(node.layer.name + " is now the the active layer.");
-            });
-        }
-    }
-    tree.on({
-        "insert": registerRadio,
-        "append": registerRadio,
-        scope: this
-    });
-    
     new Ext.Viewport({
         layout: "fit",
         hideBorders: true,

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -2,10 +2,10 @@
     <head>
         <title>GeoExt WMS Capabilities Example</title>
 
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
-        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
         
         <script type="text/javascript" src="wms-capabilities.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/wms-capabilities.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,3 +1,18 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+
+/** api: example[wms-capabilities]
+ *  WMS Capabilities Store
+ *  ----------------------
+ *  Create layer records from WMS capabilities documents.
+ */
+
 var store;
 Ext.onReady(function() {
     
@@ -30,12 +45,12 @@
     function mapPreview(grid, index) {
         var record = grid.getStore().getAt(index);
         var layer = record.get("layer").clone();
-        layer.isBaseLayer = true; // default is false
         
         var win = new Ext.Window({
             title: "Preview: " + record.get("title"),
             width: 512,
             height: 256,
+            layout: "fit",
             items: [{
                 xtype: "gx_mappanel",
                 layers: [layer],

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4,8 +4,8 @@
 
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
         
         <script type="text/javascript" src="zoom-chooser.js"></script>

Modified: sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/zoom-chooser.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,12 +1,25 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
 
+/** api: example[zoom-chooser]
+ *  Scale Chooser
+ *  -------------
+ *  Use a ComboBox to display available map scales.
+ */
+
 var mapPanel;
 
 Ext.onReady(function() {
     var map = new OpenLayers.Map();
     var layer = new OpenLayers.Layer.WMS(
-        "bluemarble",
-        "http://sigma.openplans.org/geoserver/wms?",
-        {layers: 'bluemarble'}
+        "Global Imagery",
+        "http://maps.opengeo.org/geowebcache/service/wms",
+        {layers: "bluemarble"}
     );
     map.addLayer(layer);
 

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.html (from rev 1504, core/trunk/geoext/examples/zoomslider.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,22 @@
+<html>
+    <head>
+        <title>GeoExt ZoomSlider</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/examples/shared/examples.css" />
+        <script src="http://openlayers.org/api/2.8/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+        <script type="text/javascript" src="zoomslider.js"></script>
+    </head>
+    <body>
+        <h1>GeoExt.ZoomSlider</h1>
+        <p>The ZoomSlider allows control of the map scale using an Ext.Slider.
+        It is also possible to add a special tooltip plugin, ZoomSliderTip, which
+        will show the zoom level, scale and resolution while dragging the slider
+        (the content is configurable).<p>
+        <p>The js is not minified so it is readable. See <a href="zoomslider.js">zoomslider.js</a>.</p>
+        <div id="map-container"></div>
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.js (from rev 1504, core/trunk/geoext/examples/zoomslider.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/examples/zoomslider.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: example[zoomslider]
+ *  Zoom Slider
+ *  -----------
+ *  Use a slider to control map scale.
+ */
+
+var panel, slider;
+
+Ext.onReady(function() {
+    
+    // create a map panel with an embedded slider
+    panel = new GeoExt.MapPanel({
+        title: "Map",
+        renderTo: "map-container",
+        height: 300,
+        width: 400,
+        map: {
+            controls: [new OpenLayers.Control.Navigation()]
+        },
+        layers: [new OpenLayers.Layer.WMS(
+            "Global Imagery",
+            "http://maps.opengeo.org/geowebcache/service/wms",
+            {layers: "bluemarble"}
+        )],
+        extent: [-5, 35, 15, 55],
+        items: [{
+            xtype: "gx_zoomslider",
+            vertical: true,
+            height: 100,
+            x: 10,
+            y: 20,
+            plugins: new GeoExt.ZoomSliderTip()
+        }]
+    });
+    
+    // create a separate slider bound to the map but displayed elsewhere
+    slider = new GeoExt.ZoomSlider({
+        map: panel.map,
+        aggressive: true,                                                                                                                                                   
+        width: 200,
+        plugins: new GeoExt.ZoomSliderTip({
+            template: "<div>Zoom Level: {zoom}</div>"
+        }),
+        renderTo: document.body
+    });
+
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/SingleFile.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/SingleFile.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/SingleFile.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,20 +1,9 @@
-/*
- * Copyright (C) 2007-2008  Camptocamp, OpenGeo
- *
- * This file is part of GeoExt
- *
- * GeoExt is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GeoExt is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GeoExt.  If not, see <http://www.gnu.org/licenses/>.
+/**
+ * Copyright (c) 2008-2009 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");

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeReader.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/AttributeReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = AttributeReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: AttributeReader(meta, recordType)
+ *  
+ *      :arg meta: ``Object`` Reader configuration.
+ *      :arg recordType: ``Array or Ext.data.Record`` An array of field
+ *          configuration objects or a record object.
+ *
+ *      Create a new attributes reader object.
+ *      
+ *      Valid meta properties:
+ *      
+ *      * format - ``OpenLayers.Format`` A parser for transforming the XHR response
+ *        into an array of objects representing attributes.  Defaults to
+ *        an ``OpenLayers.Format.WFSDescribeFeatureType`` parser.
+ *      * ignore - ``Object`` Properties of the ignore object should be field names.
+ *        Values are either arrays or regular expressions.
+ */
+GeoExt.data.AttributeReader = function(meta, recordType) {
+    meta = meta || {};
+    if(!meta.format) {
+        meta.format = new OpenLayers.Format.WFSDescribeFeatureType();
+    }
+    GeoExt.data.AttributeReader.superclass.constructor.call(
+        this, meta, recordType || meta.fields
+    );
+};
+
+Ext.extend(GeoExt.data.AttributeReader, Ext.data.DataReader, {
+
+    /** private: method[read]
+     *  :arg request: ``Object`` The XHR object that contains the parsed doc.
+     *  :return: ``Object``  A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Records``.
+     *  
+     *  This method is only used by a DataProxy which has retrieved data from a
+     *  remote server.
+     */
+    read: function(request) {
+        var data = request.responseXML;
+        if(!data || !data.documentElement) {
+            data = request.responseText;
+        }
+        return this.readRecords(data);
+    },
+
+    /** private: method[readRecords]
+     *  :arg data: ``DOMElement or String or Array`` A document element or XHR
+     *      response string.  As an alternative to fetching attributes data from
+     *      a remote source, an array of attribute objects can be provided given
+     *      that the properties of each attribute object map to a provided field
+     *      name.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Records``.
+     *  
+     *  Create a data block containing Ext.data.Records from an XML document.
+     */
+    readRecords: function(data) {
+        var attributes;
+        if(data instanceof Array) {
+            attributes = data;
+        } else {
+            // only works with one featureType in the doc
+            attributes = this.meta.format.read(data).featureTypes[0].properties;
+        }
+        var recordType = this.recordType;
+        var fields = recordType.prototype.fields;
+        var numFields = fields.length;
+        var attr, values, name, record, ignore, matches, value, records = [];
+        for(var i=0, len=attributes.length; i<len; ++i) {
+            ignore = false;
+            attr = attributes[i];
+            values = {};
+            for(var j=0; j<numFields; ++j) {
+                name = fields.items[j].name;
+                value = attr[name];
+                if(this.meta.ignore && this.meta.ignore[name]) {
+                    matches = this.meta.ignore[name];
+                    if(typeof matches == "string") {
+                        ignore = (matches === value);
+                    } else if(matches instanceof Array) {
+                        ignore = (matches.indexOf(value) > -1);
+                    } else if(matches instanceof RegExp) {
+                        ignore = (matches.test(value));
+                    }
+                    if(ignore) {
+                        break;
+                    }
+                }
+                values[name] = attr[name];
+            }
+            if(!ignore) {
+                records[records.length] = new recordType(values);
+            }
+        }
+
+        return {
+            success: true,
+            records: records,
+            totalRecords: records.length
+        };
+
+    }
+
+});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeStore.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/AttributeStore.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeStore.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/AttributeStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/data/AttributeReader.js
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = AttributeStore
+ *  base_link = `Ext.data.DataStore <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataStore>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: AttributeStore(config)
+ *  
+ *      Small helper class to make creating stores for remotely-loaded attributes
+ *      data easier. AttributeStore is pre-configured with a built-in
+ *      ``Ext.data.HttpProxy`` and :class:`gxp.data.AttributeReader`.  The
+ *      HttpProxy is configured to allow caching (disableCaching: false) and
+ *      uses GET. If you require some other proxy/reader combination then you'll
+ *      have to configure this with your own proxy or create a basic
+ *      ``Ext.data.Store`` and configure as needed.
+ */
+
+/** api: config[format]
+ *  ``OpenLayers.Format``
+ *  A parser for transforming the XHR response into an array of objects
+ *  representing attributes.  Defaults to an
+ *  ``OpenLayers.Format.WFSDescribeFeatureType`` parser.
+ */
+
+/** api: config[fields]
+ *  ``Array or Function``
+ *  Either an array of field definition objects as passed to
+ *  ``Ext.data.Record.create``, or a record constructor created using
+ *  ``Ext.data.Record.create``.  Defaults to ``["name", "type"]``. 
+ */
+GeoExt.data.AttributeStore = function(c) {
+    c = c || {};
+    GeoExt.data.AttributeStore.superclass.constructor.call(
+        this,
+        Ext.apply(c, {
+            proxy: c.proxy || (!c.data ?
+                new Ext.data.HttpProxy({url: c.url, disableCaching: false, method: "GET"}) :
+                undefined
+            ),
+            reader: new GeoExt.data.AttributeReader(
+                c, c.fields || ["name", "type"]
+            )
+        })
+    );
+};
+Ext.extend(GeoExt.data.AttributeStore, Ext.data.Store);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureReader.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureReader.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,20 +1,9 @@
-/*
- * Copyright (C) 2008 Eric Lemoine, Camptocamp France SAS
- *
- * This file is part of GeoExt
- *
- * GeoExt is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GeoExt is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GeoExt.  If not, see <http://www.gnu.org/licenses/>.
+/**
+ * Copyright (c) 2008-2009 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.
  */
 
 /**
@@ -112,7 +101,9 @@
                         else {
                             v = feature.attributes[field.mapping || field.name] || field.defaultValue;
                         }
-                        v = field.convert(v);
+                        if (field.convert) {
+                            v = field.convert(v);
+                        }
                         values[field.name] = v;
                     }
                 }
@@ -120,7 +111,9 @@
                 values.state = feature.state;
                 values.fid = feature.fid;
 
-                records[records.length] = new recordType(values, feature.id);
+                // newly inserted features need to be made into phantom records
+                var id = (feature.state === OpenLayers.State.INSERT) ? undefined : feature.id;
+                records[records.length] = new recordType(values, id);
             }
         }
 

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureRecord.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureRecord.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureRecord.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
 /** api: (define)
  *  module = GeoExt.data
@@ -27,18 +28,14 @@
     {name: "feature"}, {name: "state"}, {name: "fid"}
 ]);
 
-/**
- * APIFunction: GeoExt.data.FeatureRecord.create
- * Creates a constructor for a FeatureRecord, optionally with additional
- * fields.
- * 
- * Parameters:
- * o - {Array} Field definition as in {Ext.data.Record.create}. Can be omitted
- *     if no additional fields are required (records will always have fields
- *     {OpenLayers.Feature} "feature", {String} "state" and {String} "fid".
- *
- * Returns:
- * {Function} A specialized {<GeoExt.data.FeatureRecord>} constructor.
+/** api: classmethod[create]
+ *  :param o: ``Array`` Field definition as in ``Ext.data.Record.create``. Can
+ *      be omitted if no additional fields are required.
+ *  :return: ``Function`` A specialized :class:`GeoExt.data.FeatureRecord`
+ *      constructor.
+ *  
+ *  Creates a constructor for a :class:`GeoExt.data.FeatureRecord`, optionally
+ *  with additional fields.
  */
 GeoExt.data.FeatureRecord.create = function(o) {
     var f = Ext.extend(GeoExt.data.FeatureRecord, {});

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureStore.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureStore.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/FeatureStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * ¹ pending approval */
+ */
 
 /**
  * @include GeoExt/data/FeatureReader.js
@@ -42,7 +43,7 @@
  * This class can not be instantiated directly. Instead, it is meant to extend
  * {Ext.data.Store} or a subclass of it:
  * (start code)
- * var store = new (Ext.extend(Ext.data.Store, GeoExt.data.FeatureStoreMixin))({
+ * var store = new (Ext.extend(Ext.data.Store, new GeoExt.data.FeatureStoreMixin))({
  *     layer: myLayer,
  *     features: myFeatures
  * });
@@ -58,345 +59,378 @@
  * });
  * (end)
  */
-GeoExt.data.FeatureStoreMixin = {
-    
-    /** api: config[layer]
-     *  ``OpenLayers.Layer.Vector``  Layer to synchronize the store with.
-     */
-    layer: null,
-    
-    /** api: config[features]
-     *  ``Array(OpenLayers.Feature.Vector)``  Features that will be added to the
-     *  store (and the layer if provided).
-     */
+GeoExt.data.FeatureStoreMixin = function() {
+    return {
+        /** api: config[layer]
+         *  ``OpenLayers.Layer.Vector``  Layer to synchronize the store with.
+         */
+        layer: null,
+        
+        /** api: config[features]
+         *  ``Array(OpenLayers.Feature.Vector)``  Features that will be added to the
+         *  store (and the layer if provided).
+         */
 
-    /** api: config[reader]
-     *  ``Ext.data.DataReader`` The reader used to produce records from objects
-     *  features.  Default is :class:`GeoExt.data.FeatureReader`.
-     */
-    reader: null,
+        /** api: config[reader]
+         *  ``Ext.data.DataReader`` The reader used to produce records from objects
+         *  features.  Default is :class:`GeoExt.data.FeatureReader`.
+         */
+        reader: null,
 
-    /** api: config[addFeatureFilter]
-     *  ``Function`` This function is called before a feature record is added to
-     *  the store, it receives the feature from which a feature record is to be
-     *  created, if it returns false then no record is added.
-     */
-    addFeatureFilter: null,
-    
-    /** api: config[addRecordFilter]
-     *  ``Function`` This function is called before a feature is added to the
-     *  layer, it receives the feature record associated with the feature to be
-     *  added, if it returns false then no feature is added.
-     */
-    addRecordFilter: null,
-    
-    /** api: config[initDir]
-     *  ``Number``  Bitfields specifying the direction to use for the
-     *  initial sync between the layer and the store, if set to 0 then no
-     *  initial sync is done. Default is
-     *  ``GeoExt.data.FeatureStore.LAYER_TO_STORE|GeoExt.data.FeatureStore.STORE_TO_LAYER``.
-     */
+        /** api: config[addFeatureFilter]
+         *  ``Function`` This function is called before a feature record is added to
+         *  the store, it receives the feature from which a feature record is to be
+         *  created, if it returns false then no record is added.
+         */
+        addFeatureFilter: null,
+        
+        /** api: config[addRecordFilter]
+         *  ``Function`` This function is called before a feature is added to the
+         *  layer, it receives the feature record associated with the feature to be
+         *  added, if it returns false then no feature is added.
+         */
+        addRecordFilter: null,
+        
+        /** api: config[initDir]
+         *  ``Number``  Bitfields specifying the direction to use for the
+         *  initial sync between the layer and the store, if set to 0 then no
+         *  initial sync is done. Default is
+         *  ``GeoExt.data.FeatureStore.LAYER_TO_STORE|GeoExt.data.FeatureStore.STORE_TO_LAYER``.
+         */
 
-    /** private */
-    constructor: function(config) {
-        config = config || {};
-        config.reader = config.reader ||
-                        new GeoExt.data.FeatureReader({}, config.fields);
-        var layer = config.layer;
-        delete config.layer;
-        // 'features' option - is an alias 'data' option
-        if (config.features) {
-            config.data = config.features;
-        }
-        delete config.features;
-        // "initDir" option
-        var options = {initDir: config.initDir};
-        delete config.initDir;
-        arguments.callee.superclass.constructor.call(this, config);
-        if(layer) {
-            this.bind(layer, options);
-        }
-    },
+        /** private */
+        constructor: function(config) {
+            config = config || {};
+            config.reader = config.reader ||
+                            new GeoExt.data.FeatureReader({}, config.fields);
+            var layer = config.layer;
+            delete config.layer;
+            // 'features' option - is an alias 'data' option
+            if (config.features) {
+                config.data = config.features;
+            }
+            delete config.features;
+            // "initDir" option
+            var options = {initDir: config.initDir};
+            delete config.initDir;
+            arguments.callee.superclass.constructor.call(this, config);
+            if(layer) {
+                this.bind(layer, options);
+            }
+        },
 
-    /** api: method[bind]
-     *  :param layer: ``OpenLayers.Layer`` Layer that the store should be
-     *      synchronized with.
-     *  
-     *  Bind this store to a layer instance, once bound the store
-     *  is synchronized with the layer and vice-versa.
-     */ 
-    bind: function(layer, options) {
-        if(this.layer) {
-            // already bound
-            return;
-        }
-        this.layer = layer;
-        options = options || {};
+        /** api: method[bind]
+         *  :param layer: ``OpenLayers.Layer`` Layer that the store should be
+         *      synchronized with.
+         *  
+         *  Bind this store to a layer instance, once bound the store
+         *  is synchronized with the layer and vice-versa.
+         */ 
+        bind: function(layer, options) {
+            if(this.layer) {
+                // already bound
+                return;
+            }
+            this.layer = layer;
+            options = options || {};
 
-        var initDir = options.initDir;
-        if(options.initDir == undefined) {
-            initDir = GeoExt.data.FeatureStore.LAYER_TO_STORE |
-                      GeoExt.data.FeatureStore.STORE_TO_LAYER;
-        }
+            var initDir = options.initDir;
+            if(options.initDir == undefined) {
+                initDir = GeoExt.data.FeatureStore.LAYER_TO_STORE |
+                          GeoExt.data.FeatureStore.STORE_TO_LAYER;
+            }
 
-        // create a snapshot of the layer's features
-        var features = layer.features.slice(0);
+            // create a snapshot of the layer's features
+            var features = layer.features.slice(0);
 
-        if(initDir & GeoExt.data.FeatureStore.STORE_TO_LAYER) {
-            var records = this.getRange();
-            for(var i=records.length - 1; i>=0; i--) {
-                this.layer.addFeatures([records[i].get("feature")]);
+            if(initDir & GeoExt.data.FeatureStore.STORE_TO_LAYER) {
+                var records = this.getRange();
+                for(var i=records.length - 1; i>=0; i--) {
+                    this.layer.addFeatures([records[i].get("feature")]);
+                }
             }
-        }
 
-        if(initDir & GeoExt.data.FeatureStore.LAYER_TO_STORE) {
-            this.loadData(features, true /* append */);
-        }
+            if(initDir & GeoExt.data.FeatureStore.LAYER_TO_STORE) {
+                this.loadData(features, true /* append */);
+            }
 
-        layer.events.on({
-            "featuresadded": this.onFeaturesAdded,
-            "featuresremoved": this.onFeaturesRemoved,
-            "featuremodified": this.onFeatureModified,
-            scope: this
-        });
-        this.on({
-            "load": this.onLoad,
-            "clear": this.onClear,
-            "add": this.onAdd,
-            "remove": this.onRemove,
-            "update": this.onUpdate,
-            scope: this
-        });
-    },
-
-    /** api: method[unbind]
-     *  Unbind this store from the layer it is currently bound.
-     */
-    unbind: function() {
-        if(this.layer) {
-            this.layer.events.un({
+            layer.events.on({
                 "featuresadded": this.onFeaturesAdded,
                 "featuresremoved": this.onFeaturesRemoved,
                 "featuremodified": this.onFeatureModified,
                 scope: this
             });
-            this.un("load", this.onLoad, this);
-            this.un("clear", this.onClear, this);
-            this.un("add", this.onAdd, this);
-            this.un("remove", this.onRemove, this);
-            this.un("update", this.onUpdate, this);
+            this.on({
+                "load": this.onLoad,
+                "clear": this.onClear,
+                "add": this.onAdd,
+                "remove": this.onRemove,
+                "update": this.onUpdate,
+                scope: this
+            });
+        },
 
-            this.layer = null;
-        }
-    },
-   
-   
-    /** private: method[onFeaturesAdded]
-     *  Handler for layer featuresadded event
-     */
-    onFeaturesAdded: function(evt) {
-        if(!this._adding) {
-            var features = evt.features, toAdd = features;
-            if(typeof this.addFeatureFilter == "function") {
-                toAdd = [];
-                var i, len, feature;
-                for(var i=0, len=features.length; i<len; i++) {
+        /** api: method[unbind]
+         *  Unbind this store from the layer it is currently bound.
+         */
+        unbind: function() {
+            if(this.layer) {
+                this.layer.events.un({
+                    "featuresadded": this.onFeaturesAdded,
+                    "featuresremoved": this.onFeaturesRemoved,
+                    "featuremodified": this.onFeatureModified,
+                    scope: this
+                });
+                this.un("load", this.onLoad, this);
+                this.un("clear", this.onClear, this);
+                this.un("add", this.onAdd, this);
+                this.un("remove", this.onRemove, this);
+                this.un("update", this.onUpdate, this);
+
+                this.layer = null;
+            }
+        },
+       
+        /** api: method[getRecordFromFeature]
+         *  :arg feature: ``OpenLayers.Vector.Feature``
+         *  :returns: :class:`GeoExt.data.FeatureRecord` The record corresponding
+         *      to the given feature.  Returns null if no record matches.
+         *
+         *  Get the record corresponding to a feature.
+         */
+        getRecordFromFeature: function(feature) {
+            var record = null;
+            if(feature.state !== OpenLayers.State.INSERT) {
+                record = this.getById(feature.id);
+            } else {
+                var index = this.findBy(function(r) {
+                    return r.get("feature") === feature;
+                });
+                if(index > -1) {
+                    record = this.getAt(index);
+                }
+            }
+            return record;
+        },
+       
+        /** private: method[onFeaturesAdded]
+         *  Handler for layer featuresadded event
+         */
+        onFeaturesAdded: function(evt) {
+            if(!this._adding) {
+                var features = evt.features, toAdd = features;
+                if(typeof this.addFeatureFilter == "function") {
+                    toAdd = [];
+                    var i, len, feature;
+                    for(var i=0, len=features.length; i<len; i++) {
+                        feature = features[i];
+                        if(this.addFeatureFilter(feature) !== false) {
+                            toAdd.push(feature);
+                        }
+                    }
+                }
+                // add feature records to the store, when called with
+                // append true loadData triggers an "add" event and
+                // then a "load" event
+                this._adding = true;
+                this.loadData(toAdd, true /* append */);
+                delete this._adding;
+            }
+        },
+        
+        /** private: method[onFeaturesRemoved]
+         *  Handler for layer featuresremoved event
+         */
+        onFeaturesRemoved: function(evt){
+            if(!this._removing) {
+                var features = evt.features, feature, record, i;
+                for(i=features.length - 1; i>=0; i--) {
                     feature = features[i];
-                    if(this.addFeatureFilter(feature) !== false) {
-                        toAdd.push(feature);
+                    record = this.getRecordFromFeature(feature);
+                    if(record !== undefined) {
+                        this._removing = true;
+                        this.remove(record);
+                        delete this._removing;
                     }
                 }
             }
-            // add feature records to the store, when called with
-            // append true loadData triggers an "add" event and
-            // then a "load" event
-            this._adding = true;
-            this.loadData(toAdd, true /* append */);
-            delete this._adding;
-        }
-    },
-    
-    /** private: method[onFeaturesRemoved]
-     *  Handler for layer featuresremoved event
-     */
-    onFeaturesRemoved: function(evt){
-        if(!this._removing) {
-            var features = evt.features, feature, record, i;
-            for(i=features.length - 1; i>=0; i--) {
-                feature = features[i];
-                record = this.getById(feature.id);
+        },
+        
+        /** private: method[onFeatureModified]
+         *  Handler for layer featuremodified event
+         */
+        onFeatureModified: function(evt) {
+            if(!this._updating) {
+                var feature = evt.feature;
+                var record = this.getRecordFromFeature(feature);
                 if(record !== undefined) {
-                    this._removing = true;
-                    this.remove(record);
-                    delete this._removing;
+                    record.beginEdit();
+                    attributes = feature.attributes;
+                    if(attributes) {
+                        var fields = this.recordType.prototype.fields;
+                        for(var i=0, len=fields.length; i<len; i++) {
+                            var field = fields.items[i];
+                            var key = field.mapping || field.name;
+                            if(key in attributes) {
+                                record.set(field.name, field.convert(attributes[key]));
+                            }
+                        }
+                    }
+                    // the calls to set below won't trigger "update"
+                    // events because we called beginEdit to start a
+                    // "transaction", "update" will be triggered by
+                    // endEdit
+                    record.set("state", feature.state);
+                    record.set("fid", feature.fid);
+                    // Ext 3.0 does not allow circular references in objects passed
+                    // to record.set
+                    record.data["feature"] = feature;
+                    this._updating = true;
+                    record.endEdit();
+                    delete this._updating;
                 }
             }
-        }
-    },
+        },
 
-    /** private: method[onFeatureModified]
-     *  Handler for layer featuremodified event
-     */
-    onFeatureModified: function(evt) {
-        if(!this._updating) {
-            var feature = evt.feature;
-            var record = this.getById(feature.id);
-            if(record !== undefined) {
-                record.beginEdit();
-                attributes = feature.attributes;
-                if(attributes) {
-                    var fields = this.recordType.prototype.fields;
-                    for(var i=0, len=fields.length; i<len; i++) {
-                        var field = fields.items[i];
-                        var v = attributes[field.mapping || field.name] ||
-                                field.defaultValue;
-                        v = field.convert(v);
-                        record.set(field.name, v);
+        /** private: method[addFeaturesToLayer]
+         *  Given an array of records add features to the layer. This
+         *  function is used by the onLoad and onAdd handlers.
+         */
+        addFeaturesToLayer: function(records) {
+            var i, len, features, record;
+            if(typeof this.addRecordFilter == "function") {
+                features = [];
+                for(i=0, len=records.length; i<len; i++) {
+                    record = records[i];
+                    if(this.addRecordFilter(record) !== false) {
+                        features.push(record.get("feature"));
                     }
                 }
-                // the calls to set below won't trigger "update"
-                // events because we called beginEdit to start a
-                // "transaction", "update" will be triggered by
-                // endEdit
-                record.set("state", feature.state);
-                record.set("fid", feature.fid);
-                record.set("feature", feature);
-                this._updating = true;
-                record.endEdit();
-                delete this._updating;
+            } else {
+                features = new Array((len=records.length));
+                for(i=0; i<len; i++) {
+                    features[i] = records[i].get("feature");
+                }
             }
-        }
-    },
+            if(features.length > 0) {
+                this._adding = true;
+                this.layer.addFeatures(features);
+                delete this._adding;
+            }
+        },
+       
+        /** private: method[onLoad]
+         *  :param store: ``Ext.data.Store``
+         *  :param records: ``Array(Ext.data.Record)``
+         *  :param options: ``Object``
+         * 
+         *  Handler for store load event
+         */
+        onLoad: function(store, records, options) {
+            // if options.add is true an "add" event was already
+            // triggered, and onAdd already did the work of 
+            // adding the features to the layer.
+            if(!options || options.add !== true) {
+                this._removing = true;
+                this.layer.removeFeatures(this.layer.features);
+                delete this._removing;
 
-    /** private: method[addFeaturesToLayer]
-     *  Given an array of records add features to the layer. This
-     *  function is used by the onLoad and onAdd handlers.
-     */
-    addFeaturesToLayer: function(records) {
-        var i, len, features, record;
-        if(typeof this.addRecordFilter == "function") {
-            features = []
-            for(i=0, len=records.length; i<len; i++) {
-                record = records[i];
-                if(this.addRecordFilter(record) !== false) {
-                    features.push(record.get("feature"));
-                }
+                this.addFeaturesToLayer(records);
             }
-        } else {
-            features = new Array((len=records.length));
-            for(i=0; i<len; i++) {
-                features[i] = records[i].get("feature");
-            }
-        }
-        if(features.length > 0) {
-            this._adding = true;
-            this.layer.addFeatures(features);
-            delete this._adding;
-        }
-    },
-   
-    /** private: method[onLoad]
-     *  :param store: ``Ext.data.Store``
-     *  :param records: ``Array(Ext.data.Record)``
-     *  :param options: ``Object``
-     * 
-     *  Handler for store load event
-     */
-    onLoad: function(store, records, options) {
-        // if options.add is true an "add" event was already
-        // triggered, and onAdd already did the work of 
-        // adding the features to the layer.
-        if(!options || options.add !== true) {
+        },
+        
+        /** private: method[onClear]
+         *  :param store: ``Ext.data.Store``
+         *      
+         *  Handler for store clear event
+         */
+        onClear: function(store) {
             this._removing = true;
             this.layer.removeFeatures(this.layer.features);
             delete this._removing;
-
-            this.addFeaturesToLayer(records);
-        }
-    },
-    
-    /** private: method[onClear]
-     *  :param store: ``Ext.data.Store``
-     *      
-     *  Handler for store clear event
-     */
-    onClear: function(store) {
-        this._removing = true;
-        this.layer.removeFeatures(this.layer.features);
-        delete this._removing;
-    },
-    
-    /** private: method[onAdd]
-     *  :param store: ``Ext.data.Store``
-     *  :param records: ``Array(Ext.data.Record)``
-     *  :param index: ``Number``
-     * 
-     *  Handler for store add event
-     */
-    onAdd: function(store, records, index) {
-        if(!this._adding) {
-            // addFeaturesToLayer takes care of setting
-            // this._adding to true and deleting it
-            this.addFeaturesToLayer(records);
-        }
-    },
-    
-    /** private: method[onRemove]
-     *  :param store: ``Ext.data.Store``
-     *  :param records: ``Array(Ext.data.Record)``
-     *  :param index: ``Number``
-     *      
-     *  Handler for store remove event
-     */
-    onRemove: function(store, record, index){
-        if(!this._removing) {
-            var feature = record.get("feature");
-            if (this.layer.getFeatureById(feature.id) != null) {
-                this._removing = true;
-                this.layer.removeFeatures([record.get("feature")]);
-                delete this._removing;
+        },
+        
+        /** private: method[onAdd]
+         *  :param store: ``Ext.data.Store``
+         *  :param records: ``Array(Ext.data.Record)``
+         *  :param index: ``Number``
+         * 
+         *  Handler for store add event
+         */
+        onAdd: function(store, records, index) {
+            if(!this._adding) {
+                // addFeaturesToLayer takes care of setting
+                // this._adding to true and deleting it
+                this.addFeaturesToLayer(records);
             }
-        }
-    },
+        },
+        
+        /** private: method[onRemove]
+         *  :param store: ``Ext.data.Store``
+         *  :param records: ``Array(Ext.data.Record)``
+         *  :param index: ``Number``
+         *      
+         *  Handler for store remove event
+         */
+        onRemove: function(store, record, index){
+            if(!this._removing) {
+                var feature = record.get("feature");
+                if (this.layer.getFeatureById(feature.id) != null) {
+                    this._removing = true;
+                    this.layer.removeFeatures([record.get("feature")]);
+                    delete this._removing;
+                }
+            }
+        },
 
-    /** private: method[onUpdate]
-     *  :param store: ``Ext.data.Store``
-     *  :param record: ``Ext.data.Record``
-     *  :param operation: ``String``
-     *
-     *  Handler for update.
-     */
-    onUpdate: function(store, record, operation) {
-        if(!this._updating) {
-            var feature = record.get("feature");
-            if(record.fields) {
-                var cont = this.layer.events.triggerEvent(
-                    "beforefeaturemodified", {feature: feature}
-                );
-                if(cont !== false) {
-                    record.fields.each(
-                        function(field) {
-                            feature.attributes[field.mapping || field.name] =
-                                record.get(field.name);
+        /** private: method[onUpdate]
+         *  :param store: ``Ext.data.Store``
+         *  :param record: ``Ext.data.Record``
+         *  :param operation: ``String``
+         *
+         *  Handler for update.
+         */
+        onUpdate: function(store, record, operation) {
+            if(!this._updating) {
+                /**
+                  * TODO: remove this if the FeatureReader adds attributes
+                  * for all fields that map to feature.attributes.
+                  * In that case, it would be sufficient to check (key in feature.attributes). 
+                  */
+                var defaultFields = new GeoExt.data.FeatureRecord().fields;
+                var feature = record.get("feature");
+                if(record.fields) {
+                    var cont = this.layer.events.triggerEvent(
+                        "beforefeaturemodified", {feature: feature}
+                    );
+                    if(cont !== false) {
+                        var attributes = feature.attributes;
+                        record.fields.each(
+                            function(field) {
+                                var key = field.mapping || field.name;
+                                if (!defaultFields.containsKey(key)) {
+                                    attributes[key] = record.get(field.name);
+                                }
+                            }
+                        );
+                        this._updating = true;
+                        this.layer.events.triggerEvent(
+                            "featuremodified", {feature: feature}
+                        );
+                        delete this._updating;
+                        if (this.layer.getFeatureById(feature.id) != null) {
+                            this.layer.drawFeature(feature);
                         }
-                    );
-                    this._updating = true;
-                    this.layer.events.triggerEvent(
-                        "featuremodified", {feature: feature}
-                    );
-                    delete this._updating;
-                    if (this.layer.getFeatureById(feature.id) != null) {
-                        this.layer.drawFeature(feature);
                     }
                 }
             }
         }
-    }
+    };
 };
 
 GeoExt.data.FeatureStore = Ext.extend(
     Ext.data.Store,
-    GeoExt.data.FeatureStoreMixin
+    new GeoExt.data.FeatureStoreMixin
 );
 
 /**

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerReader.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerReader.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,38 +1,40 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * ¹ pending approval */
+ */
 
 /**
  * @include GeoExt/data/LayerRecord.js
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = LayerReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
 Ext.namespace("GeoExt", "GeoExt.data");
 
-/**
- * Class: GeoExt.data.LayerReader
- *      LayerReader is a specific Ext.data.DataReader for converting
- *      layers into layer records, i.e. {OpenLayers.Layer} objects
- *      into {GeoExt.data.LayerRecor} objects.
- *
- * Usage example:
- * (start code)
- *         var reader = new GeoExt.data.LayerReader();
- *         var layerData = reader.readRecords(map.layers);
- *         var numRecords = layerData.totalRecords;
- *         var layerRecords = layerData.records;
- * (end)
- *
- * Inherits from:
- *  - {Ext.data.DataReader}
+/** api: example
+ *  Sample using a reader to create records from an array of layers:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var reader = new GeoExt.data.LayerReader();
+ *      var layerData = reader.readRecords(map.layers);
+ *      var numRecords = layerData.totalRecords;
+ *      var layerRecords = layerData.records;
  */
 
-/**
- * Constructor: GeoExt.data.LayerReader
- *      Create a layer reader. The arguments passed are similar to those
- *      passed to {Ext.data.DataReader} constructor.
+/** api: constructor
+ *  .. class:: LayerReader(meta, recordType)
+ *  
+ *      Data reader class to create an array of
+ *      :class:`GeoExt.data.LayerRecord` objects from an array of 
+ *      ``OpenLayers.Layer`` objects for use in a
+ *      :class:`GeoExt.data.LayerStore` object.
  */
 GeoExt.data.LayerReader = function(meta, recordType) {
     meta = meta || {};
@@ -46,24 +48,19 @@
 
 Ext.extend(GeoExt.data.LayerReader, Ext.data.DataReader, {
 
-    /**
-     * APIProperty: totalRecords
-     * {Integer}
+    /** private: property[totalRecords]
+     *  ``Integer``
      */
     totalRecords: null,
 
-    /**
-     * APIMethod: readRecords
-     *      From an array of {OpenLayers.Layer} objects create a data block
-     *      containing {<GeoExt.data.LayerRecord>} objects.
-     *
-     * Parameters:
-     * layers - {Array({OpenLayers.Layer})} Array of layers.
-     *
-     * Returns:
-     * {Object} An object with two properties. The value of the property "records"
-     *      is the array of layer records. The value of the property "totalRecords"
-     *      is the number of records in the array.
+    /** api: method[readRecords]
+     *  :param layers: ``Array(OpenLayers.Layer)`` List of layers for creating
+     *      records.
+     *  :return: ``Object``  An object with ``records`` and ``totalRecords``
+     *      properties.
+     *  
+     *  From an array of ``OpenLayers.Layer`` objects create a data block
+     *  containing :class:`GeoExt.data.LayerRecord` objects.
      */
     readRecords : function(layers) {
         var records = [];

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerRecord.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerRecord.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerRecord.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,47 +1,55 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = LayerRecord
+ *  base_link = `Ext.data.Record <http://extjs.com/deploy/dev/docs/?class=Ext.data.Record>`_
+ */
 Ext.namespace("GeoExt.data");
 
-/**
- * Class: GeoExt.data.LayerRecord
- * A subclass of {Ext.data.Record} which provides a special record that
- * represents an {OpenLayers.Layer}. This record will always have at least a
- * layer and a title field in its data. The id of this record will be the same
- * as the id of the layer it represents.
- * 
- * Inherits from
- * - {Ext.data.Record}
+/** api: constructor
+ *  .. class:: LayerRecord
+ *  
+ *      A record that represents an ``OpenLayers.Layer``. This record
+ *      will always have at least the following fields:
+ *
+ *      * layer ``OpenLayers.Layer``
+ *      * title ``String``
  */
-/**
- * Constructor: GeoExt.data.LayerRecord
- * 
- * Parameters:
- * data - {Object} data object for the record
- * id - {String} id of the record
- */
 GeoExt.data.LayerRecord = Ext.data.Record.create([
     {name: "layer"},
     {name: "title", type: "string", mapping: "name"}
 ]);
 
-/**
- * APIFunction: GeoExt.data.LayerRecord.create
- * Creates a constructor for a LayerRecord, optionally with additional
- * fields.
- * 
- * Parameters:
- * o - {Array} Field definition as in {Ext.data.Record.create}. Can be omitted
- *     if no additional fields are required (records will always have a
- *     {OpenLayers.Layer} layer and a {String} title field).
- *
- * Returns:
- * {Function} A specialized {<GeoExt.data.LayerRecord>} constructor.
+/** api: method[clone]
+ *  :param id: ``String`` (optional) A new Record id.
+ *  :return: ``GeoExt.data.LayerRecord`` A new layer record.
+ *  
+ *  Creates a clone of this LayerRecord. 
  */
+GeoExt.data.LayerRecord.prototype.clone = function(id) { 
+    var layer = this.get("layer") && this.get("layer").clone(); 
+    return new this.constructor( 
+        Ext.applyIf({layer: layer}, this.data), 
+        id || layer.id
+    );
+}; 
+
+/** api: classmethod[create]
+ *  :param o: ``Array`` Field definition as in ``Ext.data.Record.create``. Can
+ *      be omitted if no additional fields are required.
+ *  :return: ``Function`` A specialized :class:`GeoExt.data.LayerRecord`
+ *      constructor.
+ *  
+ *  Creates a constructor for a :class:`GeoExt.data.LayerRecord`, optionally
+ *  with additional fields.
+ */
 GeoExt.data.LayerRecord.create = function(o) {
     var f = Ext.extend(GeoExt.data.LayerRecord, {});
     var p = f.prototype;

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerStore.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerStore.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/LayerStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * ¹ pending approval */
+ */
 
 /**
  * @include GeoExt/data/LayerReader.js
@@ -30,7 +31,7 @@
  *
  *  .. code-block:: javascript
  *  
- *      var store = new (Ext.extend(Ext.data.Store, GeoExt.data.LayerStoreMixin))({
+ *      var store = new (Ext.extend(Ext.data.Store, new GeoExt.data.LayerStoreMixin))({
  *          map: myMap,
  *          layers: myLayers
  *      });
@@ -39,316 +40,350 @@
  *  shortcut to the ``Ext.extend`` sequence in the above code snippet.
  */
 
-GeoExt.data.LayerStoreMixin = {
+GeoExt.data.LayerStoreMixin = function() {
+    return {
+        /** api: config[map]
+         *  ``OpenLayers.Map``
+         *  Map that this store will be in sync with.
+         */
+        
+        /** api: property[map]
+         *  ``OpenLayers.Map``
+         *  Map that the store is synchronized with.
+         */
+        map: null,
+        
+        /** api: config[layers]
+         *  ``Array(OpenLayers.Layer)``
+         *  Layers that will be added to the store (and the map, depending on the
+         *  value of the ``initDir`` option.
+         */
+        
+        /** api: config[initDir]
+         *  ``Number``
+         *  Bitfields specifying the direction to use for the initial sync between
+         *  the map and the store, if set to 0 then no initial sync is done.
+         *  Defaults to ``GeoExt.data.LayerStore.MAP_TO_STORE|GeoExt.data.LayerStore.STORE_TO_MAP``
+         */
 
-    /** api: config[map]
-     *  ``OpenLayers.Map``
-     *  Map that this store will be in sync with.
-     */
-    
-    /** api: property[map]
-     *  ``OpenLayers.Map``
-     *  Map that the store is synchronized with.
-     */
-    map: null,
-    
-    /** api: config[layers]
-     *  ``Array(OpenLayers.Layer)``
-     *  Layers that will be added to the store (and the map, depending on the
-     *  value of the ``initDir`` option.
-     */
-    
-    /** api: config[initDir]
-     *  ``Number``
-     *  Bitfields specifying the direction to use for the initial sync between
-     *  the map and the store, if set to 0 then no initial sync is done.
-     *  Defaults to ``GeoExt.data.LayerStore.MAP_TO_STORE|GeoExt.data.LayerStore.STORE_TO_MAP``
-     */
+        /** api: config[fields]
+         *  ``Array``
+         *  If provided a custom layer record type with additional fields will be
+         *  used. Default fields for every layer record are `layer`
+         *  (``OpenLayers.Layer``) `title` (``String``). The value of this option is
+         *  either a field definition objects as passed to the
+         *  :meth:`GeoExt.data.LayerRecord.create` function or a
+         *  :class:`GeoExt.data.LayerRecord` constructor created using
+         *  :meth:`GeoExt.data.LayerRecord.create`.
+         */
 
-    /** api: config[fields]
-     *  ``Array``
-     *  If provided a custom layer record type with additional fields will be
-     *  used. Default fields for every layer record are `layer`
-     *  (``OpenLayers.Layer``) `title` (``String``). The value of this option is
-     *  either a field definition objects as passed to the
-     *  :meth:`GeoExt.data.LayerRecord.create` function or a
-     *  :class:`GeoExt.data.LayerRecord` constructor created using
-     *  :meth:`GeoExt.data.LayerRecord.create`.
-     */
+        /** api: config[reader]
+         *  ``Ext.data.DataReader`` The reader used to produce
+         *  :class:`GeoExt.data.LayerRecord` objects from ``OpenLayers.Layer``
+         *  objects.  If not provided, a :class:`GeoExt.data.LayerReader` will be
+         *  used.
+         */
+        reader: null,
 
-    /** api: config[reader]
-     *  ``Ext.data.DataReader`` The reader used to produce
-     *  :class:`GeoExt.data.LayerRecord` objects from ``OpenLayers.Layer``
-     *  objects.  If not provided, a :class:`GeoExt.data.LayerReader` will be
-     *  used.
-     */
-    reader: null,
+        /** private: method[constructor]
+         */
+        constructor: function(config) {
+            config = config || {};
+            config.reader = config.reader ||
+                            new GeoExt.data.LayerReader({}, config.fields);
+            delete config.fields;
+            // "map" option
+            var map = config.map instanceof GeoExt.MapPanel ?
+                      config.map.map : config.map;
+            delete config.map;
+            // "layers" option - is an alias to "data" option
+            if(config.layers) {
+                config.data = config.layers;
+            }
+            delete config.layers;
+            // "initDir" option
+            var options = {initDir: config.initDir};
+            delete config.initDir;
+            arguments.callee.superclass.constructor.call(this, config);
+            if(map) {
+                this.bind(map, options);
+            }
+        },
 
-    /** private: method[constructor]
-     */
-    constructor: function(config) {
-        config = config || {};
-        config.reader = config.reader ||
-                        new GeoExt.data.LayerReader({}, config.fields);
-        delete config.fields;
-        // "map" option
-        var map = config.map instanceof GeoExt.MapPanel ?
-                  config.map.map : config.map;
-        delete config.map;
-        // "layers" option - is an alias to "data" option
-        if(config.layers) {
-            config.data = config.layers;
-        }
-        delete config.layers;
-        // "initDir" option
-        var options = {initDir: config.initDir};
-        delete config.initDir;
-        arguments.callee.superclass.constructor.call(this, config);
-        if(map) {
-            this.bind(map, options);
-        }
-    },
+        /** private: method[bind]
+         *  :param map: ``OpenLayers.Map`` The map instance.
+         *  :param options: ``Object``
+         *  
+         *  Bind this store to a map instance, once bound the store
+         *  is synchronized with the map and vice-versa.
+         */
+        bind: function(map, options) {
+            if(this.map) {
+                // already bound
+                return;
+            }
+            this.map = map;
+            options = options || {};
 
-    /** private: method[bind]
-     *  :param map: ``OpenLayers.Map`` The map instance.
-     *  :param options: ``Object``
-     *  
-     *  Bind this store to a map instance, once bound the store
-     *  is synchronized with the map and vice-versa.
-     */
-    bind: function(map, options) {
-        if(this.map) {
-            // already bound
-            return;
-        }
-        this.map = map;
-        options = options || {};
+            var initDir = options.initDir;
+            if(options.initDir == undefined) {
+                initDir = GeoExt.data.LayerStore.MAP_TO_STORE |
+                          GeoExt.data.LayerStore.STORE_TO_MAP;
+            }
 
-        var initDir = options.initDir;
-        if(options.initDir == undefined) {
-            initDir = GeoExt.data.LayerStore.MAP_TO_STORE |
-                      GeoExt.data.LayerStore.STORE_TO_MAP;
-        }
+            // create a snapshot of the map's layers
+            var layers = map.layers.slice(0);
 
-        // create a snapshot of the map's layers
-        var layers = map.layers.slice(0);
-
-        if(initDir & GeoExt.data.LayerStore.STORE_TO_MAP) {
-            var records = this.getRange();
-            for(var i=records.length - 1; i>=0; i--) {
-                this.map.addLayer(records[i].get("layer"));
+            if(initDir & GeoExt.data.LayerStore.STORE_TO_MAP) {
+                this.each(function(record) {
+                    this.map.addLayer(record.get("layer"));
+                }, this);
             }
-        }
-        if(initDir & GeoExt.data.LayerStore.MAP_TO_STORE) {
-            this.loadData(layers, true);
-        }
+            if(initDir & GeoExt.data.LayerStore.MAP_TO_STORE) {
+                this.loadData(layers, true);
+            }
 
-        map.events.on({
-            "changelayer": this.onChangeLayer,
-            "addlayer": this.onAddLayer,
-            "removelayer": this.onRemoveLayer,
-            scope: this
-        });
-        this.on({
-            "load": this.onLoad,
-            "clear": this.onClear,
-            "add": this.onAdd,
-            "remove": this.onRemove,
-            scope: this
-        });
-        this.data.on({
-            "replace" : this.onReplace,
-            scope: this
-        });
-    },
-
-    /** private: method[unbind]
-     *  Unbind this store from the map it is currently bound.
-     */
-    unbind: function() {
-        if(this.map) {
-            this.map.events.un({
+            map.events.on({
                 "changelayer": this.onChangeLayer,
                 "addlayer": this.onAddLayer,
                 "removelayer": this.onRemoveLayer,
                 scope: this
             });
-            this.un("load", this.onLoad, this);
-            this.un("clear", this.onClear, this);
-            this.un("add", this.onAdd, this);
-            this.un("remove", this.onRemove, this);
+            this.on({
+                "load": this.onLoad,
+                "clear": this.onClear,
+                "add": this.onAdd,
+                "remove": this.onRemove,
+                "update": this.onUpdate,
+                scope: this
+            });
+            this.data.on({
+                "replace" : this.onReplace,
+                scope: this
+            });
+        },
 
-            this.data.un("replace", this.onReplace, this);
+        /** private: method[unbind]
+         *  Unbind this store from the map it is currently bound.
+         */
+        unbind: function() {
+            if(this.map) {
+                this.map.events.un({
+                    "changelayer": this.onChangeLayer,
+                    "addlayer": this.onAddLayer,
+                    "removelayer": this.onRemoveLayer,
+                    scope: this
+                });
+                this.un("load", this.onLoad, this);
+                this.un("clear", this.onClear, this);
+                this.un("add", this.onAdd, this);
+                this.un("remove", this.onRemove, this);
 
-            this.map = null;
-        }
-    },
-    
-    /** private: method[onChangeLayer]
-     *  :param evt: ``Object``
-     * 
-     *  Handler for layer changes.  When layer order changes, this moves the
-     *  appropriate record within the store.
-     */
-    onChangeLayer: function(evt) {
-        var layer = evt.layer;
-        var recordIndex = this.findBy(function(rec, id) {
-            return rec.get("layer") === layer;
-        });
-        if(recordIndex > -1) {
-            var record = this.getAt(recordIndex);
-            if(evt.property === "order") {
-                if(!this._adding && !this._removing) {
-                    var layerIndex = this.map.getLayerIndex(layer);
-                    if(layerIndex !== recordIndex) {
-                        this._removing = true;
-                        this.remove(record);
-                        delete this._removing;
-                        this._adding = true;
-                        this.insert(layerIndex, [record]);
-                        delete this._adding;
+                this.data.un("replace", this.onReplace, this);
+
+                this.map = null;
+            }
+        },
+        
+        /** private: method[onChangeLayer]
+         *  :param evt: ``Object``
+         * 
+         *  Handler for layer changes.  When layer order changes, this moves the
+         *  appropriate record within the store.
+         */
+        onChangeLayer: function(evt) {
+            var layer = evt.layer;
+            var recordIndex = this.findBy(function(rec, id) {
+                return rec.get("layer") === layer;
+            });
+            if(recordIndex > -1) {
+                var record = this.getAt(recordIndex);
+                if(evt.property === "order") {
+                    if(!this._adding && !this._removing) {
+                        var layerIndex = this.map.getLayerIndex(layer);
+                        if(layerIndex !== recordIndex) {
+                            this._removing = true;
+                            this.remove(record);
+                            delete this._removing;
+                            this._adding = true;
+                            this.insert(layerIndex, [record]);
+                            delete this._adding;
+                        }
                     }
+                } else if(evt.property === "name") {
+                    record.set("title", layer.name);
+                } else {
+                    this.fireEvent("update", this, record, Ext.data.Record.EDIT);
                 }
+            }
+        },
+       
+        /** private: method[onAddLayer]
+         *  :param evt: ``Object``
+         *  
+         *  Handler for a map's addlayer event
+         */
+        onAddLayer: function(evt) {
+            if(!this._adding) {
+                var layer = evt.layer;
+                this._adding = true;
+                this.loadData([layer], true);
+                delete this._adding;
+            }
+        },
+        
+        /** private: method[onRemoveLayer]
+         *  :param evt: ``Object``
+         * 
+         *  Handler for a map's removelayer event
+         */
+        onRemoveLayer: function(evt){
+            //TODO replace the check for undloadDestroy with a listener for the
+            // map's beforedestroy event, doing unbind(). This can be done as soon
+            // as http://trac.openlayers.org/ticket/2136 is fixed.
+            if(this.map.unloadDestroy) {
+                if(!this._removing) {
+                    var layer = evt.layer;
+                    this._removing = true;
+                    this.remove(this.getById(layer.id));
+                    delete this._removing;
+                }
             } else {
-                this.fireEvent("update", this, record, Ext.data.Record.EDIT);
+                this.unbind();
             }
-        }
-    },
-   
-    /** private: method[onAddLayer]
-     *  :param evt: ``Object``
-     *  
-     *  Handler for a map's addlayer event
-     */
-    onAddLayer: function(evt) {
-        if(!this._adding) {
-            var layer = evt.layer;
-            this._adding = true;
-            this.loadData([layer], true);
-            delete this._adding;
-        }
-    },
-    
-    /** private: method[onRemoveLayer]
-     *  :param evt: ``Object``
-     * 
-     *  Handler for a map's removelayer event
-     */
-    onRemoveLayer: function(evt){
-        if(!this._removing) {
-            var layer = evt.layer;
+        },
+        
+        /** private: method[onLoad]
+         *  :param store: ``Ext.data.Store``
+         *  :param records: ``Array(Ext.data.Record)``
+         *  :param options: ``Object``
+         * 
+         *  Handler for a store's load event
+         */
+        onLoad: function(store, records, options) {
+            if (!Ext.isArray(records)) {
+                records = [records];
+            }
+            if (options && !options.add) {
+                this._removing = true;
+                for (var i = this.map.layers.length - 1; i >= 0; i--) {
+                    this.map.removeLayer(this.map.layers[i]);
+                }
+                delete this._removing;
+
+                // layers has already been added to map on "add" event
+                var len = records.length;
+                if (len > 0) {
+                    var layers = new Array(len);
+                    for (var j = 0; j < len; j++) {
+                        layers[j] = records[j].get("layer");
+                    }
+                    this._adding = true;
+                    this.map.addLayers(layers);
+                    delete this._adding;
+                }
+            }
+        },
+        
+        /** private: method[onClear]
+         *  :param store: ``Ext.data.Store``
+         * 
+         *  Handler for a store's clear event
+         */
+        onClear: function(store) {
             this._removing = true;
-            this.remove(this.getById(layer.id));
-            delete this._removing;
-        }
-    },
-    
-    /** private: method[onLoad]
-     *  :param store: ``Ext.data.Store``
-     *  :param records: ``Array(Ext.data.Record)``
-     *  :param options: ``Object``
-     * 
-     *  Handler for a store's load event
-     */
-    onLoad: function(store, records, options) {
-        if (!Ext.isArray(records)) {
-            records = [records];
-        }
-        if (options && !options.add) {
-            this._removing = true;
             for (var i = this.map.layers.length - 1; i >= 0; i--) {
                 this.map.removeLayer(this.map.layers[i]);
             }
             delete this._removing;
-
-            // layers has already been added to map on "add" event
-            var len = records.length;
-            if (len > 0) {
-                var layers = new Array(len);
-                for (var j = 0; j < len; j++) {
-                    layers[j] = records[j].get("layer");
+        },
+        
+        /** private: method[onAdd]
+         *  :param store: ``Ext.data.Store``
+         *  :param records: ``Array(Ext.data.Record)``
+         *  :param index: ``Number``
+         * 
+         *  Handler for a store's add event
+         */
+        onAdd: function(store, records, index) {
+            if(!this._adding) {
+                this._adding = true;
+                var layer;
+                for(var i=records.length-1; i>=0; --i) {
+                    layer = records[i].get("layer");
+                    this.map.addLayer(layer);
+                    if(index !== this.map.layers.length-1) {
+                        this.map.setLayerIndex(layer, index);
+                    }
                 }
-                this._adding = true;
-                this.map.addLayers(layers);
                 delete this._adding;
             }
-        }
-    },
-    
-    /** private: method[onClear]
-     *  :param store: ``Ext.data.Store``
-     * 
-     *  Handler for a store's clear event
-     */
-    onClear: function(store) {
-        this._removing = true;
-        for (var i = this.map.layers.length - 1; i >= 0; i--) {
-            this.map.removeLayer(this.map.layers[i]);
-        }
-        delete this._removing;
-    },
-    
-    /** private: method[onAdd]
-     *  :param store: ``Ext.data.Store``
-     *  :param records: ``Array(Ext.data.Record)``
-     *  :param index: ``Number``
-     * 
-     *  Handler for a store's add event
-     */
-    onAdd: function(store, records, index) {
-        if(!this._adding) {
-            this._adding = true;
-            var layer;
-            for(var i=records.length-1; i>=0; --i) {
-                layer = records[i].get("layer");
-                this.map.addLayer(layer);
-                if(index !== this.map.layers.length-1) {
-                    this.map.setLayerIndex(layer, index);
+        },
+        
+        /** private: method[onRemove]
+         *  :param store: ``Ext.data.Store``
+         *  :param record: ``Ext.data.Record``
+         *  :param index: ``Number``
+         * 
+         *  Handler for a store's remove event
+         */
+        onRemove: function(store, record, index){
+            if(!this._removing) {
+                var layer = record.get("layer");
+                if (this.map.getLayer(layer.id) != null) {
+                    this._removing = true;
+                    this.removeMapLayer(record);
+                    delete this._removing;
                 }
             }
-            delete this._adding;
-        }
-    },
-    
-    /** private: method[onRemove]
-     *  :param store: ``Ext.data.Store``
-     *  :param record: ``Ext.data.Record``
-     *  :param index: ``Number``
-     * 
-     *  Handler for a store's remove event
-     */
-    onRemove: function(store, record, index){
-        if(!this._removing) {
-            var layer = record.get("layer");
-            if (this.map.getLayer(layer.id) != null) {
-                this._removing = true;
-                this.removeMapLayer(record);
-                delete this._removing;
+        },
+        
+        /** private: method[onUpdate]
+         *  :param store: ``Ext.data.Store``
+         *  :param record: ``Ext.data.Record``
+         *  :param operation: ``Number``
+         * 
+         *  Handler for a store's update event
+         */
+        onUpdate: function(store, record, operation) {
+            if(operation === Ext.data.Record.EDIT) {
+                var layer = record.get("layer");
+                var title = record.get("title");
+                if(title !== layer.name) {
+                    layer.setName(title);
+                }
             }
-        }
-    },
+        },
 
-    /** private: method[removeMapLayer]
-     *  :param record: ``Ext.data.Record``
-     *  
-     *  Removes a record's layer from the bound map.
-     */
-    removeMapLayer: function(record){
-        this.map.removeLayer(record.get("layer"));
-    },
+        /** private: method[removeMapLayer]
+         *  :param record: ``Ext.data.Record``
+         *  
+         *  Removes a record's layer from the bound map.
+         */
+        removeMapLayer: function(record){
+            this.map.removeLayer(record.get("layer"));
+        },
 
-    /** private: method[onReplace]
-     *  :param key: ``String``
-     *  :param oldRecord: ``Object`` In this case, a record that has been
-     *      replaced.
-     *  :param newRecord: ``Object`` In this case, a record that is replacing
-     *      oldRecord.
+        /** private: method[onReplace]
+         *  :param key: ``String``
+         *  :param oldRecord: ``Object`` In this case, a record that has been
+         *      replaced.
+         *  :param newRecord: ``Object`` In this case, a record that is replacing
+         *      oldRecord.
 
-     *  Handler for a store's data collections' replace event
-     */
-    onReplace: function(key, oldRecord, newRecord){
-        this.removeMapLayer(oldRecord);
-    }
+         *  Handler for a store's data collections' replace event
+         */
+        onReplace: function(key, oldRecord, newRecord){
+            this.removeMapLayer(oldRecord);
+        },
+        
+        /** private: method[destroy]
+         */
+        destroy: function() {
+            this.unbind();
+            GeoExt.data.LayerStore.superclass.destroy.call(this);
+        }
+    };
 };
 
 /** api: example
@@ -371,7 +406,7 @@
  */
 GeoExt.data.LayerStore = Ext.extend(
     Ext.data.Store,
-    GeoExt.data.LayerStoreMixin
+    new GeoExt.data.LayerStoreMixin
 );
 
 /**

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ProtocolProxy.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ProtocolProxy.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ProtocolProxy.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,75 +1,62 @@
-/*
- * Copyright (C) 2008 Eric Lemoine, Camptocamp France SAS
- *
- * This file is part of GeoExt
- *
- * GeoExt is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GeoExt is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GeoExt.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-Ext.namespace('GeoExt', 'GeoExt.data');
-
 /**
- * Class: GeoExt.data.ProtocolProxy
+ * Copyright (c) 2008-2009 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.
  */
 
-/**
- * Constructor: GeoExt.data.ProtocolProxy
- *
- * Parameters:
- * config - {Object} Config object
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = ProtocolProxy
+ *  base_link = `Ext.data.DataProxy <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataProxy>`_
  */
+Ext.namespace('GeoExt', 'GeoExt.data');
+
 GeoExt.data.ProtocolProxy = function(config) {
-    GeoExt.data.ProtocolProxy.superclass.constructor.call(this);
     Ext.apply(this, config);
+    GeoExt.data.ProtocolProxy.superclass.constructor.apply(this, arguments);
 };
 
+/** api: constructor
+ *  .. class:: ProtocolProxy
+ *   
+ *      A data proxy for use with ``OpenLayers.Protocol`` objects.
+ */
 Ext.extend(GeoExt.data.ProtocolProxy, Ext.data.DataProxy, {
-    /**
-     * APIProperty: protocol
-     * {<OpenLayers.Protocol>} The protocol used to fetch features.
+
+    /** api: config[protocol]
+     *  ``OpenLayers.Protocol``
+     *  The protocol used to fetch features.
      */
     protocol: null,
 
-    /**
-     * APIProperty: abortPrevious
-     * {Boolean} Whether to abort the previous request or not, defaults
-     * to true.
+    /** api: config[abortPrevious]
+     *  ``Boolean``
+     *  Abort any previous request before issuing another.  Default is ``true``.
      */
     abortPrevious: true,
 
-    /**
-     * Property: response
-     * {<OpenLayers.Protocol.Response>} The response returned by
-     * the read call on the protocol.
+    /** private: property[response]
+     *  ``OpenLayers.Protocol.Response``
+     *  The response returned by the read call on the protocol.
      */
     response: null,
 
-    /**
-     * Method: load
+    /** private: method[load]
+     *  :param params: ``Object`` An object containing properties which are to
+     *      be used as HTTP parameters for the request to the remote server.
+     *  :param reader: ``Ext.data.DataReader`` The Reader object which converts
+     *      the data object into a block of ``Ext.data.Records``.
+     *  :param callback: ``Function`` The function into which to pass the block
+     *      of ``Ext.data.Records``. The function is passed the Record block
+     *      object, the ``args`` argument passed to the load function, and a
+     *      boolean success indicator.
+     *  :param scope: ``Object`` The scope in which to call the callback.
+     *  :param arg: ``Object`` An optional argument which is passed to the
+     *      callback as its second parameter.
      *
-     * Parameters:
-     * params - {Object} An object containing properties which are to be used
-     *     as HTTP parameters for the request to the remote server.
-     * reader - {Ext.data.DataReader} The Reader object which converts the data
-     *     object into a block of Ext.data.Records.
-     * callback - {Function} The function into which to pass the block of
-     *     Ext.data.Records. The function is passed the Record block object,
-     *     the "args" argument passed to the load function, and a boolean
-     *     success indicator
-     * scope - {Object} The scope in which to call the callback
-     * arg - {Object} An optional argument which is passed to the callback
-     *     as its second parameter.
+     *  Calls ``read`` on the protocol.
      */
     load: function(params, reader, callback, scope, arg) {
         if (this.fireEvent("beforeload", this, params) !== false) {
@@ -98,31 +85,21 @@
         }
     },
 
-    /**
-     * Method: abortRequest
-     * Called to abort any ongoing request.
+    /** private: method[abortRequest]
+     *  Called to abort any ongoing request.
      */
     abortRequest: function() {
-        // FIXME really we should rely on the protocol itself to
-        // cancel the request, the Protocol class in OpenLayers
-        // 2.7 does not expose a cancel() method
         if (this.response) {
-            var response = this.response;
-            if (response.priv &&
-                typeof response.priv.abort == "function") {
-                response.priv.abort();
-                this.response = null;
-            }
+            this.protocol.abort(this.response);
+            this.response = null;
         }
     },
 
-    /**
-     * Method: loadResponse
-     * Handle response from the protocol
-     *
-     * Parameters:
-     * o - {Object} 
-     * response - {<OpenLayers.Protocol.Response>} 
+    /** private: method[loadResponse]
+     *  :param o: ``Object``
+     *  :param response: ``OpenLayers.Protocol.Response``
+     *  
+     *  Handle response from the protocol
      */
     loadResponse: function(o, response) {
         if (response.success()) {

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ScaleStore.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ScaleStore.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/ScaleStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,33 +1,42 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * ¹ pending approval */
+ */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = ScaleStore
+ *  base_link = `Ext.data.DataStore <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataStore>`_
+ */
 Ext.namespace("GeoExt.data");
 
-/**
- *  Class: GeoExt.data.ScaleStore
- *  This store maintains a list of available zoom levels, optionally keeping it synchronized with 
- *  a Map or MapPanel instance.   The entries in the list have the following fields: 
- *  zoom - the number of the zoom level
- *  scale - the scale denominator for the zoom level
- *  resolution - the map resolution when the zoom level is active.
+/** api: constructor
+ *  .. class:: ScaleStore
+ *
+ *      A store that contains a cache of available zoom levels.  The store can
+ *      optionally be kept synchronized with an ``OpenLayers.Map`` or
+ *      :class:`GeoExt.MapPanel` object.
+ *
+ *      Records have the following fields:
+ *
+ *      * level - ``Number``  The zoom level.
+ *      * scale - ``Number`` The scale denominator.
+ *      * resolution - ``Number`` The map units per pixel.
  */
 GeoExt.data.ScaleStore = Ext.extend(Ext.data.Store, {
-    /**
-     * Property: map
-     * The OpenLayers.Map instance to which the store is bound, if any.
+
+    /** api: config[map]
+     *  ``OpenLayers.Map`` or :class:`GeoExt.MapPanel`
+     *  Optional map or map panel from which to derive scale values.
      */
     map: null,
 
-    /**
-     * Constructor: GeoExt.data.ScaleStore
-     * Construct a ScaleStore from a configuration.  The ScaleStore accepts some custom parameters 
-     * addition to the fields accepted by Ext.Store.
-     * Additional options:
-     * map - the GeoExt.MapPanel or OpenLayers.Map instance the store should stay sync'ed with
+    /** private: method[constructor]
+     *  Construct a ScaleStore from a configuration.  The ScaleStore accepts
+     *  some custom parameters addition to the fields accepted by Ext.Store.
      */
     constructor: function(config) {
         var map = (config.map instanceof GeoExt.MapPanel ? config.map.map : config.map);
@@ -40,18 +49,19 @@
 
         GeoExt.data.ScaleStore.superclass.constructor.call(this, config);
 
-        if (map) this.bind(map);
+        if (map) {
+            this.bind(map);
+        }
     },
 
-    /**
-     * APIMethod: bind
-     * Bind this store to a map; that is, maintain the zoom list in sync with the map's current 
-     * configuration.  If the map does not currently have a set scale list, then the store will 
-     * remain empty until the map is configured with one.
-     *
-     * Parameters: 
-     * map - the GeoExt.MapPanel or OpenLayers.Map to which we should bind
-     * options - additional parameters for the bind operation (optional, currently unused)
+    /** api: method[bind]
+     *  :param map: :class:`GeoExt.MapPanel` or ``OpenLayers.Map`` Panel or map
+     *      to which we should bind.
+     *  
+     *  Bind this store to a map; that is, maintain the zoom list in sync with
+     *  the map's current configuration.  If the map does not currently have a
+     *  set scale list, then the store will remain empty until the map is
+     *  configured with one.
      */
     bind: function(map, options) {
         this.map = (map instanceof GeoExt.MapPanel ? map.map : map);
@@ -63,10 +73,10 @@
         }
     },
 
-    /**
-     * APIMethod: unbind
-     * Un-bind this store from the map to which it is currently bound.  The currently stored zoom 
-     * levels will remain, but no further changes from the map will affect it.
+    /** api: method[unbind]
+     *  Un-bind this store from the map to which it is currently bound.  The
+     *  currently stored zoom levels will remain, but no further changes from
+     *  the map will affect it.
      */
     unbind: function() {
         if (this.map) {
@@ -76,13 +86,12 @@
         }
     },
 
-    /**
-     * Method: populateOnAdd
-     * This method handles the case where we have bind() called on a not-fully-configured map so 
-     * that the zoom levels can be detected when a baselayer is finally added.
-     *
-     * Parameters:
-     * evt - the OpenLayers event
+    /** private: method[populateOnAdd]
+     *  :param evt: ``Object``
+     *  
+     *  This method handles the case where we have bind() called on a
+     *  not-fully-configured map so that the zoom levels can be detected when a
+     *  baselayer is finally added.
      */
     populateOnAdd: function(evt) {
         if (evt.layer.isBaseLayer) {
@@ -91,20 +100,22 @@
         }
     },
 
-    /**
-     * Method: populateFromMap
-     * This method actually loads the zoom level information from the OpenLayers.Map and converts 
-     * it to Ext Records.
+    /** private: method[populateFromMap]
+     *  This method actually loads the zoom level information from the
+     *  OpenLayers.Map and converts it to Ext Records.
      */
     populateFromMap: function() {
         var zooms = [];
+        var resolutions = this.map.baseLayer.resolutions;
+        var units = this.map.baseLayer.units;
 
-        for (var i = this.map.numZoomLevels-1; i > 0; i--) { 
-            var res = this.map.getResolutionForZoom(i);
-            var units = this.map.baseLayer.units;
-            var scale = OpenLayers.Util.getScaleFromResolution(res, units);
-
-            zooms.push({level: i, resolution: res, scale: scale});
+        for (var i=resolutions.length-1; i >= 0; i--) {
+            var res = resolutions[i];
+            zooms.push({
+                level: i,
+                resolution: res,
+                scale: OpenLayers.Util.getScaleFromResolution(res, units)
+            });
         }
 
         this.loadData(zooms);

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesReader.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/data/LayerRecord.js
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WFSCapabilitiesReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: WFSCapabilitiesReader(meta, recordType)
+ *  
+ *      :param meta: ``Object`` Reader configuration.
+ *      :param recordType: ``Array | Ext.data.Record`` An array of field
+ *          configuration objects or a record object.  Default is
+ *          :class:`GeoExt.data.LayerRecord`.
+ *   
+ *      Data reader class to create an array of
+ *      :class:`GeoExt.data.LayerRecord` objects from a WFS GetCapabilities
+ *      response.
+ */
+GeoExt.data.WFSCapabilitiesReader = function(meta, recordType) {
+    meta = meta || {};
+    if(!meta.format) {
+        meta.format = new OpenLayers.Format.WFSCapabilities();
+    }
+    if(!(typeof recordType === "function")) {
+        recordType = GeoExt.data.LayerRecord.create(
+            recordType || meta.fields || [
+                {name: "name", type: "string"},
+                {name: "abstract", type: "string"}
+            ]
+        );
+    }
+    GeoExt.data.WFSCapabilitiesReader.superclass.constructor.call(
+        this, meta, recordType
+    );
+};
+
+Ext.extend(GeoExt.data.WFSCapabilitiesReader, Ext.data.DataReader, {
+
+    /** private: method[read]
+     *  :param request: ``Object`` The XHR object which contains the parsed XML
+     *      document.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     */
+    read: function(request) {
+        var data = request.responseXML;
+        if(!data || !data.documentElement) {
+            data = request.responseText;
+        }
+        return this.readRecords(data);
+    },
+
+    /** private: method[readRecords]
+     *  :param data: ``DOMElement | String | Object`` A document element or XHR
+     *      response string.  As an alternative to fetching capabilities data
+     *      from a remote source, an object representing the capabilities can
+     *      be provided given that the structure mirrors that returned from the
+     *      capabilities parser.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     *  
+     *  Create a data block containing Ext.data.Records from an XML document.
+     */
+    readRecords: function(data) {
+        if(typeof data === "string" || data.nodeType) {
+            data = this.meta.format.read(data);
+        }
+        var records = [], layer, l, parts, layerOptions, protocolOptions;
+        var featureTypes = data.featureTypeList.featureTypes;
+        var protocolDefaults = {
+            url: data.capability.request.getfeature.href.post
+        };
+        for(var i=0, len=featureTypes.length; i<len; i++) {
+            layer = featureTypes[i];
+            if(layer.name) {
+                // create protocol
+                parts = layer.name.split(":");
+                if (parts.length > 1) {
+                    protocolOptions = {
+                        featureType: parts[1],
+                        featurePrefix: parts[0]
+                    };
+                } else {
+                    protocolOptions = {
+                        featureType: parts[0],
+                        featurePrefix: null
+                    };
+                }
+                if(this.meta.protocolOptions) {
+                    Ext.apply(protocolOptions, this.meta.protocolOptions, 
+                        protocolDefaults);
+                } else {
+                    Ext.apply(protocolOptions, {}, protocolDefaults);
+                }
+                // create vector layer with protocol
+                layerOptions = {
+                    protocol: new OpenLayers.Protocol.WFS(protocolOptions),
+                    strategies: [new OpenLayers.Strategy.Fixed()]
+                };
+                if(this.meta.layerOptions) {
+                    Ext.apply(layerOptions, this.meta.layerOptions);
+                }
+                l = new OpenLayers.Layer.Vector(
+                    layer.title || layer.name, 
+                    layerOptions
+                );
+                records.push(new this.recordType(Ext.apply(layer, {
+                    layer: l
+                }), l.id));
+            }
+        }
+        return {
+            totalRecords: records.length,
+            success: true,
+            records: records
+        };
+    }
+});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesStore.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesStore.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesStore.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WFSCapabilitiesStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/*
+ * @include GeoExt/data/WFSCapabilitiesReader.js
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WFSCapabilitiesStore
+ *  base_link = `Ext.data.Store <http://extjs.com/deploy/dev/docs/?class=Ext.data.Store>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: WFSCapabilitiesStore
+ *  
+ *      Small helper class to make creating stores for remote WFS layer data
+ *      easier.  The store is pre-configured with a built-in
+ *      ``Ext.data.HttpProxy`` and :class:`GeoExt.data.WFSCapabilitiesReader`.
+ *      The proxy is configured to allow caching and issues requests via GET.
+ *      If you require some other proxy/reader combination then you'll have to
+ *      configure this with your own proxy or create a basic
+ *      :class:`GeoExt.data.LayerStore` and configure as needed.
+ */
+
+/** api: config[format]
+ *  ``OpenLayers.Format``
+ *  A parser for transforming the XHR response into an array of objects
+ *  representing attributes.  Defaults to an ``OpenLayers.Format.WFSCapabilities``
+ *  parser.
+ */
+
+/** api: config[fields]
+ *  ``Array | Function``
+ *  Either an Array of field definition objects as passed to
+ *  ``Ext.data.Record.create``, or a record constructor created using
+ *  ``Ext.data.Record.create``.  Defaults to ``["name", "type"]``. 
+ */
+
+GeoExt.data.WFSCapabilitiesStore = function(c) {
+    c = c || {};
+    GeoExt.data.WFSCapabilitiesStore.superclass.constructor.call(
+        this,
+        Ext.apply(c, {
+            proxy: c.proxy || (!c.data ?
+                new Ext.data.HttpProxy({url: c.url, disableCaching: false, method: "GET"}) :
+                undefined
+            ),
+            reader: new GeoExt.data.WFSCapabilitiesReader(
+                c, c.fields
+            )
+        })
+    );
+};
+Ext.extend(GeoExt.data.WFSCapabilitiesStore, Ext.data.Store);

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMCReader.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/WMCReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMCReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMCReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/data/LayerRecord.js
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMCReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: WMCReader(meta, recordType)
+ *  
+ *      :param meta: ``Object`` Reader configuration.
+ *      :param recordType: ``Array | Ext.data.Record`` An array of field
+ *          configuration objects or a record object.  Default is
+ *          :class:`GeoExt.data.LayerRecord`.
+ *   
+ *      Data reader class to create an array of
+ *      :class:`GeoExt.data.LayerRecord` objects from a WMS GetCapabilities
+ *      response.
+ */
+GeoExt.data.WMCReader = function(meta, recordType) {
+    meta = meta || {};
+    if(!meta.format) {
+        meta.format = new OpenLayers.Format.WMC();
+    }
+    if(!(typeof recordType === "function")) {
+        recordType = GeoExt.data.LayerRecord.create(
+            recordType || meta.fields || [
+                // give only non-OpenLayers fields as default recordType
+                {name: "abstract", type: "string"},
+                {name: "metadataURL", type: "string"},
+                {name: "queryable", type: "boolean"},
+                {name: "formats"}, // array
+                {name: "styles"} // array
+            ]
+        );
+    }
+    GeoExt.data.WMCReader.superclass.constructor.call(
+        this, meta, recordType
+    );
+};
+
+Ext.extend(GeoExt.data.WMCReader, Ext.data.DataReader, {
+
+    /** private: method[read]
+     *  :param request: ``Object`` The XHR object which contains the parsed XML
+     *      document.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     */
+    read: function(request) {
+        var data = request.responseXML;
+        if(!data || !data.documentElement) {
+            data = request.responseText;
+        }
+        return this.readRecords(data);
+    },
+
+    /** private: method[readRecords]
+     *  :param data: ``DOMElement | String | Object`` A document element or XHR
+     *      response string.  As an alternative to fetching capabilities data
+     *      from a remote source, an object representing the capabilities can
+     *      be provided given that the structure mirrors that returned from the
+     *      capabilities parser.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     *  
+     *  Create a data block containing Ext.data.Records from an XML document.
+     */
+    readRecords: function(data) {
+        var format = this.meta.format;
+        if(typeof data === "string" || data.nodeType) {
+            data = format.read(data);
+        }
+        var layersContext = data ? data.layersContext : undefined;
+        var records = [];        
+
+        if(layersContext) {
+            var recordType = this.recordType, fields = recordType.prototype.fields;
+            var i, lenI, j, lenJ, layerContext, values, field, v;
+            for (i = 0, lenI = layersContext.length; i < lenI; i++) {
+                layerContext = layersContext[i];
+                values = {};
+                for(j = 0, lenJ = fields.length; j < lenJ; j++){
+                    field = fields.items[j];
+                    v = layerContext[field.mapping || field.name] ||
+                        field.defaultValue;
+                    v = field.convert(v);
+                    values[field.name] = v;
+                }
+                values.layer = format.getLayerFromContext(layerContext);
+                records.push(new this.recordType(values, values.layer.id));
+            }
+        }
+        
+        return {
+            totalRecords: records.length,
+            success: true,
+            records: records
+        };
+
+    }
+
+});
+

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesReader.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesReader.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,50 +1,74 @@
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * Copyright (c) 2008-2009 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.data");
-
 /**
- * Class: GeoExt.data.WMSCapabilitiesReader
- * Data reader class to provide an array of {Ext.data.Record} objects given
- *     a WMS GetCapabilities response for use by an {Ext.data.Store}
- *     object.
- *
- * Extends:
- *  - Ext.data.DataReader
+ * @include GeoExt/data/LayerRecord.js
  */
 
-/**
- * Constructor: GeoExt.data.WMSCapabilitiesReader
- * Create a new attributes reader object.
- *
- * Parameters:
- * meta - {Object} Reader configuration.
- * recordType - {Array | Ext.data.Record} An array of field configuration
- *     objects or a record object.  Default is <GeoExt.data.LayerRecord>.
- *
- * Configuration options (meta properties):
- * format - {OpenLayers.Format} A parser for transforming the XHR response
- *     into an array of objects representing attributes.  Defaults to
- *     an {OpenLayers.Format.WMSCapabilities} parser.
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMSCapabilitiesReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
  */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: WMSCapabilitiesReader(meta, recordType)
+ *  
+ *      :param meta: ``Object`` Reader configuration from which 
+ *          ``layerOptions`` is an optional object passed as default options
+ *          to the ``OpenLayers.Layer.WMS`` constructor.
+ *      :param recordType: ``Array | Ext.data.Record`` An array of field
+ *          configuration objects or a record object.  Default is
+ *          :class:`GeoExt.data.LayerRecord` with the following fields:
+ *          name, title, abstract, queryable, opaque, noSubsets, cascaded,
+ *          fixedWidth, fixedHeight, minScale, maxScale, prefix, formats,
+ *          styles, srs, dimensions, bbox, llbbox, attribution, keywords,
+ *          identifiers, authorityURLs, metadataURLs.
+ *          The type of these fields is the same as for the matching fields in
+ *          the object returned from
+ *          ``OpenLayers.Format.WMSCapabilities::read()``.
+ *   
+ *      Data reader class to create an array of
+ *      :class:`GeoExt.data.LayerRecord` objects from a WMS GetCapabilities
+ *      response.
+ */
 GeoExt.data.WMSCapabilitiesReader = function(meta, recordType) {
     meta = meta || {};
     if(!meta.format) {
         meta.format = new OpenLayers.Format.WMSCapabilities();
     }
-    if(!(typeof recordType === "function")) {
+    if(typeof recordType !== "function") {
         recordType = GeoExt.data.LayerRecord.create(
             recordType || meta.fields || [
                 {name: "name", type: "string"},
+                {name: "title", type: "string"},
                 {name: "abstract", type: "string"},
                 {name: "queryable", type: "boolean"},
-                {name: "formats"},
-                {name: "styles"},
-                {name: "llbbox"},
-                {name: "minScale"},
-                {name: "maxScale"},
-                {name: "prefix"}
+                {name: "opaque", type: "boolean"},
+                {name: "noSubsets", type: "boolean"},
+                {name: "cascaded", type: "int"},
+                {name: "fixedWidth", type: "int"},
+                {name: "fixedHeight", type: "int"},
+                {name: "minScale", type: "float"},
+                {name: "maxScale", type: "float"},
+                {name: "prefix", type: "string"},
+                {name: "formats"}, // array
+                {name: "styles"}, // array
+                {name: "srs"}, // object
+                {name: "dimensions"}, // object
+                {name: "bbox"}, // object
+                {name: "llbbox"}, // array
+                {name: "attribution"}, // object
+                {name: "keywords"}, // array
+                {name: "identifiers"}, // object
+                {name: "authorityURLs"}, // object
+                {name: "metadataURLs"} // array
             ]
         );
     }
@@ -55,19 +79,20 @@
 
 Ext.extend(GeoExt.data.WMSCapabilitiesReader, Ext.data.DataReader, {
 
-    /**
-     * Method: read
-     * This method is only used by a DataProxy which has retrieved data from a
-     *     remote server.
-     *
-     * Parameters:
-     * request - {Object} The XHR object which contains the parsed XML
-     *     document.
-     * 
-     * Returns:
-     * {Object} A data block which is used by an {Ext.data.Store} as a cache
-     *     of Ext.data.Records.
+
+    /** api: config[attributionCls]
+     *  ``String`` CSS class name for the attribution DOM elements.
+     *  Element class names append "-link", "-image", and "-title" as
+     *  appropriate.  Default is "gx-attribution".
      */
+    attributionCls: "gx-attribution",
+
+    /** private: method[read]
+     *  :param request: ``Object`` The XHR object which contains the parsed XML
+     *      document.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     */
     read: function(request) {
         var data = request.responseXML;
         if(!data || !data.documentElement) {
@@ -75,46 +100,160 @@
         }
         return this.readRecords(data);
     },
+    
+    /** private: method[serviceExceptionFormat]
+     *  :param formats: ``Array`` An array of service exception format strings.
+     *  :return: ``String`` The (supposedly) best service exception format.
+     */
+    serviceExceptionFormat: function(formats) {
+        if (OpenLayers.Util.indexOf(formats, 
+            "application/vnd.ogc.se_inimage")>-1) {
+            return "application/vnd.ogc.se_inimage";
+        }
+        if (OpenLayers.Util.indexOf(formats, 
+            "application/vnd.ogc.se_xml")>-1) {
+            return "application/vnd.ogc.se_xml";
+        }
+        return formats[0];
+    },
+    
+    /** private: method[imageFormat]
+     *  :param layer: ``Object`` The layer's capabilities object.
+     *  :return: ``String`` The (supposedly) best mime type for requesting 
+     *      tiles.
+     */
+    imageFormat: function(layer) {
+        var formats = layer.formats;
+        if (layer.opaque && 
+            OpenLayers.Util.indexOf(formats, "image/jpeg")>-1) {
+            return "image/jpeg";
+        }
+        if (OpenLayers.Util.indexOf(formats, "image/png")>-1) {
+            return "image/png";
+        }
+        if (OpenLayers.Util.indexOf(formats, "image/png; mode=24bit")>-1) {
+            return "image/png; mode=24bit";
+        }
+        if (OpenLayers.Util.indexOf(formats, "image/gif")>-1) {
+            return "image/gif";
+        }
+        return formats[0];
+    },
 
-    /**
-     * Method: readRecords
-     * Create a data block containing Ext.data.Records from an XML document.
-     *
-     * Parameters:
-     * data - {DOMElement | Strint | Object} A document element or XHR response
-     *     string.  As an alternative to fetching capabilities data from a remote
-     *     source, an object representing the capabilities can be provided given
-     *     that the structure mirrors that returned from the capabilities parser.
-     *
-     * Returns:
-     * {Object} A data block which is used by an {Ext.data.Store} as a cache of
-     *     Ext.data.Records.
+    /** private: method[imageTransparent]
+     *  :param layer: ``Object`` The layer's capabilities object.
+     *  :return: ``Boolean`` The TRANSPARENT param.
      */
+    imageTransparent: function(layer) {
+        return layer.opaque == undefined || !layer.opaque;
+    },
+
+    /** private: method[readRecords]
+     *  :param data: ``DOMElement | String | Object`` A document element or XHR
+     *      response string.  As an alternative to fetching capabilities data
+     *      from a remote source, an object representing the capabilities can
+     *      be provided given that the structure mirrors that returned from the
+     *      capabilities parser.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     *  
+     *  Create a data block containing Ext.data.Records from an XML document.
+     */
     readRecords: function(data) {
-        
         if(typeof data === "string" || data.nodeType) {
             data = this.meta.format.read(data);
         }
-        var url = data.capability.request.getmap.href;
-        var records = [], layer;        
-        for(var i=0, len=data.capability.layers.length; i<len; i++){
-            layer = data.capability.layers[i];
-            if(layer.name) {
-                records.push(new this.recordType(Ext.apply(layer, {
-                    layer: new OpenLayers.Layer.WMS(
-                        layer.title || layer.name,
-                        url,
-                        {layers: layer.name}
-                    )
-                })));
+        var version = data.version;
+        var capability = data.capability || {};
+        var url = capability.request && capability.request.getmap &&
+            capability.request.getmap.href; 
+        var layers = capability.layers; 
+        var formats = capability.exception ? capability.exception.formats : [];
+        var exceptions = this.serviceExceptionFormat(formats);
+        var records = [];
+        
+        if(url && layers) {
+            var fields = this.recordType.prototype.fields; 
+            var layer, values, options, field, v;
+
+            for(var i=0, lenI=layers.length; i<lenI; i++){
+                layer = layers[i];
+                if(layer.name) {
+                    values = {};
+                    for(var j=0, lenJ=fields.length; j<lenJ; j++) {
+                        field = fields.items[j];
+                        v = layer[field.mapping || field.name] ||
+                        field.defaultValue;
+                        v = field.convert(v);
+                        values[field.name] = v;
+                    }
+                    options = {
+                        attribution: layer.attribution ?
+                            this.attributionMarkup(layer.attribution) :
+                            undefined,
+                        minScale: layer.minScale,
+                        maxScale: layer.maxScale
+                    };
+                    if(this.meta.layerOptions) {
+                        Ext.apply(options, this.meta.layerOptions);
+                    }
+                    values.layer = new OpenLayers.Layer.WMS(
+                        layer.title || layer.name, url, {
+                            layers: layer.name,
+                            exceptions: exceptions,
+                            format: this.imageFormat(layer),
+                            transparent: this.imageTransparent(layer),
+                            version: version
+                        }, options
+                    );
+                    records.push(new this.recordType(values, values.layer.id));
+                }
             }
         }
-
+        
         return {
             totalRecords: records.length,
             success: true,
             records: records
         };
 
+    },
+
+    /** private: method[attributionMarkup]
+     *  :param attribution: ``Object`` The attribution property of the layer
+     *      object as parsed from a WMS Capabilities document
+     *  :return: ``String`` HTML markup to display attribution
+     *      information.
+     *  
+     *  Generates attribution markup using the Attribution metadata
+     *      from WMS Capabilities
+     */
+    attributionMarkup : function(attribution){
+        var markup = [];
+        
+        if (attribution.logo){
+            markup.push("<img class='"+this.attributionCls+"-image' "
+                        + "src='" + attribution.logo.href + "' />");
+        }
+        
+        if (attribution.title) {
+            markup.push("<span class='"+ this.attributionCls + "-title'>"
+                        + attribution.title
+                        + "</span>");
+        }
+        
+        if(attribution.href){
+            for(var i = 0; i < markup.length; i++){
+                markup[i] = "<a class='"
+              + this.attributionCls + "-link' "
+                    + "href="
+                    + attribution.href
+                    + ">"
+                    + markup[i]
+                    + "</a>";
+            }
+        }
+
+        return markup.join(" ");
     }
 });

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesStore.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesStore.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSCapabilitiesStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,42 +1,50 @@
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * Copyright (c) 2008-2009 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.
  */
 
 /**
  * @include GeoExt/data/WMSCapabilitiesReader.js
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMSCapabilitiesStore
+ *  base_link = `Ext.data.Store <http://extjs.com/deploy/dev/docs/?class=Ext.data.Store>`_
+ */
 Ext.namespace("GeoExt.data");
-/**
- * Class: GeoExt.data.WMSCapabilitiesStore
- * Small helper class to make creating stores for remote WMS layer data easier.
- *     WMSCapabilitiesStore is pre-configured with a built-in
- *     {Ext.data.HttpProxy} and {GeoExt.data.WMSCapabilitiesReader}.  The
- *     HttpProxy is configured to allow caching (disableCaching: false) and uses
- *     GET.  If you require some other proxy/reader combination then you'll have
- *     to configure this with your own proxy or create a basic
- *     GeoExt.data.LayerStore and configure as needed.
- *
- * Extends:
- *  - GeoExt.data.Store
+
+/** api: constructor
+ *  .. class:: WMSCapabilitiesStore
+ *  
+ *      Small helper class to make creating stores for remote WMS layer data
+ *      easier.  The store is pre-configured with a built-in
+ *      ``Ext.data.HttpProxy`` and :class:`GeoExt.data.WMSCapabilitiesReader`.
+ *      The proxy is configured to allow caching and issues requests via GET.
+ *      If you require some other proxy/reader combination then you'll have to
+ *      configure this with your own proxy or create a basic
+ *      :class:`GeoExt.data.LayerStore` and configure as needed.
  */
 
-/**
- * Constructor: GeoExt.data.WMSCapabilitiesStore
- * Create a new WMS capabilities store object.
- *
- * Parameters:
- * config - {Object} Store configuration.
- *
- * Configuration options:
- * format - {OpenLayers.Format} A parser for transforming the XHR response into
- *     an array of objects representing attributes.  Defaults to an
- *     {OpenLayers.Format.WMSCapabilities} parser.
- * fields - {Array | Function} Either an Array of field definition objects as
- *     passed to Ext.data.Record.create, or a Record constructor created using
- *     Ext.data.Record.create.  Defaults to ["name", "type"]. 
+/** api: config[format]
+ *  ``OpenLayers.Format``
+ *  A parser for transforming the XHR response into an array of objects
+ *  representing attributes.  Defaults to an ``OpenLayers.Format.WMSCapabilities``
+ *  parser.
  */
+
+/** api: config[fields]
+ *  ``Array | Function``
+ *  Either an Array of field definition objects as passed to
+ *  ``Ext.data.Record.create``, or a record constructor created using
+ *  ``Ext.data.Record.create``.  Defaults to ``["name", "type"]``. 
+ */
+
 GeoExt.data.WMSCapabilitiesStore = function(c) {
+    c = c || {};
     GeoExt.data.WMSCapabilitiesStore.superclass.constructor.call(
         this,
         Ext.apply(c, {

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerReader.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMSDescribeLayerReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: WMSDescribeLayerReader(meta, recordType)
+ *  
+ *      :param meta: ``Object`` Reader configuration.
+ *      :param recordType: ``Array | Ext.data.Record`` An array of field
+ *          configuration objects or a record object.  Default has
+ *          fields for owsType, owsURL, and typeName.
+ *   
+ *      Data reader class to create an array of
+ *      layer description objects from a WMS DescribeLayer
+ *      response.
+ */
+GeoExt.data.WMSDescribeLayerReader = function(meta, recordType) {
+    meta = meta || {};
+    if(!meta.format) {
+        meta.format = new OpenLayers.Format.WMSDescribeLayer();
+    }
+    if(!(typeof recordType === "function")) {
+        recordType = Ext.data.Record.create(
+            recordType || meta.fields || [
+                {name: "owsType", type: "string"},
+                {name: "owsURL", type: "string"},
+                {name: "typeName", type: "string"}
+            ]
+        );
+    }
+    GeoExt.data.WMSDescribeLayerReader.superclass.constructor.call(
+        this, meta, recordType
+    );
+};
+
+Ext.extend(GeoExt.data.WMSDescribeLayerReader, Ext.data.DataReader, {
+
+    /** private: method[read]
+     *  :param request: ``Object`` The XHR object which contains the parsed XML
+     *      document.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     */
+    read: function(request) {
+        var data = request.responseXML;
+        if(!data || !data.documentElement) {
+            data = request.responseText;
+        }
+        return this.readRecords(data);
+    },
+
+    /** private: method[readRecords]
+     *  :param data: ``DOMElement | Strint | Object`` A document element or XHR
+     *      response string.  As an alternative to fetching layer description data
+     *      from a remote source, an object representing the layer descriptions can
+     *      be provided given that the structure mirrors that returned from the
+     *      layer description parser.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     *  
+     *  Create a data block containing Ext.data.Records from an XML document.
+     */
+    readRecords: function(data) {
+        
+        if(typeof data === "string" || data.nodeType) {
+            data = this.meta.format.read(data);
+        }
+        var records = [], description;        
+        for(var i=0, len=data.length; i<len; i++){
+            description = data[i];
+            if(description) {
+                records.push(new this.recordType(description));
+            }
+        }
+
+        return {
+            totalRecords: records.length,
+            success: true,
+            records: records
+        };
+
+    }
+});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerStore.js (from rev 1504, core/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerStore.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerStore.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/data/WMSDescribeLayerStore.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,61 @@
+/**x
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/data/WMSDescribeLayerReader.js
+ */
+
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMSDescribeLayerStore
+ *  base_link = `Ext.data.DataStore <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataStore>`_
+ */
+Ext.namespace("GeoExt.data");
+
+/** api: constructor
+ *  .. class:: WMSDescribeLayerStore
+ *  
+ *      Small helper class to make creating stores for remote WMS layer description
+ *      easier.  The store is pre-configured with a built-in
+ *      ``Ext.data.HttpProxy`` and :class:`GeoExt.data.WMSDescribeLayerReader`.
+ *      The proxy is configured to allow caching and issues requests via GET.
+ *      If you require some other proxy/reader combination then you'll have to
+ *      configure this with your own proxy or create a basic
+ *      store and configure as needed.
+ */
+
+/** api: config[format]
+ *  ``OpenLayers.Format``
+ *  A parser for transforming the XHR response into an array of objects
+ *  representing attributes.  Defaults to an ``OpenLayers.Format.WMSDescribeLayer``
+ *  parser.
+ */
+
+/** api: config[fields]
+ *  ``Array | Function``
+ *  Either an Array of field definition objects as passed to
+ *  ``Ext.data.Record.create``, or a record constructor created using
+ *  ``Ext.data.Record.create``.  Defaults to ``["name", "type"]``. 
+ */
+
+GeoExt.data.WMSDescribeLayerStore = function(c) {
+    c = c || {};
+    GeoExt.data.WMSDescribeLayerStore.superclass.constructor.call(
+        this,
+        Ext.apply(c, {
+            proxy: c.proxy || (!c.data ?
+                new Ext.data.HttpProxy({url: c.url, disableCaching: false, method: "GET"}) :
+                undefined
+            ),
+            reader: new GeoExt.data.WMSDescribeLayerReader(
+                c, c.fields
+            )
+        })
+    );
+};
+Ext.extend(GeoExt.data.WMSDescribeLayerStore, Ext.data.Store);

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Action.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/Action.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Action.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Action.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = Action
+ *  base_link = `Ext.Action <http://extjs.com/deploy/dev/docs/?class=Ext.Action>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to create a toolbar with an OpenLayers control into it.
+ * 
+ *  .. code-block:: javascript
+ *  
+ *      var action = new GeoExt.Action({
+ *          text: "max extent",
+ *          control: new OpenLayers.Control.ZoomToMaxExtent(),
+ *          map: map
+ *      });
+ *      var toolbar = new Ext.Toolbar([action]);
+ */
+
+/** api: constructor
+ *  .. class:: Action(config)
+ *  
+ *      Create a GeoExt.Action instance. A GeoExt.Action is created
+ *      to insert an OpenLayers control in a toolbar as a button or
+ *      in a menu as a menu item. A GeoExt.Action instance can be
+ *      used like a regular Ext.Action, look at the Ext.Action API
+ *      doc for more detail.
+ */
+GeoExt.Action = Ext.extend(Ext.Action, {
+
+    /** api: config[control]
+     *  ``OpenLayers.Control`` The OpenLayers control wrapped in this action.
+     */
+    control: null,
+
+    /** api: config[map]
+     *  ``OpenLayers.Map`` The OpenLayers map that the control should be added
+     *  to.  For controls that don't need to be added to a map or have already
+     *  been added to one, this config property may be omitted.
+     */
+    map: null,
+
+    /** private: property[uScope]
+     *  ``Object`` The user-provided scope, used when calling uHandler,
+     *  uToggleHandler, and uCheckHandler.
+     */
+    uScope: null,
+
+    /** private: property[uHandler]
+     *  ``Function`` References the function the user passes through
+     *  the "handler" property.
+     */
+    uHandler: null,
+
+    /** private: property[uToggleHandler]
+     *  ``Function`` References the function the user passes through
+     *  the "toggleHandler" property.
+     */
+    uToggleHandler: null,
+
+    /** private: property[uCheckHandler]
+     *  ``Function`` References the function the user passes through
+     *  the "checkHandler" property.
+     */
+    uCheckHandler: null,
+
+    /** private */
+    constructor: function(config) {
+        
+        // store the user scope and handlers
+        this.uScope = config.scope;
+        this.uHandler = config.handler;
+        this.uToggleHandler = config.toggleHandler;
+        this.uCheckHandler = config.checkHandler;
+
+        config.scope = this;
+        config.handler = this.pHandler;
+        config.toggleHandler = this.pToggleHandler;
+        config.checkHandler = this.pCheckHandler;
+
+        // set control in the instance, the Ext.Action
+        // constructor won't do it for us
+        var ctrl = this.control = config.control;
+        delete config.control;
+
+        // register "activate" and "deactivate" listeners
+        // on the control
+        if(ctrl) {
+            // If map is provided in config, add control to map.
+            if(config.map) {
+                config.map.addControl(ctrl);
+                delete config.map;
+            }
+            if((config.pressed || config.checked) && ctrl.map) {
+                ctrl.activate();
+            }
+            ctrl.events.on({
+                activate: this.onCtrlActivate,
+                deactivate: this.onCtrlDeactivate,
+                scope: this
+            });
+        }
+
+        arguments.callee.superclass.constructor.call(this, config);
+    },
+
+    /** private: method[pHandler]
+     *  :param cmp: ``Ext.Component`` The component that triggers the handler.
+     *
+     *  The private handler.
+     */
+    pHandler: function(cmp) {
+        var ctrl = this.control;
+        if(ctrl &&
+           ctrl.type == OpenLayers.Control.TYPE_BUTTON) {
+            ctrl.trigger();
+        }
+        if(this.uHandler) {
+            this.uHandler.apply(this.uScope, arguments);
+        }
+    },
+
+    /** private: method[pTogleHandler]
+     *  :param cmp: ``Ext.Component`` The component that triggers the toggle handler.
+     *  :param state: ``Boolean`` The state of the toggle.
+     *
+     *  The private toggle handler.
+     */
+    pToggleHandler: function(cmp, state) {
+        this.changeControlState(state);
+        if(this.uToggleHandler) {
+            this.uToggleHandler.apply(this.uScope, arguments);
+        }
+    },
+
+    /** private: method[pCheckHandler]
+     *  :param cmp: ``Ext.Component`` The component that triggers the check handler.
+     *  :param state: ``Boolean`` The state of the toggle.
+     *
+     *  The private check handler.
+     */
+    pCheckHandler: function(cmp, state) {
+        this.changeControlState(state);
+        if(this.uCheckHandler) {
+            this.uCheckHandler.apply(this.uScope, arguments);
+        }
+    },
+
+    /** private: method[changeControlState]
+     *  :param state: ``Boolean`` The state of the toggle.
+     *
+     *  Change the control state depending on the state boolean.
+     */
+    changeControlState: function(state) {
+        if(state) {
+            if(!this._activating) {
+                this._activating = true;
+                this.control.activate();
+                this._activating = false;
+            }
+        } else {
+            if(!this._deactivating) {
+                this._deactivating = true;
+                this.control.deactivate();
+                this._deactivating = false;
+            }
+        }
+    },
+
+    /** private: method[onCtrlActivate]
+     *  
+     *  Called when this action's control is activated.
+     */
+    onCtrlActivate: function() {
+        var ctrl = this.control;
+        if(ctrl.type == OpenLayers.Control.TYPE_BUTTON) {
+            this.enable();
+        } else {
+            // deal with buttons
+            this.safeCallEach("toggle", [true]);
+            // deal with check items
+            this.safeCallEach("setChecked", [true]);
+        }
+    },
+
+    /** private: method[onCtrlDeactivate]
+     *  
+     *  Called when this action's control is deactivated.
+     */
+    onCtrlDeactivate: function() {
+        var ctrl = this.control;
+        if(ctrl.type == OpenLayers.Control.TYPE_BUTTON) {
+            this.disable();
+        } else {
+            // deal with buttons
+            this.safeCallEach("toggle", [false]);
+            // deal with check items
+            this.safeCallEach("setChecked", [false]);
+        }
+    },
+
+    /** private: method[safeCallEach]
+     *
+     */
+    safeCallEach: function(fnName, args) {
+        var cs = this.items;
+        for(var i = 0, len = cs.length; i < len; i++){
+            if(cs[i][fnName]) {
+                cs[i][fnName].apply(cs[i], args);
+            }
+        }
+    }
+});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/FeatureRenderer.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/FeatureRenderer.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/FeatureRenderer.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/FeatureRenderer.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,342 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = FeatureRenderer
+ *  base_link = `Ext.Panel <http://extjs.com/deploy/dev/docs/?class=Ext.BoxComponent>`_
+ */
+Ext.namespace('GeoExt');
+
+GeoExt.FeatureRenderer = Ext.extend(Ext.BoxComponent, {
+
+    /** api: config[feature]
+     *  ``OpenLayers.Feature.Vector``
+     *  Optional vector to be drawn.  If a feature is not provided, 
+     * ``symbolType`` should be specified.
+     */
+    feature: undefined,
+    
+    /** api: config[symbolizers]
+     *  ``Array(Object)``
+     *  An array of symbolizer objects (in painters order) for rendering a 
+     *  feature.  If no symbolizers are provided, the OpenLayers default will be
+     *  used.
+     */
+    symbolizers: [OpenLayers.Feature.Vector.style["default"]],
+
+    /** api: config[symbolType]
+     *  ``String``
+     *   One of ``"Point"``, ``"Line"``, or ``"Polygon"``.  If ``feature``
+     *   is provided, it will be preferred.  Default is ``"Point"``.
+     */
+    symbolType: "Point",
+
+    /** private: property[resolution]
+     *  ``Number``
+     *  The resolution for the renderer.
+     */
+    resolution: 1,
+    
+    /** private: property[minWidth]
+     *  ``Number``
+     */
+    minWidth: 20,
+
+    /** private: property[minHeight]
+     *  ``Number``
+     */
+    minHeight: 20,
+
+    /** private: property[renderers]
+     * ``Array(String)`` 
+     *  List of supported Renderer classes. Add to this list to add support for 
+     *  additional renderers. The first renderer in the list that returns 
+     *  ``true`` for the ``supported`` method will be used, if not defined in 
+     *  the ``renderer`` config property.
+     */
+    renderers: ["SVG", "VML", "Canvas"],
+
+    /** private: property[rendererOptions]
+     *  ``Object``
+     *  Options for the renderer. See ``OpenLayers.Renderer`` for supported 
+     *  options.
+     */
+    rendererOptions: null,
+    
+    /** private: property[pointFeature]
+     *  ``OpenLayers.Feature.Vector``
+     *  Feature with point geometry.
+     */
+    pointFeature: undefined,
+    
+    /** private: property[lineFeature]
+     *  ``OpenLayers.Feature.Vector`` 
+     *  Feature with LineString geometry.  Default zig-zag is provided.
+     */
+    lineFeature: undefined,
+
+    /** private: property[polygonFeature]
+     *  ``OpenLayers.Feature.Vector``
+     *   Feature with Polygon geometry.  Default is a soft cornered rectangle.
+     */
+    polygonFeature: undefined,
+    
+    /** private: property[renderer]
+     *  ``OpenLayers.Renderer``
+     */
+    renderer: null,
+
+    /** private: method[initComponent]
+     */
+    initComponent: function() {
+        GeoExt.FeatureRenderer.superclass.initComponent.apply(this, arguments);
+        Ext.applyIf(this, {
+            pointFeature: new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Point(0, 0)
+            ),
+            lineFeature: new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.LineString([
+                    new OpenLayers.Geometry.Point(-8, -3),
+                    new OpenLayers.Geometry.Point(-3, 3),
+                    new OpenLayers.Geometry.Point(3, -3),
+                    new OpenLayers.Geometry.Point(8, 3)
+                ])
+            ),
+            polygonFeature: new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Polygon([
+                    new OpenLayers.Geometry.LinearRing([
+                        new OpenLayers.Geometry.Point(-8, -4),
+                        new OpenLayers.Geometry.Point(-6, -6),
+                        new OpenLayers.Geometry.Point(6, -6),
+                        new OpenLayers.Geometry.Point(8, -4),
+                        new OpenLayers.Geometry.Point(8, 4),
+                        new OpenLayers.Geometry.Point(6, 6),
+                        new OpenLayers.Geometry.Point(-6, 6),
+                        new OpenLayers.Geometry.Point(-8, 4)
+                    ])
+                ])
+            )
+        });
+        if(!this.feature) {
+            this.setFeature(null, {draw: false});
+        }
+        this.addEvents(
+            /** api: event[click]
+             *  Fires when the feature is clicked on.
+             *
+             *  Listener arguments:
+             *  * renderer - ``GeoExt.FeatureRenderer`` This feature renderer.
+             */
+            "click"
+        );
+    },
+
+    /** private: method[initCustomEvents]
+     */
+    initCustomEvents: function() {
+        this.clearCustomEvents();
+        this.el.on("click", this.onClick, this);
+    },
+    
+    /** private: method[clearCustomEvents]
+     */
+    clearCustomEvents: function() {
+        if (this.el && this.el.removeAllListeners) {
+            this.el.removeAllListeners();            
+        }
+    },
+    
+    /** private: method[onClick]
+     */
+    onClick: function() {
+        this.fireEvent("click", this);
+    },
+
+    /** private: method[onRender]
+     */
+    onRender: function(ct, position) {
+        if(!this.el) {
+            this.el = document.createElement("div");
+            this.el.id = this.getId();
+        }
+        if(!this.renderer || !this.renderer.supported()) {  
+            this.assignRenderer();
+        }
+        // monkey-patch renderer so we always get a resolution
+        this.renderer.map = {
+            getResolution: (function() {
+                return this.resolution;
+            }).createDelegate(this)
+        };
+        
+        this.drawFeature();
+        GeoExt.FeatureRenderer.superclass.onRender.apply(this, arguments);
+    },
+
+    /** private: method[afterRender]
+     */
+    afterRender: function() {
+        GeoExt.FeatureRenderer.superclass.afterRender.apply(this, arguments);
+        this.initCustomEvents();
+    },
+
+    /** private: method[onResize]
+     */
+    onResize: function(w, h) {
+        this.setRendererDimensions();
+        GeoExt.FeatureRenderer.superclass.onResize.apply(this, arguments);
+    },
+    
+    /** private: method[setRendererDimensions]
+     */
+    setRendererDimensions: function() {
+        var gb = this.feature.geometry.getBounds();
+        var gw = gb.getWidth();
+        var gh = gb.getHeight();
+        /**
+         * Determine resolution based on the following rules:
+         * 1) always use value specified in config
+         * 2) if not specified, use max res based on width or height of element
+         * 3) if no width or height, assume a resolution of 1
+         */
+        var resolution = this.initialConfig.resolution;
+        if(!resolution) {
+            resolution = Math.max(gw / this.width || 0, gh / this.height || 0) || 1;
+        }
+        this.resolution = resolution;
+        // determine height and width of element
+        var width = Math.max(this.width || this.minWidth, gw / resolution);
+        var height = Math.max(this.height || this.minHeight, gh / resolution);
+        // determine bounds of renderer
+        var center = gb.getCenterPixel();
+        var bhalfw = width * resolution / 2;
+        var bhalfh = height * resolution / 2;
+        var bounds = new OpenLayers.Bounds(
+            center.x - bhalfw, center.y - bhalfh,
+            center.x + bhalfw, center.y + bhalfh
+        );
+        this.renderer.setSize(new OpenLayers.Size(Math.round(width), Math.round(height)));
+        this.renderer.setExtent(bounds, true);
+    },
+
+    /** private: method[assignRenderer]
+     *  Iterate through the available renderer implementations and selects 
+     *  and assign the first one whose ``supported`` method returns ``true``.
+     */
+    assignRenderer: function()  {
+        for(var i=0, len=this.renderers.length; i<len; ++i) {
+            var Renderer = OpenLayers.Renderer[this.renderers[i]];
+            if(Renderer && Renderer.prototype.supported()) {
+                this.renderer = new Renderer(
+                    this.el, this.rendererOptions
+                );
+                break;
+            }  
+        }  
+    },
+    
+    /** api: method[setSymbolizers]
+     *  :arg symbolizers: ``Array(Object)`` An array of symbolizers
+     *  :arg options: ``Object``
+     *
+     *  Update the symbolizers used to render the feature.
+     *
+     *  Valid options:
+     *  * draw - ``Boolean`` Draw the feature after setting it.  Default is ``true``.
+     */
+    setSymbolizers: function(symbolizers, options) {
+        this.symbolizers = symbolizers;
+        if(!options || options.draw) {
+            this.drawFeature();
+        }
+    },
+    
+    /** api: method[setSymbolType]
+     *  :arg type: ``String`` One of the ``symbolType`` strings.
+     *  :arg options: ``Object``
+     * 
+     *  Create a new feature based on the geometry type and render it.
+     *
+     *  Valid options:
+     *  * draw - ``Boolean`` Draw the feature after setting it.  Default is ``true``.
+     */
+    setSymbolType: function(type, options) {
+        this.symbolType = type;
+        this.setFeature(null, options);
+    },
+    
+    /** api: method[setFeature]
+     *  :arg feature: ``OpenLayers.Feature.Vector`` The feature to be rendered.  
+     *      If none is provided, one will be created based on ``symbolType``.
+     *  :arg options: ``Object``
+     *
+     *  Update the feature and redraw.
+     *
+     *  Valid options:
+     *  * draw - ``Boolean`` Draw the feature after setting it.  Default is ``true``.
+     */
+    setFeature: function(feature, options) {
+        this.feature = feature || this[this.symbolType.toLowerCase() + "Feature"];
+        if(!options || options.draw) {
+            this.drawFeature();
+        }
+    },
+
+    /** private: method[drawFeature]
+     *  Render the feature with the symbolizers.
+     */
+    drawFeature: function() {
+        this.renderer.clear();
+        this.setRendererDimensions();
+        for (var i=0, len=this.symbolizers.length; i<len; ++i) {
+            this.renderer.drawFeature(
+                this.feature.clone(),
+                Ext.apply({}, this.symbolizers[i])
+            );
+        }
+    },
+    
+    /** api: method[update]
+     *  :arg options: ``Object`` Object with properties to be updated.
+     * 
+     *  Update the ``symbolType`` or ``feature`` and ``symbolizer`` and redraw
+     *  the feature.
+     *
+     *  Valid options:
+     *  * feature - ``OpenLayers.Feature.Vector`` The new or updated feature.  
+     *      If provided, the feature gets precedence over ``symbolType``.
+     *  * symbolType - ``String`` One of the allowed ``symbolType`` values.
+     *  * symbolizers - ``Array(Object)`` An array of symbolizer objects.
+     */
+    update: function(options) {
+        options = options || {};
+        if(options.feature) {
+            this.setFeature(options.feature, {draw: false});
+        } else if(options.symbolType) {
+            this.setSymbolType(options.symbolType, {draw: false});
+        }
+        if(options.symbolizers) {
+            this.setSymbolizers(options.symbolizers, {draw: false});
+        }
+        this.drawFeature();
+    },
+
+    /** private: method[beforeDestroy]
+     *  Private method called during the destroy sequence.
+     */
+    beforeDestroy: function() {
+        this.clearCustomEvents();
+        if (this.renderer) {
+            this.renderer.destroy();
+        }
+    }
+    
+});
+
+Ext.reg('gx_renderer', GeoExt.FeatureRenderer);

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LayerOpacitySlider.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/LayerOpacitySlider.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LayerOpacitySlider.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LayerOpacitySlider.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,293 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ */
+
+/**
+ * @include GeoExt/widgets/tips/LayerOpacitySliderTip.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LayerOpacitySlider
+ *  base_link = `Ext.Slider <http://extjs.com/deploy/dev/docs/?class=Ext.Slider>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to render a slider outside the map viewport:
+ *
+ *  .. code-block:: javascript
+ *
+ *      var slider = new GeoExt.LayerOpacitySlider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          layer: layer
+ *      });
+ *
+ *  Sample code to add a slider to a map panel:
+ *
+ *  .. code-block:: javascript
+ *
+ *      var layer = new OpenLayers.Layer.WMS(
+ *          "Global Imagery",
+ *          "http://maps.opengeo.org/geowebcache/service/wms",
+ *          {layers: "bluemarble"}
+ *      );
+ *      var panel = new GeoExt.MapPanel({
+ *          renderTo: document.body,
+ *          height: 300,
+ *          width: 400,
+ *          map: {
+ *              controls: [new OpenLayers.Control.Navigation()]
+ *          },
+ *          layers: [layer],
+ *          extent: [-5, 35, 15, 55],
+ *          items: [{
+ *              xtype: "gx_opacityslider",
+ *              layer: layer,
+ *              aggressive: true,
+ *              vertical: true,
+ *              height: 100,
+ *              x: 10,
+ *              y: 20
+ *          }]
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: LayerOpacitySlider(config)
+ *
+ *      Create a slider for controlling a layer's opacity.
+ */
+GeoExt.LayerOpacitySlider = Ext.extend(Ext.Slider, {
+
+    /** api: config[layer]
+     *  ``OpenLayers.Layer`` or :class:`GeoExt.data.LayerRecord`
+     *  The layer this slider changes the opacity of. (required)
+     */
+    /** private: property[layer]
+     *  ``OpenLayers.Layer``
+     */
+    layer: null,
+
+    /** api: config[complementaryLayer]
+     *  ``OpenLayers.Layer`` or :class:`GeoExt.data.LayerRecord` 
+     *  If provided, a layer that will be made invisible (its visibility is
+     *  set to false) when the slider value is set to its max value. If this
+     *  slider is used to fade visibility between to layers, setting
+     *  ``complementaryLayer`` and ``changeVisibility`` will make sure that
+     *  only visible tiles are loaded when the slider is set to its min or max
+     *  value. (optional)
+     */
+    complementaryLayer: null,
+
+    /** api: config[delay]
+     *  ``Number`` Time in milliseconds before setting the opacity value to the
+     *  layer. If the value change again within that time, the original value
+     *  is not set. Only applicable if aggressive is true.
+     */
+    delay: 5,
+
+    /** api: config[changeVisibilityDelay]
+     *  ``Number`` Time in milliseconds before changing the layer's visibility.
+     *  If the value changes again within that time, the layer's visibility
+     *  change does not occur. Only applicable if changeVisibility is true.
+     *  Defaults to 5.
+     */
+    changeVisibilityDelay: 5,
+
+    /** api: config[aggressive]
+     *  ``Boolean``
+     *  If set to true, the opacity is changed as soon as the thumb is moved.
+     *  Otherwise when the thumb is released (default).
+     */
+    aggressive: false,
+
+    /** api: config[changeVisibility]
+     *  ``Boolean``
+     *  If set to true, the layer's visibility is handled by the
+     *  slider, the slider makes the layer invisible when its
+     *  value is changed to the min value, and makes the layer
+     *  visible again when its value goes from the min value
+     *  to some other value. The layer passed to the constructor
+     *  must be visible, as its visibility is fully handled by
+     *  the slider. Defaults to false.
+     */
+    changeVisibility: false,
+
+    /** api: config[value]
+     *  ``Number``
+     *  The value to initialize the slider with. This value is
+     *  taken into account only if the layer's opacity is null.
+     *  If the layer's opacity is null and this value is not
+     *  defined in the config object then the slider initializes
+     *  it to the max value.
+     */
+    value: null,
+
+    /** private: method[constructor]
+     *  Construct the component.
+     */
+    constructor: function(config) {
+        if (config.layer) {
+            if (config.layer instanceof OpenLayers.Layer) {
+                this.layer = config.layer;
+            } else if (config.layer instanceof GeoExt.data.LayerRecord) {
+                this.layer = config.layer.get('layer');
+            }
+
+            if (config.complementaryLayer instanceof OpenLayers.Layer) {
+                this.complementaryLayer = config.complementaryLayer;
+            } else if (config.complementaryLayer instanceof
+                       GeoExt.data.LayerRecord) {
+                this.complementaryLayer =
+                    config.complementaryLayer.get('layer');
+            }
+
+            delete config.layer;
+            delete config.complementaryLayer;
+        }
+        GeoExt.LayerOpacitySlider.superclass.constructor.call(this, config);
+    },
+
+    /** private: method[initComponent]
+     *  Initialize the component.
+     */
+    initComponent: function() {
+        // set the slider initial value
+        if (this.layer && this.layer.opacity !== null) {
+            this.value = parseInt(
+                this.layer.opacity * (this.maxValue - this.minValue)
+            );
+        } else if (this.value == null) {
+            this.value = this.maxValue;
+        }
+
+        GeoExt.LayerOpacitySlider.superclass.initComponent.call(this);
+
+        if (this.changeVisibility && this.layer &&
+            (this.layer.opacity == 0 || this.value == this.minValue)) {
+            this.layer.setVisibility(false);
+        }
+
+        if (this.complementaryLayer &&
+            ((this.layer && this.layer.opacity == 1) ||
+             (this.value == this.maxValue))) {
+            this.complementaryLayer.setVisibility(false);
+        }
+
+        if (this.aggressive === true) {
+            this.on('change', this.changeLayerOpacity, this, {
+                buffer: this.delay
+            });
+        } else {
+            this.on('changecomplete', this.changeLayerOpacity, this);
+        }
+
+        if (this.changeVisibility === true) {
+            this.on('change', this.changeLayerVisibility, this, {
+                buffer: this.changeVisibilityDelay
+            });
+        }
+
+        if (this.complementaryLayer) {
+            this.on('change', this.changeComplementaryLayerVisibility, this, {
+                buffer: this.changeVisibilityDelay
+            });
+        }
+    },
+
+    /** private: method[changeLayerOpacity]
+     *  :param slider: :class:`GeoExt.LayerOpacitySlider`
+     *  :param value: ``Number`` The slider value
+     *
+     *  Updates the ``OpenLayers.Layer`` opacity value.
+     */
+    changeLayerOpacity: function(slider, value) {
+        if (this.layer) {
+            this.layer.setOpacity(value / 100.0);
+        }
+    },
+
+    /** private: method[changeLayerVisibility]
+     *  :param slider: :class:`GeoExt.LayerOpacitySlider`
+     *  :param value: ``Number`` The slider value
+     *
+     *  Updates the ``OpenLayers.Layer`` visibility.
+     */
+    changeLayerVisibility: function(slider, value) {
+        var currentVisibility = this.layer.getVisibility();
+        if (value == this.minValue &&
+            currentVisibility === true) {
+            this.layer.setVisibility(false);
+        } else if (value > this.minValue &&
+                   currentVisibility == false) {
+            this.layer.setVisibility(true);
+        }
+    },
+
+    /** private: method[changeComplementaryLayerVisibility]
+     *  :param slider: :class:`GeoExt.LayerOpacitySlider`
+     *  :param value: ``Number`` The slider value
+     *
+     *  Updates the complementary ``OpenLayers.Layer`` visibility.
+     */
+    changeComplementaryLayerVisibility: function(slider, value) {
+        var currentVisibility = this.complementaryLayer.getVisibility();
+        if (value == this.maxValue &&
+            currentVisibility === true) {
+            this.complementaryLayer.setVisibility(false);
+        } else if (value < this.maxValue &&
+                   currentVisibility == false) {
+            this.complementaryLayer.setVisibility(true);
+        }
+    },
+
+    /** private: method[addToMapPanel]
+     *  :param panel: :class:`GeoExt.MapPanel`
+     *
+     *  Called by a MapPanel if this component is one of the items in the panel.
+     */
+    addToMapPanel: function(panel) {
+        this.on({
+            render: function() {
+                var el = this.getEl();
+                el.setStyle({
+                    position: "absolute",
+                    zIndex: panel.map.Z_INDEX_BASE.Control
+                });
+                el.on({
+                    mousedown: this.stopMouseEvents,
+                    click: this.stopMouseEvents
+                });
+            },
+            scope: this
+        });
+    },
+
+    /** private: method[removeFromMapPanel]
+     *  :param panel: :class:`GeoExt.MapPanel`
+     *
+     *  Called by a MapPanel if this component is one of the items in the panel.
+     */
+    removeFromMapPanel: function(panel) {
+        var el = this.getEl();
+        el.un({
+            mousedown: this.stopMouseEvents,
+            click: this.stopMouseEvents,
+            scope: this
+        });
+    },
+
+    /** private: method[stopMouseEvents]
+     *  :param e: ``Object``
+     */
+    stopMouseEvents: function(e) {
+        e.stopEvent();
+    }
+});
+
+/** api: xtype = gx_opacityslider */
+Ext.reg('gx_opacityslider', GeoExt.LayerOpacitySlider);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendImage.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendImage.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendImage.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,13 +1,15 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
 /** api: (define)
  *  module = GeoExt
  *  class = LegendImage
+ *  base_link = `Ext.BoxComponent <http://extjs.com/deploy/dev/docs/?class=Ext.BoxComponent>`_
  */
 
 Ext.namespace('GeoExt');
@@ -15,8 +17,8 @@
 /** api: constructor
  *  .. class:: LegendImage(config)
  *
- *  Show a legend image in a BoxComponent and make sure load errors are dealt
- *  with.
+ *      Show a legend image in a BoxComponent and make sure load errors are 
+ *      dealt with.
  */
 GeoExt.LegendImage = Ext.extend(Ext.BoxComponent, {
 
@@ -24,29 +26,44 @@
      *  ``String``  The url of the image to load
      */
     url: null,
+    
+    /** api: config[defaultImgSrc]
+     *  ``String`` Path to image that will be used if the legend image fails
+     *  to load.  Default is Ext.BLANK_IMAGE_URL.
+     */
+    defaultImgSrc: null,
 
     /** api: config[imgCls]
      *  ``String``  Optional css class to apply to img tag
      */
     imgCls: null,
-
+    
     /** private: method[initComponent]
      *  Initializes the legend image component. 
      */
     initComponent: function() {
         GeoExt.LegendImage.superclass.initComponent.call(this);
-        this.autoEl = {tag: 'img',
-            'class': (this.imgCls ? this.imgCls : ''), src: this.url};
+        if(this.defaultImgSrc === null) {
+            this.defaultImgSrc = Ext.BLANK_IMAGE_URL;
+        }
+        this.autoEl = {
+            tag: "img",
+            "class": (this.imgCls ? this.imgCls : ""),
+            src: this.defaultImgSrc
+        };
     },
 
-    /** api: method[setUrl]
-     *  :param url: ``String`` The new url of the image.
+    /** api: method[updateLegend]
+     *  :param url: ``String`` The new URL.
      *  
      *  Sets the url of the image.
      */
-    setUrl: function(url) {
+    updateLegend: function(url) {
+        this.url = url;
         var el = this.getEl();
         if (el) {
+            el.un("error", this.onImageLoadError, this);
+            el.on("error", this.onImageLoadError, this, {single: true});
             el.dom.src = url;
         }
     },
@@ -57,14 +74,19 @@
      */
     onRender: function(ct, position) {
         GeoExt.LegendImage.superclass.onRender.call(this, ct, position);
-        this.getEl().on('error', this.onImageLoadError, this);
+        if(this.url) {
+            this.updateLegend(this.url);
+        }
     },
 
     /** private: method[onDestroy]
      *  Private method called during the destroy sequence.
      */
     onDestroy: function() {
-        this.getEl().un('error', this.onImageLoadError, this);
+        var el = this.getEl();
+        if(el) {
+            el.un("error", this.onImageLoadError, this);
+        }
         GeoExt.LegendImage.superclass.onDestroy.apply(this, arguments);
     },
     
@@ -72,9 +94,10 @@
      *  Private method called if the legend image fails loading.
      */
     onImageLoadError: function() {
-        this.getEl().dom.src = Ext.BLANK_IMAGE_URL;
+        this.getEl().dom.src = this.defaultImgSrc;
     }
 
 });
 
+/** api: xtype = gx_legendimage */
 Ext.reg('gx_legendimage', GeoExt.LegendImage);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendPanel.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendPanel.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendPanel.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,281 +1,335 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
- * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
- * of the license.
- *
- * pending approval */
-
-/** api: (define)
- *  module = GeoExt
- *  class = LegendPanel
- */
-
-Ext.namespace('GeoExt');
-
-/** api: constructor
- *  .. class:: LegendPanel(config)
- *
- *  A panel showing legends of all layers in a layer store.
- *  Depending on the layer type, a legend renderer will be chosen.
- */
-GeoExt.LegendPanel = Ext.extend(Ext.Panel, {
-
-    /** api: config[dynamic]
-     *  ``Boolean``
-     *  If false the LegendPanel will not listen to the add, remove and change 
-     *  events of the LayerStore. So it will load with the initial state of
-     *  the LayerStore and not change anymore. 
-     */
-    dynamic: true,
-    
-    /** api: config[showTitle]
-     *  ``Boolean``
-     *  Whether or not to show the title of a layer. This can be a global
-     *  setting for the whole panel, or it can be overridden on the LayerStore 
-     *  record using the hideInLegend property.
-     */
-    showTitle: true,
-
-    /** api: config[labelCls]
-     *  ``String``
-     *  Optional css class to use for the layer title labels.
-     */
-    labelCls: null,
-
-    /** api: config[bodyStyle]
-     *  ``String``
-     *  Optional style to apply to the body of the legend panels.
-     */
-    bodyStyle: '',
-
-    /** api: config[layerStore]
-     *  ``GeoExt.data.LayerStore``
-     *  The layer store containing layers to be displayed in the legend 
-     *  container. If not provided it will be taken from the MapPanel.
-     */
-    layerStore: null,
-
-    /** private: method[initComponent]
-     *  Initializes the legend panel.
-     */
-    initComponent: function() {
-        GeoExt.LegendPanel.superclass.initComponent.call(this);
-    },
-    
-    /** private: method[onRender]
-     *  Private method called when the legend panel is being rendered.
-     */
-    onRender: function() {
-        GeoExt.LegendPanel.superclass.onRender.apply(this, arguments);
-        if(!this.layerStore) {
-            this.layerStore = GeoExt.MapPanel.guess().layers;
-        }
-        this.layerStore.each(function(record) {
-                this.addLegend(record);
-            }, this);
-        if (this.dynamic) {
-            this.layerStore.on({
-                "add": this.onStoreAdd,
-                "remove": this.onStoreRemove,
-                "update": this.onStoreUpdate,
-                scope: this
-            });
-        }
-        this.doLayout();
-    },
-
-    /** private: method[recordIndexToPanelIndex]
-     *  Private method to get the panel index for a layer represented by a
-     *  record.
-     *
-     *  :param index ``Integer`` The index of the record in the store.
-     *
-     *  :return: ``Integer`` The index of the sub panel in this panel.
-     */
-    recordIndexToPanelIndex: function(index) {
-        var store = this.layerStore;
-        var count = store.getCount();
-        var panelIndex = -1;
-        for(var i=count-1; i>=0; --i) {
-            var layer = store.getAt(i).get("layer");
-            var legendGenerator = GeoExt[
-                "Legend" + layer.CLASS_NAME.split(".").pop()
-            ];
-            if(layer.displayInLayerSwitcher && legendGenerator &&
-                (store.getAt(i).get("hideInLegend") !== true)) {
-                    ++panelIndex;
-                    if(index === i) {
-                        break;
-                    }
-            }
-        }
-        return panelIndex;
-    },
-
-    /** private: method[onStoreUpdate]
-     *  Update a layer within the legend panel. Gets called when the store
-     *  fires the update event. This usually means the visibility of the layer
-     *  has changed.
-     *
-     *  :param store: ``Ext.data.Store`` The store in which the record was
-     *      changed.
-     *  :param record: ``Ext.data.Record`` The record object corresponding
-     *      to the updated layer.
-     *  :param operation: ``String`` The type of operation.
-     */
-    onStoreUpdate: function(store, record, operation) {
-        var layer = record.get('layer');
-        var legend = this.getComponent(layer.id);
-        if (legend) {
-            legend.setVisible(layer.getVisibility() && 
-                layer.displayInLayerSwitcher && !record.get('hideInLegend'));
-            if (record.get('legendURL')) {
-                var items = legend.findByType('gx_legendimage');
-                for (var i=0, len=items.length; i<len; i++) {
-                    items[i].setUrl(record.get('legendURL'));
-                }
-            }
-        }
-    },
-
-    /** private: method[onStoreAdd]
-     *  Private method called when a layer is added to the store.
-     *
-     *  :param store: ``Ext.data.Store`` The store to which the record(s) was 
-     *      added.
-     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
-     *      to the added layers.
-     *  :param index: ``Integer`` The index of the inserted record.
-     */
-    onStoreAdd: function(store, records, index) {
-        var panelIndex = this.recordIndexToPanelIndex(index);
-        for (var i=0, len=records.length; i<len; i++) {
-            this.addLegend(records[i], panelIndex);
-        }
-        this.doLayout();
-    },
-
-    /** private: method[onStoreRemove]
-     *  Private method called when a layer is removed from the store.
-     *
-     *  :param store: ``Ext.data.Store`` The store from which the record(s) was
-     *      removed.
-     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
-     *      to the removed layers.
-     *  :param index: ``Integer`` The index of the removed record.
-     */
-    onStoreRemove: function(store, record, index) {
-        this.removeLegend(record);
-    },
-
-    /** private: method[removeLegend]
-     *  Remove the legend of a layer.
-     *  :param record: ``Ext.data.Record`` The record object from the layer 
-     *      store to remove.
-     */
-    removeLegend: function(record) {
-        var legend = this.getComponent(record.get('layer').id);
-        if (legend) {
-            this.remove(legend, true);
-            this.doLayout();
-        }
-    },
-
-    /** private: method[createLegendSubpanel]
-     *  Create a legend sub panel for the layer.
-     *
-     *  :param record: ``Ext.data.Record`` The record object from the layer
-     *      store.
-     *
-     *  :return: ``Ext.Panel`` The created panel per layer
-     */
-    createLegendSubpanel: function(record) {
-        var layer = record.get('layer');
-        var mainPanel = this.createMainPanel(record);
-        if (mainPanel !== null) {
-            // the default legend can be overridden by specifying a
-            // legendURL property
-            var legend;
-            if (record.get('legendURL')) {
-                legend = new GeoExt.LegendImage({url: record.get('legendURL')});
-                mainPanel.add(legend);
-            } else {
-                var legendGenerator = GeoExt[
-                    "Legend" + layer.CLASS_NAME.split(".").pop()
-                ];
-                if (legendGenerator) {
-                    legend = new legendGenerator({layer: layer});
-                    mainPanel.add(legend);
-                }
-            }
-        }
-        return mainPanel;
-    },
-
-    /** private: method[addLegend]
-     *  Add a legend for the layer.
-     *
-     *  :param record: ``Ext.data.Record`` The record object from the layer 
-     *      store.
-     *  :param index: ``Integer`` The position at which to add the legend.
-     */
-    addLegend: function(record, index) {
-        index = index || 0;
-        var layer = record.get('layer');
-        var legendSubpanel = this.createLegendSubpanel(record);
-        if (legendSubpanel !== null) {
-           legendSubpanel.setVisible(layer.getVisibility());
-           this.insert(index, legendSubpanel);
-        }
-    },
-
-    /** private: method[createMainPanel]
-     *  Creates the main panel with a title for the layer.
-     *
-     *  :param record: ``Ext.data.Record`` The record object from the layer
-     *      store.
-     *
-     *  :return: ``Ext.Panel`` The created main panel with a label.
-     */
-    createMainPanel: function(record) {
-        var layer = record.get('layer');
-        var panel = null;
-        var legendGenerator = GeoExt[
-            "Legend" + layer.CLASS_NAME.split(".").pop()
-        ];
-        if (layer.displayInLayerSwitcher && !record.get('hideInLegend') &&
-            legendGenerator) {
-            var panelConfig = {
-                id: layer.id,
-                border: false,
-                bodyBorder: false,
-                bodyStyle: this.bodyStyle,
-                items: [
-                    new Ext.form.Label({
-                        text: (this.showTitle && !record.get('hideTitle')) ? 
-                            layer.name : '',
-                        cls: 'x-form-item x-form-item-label' +
-                            (this.labelCls ? ' ' + this.labelCls : '')
-                    })
-                ]
-            };
-            panel = new Ext.Panel(panelConfig);
-        }
-        return panel;
-    },
-
-    /** private: method[onDestroy]
-     *  Private method called during the destroy sequence.
-     */
-    onDestroy: function() {
-        if(this.layerStore) {
-            this.layerStore.un("add", this.onStoreAdd, this);
-            this.layerStore.un("remove", this.onStoreRemove, this);
-            this.layerStore.un("update", this.onStoreUpdate, this);
-        }
-        GeoExt.LegendPanel.superclass.onDestroy.apply(this, arguments);
-    }
-    
-});
-
-Ext.reg('gx_legendpanel', GeoExt.LegendPanel);
\ No newline at end of file
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LegendPanel
+ *  base_link = `Ext.Panel <http://extjs.com/deploy/dev/docs/?class=Ext.Panel>`_
+ */
+
+Ext.namespace('GeoExt');
+
+/** api: constructor
+ *  .. class:: LegendPanel(config)
+ *
+ *  A panel showing legends of all layers in a layer store.
+ *  Depending on the layer type, a legend renderer will be chosen.
+ */
+GeoExt.LegendPanel = Ext.extend(Ext.Panel, {
+
+    /** api: config[dynamic]
+     *  ``Boolean``
+     *  If false the LegendPanel will not listen to the add, remove and change 
+     *  events of the LayerStore. So it will load with the initial state of
+     *  the LayerStore and not change anymore. 
+     */
+    dynamic: true,
+    
+    /** api: config[showTitle]
+     *  ``Boolean``
+     *  Whether or not to show the title of a layer. This can be a global
+     *  setting for the whole panel, or it can be overridden on the LayerStore 
+     *  record using the hideInLegend property.
+     */
+    showTitle: true,
+
+    /** api: config[labelCls]
+     *  ``String``
+     *  Optional css class to use for the layer title labels.
+     */
+    labelCls: null,
+
+    /** api: config[bodyStyle]
+     *  ``String``
+     *  Optional style to apply to the body of the legend panels.
+     */
+    bodyStyle: '',
+
+    /** api: config[layerStore]
+     *  ``GeoExt.data.LayerStore``
+     *  The layer store containing layers to be displayed in the legend 
+     *  container. If not provided it will be taken from the MapPanel.
+     */
+    layerStore: null,
+    
+    /** api: config[legendOptions]
+     *  ``Object``
+     *  Config options for the legend generator, i.e. the panel that provides
+     *  the legend image.
+     */
+
+    /** api: config[filter]
+     *  ``Function``
+     *  A function, called in the scope of the legend panel, with a layer record
+     *  as argument. Is expected to return true for layers to be displayed, false
+     *  otherwise. By default, all layers will be displayed.
+     *
+     *  .. code-block:: javascript
+     *
+     *      filter: function(record) {
+     *          return record.get("layer").isBaseLayer;
+     *      }
+     */
+    filter: function(record) {
+        return true;
+    },
+
+    /** private: method[initComponent]
+     *  Initializes the legend panel.
+     */
+    initComponent: function() {
+        GeoExt.LegendPanel.superclass.initComponent.call(this);
+    },
+    
+    /** private: method[onRender]
+     *  Private method called when the legend panel is being rendered.
+     */
+    onRender: function() {
+        GeoExt.LegendPanel.superclass.onRender.apply(this, arguments);
+        if(!this.layerStore) {
+            this.layerStore = GeoExt.MapPanel.guess().layers;
+        }
+        this.layerStore.each(function(record) {
+                this.addLegend(record);
+            }, this);
+        if (this.dynamic) {
+            this.layerStore.on({
+                "add": this.onStoreAdd,
+                "remove": this.onStoreRemove,
+                "clear": this.onStoreClear,
+                "update": this.onStoreUpdate,
+                scope: this
+            });
+        }
+        this.doLayout();
+    },
+
+    /** private: method[recordIndexToPanelIndex]
+     *  Private method to get the panel index for a layer represented by a
+     *  record.
+     *
+     *  :param index ``Integer`` The index of the record in the store.
+     *
+     *  :return: ``Integer`` The index of the sub panel in this panel.
+     */
+    recordIndexToPanelIndex: function(index) {
+        var store = this.layerStore;
+        var count = store.getCount();
+        var panelIndex = -1;
+        var legendCount = this.items ? this.items.length : 0;
+        for(var i=count-1; i>=0; --i) {
+            var layer = store.getAt(i).get("layer");
+            var legendGenerator = GeoExt[
+                "Legend" + layer.CLASS_NAME.split(".").pop()
+            ];
+            if(layer.displayInLayerSwitcher && legendGenerator &&
+                (store.getAt(i).get("hideInLegend") !== true)) {
+                    ++panelIndex;
+                    if(index === i || panelIndex > legendCount-1) {
+                        break;
+                    }
+            }
+        }
+        return panelIndex;
+    },
+
+    /** private: method[onStoreUpdate]
+     *  Update a layer within the legend panel. Gets called when the store
+     *  fires the update event. This usually means the visibility of the layer
+     *  has changed.
+     *
+     *  :param store: ``Ext.data.Store`` The store in which the record was
+     *      changed.
+     *  :param record: ``Ext.data.Record`` The record object corresponding
+     *      to the updated layer.
+     *  :param operation: ``String`` The type of operation.
+     */
+    onStoreUpdate: function(store, record, operation) {
+        var layer = record.get('layer');
+        var legend = this.items ? this.getComponent(layer.id) : null;
+        if ((this.showTitle && !record.get('hideTitle')) && 
+            (legend && legend.items.get(0).text !== record.get('title'))) {
+                // we need to update the title
+                legend.items.get(0).setText(record.get('title'));
+        }
+        if (legend) {
+            legend.setVisible(layer.getVisibility() && layer.inRange &&
+                layer.displayInLayerSwitcher && !record.get('hideInLegend'));
+            var url = record.get("legendURL") != null ?
+                      record.get("legendURL") : undefined;
+            // the actual legend panel is the second item in
+            // the main panel
+            legend.items.get(1).updateLegend(url);
+        }
+    },
+
+    /** private: method[onStoreAdd]
+     *  Private method called when a layer is added to the store.
+     *
+     *  :param store: ``Ext.data.Store`` The store to which the record(s) was 
+     *      added.
+     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
+     *      to the added layers.
+     *  :param index: ``Integer`` The index of the inserted record.
+     */
+    onStoreAdd: function(store, records, index) {
+        var panelIndex = this.recordIndexToPanelIndex(index+records.length-1);
+        for (var i=0, len=records.length; i<len; i++) {
+            this.addLegend(records[i], panelIndex);
+        }
+        this.doLayout();
+    },
+
+    /** private: method[onStoreRemove]
+     *  Private method called when a layer is removed from the store.
+     *
+     *  :param store: ``Ext.data.Store`` The store from which the record(s) was
+     *      removed.
+     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
+     *      to the removed layers.
+     *  :param index: ``Integer`` The index of the removed record.
+     */
+    onStoreRemove: function(store, record, index) {
+        this.removeLegend(record);
+    },
+
+    /** private: method[removeLegend]
+     *  Remove the legend of a layer.
+     *  :param record: ``Ext.data.Record`` The record object from the layer 
+     *      store to remove.
+     */
+    removeLegend: function(record) {
+        var legend = this.getComponent(record.get('layer').id);
+        if (legend) {
+            this.remove(legend, true);
+            this.doLayout();
+        }
+    },
+
+    /** private: method[onStoreClear]
+     *  Private method called when a layer store is cleared.
+     *
+     *  :param store: ``Ext.data.Store`` The store from which was cleared.
+     */
+    onStoreClear: function(store) {
+        this.removeAllLegends();
+    },
+
+    /** private: method[removeAllLegends]
+     *  Remove all legends from this legend panel.
+     */
+    removeAllLegends: function() {
+        this.removeAll(true);
+        this.doLayout();
+    },
+
+    /** private: method[createLegendSubpanel]
+     *  Create a legend sub panel for the layer.
+     *
+     *  :param record: ``Ext.data.Record`` The record object from the layer
+     *      store.
+     *
+     *  :return: ``Ext.Panel`` The created panel per layer
+     */
+    createLegendSubpanel: function(record) {
+        var layer = record.get('layer');
+        var mainPanel = this.createMainPanel(record);
+        if (mainPanel !== null) {
+            // the default legend can be overridden by specifying a
+            // legendURL property
+            var legend;
+            if (record.get('legendURL')) {
+                legend = new GeoExt.LegendImage({url: record.get('legendURL')});
+                mainPanel.add(legend);
+            } else {
+                var legendGenerator = GeoExt[
+                    "Legend" + layer.CLASS_NAME.split(".").pop()
+                ];
+                if (legendGenerator) {
+                    legend = new legendGenerator(Ext.applyIf({
+                        layer: layer,
+                        record: record
+                    }, this.legendOptions));
+                    mainPanel.add(legend);
+                }
+            }
+        }
+        return mainPanel;
+    },
+
+    /** private: method[addLegend]
+     *  Add a legend for the layer.
+     *
+     *  :param record: ``Ext.data.Record`` The record object from the layer 
+     *      store.
+     *  :param index: ``Integer`` The position at which to add the legend.
+     */
+    addLegend: function(record, index) {
+        if (this.filter(record) === true) {
+            index = index || 0;
+            var layer = record.get('layer');
+            var legendSubpanel = this.createLegendSubpanel(record);
+            if (legendSubpanel !== null) {
+                legendSubpanel.setVisible(layer.getVisibility() && layer.inRange);
+                this.insert(index, legendSubpanel);
+            }
+        }
+    },
+
+    /** private: method[createMainPanel]
+     *  Creates the main panel with a title for the layer.
+     *
+     *  :param record: ``Ext.data.Record`` The record object from the layer
+     *      store.
+     *
+     *  :return: ``Ext.Panel`` The created main panel with a label.
+     */
+    createMainPanel: function(record) {
+        var layer = record.get('layer');
+        var panel = null;
+        var legendGenerator = GeoExt[
+            "Legend" + layer.CLASS_NAME.split(".").pop()
+        ];
+        if (layer.displayInLayerSwitcher && !record.get('hideInLegend') &&
+            legendGenerator) {
+            var panelConfig = {
+                id: layer.id,
+                border: false,
+                bodyBorder: false,
+                hideMode: 'offsets',
+                bodyStyle: this.bodyStyle,
+                items: [
+                    new Ext.form.Label({
+                        text: (this.showTitle && !record.get('hideTitle')) ? 
+                            layer.name : '',
+                        cls: 'x-form-item x-form-item-label' +
+                            (this.labelCls ? ' ' + this.labelCls : '')
+                    })
+                ]
+            };
+            panel = new Ext.Panel(panelConfig);
+        }
+        return panel;
+    },
+
+    /** private: method[onDestroy]
+     *  Private method called during the destroy sequence.
+     */
+    onDestroy: function() {
+        if(this.layerStore) {
+            this.layerStore.un("add", this.onStoreAdd, this);
+            this.layerStore.un("remove", this.onStoreRemove, this);
+            this.layerStore.un("clear", this.onStoreClear, this);
+            this.layerStore.un("update", this.onStoreUpdate, this);
+        }
+        GeoExt.LegendPanel.superclass.onDestroy.apply(this, arguments);
+    }
+});
+
+/** api: xtype = gx_legendpanel */
+Ext.reg('gx_legendpanel', GeoExt.LegendPanel);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendWMS.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendWMS.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/LegendWMS.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,87 +1,185 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
- * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
- * of the license.
- *
- * pending approval */
-
-/**
- * @include GeoExt/widgets/LegendImage.js
- */
-
-/** api: (define)
- *  module = GeoExt
- *  class = LegendWMS
- */
-Ext.namespace('GeoExt');
-
-/** api: constructor
- *  .. class:: LegendWMS(config)
- *
- *  Show a legend image for a WMS layer.
- */
-GeoExt.LegendWMS = Ext.extend(Ext.Panel, {
-
-    /** api: config[imageFormat]
-     *  ``String``  
-     *  The image format to request the legend image in.
-     *  Defaults to image/png.
-     */
-    imageFormat: "image/gif",
-
-    /** api: config[layer]
-     *  ``OpenLayers.Layer.WMS``
-     *  The WMS layer to request the legend for.
-     */
-    layer: null,
-
-    /** api: config[bodyBorder]
-     *  ``Boolean``
-     *  Show a border around the legend image or not. Default is false.
-     */
-    bodyBorder: false,
-
-    /** private: method[initComponent]
-     *  Initializes the WMS legend. For group layers it will create multiple
-     *  image box components.
-     */
-    initComponent: function() {
-        GeoExt.LegendWMS.superclass.initComponent.call(this);
-        this.createLegend();
-    },
-
-    /** private: method[getLegendUrl]
-     *  :param layer: ``OpenLayers.Layer.WMS`` The OpenLayers WMS layer object
-     *  :param layerName: ``String`` The name of the layer 
-     *      (used in the LAYERS parameter)
-     *  :return: ``String`` The url of the SLD WMS GetLegendGraphic request.
-     *
-     *  Get the url for the SLD WMS GetLegendGraphic request.
-     */
-    getLegendUrl: function(layerName) {
-        return this.layer.getFullRequestString({
-            REQUEST: "GetLegendGraphic",
-            WIDTH: null,
-            HEIGHT: null,
-            EXCEPTIONS: "application/vnd.ogc.se_xml",
-            LAYER: layerName,
-            LAYERS: null,
-            SRS: null,
-            FORMAT: this.imageFormat
-        });
-    },
-
-    /** private: method[createLegend]
-     *  Add one BoxComponent per sublayer to this panel.
-     */
-    createLegend: function() {
-        var layers = this.layer.params.LAYERS.split(",");
-        for (var i = 0, len = layers.length; i < len; i++){
-            var layerName = layers[i];
-            var legend = new GeoExt.LegendImage({url:
-                this.getLegendUrl(layerName)});
-            this.add(legend);
-        }
-    }
-
-});
\ No newline at end of file
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/widgets/LegendImage.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LegendWMS
+ *  base_link = `Ext.Panel <http://extjs.com/deploy/dev/docs/?class=Ext.Panel>`_
+ */
+Ext.namespace('GeoExt');
+
+/** api: constructor
+ *  .. class:: LegendWMS(config)
+ *
+ *  Show a legend image for a WMS layer. The image can be read from the styles
+ *  field of a layer record (if the record comes e.g. from a
+ *  :class:`GeoExt.data.WMSCapabilitiesReader`). If not provided, a
+ *  GetLegendGraphic request will be issued to retrieve the image.
+ */
+GeoExt.LegendWMS = Ext.extend(Ext.Panel, {
+
+    /** api: config[imageFormat]
+     *  ``String``  
+     *  The image format to request the legend image in if the url cannot be
+     *  determined from the styles field of the layer record. Defaults to
+     *  image/gif.
+     */
+    imageFormat: "image/gif",
+    
+    /** api: config[defaultStyleIsFirst]
+     *  ``String``
+     *  The WMS spec does not say if the first style advertised for a layer in
+     *  a Capabilities document is the default style that the layer is
+     *  rendered with. We make this assumption by default. To be strictly WMS
+     *  compliant, set this to false, but make sure to configure a STYLES
+     *  param with your WMS layers, otherwise LegendURLs advertised in the
+     *  GetCapabilities document cannot be used.
+     */
+    defaultStyleIsFirst: true,
+
+    /** api: config[layer]
+     *  ``OpenLayers.Layer.WMS``
+     *  The WMS layer to request the legend for. Not required if record is
+     *  provided.
+     */
+    layer: null,
+    
+    /** api: config[record]
+     *  ``Ext.data.Record``
+     *  Optional record containing the layer. If provided, and if the record
+     *  has a styles property, the legend image associated with the layer's
+     *  style will be used.
+     */
+    record: null,
+
+    /** api: config[bodyBorder]
+     *  ``Boolean``
+     *  Show a border around the legend image or not. Default is false.
+     */
+    bodyBorder: false,
+
+    /** private: method[initComponent]
+     *  Initializes the WMS legend. For group layers it will create multiple
+     *  image box components.
+     */
+    initComponent: function() {
+        GeoExt.LegendWMS.superclass.initComponent.call(this);
+        if(!this.layer) {
+            this.layer = this.record.get("layer");
+        }
+        this.updateLegend();
+    },
+
+    /** private: method[getLegendUrl]
+     *  :param layerName: ``String`` A sublayer.
+     *  :param layerNames: ``Array(String)`` The array of sublayers,
+     *      read from this.layer if not provided.
+     *  :return: ``String`` The legend URL.
+     *
+     *  Get the legend URL of a sublayer.
+     */
+    getLegendUrl: function(layerName, layerNames) {
+        var url;
+        var styles = this.record && this.record.get("styles");
+        layerNames = layerNames ||
+                             (this.layer.params.LAYERS instanceof Array) ?
+                             this.layer.params.LAYERS :
+                             this.layer.params.LAYERS.split(",");
+
+        var styleNames = this.layer.params.STYLES &&
+                             this.layer.params.STYLES.split(",");
+        var idx = layerNames.indexOf(layerName);
+        var styleName = styleNames && styleNames[idx];
+        // check if we have a legend URL in the record's
+        // "styles" data field
+        if(styles && styles.length > 0) {
+            if(styleName) {
+                Ext.each(styles, function(s) {
+                    url = (s.name == styleName && s.legend) && s.legend.href;
+                    return !url;
+                });
+            } else if(this.defaultStyleIsFirst === true && !styleNames &&
+                      !this.layer.params.SLD && !this.layer.params.SLD_BODY) {
+                url = styles[0].legend && styles[0].legend.href;
+            }
+        }
+        return url ||
+               this.layer.getFullRequestString({
+                   REQUEST: "GetLegendGraphic",
+                   WIDTH: null,
+                   HEIGHT: null,
+                   EXCEPTIONS: "application/vnd.ogc.se_xml",
+                   LAYER: layerName,
+                   LAYERS: null,
+                   STYLE: (styleName !== '') ? styleName: null,
+                   STYLES: null,
+                   SRS: null,
+                   FORMAT: this.imageFormat
+        });
+    },
+
+    /** private: method[updateLegend]
+     *  :param url: ``String`` The legend URL, derived from the
+     *      layer record or layer params (WMS GetLegendGraphic)
+     *      if not provided.
+     *
+     *  Update the legend panel, adding, removing or updating
+     *  the per-sublayer box component.
+     */
+    updateLegend: function(url) {
+        var layerNames, layerName, i, len;
+        
+        layerNames = (this.layer.params.LAYERS instanceof Array) ? 
+            this.layer.params.LAYERS :
+            this.layer.params.LAYERS.split(",");
+
+        if(this.items) {
+            var destroyList = [];
+            this.items.each(function(cmp) {
+                i = layerNames.indexOf(cmp.itemId);
+                if(i < 0) {
+                    destroyList.push(cmp);
+                } else {
+                    layerName = layerNames[i];
+                    var newUrl = url ||
+                                 this.getLegendUrl(layerName, layerNames);
+                    if(!OpenLayers.Util.isEquivalentUrl(newUrl, cmp.url)) {
+                        cmp.updateLegend(newUrl);
+                    }
+                }
+            }, this);
+            for(i = 0, len = destroyList.length; i<len; i++) {
+                var cmp = destroyList[i];
+                // cmp.destroy() does not remove the cmp from
+                // its parent container!
+                this.remove(cmp);
+                cmp.destroy();
+            }
+        }
+
+        var doLayout = false;
+        for(i = 0, len = layerNames.length; i<len; i++) {
+            layerName = layerNames[i];
+            if(!this.items || !this.getComponent(layerName)) {
+                this.add({
+                    xtype: "gx_legendimage",
+                    url: url || this.getLegendUrl(layerName, layerNames),
+                    itemId: layerName
+                });
+                doLayout = true;
+            }
+        }
+        if(doLayout) {
+            this.doLayout();
+        }
+    }
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/MapPanel.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/MapPanel.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/MapPanel.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * ¹ pending approval */
+ */
 
 /**
  * @include GeoExt/data/LayerStore.js
@@ -139,19 +140,10 @@
      */
     renderMap: function() {
         var map = this.map;
-
-        // hack: prevent map.updateSize (called from within map.render) from 
-        // zooming to the map extent. This hack is a workaround for 
-        // <http://trac.openlayers.org/ticket/2105> and must be
-        // removed once this ticket is closed.
-        var setCenter = map.setCenter;
-        map.setCenter = function() {};
         map.render(this.body.dom);
-        map.setCenter = setCenter;
-
         if(map.layers.length > 0) {
-            if(this.center) {
-                // zoom does not have to be defined
+            if(this.center || this.zoom != null) {
+                // both do not have to be defined
                 map.setCenter(this.center, this.zoom);
             } else if(this.extent) {
                 map.zoomToExtent(this.extent);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Popup.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Popup.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/Popup.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,9 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation [1]
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * [1] pending approval
  */
 
 /** api: (define)
@@ -44,6 +44,14 @@
      */
     anchored: true,
 
+    /** api: config[map]
+     *  ``OpenLayers.Map`` or :class:`GeoExt.MapPanel`
+     *  The map this popup will be anchored to (only required if ``anchored``
+     *  is set to true and the map cannot be derived from the ``feature``'s
+     *  layer.
+     */
+    map: null,
+
     /** api: config[panIn]
      *  ``Boolean`` The popup should pan the map so that the popup is
      *  fully in view when it is rendered.  Default is ``true``.
@@ -100,7 +108,7 @@
      */
     popupCls: "gx-popup",
 
-    /** api: config[ancCls
+    /** api: config[ancCls]
      *  ``String``  CSS class name for the popup's anchor.
      */
     ancCls: null,
@@ -108,10 +116,19 @@
     /** private: method[initComponent]
      *  Initializes the popup.
      */
-    initComponent: function() {        
+    initComponent: function() {
+        if(this.map instanceof GeoExt.MapPanel) {
+            this.map = this.map.map;
+        }
+        if(!this.map && this.feature && this.feature.layer) {
+            this.map = this.feature.layer.map;
+        }
         if (!this.feature && this.lonlat) {
             this.feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(this.lonlat.lon, this.lonlat.lat));
         }
+        if(this.anchored) {
+            this.addAnchorEvents();
+        }
 
         this.baseCls = this.popupCls + " " + this.baseCls;
 
@@ -128,7 +145,7 @@
         this.ancCls = this.popupCls + "-anc";
 
         //create anchor dom element.
-        this.createElement("anc", this.el);
+        this.createElement("anc", this.el.dom);
     },
 
     /** private: method[initTools]
@@ -146,38 +163,19 @@
         GeoExt.Popup.superclass.initTools.call(this);
     },
 
-    /** private: method[addToMapPanel]
-     *  :param mapPanel: :class:`MapPanel` The panel to which this popup should
-     *      be added.
-     *  
-     *  Adds this popup to a :class:`MapPanel`.  Assumes that the
-     *  MapPanel's map is already initialized and that the
-     *  Popup's feature is on the map.  This method is called by the MapPanel's
-     *  add method.
+    /** private: method[show]
+     *  Override.
      */
-    addToMapPanel: function(mapPanel) {
-        this.mapPanel = mapPanel;
-        this.map = this.mapPanel.map;
-        
-        mapPanel.on({
-            "add": {
-                fn: function() {
-                    mapPanel.doLayout();
-                    this.position();
-                    if(this.anchored) {
-                        this.anchorPopup();
-                    }
-                    this.show();
-                    if(this.panIn) {
-                        this.panIntoView();
-                    }
-                },
-                single: true,
-                scope: this
+    show: function() {
+        GeoExt.Popup.superclass.show.apply(this, arguments);
+        if(this.anchored) {
+            this.position();
+            if(this.panIn && !this._mapMove) {
+                this.panIntoView();
             }
-        });
+        }
     },
-
+    
     /** api: method[setSize]
      *  :param w: ``Integer``
      *  :param h: ``Integer``
@@ -186,7 +184,7 @@
      */
     setSize: function(w, h) {
         if(this.anc) {
-            var ancSize = this.getAnchorElement().getSize();
+            var ancSize = this.anc.getSize();
             if(typeof w == 'object') {
                 h = w.height - ancSize.height;
                 w = w.width;
@@ -202,131 +200,66 @@
      */
     position: function() {
         var centerLonLat = this.feature.geometry.getBounds().getCenterLonLat();
-        var centerPx = this.map.getViewPortPxFromLonLat(centerLonLat);
 
-        //This works for positioning with the anchor on the bottom.
-        
-        //Will have to functionalize this out later and allow
-        //for other positions relative to the feature.
-        var anchorSelector = "div." + this.ancCls;
+        if(this._mapMove === true) {
+            var visible = this.map.getExtent().containsLonLat(centerLonLat);
+            if(visible !== this.isVisible()) {
+                this.setVisible(visible);
+            }
+        }
 
-        var dx = this.anc.down(anchorSelector).getLeft(true) +
-                            this.anc.down(anchorSelector).getWidth() / 2;
-        var dy = this.el.getHeight();
-
-        //Assuming for now that the map viewport takes up
-        //the entire area of the MapPanel
-        this.setPosition(centerPx.x - dx, centerPx.y - dy);
+        if(this.isVisible()) {
+            var centerPx = this.map.getViewPortPxFromLonLat(centerLonLat);
+            var mapBox = Ext.fly(this.map.div).getBox(); 
+    
+            //This works for positioning with the anchor on the bottom.
+            
+            var anc = this.anc;
+            var dx = anc.getLeft(true) + anc.getWidth() / 2;
+            var dy = this.el.getHeight();
+    
+            //Assuming for now that the map viewport takes up
+            //the entire area of the MapPanel
+            this.setPosition(centerPx.x + mapBox.x - dx, centerPx.y + mapBox.y - dy);
+        }
     },
 
-    /** private: method[getAnchorElement]
-     *  :returns: ``Ext.Element``  The anchor element of the popup.
-     */
-    getAnchorElement: function() {
-        var anchorSelector = "div." + this.ancCls;
-        var anc = Ext.get(this.el.child(anchorSelector));
-        return anc;
-    },
-
-    /** private: method[anchorPopup]
-     *  Anchors a popup to its feature by registering listeners that reposition
-     *  the popup when the map is moved.
-     */
-    anchorPopup: function() {
-        this.map.events.on({
-            "move" : this.position,
-            scope : this            
-        });
-
-        this.on({
-            "resize": this.position,
-            "collapse": this.position,
-            "expand": this.position,
-            scope: this
-        });
-    },
-
     /** private: method[unanchorPopup]
      *  Unanchors a popup from its feature.  This removes the popup from its
      *  MapPanel and adds it to the page body.
      */
     unanchorPopup: function() {
-
+        this.removeAnchorEvents();
+        
         //make the window draggable
         this.draggable = true;
         this.header.addClass("x-window-draggable");
         this.dd = new Ext.Window.DD(this);
 
         //remove anchor
-        this.getAnchorElement().remove();
+        this.anc.remove();
         this.anc = null;
 
         //hide unpin tool
         this.tools.unpin.hide();
-
-        //keep track of whether the popup has been collapsed
-        var collapsed = this.collapsed;
-
-        //Steps to move this window out to the body
-        //TODO: Make 'unpinned' container configurable
-        this.mapPanel.remove(this, false);
-
-        this.container = Ext.getBody();
-        
-        var xy = this.getPosition();
-        this.hide();
-        this.el.appendTo(Ext.getBody());
-        this.setPagePosition(xy[0],xy[1]);
-        this.show();
-
-        //recollapse if it was collapsed before
-        if(collapsed) {
-            this.collapse();
-        }
     },
 
-    /** private: method[removeFromMapPanel]
-     *  Utility method for unbinding events that call for popup repositioning.
-     *  Called from the panel during panel.remove(popup).
-     */
-    removeFromMapPanel: function() {
-        if(this.map && this.map.events) {
-            //stop position with feature
-            this.map.events.un({
-                "move" : this.position,
-                scope : this
-            });
-        }
-
-        this.un("resize", this.position, this);
-        this.un("collapse", this.position, this);
-        this.un("expand", this.position, this);
-
-        delete this.mapPanel;
-        delete this.map;
-    },
-
     /** private: method[panIntoView]
      *  Pans the MapPanel's map so that an anchored popup can come entirely
      *  into view, with padding specified as per normal OpenLayers.Map popup
      *  padding.
      */ 
     panIntoView: function() {
-        if(!this.anchored) {
-            /*
-             * If it's not anchored, panning the map won't put popup into view
-             */
-            return;
-        }
-        this.position();
-        
-        var centerLonLat = this.feature.geometry.getBounds().getCenterLonLat(); 
+        var centerLonLat = this.feature.geometry.getBounds().getCenterLonLat();
         var centerPx = this.map.getViewPortPxFromLonLat(centerLonLat);
+        var mapBox = Ext.fly(this.map.div).getBox(); 
 
         //assumed viewport takes up whole body element of map panel
         var popupPos =  this.getPosition(true);
+        popupPos[0] -= mapBox.x;
+        popupPos[1] -= mapBox.y;
        
-        var panelSize = [this.mapPanel.getInnerWidth(), this.mapPanel.getInnerHeight()]; // [X,Y]
+        var panelSize = [mapBox.width, mapBox.height]; // [X,Y]
 
         var popupSize = this.getSize();
 
@@ -354,15 +287,56 @@
 
         this.map.pan(dx, dy);
     },
+    
+    /** private: method[onMapMove]
+     */
+    onMapMove: function() {
+        this._mapMove = true;
+        this.position();
+        delete this._mapMove;
+    },
+    
+    /** private: method[addAnchorEvents]
+     */
+    addAnchorEvents: function() {
+        this.map.events.on({
+            "move" : this.onMapMove,
+            scope : this            
+        });
+        
+        this.on({
+            "resize": this.position,
+            "collapse": this.position,
+            "expand": this.position,
+            scope: this
+        });
+    },
+    
+    /** private: method[removeAnchorEvents]
+     */
+    removeAnchorEvents: function() {
+        //stop position with feature
+        this.map.events.un({
+            "move" : this.onMapMove,
+            scope : this
+        });
 
+        this.un("resize", this.position, this);
+        this.un("collapse", this.position, this);
+        this.un("expand", this.position, this);
+
+    },
+
     /** private: method[beforeDestroy]
      *  Cleanup events before destroying the popup.
      */
     beforeDestroy: function() {
-        this.removeFromMapPanel();
+        if(this.anchored) {
+            this.removeAnchorEvents();
+        }
         GeoExt.Popup.superclass.beforeDestroy.call(this);
     }
 });
 
-/** api: xtype = gx_mappanel */
+/** api: xtype = gx_popup */
 Ext.reg('gx_popup', GeoExt.Popup); 

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/ZoomSlider.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/ZoomSlider.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/ZoomSlider.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/ZoomSlider.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,285 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/widgets/tips/ZoomSliderTip.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = ZoomSlider
+ *  base_link = `Ext.Slider <http://extjs.com/deploy/dev/docs/?class=Ext.Slider>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to render a slider outside the map viewport:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var slider = new GeoExt.ZoomSlider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          map: map
+ *      });
+ *     
+ *  Sample code to add a slider to a map panel:
+ * 
+ *  .. code-block:: javascript
+ * 
+ *      var panel = new GeoExt.MapPanel({
+ *          renderTo: document.body,
+ *          height: 300,
+ *          width: 400,
+ *          map: {
+ *              controls: [new OpenLayers.Control.Navigation()]
+ *          },
+ *          layers: [new OpenLayers.Layer.WMS(
+ *              "Global Imagery",
+ *              "http://maps.opengeo.org/geowebcache/service/wms",
+ *              {layers: "bluemarble"}
+ *          )],
+ *          extent: [-5, 35, 15, 55],
+ *          items: [{
+ *              xtype: "gx_zoomslider",
+ *              aggressive: true,
+ *              vertical: true,
+ *              height: 100,
+ *              x: 10,
+ *              y: 20
+ *          }]
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: ZoomSlider(config)
+ *   
+ *      Create a slider for controlling a map's zoom level.
+ */
+GeoExt.ZoomSlider = Ext.extend(Ext.Slider, {
+    
+    /** api: config[map]
+     *  ``OpenLayers.Map`` or :class:`GeoExt.MapPanel`
+     *  The map that the slider controls.
+     */
+    map: null,
+    
+    /** api: config[baseCls]
+     *  ``String``
+     *  The CSS class name for the slider elements.  Default is "gx-zoomslider".
+     */
+    baseCls: "gx-zoomslider",
+
+    /** api: config[aggressive]
+     *  ``Boolean``
+     *  If set to true, the map is zoomed as soon as the thumb is moved. Otherwise 
+     *  the map is zoomed when the thumb is released (default).
+     */
+    aggressive: false,
+    
+    /** private: property[updating]
+     *  ``Boolean``
+     *  The slider position is being updated by itself (based on map zoomend).
+     */
+    updating: false,
+    
+    /** private: method[initComponent]
+     *  Initialize the component.
+     */
+    initComponent: function() {
+        GeoExt.ZoomSlider.superclass.initComponent.call(this);
+        
+        if(this.map) {
+            if(this.map instanceof GeoExt.MapPanel) {
+                this.map = this.map.map;
+            }
+            this.bind(this.map);
+        }
+
+        if (this.aggressive === true) {
+            this.on('change', this.changeHandler, this);
+        } else {
+            this.on('changecomplete', this.changeHandler, this);
+        }
+        this.on("beforedestroy", this.unbind, this);        
+    },
+    
+    /** private: method[onRender]
+     *  Override onRender to set base css class.
+     */
+    onRender: function() {
+        GeoExt.ZoomSlider.superclass.onRender.apply(this, arguments);
+        this.el.addClass(this.baseCls);
+    },
+
+    /** private: method[afterRender]
+     *  Override afterRender because the render event is fired too early
+     *  to call update.
+     */
+    afterRender : function(){
+        Ext.Slider.superclass.afterRender.apply(this, arguments);
+        this.update();
+    },
+    
+    /** private: method[addToMapPanel]
+     *  :param panel: :class:`GeoExt.MapPanel`
+     *  
+     *  Called by a MapPanel if this component is one of the items in the panel.
+     */
+    addToMapPanel: function(panel) {
+        /**
+         * TODO: Remove this when we drop support for Ext 2.
+         * We need special treatment for Ext 2 because components don't have
+         * the "afterrender" event.  Here we wait until the render sequence
+         * finishes before binding the component to the map.
+         */
+        // START SPECIAL TREATMENT FOR EXT 2
+        if (!this.events.afterrender) {
+            this.on({
+                render: function() {
+                    window.setTimeout(
+                        this.bind.createDelegate(this, [panel.map]), 0
+                    );
+                },
+                scope: this
+            });
+        }
+        // END SPECIAL TREATMENT FOR EXT 2
+        this.on({
+            render: function() {
+                var el = this.getEl();
+                el.setStyle({
+                    position: "absolute",
+                    zIndex: panel.map.Z_INDEX_BASE.Control
+                });
+                el.on({
+                    mousedown: this.stopMouseEvents,
+                    click: this.stopMouseEvents
+                });
+            },
+            afterrender: function() {
+                this.bind(panel.map);
+            },
+            scope: this
+        });
+    },
+    
+    /** private: method[stopMouseEvents]
+     *  :param e: ``Object``
+     */
+    stopMouseEvents: function(e) {
+        e.stopEvent();
+    },
+    
+    /** private: method[removeFromMapPanel]
+     *  :param panel: :class:`GeoExt.MapPanel`
+     *  
+     *  Called by a MapPanel if this component is one of the items in the panel.
+     */
+    removeFromMapPanel: function(panel) {
+        var el = this.getEl();
+        el.un("mousedown", this.stopMouseEvents, this);
+        el.un("click", this.stopMouseEvents, this);
+        this.unbind();
+    },
+    
+    /** private: method[bind]
+     *  :param map: ``OpenLayers.Map``
+     */
+    bind: function(map) {
+        this.map = map;
+        this.map.events.on({
+            zoomend: this.update,
+            changebaselayer: this.initZoomValues,
+            scope: this
+        });
+        if(this.map.baseLayer) {
+            this.initZoomValues();
+            this.update();
+        }
+    },
+    
+    /** private: method[unbind]
+     */
+    unbind: function() {
+        if(this.map) {
+            this.map.events.un({
+                zoomend: this.update,
+                changebaselayer: this.initZoomValues,
+                scope: this
+            });
+        }
+    },
+    
+    /** private: method[initZoomValues]
+     *  Set the min/max values for the slider if not set in the config.
+     */
+    initZoomValues: function() {
+        var layer = this.map.baseLayer;
+        if(this.initialConfig.minValue === undefined) {
+            this.minValue = layer.minZoomLevel || 0;
+        }
+        if(this.initialConfig.maxValue === undefined) {
+            this.maxValue = layer.maxZoomLevel || layer.numZoomLevels - 1;
+        }
+    },
+    
+    /** api: method[getZoom]
+     *  :return: ``Number`` The map zoom level.
+     *  
+     *  Get the zoom level for the associated map based on the slider value.
+     */
+    getZoom: function() {
+        return this.getValue();
+    },
+    
+    /** api: method[getScale]
+     *  :return: ``Number`` The map scale denominator.
+     *  
+     *  Get the scale denominator for the associated map based on the slider value.
+     */
+    getScale: function() {
+        return OpenLayers.Util.getScaleFromResolution(
+            this.map.getResolutionForZoom(this.getValue()),
+            this.map.getUnits()
+        );
+    },
+    
+    /** api: method[getResolution]
+     *  :return: ``Number`` The map resolution.
+     *  
+     *  Get the resolution for the associated map based on the slider value.
+     */
+    getResolution: function() {
+        return this.map.getResolutionForZoom(this.getValue());
+    },
+    
+    /** private: method[changeHandler]
+     *  Registered as a listener for slider changecomplete.  Zooms the map.
+     */
+    changeHandler: function() {
+        if(this.map && !this.updating) {
+            this.map.zoomTo(this.getValue());
+        }
+    },
+    
+    /** private: method[update]
+     *  Registered as a listener for map zoomend.  Updates the value of the slider.
+     */
+    update: function() {
+        if(this.rendered && this.map) {
+            this.updating = true;
+            this.setValue(this.map.getZoom());
+            this.updating = false;
+        }
+    }
+
+});
+
+/** api: xtype = gx_zoomslider */
+Ext.reg('gx_zoomslider', GeoExt.ZoomSlider);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/BasicForm.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/BasicForm.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/BasicForm.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
 /**
  * @include GeoExt/widgets/form/SearchAction.js
@@ -34,6 +35,20 @@
      */
     protocol: null,
 
+    /**
+     * private: property[prevResponse]
+     * ``OpenLayers.Protocol.Response`` The response return by a call to
+     *  protocol.read method.
+     */
+    prevResponse: null,
+
+    /**
+     * api: config[autoAbort]
+     * ``Boolean`` Tells if pending requests should be aborted
+     *      when a new action is performed.
+     */
+    autoAbort: true,
+
     /** api: method[doAction]
      *  :param action: ``String or Ext.form.Action`` Either the name
      *      of the action or a ``Ext.form.Action`` instance.
@@ -46,7 +61,10 @@
      */
     doAction: function(action, options) {
         if(action == "search") {
-            options = Ext.applyIf(options || {}, {protocol: this.protocol});
+            options = Ext.applyIf(options || {}, {
+                protocol: this.protocol,
+                abortPrevious: this.autoAbort
+            });
             action = new GeoExt.form.SearchAction(this, options);
         }
         return GeoExt.form.BasicForm.superclass.doAction.call(

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/FormPanel.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/FormPanel.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/FormPanel.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
 /** api: (define)
  *  module = GeoExt.form

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/SearchAction.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/SearchAction.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form/SearchAction.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
 /** api: (define)
  *  module = GeoExt.form
@@ -40,7 +41,8 @@
  *              url: "http://publicus.opengeo.org/geoserver/wfs",
  *              featureType: "tasmania_roads",
  *              featureNS: "http://www.openplans.org/topp"
- *          })
+ *          }),
+ *          abortPrevious: true
  *      });
  *
  *      formPanel.getForm().doAction(searchAction, {
@@ -61,6 +63,9 @@
  *
  *      * form ``Ext.form.BasicForm`` A basic form instance.
  *      * options ``Object`` Options passed to the protocol'read method
+ *            One can add an abortPrevious property to these options, if set
+ *            to true, the abort method will be called on the protocol if
+ *            there's a pending request.
  *
  *      When run this action builds an ``OpenLayers.Filter`` from the form
  *      and passes this filter to its protocol's read method. The form fields
@@ -109,13 +114,19 @@
         var o = this.options;
         var f = GeoExt.form.toFilter(this.form);
         if(o.clientValidation === false || this.form.isValid()){
-            this.response = o.protocol.read(
+
+            if (o.abortPrevious && this.form.prevResponse) {
+                o.protocol.abort(this.form.prevResponse);
+            }
+
+            this.form.prevResponse = o.protocol.read(
                 Ext.applyIf({
                     filter: f,
                     callback: this.handleResponse,
                     scope: this
                 }, o)
             );
+
         } else if(o.clientValidation !== false){
             // client validation failed
             this.failureType = Ext.form.Action.CLIENT_INVALID;
@@ -130,6 +141,7 @@
      *  Handle the response to the search query.
      */
     handleResponse: function(response) {
+        this.form.prevResponse = null;
         this.response = response;
         if(response.success()) {
             this.form.afterAction(this, true);

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/form.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,26 +1,23 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * pending approval */
+ */
 
 Ext.namespace("GeoExt.form");
 
-/**
- * Function: GeoExt.form.toFilter
- * Create an {OpenLayers.Filter} object from a {Ext.form.BasicForm}
- *     or a {Ext.form.FormPanel} instance.
- *
- * Parameters:
- * form - {Ext.form.BasicForm|Ext.form.FormPanel}
- * logicalOp - {String} Either {OpenLayers.Filter.Logical.AND}
- *     or {OpenLayers.Filter.Logical.OR}, set to
- *     {OpenLayers.Filter.Logical.AND} if null or
- *     undefined.
- *
- * Returns:
- * {OpenLayers.Filter}
+/** private: function[toFilter]
+ *  :param form: ``Ext.form.BasicForm|Ext.form.FormPanel``
+ *  :param logicalOp: ``String`` Either ``OpenLayers.Filter.Logical.AND`` or
+ *      ``OpenLayers.Filter.Logical.OR``, set to
+ *      ``OpenLayers.Filter.Logical.AND`` if null or undefined
+ *      
+ *  :return: ``OpenLayers.Filter``
+ *  
+ *  Create an {OpenLayers.Filter} object from a {Ext.form.BasicForm}
+ *      or a {Ext.form.FormPanel} instance.
  */
 GeoExt.form.toFilter = function(form, logicalOp) {
     if(form instanceof Ext.form.FormPanel) {
@@ -54,10 +51,9 @@
     });
 };
 
-/**
- * Constant: GeoExt.form.omForm.FILTER_MAP
- * An object mapping operator strings as found in field names to
- *     {OpenLayers.Filter.Comparison} types.
+/** private: constant[FILTER_MAP]
+ *  An object mapping operator strings as found in field names to
+ *      ``OpenLayers.Filter.Comparison`` types.
  */
 GeoExt.form.toFilter.FILTER_MAP = {
     "eq": OpenLayers.Filter.Comparison.EQUAL_TO,

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/grid)

Deleted: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js
===================================================================
--- core/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,317 +0,0 @@
-/**
- * Copyright (c) 2008-2009 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.
- */
-
-/** api: (define)
- *  module = GeoExt.grid
- *  class = FeatureSelectionModel
- *  base_link = `Ext.grid.RowSelectionModel <http://extjs.com/deploy/dev/docs/?class=Ext.grid.RowSelectionModel>`_
- */
-
-Ext.namespace('GeoExt.grid');
-
-/** api: constructor
- *  .. class:: FeatureSelectionModel
- *
- *      A row selection model which enables automatic selection of features
- *      in the map when rows are selected in the grid and vice-versa.
- */
-
-/** api: example
- *  Sample code to create a feature grid with a feature selection model:
- *  
- *  .. code-block:: javascript
- *
- *       var gridPanel = new Ext.grid.GridPanel({
- *          title: "Feature Grid",
- *          region: "east",
- *          store: store,
- *          width: 320,
- *          columns: [{
- *              header: "Name",
- *              width: 200,
- *              dataIndex: "name"
- *          }, {
- *              header: "Elevation",
- *              width: 100,
- *              dataIndex: "elevation"
- *          }],
- *          sm: new GeoExt.grid.FeatureSelectionModel() 
- *      });
- */
-
-GeoExt.grid.FeatureSelectionModelMixin = function() {
-    return {
-        /** api: config[autoActivateControl]
-         *  ``Boolean`` If true the select feature control is activated and
-         *  deactivated when binding and unbinding. Defaults to true.
-         */
-        autoActivateControl: true,
-
-        /** api: config[layerFromStore]
-         *  ``Boolean`` If true, and if the constructor is passed neither a
-         *  layer nor a select feature control, a select feature control is
-         *  created using the layer found in the grid's store. Set it to
-         *  false if you want to manually bind the selection model to a
-         *  layer. Defaults to true.
-         */
-        layerFromStore: true,
-
-        /** api: config[selectControl]
-         *
-         *  ``OpenLayers.Control.SelectFeature`` A select feature control. If not
-         *  provided one will be created.  If provided any "layer" config option
-         *  will be ignored, and its "multiple" option will be used to configure
-         *  the selectionModel.  If an ``Object`` is provided here, it will be
-         *  passed as config to the SelectFeature constructor, and the "layer"
-         *  config option will be used for the layer.
-         */
-
-        /** private: property[selectControl] 
-         *  ``OpenLayers.Control.SelectFeature`` The select feature control 
-         *  instance. 
-         */ 
-        selectControl: null, 
-        
-        /** api: config[layer]
-         *  ``OpenLayers.Layer.Vector`` The vector layer used for the creation of
-         *  the select feature control, it must already be added to the map. If not
-         *  provided, the layer bound to the grid's store, if any, will be used.
-         */
-
-        /** private: property[bound]
-         *  ``Boolean`` Flag indicating if the selection model is bound.
-         */
-        bound: false,
-        
-        /** private: property[superclass]
-         *  ``Ext.grid.AbstractSelectionModel`` Our superclass.
-         */
-        superclass: null,
-
-        /** private */
-        constructor: function(config) {
-            config = config || {};
-            if(config.selectControl instanceof OpenLayers.Control.SelectFeature) { 
-                if(!config.singleSelect) {
-                    var ctrl = config.selectControl;
-                    config.singleSelect = !(ctrl.multiple || !!ctrl.multipleKey);
-                }
-            } else if(config.layer instanceof OpenLayers.Layer.Vector) {
-                this.selectControl = this.createSelectControl(
-                    config.layer, config.selectControl
-                );
-                delete config.layer;
-                delete config.selectControl;
-            }
-            this.superclass = arguments.callee.superclass;
-            this.superclass.constructor.call(this, config);
-        },
-        
-        /** private: method[initEvents]
-         *
-         *  Called after this.grid is defined
-         */
-        initEvents: function() {
-            this.superclass.initEvents.call(this);
-            if(this.layerFromStore) {
-                var layer = this.grid.getStore() && this.grid.getStore().layer;
-                if(layer &&
-                   !(this.selectControl instanceof OpenLayers.Control.SelectFeature)) {
-                    this.selectControl = this.createSelectControl(
-                        layer, this.selectControl
-                    );
-                }
-            }
-            if(this.selectControl) {
-                this.bind(this.selectControl);
-            }
-        },
-
-        /** private: createSelectControl
-         *  :param layer: ``OpenLayers.Layer.Vector`` The vector layer.
-         *  :param config: ``Object`` The select feature control config.
-         *
-         *  Create the select feature control.
-         */
-        createSelectControl: function(layer, config) {
-            config = config || {};
-            var singleSelect = config.singleSelect !== undefined ?
-                               config.singleSelect : this.singleSelect;
-            config = OpenLayers.Util.extend({
-                toggle: true,
-                multipleKey: singleSelect ? null :
-                    (Ext.isMac ? "metaKey" : "ctrlKey")
-            }, config);
-            var selectControl = new OpenLayers.Control.SelectFeature(
-                layer, config
-            );
-            layer.map.addControl(selectControl);
-            return selectControl;
-        },
-        
-        /** api: method[bind]
-         *
-         *  :param obj: ``OpenLayers.Layer.Vector`` or
-         *      ``OpenLayers.Control.SelectFeature`` The object this selection model
-         *      should be bound to, either a vector layer or a select feature
-         *      control.
-         *  :param options: ``Object`` An object with a "controlConfig"
-         *      property referencing the configuration object to pass to the
-         *      ``OpenLayers.Control.SelectFeature`` constructor.
-         *  :return: ``OpenLayers.Control.SelectFeature`` The select feature
-         *      control this selection model uses.
-         *
-         *  Bind the selection model to a layer or a SelectFeature control.
-         */
-        bind: function(obj, options) {
-            if(!this.bound) {
-                options = options || {};
-                this.selectControl = obj;
-                if(obj instanceof OpenLayers.Layer.Vector) {
-                    this.selectControl = this.createSelectControl(
-                        obj, options.controlConfig
-                    );
-                }
-                if(this.autoActivateControl) {
-                    this.selectControl.activate();
-                }
-                var layers = this.getLayers();
-                for(var i = 0, len = layers.length; i < len; i++) {
-                    layers[i].events.on({
-                        featureselected: this.featureSelected,
-                        featureunselected: this.featureUnselected,
-                        scope: this
-                    });
-                }
-                this.on("rowselect", this.rowSelected, this);
-                this.on("rowdeselect", this.rowDeselected, this);
-                this.bound = true;
-            }
-            return this.selectControl;
-        },
-        
-        /** api: method[unbind]
-         *  :return: ``OpenLayers.Control.SelectFeature`` The select feature
-         *      control this selection model used.
-         *
-         *  Unbind the selection model from the layer or SelectFeature control.
-         */
-        unbind: function() {
-            var selectControl = this.selectControl;
-            if(this.bound) {
-                var layers = this.getLayers();
-                for(var i = 0, len = layers.length; i < len; i++) {
-                    layers[i].events.un({
-                        featureselected: this.featureSelected,
-                        featureunselected: this.featureUnselected,
-                        scope: this
-                    });
-                }
-                this.un("rowselect", this.rowSelected, this);
-                this.un("rowdeselect", this.rowDeselected, this);
-                if(this.autoActivateControl) {
-                    selectControl.deactivate();
-                }
-                this.selectControl = null;
-                this.bound = false;
-            }
-            return selectControl;
-        },
-        
-        /** private: method[featureSelected]
-         *  :param evt: ``Object`` An object with a feature property referencing
-         *                         the selected feature.
-         */
-        featureSelected: function(evt) {
-            if(!this._selecting) {
-                var store = this.grid.store;
-                var row = store.findBy(function(record, id) {
-                    return record.data.feature == evt.feature;
-                });
-                if(row != -1 && !this.isSelected(row)) {
-                    this._selecting = true;
-                    this.selectRow(row, !this.singleSelect);
-                    this._selecting = false;
-                    // focus the row in the grid to ensure it is visible
-                    this.grid.getView().focusRow(row);
-                }
-            }
-        },
-        
-        /** private: method[featureUnselected]
-         *  :param evt: ``Object`` An object with a feature property referencing
-         *                         the unselected feature.
-         */
-        featureUnselected: function(evt) {
-            if(!this._selecting) {
-                var store = this.grid.store;
-                var row = store.findBy(function(record, id) {
-                    return record.data.feature == evt.feature;
-                });
-                if(row != -1 && this.isSelected(row)) {
-                    this._selecting = true;
-                    this.deselectRow(row); 
-                    this._selecting = false;
-                    this.grid.getView().focusRow(row);
-                }
-            }
-        },
-        
-        /** private: method[rowSelected]
-         *  :param model: ``Ext.grid.RowSelectModel`` The row select model.
-         *  :param row: ``Integer`` The row index.
-         *  :param record: ``Ext.data.Record`` The record.
-         */
-        rowSelected: function(model, row, record) {
-            var feature = record.data.feature;
-            if(!this._selecting && feature) {
-                var layers = this.getLayers();
-                for(var i = 0, len = layers.length; i < len; i++) {
-                    if(layers[i].selectedFeatures.indexOf(feature) == -1) {
-                        this._selecting = true;
-                        this.selectControl.select(feature);
-                        this._selecting = false;
-                        break;
-                    }
-                }
-             }
-        },
-        
-        /** private: method[rowDeselected]
-         *  :param model: ``Ext.grid.RowSelectModel`` The row select model.
-         *  :param row: ``Integer`` The row index.
-         *  :param record: ``Ext.data.Record`` The record.
-         */
-        rowDeselected: function(model, row, record) {
-            var feature = record.data.feature;
-            if(!this._selecting && feature) {
-                var layers = this.getLayers();
-                for(var i = 0, len = layers.length; i < len; i++) {
-                    if(layers[i].selectedFeatures.indexOf(feature) != -1) {
-                        this._selecting = true;
-                        this.selectControl.unselect(feature);
-                        this._selecting = false;
-                        break;
-                    }
-                }
-            }
-        },
-
-        /** private: method[getLayers]
-         *  Return the layers attached to the select feature control.
-         */
-        getLayers: function() {
-            return this.selectControl.layers || [this.selectControl.layer];
-        }
-    };
-};
-
-GeoExt.grid.FeatureSelectionModel = Ext.extend(
-    Ext.grid.RowSelectionModel,
-    new GeoExt.grid.FeatureSelectionModelMixin
-);

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/grid/FeatureSelectionModel.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,317 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt.grid
+ *  class = FeatureSelectionModel
+ *  base_link = `Ext.grid.RowSelectionModel <http://extjs.com/deploy/dev/docs/?class=Ext.grid.RowSelectionModel>`_
+ */
+
+Ext.namespace('GeoExt.grid');
+
+/** api: constructor
+ *  .. class:: FeatureSelectionModel
+ *
+ *      A row selection model which enables automatic selection of features
+ *      in the map when rows are selected in the grid and vice-versa.
+ */
+
+/** api: example
+ *  Sample code to create a feature grid with a feature selection model:
+ *  
+ *  .. code-block:: javascript
+ *
+ *       var gridPanel = new Ext.grid.GridPanel({
+ *          title: "Feature Grid",
+ *          region: "east",
+ *          store: store,
+ *          width: 320,
+ *          columns: [{
+ *              header: "Name",
+ *              width: 200,
+ *              dataIndex: "name"
+ *          }, {
+ *              header: "Elevation",
+ *              width: 100,
+ *              dataIndex: "elevation"
+ *          }],
+ *          sm: new GeoExt.grid.FeatureSelectionModel() 
+ *      });
+ */
+
+GeoExt.grid.FeatureSelectionModelMixin = function() {
+    return {
+        /** api: config[autoActivateControl]
+         *  ``Boolean`` If true the select feature control is activated and
+         *  deactivated when binding and unbinding. Defaults to true.
+         */
+        autoActivateControl: true,
+
+        /** api: config[layerFromStore]
+         *  ``Boolean`` If true, and if the constructor is passed neither a
+         *  layer nor a select feature control, a select feature control is
+         *  created using the layer found in the grid's store. Set it to
+         *  false if you want to manually bind the selection model to a
+         *  layer. Defaults to true.
+         */
+        layerFromStore: true,
+
+        /** api: config[selectControl]
+         *
+         *  ``OpenLayers.Control.SelectFeature`` A select feature control. If not
+         *  provided one will be created.  If provided any "layer" config option
+         *  will be ignored, and its "multiple" option will be used to configure
+         *  the selectionModel.  If an ``Object`` is provided here, it will be
+         *  passed as config to the SelectFeature constructor, and the "layer"
+         *  config option will be used for the layer.
+         */
+
+        /** private: property[selectControl] 
+         *  ``OpenLayers.Control.SelectFeature`` The select feature control 
+         *  instance. 
+         */ 
+        selectControl: null, 
+        
+        /** api: config[layer]
+         *  ``OpenLayers.Layer.Vector`` The vector layer used for the creation of
+         *  the select feature control, it must already be added to the map. If not
+         *  provided, the layer bound to the grid's store, if any, will be used.
+         */
+
+        /** private: property[bound]
+         *  ``Boolean`` Flag indicating if the selection model is bound.
+         */
+        bound: false,
+        
+        /** private: property[superclass]
+         *  ``Ext.grid.AbstractSelectionModel`` Our superclass.
+         */
+        superclass: null,
+
+        /** private */
+        constructor: function(config) {
+            config = config || {};
+            if(config.selectControl instanceof OpenLayers.Control.SelectFeature) { 
+                if(!config.singleSelect) {
+                    var ctrl = config.selectControl;
+                    config.singleSelect = !(ctrl.multiple || !!ctrl.multipleKey);
+                }
+            } else if(config.layer instanceof OpenLayers.Layer.Vector) {
+                this.selectControl = this.createSelectControl(
+                    config.layer, config.selectControl
+                );
+                delete config.layer;
+                delete config.selectControl;
+            }
+            this.superclass = arguments.callee.superclass;
+            this.superclass.constructor.call(this, config);
+        },
+        
+        /** private: method[initEvents]
+         *
+         *  Called after this.grid is defined
+         */
+        initEvents: function() {
+            this.superclass.initEvents.call(this);
+            if(this.layerFromStore) {
+                var layer = this.grid.getStore() && this.grid.getStore().layer;
+                if(layer &&
+                   !(this.selectControl instanceof OpenLayers.Control.SelectFeature)) {
+                    this.selectControl = this.createSelectControl(
+                        layer, this.selectControl
+                    );
+                }
+            }
+            if(this.selectControl) {
+                this.bind(this.selectControl);
+            }
+        },
+
+        /** private: createSelectControl
+         *  :param layer: ``OpenLayers.Layer.Vector`` The vector layer.
+         *  :param config: ``Object`` The select feature control config.
+         *
+         *  Create the select feature control.
+         */
+        createSelectControl: function(layer, config) {
+            config = config || {};
+            var singleSelect = config.singleSelect !== undefined ?
+                               config.singleSelect : this.singleSelect;
+            config = OpenLayers.Util.extend({
+                toggle: true,
+                multipleKey: singleSelect ? null :
+                    (Ext.isMac ? "metaKey" : "ctrlKey")
+            }, config);
+            var selectControl = new OpenLayers.Control.SelectFeature(
+                layer, config
+            );
+            layer.map.addControl(selectControl);
+            return selectControl;
+        },
+        
+        /** api: method[bind]
+         *
+         *  :param obj: ``OpenLayers.Layer.Vector`` or
+         *      ``OpenLayers.Control.SelectFeature`` The object this selection model
+         *      should be bound to, either a vector layer or a select feature
+         *      control.
+         *  :param options: ``Object`` An object with a "controlConfig"
+         *      property referencing the configuration object to pass to the
+         *      ``OpenLayers.Control.SelectFeature`` constructor.
+         *  :return: ``OpenLayers.Control.SelectFeature`` The select feature
+         *      control this selection model uses.
+         *
+         *  Bind the selection model to a layer or a SelectFeature control.
+         */
+        bind: function(obj, options) {
+            if(!this.bound) {
+                options = options || {};
+                this.selectControl = obj;
+                if(obj instanceof OpenLayers.Layer.Vector) {
+                    this.selectControl = this.createSelectControl(
+                        obj, options.controlConfig
+                    );
+                }
+                if(this.autoActivateControl) {
+                    this.selectControl.activate();
+                }
+                var layers = this.getLayers();
+                for(var i = 0, len = layers.length; i < len; i++) {
+                    layers[i].events.on({
+                        featureselected: this.featureSelected,
+                        featureunselected: this.featureUnselected,
+                        scope: this
+                    });
+                }
+                this.on("rowselect", this.rowSelected, this);
+                this.on("rowdeselect", this.rowDeselected, this);
+                this.bound = true;
+            }
+            return this.selectControl;
+        },
+        
+        /** api: method[unbind]
+         *  :return: ``OpenLayers.Control.SelectFeature`` The select feature
+         *      control this selection model used.
+         *
+         *  Unbind the selection model from the layer or SelectFeature control.
+         */
+        unbind: function() {
+            var selectControl = this.selectControl;
+            if(this.bound) {
+                var layers = this.getLayers();
+                for(var i = 0, len = layers.length; i < len; i++) {
+                    layers[i].events.un({
+                        featureselected: this.featureSelected,
+                        featureunselected: this.featureUnselected,
+                        scope: this
+                    });
+                }
+                this.un("rowselect", this.rowSelected, this);
+                this.un("rowdeselect", this.rowDeselected, this);
+                if(this.autoActivateControl) {
+                    selectControl.deactivate();
+                }
+                this.selectControl = null;
+                this.bound = false;
+            }
+            return selectControl;
+        },
+        
+        /** private: method[featureSelected]
+         *  :param evt: ``Object`` An object with a feature property referencing
+         *                         the selected feature.
+         */
+        featureSelected: function(evt) {
+            if(!this._selecting) {
+                var store = this.grid.store;
+                var row = store.findBy(function(record, id) {
+                    return record.data.feature == evt.feature;
+                });
+                if(row != -1 && !this.isSelected(row)) {
+                    this._selecting = true;
+                    this.selectRow(row, !this.singleSelect);
+                    this._selecting = false;
+                    // focus the row in the grid to ensure it is visible
+                    this.grid.getView().focusRow(row);
+                }
+            }
+        },
+        
+        /** private: method[featureUnselected]
+         *  :param evt: ``Object`` An object with a feature property referencing
+         *                         the unselected feature.
+         */
+        featureUnselected: function(evt) {
+            if(!this._selecting) {
+                var store = this.grid.store;
+                var row = store.findBy(function(record, id) {
+                    return record.data.feature == evt.feature;
+                });
+                if(row != -1 && this.isSelected(row)) {
+                    this._selecting = true;
+                    this.deselectRow(row); 
+                    this._selecting = false;
+                    this.grid.getView().focusRow(row);
+                }
+            }
+        },
+        
+        /** private: method[rowSelected]
+         *  :param model: ``Ext.grid.RowSelectModel`` The row select model.
+         *  :param row: ``Integer`` The row index.
+         *  :param record: ``Ext.data.Record`` The record.
+         */
+        rowSelected: function(model, row, record) {
+            var feature = record.data.feature;
+            if(!this._selecting && feature) {
+                var layers = this.getLayers();
+                for(var i = 0, len = layers.length; i < len; i++) {
+                    if(layers[i].selectedFeatures.indexOf(feature) == -1) {
+                        this._selecting = true;
+                        this.selectControl.select(feature);
+                        this._selecting = false;
+                        break;
+                    }
+                }
+             }
+        },
+        
+        /** private: method[rowDeselected]
+         *  :param model: ``Ext.grid.RowSelectModel`` The row select model.
+         *  :param row: ``Integer`` The row index.
+         *  :param record: ``Ext.data.Record`` The record.
+         */
+        rowDeselected: function(model, row, record) {
+            var feature = record.data.feature;
+            if(!this._selecting && feature) {
+                var layers = this.getLayers();
+                for(var i = 0, len = layers.length; i < len; i++) {
+                    if(layers[i].selectedFeatures.indexOf(feature) != -1) {
+                        this._selecting = true;
+                        this.selectControl.unselect(feature);
+                        this._selecting = false;
+                        break;
+                    }
+                }
+            }
+        },
+
+        /** private: method[getLayers]
+         *  Return the layers attached to the select feature control.
+         */
+        getLayers: function() {
+            return this.selectControl.layers || [this.selectControl.layer];
+        }
+    };
+};
+
+GeoExt.grid.FeatureSelectionModel = Ext.extend(
+    Ext.grid.RowSelectionModel,
+    new GeoExt.grid.FeatureSelectionModelMixin
+);

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tips)

Deleted: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js
===================================================================
--- core/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,78 +0,0 @@
-/**
- * Copyright (c) 2008-2009 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.
- */
-
-/**
- * @requires GeoExt/widgets/tips/SliderTip.js
- */
-
-/** api: (extends)
- *  GeoExt/widgets/tips/SliderTip.js
- */
-
-/** api: (define)
- *  module = GeoExt
- *  class = LayerOpacitySliderTip
- *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
- */
-Ext.namespace("GeoExt");
-
-/** api: example
- *  Sample code to create a slider tip to display scale and resolution:
- *
- *  .. code-block:: javascript
- *
- *      var slider = new GeoExt.LayerOpacitySlider({
- *          renderTo: document.body,
- *          width: 200,
- *          layer: layer,
- *          plugins: new GeoExt.LayerOpacitySliderTip({
- *              template: "Opacity: {opacity}%"
- *          })
- *      });
- */
-
-/** api: constructor
- *  .. class:: LayerOpacitySliderTip(config)
- *
- *      Create a slider tip displaying :class:`GeoExt.LayerOpacitySlider` values.
- */
-GeoExt.LayerOpacitySliderTip = Ext.extend(GeoExt.SliderTip, {
-
-    /** api: config[template]
-     *  ``String``
-     *  Template for the tip. Can be customized using the following keywords in
-     *  curly braces:
-     *
-     *  * ``opacity`` - the opacity value in percent.
-     */
-    template: '<div>{opacity}%</div>',
-
-    /** private: property[compiledTemplate]
-     *  ``Ext.Template``
-     *  The template compiled from the ``template`` string on init.
-     */
-    compiledTemplate: null,
-
-    /** private: method[init]
-     *  Called to initialize the plugin.
-     */
-    init: function(slider) {
-        this.compiledTemplate = new Ext.Template(this.template);
-        GeoExt.LayerOpacitySliderTip.superclass.init.call(this, slider);
-    },
-
-    /** private: method[getText]
-     *  :param slider: ``Ext.Slider`` The slider this tip is attached to.
-     */
-    getText: function(slider) {
-        var data = {
-            opacity: slider.getValue()
-        };
-        return this.compiledTemplate.apply(data);
-    }
-});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/LayerOpacitySliderTip.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @requires GeoExt/widgets/tips/SliderTip.js
+ */
+
+/** api: (extends)
+ *  GeoExt/widgets/tips/SliderTip.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LayerOpacitySliderTip
+ *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to create a slider tip to display scale and resolution:
+ *
+ *  .. code-block:: javascript
+ *
+ *      var slider = new GeoExt.LayerOpacitySlider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          layer: layer,
+ *          plugins: new GeoExt.LayerOpacitySliderTip({
+ *              template: "Opacity: {opacity}%"
+ *          })
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: LayerOpacitySliderTip(config)
+ *
+ *      Create a slider tip displaying :class:`GeoExt.LayerOpacitySlider` values.
+ */
+GeoExt.LayerOpacitySliderTip = Ext.extend(GeoExt.SliderTip, {
+
+    /** api: config[template]
+     *  ``String``
+     *  Template for the tip. Can be customized using the following keywords in
+     *  curly braces:
+     *
+     *  * ``opacity`` - the opacity value in percent.
+     */
+    template: '<div>{opacity}%</div>',
+
+    /** private: property[compiledTemplate]
+     *  ``Ext.Template``
+     *  The template compiled from the ``template`` string on init.
+     */
+    compiledTemplate: null,
+
+    /** private: method[init]
+     *  Called to initialize the plugin.
+     */
+    init: function(slider) {
+        this.compiledTemplate = new Ext.Template(this.template);
+        GeoExt.LayerOpacitySliderTip.superclass.init.call(this, slider);
+    },
+
+    /** private: method[getText]
+     *  :param slider: ``Ext.Slider`` The slider this tip is attached to.
+     */
+    getText: function(slider) {
+        var data = {
+            opacity: slider.getValue()
+        };
+        return this.compiledTemplate.apply(data);
+    }
+});

Deleted: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js
===================================================================
--- core/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,127 +0,0 @@
-/**
- * Copyright (c) 2008-2009 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.
- */
-
-/** api: (define)
- *  module = GeoExt
- *  class = SliderTip
- *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
- */
-Ext.namespace("GeoExt");
-
-/** api: example
- *  Sample code to create a slider tip to display slider value on hover:
- * 
- *  .. code-block:: javascript
- *     
- *      var slider = new Ext.Slider({
- *          renderTo: document.body,
- *          width: 200,
- *          plugins: new GeoExt.SliderTip()
- *      });
- */
-
-/** api: constructor
- *  .. class:: SliderTip(config)
- *   
- *      Create a slider tip displaying ``Ext.Slider`` values over slider thumbs.
- */
-GeoExt.SliderTip = Ext.extend(Ext.Tip, {
-
-    /** api: config[hover]
-     *  ``Boolean``
-     *  Display the tip when hovering over the thumb.  If ``false``, tip will
-     *  only be displayed while dragging.  Default is ``true``.
-     */
-    hover: true,
-    
-    /** api: config[minWidth]
-     *  ``Number``
-     *  Minimum width of the tip.  Default is 10.
-     */
-    minWidth: 10,
-
-    /** api: config[minWidth]
-     *  ``Number``
-     *  Minimum width of the tip.  Default is 10.
-     */
-    minWidth: 10,
-    
-    /** api: config[offsets]
-     *  ``Array(Number)``
-     *  A two item list that provides x, y offsets for the tip.  Default is
-     *  [0, -10].
-     */
-    offsets : [0, -10],
-    
-    /** private: property[dragging]
-     *  ``Boolean``
-     *  The thumb is currently being dragged.
-     */
-    dragging: false,
-
-    /** private: method[init]
-     *  :param slider: ``Ext.Slider``
-     *  
-     *  Called when the plugin is initialized.
-     */
-    init: function(slider) {
-        slider.on({
-            dragstart: this.onSlide,
-            drag: this.onSlide,
-            dragend: this.hide,
-            destroy: this.destroy,
-            scope: this
-        });
-        if(this.hover) {
-            slider.on("render", this.registerThumbListeners, this);
-        }
-        this.slider = slider;
-    },
-
-    /** private: method[registerThumbListeners]
-     *  Set as a listener for 'render' if hover is true.
-     */
-    registerThumbListeners: function() {
-        this.slider.thumb.on({
-            "mouseover": function() {
-                this.onSlide(this.slider);
-                this.dragging = false;
-            },
-            "mouseout": function() {
-                if(!this.dragging) {
-                    this.hide.apply(this, arguments);
-                }
-            },
-            scope: this
-        });
-    },
-
-    /** private: method[onSlide]
-     *  :param slider: ``Ext.Slider``
-     *
-     *  Listener for dragstart and drag.
-     */
-    onSlide: function(slider) {
-        this.dragging = true;
-        this.show();
-        this.body.update(this.getText(slider));
-        this.doAutoWidth();
-        this.el.alignTo(slider.thumb, 'b-t?', this.offsets);
-    },
-
-    /** api: config[getText]
-     *  :param slider: ``Ext.Slider``
-     *
-     *  ``Function``
-     *  Function that generates the string value to be displayed in the tip.  By
-     *  default, the return from slider.getValue() is displayed.
-     */
-    getText : function(slider) {
-        return slider.getValue();
-    }
-});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/SliderTip.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = SliderTip
+ *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to create a slider tip to display slider value on hover:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var slider = new Ext.Slider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          plugins: new GeoExt.SliderTip()
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: SliderTip(config)
+ *   
+ *      Create a slider tip displaying ``Ext.Slider`` values over slider thumbs.
+ */
+GeoExt.SliderTip = Ext.extend(Ext.Tip, {
+
+    /** api: config[hover]
+     *  ``Boolean``
+     *  Display the tip when hovering over the thumb.  If ``false``, tip will
+     *  only be displayed while dragging.  Default is ``true``.
+     */
+    hover: true,
+    
+    /** api: config[minWidth]
+     *  ``Number``
+     *  Minimum width of the tip.  Default is 10.
+     */
+    minWidth: 10,
+
+    /** api: config[minWidth]
+     *  ``Number``
+     *  Minimum width of the tip.  Default is 10.
+     */
+    minWidth: 10,
+    
+    /** api: config[offsets]
+     *  ``Array(Number)``
+     *  A two item list that provides x, y offsets for the tip.  Default is
+     *  [0, -10].
+     */
+    offsets : [0, -10],
+    
+    /** private: property[dragging]
+     *  ``Boolean``
+     *  The thumb is currently being dragged.
+     */
+    dragging: false,
+
+    /** private: method[init]
+     *  :param slider: ``Ext.Slider``
+     *  
+     *  Called when the plugin is initialized.
+     */
+    init: function(slider) {
+        slider.on({
+            dragstart: this.onSlide,
+            drag: this.onSlide,
+            dragend: this.hide,
+            destroy: this.destroy,
+            scope: this
+        });
+        if(this.hover) {
+            slider.on("render", this.registerThumbListeners, this);
+        }
+        this.slider = slider;
+    },
+
+    /** private: method[registerThumbListeners]
+     *  Set as a listener for 'render' if hover is true.
+     */
+    registerThumbListeners: function() {
+        this.slider.thumb.on({
+            "mouseover": function() {
+                this.onSlide(this.slider);
+                this.dragging = false;
+            },
+            "mouseout": function() {
+                if(!this.dragging) {
+                    this.hide.apply(this, arguments);
+                }
+            },
+            scope: this
+        });
+    },
+
+    /** private: method[onSlide]
+     *  :param slider: ``Ext.Slider``
+     *
+     *  Listener for dragstart and drag.
+     */
+    onSlide: function(slider) {
+        this.dragging = true;
+        this.show();
+        this.body.update(this.getText(slider));
+        this.doAutoWidth();
+        this.el.alignTo(slider.thumb, 'b-t?', this.offsets);
+    },
+
+    /** api: config[getText]
+     *  :param slider: ``Ext.Slider``
+     *
+     *  ``Function``
+     *  Function that generates the string value to be displayed in the tip.  By
+     *  default, the return from slider.getValue() is displayed.
+     */
+    getText : function(slider) {
+        return slider.getValue();
+    }
+});

Deleted: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js
===================================================================
--- core/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,84 +0,0 @@
-/**
- * Copyright (c) 2008-2009 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.
- */
-
-/**
- * @requires GeoExt/widgets/tips/SliderTip.js
- */
-
-/** api: (extends)
- *  GeoExt/widgets/tips/SliderTip.js
- */
-
-/** api: (define)
- *  module = GeoExt
- *  class = ZoomSliderTip
- *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
- */
-Ext.namespace("GeoExt");
-
-/** api: example
- *  Sample code to create a slider tip to display scale and resolution:
- * 
- *  .. code-block:: javascript
- *     
- *      var slider = new GeoExt.ZoomSlider({
- *          renderTo: document.body,
- *          width: 200,
- *          map: map,
- *          plugins: new GeoExt.ZoomSliderTip({
- *              template: "Scale: 1 : {scale}<br>Resolution: {resolution}"
- *          })
- *      });
- */
-
-/** api: constructor
- *  .. class:: ZoomSliderTip(config)
- *   
- *      Create a slider tip displaying :class:`GeoExt.ZoomSlider` values.
- */
-GeoExt.ZoomSliderTip = Ext.extend(GeoExt.SliderTip, {
-    
-    /** api: config[template]
-     *  ``String``
-     *  Template for the tip. Can be customized using the following keywords in
-     *  curly braces:
-     *  
-     *  * ``zoom`` - the zoom level
-     *  * ``resolution`` - the resolution
-     *  * ``scale`` - the scale denominator
-     */
-    template: '<div>Zoom Level: {zoom}</div>' +
-        '<div>Resolution: {resolution}</div>' +
-        '<div>Scale: 1 : {scale}</div>',
-    
-    /** private: property[compiledTemplate]
-     *  ``Ext.Template``
-     *  The template compiled from the ``template`` string on init.
-     */
-    compiledTemplate: null,
-    
-    /** private: method[init]
-     *  Called to initialize the plugin.
-     */
-    init: function(slider) {
-        this.compiledTemplate = new Ext.Template(this.template);
-        GeoExt.ZoomSliderTip.superclass.init.call(this, slider);
-    },
-    
-    /** private: method[getText]
-     *  :param slider: ``Ext.Slider`` The slider this tip is attached to.
-     */
-    getText: function(slider) {
-        var data = {
-            zoom: slider.getZoom(),
-            resolution: slider.getResolution(),
-            scale: Math.round(slider.getScale()) 
-        };
-        return this.compiledTemplate.apply(data);
-    }
-});

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tips/ZoomSliderTip.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @requires GeoExt/widgets/tips/SliderTip.js
+ */
+
+/** api: (extends)
+ *  GeoExt/widgets/tips/SliderTip.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = ZoomSliderTip
+ *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to create a slider tip to display scale and resolution:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var slider = new GeoExt.ZoomSlider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          map: map,
+ *          plugins: new GeoExt.ZoomSliderTip({
+ *              template: "Scale: 1 : {scale}<br>Resolution: {resolution}"
+ *          })
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: ZoomSliderTip(config)
+ *   
+ *      Create a slider tip displaying :class:`GeoExt.ZoomSlider` values.
+ */
+GeoExt.ZoomSliderTip = Ext.extend(GeoExt.SliderTip, {
+    
+    /** api: config[template]
+     *  ``String``
+     *  Template for the tip. Can be customized using the following keywords in
+     *  curly braces:
+     *  
+     *  * ``zoom`` - the zoom level
+     *  * ``resolution`` - the resolution
+     *  * ``scale`` - the scale denominator
+     */
+    template: '<div>Zoom Level: {zoom}</div>' +
+        '<div>Resolution: {resolution}</div>' +
+        '<div>Scale: 1 : {scale}</div>',
+    
+    /** private: property[compiledTemplate]
+     *  ``Ext.Template``
+     *  The template compiled from the ``template`` string on init.
+     */
+    compiledTemplate: null,
+    
+    /** private: method[init]
+     *  Called to initialize the plugin.
+     */
+    init: function(slider) {
+        this.compiledTemplate = new Ext.Template(this.template);
+        GeoExt.ZoomSliderTip.superclass.init.call(this, slider);
+    },
+    
+    /** private: method[getText]
+     *  :param slider: ``Ext.Slider`` The slider this tip is attached to.
+     */
+    getText: function(slider) {
+        var data = {
+            zoom: slider.getZoom(),
+            resolution: slider.getResolution(),
+            scale: Math.round(slider.getScale()) 
+        };
+        return this.compiledTemplate.apply(data);
+    }
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/BaseLayerContainer.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/BaseLayerContainer.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/BaseLayerContainer.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,5 +1,9 @@
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * Copyright (c) 2008-2009 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.
  */
 
 /**
@@ -7,59 +11,52 @@
  */
 Ext.namespace("GeoExt.tree");
 
-/**
- * Class: GeoExt.tree.BaseLayerContainer
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = BaseLayerContainer
+ */
+
+/** api: (extends)
+ * GeoExt/widgets/tree/LayerContainer.js
+ */
+
+/** api: constructor
+ *  .. class:: BaseLayerContainer
  * 
- * A layer container that will collect all base layers of an OpenLayers map.
- * Only layers that have displayInLayerSwitcher set to true will be included.
+ *     A layer container that will collect all base layers of an OpenLayers
+ *     map. Only layers that have displayInLayerSwitcher set to true will be
+ *     included. The childrens' iconCls defaults to "gx-tree-baselayer-icon".
+ *     
+ *     Children will be rendered with a radio button instead of a checkbox,
+ *     showing the user that only one base layer can be active at a time.
  * 
- * To use this node type in JSON config, set nodeType to
- * "olBaseLayerContainer".
- * 
- * Inherits from:
- * - <GeoExt.tree.LayerContainer>
+ *     To use this node type in ``TreePanel`` config, set nodeType to
+ *     "gx_baselayercontainer".
  */
 GeoExt.tree.BaseLayerContainer = Ext.extend(GeoExt.tree.LayerContainer, {
 
-    /**
-     * Constructor: GeoExt.tree.BaseLayerContainer
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
+     *  Private constructor override.
      */
     constructor: function(config) {
-        config.text = config.text || "Base Layer";
-        GeoExt.tree.BaseLayerContainer.superclass.constructor.apply(this, arguments);
-    },
+        config = Ext.applyIf(config || {}, {
+            text: "Base Layer",
+            loader: {}
+        });
+        config.loader = Ext.applyIf(config.loader, {
+            baseAttrs: Ext.applyIf(config.loader.baseAttrs || {}, {
+                iconCls: 'gx-tree-baselayer-icon',
+                checkedGroup: 'baselayer'
+            }),
+            filter: function(record) {
+                var layer = record.get("layer");
+                return layer.displayInLayerSwitcher === true &&
+                    layer.isBaseLayer === true;
+            }
+        });
 
-    /**
-     * Method: addLayerNode
-     * Adds a child node representing a base layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to add a node for
-     */
-    addLayerNode: function(layerRecord) {
-        var layer = layerRecord.get("layer");
-        if (layer.isBaseLayer == true) {
-            GeoExt.tree.BaseLayerContainer.superclass.addLayerNode.call(this,
-                layerRecord);
-        }
-    },
-    
-    /**
-     * Method: removeLayerNode
-     * Removes a child node representing a base layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to remove the node for
-     */
-    removeLayerNode: function(layerRecord) {
-        var layer = layerRecord.get("layer");
-        if (layer.isBaseLayer == true) {
-            GeoExt.tree.BaseLayerContainer.superclass.removeLayerNode.call(this,
-                layerRecord);
-    	}
+        GeoExt.tree.BaseLayerContainer.superclass.constructor.call(this,
+            config);
     }
 });
 

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerContainer.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerContainer.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerContainer.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,224 +1,99 @@
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * Copyright (c) 2008-2009 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.
  */
 
 /**
- * @include GeoExt/widgets/tree/LayerNode.js
+ * @include GeoExt/widgets/tree/LayerLoader.js
  */
 Ext.namespace("GeoExt.tree");
 
 /** api: (define)
  *  module = GeoExt.tree
  *  class = LayerContainer
- *  base_link = `Ext.tree.TreeNode <http://extjs.com/deploy/dev/docs/?class=Ext.tree.TreeNode>`_
+ *  base_link = `Ext.tree.AsyncTreeNode <http://extjs.com/deploy/dev/docs/?class=Ext.tree.AsyncTreeNode>`_
  */
 
 /** api: constructor
  *  .. class:: LayerContainer
  * 
- *      A subclass of ``Ext.tree.TreeNode`` that will collect all layers of an
+ *      A subclass of ``Ext.tree.AsyncTreeNode`` that will collect all layers of an
  *      OpenLayers map. Only layers that have displayInLayerSwitcher set to true
- *      will be included. The childrens' iconCls will be set to "baselayer-icon"
- *      for base layers, and to "layer-icon" for overlay layers.
+ *      will be included. The childrens' iconCls defaults to
+ *      "gx-tree-layer-icon".
+ *      
+ *      Note: if this container is loaded by an ``Ext.tree.TreeLoader``, the
+ *      ``applyLoader`` config option of that loader needs to be set to
+ *      "false". Also note that the list of available uiProviders will be
+ *      taken from the ownerTree if this container's loader is configured
+ *      without one.
  * 
  *      To use this node type in ``TreePanel`` config, set nodeType to
- *      "olLayerContainer".
+ *      "gx_layercontainer".
  */
-GeoExt.tree.LayerContainer = Ext.extend(Ext.tree.TreeNode, {
+GeoExt.tree.LayerContainer = Ext.extend(Ext.tree.AsyncTreeNode, {
     
-    /** api: config[layerStore]
-     *  :class:`GeoExt.data.LayerStore`
-     *  The layer store containing layers to be displayed in the container.
+    /** api: config[loader]
+     *  :class:`GeoExt.tree.LayerLoader` or ``Object`` The loader to use with
+     *  this container. If an ``Object`` is provided, a
+     *  :class:`GeoExt.tree.LayerLoader`, configured with the the properties
+     *  from the provided object, will be created. 
      */
-    layerStore: null,
     
-    /** api: config[defaults]
-     *  ``Object``
-     *  A configuration object passed to all nodes that this container creates.
+    /** api: config[layerStore]
+     *  :class:`GeoExt.data.LayerStore` The layer store containing layers to be
+     *  displayed in the container. If loader is not provided or provided as
+     *  ``Object``, this property will be set as the store option of the
+     *  loader. Otherwise it will be ignored.
      */
-    defaults: null,
-
+    
     /** private: method[constructor]
      *  Private constructor override.
      */
     constructor: function(config) {
-        this.layerStore = config.layerStore;
-        this.defaults = config.defaults;
-        GeoExt.tree.LayerContainer.superclass.constructor.apply(this, arguments);
+        config = Ext.applyIf(config || {}, {
+            text: "Layers"
+        });
+        this.loader = config.loader instanceof GeoExt.tree.LayerLoader ?
+            config.loader :
+            new GeoExt.tree.LayerLoader(Ext.applyIf(config.loader || {}, {
+                store: config.layerStore
+            }));
+        
+        GeoExt.tree.LayerContainer.superclass.constructor.call(this, config);
     },
-
-    /** private: method[render]
-     *  :param bulkRender: ``Boolean``
-     */
-    render: function(bulkRender) {
-        if (!this.rendered) {
-            if(!this.layerStore) {
-                this.layerStore = GeoExt.MapPanel.guess().layers;
-            }
-            this.layerStore.each(function(record) {
-                this.addLayerNode(record);
-            }, this);
-            this.layerStore.on({
-                "add": this.onStoreAdd,
-                "remove": this.onStoreRemove,
-                scope: this
-            });
-        }
-        GeoExt.tree.LayerContainer.superclass.render.call(this, bulkRender);
-    },
     
-    /** private: method[onStoreAdd]
-     *  :param store: ``Ext.data.Store``
-     *  :param records: ``Array(Ext.data.Record)``
-     *  :param index: ``Number``
-     *  
-     *  Listener for the store's add event.
-     */
-    onStoreAdd: function(store, records, index) {
-        if(!this._reordering) {
-            var nodeIndex = this.recordIndexToNodeIndex(index);
-            for(var i=0; i<records.length; ++i) {
-                this.addLayerNode(records[i], nodeIndex);
-            }
-        }
-    },
-    
-    /** private: method[onStoreRemove]
-     *  :param store: ``Ext.data.Store``
-     *  :param record: ``Ext.data.Record``
-     *  :param index: ``Number``
-     *  
-     *  Listener for the store's remove event.
-     */
-    onStoreRemove: function(store, record, index) {
-        if(!this._reordering) {
-            this.removeLayerNode(record);
-        }
-    },
-
-    /** private: method[onDestroy]
-     */
-    onDestroy: function() {
-        if(this.layerStore) {
-            this.layerStore.un("add", this.onStoreAdd, this);
-            this.layerStore.un("remove", this.onStoreRemove, this);
-        }
-        GeoExt.tree.LayerContainer.superclass.onDestroy.apply(this, arguments);
-    },
-    
     /** private: method[recordIndexToNodeIndex]
      *  :param index: ``Number`` The record index in the layer store.
      *  :return: ``Number`` The appropriate child node index for the record.
      */
     recordIndexToNodeIndex: function(index) {
-        var store = this.layerStore;
+        var store = this.loader.store;
         var count = store.getCount();
+        var nodeCount = this.childNodes.length;
         var nodeIndex = -1;
         for(var i=count-1; i>=0; --i) {
-            if(store.getAt(i).get("layer").displayInLayerSwitcher) {
+            if(this.loader.filter(store.getAt(i)) === true) {
                 ++nodeIndex;
-                if(index === i) {
+                if(index === i || nodeIndex > nodeCount-1) {
                     break;
                 }
             }
-        };
+        }
         return nodeIndex;
     },
     
-    /** private: method[nodeIndexToRecordIndex]
-     *  :param index: ``Number`` The child node index.
-     *  :return: ``Number`` The appropriate record index for the node.
-     *  
-     *  Convert a child node index to a record index.
+    /** private: method[destroy]
      */
-    nodeIndexToRecordIndex: function(index) {
-        var store = this.layerStore;
-        var count = store.getCount();
-        var nodeIndex = -1;
-        for(var i=count-1; i>=0; --i) {
-            if(store.getAt(i).get("layer").displayInLayerSwitcher) {
-                ++nodeIndex;
-                if(index === nodeIndex) {
-                    break;
-                }
-            }
-        }
-        return i;
-    },
-    
-    /** private: method[addLayerNode]
-     *  :param layerRecord: ``Ext.data.Record`` The layer record containing the
-     *      layer to be added.
-     *  :param index: ``Number`` Optional index for the new layer.  Default is 0.
-     *  
-     *  Adds a child node representing a layer of the map
-     */
-    addLayerNode: function(layerRecord, index) {
-        index = index || 0;
-        var layer = layerRecord.get("layer");
-        if (layer.displayInLayerSwitcher === true) {
-            var node = new GeoExt.tree.LayerNode(Ext.applyIf({
-                iconCls: layer.isBayeLayer ? 'baselayer-icon' : 'layer-icon',
-                layer: layer,
-                layerStore: this.layerStore
-            }, this.defaults));
-            var sibling = this.item(index);
-            if(sibling) {
-                this.insertBefore(node, sibling);
-            } else {
-                this.appendChild(node);
-            }
-            node.on("move", this.onChildMove, this);
-        }
-    },
-    
-    /** private: method[removeLayerNode]
-     *  :param layerRecord: ``Ext.data.Record`` The layer record containing the
-     *      layer to be removed.
-     * 
-     *  Removes a child node representing a layer of the map
-     */
-    removeLayerNode: function(layerRecord) {
-        var layer = layerRecord.get("layer");
-        if (layer.displayInLayerSwitcher == true) {
-            var node = this.findChildBy(function(node) {
-                return node.layer == layer;
-            });
-            if(node) {
-                node.un("move", this.onChildMove, this);
-                node.remove();
-            }
-    	}
-    },
-    
-    /** private: method[onChildMove]
-     *  :param tree: ``Ext.data.Tree``
-     *  :param node: ``Ext.tree.TreeNode``
-     *  :param oldParent: ``Ext.tree.TreeNode``
-     *  :param newParent: ``Ext.tree.TreeNode``
-     *  :param index: ``Number``
-     *  
-     *  Listener for child node "move" events.  This updates the order of
-     *  records in the store based on new node order if the node has not
-     *  changed parents.
-     */
-    onChildMove: function(tree, node, oldParent, newParent, index) {
-        if(oldParent === newParent) {
-            var newRecordIndex = this.nodeIndexToRecordIndex(index);
-            var oldRecordIndex = this.layerStore.findBy(function(record) {
-                return record.get("layer") === node.layer;
-            });
-            // remove the record and re-insert it at the correct index
-            var record = this.layerStore.getAt(oldRecordIndex);
-            this._reordering = true;
-            this.layerStore.remove(record);
-            this.layerStore.insert(newRecordIndex, [record]);
-            delete this._reordering;
-        }
+    destroy: function() {
+        delete this.layerStore;
+        GeoExt.tree.LayerContainer.superclass.destroy.apply(this, arguments);
     }
-    
 });
-
+    
 /**
  * NodeType: gx_layercontainer
  */

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerLoader.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tree/LayerLoader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerLoader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerLoader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,317 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/**
+ * @include GeoExt/widgets/tree/LayerNode.js
+ */
+Ext.namespace("GeoExt.tree");
+
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = LayerLoader
+ *  base_link = `Ext.util.Observable <http://extjs.com/deploy/dev/docs/?class=Ext.util.Observable>`_
+ */
+
+/** api: constructor
+ *  .. class:: LayerLoader
+ * 
+ *      A loader that will load all layers of a :class:`GeoExt.data.LayerStore`
+ *      By default, only layers that have displayInLayerSwitcher set to true
+ *      will be included. The childrens' iconCls defaults to
+ *      "gx-tree-layer-icon".
+ */
+GeoExt.tree.LayerLoader = function(config) {
+    Ext.apply(this, config);
+    this.addEvents(
+    
+        /** api: events[beforeload]
+         *  Triggered before loading children. Return false to avoid
+         *  loading children.
+         *  
+         *  Listener arguments:
+         *  * loader - :class:`GeoExt.tree.LayerLoader` this loader
+         *  * node - ``Ex.tree.TreeNode`` the node that this loader is
+         *      configured with
+         */
+        "beforeload",
+        
+        /** api: events[load]
+         *  Triggered after children wer loaded.
+         *  
+         *  Listener arguments:
+         *  * loader - :class:`GeoExt.tree.LayerLoader` this loader
+         *  * node - ``Ex.tree.TreeNode`` the node that this loader is
+         *      configured with
+         */
+        "load"
+    );
+
+    GeoExt.tree.LayerLoader.superclass.constructor.call(this);
+};
+
+Ext.extend(GeoExt.tree.LayerLoader, Ext.util.Observable, {
+
+    /** api: config[store]
+     *  :class:`GeoExt.data.LayerStore`
+     *  The layer store containing layers to be added by this loader.
+     */
+    store: null,
+    
+    /** api: config[filter]
+     *  ``Function``
+     *  A function, called in the scope of this loader, with a layer record
+     *  as argument. Is expected to return true for layers to be loaded, false
+     *  otherwise. By default, the filter checks for displayInLayerSwitcher:
+     *  
+     *  .. code-block:: javascript
+     *  
+     *      filter: function(record) {
+     *          return record.get("layer").displayInLayerSwitcher == true
+     *      }
+     */
+    filter: function(record) {
+        return record.get("layer").displayInLayerSwitcher == true;
+    },
+    
+    /** api: config[uiProviders]
+     *  ``Object``
+     *  An optional object containing properties which specify custom
+     *  GeoExt.tree.LayerNodeUI implementations. If the optional uiProvider
+     *  attribute for child nodes is a string rather than a reference to a
+     *  TreeNodeUI implementation, then that string value is used as a
+     *  property name in the uiProviders object. If not provided, the
+     *  uiProviders object will be taken from the ownerTree.
+     */
+    uiProviders: null,
+    
+    /** private: method[load]
+     *  :param node: ``Ext.tree.TreeNode`` The node to add children to.
+     *  :param callback: ``Function``
+     */
+    load: function(node, callback) {
+        if(this.fireEvent("beforeload", this, node)) {
+            this.removeStoreHandlers();
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            
+            if(!this.uiProviders) {
+                this.uiProviders = node.getOwnerTree().getLoader().uiProviders;
+            }
+    
+            if(!this.store) {
+                this.store = GeoExt.MapPanel.guess().layers;
+            }
+            this.store.each(function(record) {
+                this.addLayerNode(node, record);
+            }, this);
+            this.addStoreHandlers(node);
+    
+            if(typeof callback == "function"){
+                callback();
+            }
+            
+            this.fireEvent("load", this, node);
+        }
+    },
+    
+    /** private: method[onStoreAdd]
+     *  :param store: ``Ext.data.Store``
+     *  :param records: ``Array(Ext.data.Record)``
+     *  :param index: ``Number``
+     *  :param node: ``Ext.tree.TreeNode``
+     *  
+     *  Listener for the store's add event.
+     */
+    onStoreAdd: function(store, records, index, node) {
+        if(!this._reordering) {
+            var nodeIndex = node.recordIndexToNodeIndex(index+records.length-1);
+            for(var i=0; i<records.length; ++i) {
+                this.addLayerNode(node, records[i], nodeIndex);
+            }
+        }
+    },
+    
+    /** private: method[onStoreRemove]
+     *  :param store: ``Ext.data.Store``
+     *  :param record: ``Ext.data.Record``
+     *  :param index: ``Number``
+     *  :param node: ``Ext.tree.TreeNode``
+     *  
+     *  Listener for the store's remove event.
+     */
+    onStoreRemove: function(store, record, index, node) {
+        if(!this._reordering) {
+            this.removeLayerNode(node, record);
+        }
+    },
+
+    /** private: method[addLayerNode]
+     *  :param node: ``Ext.tree.TreeNode`` The node that the layer node will
+     *      be added to as child.
+     *  :param layerRecord: ``Ext.data.Record`` The layer record containing the
+     *      layer to be added.
+     *  :param index: ``Number`` Optional index for the new layer.  Default is 0.
+     *  
+     *  Adds a child node representing a layer of the map
+     */
+    addLayerNode: function(node, layerRecord, index) {
+        index = index || 0;
+        if (this.filter(layerRecord) === true) {
+            var child = this.createNode({
+                nodeType: 'gx_layer',
+                layer: layerRecord.get("layer"),
+                layerStore: this.store
+            });
+            var sibling = node.item(index);
+            if(sibling) {
+                node.insertBefore(child, sibling);
+            } else {
+                node.appendChild(child);
+            }
+            child.on("move", this.onChildMove, this);
+        }
+    },
+
+    /** private: method[removeLayerNode]
+     *  :param node: ``Ext.tree.TreeNode`` The node that the layer node will
+     *      be removed from as child.
+     *  :param layerRecord: ``Ext.data.Record`` The layer record containing the
+     *      layer to be removed.
+     * 
+     *  Removes a child node representing a layer of the map
+     */
+    removeLayerNode: function(node, layerRecord) {
+        if (this.filter(layerRecord) === true) {
+            var child = node.findChildBy(function(node) {
+                return node.layer == layerRecord.get("layer");
+            });
+            if(child) {
+                child.un("move", this.onChildMove, this);
+                child.remove();
+                node.reload();
+            }
+    	}
+    },
+    
+    /** private: method[onChildMove]
+     *  :param tree: ``Ext.data.Tree``
+     *  :param node: ``Ext.tree.TreeNode``
+     *  :param oldParent: ``Ext.tree.TreeNode``
+     *  :param newParent: ``Ext.tree.TreeNode``
+     *  :param index: ``Number``
+     *  
+     *  Listener for child node "move" events.  This updates the order of
+     *  records in the store based on new node order if the node has not
+     *  changed parents.
+     */
+    onChildMove: function(tree, node, oldParent, newParent, index) {
+        this._reordering = true;
+        var oldRecordIndex = this.store.findBy(function(record) {
+            return record.get("layer") === node.layer;
+        });
+        // remove the record and re-insert it at the correct index
+        var record = this.store.getAt(oldRecordIndex);
+
+        if(newParent instanceof GeoExt.tree.LayerContainer && 
+                                    this.store === newParent.loader.store) {
+            newParent.loader._reordering = true;
+            this.store.remove(record);
+            var newRecordIndex;
+            if(newParent.childNodes.length > 1) {
+                // find index by neighboring node in the same container
+                var searchIndex = (index === 0) ? index + 1 : index - 1;
+                newRecordIndex = this.store.findBy(function(r) {
+                    return newParent.childNodes[searchIndex].layer === r.get("layer");
+                });
+                index === 0 && newRecordIndex++;
+            } else if(oldParent.parentNode === newParent.parentNode){
+                // find index by last node of a container above
+                var prev = newParent;
+                do {
+                    prev = prev.previousSibling;
+                } while (prev && !(prev instanceof GeoExt.tree.LayerContainer && prev.lastChild));
+                if(prev) {
+                    newRecordIndex = this.store.findBy(function(r) {
+                        return prev.lastChild.layer === r.get("layer");
+                    });
+                } else {
+                    // find indext by first node of a container below
+                    var next = newParent;
+                    do {
+                        next = next.nextSibling;
+                    } while (next && !(next instanceof GeoExt.tree.LayerContainer && next.firstChild));
+                    if(next) {
+                        newRecordIndex = this.store.findBy(function(r) {
+                            return next.firstChild.layer === r.get("layer");
+                        });
+                    }
+                    newRecordIndex++;
+                }
+            }
+            if(newRecordIndex !== undefined) {
+                this.store.insert(newRecordIndex, [record]);
+                window.setTimeout(function() {
+                    newParent.reload();
+                    oldParent.reload();
+                });
+            } else {
+                this.store.insert(oldRecordIndex, [record]);
+            }
+            delete newParent.loader._reordering;
+        }
+        delete this._reordering;
+    },
+    
+    /** private: method[addStoreHandlers]
+     *  :param node: :class:`GeoExt.tree.LayerNode`
+     */
+    addStoreHandlers: function(node) {
+        if(!this._storeHandlers) {
+            this._storeHandlers = {
+                "add": this.onStoreAdd.createDelegate(this, [node], true),
+                "remove": this.onStoreRemove.createDelegate(this, [node], true)
+            };
+            for(var evt in this._storeHandlers) {
+                this.store.on(evt, this._storeHandlers[evt], this);
+            }
+        }
+    },
+    
+    /** private: method[removeStoreHandlers]
+     */
+    removeStoreHandlers: function() {
+        if(this._storeHandlers) {
+            for(var evt in this._storeHandlers) {
+                this.store.un(evt, this._storeHandlers[evt], this);
+            }
+            delete this._storeHandlers;
+        }
+    },
+
+    /** private: method[createNode]
+     *  :param attr: ``Object`` attributes for the new node
+     */
+    createNode: function(attr){
+        if(this.baseAttrs){
+            Ext.apply(attr, this.baseAttrs);
+        }
+        if(typeof attr.uiProvider == 'string'){
+           attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
+        }
+        attr.nodeType = attr.nodeType || "gx_layer";
+
+        return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
+    },
+
+    /** private: method[destroy]
+     */
+    destroy: function() {
+        this.removeStoreHandlers();
+    }
+});

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerNode.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerNode.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerNode.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,6 +1,11 @@
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * Copyright (c) 2008-2009 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
@@ -25,48 +30,109 @@
      *  :param bulkRender: ``Boolean``
      */
     render: function(bulkRender) {
-        GeoExt.tree.LayerNodeUI.superclass.render.call(this, 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.radioGroup && this.radio === null) {
-            this.radio = Ext.DomHelper.insertAfter(this.checkbox,
-                ['<input type="radio" class="x-tree-node-radio" name="',
+            this.radio = Ext.DomHelper.insertAfter(cb,
+                ['<input type="radio" class="gx-tree-layer-radio" name="',
                 a.radioGroup, '_radio"></input>'].join(""));
         }
+        if(a.checkedGroup) {
+            // replace the checkbox with a radio button
+            var radio = Ext.DomHelper.insertAfter(cb,
+                ['<input type="radio" name="', a.checkedGroup,
+                '_checkbox" class="', cb.className,
+                cb.checked ? '" checked="checked"' : '',
+                '"></input>'].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('input[type=radio]', 1)) {
+        if (e.getTarget('.gx-tree-layer-radio', 1)) {
+            this.radio.defaultChecked = this.radio.checked;
             this.fireEvent("radiochange", this.node);
+        } else if(e.getTarget('.x-tree-node-cb', 1)) {
+            this.toggleCheck(this.isChecked());
         } else {
-            GeoExt.tree.LayerNodeUI.superclass.onClick.call(this, e);
+            GeoExt.tree.LayerNodeUI.superclass.onClick.apply(this, arguments);
         }
     },
     
     /** private: method[toggleCheck]
-     *  :param value: ``Boolean``
+     * :param value: ``Boolean``
      */
     toggleCheck: function(value) {
+        value = (value === undefined ? !this.isChecked() : value);
         GeoExt.tree.LayerNodeUI.superclass.toggleCheck.call(this, value);
-        var node = this.node;
-        var layer = this.node.layer;
-        node.visibilityChanging = true;
-        if(this.checkbox && (layer.getVisibility() != this.isChecked())) {
-            layer.setVisibility(this.isChecked());
+        
+        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(group) {
+            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 ui = n.getUI();
+                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) {
+                var ui = this.node.getUI();
+                layer.setVisibility(true);
+            }
         }
-        node.visibilityChanging = false;
     },
     
-    /** private: method[onDestroy]
+    /** 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 
      */
-    onDestroy: function() {
+    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);
+    },
+
+    /** private: method[destroy]
+     */
+    destroy: function() {
         delete this.radio;
-        GeoExt.tree.LayerNodeUI.superclass.onDestroy.call(this);
+        GeoExt.tree.LayerNodeUI.superclass.destroy.apply(this, arguments);
     }
 });
 
+
 /** api: (define)
  *  module = GeoExt.tree
  *  class = LayerNode
@@ -79,24 +145,30 @@
  *      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 "layer-icon",
- *      unless it has children.
+ *      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.
  * 
  *      If the node has a radioGroup attribute configured, the node will be
- *      rendered with a radio button. This works like the checkbox with the
- *      checked attribute, but radioGroup is a string that identifies the options
- *      group. Clicking the radio button will fire a radioChange event.
+ *      rendered with a radio button next to the checkbox. This works like the
+ *      checkbox with the checked attribute, but radioGroup is a string that
+ *      identifies the options group. Clicking the radio button will fire a
+ *      radioChange event.
  * 
  *      To use this node type in a ``TreePanel`` config, set ``nodeType`` to
  *      "gx_layer".
  */
-GeoExt.tree.LayerNode = Ext.extend(Ext.tree.TreeNode, {
+GeoExt.tree.LayerNode = Ext.extend(Ext.tree.AsyncTreeNode, {
     
     /** api: config[layer]
      *  ``OpenLayers.Layer or String``
@@ -122,32 +194,28 @@
      */
     layerStore: null,
     
-    /** api: config[childNodeType]
-     *  ``Ext.tree.Node or String``
-     *  Node class or nodeType of childnodes for this node. A node type provided
-     *  here needs to have an add method, with a scope argument. This method
-     *  will be run by this node in the context of this node, to create child nodes.
+    /** 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.
      */
-    childNodeType: null,
     
-    /** private: property[visibilityChanging]
-     * {Boolean} private property indicating layer visibility being changed
-     *     by this node in order to prevent visibilitychanged events bouncing
-     *     back and forth
-     */
-    visibilityChanging: false,
-    
     /** private: method[constructor]
      *  Private constructor override.
      */
     constructor: function(config) {
-        config.leaf = config.leaf || !config.children;
+        config.leaf = config.leaf || !(config.children || config.loader);
         
-        config.iconCls = typeof config.iconCls == "undefined" &&
-            !config.children ? "layer-icon" : config.iconCls;
-        // checked status will be set by layer event, so setting it to false
-        // to always get the checkbox rendered
-        config.checked = false;
+        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;
         this.addEvents(
@@ -160,9 +228,11 @@
         
         Ext.apply(this, {
             layer: config.layer,
-            layerStore: config.layerStore,
-            childNodeType: config.childNodeType
+            layerStore: config.layerStore
         });
+        if (config.text) {
+            this.fixedText = true;
+        }
         GeoExt.tree.LayerNode.superclass.constructor.apply(this, arguments);
     },
 
@@ -191,19 +261,19 @@
             
             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;
                 }
                 
-                if(this.childNodeType) {
-                    this.addChildNodes();
-                }
-                
                 ui.show();
-                ui.toggleCheck(layer.getVisibility());
                 this.addVisibilityEventHandlers();
-                // set initial checked status
-                this.attributes.checked = layer.getVisibility();
             } else {
                 ui.hide();
             }
@@ -212,7 +282,7 @@
                 this.addStoreEventHandlers(layer);
             }            
         }
-        GeoExt.tree.LayerNode.superclass.render.call(this, bulkRender);
+        GeoExt.tree.LayerNode.superclass.render.apply(this, arguments);
     },
     
     /** private: method[addVisibilityHandlers]
@@ -220,63 +290,129 @@
      *  state
      */
     addVisibilityEventHandlers: function() {
-        this.layer.events.register("visibilitychanged", this, function() {
-            if(!this.visibilityChanging &&
-                    this.attributes.checked != this.layer.getVisibility()) {
-                this.getUI().toggleCheck(this.layer.getVisibility());
-            }
-        });
+        this.layer.events.on({
+            "visibilitychanged": this.onLayerVisibilityChanged,
+            scope: this
+        }); 
         this.on({
-            "checkchange": function(node, checked) {
-                if (checked && this.layer.isBaseLayer && this.layer.map) {
-                    this.layer.map.setBaseLayer(this.layer);
-                }
-                this.layer.setVisibility(checked);
-            },
+            "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": function(store, records, index) {
-                var l;
-                for(var i=0; i<records.length; ++i) {
-                    l = records[i].get("layer");
-                    if(this.layer == l) {
-                        this.getUI().show();
-                    } else if (this.layer == l.name) {
-                        // layer is a string, which means the node has not yet
-                        // been rendered because the layer was not found. But
-                        // now we have the layer and can render.
-                        this.render(bulkRender);
-                        return;
-                    }
-                }
-            },
-            "remove": function(store, record, index) {
-                if(this.layer == record.get("layer")) {
-                    this.getUI().hide();
-                }
-            },
+            "add": this.onStoreAdd,
+            "remove": this.onStoreRemove,
+            "update": this.onStoreUpdate,
             scope: this
         });
     },
     
-    /** private: method[addChildNodes]
-     *  Calls the add method of a node type configured as ``childNodeType``
-     *  to add children.
+    /** private: method[onStoreAdd]
+     *  :param store: ``Ext.data.Store``
+     *  :param records: ``Array(Ext.data.Record)``
+     *  :param index: ``Number``
+     *
+     *  handler for add events on the store 
      */
-    addChildNodes: function() {
-        if(typeof this.childNodeType == "string") {
-            Ext.tree.TreePanel.nodeTypes[this.childNodeType].add(this);
-        } else if(typeof this.childNodeType.add === "function") {
-            this.childNodeType.add(this);
+    onStoreAdd: function(store, records, index) {
+        var l;
+        for(var i=0; i<records.length; ++i) {
+            l = records[i].get("layer");
+            if(this.layer == l) {
+                this.getUI().show();
+                break;
+            } else if (this.layer == l.name) {
+                // layer is a string, which means the node has not yet
+                // been rendered because the layer was not found. But
+                // now we have the layer and can render.
+                this.render();
+                break;
+            }
         }
+    },
+    
+    /** private: method[onStoreRemove]
+     *  :param store: ``Ext.data.Store``
+     *  :param record: ``Ext.data.Record``
+     *  :param index: ``Number``
+     *
+     *  handler for remove events on the store 
+     */
+    onStoreRemove: function(store, record, index) {
+        if(this.layer == record.get("layer")) {
+            this.getUI().hide();
+        }
+    },
+
+    /** private: method[onStoreUpdate]
+     *  :param store: ``Ext.data.Store``
+     *  :param record: ``Ext.data.Record``
+     *  :param operation: ``String``
+     *  
+     *  Listener for the store's update event.
+     */
+    onStoreUpdate: function(store, record, operation) {
+        var layer = record.get("layer");
+        if(!this.fixedText && (this.layer == layer && this.text !== layer.name)) {
+            this.setText(layer.name);
+        }
+    },
+
+    /** private: method[destroy]
+     */
+    destroy: function() {
+        var layer = this.layer;
+        if (layer instanceof OpenLayers.Layer) {
+            layer.events.un({
+                "visibilitychanged": this.onLayerVisibilityChanged,
+                scope: this
+            });
+        }
+        delete this.layer;
+        var layerStore = this.layerStore;
+        if(layerStore) {
+            layerStore.un("add", this.onStoreAdd, this);
+            layerStore.un("remove", this.onStoreRemove, this);
+            layerStore.un("update", this.onStoreUpdate, this);
+        }
+        delete this.layerStore;
+        this.un("checkchange", this.onCheckChange, this);
+
+        GeoExt.tree.LayerNode.superclass.destroy.apply(this, arguments);
     }
 });
 

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamLoader.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamLoader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamLoader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamLoader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2008-2009 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");
+
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = LayerParamLoader
+ *  base_link = `Ext.util.Observable <http://extjs.com/deploy/dev/docs/?class=Ext.util.Observable>`_
+ */
+
+/** api: constructor
+ *  .. class:: LayerParamLoader
+ * 
+ *      A loader that creates children from its node's layer
+ *      (``OpenLayers.Layer.HTTPRequest``) by items in one of the values in
+ *      the layer's params object.
+ */
+GeoExt.tree.LayerParamLoader = function(config) {
+    Ext.apply(this, config);
+    this.addEvents(
+    
+        /** api: events[beforeload]
+         *  Triggered before loading children. Return false to avoid
+         *  loading children.
+         *  
+         *  Listener arguments:
+         *  * loader - :class:`GeoExt.tree.LayerLoader` this loader
+         *  * node - ``Ex.tree.TreeNode`` the node that this loader is
+         *      configured with
+         */
+        "beforeload",
+        
+        /** api: events[load]
+         *  Triggered after children were loaded.
+         *  
+         *  Listener arguments:
+         *  * loader - :class:`GeoExt.tree.LayerLoader` this loader
+         *  * node - ``Ex.tree.TreeNode`` the node that this loader is
+         *      configured with
+         */
+        "load"
+    );
+
+    GeoExt.tree.LayerLoader.superclass.constructor.call(this);
+};
+
+Ext.extend(GeoExt.tree.LayerParamLoader, Ext.util.Observable, {
+    
+    /** api: config[param]
+     *  ``String`` Key for a param (key-value pair in the params object of the
+     *  layer) that this loader uses to create childnodes from its items. The
+     *  value can either be an ``Array`` or a ``String``, delimited by the
+     *  character (or string) provided as ``delimiter`` config option.
+     */
+    
+    /** private: property[param]
+     *  ``String``
+     */
+    param: null,
+    
+    /** api: config[delimiter]
+     *  ``String`` Delimiter of the ``param``'s value's items. Default is
+     *  ``,`` (comma). If the ``param``'s value is an array, this property has
+     *  no effect.
+     */
+    
+    /** private: property[delimiter]
+     *  ``String``
+     */
+    delimiter: ",",
+
+    /** private: method[load]
+     *  :param node: ``Ext.tree.TreeNode`` The node to add children to.
+     *  :param callback: ``Function``
+     */
+    load: function(node, callback) {
+        if(this.fireEvent("beforeload", this, node)) {
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            
+            var paramValue =
+                (node.layer instanceof OpenLayers.Layer.HTTPRequest) &&
+                node.layer.params[this.param];
+            if(paramValue) {
+                var items = (paramValue instanceof Array) ?
+                    paramValue :
+                    paramValue.split(this.delimiter);
+
+                Ext.each(items, function(item) {
+                    this.addParamNode(item, node);
+                }, this);
+            }
+    
+            if(typeof callback == "function"){
+                callback();
+            }
+            
+            this.fireEvent("load", this, node);
+        }
+    },
+    
+    /** private: method[addParamNode]
+     *  :param paramItem: ``String`` The param item that the child node will
+     *      represent.
+     *  :param node: :class:`GeoExt.tree.LayerNode`` The node that the param
+     *      node will be added to as child.
+     *  
+     *  Adds a child node representing a param value of the layer
+     */
+    addParamNode: function(paramItem, node) {
+        var child = this.createNode({
+            layer: node.layer,
+            param: this.param,
+            item: paramItem,
+            delimiter: this.delimiter
+        });
+        var sibling = node.item(0);
+        if(sibling) {
+            node.insertBefore(child, sibling);
+        } else {
+            node.appendChild(child);
+        }
+    },
+
+    /** private: method[createNode]
+     *  :param attr: ``Object`` attributes for the new node
+     */
+    createNode: function(attr){
+        if(this.baseAttrs){
+            Ext.apply(attr, this.baseAttrs);
+        }
+        if(typeof attr.uiProvider == 'string'){
+           attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
+        }
+        attr.nodeType = attr.nodeType || "gx_layerparam";
+
+        return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
+    }
+});
\ No newline at end of file

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamNode.js (from rev 1504, core/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamNode.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamNode.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/LayerParamNode.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2008-2009 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");
+
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = LayerParamNode
+ *  base_link = `Ext.util.Observable <http://extjs.com/deploy/dev/docs/?class=Ext.tree.TreeNode>`_
+ */
+
+/** api: constructor
+ *  .. class:: LayerParamNode
+ * 
+ *  A subclass of ``Ext.tree.TreeNode`` that represents a value of a list of
+ *  values provided as one of an ``OpenLayers.Layer.HTTPRequest``'s params.
+ *  The default iconCls for this node's icon is "gx-tree-layerparam-icon".
+ *  
+ *  To use this node type in a ``TreePanel`` config, set ``nodeType`` to
+ *  "gx_layerparam".
+ */
+GeoExt.tree.LayerParamNode = Ext.extend(Ext.tree.TreeNode, {
+    
+    /** api: config[layer]
+     *  ``OpenLayers.Layer.HTTPRequest|String`` The layer that this node
+     *  represents a subnode of. If provided as string, the string has to
+     *  match the title of one of the records in the ``layerStore``.
+     */
+    
+    /** private: property[layer]
+     *  ``OpenLayers.Layer.HTTPRequest``
+     */
+    layer: null,
+    
+    /** api: config[layerStore]
+     *  :class:`GeoExt.data.LayerStore` Only used if layer is provided as
+     *  string. The store where we can find the layer. If not provided, the
+     *  store of a map panel found by ``GeoExt.MapPanel::guess`` will be used.
+     */
+    
+    /** api: config[param]
+     *  ``String`` Key for a param (key-value pair in the params object of the
+     *  layer) that this node represents an item of. The value can either be an
+     *  ``Array`` or a ``String``, delimited by the character (or string)
+     *  provided as ``delimiter`` config option.
+     */
+    
+    /** private: property[param]
+     *  ``String``
+     */
+    param: null,
+    
+    /** api: config[item]
+     *  ``String`` The param's value's item that this node represents.
+     */
+    
+    /** private: property[item]
+     *  ``String``
+     */
+    item: null,
+    
+    /** api: config[delimiter]
+     *  ``String`` Delimiter of the ``param``'s value's items. Default is
+     *  ``,`` (comma). If the ``param``'s value is an array, this property
+     *  has no effect.
+     */
+    
+    /** private: property[delimiter]
+     *  ``String``
+     */
+    delimiter: null,
+    
+    /** private: property[allItems]
+     *  ``Array`` All items in the param value.
+     */
+    allItems: null,
+    
+    /** private: method[constructor]
+     *  Private constructor override.
+     */
+    constructor: function(attributes) {
+        var config = attributes || {};
+        config.iconCls = config.iconCls || "gx-tree-layerparam-icon";
+        config.text = config.text || config.item;
+        typeof config.layer == "string" ? false :
+            config.layer.getVisibility();
+        
+        this.param = config.param;
+        this.item = config.item;
+        this.delimiter = config.delimiter || ",";
+                
+        GeoExt.tree.LayerParamNode.superclass.constructor.apply(this, arguments);
+    },
+    
+    /** private: method[render]
+     *  Private override.
+     */
+    render: function(bulkRender) {
+        var layer = this.attributes.layer;
+        if(typeof layer == "string") {
+            var store = this.attributes.layerStore || GeoExt.MapPanel.guess().layers;
+            var i = store.findBy(function(o) {
+                return o.get("title") == layer;
+            });
+            layer = store.getAt(i).get("layer");
+        }
+        this.layer = layer;
+        this.allItems = this.getItems();
+        
+        var visibility = layer.getVisibility();
+        this.attributes.checked = this.attributes.checked == null ?
+            visibility : this.attributes.checked;
+        if(this.attributes.checked !== visibility) {
+            this.onCheckChange(this, !visibility);
+        }
+
+        this.addVisibilityEventHandlers();
+        GeoExt.tree.LayerParamNode.superclass.render.apply(this, arguments);
+    },
+    
+    /** private: method[getItems]
+     *  :return: ``Array`` the items of this node's layer's param
+     */
+    getItems: function() {
+        var paramValue = this.layer.params[this.param];
+        return paramValue instanceof Array ?
+            paramValue :
+            (paramValue ? paramValue.split(this.delimiter) : []);
+    },
+    
+    /** private: method[createParams]
+     *  :param items: ``Array``
+     *  :return: ``Object`` The params object to pass to mergeNewParams
+     */
+    createParams: function(items) {
+        var params = {};
+        params[this.param] = this.layer.params[this.param] instanceof Array ?
+            items :
+            items.join(this.delimiter);
+        return params;
+    },
+    
+    /** 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[onLayerVisibilityChanged]
+     *  Handler for visibilitychanged events on the layer.
+     */
+    onLayerVisibilityChanged: function() {
+        if(this.getItems().length === 0) {
+            this.layer.mergeNewParams(this.createParams(this.allItems));
+        }
+        var visible = this.layer.getVisibility();
+        if(visible && this.getItems().indexOf(this.item) !== -1) {
+            this.getUI().toggleCheck(true);
+        }
+        if(!visible) {
+            this.layer.mergeNewParams(this.createParams([]));
+            this.getUI().toggleCheck(false);
+        }
+    },
+    
+    /** private: method[onCheckChange]
+     *  :param node: :class:`GeoExt.tree.LayerParamNode``
+     *  :param checked: ``Boolean``
+     *
+     *  Handler for checkchange events.
+     */
+    onCheckChange: function(node, checked) {
+        var layer = this.layer;
+
+        var newItems = [];
+        var curItems = this.getItems();
+        // if the layer is invisible, and a subnode is checked for the first
+        // time, we need to pretend that no subnode param items are set.
+        if(checked === true && layer.getVisibility() === false &&
+                                curItems.length === this.allItems.length) {
+            curItems = [];
+            
+        }
+        Ext.each(this.allItems, function(item) {
+            if((item !== this.item && curItems.indexOf(item) !== -1) ||
+                            (checked === true && item === this.item)) {
+                newItems.push(item);
+            }
+        }, this);
+        
+        var visible = (newItems.length > 0);
+        // if there is something to display, we want to update the params
+        // before the layer is turned on
+        visible && layer.mergeNewParams(this.createParams(newItems));
+        if(visible !== layer.getVisibility()) {
+            layer.setVisibility(visible);
+        }
+        // if there is nothing to display, we want to update the params
+        // when the layer is turned off, so we don't fire illegal requests
+        // (i.e. param value being empty)
+        (!visible) && layer.mergeNewParams(this.createParams([]));
+    },
+    
+    /** private: method[destroy]
+     */
+    destroy: function() {
+        var layer = this.layer;
+        if (layer instanceof OpenLayers.Layer) {
+            layer.events.un({
+                "visibilitychanged": this.onLayerVisibilityChanged,
+                scope: this
+            });
+        }
+        delete this.layer;
+        
+        this.un("checkchange", this.onCheckChange, this);
+
+        GeoExt.tree.LayerNode.superclass.destroy.apply(this, arguments);
+    }
+});
+
+/**
+ * NodeType: gx_layerparam
+ */
+Ext.tree.TreePanel.nodeTypes.gx_layerparam = GeoExt.tree.LayerParamNode;

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/OverlayLayerContainer.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/OverlayLayerContainer.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt/widgets/tree/OverlayLayerContainer.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,5 +1,9 @@
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * Copyright (c) 2008-2009 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.
  */
 
 /**
@@ -7,60 +11,44 @@
  */
 Ext.namespace("GeoExt.tree");
 
-/**
- * Class: GeoExt.tree.OverlayLayerContainer
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = OverlayLayerContainer
+ */
+
+/** api: (extends)
+ * GeoExt/widgets/tree/LayerContainer.js
+ */
+
+/** api: constructor
+ * .. class:: OverlayLayerContainer
  * 
- * A layer container that will collect all overlay layers of an OpenLayers map.
- * Only layers that have displayInLayerSwitcher set to true will be included.
+ *     A layer container that will collect all overlay layers of an OpenLayers
+ *     map. Only layers that have displayInLayerSwitcher set to true will be
+ *     included.
  * 
- * To use this node type in JSON config, set nodeType to
- * "olOverlayLayerContainer".
- * 
- * Inherits from:
- * - <GeoExt.tree.LayerContainer>
+ *     To use this node type in ``TreePanel`` config, set nodeType to
+ *     "gx_overlaylayerontainer".
  */
 GeoExt.tree.OverlayLayerContainer = Ext.extend(GeoExt.tree.LayerContainer, {
 
-    /**
-     * Constructor: GeoExt.tree.OverlayLayerContainer
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
+     *  Private constructor override.
      */
     constructor: function(config) {
-        config.text = config.text || "Overlays";
-        GeoExt.tree.OverlayLayerContainer.superclass.constructor.apply(this,
-            arguments);
-    },
-
-    /**
-     * Method: addLayerNode
-     * Adds a child node representing a overlay layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to add a node for
-     */
-    addLayerNode: function(layerRecord) {
-        var layer = layerRecord.get("layer");
-        if (layer.isBaseLayer == false) {
-            GeoExt.tree.OverlayLayerContainer.superclass.addLayerNode.call(this,
-                layerRecord);
-        }
-    },
-    
-    /**
-     * Method: removeLayerNode
-     * Removes a child node representing an overlay layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to remove the node for
-     */
-    removeLayerNode: function(layerRecord) {
-        var layer = layerRecord.get("layer");
-        if (layer.isBaseLayer == false) {
-            GeoExt.tree.OverlayLayerContainer.superclass.removeLayerNode.call(
-                this, layerRecord);
-    	}
+        config = Ext.applyIf(config || {}, {
+            text: "Overlays"
+        });
+        config.loader = Ext.applyIf(config.loader || {}, {
+            filter: function(record){
+                var layer = record.get("layer");
+                return layer.displayInLayerSwitcher === true &&
+                layer.isBaseLayer === false;
+            }
+        });
+        
+        GeoExt.tree.OverlayLayerContainer.superclass.constructor.call(this,
+            config);
     }
 });
 

Modified: sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/GeoExt.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,10 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+/**
+ * Copyright (c) 2008-2009 The Open Source Geospatial Foundation
+ * 
  * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
  * of the license.
- * 
- * ¹ pending approval */
+ */
 
 /*
  * The code in this file is based on code taken from OpenLayers.
@@ -32,7 +33,12 @@
      */
     var getScriptLocation = function() {
         var scriptLocation = "";
-        var scripts = document.getElementsByTagName('script');
+        // If we load other scripts right before GeoExt using the same
+        // mechanism to add script resources dynamically (e.g. OpenLayers), 
+        // document.getElementsByTagName will not find the GeoExt script tag
+        // in FF2. Using document.documentElement.getElementsByTagName instead
+        // works around this issue.
+        var scripts = document.documentElement.getElementsByTagName('script');
         for(var i=0, len=scripts.length; i<len; i++) {
             var src = scripts[i].getAttribute('src');
             if(src) {
@@ -58,6 +64,8 @@
      */
     if(!singleFile) {
         var jsfiles = new Array(
+            "GeoExt/data/AttributeReader.js",
+            "GeoExt/data/AttributeStore.js",
             "GeoExt/data/FeatureRecord.js",
             "GeoExt/data/FeatureReader.js",
             "GeoExt/data/FeatureStore.js",
@@ -67,20 +75,36 @@
             "GeoExt/data/ScaleStore.js",
             "GeoExt/data/WMSCapabilitiesReader.js",
             "GeoExt/data/WMSCapabilitiesStore.js",
+            "GeoExt/data/WFSCapabilitiesReader.js",
+            "GeoExt/data/WFSCapabilitiesStore.js",
+            "GeoExt/data/WMSDescribeLayerReader.js",
+            "GeoExt/data/WMSDescribeLayerStore.js",
+            "GeoExt/data/WMCReader.js",
+            "GeoExt/widgets/Action.js",
             "GeoExt/data/ProtocolProxy.js",
+            "GeoExt/widgets/FeatureRenderer.js",
             "GeoExt/widgets/MapPanel.js",
             "GeoExt/widgets/Popup.js",
             "GeoExt/widgets/form.js",
             "GeoExt/widgets/form/SearchAction.js",
             "GeoExt/widgets/form/BasicForm.js",
             "GeoExt/widgets/form/FormPanel.js",
+            "GeoExt/widgets/tips/SliderTip.js",
+            "GeoExt/widgets/tips/LayerOpacitySliderTip.js",
+            "GeoExt/widgets/tips/ZoomSliderTip.js",
             "GeoExt/widgets/tree/LayerNode.js",
+            "GeoExt/widgets/tree/LayerLoader.js",
             "GeoExt/widgets/tree/LayerContainer.js",
             "GeoExt/widgets/tree/BaseLayerContainer.js",
             "GeoExt/widgets/tree/OverlayLayerContainer.js",
+            "GeoExt/widgets/tree/LayerParamNode.js",
+            "GeoExt/widgets/tree/LayerParamLoader.js",
+            "GeoExt/widgets/LayerOpacitySlider.js",
             "GeoExt/widgets/LegendImage.js",
             "GeoExt/widgets/LegendWMS.js",
-            "GeoExt/widgets/LegendPanel.js"
+            "GeoExt/widgets/LegendPanel.js",
+            "GeoExt/widgets/ZoomSlider.js",
+            "GeoExt/widgets/grid/FeatureSelectionModel.js"
         );
 
         var agent = navigator.userAgent;

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/overrides (from rev 1504, core/trunk/geoext/lib/overrides)

Deleted: sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js
===================================================================
--- core/trunk/geoext/lib/overrides/override-ext-ajax.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,82 +0,0 @@
-/**
- * Copyright (c) 2008-2009 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.
- */
-
-/** api: override = Ext.Ajax */
-(function() {
-
-    /** private: function[createComplete]
-     *  ``Function``
-     */
-    var createComplete = function(fn, cb) {
-        return function(request) {
-            if(cb && cb[fn]) {
-                cb[fn].call(cb.scope || window, {
-                    responseText: request.responseText,
-                    responseXML: request.responseXML,
-                    argument: cb.argument
-                });
-            }
-        };
-    };
-
-    Ext.apply(Ext.lib.Ajax, {
-        /** private: method[request]
-         */
-        request: function(method, uri, cb, data, options) {
-            options = options || {};
-            var hs = options.headers;
-            if(options.xmlData) {
-                if(!hs || !hs["Content-Type"]) {
-                    hs = hs || {};
-                    hs["Content-Type"] = "text/xml";
-                }
-                method = (method ? method :
-                    (options.method ? options.method : "POST"));
-                data = options.xmlData;
-            } else if(options.jsonData) {
-                if(!hs || !hs["Content-Type"]) {
-                    hs = hs || {};
-                    hs["Content-Type"] = "application/json";
-                }
-                method = (method ? method :
-                    (options.method ? options.method : "POST"));
-                data = typeof options.jsonData == "object" ?
-                       Ext.encode(options.jsonData) : options.jsonData;
-            }
-            // options.params means form-encoded data, so change content-type
-            if (options.params && (!hs || !hs["Content-Type"])) {
-                hs = hs || {};
-                hs["Content-Type"] = "application/x-www-form-urlencoded";
-            }
-            return OpenLayers.Request.issue({
-                success: createComplete("success", cb),
-                failure: createComplete("failure", cb),
-                headers: options.headers,
-                method: method,
-                headers: hs,
-                data: data,
-                url: uri
-            });
-        },
-
-        /** private: method[isCallInProgress]
-         *  :params request: ``Object`` The XHR object.
-         */
-        isCallInProgress: function(request) {
-            // do not prevent our caller from calling abort()
-            return true;
-        },
-
-        /** private: method[abort]
-         *  :params request: ``Object`` The XHR object.
-         */
-        abort: function(request) {
-            request.abort();
-        }
-    });
-})();

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js (from rev 1504, core/trunk/geoext/lib/overrides/override-ext-ajax.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2008-2009 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.
+ */
+
+/** api: override = Ext.Ajax */
+(function() {
+
+    /** private: function[createComplete]
+     *  ``Function``
+     */
+    var createComplete = function(fn, cb) {
+        return function(request) {
+            if(cb && cb[fn]) {
+                cb[fn].call(cb.scope || window, {
+                    responseText: request.responseText,
+                    responseXML: request.responseXML,
+                    argument: cb.argument
+                });
+            }
+        };
+    };
+
+    Ext.apply(Ext.lib.Ajax, {
+        /** private: method[request]
+         */
+        request: function(method, uri, cb, data, options) {
+            options = options || {};
+            var hs = options.headers;
+            if(options.xmlData) {
+                if(!hs || !hs["Content-Type"]) {
+                    hs = hs || {};
+                    hs["Content-Type"] = "text/xml";
+                }
+                method = (method ? method :
+                    (options.method ? options.method : "POST"));
+                data = options.xmlData;
+            } else if(options.jsonData) {
+                if(!hs || !hs["Content-Type"]) {
+                    hs = hs || {};
+                    hs["Content-Type"] = "application/json";
+                }
+                method = (method ? method :
+                    (options.method ? options.method : "POST"));
+                data = typeof options.jsonData == "object" ?
+                       Ext.encode(options.jsonData) : options.jsonData;
+            }
+            // options.params means form-encoded data, so change content-type
+            if (options.params && (!hs || !hs["Content-Type"])) {
+                hs = hs || {};
+                hs["Content-Type"] = "application/x-www-form-urlencoded";
+            }
+            return OpenLayers.Request.issue({
+                success: createComplete("success", cb),
+                failure: createComplete("failure", cb),
+                headers: options.headers,
+                method: method,
+                headers: hs,
+                data: data,
+                url: uri
+            });
+        },
+
+        /** private: method[isCallInProgress]
+         *  :params request: ``Object`` The XHR object.
+         */
+        isCallInProgress: function(request) {
+            // do not prevent our caller from calling abort()
+            return true;
+        },
+
+        /** private: method[abort]
+         *  :params request: ``Object`` The XHR object.
+         */
+        abort: function(request) {
+            request.abort();
+        }
+    });
+})();

Deleted: sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst
===================================================================
--- core/trunk/geoext/lib/overrides/override-ext-ajax.jst	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,42 +0,0 @@
-.. _overrides.ext.ajax:
-
-Ext.Ajax Overrides
-==================
-
-GeoExt provides an override for the functionality in
-`Ext.Ajax <http://extjs.com/deploy/dev/docs/?class=Ext.Ajax>`_.  If you include
-the :file:`override-ext-ajax.js` file in your build, any calls to ``Ext.Ajax``
-methods will be routed through ``OpenLayers.Request`` methods.  The practical
-implication of this is that you can set the ``OpenLayers.ProxyHost`` property
-in your application and have this proxy used by Ext components that call
-``Ext.Ajax`` methods.
-
-To include :file:`override-ext-ajax.js` in your build, the GeoExt section in
-your build config should look like one of the following:
-
-.. code-block:: ini
-
-    # include everything (including override-ext-ajax.js)
-    [GeoExt.js]
-    root = ../lib
-    license = geoext-license.js
-    exclude =
-        GeoExt.js
-        GeoExt/SingleFile.js
-
-
-or
-
-.. code-block:: ini
-
-    # custom build
-    [GeoExt.js]
-    root = ../lib
-    license = geoext-license.js
-    include =
-        overrides/override-ext-ajax.js
-        # other files listed here ...
-    exclude =
-        GeoExt.js
-        GeoExt/SingleFile.js
-

Copied: sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst (from rev 1504, core/trunk/geoext/lib/overrides/override-ext-ajax.jst)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/lib/overrides/override-ext-ajax.jst	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,42 @@
+.. _overrides.ext.ajax:
+
+Ext.Ajax Overrides
+==================
+
+GeoExt provides an override for the functionality in
+`Ext.Ajax <http://extjs.com/deploy/dev/docs/?class=Ext.Ajax>`_.  If you include
+the :file:`override-ext-ajax.js` file in your build, any calls to ``Ext.Ajax``
+methods will be routed through ``OpenLayers.Request`` methods.  The practical
+implication of this is that you can set the ``OpenLayers.ProxyHost`` property
+in your application and have this proxy used by Ext components that call
+``Ext.Ajax`` methods.
+
+To include :file:`override-ext-ajax.js` in your build, the GeoExt section in
+your build config should look like one of the following:
+
+.. code-block:: ini
+
+    # include everything (including override-ext-ajax.js)
+    [GeoExt.js]
+    root = ../lib
+    license = geoext-license.js
+    exclude =
+        GeoExt.js
+        GeoExt/SingleFile.js
+
+
+or
+
+.. code-block:: ini
+
+    # custom build
+    [GeoExt.js]
+    root = ../lib
+    license = geoext-license.js
+    include =
+        overrides/override-ext-ajax.js
+        # other files listed here ...
+    exclude =
+        GeoExt.js
+        GeoExt/SingleFile.js
+

Modified: sandbox/ahocevar/playground/trunk/geoext/license.txt
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/license.txt	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/license.txt	2009-11-27 09:20:18 UTC (rev 1505)
@@ -7,7 +7,7 @@
 License
 =======
 
-Copyright (c) 2008-2009, The Open Source Geospatial Foundation ¹
+Copyright (c) 2008-2009, The Open Source Geospatial Foundation
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -34,7 +34,6 @@
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 
-¹ pending approval
 
 
 Notice about ExtJS

Deleted: sandbox/ahocevar/playground/trunk/geoext/resources/css/example.css
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/resources/css/example.css	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/resources/css/example.css	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,16 +0,0 @@
-/*
- * Styles can be theme specific, as shown in the example below.
- * 
- * style for the default theme:
- * .my-style {
- *     background: transparent url(../images/default/anchor.png) no-repeat 0 0;
- *     postion: relative;
- *     top: -1px;
- *     left: 5px;
- * }
- * 
- * modifier for the gray theme:
- * .xtheme-gray .my-style {
- *     background: transparent url(../images/gray/anchor.png) no-repeat 0 0;
- * }
- */

Modified: sandbox/ahocevar/playground/trunk/geoext/resources/css/geoext-all-debug.css
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/resources/css/geoext-all-debug.css	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/resources/css/geoext-all-debug.css	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,2 +1,6 @@
- at import "example.css";
+/**
+ * This file combines all default css files. It will be parsed by the build
+ * processor to generate a minified geoext-all.css file. Theme specific
+ * overrides go into gxtheme-<theme>.css
+ */
 @import "popup.css";

Modified: sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-gray.css
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-gray.css	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-gray.css	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,9 +1,9 @@
 .gx-popup-anc {
-	background: transparent url(../images/gray/anchor.png) no-repeat 0 0;
-        position: relative;
-        top:-1px;
-        left:5px;
-        z-index:2;
-        height:16px;
-        width:31px;
+    background: transparent url(../images/gray/anchor.png) no-repeat 0 0;
+    position: relative;
+    top:-1px;
+    left:5px;
+    z-index:2;
+    height:16px;
+    width:31px;
 }
\ No newline at end of file

Copied: sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-slate.css (from rev 1504, core/trunk/geoext/resources/css/gxtheme-slate.css)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-slate.css	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/resources/css/gxtheme-slate.css	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,9 @@
+.gx-popup-anc {
+	background: transparent url(../images/slate/anchor.png) no-repeat 0 0;
+        position: relative;
+        top:-1px;
+        left:5px;
+        z-index:2;
+        height:16px;
+        width:31px;
+}

Modified: sandbox/ahocevar/playground/trunk/geoext/resources/css/popup.css
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/resources/css/popup.css	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/resources/css/popup.css	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,10 +1,9 @@
-
 .gx-popup-anc {
-	background: transparent url(../images/default/anchor.png) no-repeat 0 0;
-        position: relative;
-        top:-1px;
-        left:5px;
-        z-index:2;
-        height:16px;
-        width:31px;
+    background: transparent url(../images/default/anchor.png) no-repeat 0 0;
+    position: relative;
+    top:-1px;
+    left:5px;
+    z-index:2;
+    height:16px;
+    width:31px;
 }

Modified: sandbox/ahocevar/playground/trunk/geoext/resources/images/default/anchor.png
===================================================================
(Binary files differ)

Modified: sandbox/ahocevar/playground/trunk/geoext/resources/images/gray/anchor.png
===================================================================
(Binary files differ)

Copied: sandbox/ahocevar/playground/trunk/geoext/resources/images/slate (from rev 1504, core/trunk/geoext/resources/images/slate)

Deleted: sandbox/ahocevar/playground/trunk/geoext/resources/images/slate/anchor.png
===================================================================
(Binary files differ)

Copied: sandbox/ahocevar/playground/trunk/geoext/resources/images/slate/anchor.png (from rev 1504, core/trunk/geoext/resources/images/slate/anchor.png)
===================================================================
(Binary files differ)

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/adapter (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/adapter)

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+    <script type="text/javascript" src="AttributeReader.js"></script>
+
+    <script type="text/javascript">
+
+    function test_read(t) {
+        t.plan(3);
+
+        var reader = new GeoExt.data.AttributeReader({}, [
+            "name",
+            "type"
+        ]);
+
+        var records = reader.read({responseXML : doc});
+
+        //1 test
+        t.eq(records.totalRecords, 23, 'readRecords returns correct number of records');
+
+        var record = records.records[2];
+
+        //2 tests -- testing the fields of a record
+        t.eq(record.get("name"), "STATE_FIPS", "[2] correct attribute name");
+        t.eq(record.get("type"), "xsd:string", "[2] correct attribute type name");
+
+    }
+
+    function test_ignoreString(t) {
+        t.plan(1);
+
+        var reader = new GeoExt.data.AttributeReader({
+            ignore: {type: "xsd:string"}
+        }, [
+            "name",
+            "type"
+        ]);
+
+        var records = reader.read({responseXML : doc});
+
+        //1 test
+        t.eq(records.totalRecords, 19, 'right number of records are ignored (ignores String)');
+    }
+
+    function test_ignoreArray(t) {
+         t.plan(1);
+
+         var reader = new GeoExt.data.AttributeReader({
+             ignore: {type: ["xsd:double", "gml:MultiSurfacePropertyType"]}
+         }, [
+             "name",
+             "type"
+         ]);
+
+         var records = reader.read({responseXML : doc});
+
+         //1 test
+         t.eq(records.totalRecords, 4, 'right number of records are ignored (ignores Array)');
+    }
+
+    function test_ignoreRegexp(t) {
+        t.plan(1);
+
+        var reader = new GeoExt.data.AttributeReader({
+            ignore: {name: new RegExp('^S')}
+        }, [
+            "name",
+            "type"
+        ]);
+
+        var records = reader.read({responseXML : doc});
+
+        //1 test
+        t.eq(records.totalRecords, 17, 'right number of records are ignored (ignores RegExp)');
+    }
+
+    </script>
+  <body>
+    <div id="map"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.js (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,39 @@
+var doc = (new OpenLayers.Format.XML).read(
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" xmlns:topp="http://www.openplans.org/topp" elementFormDefault="qualified" targetNamespace="http://www.openplans.org/topp">' +
+  '<xsd:import namespace="http://www.opengis.net/gml" schemaLocation="http://sigma.openplans.org:80/geoserver/schemas/gml/3.1.1/base/gml.xsd"/>' +
+  '<xsd:complexType name="statesType">' +
+    '<xsd:complexContent>' +
+      '<xsd:extension base="gml:AbstractFeatureType">' +
+        '<xsd:sequence>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="the_geom" nillable="true" type="gml:MultiSurfacePropertyType"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="STATE_NAME" nillable="true" type="xsd:string"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="STATE_FIPS" nillable="true" type="xsd:string"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="SUB_REGION" nillable="true" type="xsd:string"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="STATE_ABBR" nillable="true" type="xsd:string"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="LAND_KM" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="WATER_KM" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="PERSONS" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="FAMILIES" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="HOUSHOLD" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="MALE" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="FEMALE" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="WORKERS" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="DRVALONE" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="CARPOOL" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="PUBTRANS" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="EMPLOYED" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="UNEMPLOY" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="SERVICE" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="MANUAL" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="P_MALE" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="P_FEMALE" nillable="true" type="xsd:double"/>' +
+          '<xsd:element maxOccurs="1" minOccurs="0" name="SAMP_POP" nillable="true" type="xsd:double"/>' +
+        '</xsd:sequence>' +
+      '</xsd:extension>' +
+    '</xsd:complexContent>' +
+  '</xsd:complexType>' +
+  '<xsd:element name="states" substitutionGroup="gml:_Feature" type="topp:statesType"/>' +
+'</xsd:schema>'
+);
+

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/FeatureStore.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/FeatureStore.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/FeatureStore.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -156,6 +156,35 @@
                  'Adding a record with "add" does not add feature to layer if addRecordFilter returns false');
         }
         
+        function test_getRecordFromFeature(t) {
+            t.plan(2);
+
+            var layer = new OpenLayers.Layer.Vector();
+            var store = new GeoExt.data.FeatureStore({
+                layer: layer
+            });
+            var features = [ 
+                new OpenLayers.Feature.Vector(), 
+                new OpenLayers.Feature.Vector() 
+            ];
+            features[0].state = OpenLayers.State.INSERT;
+            layer.addFeatures(features);
+            
+            var record;
+            
+            // confirm that we can get a phantom record
+            record = store.getRecordFromFeature(features[0]);
+            t.ok((record && record.get("feature")) === features[0],
+                 "phantom record retrieved");
+            
+            // confirm that we can get a normal record
+            record = store.getRecordFromFeature(features[1]);
+            t.ok((record && record.get("feature")) === features[1],
+                 "normal record retrieved");
+
+            layer.destroy();
+        }
+        
         function test_addFeatures_removeFeatures(t) {
             
             t.plan(5);
@@ -163,7 +192,11 @@
             var features = [ 
                 new OpenLayers.Feature.Vector(), 
                 new OpenLayers.Feature.Vector() 
-            ]; 
+            ];
+            
+            // set state of one feature to insert to test phantom record removal
+            features[0].state = OpenLayers.State.INSERT;
+            
             var layer = new OpenLayers.Layer.Vector("Foo layer");
             var store = new GeoExt.data.FeatureStore({
                 layer: layer
@@ -187,19 +220,30 @@
         }
 
         function test_featuremodified_update(t) {
-            t.plan(6);
+            t.plan(10);
 
             /*
              * Set up
              */
             var feature, id, layer, store, recordType, record;
-
+            
             feature = new OpenLayers.Feature.Vector(null, {
                 foo: "foo",
                 bar: "bar"
             });
+            // confirm that we can update phantom records
+            feature.state = OpenLayers.State.INSERT;
 
             id = feature.id;
+            
+            function keys(obj) {
+                var list = [];
+                for(var k in obj) {
+                    list.push(k);
+                }
+                return list;
+            }
+            var originalAttrs = keys(feature.attributes);
 
             recordType = GeoExt.data.FeatureRecord.create([
                 {name: "foo"}, {name: "bar"}
@@ -212,11 +256,12 @@
                 fields: recordType,
                 data: [feature]
             });
+            record = store.getRecordFromFeature(feature);
 
-            t.eq(store.getById(id).get("foo"), "foo",
+            t.eq(record.get("foo"), "foo",
                  "record gets correct initial value for property \"foo\"");
 
-            t.eq(store.getById(id).get("bar"), "bar",
+            t.eq(record.get("bar"), "bar",
                  "record gets correct initial value for property \"bar\"");
 
             /*
@@ -228,24 +273,45 @@
             feature.attributes.bar = "bar2";
             layer.events.triggerEvent("featuremodified", {feature: feature});
 
-            t.eq(store.getById(id).get("foo"), "foo2",
+            t.eq(record.get("foo"), "foo2",
                  "featuremodified event causes update of record property \"foo\"");
 
-            t.eq(store.getById(id).get("bar"), "bar2",
+            t.eq(record.get("bar"), "bar2",
                  "featuremodified event causes update of record property \"bar\"");
 
             // update
-            record = store.getById(id);
             record.set("foo", "foo3");
             record.set("bar", "bar3");
 
-            t.eq(layer.getFeatureById(id).attributes.foo, "foo3",
+            t.eq(feature.attributes.foo, "foo3",
                  "update event causes update of feature property \"foo\"");
 
-            t.eq(layer.getFeatureById(id).attributes.bar, "bar3",
+            t.eq(feature.attributes.bar, "bar3",
                  "update event causes update of feature property \"bar\"");
+            
+            // make sure calling record.set didn't add any attributes
+            var currentAttrs = keys(feature.attributes);
+            t.eq(originalAttrs.length, currentAttrs.length,
+                 "no new attributes added: " + currentAttrs.join(", "));
+            
+            var feature2 = new OpenLayers.Feature.Vector(null, {
+                "foo": "foo_f2"
+            });
+            layer.addFeatures([feature2]);
+            store.getById(feature2.id).set("bar", "bar_f2");
+            
+            t.eq(feature2.attributes.bar, "bar_f2", "previously undefined attribute set correctly");
+            
 
-         }
+            // update with falsey vlues
+            feature.attributes.foo = false;
+            feature.attributes.bar = 0;
+            layer.events.triggerEvent("featuremodified", {feature: feature});
+            
+            t.eq(record.get("foo"), false, "false correctly set");
+            t.eq(record.get("bar"), 0, "0 correctly set");
+
+        }
     </script> 
  
   <body> 

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerReader.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerReader.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerReader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -33,7 +33,7 @@
     }
 
     function test_readRecords(t) {
-        t.plan(5);
+        t.plan(6);
 
         var reader, layers, data;
 
@@ -50,6 +50,8 @@
              "readRecords returns expected number of records");
         t.ok(data.records[0] instanceof GeoExt.data.LayerRecord,
              "readRecords returns records of expected type");
+        t.ok(data.records[0].id == layers[0].id,
+             "readRecords returns records with expected \"id\"");
         t.ok(data.records[0].get("layer") == layers[0],
              "readRecords returns records with expected \"layer\" field");
         t.eq(data.records[0].get("title"), layers[0].name,

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerRecord.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerRecord.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerRecord.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -43,6 +43,27 @@
             record = new c({layer: layer, title: layer.name, foo: "bar"}, layer.id);
             t.eq(record.get("foo"), "bar", "foo data row set correctly");
         }
+
+         
+        function test_clone(t) { 
+             
+            t.plan(4); 
+             
+            var layer = new OpenLayers.Layer(); 
+            var recordType = GeoExt.data.LayerRecord.create(); 
+             
+            var record = new recordType({layer: layer, title: layer.name}); 
+            var clone = record.clone(); 
+
+            t.ok(clone.id !== record.id, "clone record does not have original id"); 
+            t.ok(clone.id == clone.get("layer").id, "clone record id is same as its layer id"); 
+             
+            record.set("title", "foo"); 
+            t.ok(clone.get("title") !== "foo", "setting a property on original doesn't modify clone"); 
+             
+            t.ok(clone.get("layer") !== layer, "clone does not have original layer"); 
+             
+        } 
         
     </script>
   <body>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerStore.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerStore.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/LayerStore.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -84,6 +84,35 @@
             t.eq(mapPanel.layers.getCount(),1,"Adding layers to MapPanel's LayerStore does not create duplicate layers"); 
         }
 
+        function test_store_to_map(t) {
+
+            t.plan(8);
+            
+            var map = new OpenLayers.Map("mappanel");
+            var layers = [new OpenLayers.Layer.Vector("a"),
+                          new OpenLayers.Layer.Vector("b"),
+                          new OpenLayers.Layer.Vector("c")];
+
+            var store = new GeoExt.data.LayerStore({
+                map: map,
+                layers: layers
+            });
+
+            t.eq(store.getCount(), 3, "three layers in store");
+            t.eq(map.layers.length, 3, "three layers on map");
+
+            t.eq(store.getAt(0).get("layer").name, "a", "first layer correct in store");
+            t.eq(map.layers[0].name, "a", "first layer correct on map");
+
+            t.eq(store.getAt(1).get("layer").name, "b", "second layer correct in store");
+            t.eq(map.layers[1].name, "b", "second layer correct on map");
+
+            t.eq(store.getAt(2).get("layer").name, "c", "third layer correct in store");
+            t.eq(map.layers[2].name, "c", "third layer correct on map");
+
+            map.destroy();
+        }
+
         function test_load_clear(t) {
             t.plan(2);
             
@@ -285,6 +314,26 @@
 
         }
         
+        function test_update(t) {
+            t.plan(2);
+            
+            var map = new OpenLayers.Map("mappanel");
+            var layer = new OpenLayers.Layer("foo");
+            map.addLayer(layer);
+
+            var store = new GeoExt.data.LayerStore({
+                map: map
+            });
+
+            layer.setName("newName");
+            t.eq(store.getAt(0).get("title"), "newName", "record title synced from layer name");
+            
+            store.getAt(0).set("title", "newTitle");
+            t.eq(layer.name, "newTitle", "layer name synced from record title");
+                                    
+            map.destroy();
+        }
+        
         function test_events(t) {
             t.plan(21);
             
@@ -370,7 +419,30 @@
             
         }
         
+        function test_map_destroy(t) {
+            t.plan(1);
+            
+            var map = new OpenLayers.Map({div: "mappanel", allOverlays: true});
+            var a = new OpenLayers.Layer("a");
+            var b = new OpenLayers.Layer("b");
+            map.addLayers([a, b]);
 
+            var store = new GeoExt.data.LayerStore({
+                map: map
+            });
+            
+            var count = 0;
+            store.on("remove", function() {
+                count++;
+            });
+            
+            map.removeLayer(a);
+            map.destroy();
+            
+            t.eq(count, 1, "store's remove handler called once");
+        }
+        
+
     </script>
   </head>  
   <body>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ProtocolProxy.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ProtocolProxy.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ProtocolProxy.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -7,7 +7,7 @@
     <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
     <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
 
-    <script type="text/javascript"><!--
+    <script type="text/javascript">
         function test_constructor(t) {
             t.plan(1);
             // setup
@@ -20,6 +20,7 @@
             t.ok(proxy.protocol == protocol,
                  "ctor sets protocol in protocol property");
         }
+
         function test_load(t) {
             t.plan(8);
             // setup
@@ -67,8 +68,30 @@
             // 8 tests
             proxy.load(params, reader, callback, scope, arg);
         }
-    --></script>
+        
+        function test_abort(t) {
+            t.plan(1);
 
+            var protocol = new OpenLayers.Protocol({
+                read: function(o) {
+                    return new OpenLayers.Protocol.Response();
+                },
+                abort: function(response) {
+                    t.ok("protocol.abort called");
+                }
+            });
+            var proxy = new GeoExt.data.ProtocolProxy({
+                protocol: protocol
+            });
+
+            proxy.load(); // abort not called because proxy.response is null
+            proxy.load(); // abort called
+            proxy.abortPrevious = false;
+            proxy.load(); // abort not called because proxy.abortPrevious is false
+        }
+
+    </script>
+
   <body>
     <div id="map"></div>
   </body>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ScaleStore.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ScaleStore.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/ScaleStore.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -45,6 +45,8 @@
             map = createMap();
             store = new GeoExt.data.ScaleStore({map: map});
             t.ok(store.map == map, "ctor sets the passed map in the instance");
+            
+            map.destroy();
         }
 
         function test_bind_unbind(t) {
@@ -84,15 +86,31 @@
             store.unbind();
             t.eq(map.events.listeners["changebaselayer"][0], undefined,
                  "unbind unregisters changebaselayer listener (map has a base layer)");
+            
+            map.destroy();
         }
 
         function test_scalestore(t) {
-            t.plan(1);
+            t.plan(3);
 
             var mapPanel = loadMapPanel();
             var map = mapPanel.map;
             var store = new GeoExt.data.ScaleStore({map: map});
-            t.eq(store.data.length, 15, 'Found expected number of zoomlevels');
+            var levels = map.baseLayer.resolutions.length;
+            t.eq(store.getCount(), levels,
+                 'Found expected number of zoomlevels: ' + levels);
+            
+            var low = store.getAt(0).get("resolution");
+            t.eq(low, map.baseLayer.resolutions[levels-1],
+                 'First record has lowest res value: ' + low);
+
+            var high = store.getAt(levels-1).get("resolution");
+            t.eq(high, map.baseLayer.resolutions[0],
+                 'Last record has highest res value: ' + high);
+            
+            map.destroy();
+            mapPanel.destroy();
+            
         }
         
     </script>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+    <script type="text/javascript" src="WFSCapabilitiesReader.js"></script>
+
+    <script type="text/javascript">
+      
+        function test_constructor(t) {
+            t.plan(2);
+            var reader = new GeoExt.data.WFSCapabilitiesReader();
+
+            var fields = reader.recordType.prototype.fields;
+
+            // 1 test
+            t.eq(fields.items.length, 4, 'number of default items is correct');
+
+            var reader = new GeoExt.data.WFSCapabilitiesReader({},[
+                {name: "foo"},
+                {name: "bar"}
+            ]);
+
+            var fields = reader.recordType.prototype.fields;
+
+            //1 test
+            t.ok(fields.items[2].name == 'foo' &&
+                 fields.items[3].name == 'bar',
+                 'field values set from configuration are correct');
+        }
+        
+        function test_read(t) {
+            t.plan(7);
+
+            var reader = new GeoExt.data.WFSCapabilitiesReader();
+
+            var records = reader.read({responseXML : doc});
+
+            //1 test
+            t.eq(records.totalRecords, 5, 'readRecords returns correct number of records');
+            
+            var record = records.records[0];
+
+            //3 tests -- testing the fields of a record
+            t.eq(record.get("name"), "sf:archsites", "[0] correct layer name");
+            t.eq(record.get("title"), "Spearfish archeological sites", "[0] correct layer title");
+            t.eq(
+                record.get("abstract"),
+               "Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA",
+                "[0] correct layer abstract"
+            );
+
+            //3 tests -- Testing the layer field (and its default protocol)
+            var layer = record.get("layer");
+            t.eq(layer.CLASS_NAME, "OpenLayers.Layer.Vector", "[0] layer field is of type OpenLayers.Layer.Vector");
+            t.eq(layer.protocol.CLASS_NAME, "OpenLayers.Protocol.WFS.v1_0_0", "[0] protocol is of type OpenLayers.Protocol.WFS.v1_0_0");
+            t.eq(layer.protocol.url, "http://someserver.com:8080/geoserver/wfs?", "[0] protocol has correct URL");
+        }
+        
+    </script>
+  <body>
+    <div id="map"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.js (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WFSCapabilitiesReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,329 @@
+var doc = (new OpenLayers.Format.XML).read(
+'<?xml version="1.0" encoding="UTF-8"?>'+
+'<WFS_Capabilities version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://someserver.com:8080/geoserver/schemas/wfs/1.0.0/WFS-capabilities.xsd" xmlns="http://www.opengis.net/wfs" xmlns:cite="http://www.opengeospatial.net/cite" xmlns:it.geosolutions="http://www.geo-solutions.it" xmlns:tiger="http://www.census.gov" xmlns:sde="http://geoserver.sf.net" xmlns:topp="http://www.openplans.org/topp" xmlns:sf="http://www.openplans.org/spearfish" xmlns:nurc="http://www.nurc.nato.int" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'+
+    '<Service>'+
+        '<Name>WFS</Name>'+
+        '<Title>GeoServer Web Feature Service</Title>'+
+        '<Abstract>This is the reference implementation of WFS 1.0.0 and WFS 1.1.0, supports all WFS operations including Transaction.</Abstract>'+
+        '<Keywords>WFS, WMS, GEOSERVER</Keywords>'+
+        '<OnlineResource>http://someserver.com:8080/geoserver/wfs</OnlineResource>'+
+        '<Fees>NONE</Fees>'+
+        '<AccessConstraints>NONE</AccessConstraints>'+
+    '</Service>'+
+    '<Capability>'+
+        '<Request>'+
+            '<GetCapabilities>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Get onlineResource="http://someserver.com:8080/geoserver/wfs?request=GetCapabilities" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Post onlineResource="http://someserver.com:8080/geoserver/wfs?" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+            '</GetCapabilities>'+
+            '<DescribeFeatureType>'+
+                '<SchemaDescriptionLanguage>'+
+                    '<XMLSCHEMA />'+
+                '</SchemaDescriptionLanguage>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Get onlineResource="http://someserver.com:8080/geoserver/wfs?request=DescribeFeatureType" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Post onlineResource="http://someserver.com:8080/geoserver/wfs?" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+            '</DescribeFeatureType>'+
+            '<GetFeature>'+
+                '<ResultFormat>'+
+                    '<GML2 />'+
+                    '<SHAPE-ZIP />'+
+                    '<GEOJSON />'+
+                    '<csv />'+
+                    '<GML3 />'+
+                '</ResultFormat>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Get onlineResource="http://someserver.com:8080/geoserver/wfs?request=GetFeature" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Post onlineResource="http://someserver.com:8080/geoserver/wfs?" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+            '</GetFeature>'+
+            '<Transaction>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Get onlineResource="http://someserver.com:8080/geoserver/wfs?request=Transaction" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Post onlineResource="http://someserver.com:8080/geoserver/wfs?" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+            '</Transaction>'+
+            '<LockFeature>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Get onlineResource="http://someserver.com:8080/geoserver/wfs?request=LockFeature" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Post onlineResource="http://someserver.com:8080/geoserver/wfs?" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+            '</LockFeature>'+
+            '<GetFeatureWithLock>'+
+                '<ResultFormat>'+
+                    '<GML2 />'+
+                '</ResultFormat>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Get onlineResource="http://someserver.com:8080/geoserver/wfs?request=GetFeatureWithLock" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+                '<DCPType>'+
+                    '<HTTP>'+
+                        '<Post onlineResource="http://someserver.com:8080/geoserver/wfs?" />'+
+                    '</HTTP>'+
+                '</DCPType>'+
+            '</GetFeatureWithLock>'+
+        '</Request>'+
+    '</Capability>'+
+    '<FeatureTypeList>'+
+        '<Operations>'+
+            '<Query />'+
+            '<Insert />'+
+            '<Update />'+
+            '<Delete />'+
+            '<Lock />'+
+        '</Operations>'+
+        '<FeatureType>'+
+            '<Name>sf:archsites</Name>'+
+            '<Title>Spearfish archeological sites</Title>'+
+            '<Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract>'+
+            '<Keywords>archsites, spearfish, sfArchsites, archeology</Keywords>'+
+            '<SRS>EPSG:26713</SRS>'+
+            '<LatLongBoundingBox minx="-103.8725637911543" miny="44.37740330855979" maxx="-103.63794182141925" maxy="44.48804280772808" />'+
+        '</FeatureType>'+
+        '<FeatureType>'+
+            '<Name>sf:bugsites</Name>'+
+            '<Title>Spearfish bug locations</Title>'+
+            '<Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract>'+
+            '<Keywords>spearfish, sfBugsites, insects, bugsites, tiger_beetles</Keywords>'+
+            '<SRS>EPSG:26713</SRS>'+
+            '<LatLongBoundingBox minx="-103.86796131703647" miny="44.373938816704396" maxx="-103.63773523234195" maxy="44.43418821380063" />'+
+        '</FeatureType>'+
+        '<FeatureType>'+
+            '<Name>sf:restricted</Name>'+
+            '<Title>Spearfish restricted areas</Title>'+
+            '<Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract>'+
+            '<Keywords>spearfish, restricted, areas, sfRestricted</Keywords>'+
+            '<SRS>EPSG:26713</SRS>'+
+            '<LatLongBoundingBox minx="-103.85057172920756" miny="44.39436387625042" maxx="-103.74741494853805" maxy="44.48215752041131" />'+
+        '</FeatureType>'+
+        '<FeatureType>'+
+            '<Name>sf:roads</Name>'+
+            '<Title>Spearfish roads</Title>'+
+            '<Abstract>Sample data from GRASS, road layout, Spearfish, South Dakota, USA</Abstract>'+
+            '<Keywords>sfRoads, spearfish, roads</Keywords>'+
+            '<SRS>EPSG:26713</SRS>'+
+            '<LatLongBoundingBox minx="-103.87741691493184" miny="44.37087275281798" maxx="-103.62231404880659" maxy="44.50015918338962" />'+
+        '</FeatureType>'+
+        '<FeatureType>'+
+            '<Name>sf:streams</Name>'+
+            '<Title>Spearfish streams</Title>'+
+            '<Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract>'+
+            '<Keywords>spearfish, sfStreams, streams</Keywords>'+
+            '<SRS>EPSG:26713</SRS>'+
+            '<LatLongBoundingBox minx="-103.87789019829768" miny="44.372335260095554" maxx="-103.62287788915457" maxy="44.502218486214815" />'+
+        '</FeatureType>'+
+    '</FeatureTypeList>'+
+    '<ogc:Filter_Capabilities>'+
+        '<ogc:Spatial_Capabilities>'+
+            '<ogc:Spatial_Operators>'+
+                '<ogc:Disjoint />'+
+                '<ogc:Equals />'+
+                '<ogc:DWithin />'+
+                '<ogc:Beyond />'+
+                '<ogc:Intersect />'+
+                '<ogc:Touches />'+
+                '<ogc:Crosses />'+
+                '<ogc:Within />'+
+                '<ogc:Contains />'+
+                '<ogc:Overlaps />'+
+                '<ogc:BBOX />'+
+            '</ogc:Spatial_Operators>'+
+        '</ogc:Spatial_Capabilities>'+
+        '<ogc:Scalar_Capabilities>'+
+            '<ogc:Logical_Operators />'+
+            '<ogc:Comparison_Operators>'+
+                '<ogc:Simple_Comparisons />'+
+                '<ogc:Between />'+
+                '<ogc:Like />'+
+                '<ogc:NullCheck />'+
+            '</ogc:Comparison_Operators>'+
+            '<ogc:Arithmetic_Operators>'+
+                '<ogc:Simple_Arithmetic />'+
+                '<ogc:Functions>'+
+                    '<ogc:Function_Names>'+
+                        '<ogc:Function_Name nArgs="1">abs</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">abs_2</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">abs_3</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">abs_4</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">acos</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Area</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">asin</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">atan</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">atan2</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">between</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">boundary</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">boundaryDimension</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">buffer</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">bufferWithSegments</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">ceil</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">centroid</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">classify</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Average</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Bounds</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Count</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Max</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Median</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Min</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Sum</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">Collection_Unique</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">Concatenate</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">contains</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">convert</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">convexHull</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">cos</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">crosses</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">dateFormat</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">dateParse</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">difference</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">dimension</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">disjoint</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">distance</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">double2bool</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">endPoint</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">envelope</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">EqualInterval</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">equalsExact</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">equalsExactTolerance</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">equalTo</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">exp</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">exteriorRing</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">floor</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">geometryType</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">geomFromWKT</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">geomLength</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">getGeometryN</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">getX</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">getY</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">getZ</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">greaterEqualThan</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">greaterThan</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="0">id</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">IEEEremainder</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">if_then_else</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="11">in10</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">in2</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="4">in3</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="5">in4</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="6">in5</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="7">in6</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="8">in7</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="9">in8</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="10">in9</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">int2bbool</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">int2ddouble</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">interiorPoint</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">interiorRingN</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">intersection</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">intersects</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">isClosed</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">isEmpty</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">isLike</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">isNull</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">isRing</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">isSimple</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">isValid</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">isWithinDistance</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">length</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">lessEqualThan</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">lessThan</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">log</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">max</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">max_2</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">max_3</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">max_4</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">min</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">min_2</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">min_3</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">min_4</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">not</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">notEqualTo</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">numberFormat</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">numGeometries</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">numInteriorRing</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">numPoints</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">overlaps</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">parseBoolean</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">parseDouble</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">parseInt</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">parseLong</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="0">pi</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">pointN</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">pow</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">PropertyExists</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">Quantile</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="0">random</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">relate</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">relatePattern</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">rint</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">round</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">round_2</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">roundDouble</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">sin</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">sqrt</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">StandardDeviation</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">startPoint</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strConcat</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strEndsWith</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strEqualsIgnoreCase</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strIndexOf</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strLastIndexOf</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">strLength</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strMatches</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="4">strReplace</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strStartsWith</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="3">strSubstring</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">strSubstringStart</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">strToLowerCase</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">strToUpperCase</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">strTrim</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">symDifference</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">tan</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">toDegrees</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">toRadians</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">touches</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="1">toWKT</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">union</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">UniqueInterval</ogc:Function_Name>'+
+                        '<ogc:Function_Name nArgs="2">within</ogc:Function_Name>'+
+                    '</ogc:Function_Names>'+
+                '</ogc:Functions>'+
+            '</ogc:Arithmetic_Operators>'+
+        '</ogc:Scalar_Capabilities>'+
+    '</ogc:Filter_Capabilities>'+
+'</WFS_Capabilities>'
+);
\ No newline at end of file

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/WMCReader.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+    <script type="text/javascript" src="WMCReader.js"></script>
+
+    <script type="text/javascript">
+      
+        function test_constructor(t) {
+            t.plan(2);
+            var reader = new GeoExt.data.WMCReader();
+
+            var fields = reader.recordType.prototype.fields;
+
+            // 1 test
+            t.eq(fields.items.length, 7, 'number of default items is correct');
+
+            reader = new GeoExt.data.WMCReader({},[
+                {name: "foo"},
+                {name: "bar"}
+            ]);
+
+            fields = reader.recordType.prototype.fields;
+
+            //1 test
+            t.ok(fields.items[2].name == 'foo' &&
+                 fields.items[3].name == 'bar',
+                 'field values set from configuration are correct');
+        }
+        function test_read(t) {
+            t.plan(19);
+
+            // test a reader with the only two default LayerRecord fields
+
+            var reader = new GeoExt.data.WMCReader({},{});
+            var records = reader.read({responseXML : doc});
+
+            //1 test
+            t.eq(records.totalRecords, 3, 'readRecords returns correct number of records');
+
+            var record = records.records[1];
+            //2 tests -- testing the fields of a record
+            t.eq(record.get("title"), "geob:communes_geofla", "correct title record field");
+            t.eq(record.get("abstract"), undefined, "correct undefined abstract which is not part of fields");
+
+
+            // test a reader with all default fields
+
+            var reader = new GeoExt.data.WMCReader();
+            var records = reader.read({responseXML : doc});
+
+            //1 test
+            t.eq(records.totalRecords, 3, 'readRecords returns correct number of records');
+
+            var record = records.records[1];
+            //10 tests -- testing the fields of a record
+            t.eq(record.get("title"), "geob:communes_geofla", "correct title record field");
+            t.eq(record.get("abstract"), "Communes abstract", "correct abstract record field");
+            t.eq(record.get("metadataURL"), "", "correct metadataURL record field");
+            t.eq(record.get("queryable"), true, "correct queryable record field");
+            t.eq(record.get("formats").length, 28, "correct length for formats record field");
+            t.eq(record.get("formats")[0].value, "image/png", "correct value for formats record field");
+            t.eq(record.get("styles").length, 2, "correct length for styles record field");
+            t.eq(record.get("styles")[0].abstract, "Default line style, 1 pixel wide blue", "correct abstract for styles record field");
+            t.eq(record.get("styles")[0].name, "line", "correct name for styles record field");
+            t.eq(record.get("styles")[0].title, "1 px blue line", "correct title for styles record field");
+
+            //4 tests -- Testing the layer field
+            var layer = record.get("layer");
+            t.eq(layer.CLASS_NAME, "OpenLayers.Layer.WMS", "layer record field is of type OpenLayers.Layer.WMS");
+            t.eq(layer.url, "../geoserver/wms?SERVICE=WMS&", "layer record field has correct URL");
+            t.eq(layer.params.LAYERS, "geob:communes_geofla","layer record field has correct LAYERS parameter");
+            t.eq(layer.name, "Communes","layer record field has correct name");
+
+            //1 test
+            t.eq(record.id, layer.id, 'record id is the same as layer id');
+        }
+    </script>
+  <body>
+    <div id="map"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.js (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/WMCReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMCReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,133 @@
+var doc = (new OpenLayers.Format.XML).read(
+    '<ViewContext xmlns="http://www.opengis.net/context" version="1.1.0" id="default.wmc" xsi:schemaLocation="http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+      '<General>' +
+        '<Window width="1031" height="389"/>' +
+        '<BoundingBox minx="-37179.8441128839986" miny="6691080.05881679989" maxx="540179.844112879946" maxy="6908919.94118320011" SRS="EPSG:2154"/>' +
+        '<Title/>' +
+        '<Extension>' +
+          '<ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="83000.0000000000000" miny="6700000.00000000000" maxx="420000.000000000000" maxy="6900000.00000000000"/>' +
+        '</Extension>' +
+      '</General>' +
+      '<LayerList>' +
+        '<Layer queryable="0" hidden="0">' +
+          '<Server service="OGC:WMS" version="1.1.1">' +
+            '<OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="../geoserver/gwc/service/wms"/>' +
+          '</Server>' +
+          '<Name>geob:SC1000_0050_7130_L93</Name>' +
+          '<Title>Scan 1000</Title>' +
+          '<Abstract>Scan 1000 abstract</Abstract>' +
+          '<sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">250000.0000000000</sld:MinScaleDenominator>' +
+          '<sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">2000000.000000000</sld:MaxScaleDenominator>' +
+          '<FormatList>' +
+            '<Format current="1">image/png</Format>' +
+          '</FormatList>' +
+          '<StyleList>' +
+            '<Style>' +
+              '<Name/>' +
+              '<Title/>' +
+            '</Style>' +
+          '</StyleList>' +
+          '<Extension>' +
+            '<ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="83000.0000000000000" miny="6700000.00000000000" maxx="420000.000000000000" maxy="6900000.00000000000"/>' +
+            '<ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels>' +
+            '<ol:units xmlns:ol="http://openlayers.org/context">m</ol:units>' +
+            '<ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer>' +
+            '<ol:opacity xmlns:ol="http://openlayers.org/context">1</ol:opacity>' +
+            '<ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher>' +
+            '<ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile>' +
+          '</Extension>' +
+        '</Layer>' +
+        '<Layer queryable="1" hidden="0">' +
+          '<Server service="OGC:WMS" version="1.1.1">' +
+            '<OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="../geoserver/wms?SERVICE=WMS&amp;"/>' +
+          '</Server>' +
+          '<Name>geob:communes_geofla</Name>' +
+          '<Title>Communes</Title>' +
+          '<Abstract>Communes abstract</Abstract>' +
+          '<FormatList>' +
+            '<Format current="1">image/png</Format>' +
+            '<Format>application/atom xml</Format>' +
+            '<Format>application/atom+xml</Format>' +
+            '<Format>application/openlayers</Format>' +
+            '<Format>application/pdf</Format>' +
+            '<Format>application/rss xml</Format>' +
+            '<Format>application/rss+xml</Format>' +
+            '<Format>application/vnd.google-earth.kml</Format>' +
+            '<Format>application/vnd.google-earth.kml xml</Format>' +
+            '<Format>application/vnd.google-earth.kml+xml</Format>' +
+            '<Format>application/vnd.google-earth.kmz</Format>' +
+            '<Format>application/vnd.google-earth.kmz xml</Format>' +
+            '<Format>application/vnd.google-earth.kmz+xml</Format>' +
+            '<Format>atom</Format>' +
+            '<Format>image/geotiff</Format>' +
+            '<Format>image/geotiff8</Format>' +
+            '<Format>image/gif</Format>' +
+            '<Format>image/jpeg</Format>' +
+            '<Format>image/png8</Format>' +
+            '<Format>image/svg</Format>' +
+            '<Format>image/svg xml</Format>' +
+            '<Format>image/svg+xml</Format>' +
+            '<Format>image/tiff</Format>' +
+            '<Format>image/tiff8</Format>' +
+            '<Format>kml</Format>' +
+            '<Format>kmz</Format>' +
+            '<Format>openlayers</Format>' +
+            '<Format>rss</Format>' +
+          '</FormatList>' +
+          '<StyleList>' +
+            '<Style>' +
+              '<Name>line</Name>' +
+              '<Title>1 px blue line</Title>' +
+              '<Abstract>Default line style, 1 pixel wide blue</Abstract>' +
+            '</Style>' +
+            '<Style>' +
+              '<Name>dpt_classif</Name>' +
+              '<Title>Classification par départements</Title>' +
+            '</Style>' +
+          '</StyleList>' +
+          '<Extension>' +
+            '<ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="83000.0000000000000" miny="6700000.00000000000" maxx="420000.000000000000" maxy="6900000.00000000000"/>' +
+            '<ol:transparent xmlns:ol="http://openlayers.org/context">true</ol:transparent>' +
+            '<ol:numZoomLevels xmlns:ol="http://openlayers.org/context">13</ol:numZoomLevels>' +
+            '<ol:units xmlns:ol="http://openlayers.org/context">m</ol:units>' +
+            '<ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer>' +
+            '<ol:opacity xmlns:ol="http://openlayers.org/context">0.5</ol:opacity>' +
+            '<ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher>' +
+            '<ol:singleTile xmlns:ol="http://openlayers.org/context">true</ol:singleTile>' +
+          '</Extension>' +
+        '</Layer>' +
+        '<Layer queryable="1" hidden="0">' +
+          '<Server service="OGC:WMS" version="1.1.1">' +
+            '<OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://geolittoral.application.equipement.gouv.fr/map/mapserv?map=/opt/data/carto/applis/geolittoral/map/metropole.www.map&amp;"/>' +
+          '</Server>' +
+          '<Name>Sentiers_littoraux</Name>' +
+          '<Title>Sentiers littoraux</Title>' +
+          '<FormatList>' +
+            '<Format current="1">image/png</Format>' +
+            '<Format>image/gif</Format>' +
+            '<Format>image/png; mode=24bit</Format>' +
+            '<Format>image/jpeg</Format>' +
+            '<Format>image/vnd.wap.wbmp</Format>' +
+            '<Format>image/tiff</Format>' +
+            '<Format>image/svg+xml</Format>' +
+          '</FormatList>' +
+          '<StyleList>' +
+            '<Style>' +
+              '<Name>default</Name>' +
+              '<Title>default</Title>' +
+            '</Style>' +
+          '</StyleList>' +
+          '<Extension>' +
+            '<ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="83000.0000000000000" miny="6700000.00000000000" maxx="420000.000000000000" maxy="6900000.00000000000"/>' +
+            '<ol:transparent xmlns:ol="http://openlayers.org/context">true</ol:transparent>' +
+            '<ol:numZoomLevels xmlns:ol="http://openlayers.org/context">13</ol:numZoomLevels>' +
+            '<ol:units xmlns:ol="http://openlayers.org/context">m</ol:units>' +
+            '<ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer>' +
+            '<ol:opacity xmlns:ol="http://openlayers.org/context">1</ol:opacity>' +
+            '<ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher>' +
+            '<ol:singleTile xmlns:ol="http://openlayers.org/context">true</ol:singleTile>' +
+          '</Extension>' +
+        '</Layer>' +
+      '</LayerList>' +
+    '</ViewContext>'
+);

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -17,9 +17,8 @@
             var fields = reader.recordType.prototype.fields;
 
             // 1 test
-            t.eq(fields.items.length, 11, 'number of default items is correct');
+            t.eq(fields.items.length, 24, 'number of default items is correct');
 
-
             var reader = new GeoExt.data.WMSCapabilitiesReader({},[
                 {name: "foo"},
                 {name: "bar"}
@@ -33,18 +32,41 @@
                  'field values set from configuration are correct');
         }
         function test_read(t) {
-            t.plan(13);
+            t.plan(39);
 
-            var reader = new GeoExt.data.WMSCapabilitiesReader();
+            // test a reader with the only two default LayerRecord fields
+            var reader = new GeoExt.data.WMSCapabilitiesReader({}, []);
 
-            var records = reader.read({responseXML : doc});
+            var records = reader.read({responseXML: doc});
 
             //1 test
-            t.eq(records.totalRecords, 22, 'readRecords returns correct number of records');
-            
+            t.eq(records.records[0].fields.items.length, 2, 'LayerRecord with 2 default fields');
+
             var record = records.records[2];
+            //2 tests -- testing the fields of a record
+            t.eq(record.get("title"), "tiger:tiger_roads", "correct layer title");
+            t.eq(record.get("abstract"), undefined, "correct undefined abstract which is not part of fields");
 
-            //8 tests -- testing the fields of a record
+            // test a reader with all default fields
+
+            var reader = new GeoExt.data.WMSCapabilitiesReader({
+                layerOptions: {
+                    singleTile: true
+                }
+            });
+            var records = reader.read({responseXML: doc});
+
+            //1 test
+            t.eq(records.totalRecords, 22, 'readRecords returns correct number of records');
+
+            var record, layer;
+
+            //1 test -- testing value of record id
+            record = records.records[2];
+            t.ok(record.id == record.get("layer").id, "[2] correct record id");
+
+            //22 tests -- testing the fields of a record
+            record = records.records[2];
             t.eq(record.get("name"), "tiger:tiger_roads", "[2] correct layer name");
             t.eq(record.get("title"), "Manhattan (NY) roads", "[2] correct layer title");
             t.eq(
@@ -65,14 +87,64 @@
                 "[2] correct legend url"
             );
             t.eq(record.get("queryable"), true, "[2] correct queryable attribute");
+            t.eq(record.get("opaque"), true, "[2] correct opaque attribute");
+            t.eq(record.get("cascaded"), 3, "[2] correct cascaded attribute");
+            t.eq(record.get("noSubsets"), true, "[2] correct noSubsets attribute");
+            t.eq(record.get("fixedWidth"), 400, "[2] correct fixedWidth attribute");
+            t.eq(record.get("fixedHeight"), 200, "[2] correct fixedHeight attribute");
+            t.eq(record.get("formats")[0], "image/png", "[2] correct image/png formats attribute");
+            t.eq(record.get("formats")[14], "image/geotiff", "[2] correct image/geotiff formats attribute");
+            t.eq(record.get("formats")[27], "rss", "[2] correct rss formats attribute");            
+            var srs = record.get("srs");
+            var count = 0;
+            for (var key in srs) {
+                if (srs.hasOwnProperty(key)) {
+                    count++;
+                }
+            }
+            t.eq(count, 3912, "[2] correct number of supported srs");
+            t.eq(srs["EPSG:42303"], true, "[2] random srs in list is supported");
+            t.eq(record.get("prefix"), "tiger", "[2] correct prefix attribute");
+            t.eq(
+                record.get("bbox")["EPSG:4326"].bbox,
+                [-74.02722,40.684221,-73.907005,40.878178],
+                "[2] correct bbox"
+            );
+            t.eq(record.get("authorityURLs").gcmd, "http://www.authority.com", "[2] correct authorityURLs attribute");
+            t.eq(record.get("identifiers").gcmd, "id_value", "[2] correct identifiers attribute");
+            // cannot test "dimensions" until we have nested layers: see http://trac.openlayers.org/ticket/2144
 
-            //4 tests -- Testing the layer field
-            var layer = record.get("layer");
+            //7 tests -- Testing the layer field
+            record = records.records[2];
+            layer = record.get("layer");
             t.eq(layer.CLASS_NAME, "OpenLayers.Layer.WMS", "[2] layer field is of type OpenLayers.Layer.WMS");
             t.eq(layer.url, "http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&", "[2] layer field has correct URL");
             t.eq(layer.params.LAYERS, "tiger:tiger_roads","[2] layer field has correct LAYERS parameter");
             t.eq(layer.name, "Manhattan (NY) roads","[2] layer field has correct name");
-
+            t.eq(typeof(layer.minScale), "number","[2] layer minScale has been set");
+            t.eq(typeof(layer.maxScale), "number","[2] layer maxScale has been set");
+            t.eq(layer.singleTile, true, "[2] layer field has correct singleTile attribute (from WMSCapabilitiesReader constructor)");
+            
+            // 3 tests -- attribution markup
+            record = records.records[2];
+            layer = record.get("layer");
+            var attribution = layer.attribution;
+            t.ok(attribution.indexOf("http://foo/logo.png") !== -1, "attribution markup has a logo");
+            t.ok(attribution.indexOf("Foo Authority") !== -1, "attribution markup has a title");
+            t.ok(attribution.indexOf("http://foo/about/") !== -1, "attribution has a link");
+            
+            // 2 tests - transparent param
+            var transparent;
+            record = records.records[2];
+            layer = record.get("layer");
+            transparent = layer.params.TRANSPARENT;
+            t.eq(transparent, false,
+                "TRANSPARENT param is false if opacity set to 1");
+            record = records.records[1];
+            layer = record.get("layer");
+            transparent = layer.params.TRANSPARENT;
+            t.eq(transparent, true,
+                "TRANSPARENT param is true if no opacity set");
         }
     </script>
   <body>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.js
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.js	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSCapabilitiesReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -4110,7 +4110,7 @@
               '</LegendURL>' +
             '</Style>' +
           '</Layer>' +
-          '<Layer queryable="1">' +
+          '<Layer queryable="1" opaque="1" cascaded="3" noSubsets="1" fixedWidth="400" fixedHeight="200">' +
             '<Name>tiger:tiger_roads</Name>' +
             '<Title>Manhattan (NY) roads</Title>' +
             '<Abstract>Highly simplified road layout of Manhattan in New York..</Abstract>' +
@@ -4132,6 +4132,16 @@
       'AUTHORITY["EPSG","4326"]]-->' +
             '<LatLonBoundingBox minx="-74.08769307536667" miny="40.660618924633326" maxx="-73.84653192463333" maxy="40.90178007536667"/>' +
             '<BoundingBox SRS="EPSG:4326" minx="-74.02722" miny="40.684221" maxx="-73.907005" maxy="40.878178"/>' +
+            '<Attribution>' +
+              '<Title>Foo Authority</Title>' +
+              '<OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://foo/about/" />' +
+              '<LogoURL width="24" height="24">' +
+                '<Format>image/png</Format>' +
+                '<OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://foo/logo.png" />' +
+              '</LogoURL>' +
+            '</Attribution>' +
+            '<AuthorityURL name="gcmd"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.authority.com" /></AuthorityURL>' +
+            '<Identifier authority="gcmd">id_value</Identifier>' +
             '<Style>' +
               '<Name>tiger_roads</Name>' +
               '<Title>Default Styler</Title>' +
@@ -4141,6 +4151,7 @@
                 '<OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:tiger_roads"/>' +
               '</LegendURL>' +
             '</Style>' +
+            '<ScaleHint min="37.4177136322228" max="299.341709057782" />' +
           '</Layer>' +
           '<Layer queryable="1">' +
             '<Name>sf:archsites</Name>' +

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+    <script type="text/javascript" src="WMSDescribeLayerReader.js"></script>
+
+    <script type="text/javascript">
+      
+        function test_constructor(t) {
+            t.plan(2);
+            var reader = new GeoExt.data.WMSDescribeLayerReader();
+
+            var fields = reader.recordType.prototype.fields;
+
+            // 1 test
+            t.eq(fields.items.length, 3, 'number of default items is correct');
+
+
+            var reader = new GeoExt.data.WMSDescribeLayerReader({},[
+                {name: "foo"},
+                {name: "bar"}
+            ]);
+
+            var fields = reader.recordType.prototype.fields;
+
+            //1 test
+            t.ok(fields.items[0].name == 'foo' &&
+                 fields.items[1].name == 'bar',
+                 'field values set from configuration are correct');
+        }
+        function test_read(t) {
+            t.plan(4);
+
+            var reader = new GeoExt.data.WMSDescribeLayerReader();
+
+            var records = reader.read({responseXML : doc});
+
+            //1 test
+            t.eq(records.totalRecords, 2, 'readRecords returns correct number of records');
+            
+            var record = records.records[0];
+
+            //3 tests -- testing the fields of a record
+            t.eq(record.get("owsType"), "WFS", "[0] correct owsType");
+            t.eq(record.get("owsURL"), "http://demo.opengeo.org/geoserver/wfs/WfsDispatcher?", "[0] correct owsURL");
+            t.eq(record.get("typeName"),"topp:states", "[0] correct typeName");
+        }
+    </script>
+  <body>
+    <div id="map"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.js (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.js)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.js	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/data/WMSDescribeLayerReader.js	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,11 @@
+var doc = (new OpenLayers.Format.XML).read(
+    '<?xml version="1.0" encoding="UTF-8"?>'+
+    '<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "http://demo.opengeo.org/geoserver/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd">'+
+    '<WMS_DescribeLayerResponse version="1.1.1">'+
+        '<LayerDescription name="topp:states" wfs="http://demo.opengeo.org/geoserver/wfs/WfsDispatcher?">'+
+            '<Query typeName="topp:states"/>'+
+        '</LayerDescription>'+
+        '<LayerDescription name="topp:bluemarble" wfs="http://demo.opengeo.org/geoserver/wfs/WfsDispatcher?">'+
+        '</LayerDescription>'+
+    '</WMS_DescribeLayerResponse>'
+);

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Action.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/Action.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Action.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Action.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,440 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+        function test_constructor(t) {
+            t.plan(12)
+
+            var ctrl, scope, handler, toggleHandler, checkHandler, cfg, action;
+
+            ctrl = new OpenLayers.Control();
+            
+            var map = new OpenLayers.Map();
+
+            scope = {}, handler = function() {};
+            toggleHandler = function() {}, checkHandler = function() {};
+
+            cfg = {
+                control: ctrl,
+                map: map,
+                scope: scope,
+                handler: handler,
+                toggleHandler: toggleHandler,
+                checkHandler: checkHandler
+            };
+
+            action = new GeoExt.Action(cfg);
+
+            t.ok(action.control == ctrl,
+                 "constructor sets control in the instance");
+            t.ok(action.control.map === map,
+                 "constructor adds control to map if provided");
+            t.ok(!action.initialConfig.map,
+                 "action does not have a reference to the map");
+            t.ok(action.uScope == scope,
+                 "constructor sets this.uScope to user-provided scope");
+            t.ok(action.uHandler == handler,
+                 "constructor sets this.uHandler to user-provided handler");
+            t.ok(action.uToggleHandler == toggleHandler,
+                 "constructor sets this.uToggleHandler to user-provided toggleHandler");
+            t.ok(action.uCheckHandler == checkHandler,
+                 "constructor sets this.uCheckHandler to user-provided checkHandler");
+
+            t.eq(action.initialConfig.control, undefined,
+                 "constructor does not set control in the initial config");
+            t.ok(action.initialConfig.scope == action,
+                 "constructor sets scope to this in the initial config");
+            t.ok(action.initialConfig.handler == action.pHandler,
+                 "constructor sets handler to this.pHandler in the initial config");
+            t.ok(action.initialConfig.toggleHandler == action.pToggleHandler,
+                 "constructor sets toggleHandler to this.ptoggleHandler in the initial config");
+            t.ok(action.initialConfig.checkHandler == action.pCheckHandler,
+                 "constructor sets checkHandler to this.pCheckHandler in the initial config");
+            
+            map.destroy();
+        }
+
+        function test_button(t) {
+            t.plan(6);
+
+            var menu, menuBtn, tb, ctrl, triggerCnt, handlerCnt;
+            var action, btn, item;
+
+            var evt = {
+                preventDefault: function() {},
+                stopEvent: function() {},
+                button: 0
+            };
+
+            menu = new Ext.menu.Menu({
+                // with shadow set to its default value ("sides")
+                // IE throws "object: Invalid argument" exceptions
+                shadow: false
+            });
+            menuBtn = new Ext.Button({menu: menu});
+
+            tb = new Ext.Toolbar({
+                renderTo: "toolbar",
+                buttons: [menuBtn]
+            });
+
+            // the "button" control
+            ctrl = new OpenLayers.Control({
+                type: OpenLayers.Control.TYPE_BUTTON,
+                trigger: function() {
+                    triggerCnt++;
+                }
+            });
+            ctrl.activate();
+
+            action = new GeoExt.Action({
+                control: ctrl,
+                handler: function() {
+                    handlerCnt++;
+                }
+            });
+            
+            // create button from action and it to toolbar
+            btn = new Ext.Button(action);
+            tb.add(btn);
+
+            // simulate click on button
+            // 2 tests
+            // test that the control's trigger is called exactly once
+            // test that the action handler is called exactly once
+            triggerCnt = 0;
+            handlerCnt = 0;
+            btn.onClick(evt);
+            t.eq(triggerCnt, 1, "click on button calls control trigger once");
+            t.eq(handlerCnt, 1, "click on button calls action handler once");
+
+            // create menu item from action and it to menu
+            item = new Ext.menu.Item(action);
+            menu.add(item);
+
+            // simulate click on menu item
+            // 2 tests
+            // test that the control's trigger is called exactly once
+            // test that the action handler is called exactly once
+            triggerCnt = 0;
+            handlerCnt = 0;
+            menuBtn.showMenu();
+            item.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(triggerCnt, 1, "click on menu item calls control trigger once");
+            t.eq(handlerCnt, 1, "click on menu item calls action handler once");
+
+            // deactivate control
+            // 2 tests
+            // test that button is disabled
+            // test that menu item is disabled
+            ctrl.deactivate();
+            t.eq(btn.disabled, true, "deactivating control disables button");
+            t.eq(btn.disabled, true, "deactivating control disables menu item");
+        }
+
+        function test_toggle(t) {
+            t.plan(12);
+
+            var menu, menuBtn, tb, ctrl;
+            var activateCnt, deactivateCnt, toggleHandlerCnt, checkHandlerCnt;
+            var action, btn, item;
+
+            var evt = {
+                preventDefault: function() {},
+                stopEvent: function() {},
+                button: 0
+            };
+
+            menu = new Ext.menu.Menu({
+                // with shadow set to its default value ("sides")
+                // IE throws "object: Invalid argument" exceptions
+                shadow: false
+            });
+            menuBtn = new Ext.Button({menu: menu});
+
+            tb = new Ext.Toolbar({
+                renderTo: "toolbar",
+                buttons: [menuBtn]
+            });
+
+            // the control
+            ctrl = new OpenLayers.Control({
+                activate: function() {
+                    activateCnt++;
+                    return OpenLayers.Control.prototype.activate.call(this, arguments);
+                },
+                deactivate: function() {
+                    deactivateCnt++;
+                    return OpenLayers.Control.prototype.deactivate.call(this, arguments);
+                }
+            });
+            ctrl.activate();
+
+            action = new GeoExt.Action({
+                control: ctrl,
+                enableToggle: true,
+                toggleHandler: function() {
+                    toggleHandlerCnt++;
+                },
+                checkHandler: function() {
+                    checkHandlerCnt++;
+                }
+            });
+            
+            // create button from action and it to toolbar
+            btn = new Ext.Button(action);
+            tb.add(btn);
+
+            // simulate click on button
+            // 2 tests
+            // test that the control gets activated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            toggleHandlerCnt = 0;
+            btn.onClick(evt);
+            t.eq(activateCnt, 1, "click on button activates control once");
+            t.eq(deactivateCnt, 0, "click on button does not deactivate control");
+            t.eq(toggleHandlerCnt, 1, "click on button calls toggle handler once");
+
+            // simulate click on button
+            // 2 tests
+            // test that the control gets deactivated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            toggleHandlerCnt = 0;
+            btn.onClick(evt);
+            t.eq(activateCnt, 0, "click again on button does not activate control");
+            t.eq(deactivateCnt, 1, "click again on button deactivates control once");
+            t.eq(toggleHandlerCnt, 1, "click again on button calls toggle handler once");
+
+            // create menu item from action and it to menu
+            item = new Ext.menu.CheckItem(action);
+            menu.add(item);
+
+            // simulate click on menu item
+            // 2 tests
+            // test that the control gets activated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            checkHandlerCnt = 0;
+            menuBtn.showMenu();
+            item.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(activateCnt, 1, "click on menu item activates control once");
+            t.eq(deactivateCnt, 0, "click on menu item does not deactivate control");
+            t.eq(checkHandlerCnt, 1, "click on menu item calls check handler once");
+
+            // simulate click on menu item
+            // 2 tests
+            // test that the control gets deactivated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            checkHandlerCnt = 0;
+            menuBtn.showMenu();
+            item.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(activateCnt, 0, "click again on menu item does not activate control");
+            t.eq(deactivateCnt, 1, "click again on menu item deactivates control once");
+            t.eq(checkHandlerCnt, 1, "click again on menu item calls check handler once");
+        }
+
+        function test_toggle_group(t) {
+            t.plan(8);
+
+            var menu, menuBtn, tb, ctrl1, ctrl2;
+
+            var activateCtrl1Cnt, deactivateCtrl1Cnt;
+            var activateCtrl2Cnt, deactivateCtrl2Cnt;
+            
+            var action1, action2, btn1, btn2, item1, item2;
+
+            var evt = {
+                preventDefault: function() {},
+                stopEvent: function() {},
+                button: 0
+            };
+
+            menu = new Ext.menu.Menu({
+                // with shadow set to its default value ("sides")
+                // IE throws "object: Invalid argument" exceptions
+                shadow: false
+            });
+            menuBtn = new Ext.Button({menu: menu});
+
+            tb = new Ext.Toolbar({
+                renderTo: "toolbar",
+                buttons: [menuBtn]
+            });
+
+            // the controls
+            ctrl1 = new OpenLayers.Control({
+                activate: function() {
+                    activateCtrl1Cnt++;
+                    return OpenLayers.Control.prototype.activate.call(this, arguments);
+                },
+                deactivate: function() {
+                    deactivateCtrl1Cnt++;
+                    return OpenLayers.Control.prototype.deactivate.call(this, arguments);
+                }
+            });
+            ctrl1.deactivate();
+            ctrl2 = new OpenLayers.Control({
+                activate: function() {
+                    activateCtrl2Cnt++;
+                    return OpenLayers.Control.prototype.activate.call(this, arguments);
+                },
+                deactivate: function() {
+                    deactivateCtrl2Cnt++;
+                    return OpenLayers.Control.prototype.deactivate.call(this, arguments);
+                }
+            });
+            ctrl2.activate();
+
+            // the actions
+            action1 = new GeoExt.Action({
+                control: ctrl1,
+                toggleGroup: "ctrl",
+                pressed: false
+            });
+            action2 = new GeoExt.Action({
+                control: ctrl2,
+                toggleGroup: "ctrl",
+                pressed: true
+            });
+            
+            // create buttons from actions and add them to toolbar
+            btn1 = new Ext.Button(action1);
+            tb.add(btn1);
+            btn2 = new Ext.Button(action2);
+            tb.add(btn2);
+
+            // simulate click on btn1
+            // 2 tests
+            // test that ctrl1 gets activated once
+            // test that ctrl2 gets deactivated once
+            activateCtrl1Cnt = 0;
+            deactivateCtrl2Cnt = 0;
+            btn1.onClick(evt);
+            t.eq(activateCtrl1Cnt, 1, "click on btn1 activates ctrl1 once");
+            t.eq(deactivateCtrl2Cnt, 1, "click on btn1 deactivates ctrl2 once");
+
+            // simulate click on btn2
+            // 2 tests
+            // test that ctrl1 gets deactivated once
+            // test that ctrl2 gets activated once
+            deactivateCtrl1Cnt = 0;
+            activateCtrl2Cnt = 0;
+            btn2.onClick(evt);
+            t.eq(deactivateCtrl1Cnt, 1, "click on btn2 deactivates ctrl1 once");
+            t.eq(activateCtrl2Cnt, 1, "click on btn2 activates ctrl2 once");
+
+            // create menu items from actions and them to menu
+            item1 = new Ext.menu.CheckItem(action1);
+            menu.add(item1);
+            item2 = new Ext.menu.CheckItem(action2);
+            menu.add(item2);
+
+            // simulate click on item1
+            // 2 tests
+            // test that ctrl1 gets activated once
+            // test that ctrl2 gets deactivated once
+            activateCtrl1Cnt = 0;
+            deactivateCtrl2Cnt = 0;
+            menuBtn.showMenu();
+            item1.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(activateCtrl1Cnt, 1, "click on item1 activates ctrl1 once");
+            t.eq(deactivateCtrl2Cnt, 1, "click on item1 deactivates ctrl2 once");
+
+            // simulate click on item2
+            // 2 tests
+            // test that ctrl1 gets deactivated once
+            // test that ctrl2 gets activated once
+            deactivateCtrl1Cnt = 0;
+            activateCtrl2Cnt = 0;
+            menuBtn.showMenu();
+            item2.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(deactivateCtrl1Cnt, 1, "click on item2 deactivates ctrl1 once");
+            t.eq(activateCtrl2Cnt, 1, "click on item2 activates ctrl2 once");
+        }
+        
+        function test_pressed(t) {
+            
+            t.plan(2);
+            
+            var map = new OpenLayers.Map();
+            var ctrl1 = new OpenLayers.Control();
+            var ctrl2 = new OpenLayers.Control();
+            
+            // confirm that control is activated with pressed true
+            var act1 = new GeoExt.Action({
+                control: ctrl1,
+                map: map,
+                pressed: true,
+                enableToggle: true,
+                toggleGroup: "group"
+            });
+            t.eq(ctrl1.active, true, "ctrl1 activated with pressed true");
+            
+            // confirm that a control is not activated with pressed false
+            var act2 = new GeoExt.Action({
+                control: ctrl2,
+                map: map,
+                pressed: false,
+                enableToggle: true,
+                toggleGroup: "group"
+            });
+            t.ok(!ctrl2.active, "ctrl2 not activated with pressed false");
+            
+            map.destroy();
+            
+        }
+        
+        function test_checked(t) {
+            
+            t.plan(2);
+            
+            var map = new OpenLayers.Map();
+            var ctrl1 = new OpenLayers.Control();
+            var ctrl2 = new OpenLayers.Control();
+            
+            // confirm that control is activated with checked true
+            var act1 = new GeoExt.Action({
+                control: ctrl1,
+                map: map,
+                checked: true,
+                enableToggle: true,
+                toggleGroup: "group"
+            });
+            t.eq(ctrl1.active, true, "ctrl1 activated with checked true");
+            
+            // confirm that a control is not activated with checked false
+            var act2 = new GeoExt.Action({
+                control: ctrl2,
+                map: map,
+                checked: false,
+                enableToggle: true,
+                toggleGroup: "group"
+            });
+            t.ok(!ctrl2.active, "ctrl2 not activated with checked false");
+            
+            map.destroy();
+            
+        }
+        
+    </script>
+  <body>
+    <div id="toolbar"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/FeatureRenderer.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/FeatureRenderer.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/FeatureRenderer.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/FeatureRenderer.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,95 @@
+<html><head>
+<script src="../../../../../openlayers/lib/OpenLayers.js"></script>
+<script src="../../../../../ext/adapter/ext/ext-base.js"></script>
+<script src="../../../../../ext/ext-all-debug.js"></script>
+<script src="../../../../lib/GeoExt.js"></script>
+<script>
+
+function test_initialize(t) {
+
+    t.plan(3);
+
+    var renderer = new GeoExt.FeatureRenderer();
+    
+    t.ok(renderer instanceof Ext.BoxComponent, "instanceof BoxComponent");
+    
+    var feature = renderer.feature;
+    t.ok(feature, "renderer feature set by default");
+    t.ok(feature.geometry instanceof OpenLayers.Geometry.Point, "point feature by default");
+    
+    renderer.destroy();
+    
+}
+
+function test_drawFeature(t) {
+    t.plan(4);
+    
+
+    var renderer = new GeoExt.FeatureRenderer({
+        renderTo: document.body
+    });
+    
+    t.ok(renderer.rendered, "rendered");
+    t.ok(renderer.renderer instanceof OpenLayers.Renderer, "has an OL renderer");
+    
+    var log = [];
+    renderer.renderer.drawFeature = function() {
+        log.push(arguments);
+        OpenLayers.Renderer.prototype.drawFeature.apply(this, arguments);
+    };
+    
+    renderer.update({
+        symbolizers: [{pointRadius: 3}, {pointRadius: 2, fillColor: "white"}]
+    });
+    t.eq(log.length, 2, "drawFeature called twice for two symbolizers");
+    t.ok(log[0][0] !== log[1][0], "drawFeature called with unique features");
+    
+    renderer.destroy();
+    
+}
+
+function test_update(t) {
+
+    t.plan(6);
+    
+    var renderer = new GeoExt.FeatureRenderer({
+        renderTo: document.body
+    });
+    
+    var count = 0;
+    renderer.drawFeature = function() {
+        ++count;
+        GeoExt.FeatureRenderer.prototype.drawFeature.apply(this, arguments);
+    };
+    
+    // update with new symbol type
+    renderer.update({symbolType: "Polygon"});
+    t.eq(count, 1, "drawFeature called when updating symbolType");
+    t.ok(renderer.feature.geometry instanceof OpenLayers.Geometry.Polygon, "polygon feature created when setting symbolType");
+    
+    // update with a new feature
+    count = 0;
+    var feature = new OpenLayers.Feature.Vector(
+        OpenLayers.Geometry.fromWKT("LINESTRING(0 0, 1 1)")
+    );
+    renderer.update({feature: feature});
+    t.eq(count, 1, "drawFeature called when updating feature");
+    t.ok(renderer.feature === feature, "feature set when updating");
+    
+    // update with new symbolizers
+    count = 0;
+    var symbolizers = [{
+        strokeWidth: 3, strokeColor: "red"
+    }, {
+        strokeWidth: 1, strokeColor: "blue"
+    }];
+    renderer.update({symbolizers: symbolizers});
+    t.eq(count, 1, "drawFeature called when updating symbolizers");
+    t.ok(renderer.symbolizers === symbolizers, "symbolizers set when updating");
+    
+    renderer.destroy();
+    
+}
+    
+</script>
+</head><body></body></html>
\ No newline at end of file

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LayerOpacitySlider.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/LayerOpacitySlider.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LayerOpacitySlider.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LayerOpacitySlider.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,289 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+
+        function test_constructor(t) {
+            t.plan(8);
+
+            var record, store, slider;
+            var layer = new OpenLayers.Layer("a");
+
+            record = new (GeoExt.data.LayerRecord.create())(
+                {layer: layer, title: layer.name}, layer.id
+            );
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: record
+            });
+            t.eq(slider.layer.id, record.id, "layer parameter is a GeoExt.data.LayerRecord");
+            slider.destroy();
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer
+            });
+            t.eq(layer.id, slider.layer.id, "layer parameter is a OpenLayers.Layer.WMS");
+            slider.destroy();
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer
+            });
+            t.eq(slider.value, 100,
+                 "ctor sets value to max value if layer opacity is " +
+                 "null and value isn't defined in config");
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer,
+                changeVisibility: true
+            });
+            t.eq(slider.changeVisibility, true,
+                 "ctor sets changeVisibility to true in instance");
+            slider.destroy();
+
+            layer.setOpacity(0.0);
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer,
+                changeVisibility: true
+            });
+            t.eq(layer.getVisibility(), false,
+                 "ctor makes layer invisible if layer opacity is 0");
+            layer.setVisibility(true);
+            slider.destroy();
+
+            layer.setOpacity(0.5);
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer,
+                changeVisibility: true
+            });
+            t.eq(layer.getVisibility(), true,
+                 "ctor does not change layer visibility if layer opacity is non 0");
+            slider.destroy();
+
+            layer.opacity = null;
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer,
+                value: 0,
+                changeVisibility: true
+            });
+            t.eq(layer.getVisibility(), false,
+                 "ctor makes layer invisible if layer opacity is " +
+                 "null and value is min value");
+            layer.setVisibility(true);
+            slider.destroy();
+
+            layer.opacity = null;
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer,
+                value: 0.5,
+                changeVisibility: true
+            });
+            t.eq(layer.getVisibility(), true,
+                 "ctor does not change layer visibility if layer " +
+                 "opacity is null and value is not min value");
+            layer.setVisibility(true);
+            slider.destroy();
+        }
+
+        function test_constructor_complementary(t) {
+            t.plan(5);
+
+            var layer1, layer2, record1, record2, slider;
+
+            var layer1 = new OpenLayers.Layer("1");
+            var layer2 = new OpenLayers.Layer("2");
+
+            record1 = new (GeoExt.data.LayerRecord.create())(
+                {layer: layer1, title: layer1.name}, layer1.id
+            );
+            record2 = new (GeoExt.data.LayerRecord.create())(
+                {layer: layer2, title: layer2.name}, layer2.id
+            );
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer1,
+                complementaryLayer: layer2
+            });
+            t.ok(slider.complementaryLayer == layer2,
+                 "ctor correctly sets complementary layer in " +
+                 "the instance [layer]");
+            slider.destroy();
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: record1,
+                complementaryLayer: record2
+            });
+            t.ok(slider.complementaryLayer == layer2,
+                 "ctor correctly sets complementary layer in " +
+                 "the instance [record]");
+            slider.destroy();
+
+            layer1.setOpacity(1);
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer1,
+                complementaryLayer: layer2
+            });
+            t.eq(layer2.getVisibility(), false,
+                 "ctor makes complementary layer invisible if the " +
+                 "main layer opacity is 1");
+            layer2.setVisibility(true);
+            slider.destroy();
+
+            layer1.setOpacity(0.5);
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer1,
+                complementaryLayer: layer2
+            });
+            t.eq(layer2.getVisibility(), true,
+                 "ctor does not change complementary layer visibility "+
+                 "if the main layer opacity is not 1");
+            slider.destroy();
+
+            layer1.opacity = null;
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: layer1,
+                complementaryLayer: layer2,
+                value: 100
+            });
+            t.eq(layer2.getVisibility(), false,
+                 "ctor makes complementary layer invisible if the " +
+                 "main layer opacity is null but the slider " +
+                 "value is set to max value in the config");
+            slider.destroy();
+        }
+
+        function test_initalOpacity(t) {
+            t.plan(3);
+
+            var slider = new GeoExt.LayerOpacitySlider({
+                layer: new OpenLayers.Layer('foo')
+            });
+            t.ok(slider.getValue() == 100,
+                 "set the value to 100 if the layer has no opacity");
+            slider.destroy();
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: new OpenLayers.Layer('foo', { opacity: 0 })
+            });
+            t.ok(slider.getValue() == 0,
+                 "initial layer's opacity sets the slider value");
+            slider.destroy();
+
+            slider = new GeoExt.LayerOpacitySlider({
+                layer: new OpenLayers.Layer('foo', { opacity: 0.42 })
+            });
+            t.ok(slider.getValue() == 42,
+                 "initial layer's opacity sets the slider value");
+            slider.destroy();
+        }
+
+        function test_aggressive(t) {
+            t.plan(2);
+
+            var slider1 = new GeoExt.LayerOpacitySlider({
+                renderTo: document.body,
+                layer: new OpenLayers.Layer('foo'),
+                aggressive: false
+            });
+            slider1.on({
+                changecomplete: function() {
+                    t.ok(true, "changecomplete triggered in non-aggressive mode");
+                }
+            });
+
+            var slider2 = new GeoExt.LayerOpacitySlider({
+                renderTo: document.body,
+                layer: new OpenLayers.Layer('foo'),
+                aggressive: true
+            });
+            slider2.on({
+                change: function() {
+                    t.ok(true, "change triggered in aggressive mode");
+                }
+            });
+            slider1.setValue(42, undefined, true);
+            slider2.setValue(42, undefined, true);
+
+            slider1.destroy();
+            slider2.destroy();
+        }
+
+        function test_visibility(t) {
+            t.plan(3);
+            
+            var slider, layer;
+
+            layer = new OpenLayers.Layer("a");
+
+            slider = new GeoExt.LayerOpacitySlider({
+                renderTo: document.body,
+                layer: layer,
+                changeVisibility: true,
+                changeVisibilityDelay: 0
+            });
+
+            slider.setValue(slider.minValue);
+            t.eq(layer.getVisibility(), false,
+                 "setting slider value to min value makes the " +
+                 "layer invisible");
+
+            slider.setValue(slider.minValue + 1);
+            t.eq(layer.getVisibility(), true,
+                 "setting slider value to some value different " +
+                 "than min value makes the layer visible again");
+
+            slider.setValue(slider.minValue + 2);
+            t.eq(layer.getVisibility(), true,
+                 "setting slider value to some other value different " +
+                 "than min value does not make the layer invisible");
+
+            slider.destroy();
+        }
+
+        function test_visibility_complementary_layer(t) {
+            t.plan(4);
+
+            var layer1, layer2, slider;
+
+            var layer1 = new OpenLayers.Layer("1");
+            var layer2 = new OpenLayers.Layer("2");
+
+            slider = new GeoExt.LayerOpacitySlider({
+                renderTo: document.body,
+                layer: layer1,
+                complementaryLayer: layer2,
+                changeVisibilityDelay: 0
+            });
+
+            slider.value = 99;
+            slider.setValue(slider.maxValue);
+            t.eq(layer2.getVisibility(), false,
+                 "setting slider value to max value makes " +
+                 "complementary layer invisible");
+
+            slider.setValue(slider.maxValue - 1);
+            t.eq(layer2.getVisibility(), true,
+                 "setting slider value to some value different " +
+                 "than max value makes the complementary layer " +
+                 "visible again");
+
+            slider.setValue(slider.maxValue - 2);
+            t.eq(layer2.getVisibility(), true,
+                 "setting slider value to some other value different " +
+                 "than max value does not make the complementary layer " +
+                 "invisible");
+
+            slider.setValue(slider.minValue);
+            t.eq(layer2.getVisibility(), true,
+                 "setting slider value to min value does not make " +
+                 "the complementary layer invisible");
+
+            slider.destroy();
+        }
+    </script>
+  <body>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendImage.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/LegendImage.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendImage.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendImage.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+
+        function test_constructor(t) {
+            t.plan(2);
+            
+            var legend;
+            
+            // create a legend with the default config
+            legend = new GeoExt.LegendImage();
+            t.eq(legend.defaultImgSrc, Ext.BLANK_IMAGE_URL, "defaultImgSrc defaults to Ext.BLANK_IMAGE_URL");
+            legend.destroy();
+            
+            // create a legend with a custom defaultImgSrc
+            legend = new GeoExt.LegendImage({defaultImgSrc: "foo"});
+            t.eq(legend.defaultImgSrc, "foo", "defaultImgSrc can be set in config");
+            legend.destroy();
+            
+        }
+        
+        function test_onImageLoadError(t) {
+            t.plan(2);
+            
+            var legend, calls = 0;
+
+            // create a legend with a bogus image url (one call to error handler)
+            legend = new GeoExt.LegendImage({
+                url: "bogus",
+                defaultImgSrc: "also-bogus",
+                renderTo: "legend",
+                onImageLoadError: function() {
+                    ++calls;
+                    GeoExt.LegendImage.prototype.onImageLoadError.apply(this, arguments);
+                }
+            });
+            t.delay_call(0.5, function() {
+                t.eq(calls, 1, "onImageLoadError called once for bogus image src");
+                var el = legend.getEl();
+                t.eq(el && el.dom.src.split("/").pop(), "also-bogus", "defaultImgSrc set as image src");
+                legend.destroy();
+            });            
+            
+        }
+
+
+    </script>
+  <body>
+    <div id="legend"></div>
+  </body>
+</html>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendPanel.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendPanel.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendPanel.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,155 +1,288 @@
-<!DOCTYPE html>
-<html debug="true">
-  <head>
-    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
-    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
-    <script type="text/javascript" src="../../../../../ext/ext-all.js"></script>
-    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
-
-    <script type="text/javascript">
-
-        function createMap() {
-            var map = new OpenLayers.Map({allOverlays: true});
-            var layer = new OpenLayers.Layer.WMS("test", '/ows', {layers: 'a'});
-            map.addLayer(layer);
-            return map;
-        }
-
-        function loadMapPanel() {
-            var map = createMap();
-
-            mapPanel = new GeoExt.MapPanel({
-                // panel options
-                id: "map-panel",
-                title: "GeoExt MapPanel",
-                renderTo: "mappanel",
-                height: 400,
-                width: 600,
-                // map panel-specific options
-                map: map,
-                center: new OpenLayers.LonLat(5, 45),
-                zoom: 4
-            });
-
-            return mapPanel;
-        }
-
-        function test_legendurl(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            var newUrl = "http://www.geoext.org//trac/geoext/chrome/site/img/GeoExt.png";
-            mapPanel.layers.getAt(0).set("legendURL", newUrl);
-
-            var item = lp.getComponent(mapPanel.map.layers[0].id);
-            var url = item.items.items[1].items.items[0].getEl().dom.src;
-            t.eq(url, newUrl, "Update the image with the provided legendURL");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_togglevisibility(t) {
-            t.plan(2);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            mapPanel.map.layers[0].setVisibility(false);
-            var id = mapPanel.layers.getAt(0).get('layer').id;
-            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
-
-            mapPanel.map.layers[0].setVisibility(true);
-            t.eq(lp.getComponent(id).hidden, false, "Layer has been made visible again in legend");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_hide(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            mapPanel.layers.getAt(0).set("hideInLegend", true);
-            var id = mapPanel.layers.getAt(0).get('layer').id;
-            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_dynamic(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                dynamic: false,
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            var layer;
-            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
-            mapPanel.map.addLayer(layer);
-
-            t.eq(lp.items.length, 1, "If dynamic is false, do not add or remove layers from legend");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_wms(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            var item = lp.getComponent(mapPanel.map.layers[0].id);
-            var url = item.items.items[1].items.items[0].url;
-            var expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&LAYER=a";
-            t.eq(url, expectedUrl, "GetLegendGraphic url is generated correctly");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_addremove(t) {
-            t.plan(4);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-            t.eq(lp.items.length, 1, "Same number of layers in legend panel and in map");
-
-            var item = lp.getComponent(mapPanel.map.layers[0].id);
-
-            var layer;
-            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
-            mapPanel.map.addLayer(layer);
-
-            t.eq(lp.items.length, 2, "New WMS layer has been added");
-
-            layer = new OpenLayers.Layer.WMS("test3", '/ows', {layers: 'c'}, {visibility: false});
-            mapPanel.map.addLayer(layer);
-
-            t.eq(lp.items.length, 3, "A non visible WMS layer will be added but will be invisible");
-
-            mapPanel.map.removeLayer(mapPanel.map.layers[0]);
-            t.eq(lp.items.length, 2, "Removing the WMS layer really removes the legend from the panel");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-    </script>
-  <body>
-    <div id="legendpanel"></div>
-    <div id="mappanel"></div>
-  </body>
-</html>
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+
+        function loadMapPanel() {
+            mapPanel = new GeoExt.MapPanel({
+                // panel options
+                id: "map-panel",
+                title: "GeoExt MapPanel",
+                renderTo: "mappanel",
+                height: 400,
+                width: 600,
+                // map panel-specific options
+                layers: [
+                    new OpenLayers.Layer.WMS("test", '/ows', {layers: 'a'})
+                ],
+                center: new OpenLayers.LonLat(5, 45),
+                zoom: 4
+            });
+
+            return mapPanel;
+        }
+
+        function test_legendurl(t) {
+            t.plan(3);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var newUrl = "http://trac.geoext.org/chrome/site/img/GeoExt.png";
+            mapPanel.layers.getAt(0).set("legendURL", newUrl);
+
+            var item = lp.getComponent(mapPanel.map.layers[0].id);
+            var url = item.items.get(1).items.get(0).getEl().dom.src;
+            t.eq(url, newUrl, "Update the image with the provided legendURL");
+
+            var vectorLayer = new OpenLayers.Layer.Vector("vector layer");
+            mapPanel.map.addLayer(vectorLayer);
+
+            vectorLayer.setVisibility(false);
+
+            t.eq(lp.items.length, 1, "Currently there are no legends for non WMS layers");
+
+            var wms = new OpenLayers.Layer.WMS("testArray", '/ows', {layers: ['a', 'b', 'c']});
+            mapPanel.map.addLayer(wms);
+
+            t.eq(lp.items.length, 2, "The legend panel can deal with WMS layers which have a LAYERS params which is an array");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_togglevisibility(t) {
+            t.plan(2);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            mapPanel.map.layers[0].setVisibility(false);
+            var id = mapPanel.layers.getAt(0).get('layer').id;
+            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
+
+            mapPanel.map.layers[0].setVisibility(true);
+            t.eq(lp.getComponent(id).hidden, false, "Layer has been made visible again in legend");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_hide(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            mapPanel.layers.getAt(0).set("hideInLegend", true);
+            var id = mapPanel.layers.getAt(0).get('layer').id;
+            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_dynamic(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                dynamic: false,
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var layer;
+            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
+            mapPanel.map.addLayer(layer);
+
+            t.eq(lp.items.length, 1, "If dynamic is false, do not add or remove layers from legend");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_wms(t) {
+            t.plan(3);
+            var mapPanel = loadMapPanel();
+            var LegendWMS = GeoExt.LegendWMS;
+            GeoExt.LegendWMS = function(config) {
+                t.ok(config.record == mapPanel.layers.getAt(0), "layer record passed correctly");
+                t.ok(config.layer == mapPanel.map.layers[0], "layer passed correctly");
+                t.eq(config.foo, "bar", "legendOptions passed correctly");
+                return new Ext.Panel();
+            }
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel',
+                legendOptions: {foo: "bar"}
+            });
+            lp.render();
+
+            lp.destroy();
+            mapPanel.destroy();
+            GeoExt.LegendWMS = LegendWMS;
+        }
+
+        function test_addremove(t) {
+            t.plan(4);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+            t.eq(lp.items.length, 1, "Same number of layers in legend panel and in map");
+
+            var item = lp.getComponent(mapPanel.map.layers[0].id);
+
+            var layer;
+            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
+            mapPanel.map.addLayer(layer);
+
+            t.eq(lp.items.length, 2, "New WMS layer has been added");
+
+            layer = new OpenLayers.Layer.WMS("test3", '/ows', {layers: 'c'}, {visibility: false});
+            mapPanel.map.addLayer(layer);
+
+            t.eq(lp.items.length, 3, "A non visible WMS layer will be added but will be invisible");
+
+            mapPanel.map.removeLayer(mapPanel.map.layers[0]);
+            t.eq(lp.items.length, 2, "Removing the WMS layer really removes the legend from the panel");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_clear(t) {
+            t.plan(2);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+            t.eq(lp.items.length, 1, "Same number of layers in legend panel and in layerstore");
+
+            mapPanel.layers.removeAll();
+            t.eq(lp.items.length, 0, "When layerstore is cleared, legend panel is cleared");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_changelayername(t) {
+            t.plan(3);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var layer = mapPanel.map.layers[0];
+            var cmp = lp.getComponent(layer.id);
+            t.eq(cmp.items.get(0).text, 'test', "Layer name is test before change");
+
+            layer.setName("My new name");
+
+            t.eq(cmp.items.get(0).text, "My new name", "Layer name was changed correctly to 'My new name'");
+
+            lp.destroy();
+
+            var lp  = new GeoExt.LegendPanel({
+                showTitle: false,
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            layer.setName("My new new name");
+            var cmp = lp.getComponent(layer.id);
+            t.eq(cmp.items.get(0).text, "", "When showTitle is false, there is no label for a layer and it is not changed");
+
+            lp.destroy();
+
+            mapPanel.destroy();
+        }
+
+        function test_filter(t) {
+            t.plan(2);
+
+            var map = new OpenLayers.Map();
+            var layer = new OpenLayers.Layer.WMS("test", '/ows', {layers: 'a'}, {isBaseLayer: true});
+            map.addLayer(layer);
+            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b'}, {isBaseLayer: false});
+            map.addLayer(layer);
+
+            var mapPanel = new GeoExt.MapPanel({
+                map: map,
+                id: "map-panel",
+                title: "GeoExt MapPanel",
+                renderTo: "mappanel",
+                height: 400,
+                width: 600
+            });
+
+            var lp = new GeoExt.LegendPanel({
+                filter: function(record) { return !record.get("layer").isBaseLayer; },
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            t.eq(lp.items.length, 1, "Using the filter function only the non base layers will be shown in the legend");
+
+            lp.destroy();
+
+            var lp = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            t.eq(lp.items.length, 2, "With no filter both layers are drawn");
+
+            lp.destroy();
+            map.destroy();
+            mapPanel.destroy();
+
+        }
+
+        function test_changelayerparams(t) {
+            t.plan(2);
+
+            var mapPanel = loadMapPanel();
+            var lp = new GeoExt.LegendPanel({
+                renderTo: "legendpanel"
+            });
+            var layer = mapPanel.map.layers[0];
+            var cmp = lp.getComponent(layer.id);
+
+            layer.mergeNewParams({
+                layers: "a,b"
+            });
+            t.eq(cmp.items.get(1).items.getCount(), 2,
+                 "mergeNewParams caused addition of a legend image");
+
+            layer.mergeNewParams({
+                layers: "b"
+            });
+            t.eq(cmp.items.get(1).items.getCount(), 1,
+                 "mergeNewParams caused removal of a legend image");
+        }
+
+        function test_scaledependency(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            mapPanel.map.layers[0].inRange = false;
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var id = mapPanel.layers.getAt(0).get('layer').id;
+            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend because it is not in scale");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+    </script>
+  <body>
+    <div id="legendpanel"></div>
+    <div id="mappanel"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendWMS.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/LegendWMS.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendWMS.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/LegendWMS.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+
+        function loadMapPanel() {
+            var mapPanel = new GeoExt.MapPanel({
+                // panel options
+                id: "map-panel",
+                title: "GeoExt MapPanel",
+                renderTo: "mappanel",
+                height: 400,
+                width: 600,
+                // map panel-specific options
+                layers: [
+                    new OpenLayers.Layer.WMS("test", '/ows', {layers: 'a'})
+                ],
+                center: new OpenLayers.LonLat(5, 45),
+                zoom: 4
+            });
+
+            return mapPanel;
+        }
+
+        function test_legendurl(t) {
+            t.plan(5);
+
+            var l, url, expectedUrl;
+            var mapPanel = loadMapPanel();
+            l = new GeoExt.LegendWMS({
+                renderTo: 'legendwms',
+                record: mapPanel.layers.getAt(0)
+            });
+            l.render();
+
+            url = l.items.get(0).url;
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&LAYER=a";
+            t.eq(url, expectedUrl, "GetLegendGraphic url is generated correctly");
+            l.destroy()
+            
+            mapPanel.map.layers[0].params.STYLES = "bar";
+            mapPanel.layers.getAt(0).set("styles", [{
+                name: "bar",
+                legend: {href: "foo"}
+            }]);
+            l = new GeoExt.LegendWMS({
+                renderTo: 'legendwms',
+                record: mapPanel.layers.getAt(0)
+            });
+            l.render();
+            url = l.items.get(0).url;
+            t.eq(url, "foo", "legend url from styles field of layer record used correctly.");
+            l.destroy();
+            mapPanel.map.layers[0].params.STYLES = "";
+            
+            l = new GeoExt.LegendWMS({
+                renderTo: 'legendwms',
+                record: mapPanel.layers.getAt(0),
+                defaultStyleIsFirst: true
+            });
+            l.render();
+            url = l.items.get(0).url;
+            t.eq(url, "foo", "legend url from styles field of layer record used correctly when defaultStyleIsFirst set to true and layer has no STYLES param.");
+            l.destroy();
+
+            mapPanel.map.layers[0].params.SLD = "sld";
+            l = new GeoExt.LegendWMS({
+                renderTo: 'legendwms',
+                record: mapPanel.layers.getAt(0),
+                defaultStyleIsFirst: true
+            });
+            l.render();
+            url = l.items.get(0).url;
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&SLD=sld&LAYER=a";
+            t.eq(url, expectedUrl, "GetLegendGraphic url is generated when layer has SLD set");
+            l.destroy();
+            delete mapPanel.map.layers[0].params.SLD;
+
+            mapPanel.map.layers[0].params.SLD_BODY = "sld_body";
+            l = new GeoExt.LegendWMS({
+                renderTo: 'legendwms',
+                record: mapPanel.layers.getAt(0),
+                defaultStyleIsFirst: true
+            });
+            l.render();
+            url = l.items.get(0).url;
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&SLD_BODY=sld_body&LAYER=a";
+            t.eq(url, expectedUrl, "GetLegendGraphic url is generated when layer has SLD_BODY set");
+            l.destroy();
+            delete mapPanel.map.layers[0].params.SLD_BODY;
+
+            mapPanel.destroy();
+        }
+
+        function test_updateLegend(t) {
+            t.plan(8);
+
+            // set up
+
+            var mapPanel = loadMapPanel();
+            var layerRecord = mapPanel.layers.getAt(0);
+
+            var url, expectedUrl, cmp;
+
+            var l = new GeoExt.LegendWMS({
+                renderTo: 'legendwms',
+                record: layerRecord
+            });
+
+            // test
+
+            // #1
+            layerRecord.get("layer").mergeNewParams({
+                layers: "b"
+            });
+            l.updateLegend();
+            t.ok(!l.getComponent("a"),
+                 "updateLegend removes old components");
+            t.ok(l.getComponent("b"),
+                 "updateLegend adds new components");
+
+            // #2
+            layerRecord.get("layer").mergeNewParams({
+                layers: "b,c",
+                foo: "bar"
+            });
+            l.updateLegend();
+            t.ok(l.getComponent("b"),
+                 "updateLegend does not remove components to be updated");
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&FOO=bar&LAYER=b";
+            t.eq(l.getComponent("b").url, expectedUrl,
+                 "updateLegend updates component URL");
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&FOO=bar&LAYER=c";
+            t.eq(l.getComponent("c").url, expectedUrl,
+                 "updateLegend sets correct URL in new component");
+
+            // #3
+            layerRecord.get("layer").mergeNewParams({
+                layers: "c",
+                styles: "style1"
+            });
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&FOO=bar&LAYER=c&STYLE=style1";
+            l.updateLegend();
+            t.eq(l.getComponent("c").url, expectedUrl,
+                 "updateLegend sets correct STYLE params in URL");
+
+            // #4
+            layerRecord.set("styles", [{
+                name: "style1",
+                legend: {
+                    href: "http://url-to-legend.org/"
+                }
+            }]);
+            l.updateLegend();
+            expectedUrl = "http://url-to-legend.org/";
+            t.eq(l.getComponent("c").url, expectedUrl,
+                 "updateLegend uses the legend href from the styles field");
+
+            // #5
+            layerRecord.get("layer").mergeNewParams({
+                layers: "c",
+                styles: null,
+                sld: "http://url-to-sld.org/"
+            });
+            l.updateLegend();
+            expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&FOO=bar&SLD=http%3A%2F%2Furl-to-sld.org%2F&LAYER=c";
+            t.eq(l.getComponent("c").url, expectedUrl,
+                 "updateLegend does not use the legend href from the " +
+                 "styles field if SLD is set in the layer params");
+
+            // tear down
+
+            l.destroy()
+        }
+
+
+    </script>
+  <body>
+    <div id="legendwms"></div>
+    <div id="mappanel"></div>
+  </body>
+</html>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/MapPanel.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/MapPanel.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/MapPanel.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -69,8 +69,6 @@
             panel = new GeoExt.MapPanel({
             });
             t.eq(panel.map.allOverlays, true, "allOverlays set to true if no map is provided to panel");
-            // since we created the map, we destroy it
-            map.destroy();
             panel.destroy();
             
             panel = new GeoExt.MapPanel({
@@ -79,12 +77,28 @@
                 }
             });
             t.eq(panel.map.allOverlays, true, "allOverlays set to true if map config is provided to panel");
-            // since we created the map, we destroy it
-            map.destroy();
             panel.destroy();
             
         }
 
+        function test_zoom(t) {
+            
+            t.plan(1);
+            
+            var panel = new GeoExt.MapPanel({
+                title: "GeoExt MapPanel",
+                renderTo: "mappanel",
+                height: 400,
+                width: 600,
+                layers: [new OpenLayers.Layer()],
+                zoom: 4
+            });
+            
+            t.eq(panel.map.zoom, 4, "zoom correctly set");
+            
+            panel.destroy();
+        }
+
         function test_extent(t) {
             
             t.plan(3);
@@ -269,6 +283,7 @@
                 }]
             });
 
+            map.destroy();
             panel.destroy();
         }
 

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Popup.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Popup.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/Popup.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -11,7 +11,7 @@
 
         function setupContext() {        
 
-            var map = new OpenLayers.Map();
+            var map = new OpenLayers.Map({panMethod: null}); // avoid tween panning for tests
             var layer = new OpenLayers.Layer("test", {isBaseLayer: true});
             map.addLayer(layer);
 
@@ -29,9 +29,13 @@
             });
 
             var feature = new OpenLayers.Feature.Vector(
-                new OpenLayers.Geometry.Point(100,50),
+                new OpenLayers.Geometry.Point(5,45),
                 {name: "My Feature"}
             );
+            feature.layer = {
+                map: map,
+                removeFeatures: function() {}
+            };
 
             return {
                 feature: feature,
@@ -63,53 +67,70 @@
 
             var pop = popup(context.feature);
 
-            context.mapPanel.add(pop);
+            pop.show();
 
-            t.ok(context.mapPanel.el.child("div." + pop.popupCls),"Map panel contains popup");
-            
+            t.ok(Ext.getBody().child("div." + pop.popupCls),"viewport contains popup");
+
             tearDown(context);
         }
 
         function test_anchorPopup(t) {
-            t.plan(4);
+            t.plan(5);
 
             var context = setupContext();
 
             var pop = popup(context.feature);
 
-            context.mapPanel.add(pop);
-
+            // show the popup and move the map to ensure popup is actually visible
+            pop.show();
+            context.map.setCenter(new OpenLayers.LonLat(5, 45));
+            
+            var moves = 0;
             pop.on({
-                'move' : function(c,x,y){
-                    t.ok(true,"Move event fired on " + action); //should happen twice, on call to position()
+                move: function() {
+                    ++moves;
                 },
                 scope : this
             });
 
-            t.ok(pop.getAnchorElement(), "Popup has anchor element");
+            t.ok(pop.anc, "Popup has anchor element");
 
-            var action = "map move";
-            context.map.events.triggerEvent("move");
-            
-            action = "popup collapse";
+            // move the map and confirm that popup moves
+            context.map.setCenter(new OpenLayers.LonLat(6, 45));
+            t.eq(moves, 1, "anchored popup moves once on map.setCenter");
+            moves = 0;
+
+            // anchored popup needs to reposition on collapse, resize and
+            // expand to keep the anchor point on the feature
+
+            // collapse popup and and confirm that it moves
             pop.collapse();
+            t.eq(moves, 1, "anchored popup moves once on collapse");
+            moves = 0;
 
-            action = "popup expand"
+            // expand popup and confirm that it moves
             pop.expand();
+            t.eq(moves, 1, "anchored popup moves once on expand");
+            moves = 0;
             
+            // resize popup and confirm that it moves
+            pop.setSize(100, 100);
+            t.eq(moves, 1, "anchored popup moves once on resize");
+            moves = 0;
+
             tearDown(context);
         }
 
 
         function test_unanchorPopup(t) {
-            t.plan(6);
+            t.plan(4);
 
             var context = setupContext();
 
             var pop = popup(context.feature, context.mapPanel);
 
-            context.mapPanel.add(pop);
-        
+            pop.show();
+
             pop.collapse();
 
             var origPos = pop.getPosition();
@@ -118,10 +139,8 @@
 
             var newPos = pop.getPosition();
 
-            t.ok(!pop.getAnchorElement(),"Anchor element removed");
+            t.ok(!pop.anc,"Anchor element removed");
             t.ok(!this.collapsed, "Preserved collapsed state");
-            t.ok(!context.mapPanel.el.child("div." + pop.popupCls),"Map panel does not contain popup");
-            t.ok(Ext.getBody().child("div." + pop.popupCls),"Document body contains popup element");
             t.eq(origPos[0],newPos[0],"Popup remains in same position (X)");
             t.eq(origPos[1],newPos[1],"Popup remains in same position (Y)");
 
@@ -151,7 +170,7 @@
 
             var context = setupContext();
             var pop = popup(context.feature);
-            context.mapPanel.add(pop);
+            pop.show();
             
             var called = false;
             pop.on({

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/ZoomSlider.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/ZoomSlider.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/ZoomSlider.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/ZoomSlider.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+       
+        function test_zoomslider(t) {
+            t.plan(7);
+            
+            var map = new OpenLayers.Map({
+                div: "map",
+                allOverlays: true
+            });
+            map.addLayer(new OpenLayers.Layer());
+            map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+            
+            var slider = new GeoExt.ZoomSlider({
+                map: map,
+                renderTo: document.body
+            });
+            
+            // test range of values
+            t.eq(slider.minValue, 0, "slider min is 0");
+            t.eq(slider.maxValue, 15, "slider can go to 15");
+            
+            // test initial value
+            t.eq(slider.getValue(), 2, "slider has correct value after setCenter");
+            
+            // zoom in and test that value is updated
+            map.zoomIn();
+            t.eq(slider.getValue(), 3, "slider has correct value after zoomIn");
+            
+            // test that zoomTo updates slider value
+            map.zoomTo(0);
+            t.eq(slider.getValue(), 0, "slider has correct value after zoomTo");
+            
+            // test that slider can be destroyed
+            try {
+                slider.destroy();
+                t.ok(true, "slider.destroy does not cause problems");
+            } catch(err) {
+                t.fail("slider.destroy causes problems: " + err);
+            }
+            
+            // test that map can be zoomed without trouble after slider destroy
+            try {
+                map.zoomIn();
+                t.ok(true, "map.zoomIn does not cause problems after slider.destroy");
+            } catch(err) {
+                t.fail("map.zoomIn causes problems after slider.destroy: " + err);
+            }
+            
+            map.destroy();
+
+        }
+        
+        function test_zoomslider_aggressive(t) {
+            t.plan(2);
+            var slider1 = new GeoExt.ZoomSlider({
+                renderTo: document.body
+            });
+            slider1.on({
+                "changecomplete": function() {
+                    t.ok(true, "changecomplete triggered in non-aggressive mode");
+                }
+            });
+            var slider2 = new GeoExt.ZoomSlider({
+                renderTo: document.body,
+                aggressive: true
+            });
+            slider2.on({
+                "change": function() {
+                    t.ok(true, "change triggered in aggressive mode");
+                }
+            });
+            
+            slider1.setValue(slider1.maxValue, undefined, true);
+            slider2.setValue(slider2.maxValue, undefined, true);
+            
+            slider1.destroy();
+            slider2.destroy();
+        }
+
+    </script>
+  </head>
+  <body>
+    <div id="map" style="width: 512px; height: 256px;"></div>
+  </body>
+</html>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/form/SearchAction.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/form/SearchAction.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/form/SearchAction.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -63,8 +63,69 @@
         /*
          * Test
          */
-       action.run();
+
+        action.run();
+
+        /*
+         * Tear down
+         */
+
+        form.destroy();
     }
+
+    function test_abort(t) {
+        t.plan(2);
+
+        /*
+         * Set up
+         */
+
+        var field, form, action, protocol, log = {};
+
+        var field = new Ext.form.TextField({
+            name: "foo__eq",
+            value: "bar"
+        });
+
+        form = new Ext.form.FormPanel({
+            renderTo: "form",
+            items: [field]
+        });
+
+        protocol = new OpenLayers.Protocol({
+            read: function(options) {
+                return "something";
+            },
+            abort: function() {
+                log.abort++;
+            }
+        });
+
+        action = new GeoExt.form.SearchAction(form.getForm(), {
+            protocol: protocol,
+            clientValidation: false,
+            abortPrevious: true
+        });
+
+        /*
+         * Test
+         */
+
+        log.abort = 0;
+        action.run();
+        action.run();
+        t.ok(log.abort == 1, "protocol abort called once");
+
+        log.abort = 0;
+        action.options.abortPrevious = false;
+        action.run();
+        t.ok(log.abort == 0, "protocol abort not called");
+
+        /*
+         * Tear down
+         */
+        form.destroy();
+    }
     </script>
   <body>
     <div id="form"></div>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/grid)

Deleted: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html
===================================================================
--- core/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,376 +0,0 @@
-<!DOCTYPE html>
-<html debug="true">
-  <head>
-    <script type="text/javascript" src="../../../../../../openlayers/lib/OpenLayers.js"></script>
-    <script type="text/javascript" src="../../../../../../ext/adapter/ext/ext-base.js"></script>
-    <script type="text/javascript" src="../../../../../../ext/ext-all-debug.js"></script>
-    <script type="text/javascript" src="../../../../../lib/GeoExt.js"></script>
-
-    <script type="text/javascript">
-    
-        function test_init(t) {
-            t.plan(11);
-
-            /*
-             * Set up
-             */
-            var map, layer, layers, selectControl, store, sm;
-            
-            map = new OpenLayers.Map();
-            layer = new OpenLayers.Layer.Vector("vector");
-            layers = [layer];
-            map.addLayers(layers);
-
-            store = new GeoExt.data.FeatureStore({layer: layer});
-
-            /*
-             * Test
-             */
-
-            // create a feature selection model
-            // 1 test
-            sm = new GeoExt.grid.FeatureSelectionModel();
-            t.ok(sm instanceof Ext.grid.RowSelectionModel,
-                 "a feature selection model is a row selection model");
-
-            // create a feature selection model and give it a
-            // select feature control
-            // 2 tests
-            selectControl = new OpenLayers.Control.SelectFeature(layer);
-            sm = new GeoExt.grid.FeatureSelectionModel({
-                selectControl: selectControl, multiple: false
-            });
-            t.ok(sm.selectControl == selectControl,
-                 "ctor sets the passed select feature control in the instance");
-            t.eq(sm.singleSelect, true,
-                 "ctor sets singleSelect to true in the instance");
-
-            // create a feature selection model with singleSelect true and give
-            // it a select feature control
-            // 1 test
-            selectControl = new OpenLayers.Control.SelectFeature(layer);
-            sm = new GeoExt.grid.FeatureSelectionModel({
-                singleSelect: true,
-                selectControl: selectControl
-            });
-            t.eq(sm.selectControl.multiple, false,
-                 "ctor configures the select feature control with multiple false");
-
-            // create a feature selection model and give it a layer and
-            // a select control config
-            // 3 tests
-            sm = new GeoExt.grid.FeatureSelectionModel({
-                layer: layer,
-                selectControl: {
-                    hover: true
-                }
-            });
-            t.ok(sm.selectControl instanceof OpenLayers.Control.SelectFeature,
-                 "ctor creates a select feature control when passed a layer");
-            t.ok(sm.selectControl.layer == layer,
-                 "ctor configures the select feature control with the passed layer");
-            t.eq(sm.selectControl.hover, true,
-                 "ctor configures the select feature control with the passed config");
-
-            // create a feature selection model and create a grid with it
-            // 3 tests
-            sm = new GeoExt.grid.FeatureSelectionModel({
-                selectControl: {
-                    hover: true
-                }
-            });
-            var grid = new Ext.grid.GridPanel({
-                renderTo: "grid",
-                store: store,
-                columns: [{
-                    header: "name"
-                }],
-                sm: sm,
-                deferRowRender: false
-            });
-            t.ok(sm.selectControl instanceof OpenLayers.Control.SelectFeature,
-                 "init creates a select feature control ");
-            t.ok(sm.selectControl.layer == layer,
-                 "init configures the select feature control with the store layer");
-            t.eq(sm.selectControl.hover, true,
-                 "init configures the select feature control with the passed config");
-            grid.destroy();
-
-            // 1 test
-            var CheckboxSelectionModel = Ext.extend(
-                Ext.grid.CheckboxSelectionModel,
-                new GeoExt.grid.FeatureSelectionModelMixin
-            );
-            sm = new CheckboxSelectionModel();
-            t.ok(sm instanceof Ext.grid.CheckboxSelectionModel,
-                 "instance is a checkbox selection model");
-        }
-
-        function test_row_selection(t) {
-            t.plan(7);
-
-            /*
-             * Set up
-             */
-
-            var map, layer, features, store, sm, grid, e;
-
-            map = new OpenLayers.Map('map');
-
-            layer = new OpenLayers.Layer.Vector("vector");
-            map.addLayer(layer);
-
-            features = [
-                new OpenLayers.Feature.Vector(null,
-                    {foo: "foo1", bar: "bar1"}
-                ),
-                new OpenLayers.Feature.Vector(null,
-                    {foo: "foo2", bar: "bar2"}
-                )
-            ];
-            
-            store = new GeoExt.data.FeatureStore({
-                layer: layer,
-                data: features
-            });
-
-            sm = new GeoExt.grid.FeatureSelectionModel();
-
-            grid = new Ext.grid.GridPanel({
-                renderTo: "grid",
-                store: store,
-                columns: [{
-                    dataIndex: "foo"
-                }, {
-                    dataIndex: "bar"
-                }],
-                sm: sm,
-                deferRowRender: false
-            });
-
-            /*
-             * Test
-             */
-
-            // simulate a mousedown on the first row
-            // test that the first feature is selected in the layer
-            e = {
-                button: 0,
-                shiftKey: false,
-                ctrlKey: false
-            };
-            grid.fireEvent("rowmousedown", grid, 0, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[0]) > -1,
-                 "click on row 0 selects feature 0");
-                 
-            // simulate a mousedown on the first row
-            // test that the first feature is deselected in the layer
-            e = {
-                button: 0,
-                shiftKey: false,
-                ctrlKey: true
-            };
-            grid.fireEvent("rowmousedown", grid, 0, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[0]) < 0,
-                 "click on row 0 deselects feature 0");
-
-            // simulate a mousedown on the second row
-            grid.fireEvent("rowmousedown", grid, 1, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[1]) > -1,
-                 "click on row 1 selects feature 1");
-
-            sm.clearSelections();
-
-            // select feature 0
-            // test that the first row is selected
-            sm.selectControl.select(features[0]);
-            t.ok(sm.isSelected(0),
-                 "selecting feature 0 selects row 0");
-
-            // select feature 1
-            // test that the second row is selected
-            sm.selectControl.select(features[1]);
-            t.ok(sm.isSelected(1),
-                 "selecting feature 1 selects row 1");
-
-            // unselect feature 0
-            // test that the first row is selected
-            sm.selectControl.unselect(features[0]);
-            t.ok(!sm.isSelected(0),
-                 "unselecting feature 0 unselects row 0");
-
-            // unselect feature 1
-            // test that the second row is unselected
-            sm.selectControl.unselect(features[1]);
-            t.ok(!sm.isSelected(1),
-                 "unselecting feature 1 unselects row 1");
-            
-            /*
-             * Tear down
-             */
-            grid.destroy();
-        }
-        
-        
-        function test_bind_unbind(t) {
-            t.plan(9);
-
-            /*
-             * Set up
-             */
-
-            var map, layer, selectControl, features, store, sm, grid, e;
-
-            map = new OpenLayers.Map('map');
-
-            layer = new OpenLayers.Layer.Vector("vector");
-            map.addLayer(layer);
-
-            selectControl = new OpenLayers.Control.SelectFeature(layer);
-            map.addControl(selectControl);
-
-            features = [
-                new OpenLayers.Feature.Vector(null,
-                    {foo: "foo1", bar: "bar1"}
-                ),
-                new OpenLayers.Feature.Vector(null,
-                    {foo: "foo2", bar: "bar2"}
-                )
-            ];
-            
-            store = new GeoExt.data.FeatureStore({
-                layer: layer,
-                data: features
-            });
-
-            sm = new GeoExt.grid.FeatureSelectionModel({
-                layerFromStore: false
-            });
-
-            grid = new Ext.grid.GridPanel({
-                renderTo: "grid",
-                store: store,
-                columns: [{
-                    dataIndex: "foo"
-                }, {
-                    dataIndex: "bar"
-                }],
-                sm: sm,
-                deferRowRender: false
-            });
- 
-            /*
-             * Test
-             */
-
-            // simulate a mousedown on the first row
-            // test that the first feature is not selected in the layer
-            e = {
-                button: 0,
-                shiftKey: false,
-                ctrlKey: false
-            };
-            grid.fireEvent("rowmousedown", grid, 0, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[0]) < 0,
-                 "click on row 0 does not select feature 0");
-            sm.clearSelections();
-            
-            // select feature 0
-            // test that the first row is not selected
-            selectControl.select(features[0]);
-            t.ok(!sm.isSelected(0),
-                 "selecting feature 0 does not select row 0");
-            selectControl.unselect(features[0]);
-
-            // bind the select control to the selection model
-            sm.bind(selectControl);
-
-            // simulate a mousedown on the second row
-            // test that the second feature is selected in the layer
-            e = {
-                button: 0,
-                shiftKey: false,
-                ctrlKey: false
-            };
-            grid.fireEvent("rowmousedown", grid, 1, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[1]) > -1,
-                 "click on row 1 selects feature 1");
-            sm.clearSelections();
-            
-            // select feature 1
-            // test that the second row is selected
-            selectControl.select(features[1]);
-            t.ok(sm.isSelected(1),
-                 "selecting feature 1 selects row 1");
-            selectControl.unselect(features[1]);
-            sm.clearSelections();
-            
-            // unbind row and feature selection
-            sm.unbind(); 
-            // (side effect: selectControl is deactivated)
-            
-            // simulate a mousedown on the first row
-            // test that the first feature is not selected in the layer
-            e = {
-                button: 0,
-                shiftKey: false,
-                ctrlKey: false
-            };
-            grid.fireEvent("rowmousedown", grid, 0, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[0]) < 0,
-                 "click on row 0 does not select feature 0");
-            sm.clearSelections();
-            
-            // select feature 0
-            // test that the first row is not selected
-            selectControl.select(features[0]);
-            t.ok(!sm.isSelected(0),
-                 "selecting feature 0 does not select row 0");
-            selectControl.unselect(features[0]);
-            
-            // bind selection of features on a layer to rows
-            sm.bind(layer, {controlConfig: {hover: true}});
-            
-            // verify that controlConfig has been applied
-            t.eq(sm.selectControl.hover, true,
-                 "bind configures correctly the select feature control");
-
-            // simulate a mousedown on the second row
-            // test that the second feature is selected in the layer
-            e = {
-                button: 0,
-                shiftKey: false,
-                ctrlKey: false
-            };
-            grid.fireEvent("rowmousedown", grid, 1, e);
-            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
-                                         features[1]) > -1,
-                 "click on row 1 selects feature 1");
-            sm.clearSelections();
-            
-            // select feature 1
-            // test that the second row is selected
-            sm.selectControl.select(features[1]);
-            t.ok(sm.isSelected(1),
-                 "selecting feature 1 selects row 1");
-            sm.selectControl.unselect(features[1]);
-            sm.clearSelections();
-            
-            /*
-             * Tear down
-             */
-            grid.destroy();
-        }
-    </script>
-
-  <body>
-      <div id="map" style="width:100px;height:100px"></div>
-      <div id="grid"></div>
-  </body>
-</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,376 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+    
+        function test_init(t) {
+            t.plan(11);
+
+            /*
+             * Set up
+             */
+            var map, layer, layers, selectControl, store, sm;
+            
+            map = new OpenLayers.Map();
+            layer = new OpenLayers.Layer.Vector("vector");
+            layers = [layer];
+            map.addLayers(layers);
+
+            store = new GeoExt.data.FeatureStore({layer: layer});
+
+            /*
+             * Test
+             */
+
+            // create a feature selection model
+            // 1 test
+            sm = new GeoExt.grid.FeatureSelectionModel();
+            t.ok(sm instanceof Ext.grid.RowSelectionModel,
+                 "a feature selection model is a row selection model");
+
+            // create a feature selection model and give it a
+            // select feature control
+            // 2 tests
+            selectControl = new OpenLayers.Control.SelectFeature(layer);
+            sm = new GeoExt.grid.FeatureSelectionModel({
+                selectControl: selectControl, multiple: false
+            });
+            t.ok(sm.selectControl == selectControl,
+                 "ctor sets the passed select feature control in the instance");
+            t.eq(sm.singleSelect, true,
+                 "ctor sets singleSelect to true in the instance");
+
+            // create a feature selection model with singleSelect true and give
+            // it a select feature control
+            // 1 test
+            selectControl = new OpenLayers.Control.SelectFeature(layer);
+            sm = new GeoExt.grid.FeatureSelectionModel({
+                singleSelect: true,
+                selectControl: selectControl
+            });
+            t.eq(sm.selectControl.multiple, false,
+                 "ctor configures the select feature control with multiple false");
+
+            // create a feature selection model and give it a layer and
+            // a select control config
+            // 3 tests
+            sm = new GeoExt.grid.FeatureSelectionModel({
+                layer: layer,
+                selectControl: {
+                    hover: true
+                }
+            });
+            t.ok(sm.selectControl instanceof OpenLayers.Control.SelectFeature,
+                 "ctor creates a select feature control when passed a layer");
+            t.ok(sm.selectControl.layer == layer,
+                 "ctor configures the select feature control with the passed layer");
+            t.eq(sm.selectControl.hover, true,
+                 "ctor configures the select feature control with the passed config");
+
+            // create a feature selection model and create a grid with it
+            // 3 tests
+            sm = new GeoExt.grid.FeatureSelectionModel({
+                selectControl: {
+                    hover: true
+                }
+            });
+            var grid = new Ext.grid.GridPanel({
+                renderTo: "grid",
+                store: store,
+                columns: [{
+                    header: "name"
+                }],
+                sm: sm,
+                deferRowRender: false
+            });
+            t.ok(sm.selectControl instanceof OpenLayers.Control.SelectFeature,
+                 "init creates a select feature control ");
+            t.ok(sm.selectControl.layer == layer,
+                 "init configures the select feature control with the store layer");
+            t.eq(sm.selectControl.hover, true,
+                 "init configures the select feature control with the passed config");
+            grid.destroy();
+
+            // 1 test
+            var CheckboxSelectionModel = Ext.extend(
+                Ext.grid.CheckboxSelectionModel,
+                new GeoExt.grid.FeatureSelectionModelMixin
+            );
+            sm = new CheckboxSelectionModel();
+            t.ok(sm instanceof Ext.grid.CheckboxSelectionModel,
+                 "instance is a checkbox selection model");
+        }
+
+        function test_row_selection(t) {
+            t.plan(7);
+
+            /*
+             * Set up
+             */
+
+            var map, layer, features, store, sm, grid, e;
+
+            map = new OpenLayers.Map('map');
+
+            layer = new OpenLayers.Layer.Vector("vector");
+            map.addLayer(layer);
+
+            features = [
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo1", bar: "bar1"}
+                ),
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo2", bar: "bar2"}
+                )
+            ];
+            
+            store = new GeoExt.data.FeatureStore({
+                layer: layer,
+                data: features
+            });
+
+            sm = new GeoExt.grid.FeatureSelectionModel();
+
+            grid = new Ext.grid.GridPanel({
+                renderTo: "grid",
+                store: store,
+                columns: [{
+                    dataIndex: "foo"
+                }, {
+                    dataIndex: "bar"
+                }],
+                sm: sm,
+                deferRowRender: false
+            });
+
+            /*
+             * Test
+             */
+
+            // simulate a mousedown on the first row
+            // test that the first feature is selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) > -1,
+                 "click on row 0 selects feature 0");
+                 
+            // simulate a mousedown on the first row
+            // test that the first feature is deselected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: true
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) < 0,
+                 "click on row 0 deselects feature 0");
+
+            // simulate a mousedown on the second row
+            grid.fireEvent("rowmousedown", grid, 1, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[1]) > -1,
+                 "click on row 1 selects feature 1");
+
+            sm.clearSelections();
+
+            // select feature 0
+            // test that the first row is selected
+            sm.selectControl.select(features[0]);
+            t.ok(sm.isSelected(0),
+                 "selecting feature 0 selects row 0");
+
+            // select feature 1
+            // test that the second row is selected
+            sm.selectControl.select(features[1]);
+            t.ok(sm.isSelected(1),
+                 "selecting feature 1 selects row 1");
+
+            // unselect feature 0
+            // test that the first row is selected
+            sm.selectControl.unselect(features[0]);
+            t.ok(!sm.isSelected(0),
+                 "unselecting feature 0 unselects row 0");
+
+            // unselect feature 1
+            // test that the second row is unselected
+            sm.selectControl.unselect(features[1]);
+            t.ok(!sm.isSelected(1),
+                 "unselecting feature 1 unselects row 1");
+            
+            /*
+             * Tear down
+             */
+            grid.destroy();
+        }
+        
+        
+        function test_bind_unbind(t) {
+            t.plan(9);
+
+            /*
+             * Set up
+             */
+
+            var map, layer, selectControl, features, store, sm, grid, e;
+
+            map = new OpenLayers.Map('map');
+
+            layer = new OpenLayers.Layer.Vector("vector");
+            map.addLayer(layer);
+
+            selectControl = new OpenLayers.Control.SelectFeature(layer);
+            map.addControl(selectControl);
+
+            features = [
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo1", bar: "bar1"}
+                ),
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo2", bar: "bar2"}
+                )
+            ];
+            
+            store = new GeoExt.data.FeatureStore({
+                layer: layer,
+                data: features
+            });
+
+            sm = new GeoExt.grid.FeatureSelectionModel({
+                layerFromStore: false
+            });
+
+            grid = new Ext.grid.GridPanel({
+                renderTo: "grid",
+                store: store,
+                columns: [{
+                    dataIndex: "foo"
+                }, {
+                    dataIndex: "bar"
+                }],
+                sm: sm,
+                deferRowRender: false
+            });
+ 
+            /*
+             * Test
+             */
+
+            // simulate a mousedown on the first row
+            // test that the first feature is not selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) < 0,
+                 "click on row 0 does not select feature 0");
+            sm.clearSelections();
+            
+            // select feature 0
+            // test that the first row is not selected
+            selectControl.select(features[0]);
+            t.ok(!sm.isSelected(0),
+                 "selecting feature 0 does not select row 0");
+            selectControl.unselect(features[0]);
+
+            // bind the select control to the selection model
+            sm.bind(selectControl);
+
+            // simulate a mousedown on the second row
+            // test that the second feature is selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 1, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[1]) > -1,
+                 "click on row 1 selects feature 1");
+            sm.clearSelections();
+            
+            // select feature 1
+            // test that the second row is selected
+            selectControl.select(features[1]);
+            t.ok(sm.isSelected(1),
+                 "selecting feature 1 selects row 1");
+            selectControl.unselect(features[1]);
+            sm.clearSelections();
+            
+            // unbind row and feature selection
+            sm.unbind(); 
+            // (side effect: selectControl is deactivated)
+            
+            // simulate a mousedown on the first row
+            // test that the first feature is not selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) < 0,
+                 "click on row 0 does not select feature 0");
+            sm.clearSelections();
+            
+            // select feature 0
+            // test that the first row is not selected
+            selectControl.select(features[0]);
+            t.ok(!sm.isSelected(0),
+                 "selecting feature 0 does not select row 0");
+            selectControl.unselect(features[0]);
+            
+            // bind selection of features on a layer to rows
+            sm.bind(layer, {controlConfig: {hover: true}});
+            
+            // verify that controlConfig has been applied
+            t.eq(sm.selectControl.hover, true,
+                 "bind configures correctly the select feature control");
+
+            // simulate a mousedown on the second row
+            // test that the second feature is selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 1, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[1]) > -1,
+                 "click on row 1 selects feature 1");
+            sm.clearSelections();
+            
+            // select feature 1
+            // test that the second row is selected
+            sm.selectControl.select(features[1]);
+            t.ok(sm.isSelected(1),
+                 "selecting feature 1 selects row 1");
+            sm.selectControl.unselect(features[1]);
+            sm.clearSelections();
+            
+            /*
+             * Tear down
+             */
+            grid.destroy();
+        }
+    </script>
+
+  <body>
+      <div id="map" style="width:100px;height:100px"></div>
+      <div id="grid"></div>
+  </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/BaseLayerContainer.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/tree/BaseLayerContainer.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/BaseLayerContainer.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/BaseLayerContainer.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,76 @@
+<html>
+    <head>
+        <script src="../../../../../../openlayers/lib/OpenLayers.js"></script>
+        <script src="../../../../../../ext/adapter/ext/ext-base.js"></script>
+        <script src="../../../../../../ext/ext-all-debug.js"></script>
+        <script src="../../../../../lib/GeoExt.js"></script>
+    
+        <script>
+        
+        function test_render(t) {
+            
+            t.plan(2);
+            
+            var map = new OpenLayers.Map({
+                div: "map"
+            });
+            // add a base layer
+            var layer = new OpenLayers.Layer("foo", {isBaseLayer: true});
+            map.addLayer(layer);
+            // add an overlay
+            map.addLayer(new OpenLayers.Layer());
+            
+            var store = new GeoExt.data.LayerStore({
+                map: map
+            });
+            
+            var node = new GeoExt.tree.BaseLayerContainer({
+                layerStore: store,
+                expanded: true
+            });
+            
+            var panel = new Ext.tree.TreePanel({
+                renderTo: document.body,
+                root: node
+            });
+            
+            t.eq(node.childNodes.length, 1, "only base layers get added to the container");
+            t.eq(node.firstChild.attributes.iconCls, "gx-tree-baselayer-icon", "iconClass for child set correctly");
+
+            node.destroy();
+            map.destroy();
+            
+        }
+        
+        function test_addLayerNode(t) {
+            
+            t.plan(2);
+            
+            // setup
+            var original = GeoExt.tree.LayerContainer.prototype.addLayerNode;
+            var container = new GeoExt.tree.BaseLayerContainer({});
+            var args;
+            GeoExt.tree.LayerContainer.prototype.addLayerNode = function() {
+                args = arguments;
+            };
+            var Rec = Ext.data.Record.create([{name: "layer"}]);
+            var record = new Rec({
+                layer: new OpenLayers.Layer(null, {isBaseLayer: true})
+            });
+            
+            // call addLayerNode with two args and confirm the super got both
+            container.addLayerNode(record, 4);
+            t.eq(args.length, 2, "method called with two arguments");
+            t.eq(args[1], 4, "method called with correct second argument");
+            
+            // clean up
+            GeoExt.tree.LayerContainer.prototype.addLayerNode = original;
+            
+        }
+        
+        </script>
+    </head>
+    <body>
+        <div id="map" style="width: 100px; height: 100px;"></div>
+    </body>
+</html>
\ No newline at end of file

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerContainer.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerContainer.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerContainer.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -12,15 +12,17 @@
             t.plan(2);
             
             var store = new GeoExt.data.LayerStore();
-            var defaults = {};
+            var defaults = {foo: "bar"};
             
             var node = new GeoExt.tree.LayerContainer({
                 layerStore: store,
-                defaults: defaults
+                loader: {
+                    baseAttrs: defaults
+                }
             });
             
-            t.ok(node.layerStore === store, "layerStore set");
-            t.ok(node.defaults === defaults, "defaults set");
+            t.ok(node.loader.store === store, "layerStore set");
+            t.eq(node.loader.baseAttrs.foo, "bar", "baseAttrs set");
             
             node.destroy();
             
@@ -28,7 +30,7 @@
         
         function test_render(t) {
             
-            t.plan(2);
+            t.plan(3);
             
             var map = new OpenLayers.Map({
                 div: "map",
@@ -42,7 +44,8 @@
             });
             
             var node = new GeoExt.tree.LayerContainer({
-                layerStore: store
+                layerStore: store,
+                expanded: true
             });
             
             var panel = new Ext.tree.TreePanel({
@@ -52,11 +55,167 @@
             
             t.eq(node.childNodes && node.childNodes.length, 1, "container has one child");
             t.ok(node.firstChild.layer === layer, "child layer is correct");
+            t.eq(node.firstChild.attributes.iconCls, "gx-tree-layer-icon", "iconClass for child set correctly");
 
             node.destroy();
+            map.destroy();
             
         }
         
+        function test_order(t) {
+            
+            t.plan(16);
+            
+            var map = new OpenLayers.Map({
+                div: "map",
+                allOverlays: true
+            });
+            
+            var store = new GeoExt.data.LayerStore({
+                map: map
+            });
+            
+            var root = new GeoExt.tree.LayerContainer({
+                layerStore: store,
+                expanded: true
+            });
+            
+            var panel = new Ext.tree.TreePanel({
+                renderTo: document.body,
+                root: root
+            });
+            
+            var a = new OpenLayers.Layer("a");
+            var b = new OpenLayers.Layer("b");
+            var c = new OpenLayers.Layer("c");
+            var d = new OpenLayers.Layer("d");
+            
+            var reader = new GeoExt.data.LayerReader();
+            
+            // add two records to empty root
+            store.add(reader.readRecords([a, b]).records);
+            t.eq(root.childNodes.length, 2, "[a, b] two records added");
+            t.eq(root.childNodes[0].layer.name, "b", "[a, b] last layer drawn at top of root");
+            t.eq(root.childNodes[1].layer.name, "a", "[a, b] first layer drawn at bottom of root");
+            
+            // add two records to root with two existing child nodes
+            store.add(reader.readRecords([c, d]).records);
+            t.eq(root.childNodes.length, 4, "[a, b, c, d] four records total");
+            t.eq(root.childNodes[0].layer.name, "d", "[a, b, c, d] last layer drawn at top of root");
+            t.eq(root.childNodes[1].layer.name, "c", "[a, b, c, d] third layer drawn at correct index");
+            t.eq(root.childNodes[2].layer.name, "b", "[a, b, c, d] second layer drawn at correct index");
+            t.eq(root.childNodes[3].layer.name, "a", "[a, b, c, d] first layer drawn at bottom of root");
+            
+            // remove the first two layers in draw order
+            store.remove(store.getAt(0));
+            store.remove(store.getAt(0));
+            t.eq(root.childNodes.length, 2, "[c, d] two records total");
+            t.eq(root.childNodes[0].layer.name, "d", "[c, d] last layer drawn at top of root");
+            t.eq(root.childNodes[1].layer.name, "c", "[c, d] first layer drawn at bottom of root");
+            
+            // insert two records in the middle
+            store.insert(1, reader.readRecords([a, b]).records);
+            t.eq(root.childNodes.length, 4, "[c, a, b, d] four records total");
+            t.eq(root.childNodes[0].layer.name, "d", "[c, a, b, d] last layer drawn at top of root");
+            t.eq(root.childNodes[1].layer.name, "b", "[c, a, b, d] third layer drawn at correct index");
+            t.eq(root.childNodes[2].layer.name, "a", "[c, a, b, d] second layer drawn at correct index");
+            t.eq(root.childNodes[3].layer.name, "c", "[c, a, b, d] first layer drawn at bottom of root");
+            
+            root.destroy();
+            map.destroy();
+            
+        }
+        
+        function test_group_move(t) {
+            t.plan(8);
+            
+            var map = new OpenLayers.Map({
+                div: "map",
+                allOverlays: true
+            });
+            
+            map.addLayers([
+                new OpenLayers.Layer("a"),
+                new OpenLayers.Layer("b"),
+                new OpenLayers.Layer("c", {displayInLayerSwitcher: false}),
+                new OpenLayers.Layer("d", {displayInLayerSwitcher: false})
+            ]);
+
+            var store = new GeoExt.data.LayerStore({
+                map: map
+            });
+
+            var root = new Ext.tree.TreeNode({
+                expanded: true
+            });
+            
+            var panel = new Ext.tree.TreePanel({
+                renderTo: document.body,
+                root: root,
+                listeners: {
+                    beforemovenode: function(tree, node, oldParent, newParent, index){
+                        // change the group when moving to a new container
+                        if (oldParent !== newParent) {
+                            var index = store.findBy(function(r){
+                                return r.get("layer") === node.layer;
+                            });
+                            var layer = store.getAt(index).get("layer");
+                            layer.displayInLayerSwitcher = !layer.displayInLayerSwitcher;
+                        }
+                    },
+                    scope: this
+                }
+            });
+
+            var container1 = new GeoExt.tree.LayerContainer({
+                loader: {
+                    store: store,
+                    filter: function(record) {
+                        return !record.get("layer").displayInLayerSwitcher
+                    }
+                },
+                expanded: true
+            });
+            root.appendChild(container1);
+            var container2 = new GeoExt.tree.LayerContainer({
+                loader: {
+                    store: store,
+                    filter: function(record) {
+                        return record.get("layer").displayInLayerSwitcher
+                    }
+                },
+                expanded: true
+            });
+            root.appendChild(container2);
+            
+            // testing if layers get grouped by filter properly
+            t.eq(container1.childNodes[0].text, "d", "d is 1st node on container1");
+            t.eq(container1.childNodes[1].text, "c", "c is 2nd node on container1");
+            t.eq(container2.childNodes[0].text, "b", "b is 1st node on container2");
+            t.eq(container2.childNodes[1].text, "a", "a is 2nd node on container2");
+            
+            // testing if indices are determined correctly from previous node in container
+            container2.appendChild(container1.childNodes[0]);
+            t.eq(map.getLayerIndex(container2.childNodes[2].layer), 0, "[c, b, a, d] d moved to the bottom");
+            
+            // testing if indices are determined correctly from next layer in container
+            container2.insertBefore(container1.childNodes[0], container2.childNodes[1]);
+            t.eq(map.getLayerIndex(container2.childNodes[0].layer), 3, "[b, a, d, c] c is now the 4th layer on the map");
+            
+            // testing if indices are determined correctly from container below
+            container1.appendChild(container2.childNodes[0]);
+            t.eq(map.getLayerIndex(container1.childNodes[0].layer), 3, "[b, a, d, c] c is still the 4th layer on the map");
+
+            for(var i=0; i<3; ++i) {
+                container1.appendChild(container2.childNodes[0]);
+            }
+            
+            // testing if indices are determined correctly from container above
+            container2.appendChild(container1.childNodes[0]);
+            t.eq(map.getLayerIndex(container2.childNodes[0].layer), 0, "[a,d,c,b] b moved to the bottom");
+            
+        }
+        
         </script>
     </head>
     <body>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerLoader.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerLoader.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerLoader.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerLoader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,76 @@
+<html>
+    <head>
+        <script src="../../../../../../openlayers/lib/OpenLayers.js"></script>
+        <script src="../../../../../../ext/adapter/ext/ext-base.js"></script>
+        <script src="../../../../../../ext/ext-all-debug.js"></script>
+        <script src="../../../../../lib/GeoExt.js"></script>
+    
+        <script>
+        
+        function test_constructor(t) {
+            
+            t.plan(2);
+            
+            var loader = new GeoExt.tree.LayerLoader({
+                store: "foo",
+                filter: "bar"
+            });
+            
+            t.eq(loader.store, "foo", "store set");
+            t.eq(loader.filter, "bar", "filter set");
+        }
+        
+        function test_load(t) {
+            
+            t.plan(3);
+            
+            var args;
+            
+            var loader = new GeoExt.tree.LayerLoader({
+                store: new GeoExt.data.LayerStore(),
+                listeners: {
+                    "beforeload": function(loader, node) {
+                        return node.attributes.load;
+                    },
+                    "load": function(loader, node) {
+                        args = {
+                            loader: loader,
+                            node: node
+                        };
+                    }
+                }
+            });
+            
+            var node = new Ext.tree.AsyncTreeNode({
+                loader: loader,
+                load: false,
+                expanded: true
+            });
+            var tree = new Ext.tree.TreePanel({root: node});
+            tree.render("tree");
+            t.ok(!args, "loading aborted by beforeload event");
+            
+            tree.destroy();
+            node.destroy();
+            
+            node = new Ext.tree.AsyncTreeNode({
+                loader: loader,
+                load: true,
+                expanded: true
+            });
+            tree = new Ext.tree.TreePanel({root: node});
+            tree.render("tree");
+            t.ok(args.loader == loader, "loader passed correctly to 'load' handler");
+            t.ok(args.node == node, "node passed correctly to 'node' handler");
+            
+            tree.destroy();
+            node.destroy();
+            loader.destroy();
+        }
+        
+        </script>
+    </head>
+    <body>
+        <div id="tree" style="width: 100px; height: 100px;"></div>
+    </body>
+</html>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerNode.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerNode.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerNode.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -9,16 +9,20 @@
         
         function test_constructor(t) {
             
-            t.plan(1);
+            t.plan(2);
             
             var store = new GeoExt.data.LayerStore();
             
             var node = new GeoExt.tree.LayerNode({
                 layer: "foo",
-                layerStore: store
+                layerStore: store,
+                loader: {
+                    param: "foo"
+                }
             });
             
             t.ok(node.layerStore === store, "layerStore set");
+            t.ok(node.attributes.loader instanceof GeoExt.tree.LayerParamLoader, "LayerParamLoader created from object.");
             
             node.destroy();
             
@@ -26,7 +30,7 @@
         
         function test_render(t) {
             
-            t.plan(5);
+            t.plan(8);
             
             var layer = new OpenLayers.Layer("foo");
             
@@ -38,11 +42,7 @@
             var node = new GeoExt.tree.LayerNode({
                 layer: "foo",
                 radioGroup: "group",
-                childNodeType: {
-                    add: function() {
-                        t.ok(true, "add function of childNodeType called");
-                    }
-                }
+                checkedGroup: "check"
             });
             
             node.on("radiochange", function() {
@@ -56,23 +56,143 @@
             
             mapPanel.on("render", function() {
                 t.ok(node.layer === layer, "layer found on detected map panel");
-                
+
+                t.ok(node.ui.checkbox, "node has a checkbox");
                 t.ok(node.ui.radio, "node has a radio button");
                 // simulate a click event for testing the radiochange event
                 node.ui.onClick({getTarget: function() {return true}});
                 
+                t.eq(node.ui.checkbox.type, "radio", "checkbox rendered as radio button when checkedGroup is configured");
+                t.eq(node.ui.checkbox.name, "check_checkbox", "option group name set correctly according to checkedGroup");
+                
+                layer.setVisibility(false);
+                t.eq(layer.visibility, true, "unchecking a layer with checkedGroup has no effect");
+                
+                delete node.attributes.checkedGroup;
                 node.ui.toggleCheck();
-                t.eq(layer.visibility, false, "unchecking node hides layer");
+                t.eq(layer.visibility, false, "unchecking a layer without checkedGroup hides the layer");
             });
 
             mapPanel.render("map");
             
+            mapPanel.destroy();
         }
         
+        function test_enforceOneVisible(t) {
+            t.plan(8);
+
+            var layers = [
+                new OpenLayers.Layer("foo"),
+                new OpenLayers.Layer("bar")
+            ];
+            var mapPanel = new GeoExt.MapPanel({
+                layers: layers,
+                allOverlays: true
+            });
+            var root = new GeoExt.tree.LayerContainer({
+                loader: {
+                    baseAttrs: {checkedGroup: "group"}
+                },
+                expanded: true
+            });
+            var panel = new Ext.tree.TreePanel({
+                renderTo: "tree",
+                root: root
+            });
+            mapPanel.render("map");
+
+            // two overlay layers in the same checkedGroup: only one can be visible
+            var nodes = panel.getRootNode().childNodes;
+            t.eq(nodes[0].layer.getVisibility(), false, "Layer on top is hidden");
+            t.eq(nodes[1].layer.getVisibility(), true, "Layer on bottom is visible");
+            
+            delete root.loader.baseAttrs.checkedGroup;
+            mapPanel.map.allOverlays = false;
+            
+            // without a custom checkedGroup, base layers get the gx_baselayer group assigned
+            mapPanel.layers.on("add", function(){
+                t.eq(nodes[0].attributes.checkedGroup, "gx_baselayer", "gx_baselayer checkedGroup set for base layer");
+            }, this, {single: true});
+            mapPanel.map.addLayer(new OpenLayers.Layer("foo1", {isBaseLayer: true}));
+                        
+            root.loader.baseAttrs.checkedGroup = "another_group";
+            
+            // a custom checkedGroup will override the gx_baselayer default
+            mapPanel.layers.on("add", function() {
+                t.eq(nodes[0].attributes.checkedGroup, "another_group", "custom checkedGroup set for base layer");
+            }, this, {single: true});
+            mapPanel.map.addLayer(new OpenLayers.Layer("bar", {isBaseLayer: true}));
+            
+            // overlays also get the custom checkedGroup assigned
+            mapPanel.layers.on("add", function() {
+                t.eq(nodes[0].attributes.checkedGroup, "another_group", "custom checkedGroup set for overlay");
+                // the another_group baselayer from above is invisible (the gx_baselayer one is visible)
+                t.eq(nodes[0].layer.getVisibility(), true, "overlay in checkedGroup visible because no other layer in group is visible");
+                // now making it visible
+                nodes[1].layer.setVisibility(true);
+                // and the overlay in the same checkedGroup gets hidden
+                t.eq(nodes[0].layer.getVisibility(), false, "overlay in checkedGroup now hidden because base layer in group is visible");
+            }, this, {single: true});
+            mapPanel.map.addLayer(new OpenLayers.Layer("foo2", {isBaseLayer: false}));
+            
+            // remove all layers except one visible and one invisible layer
+            mapPanel.map.removeLayer(nodes[0].layer);
+            mapPanel.map.removeLayer(nodes[0].layer);
+            mapPanel.map.removeLayer(nodes[0].layer);
+            // now there is only one layer in the another_group
+            // "bar" (invisible) is now in the group at position [0], "foo" (visible) at [1]
+            mapPanel.layers.on("remove", function() {
+                t.eq(nodes[0].layer.getVisibility(), true, "Previously invisible layer was made visible because the visible layer has been removed")
+            }, this, {single: true});
+            // removing "foo", so "bar" will have to be made visible
+            mapPanel.map.removeLayer(nodes[1].layer);
+            
+            mapPanel.destroy();
+        }
+
+        function test_changelayername(t) {
+            t.plan(3);
+
+            var layer = new OpenLayers.Layer("test");
+
+            var mapPanel = new GeoExt.MapPanel({
+                layers: [layer],
+                allOverlays: true
+            });
+
+            var node = new GeoExt.tree.LayerNode({
+                layer: "test",
+                layerStore: mapPanel.layers
+            });
+
+            var panel = new Ext.tree.TreePanel({
+                root: node
+            });
+            panel.render(document.body);
+
+            layer.setName("My new name");
+            t.eq(node.text, "My new name", "Node is named 'My new name' after layer setName is called");
+            
+            layer.setName("Second Name");
+            t.eq(node.text, "Second Name", "Layer name (title) can be set multiple times and the node respects it.");
+            
+            node = new GeoExt.tree.LayerNode({
+                layer: "My new name",
+                layerStore: mapPanel.layers,
+                text: "My custom text"
+            });
+            
+            layer.setName("test");
+            t.eq(node.text, "My custom text", "Node text does not change with layer name if custom text was provided");
+
+            node.destroy();
+            mapPanel.destroy();
+        }
+        
         </script>
     </head>
     <body>
         <div id="map" style="width: 100px; height: 100px;"></div>
         <div id="tree" style="width: 100px; height: 100px;"></div>
     </body>
-</html>
\ No newline at end of file
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamLoader.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamLoader.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamLoader.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamLoader.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,75 @@
+<html>
+    <head>
+        <script src="../../../../../../openlayers/lib/OpenLayers.js"></script>
+        <script src="../../../../../../ext/adapter/ext/ext-base.js"></script>
+        <script src="../../../../../../ext/ext-all-debug.js"></script>
+        <script src="../../../../../lib/GeoExt.js"></script>
+    
+        <script>
+            
+        function setupTree() {
+            var layer = new OpenLayers.Layer.WMS("Group", "http://localhost/wms", {
+                 "LAYERS": ["layer1", "layer2"]
+            });
+           
+            return new Ext.tree.TreePanel({
+                renderTo: "tree",
+                loader: {
+                    applyLoader: false
+                },
+                root: {
+                    text: "Layers",
+                    expanded: true,
+                    children: [{
+                        nodeType: "gx_layer",
+                        expanded: true,
+                        layer: layer,
+                        loader: {
+                            param: "LAYERS"
+                        }
+                    }]
+                }
+            });
+        }
+        
+        function test_constructor(t) {
+            t.plan(4);
+            
+            var loader = new GeoExt.tree.LayerParamLoader({
+                param: "bar"
+            });
+            
+            t.ok(loader instanceof GeoExt.tree.LayerParamLoader, "instance created.");
+            t.eq(loader.param, "bar", "param set.");
+            t.eq(loader.delimiter, ",", "default delimiter set.");
+            
+            loader = new GeoExt.tree.LayerParamLoader({
+                param: "bar",
+                delimiter: ";"
+            });
+
+            t.eq(loader.delimiter, ";", "custom delimiter set.");
+        }
+        
+        function test_load(t) {
+            t.plan(4);
+
+            var panel = setupTree();
+            var nodes = panel.getRootNode().childNodes[0].childNodes;
+            var layer = nodes[0].layer;
+            
+            t.eq(nodes.length, 2, "2 child nodes created.");
+            t.eq(nodes[0].param, "LAYERS", "param for 1st node set.");
+            t.eq(nodes[0].item, "layer2", "item for 1st node set.");
+            t.eq(nodes[1].item, "layer1", "item for 2nd node set.");
+            
+            panel.destroy();
+            layer.destroy();
+        }
+
+        </script>
+    </head>
+    <body>
+        <div id="tree" style="width: 100px; height: 100px;"></div>
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamNode.html (from rev 1504, core/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamNode.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamNode.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/GeoExt/widgets/tree/LayerParamNode.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,174 @@
+<html>
+    <head>
+        <script src="../../../../../../openlayers/lib/OpenLayers.js"></script>
+        <script src="../../../../../../ext/adapter/ext/ext-base.js"></script>
+        <script src="../../../../../../ext/ext-all-debug.js"></script>
+        <script src="../../../../../lib/GeoExt.js"></script>
+    
+        <script>
+            
+        function setupTree(layer1Checked) {
+            var layer = new OpenLayers.Layer.WMS("Group", "http://localhost/wms", {
+                 "LAYERS": ["layer1", "layer2"],
+                 "CQL_FILTER": "city='Vienna' OR city='Graz'"
+            }, {
+                visibility: false
+            });
+           
+            var store = new GeoExt.data.LayerStore({
+                layers: [layer]
+            });
+
+            return new Ext.tree.TreePanel({
+                renderTo: "tree",
+                loader: new Ext.tree.TreeLoader({
+                    baseAttrs: {
+                        layerStore: store
+                    }
+                }),
+                root: {
+                    text: "Layers",
+                    expanded: true,
+                    children: [{
+                        nodeType: "gx_layerparam",
+                        layer: layer,
+                        param: "LAYERS",
+                        item: "layer1",
+                        checked: layer1Checked
+                    }, {
+                        nodeType: "gx_layerparam",
+                        layer: layer,
+                        param: "LAYERS",
+                        item: "layer2",
+                        delimiter: ";"
+                    }, {
+                        nodeType: "gx_layerparam",
+                        // provide this layer as string to see if the layer can
+                        // be configured from the store provided in baseParams
+                        layer: "Group",
+                        param: "CQL_FILTER",
+                        item: "city='Vienna'",
+                        delimiter: " OR "
+                    }]
+                }
+            });
+        }
+        
+        function test_constructor(t) {
+            t.plan(4);
+            
+            var layer = {
+                params: {bar: ["before", "foobar", "after"]},
+                getVisibility: function(){}
+            };
+            
+            var node = new GeoExt.tree.LayerParamNode({
+                layer: layer,
+                param: "bar",
+                item: "foobar"
+            });
+            
+            t.eq(node.param, "bar", "param set.");
+            t.eq(node.item, "foobar", "item set.");
+            t.eq(node.delimiter, ",", "default delimiter set.");
+            
+            node.destroy();
+            
+            node = new GeoExt.tree.LayerParamNode({
+                layer: layer,
+                param: "bar",
+                item: "foobar",
+                delimiter: ";"
+            });
+
+            t.eq(node.delimiter, ";", "custom delimiter set.");
+            
+            node.destroy();
+        }
+        
+        function test_render(t) {
+            t.plan(5);
+            
+            var panel = setupTree(true);
+            var nodes = panel.getRootNode().childNodes;
+            var layer = nodes[0].layer;
+
+            t.ok(nodes[2].layer instanceof OpenLayers.Layer, "layer set from store.");
+            t.eq(nodes[0].allItems, ["layer1", "layer2"], "allItems with set from array");
+            t.eq(nodes[0].attributes.checked, true, "checked attribute maintained correctly");
+            t.eq(layer.params.LAYERS, ["layer1"], "layer displayed with checked item");
+            t.eq(nodes[2].allItems, ["city='Vienna'", "city='Graz'"], "allItems with custom delimiter set");
+            
+            panel.destroy();
+            nodes[2].attributes.layerStore.destroy();
+            layer.destroy();
+        }
+        
+        function test_onLayerVisibilityChanged(t) {
+            t.plan(6);
+
+            var panel = setupTree();
+            var nodes = panel.getRootNode().childNodes;
+            var layer = nodes[0].layer;
+            
+            t.eq(nodes[0].attributes.checked, false, "node for layer1 is unchecked.");
+            t.eq(nodes[1].attributes.checked, false, "node for layer2 is unchecked.");
+            
+            layer.setVisibility(true);
+            t.eq(nodes[0].attributes.checked, true, "node for layer1 is checked.");
+            t.eq(nodes[1].attributes.checked, true, "node for layer2 is checked.");
+            
+            layer.setVisibility(false);
+            t.eq(nodes[0].attributes.checked, false, "node for layer1 is unchecked again.");
+            t.eq(nodes[1].attributes.checked, false, "node for layer2 is unchecked again.");
+            
+            panel.destroy();
+            nodes[2].attributes.layerStore.destroy();
+            layer.destroy();
+        }
+        
+        function test_onCheckChange(t) {
+            t.plan(9);
+            
+            var panel = setupTree();
+            var nodes = panel.getRootNode().childNodes;
+            var layer = nodes[0].layer;
+            
+            layer.setVisibility(false);
+            
+            // now all nodes are unchecked, but the layer has all params set
+            
+            t.eq(nodes[1].attributes.checked, false, "node unchecked because layer is invisible.");
+            
+            nodes[1].getUI().toggleCheck(true);
+            
+            // now the layer has just the param item of nodes[0] set, and is visible
+            
+            t.eq(layer.getVisibility(), true, "layer is visible after checking a subnode.");
+            t.eq(layer.params.LAYERS, ["layer2"], "correct sublayer is set after checking it.");
+            
+            nodes[1].getUI().toggleCheck(false);
+            t.eq(layer.params.LAYERS, [], "layer2 invisible again after unchecking.");
+            t.eq(layer.getVisibility(), false, "layer hidden if no sublayers are visible");
+            
+            nodes[0].getUI().toggleCheck(true);
+            t.eq(layer.params.LAYERS, ["layer1"], "only checked sublayer is visible.");
+            t.eq(layer.getVisibility(), true, "layer set to visible with checked sublayer.");
+
+            // now check if split and join with custom delimiter works
+            nodes[2].getUI().toggleCheck(false);
+            t.eq(layer.params.CQL_FILTER, "city='Graz'", "param with custom delimiter removed.");
+            nodes[2].getUI().toggleCheck(true);
+            t.eq(layer.params.CQL_FILTER, "city='Vienna' OR city='Graz'", "param with custom delimiter re-added.");
+
+            panel.destroy();
+            nodes[2].attributes.layerStore.destroy();
+            layer.destroy();
+        }
+                
+        </script>
+    </head>
+    <body>
+        <div id="tree" style="width: 100px; height: 100px;"></div>
+    </body>
+</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides (from rev 1504, core/trunk/geoext/tests/lib/overrides)

Deleted: sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html
===================================================================
--- core/trunk/geoext/tests/lib/overrides/override-ext-ajax.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,184 +0,0 @@
-<!DOCTYPE html>
-<html debug="true">
-  <head>
-    <script type="text/javascript" src="../../../../openlayers/lib/OpenLayers.js"></script>
-    <script type="text/javascript" src="../../../../ext/adapter/ext/ext-base.js"></script>
-    <script type="text/javascript" src="../../../../ext/ext-all-debug.js"></script>
-    <script type="text/javascript" src="../../../lib/overrides/override-ext-ajax.js"></script>
-    <script type="text/javascript" src="../../../lib/GeoExt.js"></script>
-
-    <script type="text/javascript">
-        function setup() {
-            window._xhr = OpenLayers.Request.XMLHttpRequest;
-            var anon = new Function();
-            OpenLayers.Request.XMLHttpRequest = function() {};
-            OpenLayers.Request.XMLHttpRequest.prototype = {
-                open: anon,
-                setRequestHeader: anon,
-                send: anon
-            };
-            OpenLayers.Request.XMLHttpRequest.DONE = 4;
-        }
-
-        function teardown() {
-            OpenLayers.Request.XMLHttpRequest = window._xhr;
-        }
-
-        function test_request(t) {
-            t.plan(7);
-
-            /*
-             * Setup
-             */
-
-            setup();
-
-            var _srh;
-            var proto = OpenLayers.Request.XMLHttpRequest.prototype;
-
-            var request, data;
-
-            var argument= {"some": "argument"};
-            var scope = {"some": "scope"};
-            var headers = {"some": "headers"}; 
-
-            var successObj, failureObj;
-            var success = function(o) {
-                successObj = {scope: this, arg: o.argument};
-            };
-            var failure = function(o) {
-                failureObj = {scope: this, arg: o.argument};
-            };
-
-            /*
-             * Test
-             */
-
-            // test "success" callback
-            // 3 tests
-            data = "some data";
-            successObj = failureObj = null;
-            _srh = proto.setRequestHeader; 
-            proto.setRequestHeader = function(k, v) {
-                t.ok(k == "some" && v == "headers",
-                     "headers properly set");
-            };
-            request = Ext.lib.Ajax.request("GET", "http://foo",
-                {
-                    success: success,
-                    failure: failure,
-                    argument: argument,
-                    scope: scope
-                },
-                data,
-                {
-                    headers: headers
-                }
-            );
-            request.readyState = OpenLayers.Request.XMLHttpRequest.DONE;
-            request.status = 200;
-            request.onreadystatechange();
-            t.eq(successObj.scope, scope,
-                 "success cb called with proper scope");
-            t.eq(successObj.arg, argument,
-                 "success cb called with proper argument");
-            proto.setRequestHeader = _srh;
-
-            // test "failure" callback
-            // 2 tests
-            data = "some data";
-            successObj = failureObj = null;
-            request = Ext.lib.Ajax.request("GET", "http://foo",
-                {
-                    success: success,
-                    failure: failure,
-                    argument: argument,
-                    scope: scope
-                },
-                data,
-                {
-                    headers: headers
-                }
-            );
-            request.readyState = OpenLayers.Request.XMLHttpRequest.DONE;
-            request.status = 400;
-            request.onreadystatechange();
-            t.eq(failureObj.scope, scope,
-                 "failure cb called with proper scope");
-            t.eq(failureObj.arg, argument,
-                 "failure cb called with proper argument");
-
-            // test xmlData
-            // 1 test
-            data = "some data";
-            _srh = proto.setRequestHeader; 
-            proto.setRequestHeader = function(k, v) {
-                t.ok(k == "Content-Type" && v == "text/xml",
-                     "Content-Type header properly set");
-            };
-            request = Ext.lib.Ajax.request("GET", "http://foo",
-                {
-                },
-                null,
-                {
-                    xmlData: data
-                }
-            );
-            proto.setRequestHeader = _srh;
-
-            // test jsonData
-            // 1 test
-            data = "some data";
-            _srh = proto.setRequestHeader; 
-            proto.setRequestHeader = function(k, v) {
-                t.ok(k == "Content-Type" && v == "application/json",
-                     "Content-Type header properly set");
-            };
-            request = Ext.lib.Ajax.request("GET", "http://foo",
-                {
-                },
-                null,
-                {
-                    jsonData: data
-                }
-            );
-            proto.setRequestHeader = _srh;
-
-            /*
-             * Teardown
-             */
-            teardown();
-        }
-
-        function test_serializeForm(t) {
-            t.plan(1);
-
-            var expect = "key1=val1&key2=val2";
-            var result = Ext.lib.Ajax.serializeForm(Ext.get("form").dom);
-            t.eq(result, expect,
-                 "serializeForm returns expected result (" + expect + ")");
-        }
-
-        function test_formPost(t) {
-            t.plan(1);
-            var proto = OpenLayers.Request.XMLHttpRequest.prototype;
-            proto.setRequestHeader = function(k, v) {
-                t.ok(k == "Content-Type" && v == "application/x-www-form-urlencoded",
-                     "Content-Type header properly set for form POST");
-            };
-            Ext.Ajax.request({
-                failure: function(response) { },
-                method: 'POST',
-                success: function(response) { },
-                params: { username: 'bart', password: 'xx'},
-                url: 'foo.php'
-            });
-        }
-    </script>
-  <body>
-  <form id="form">
-      <input type="text" name="key1" value="val1"></input>
-      <input type="checkbox" name="key2" value="val2" checked="checked"></input>
-  </form>
-  </body>
-</html>

Copied: sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html (from rev 1504, core/trunk/geoext/tests/lib/overrides/override-ext-ajax.html)
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html	                        (rev 0)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/lib/overrides/override-ext-ajax.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../lib/overrides/override-ext-ajax.js"></script>
+    <script type="text/javascript" src="../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+        function setup() {
+            window._xhr = OpenLayers.Request.XMLHttpRequest;
+            var anon = new Function();
+            OpenLayers.Request.XMLHttpRequest = function() {};
+            OpenLayers.Request.XMLHttpRequest.prototype = {
+                open: anon,
+                setRequestHeader: anon,
+                send: anon
+            };
+            OpenLayers.Request.XMLHttpRequest.DONE = 4;
+        }
+
+        function teardown() {
+            OpenLayers.Request.XMLHttpRequest = window._xhr;
+        }
+
+        function test_request(t) {
+            t.plan(7);
+
+            /*
+             * Setup
+             */
+
+            setup();
+
+            var _srh;
+            var proto = OpenLayers.Request.XMLHttpRequest.prototype;
+
+            var request, data;
+
+            var argument= {"some": "argument"};
+            var scope = {"some": "scope"};
+            var headers = {"some": "headers"}; 
+
+            var successObj, failureObj;
+            var success = function(o) {
+                successObj = {scope: this, arg: o.argument};
+            };
+            var failure = function(o) {
+                failureObj = {scope: this, arg: o.argument};
+            };
+
+            /*
+             * Test
+             */
+
+            // test "success" callback
+            // 3 tests
+            data = "some data";
+            successObj = failureObj = null;
+            _srh = proto.setRequestHeader; 
+            proto.setRequestHeader = function(k, v) {
+                t.ok(k == "some" && v == "headers",
+                     "headers properly set");
+            };
+            request = Ext.lib.Ajax.request("GET", "http://foo",
+                {
+                    success: success,
+                    failure: failure,
+                    argument: argument,
+                    scope: scope
+                },
+                data,
+                {
+                    headers: headers
+                }
+            );
+            request.readyState = OpenLayers.Request.XMLHttpRequest.DONE;
+            request.status = 200;
+            request.onreadystatechange();
+            t.eq(successObj.scope, scope,
+                 "success cb called with proper scope");
+            t.eq(successObj.arg, argument,
+                 "success cb called with proper argument");
+            proto.setRequestHeader = _srh;
+
+            // test "failure" callback
+            // 2 tests
+            data = "some data";
+            successObj = failureObj = null;
+            request = Ext.lib.Ajax.request("GET", "http://foo",
+                {
+                    success: success,
+                    failure: failure,
+                    argument: argument,
+                    scope: scope
+                },
+                data,
+                {
+                    headers: headers
+                }
+            );
+            request.readyState = OpenLayers.Request.XMLHttpRequest.DONE;
+            request.status = 400;
+            request.onreadystatechange();
+            t.eq(failureObj.scope, scope,
+                 "failure cb called with proper scope");
+            t.eq(failureObj.arg, argument,
+                 "failure cb called with proper argument");
+
+            // test xmlData
+            // 1 test
+            data = "some data";
+            _srh = proto.setRequestHeader; 
+            proto.setRequestHeader = function(k, v) {
+                t.ok(k == "Content-Type" && v == "text/xml",
+                     "Content-Type header properly set");
+            };
+            request = Ext.lib.Ajax.request("GET", "http://foo",
+                {
+                },
+                null,
+                {
+                    xmlData: data
+                }
+            );
+            proto.setRequestHeader = _srh;
+
+            // test jsonData
+            // 1 test
+            data = "some data";
+            _srh = proto.setRequestHeader; 
+            proto.setRequestHeader = function(k, v) {
+                t.ok(k == "Content-Type" && v == "application/json",
+                     "Content-Type header properly set");
+            };
+            request = Ext.lib.Ajax.request("GET", "http://foo",
+                {
+                },
+                null,
+                {
+                    jsonData: data
+                }
+            );
+            proto.setRequestHeader = _srh;
+
+            /*
+             * Teardown
+             */
+            teardown();
+        }
+
+        function test_serializeForm(t) {
+            t.plan(1);
+
+            var expect = "key1=val1&key2=val2";
+            var result = Ext.lib.Ajax.serializeForm(Ext.get("form").dom);
+            t.eq(result, expect,
+                 "serializeForm returns expected result (" + expect + ")");
+        }
+
+        function test_formPost(t) {
+            t.plan(1);
+            var proto = OpenLayers.Request.XMLHttpRequest.prototype;
+            proto.setRequestHeader = function(k, v) {
+                t.ok(k == "Content-Type" && v == "application/x-www-form-urlencoded",
+                     "Content-Type header properly set for form POST");
+            };
+            Ext.Ajax.request({
+                failure: function(response) { },
+                method: 'POST',
+                success: function(response) { },
+                params: { username: 'bart', password: 'xx'},
+                url: 'foo.php'
+            });
+        }
+    </script>
+  <body>
+  <form id="form">
+      <input type="text" name="key1" value="val1"></input>
+      <input type="checkbox" name="key2" value="val2" checked="checked"></input>
+  </form>
+  </body>
+</html>

Modified: sandbox/ahocevar/playground/trunk/geoext/tests/list-tests.html
===================================================================
--- sandbox/ahocevar/playground/trunk/geoext/tests/list-tests.html	2009-11-26 22:59:44 UTC (rev 1504)
+++ sandbox/ahocevar/playground/trunk/geoext/tests/list-tests.html	2009-11-27 09:20:18 UTC (rev 1505)
@@ -1,4 +1,6 @@
 <ul id="testlist">
+  <li>lib/overrides/override-ext-ajax.html</li>
+  <li>lib/GeoExt/data/AttributeReader.html</li>
   <li>lib/GeoExt/data/FeatureRecord.html</li>
   <li>lib/GeoExt/data/FeatureReader.html</li>
   <li>lib/GeoExt/data/FeatureStore.html</li>
@@ -7,14 +9,28 @@
   <li>lib/GeoExt/data/LayerStore.html</li>
   <li>lib/GeoExt/data/ScaleStore.html</li>
   <li>lib/GeoExt/data/ProtocolProxy.html</li>
+  <li>lib/GeoExt/data/WFSCapabilitiesReader.html</li>
   <li>lib/GeoExt/data/WMSCapabilitiesReader.html</li>
+  <li>lib/GeoExt/data/WMSDescribeLayerReader.html</li>
+  <li>lib/GeoExt/data/WMCReader.html</li>
+  <li>lib/GeoExt/widgets/Action.html</li>
+  <li>lib/GeoExt/widgets/FeatureRenderer.html</li>
+  <li>lib/GeoExt/widgets/LayerOpacitySlider.html</li>
   <li>lib/GeoExt/widgets/MapPanel.html</li>
   <li>lib/GeoExt/widgets/Popup.html</li>
   <li>lib/GeoExt/widgets/form.html</li>
   <li>lib/GeoExt/widgets/form/SearchAction.html</li>
   <li>lib/GeoExt/widgets/form/BasicForm.html</li>
   <li>lib/GeoExt/widgets/form/FormPanel.html</li>
+  <li>lib/GeoExt/widgets/tree/BaseLayerContainer.html</li>
+  <li>lib/GeoExt/widgets/tree/LayerContainer.html</li>
+  <li>lib/GeoExt/widgets/tree/LayerLoader.html</li>
   <li>lib/GeoExt/widgets/tree/LayerNode.html</li>
-  <li>lib/GeoExt/widgets/tree/LayerContainer.html</li>
+  <li>lib/GeoExt/widgets/tree/LayerParamLoader.html</li>
+  <li>lib/GeoExt/widgets/tree/LayerParamNode.html</li>
+  <li>lib/GeoExt/widgets/LegendImage.html</li>
   <li>lib/GeoExt/widgets/LegendPanel.html</li>
+  <li>lib/GeoExt/widgets/LegendWMS.html</li>
+  <li>lib/GeoExt/widgets/ZoomSlider.html</li>
+  <li>lib/GeoExt/widgets/grid/FeatureSelectionModel.html</li>
 </ul>



More information about the Commits mailing list