[Commits] r1303 - in sandbox/camptocamp/geobretagne: examples lib lib/GeoExt/data lib/GeoExt/widgets lib/GeoExt/widgets/tree tests tests/lib/GeoExt/data tests/lib/GeoExt/widgets tests/lib/GeoExt/widgets/tree

commits at geoext.org commits at geoext.org
Wed Aug 5 13:24:10 CEST 2009


Author: elemoine
Date: 2009-08-05 13:24:10 +0200 (Wed, 05 Aug 2009)
New Revision: 1303

Added:
   sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeReader.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeStore.js
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.html
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.js
Modified:
   sandbox/camptocamp/geobretagne/examples/attributes.html
   sandbox/camptocamp/geobretagne/examples/attributes.js
   sandbox/camptocamp/geobretagne/examples/layeropacityslider.html
   sandbox/camptocamp/geobretagne/examples/layeropacityslider.js
   sandbox/camptocamp/geobretagne/lib/GeoExt.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/data/FeatureStore.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/data/ProtocolProxy.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/LayerOpacitySlider.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/MapPanel.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerContainer.js
   sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerNode.js
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/FeatureStore.html
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/LayerOpacitySlider.html
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/tree/LayerNode.html
   sandbox/camptocamp/geobretagne/tests/list-tests.html
Log:
svn merge -r1289:HEAD http://svn.geoext.org/core/trunk/geoext .


Modified: sandbox/camptocamp/geobretagne/examples/attributes.html
===================================================================
--- sandbox/camptocamp/geobretagne/examples/attributes.html	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/examples/attributes.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -1,6 +1,6 @@
 <html>
     <head>
-        <title>GeoExt AttributesReader and AttributesStore</title>
+        <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" />
@@ -12,8 +12,8 @@
 
     </head>
     <body>
-        <h1>AttributesReader and AttributesStore</h1>
-        <p>This is example that shows how create an AttributesStore with
+        <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.

Modified: sandbox/camptocamp/geobretagne/examples/attributes.js
===================================================================
--- sandbox/camptocamp/geobretagne/examples/attributes.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/examples/attributes.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -10,7 +10,7 @@
 Ext.onReady(function() {
     
     // create a new attributes store
-    store = new GeoExt.data.AttributesStore({
+    store = new GeoExt.data.AttributeStore({
         url: "data/describe_feature_type.xml"
     });
     store.load();

Modified: sandbox/camptocamp/geobretagne/examples/layeropacityslider.html
===================================================================
--- sandbox/camptocamp/geobretagne/examples/layeropacityslider.html	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/examples/layeropacityslider.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -7,6 +7,11 @@
         <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>
@@ -18,14 +23,22 @@
         LayerOpacitySliderTip, which will show the opacity value while dragging
         the slider (the content is configurable).<p>
 
-        <p>In this example, the slider below the map is in aggressive mode: the
-        opacity is changed as soon as the slider is moved. The slider into the
-        map is not aggressive: the opacity is changed when the slider is
+        <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="map-container"></div>
+            <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>

Modified: sandbox/camptocamp/geobretagne/examples/layeropacityslider.js
===================================================================
--- sandbox/camptocamp/geobretagne/examples/layeropacityslider.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/examples/layeropacityslider.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -6,7 +6,7 @@
  * of the license.
  */
 
-var panel, wms, slider;
+var panel1, panel2, wms, slider;
 
 Ext.onReady(function() {
     
@@ -17,9 +17,9 @@
     );
 
     // create a map panel with an embedded slider
-    panel = new GeoExt.MapPanel({
-        title: "Map",
-        renderTo: "map-container",
+    panel1 = new GeoExt.MapPanel({
+        title: "Map 1",
+        renderTo: "map1-container",
         height: 300,
         width: 400,
         map: {
@@ -37,7 +37,6 @@
             plugins: new GeoExt.LayerOpacitySliderTip()
         }]
     });
-    
     // create a separate slider bound to the map but displayed elsewhere
     slider = new GeoExt.LayerOpacitySlider({
         layer: wms,
@@ -45,6 +44,47 @@
         width: 200,
         isFormField: true,
         fieldLabel: "opacity",
-        renderTo: document.body
+        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
+        })
+    });
+
 });

Copied: sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeReader.js (from rev 1302, core/trunk/geoext/lib/GeoExt/data/AttributeReader.js)
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeReader.js	                        (rev 0)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeReader.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -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/camptocamp/geobretagne/lib/GeoExt/data/AttributeStore.js (from rev 1302, core/trunk/geoext/lib/GeoExt/data/AttributeStore.js)
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeStore.js	                        (rev 0)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/data/AttributeStore.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -0,0 +1,59 @@
+/**
+ * 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) {
+    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);
\ No newline at end of file

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt/data/FeatureStore.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/data/FeatureStore.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/data/FeatureStore.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -261,7 +261,9 @@
                 // endEdit
                 record.set("state", feature.state);
                 record.set("fid", feature.fid);
-                record.set("feature", feature);
+                // 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;
@@ -369,16 +371,25 @@
      */
     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) {
-                            feature.attributes[field.mapping || field.name] =
-                                record.get(field.name);
+                            var key = field.mapping || field.name;
+                            if (!defaultFields.containsKey(key)) {
+                                attributes[key] = record.get(field.name);
+                            }
                         }
                     );
                     this._updating = true;

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt/data/ProtocolProxy.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/data/ProtocolProxy.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/data/ProtocolProxy.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -14,8 +14,8 @@
 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

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/LayerOpacitySlider.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/LayerOpacitySlider.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/LayerOpacitySlider.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -65,9 +65,24 @@
 
     /** 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
@@ -75,6 +90,14 @@
      */
     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.
@@ -82,17 +105,27 @@
      */
     aggressive: false,
 
-    /** private: property[minValue]
-     *  ``Number``
-     *  The minimum slider value, layer is fully transparent
+    /** 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.
      */
-    minValue: 0,
+    changeVisibility: false,
 
-    /** private: property[maxValue]
+    /** api: config[value]
      *  ``Number``
-     *  The maximum slider value, layer is fully opaque.
+     *  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.
      */
-    maxValue: 100,
+    value: null,
 
     /** private: method[constructor]
      *  Construct the component.
@@ -104,7 +137,17 @@
             } 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);
     },
@@ -115,35 +158,93 @@
     initComponent: function() {
         // set the slider initial value
         if (this.layer && this.layer.opacity !== null) {
-            this.value = parseInt(this.layer.opacity * 100);
-        } else {
-            // assume that the layer has no opacity
-            this.value = 100;
+            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.opacityChanged, this, {
+            this.on('change', this.changeLayerOpacity, this, {
                 buffer: this.delay
             });
         } else {
-            this.on('changecomplete', this.opacityChanged, this);
+            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[opacityChanged]
+    /** private: method[changeLayerOpacity]
      *  :param slider: :class:`GeoExt.LayerOpacitySlider`
      *  :param value: ``Number`` The slider value
      *
      *  Updates the ``OpenLayers.Layer`` opacity value.
      */
-    opacityChanged: function(slider, 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`
      *

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/MapPanel.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/MapPanel.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/MapPanel.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -140,16 +140,7 @@
      */
     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 || this.zoom != null) {
                 // both do not have to be defined

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerContainer.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerContainer.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerContainer.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -20,7 +20,7 @@
 /** 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 defaults to
  *      "gx-tree-layer-icon".
@@ -59,7 +59,7 @@
         });
         this.loader = config.loader instanceof GeoExt.tree.LayerLoader ?
             config.loader :
-            new GeoExt.tree.LayerLoader(Ext.applyIf(config.loader, {
+            new GeoExt.tree.LayerLoader(Ext.applyIf(config.loader || {}, {
                 store: config.layerStore
             }));
         

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerNode.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerNode.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/tree/LayerNode.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -34,22 +34,25 @@
         if (a.checked === undefined) {
             a.checked = this.node.layer.getVisibility();
         }
-        GeoExt.tree.LayerNodeUI.superclass.render.call(this, bulkRender);
+        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,
+            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(this.checkbox,
+            var radio = Ext.DomHelper.insertAfter(cb,
                 ['<input type="radio" name="', a.checkedGroup,
-                '_checkbox" class="', this.checkbox.className, 
-                this.checkbox.checked ? '" checked="checked"' : '',
+                '_checkbox" class="', cb.className,
+                cb.checked ? '" checked="checked"' : '',
                 '"></input>'].join(""));
-            Ext.get(this.checkbox).remove();
+            radio.defaultChecked = cb.defaultChecked;
+            Ext.get(cb).remove();
             this.checkbox = radio;
         }
+        this.enforceOneVisible();
     },
     
     /** private: method[onClick]
@@ -59,34 +62,85 @@
         if (e.getTarget('.gx-tree-layer-radio', 1)) {
             this.fireEvent("radiochange", this.node);
         } else if(e.getTarget('.x-tree-node-cb', 1)) {
-            GeoExt.tree.LayerNodeUI.superclass.onCheckChange.call(this);
+            this.onCheckChange();
         } 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) {
-        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());
+        if(!this._visibilityChanging) {
+            this._visibilityChanging = true;
+            
+            // make sure we do not hide the checked layer from a checkedGroup
+            value = (value === undefined ? !this.isChecked() : value) ||
+                    (this.isChecked() && !!this.node.attributes.checkedGroup);
+            GeoExt.tree.LayerNodeUI.superclass.toggleCheck.call(this, value);
+            
+            this.enforceOneVisible();
+
+            delete this._visibilityChanging;
         }
-        node.visibilityChanging = false;
     },
     
+    /** private: method[enforceOneVisible]
+     * 
+     *  Makes sure that only one layer is visible if checkedGroup is set.
+     *  This can only work when ``layer.setVisibility()`` does not trigger
+     *  ``this.toggleCheck()``. If it does, ``this._visibilityChanging`` has
+     *  to be set to true before calling this method.
+     */
+    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) {
+                        // toggleCheck won't be called (_visibilityChanging
+                        // set to true when we are called from toggleCheck(),
+                        // and layer visibility handler is not yet set when we
+                        // are called from render()), so we synchronize the
+                        // button state manually
+                        ui.checkbox.defaultChecked = false;
+                        ui.checkbox.checked = false;
+                        l.setVisibility(false);
+                    }
+                }
+            });
+            // enforce "at least one visible"
+            if(checkedCount === 0 && attributes.checked == false) {
+                var ui = this.node.getUI();
+                // toggleCheck won't be called (_visibilityChanging set to
+                // true when we are called from toggleCheck(), and layer
+                // visibility handler is not yet set when we are called from
+                // render()), so we synchronize the button state manually
+                ui.checkbox.defaultChecked = true;
+                ui.checkbox.checked = true;
+                layer.setVisibility(true);
+            }
+        }
+    },
+    
     /** private: method[destroy]
      */
     destroy: function() {
         delete this.radio;
-        GeoExt.tree.LayerNodeUI.superclass.destroy.call(this);
+        GeoExt.tree.LayerNodeUI.superclass.destroy.apply(this, arguments);
     }
 });
 
+
 /** api: (define)
  *  module = GeoExt.tree
  *  class = LayerNode
@@ -156,21 +210,15 @@
      */
     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.iconCls = typeof config.iconCls == "undefined" &&
-            !config.children ? "layer-icon" : config.iconCls;
+        if(!config.iconCls && !config.children) {
+            config.iconCls = "gx-tree-layer-icon";
+        }
         
         this.defaultUI = this.defaultUI || GeoExt.tree.LayerNodeUI;
         this.addEvents(
@@ -214,6 +262,13 @@
             
             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;
                 }
@@ -232,7 +287,7 @@
                 this.addStoreEventHandlers(layer);
             }            
         }
-        GeoExt.tree.LayerNode.superclass.render.call(this, bulkRender);
+        GeoExt.tree.LayerNode.superclass.render.apply(this, arguments);
     },
     
     /** private: method[addVisibilityHandlers]
@@ -254,10 +309,7 @@
      *  handler for visibilitychanged events on the layer
      */
     onLayerVisibilityChanged: function() {
-        if(!this.visibilityChanging &&
-                this.attributes.checked != this.layer.getVisibility()) {
-            this.getUI().toggleCheck(this.layer.getVisibility());
-        }
+        this.getUI().toggleCheck(this.layer.getVisibility());
     },
     
     /** private: method[onCheckChange]
@@ -267,10 +319,14 @@
      *  handler for checkchange events 
      */
     onCheckChange: function(node, checked) {
-        if (checked && this.layer.isBaseLayer && this.layer.map) {
-            this.layer.map.setBaseLayer(this.layer);
+        if(checked != this.layer.getVisibility()) {
+            var layer = this.layer;
+            if(checked && layer.isBaseLayer && layer.map) {
+                layer.map.setBaseLayer(layer);
+            } else {
+                layer.setVisibility(checked);
+            }
         }
-        this.layer.setVisibility(checked);
     },
     
     /** private: method[addStoreEventHandlers]
@@ -299,12 +355,13 @@
             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(bulkRender);
-                return;
+                this.render();
+                break;
             }
         }
     },
@@ -369,7 +426,7 @@
         delete this.layerStore;
         this.un("checkchange", this.onCheckChange, this);
 
-        GeoExt.tree.LayerNode.superclass.destroy.call(this);
+        GeoExt.tree.LayerNode.superclass.destroy.apply(this, arguments);
     }
 });
 

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt.js	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -66,6 +66,8 @@
         var jsfiles = new Array(
             "GeoExt/data/AttributesReader.js",
             "GeoExt/data/AttributesStore.js",
+            "GeoExt/data/AttributeReader.js",
+            "GeoExt/data/AttributeStore.js",
             "GeoExt/data/FeatureRecord.js",
             "GeoExt/data/FeatureReader.js",
             "GeoExt/data/FeatureStore.js",

Copied: sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.html (from rev 1302, core/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.html)
===================================================================
--- sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.html	                        (rev 0)
+++ sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -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/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.js (from rev 1302, core/trunk/geoext/tests/lib/GeoExt/data/AttributeReader.js)
===================================================================
--- sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.js	                        (rev 0)
+++ sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/AttributeReader.js	2009-08-05 11:24:10 UTC (rev 1303)
@@ -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/camptocamp/geobretagne/tests/lib/GeoExt/data/FeatureStore.html
===================================================================
--- sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/FeatureStore.html	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/tests/lib/GeoExt/data/FeatureStore.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -187,19 +187,28 @@
         }
 
         function test_featuremodified_update(t) {
-            t.plan(6);
+            t.plan(8);
 
             /*
              * Set up
              */
             var feature, id, layer, store, recordType, record;
-
+            
             feature = new OpenLayers.Feature.Vector(null, {
                 foo: "foo",
                 bar: "bar"
             });
 
             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"}
@@ -244,6 +253,19 @@
 
             t.eq(layer.getFeatureById(id).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");
 
          }
     </script> 

Modified: sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/LayerOpacitySlider.html
===================================================================
--- sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/LayerOpacitySlider.html	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/LayerOpacitySlider.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -9,7 +9,7 @@
     <script type="text/javascript">
 
         function test_constructor(t) {
-            t.plan(2);
+            t.plan(8);
 
             var record, store, slider;
             var layer = new OpenLayers.Layer("a");
@@ -23,13 +23,138 @@
             t.eq(slider.layer.id, record.id, "layer parameter is a GeoExt.data.LayerRecord");
             slider.destroy();
 
-            var slider = new GeoExt.LayerOpacitySlider({
+            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);
 
@@ -85,6 +210,79 @@
             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>

Modified: sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/tree/LayerNode.html
===================================================================
--- sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/tree/LayerNode.html	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/tree/LayerNode.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -26,7 +26,7 @@
         
         function test_render(t) {
             
-            t.plan(8);
+            t.plan(9);
             
             var layer = new OpenLayers.Layer("foo");
             
@@ -64,16 +64,93 @@
                 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 correclty according to checkedGroup");
+                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(2);
 

Modified: sandbox/camptocamp/geobretagne/tests/list-tests.html
===================================================================
--- sandbox/camptocamp/geobretagne/tests/list-tests.html	2009-08-04 21:05:17 UTC (rev 1302)
+++ sandbox/camptocamp/geobretagne/tests/list-tests.html	2009-08-05 11:24:10 UTC (rev 1303)
@@ -1,5 +1,6 @@
 <ul id="testlist">
   <li>lib/GeoExt/adapter/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>



More information about the Commits mailing list