Ticket #1606: strategy.patch
| File strategy.patch, 62.4 kB (added by tschaub, 1 year ago) |
|---|
-
tests/Strategy/Cluster.html
old new 1 <html> 2 <head> 3 <script src="../../lib/OpenLayers.js"></script> 4 <script type="text/javascript"> 5 6 function test_activate(t) { 7 t.plan(2); 8 9 var strategy = new OpenLayers.Strategy.Cluster(); 10 t.eq(strategy.active, false, "not active after construction"); 11 12 var layer = new OpenLayers.Layer.Vector("Vector Layer", { 13 strategies: [strategy] 14 }); 15 var map = new OpenLayers.Map('map'); 16 map.addLayer(layer); 17 18 t.eq(strategy.active, true, "active after adding to map"); 19 } 20 21 function test_clusters(t) { 22 t.plan(10); 23 24 function featuresEq(got, exp) { 25 var eq = false; 26 if(got instanceof Array && exp instanceof Array) { 27 if(got.length === exp.length) { 28 for(var i=0; i<got.length; ++i) { 29 if(got[i] !== exp[i]) { 30 console.log(got[i], exp[i]); 31 break; 32 } 33 } 34 eq = (i == got.length); 35 } 36 } 37 return eq; 38 } 39 40 var strategy = new OpenLayers.Strategy.Cluster(); 41 var layer = new OpenLayers.Layer.Vector("Vector Layer", { 42 strategies: [strategy], 43 isBaseLayer: true 44 }); 45 var map = new OpenLayers.Map('map', { 46 resolutions: [4, 2, 1], 47 maxExtent: new OpenLayers.Bounds(-40, -40, 40, 40) 48 }); 49 map.addLayer(layer); 50 51 // create features in a line, 1 unit apart 52 var features = new Array(80); 53 for(var i=0; i<80; ++i) { 54 features[i] = new OpenLayers.Feature.Vector( 55 new OpenLayers.Geometry.Point(-40 + i, 0) 56 ); 57 } 58 59 map.setCenter(new OpenLayers.LonLat(0, 0), 0); 60 layer.addFeatures(features); 61 62 // resolution 4 63 // threshold: 4 * 20 = 80 units 64 // one cluster 65 t.eq(layer.features.length, 1, "[4] layer has one cluster"); 66 t.ok(featuresEq(layer.features[0].cluster, features), "[4] cluster includes all features"); 67 68 // resolution 2 69 // threshold: 2 * 20 = 40 units 70 // two clusters (41 and 39) - first cluster includes all features within 40 units of the first (0-40 or 41 features) 71 map.zoomIn(); 72 t.eq(layer.features.length, 2, "[2] layer has two clusters"); 73 t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 41)), "[2] first cluster includes first 41 features"); 74 t.ok(featuresEq(layer.features[1].cluster, features.slice(41, 80)), "[2] second cluster includes last 39 features"); 75 76 // resolution 1 77 // threshold: 1 * 20 = 20 units 78 // four clusters (21, 21, 21, and 17) 79 map.zoomIn(); 80 t.eq(layer.features.length, 4, "[1] layer has four clusters"); 81 t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 21)), "[1] first cluster includes first 21 features"); 82 t.ok(featuresEq(layer.features[1].cluster, features.slice(21, 42)), "[2] second cluster includes second 21 features"); 83 t.ok(featuresEq(layer.features[2].cluster, features.slice(42, 63)), "[2] third cluster includes third 21 features"); 84 t.ok(featuresEq(layer.features[3].cluster, features.slice(63, 80)), "[2] fourth cluster includes last 17 features"); 85 } 86 87 function test_deactivate(t) { 88 t.plan(2); 89 90 var strategy = new OpenLayers.Strategy.Cluster(); 91 var layer = new OpenLayers.Layer.Vector("Vector Layer", { 92 strategies: [strategy] 93 }); 94 var map = new OpenLayers.Map('map'); 95 map.addLayer(layer); 96 97 t.eq(strategy.active, true, "active after adding to map"); 98 99 map.removeLayer(layer); 100 t.eq(strategy.active, false, "not active after removing from map"); 101 } 102 103 </script> 104 </head> 105 <body> 106 <div id="map" style="width: 400px; height: 200px" /> 107 </body> 108 </html> -
tests/Strategy/Paging.html
old new 1 <html> 2 <head> 3 <script src="../../lib/OpenLayers.js"></script> 4 <script type="text/javascript"> 5 6 function test_activate(t) { 7 t.plan(2); 8 9 var strategy = new OpenLayers.Strategy.Paging(); 10 t.eq(strategy.active, false, "not active after construction"); 11 12 var layer = new OpenLayers.Layer.Vector("Vector Layer", { 13 strategies: [strategy] 14 }); 15 var map = new OpenLayers.Map('map'); 16 map.addLayer(layer); 17 18 t.eq(strategy.active, true, "active after adding to map"); 19 } 20 21 function test_paging(t) { 22 t.plan(18); 23 24 var strategy = new OpenLayers.Strategy.Paging(); 25 var layer = new OpenLayers.Layer.Vector("Vector Layer", { 26 strategies: [strategy], 27 drawFeature: function() {} 28 }); 29 var map = new OpenLayers.Map('map'); 30 map.addLayer(layer); 31 32 var features = new Array(25); 33 for(var i=0; i<features.length; ++i) { 34 features[i] = {destroy: function() {}}; 35 } 36 37 function featuresEq(got, exp) { 38 var eq = false; 39 if(got instanceof Array && exp instanceof Array) { 40 if(got.length === exp.length) { 41 for(var i=0; i<got.length; ++i) { 42 if(got[i] !== exp[i]) { 43 console.log(got[i], exp[i]); 44 break; 45 } 46 } 47 eq = (i == got.length); 48 } 49 } 50 return eq; 51 } 52 53 var len = strategy.pageLength(); 54 t.eq(len, 10, "page length defaults to 10"); 55 56 // add 25 features to the layer 57 layer.addFeatures(features); 58 t.eq(strategy.features.length, features.length, "strategy caches all features"); 59 t.eq(layer.features.length, len, "layer gets one page of features"); 60 t.ok(featuresEq(layer.features, features.slice(0, len)), "layer gets first page initially"); 61 t.eq(strategy.pageNum(), 0, "strategy reports 0 based page number"); 62 t.eq(strategy.pageCount(), Math.ceil(features.length / len), "strategy reports correct number of pages"); 63 64 // load next page of features 65 var changed = strategy.pageNext(); 66 t.eq(changed, true, "(1) strategy reports change"); 67 t.eq(strategy.pageNum(), 1, "second page"); 68 t.ok(featuresEq(layer.features, features.slice(len, 2*len)), "layer has second page of features"); 69 70 // load next page of features (half page) 71 changed = strategy.pageNext(); 72 t.eq(changed, true, "(2) strategy reports change"); 73 t.eq(strategy.pageNum(), 2, "third page"); 74 75 // try to change forward again 76 changed = strategy.pageNext(); 77 t.eq(changed, false, "strategy reports no change"); 78 t.eq(layer.features.length, features.length % len, "layer has partial page"); 79 t.ok(featuresEq(layer.features, features.slice(2*len, 3*len)), "layer has third page of features"); 80 t.eq(strategy.pageNum(), 2, "still on third page"); 81 82 // change back a page 83 changed = strategy.pagePrevious(); 84 t.eq(changed, true, "(3) strategy reports change"); 85 t.eq(strategy.pageNum(), 1, "back on second page"); 86 t.ok(featuresEq(layer.features, features.slice(len, 2*len)), "layer has second page of features again"); 87 88 layer.destroy(); 89 90 } 91 92 function test_deactivate(t) { 93 t.plan(2); 94 95 var strategy = new OpenLayers.Strategy.Paging(); 96 var layer = new OpenLayers.Layer.Vector("Vector Layer", { 97 strategies: [strategy] 98 }); 99 var map = new OpenLayers.Map('map'); 100 map.addLayer(layer); 101 102 t.eq(strategy.active, true, "active after adding to map"); 103 104 map.removeLayer(layer); 105 t.eq(strategy.active, false, "not active after removing from map"); 106 } 107 108 </script> 109 </head> 110 <body> 111 <div id="map" style="width: 400px; height: 200px" /> 112 </body> 113 </html> -
tests/list-tests.html
old new 124 124 <li>Request/XMLHttpRequest.html</li> 125 125 <li>Rule.html</li> 126 126 <li>Strategy.html</li> 127 <li>Strategy/Cluster.html</li> 127 128 <li>Strategy/Fixed.html</li> 129 <li>Strategy/Paging.html</li> 128 130 <li>Strategy/BBOX.html</li> 129 131 <li>Style.html</li> 130 132 <li>StyleMap.html</li> -
lib/OpenLayers/Strategy/Paging.js
old new 1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD 2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the 3 * full text of the license. */ 4 5 /** 6 * @requires OpenLayers/Strategy.js 7 */ 8 9 /** 10 * Class: OpenLayers.Strategy.Paging 11 * Strategy for vector feature paging 12 * 13 * Inherits from: 14 * - <OpenLayers.Strategy> 15 */ 16 OpenLayers.Strategy.Paging = OpenLayers.Class(OpenLayers.Strategy, { 17 18 /** 19 * Property: layer 20 * {<OpenLayers.Layer.Vector>} 21 */ 22 layer: null, 23 24 /** 25 * Property: features 26 * {Array(<OpenLayers.Feature.Vector>)} Cached features. 27 */ 28 features: null, 29 30 /** 31 * Property: length 32 * {Integer} Number of features per page. Default is 10. 33 */ 34 length: 10, 35 36 /** 37 * Property: num 38 * {Integer} The currently displayed page number. 39 */ 40 num: null, 41 42 /** 43 * Property: paging 44 * {Boolean} The strategy is currently changing pages. 45 */ 46 paging: false, 47 48 /** 49 * Constructor: OpenLayers.Strategy.Paging 50 * Create a new paging strategy. 51 * 52 * Parameters: 53 * options - {Object} Optional object whose properties will be set on the 54 * instance. 55 */ 56 initialize: function(options) { 57 OpenLayers.Strategy.prototype.initialize.apply(this, [options]); 58 }, 59 60 /** 61 * Method: activate 62 * Activate the strategy. Register any listeners, do appropriate setup. 63 * 64 * Returns: 65 * {Boolean} The strategy was successfully activated. 66 */ 67 activate: function() { 68 var activated = OpenLayers.Strategy.prototype.activate.call(this); 69 if(activated) { 70 this.layer.events.on({ 71 "beforefeaturesadded": this.cacheFeatures, 72 scope: this 73 }); 74 } 75 return activated; 76 }, 77 78 /** 79 * Method: deactivate 80 * Deactivate the strategy. Unregister any listeners, do appropriate 81 * tear-down. 82 * 83 * Returns: 84 * {Boolean} The strategy was successfully deactivated. 85 */ 86 deactivate: function() { 87 var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); 88 if(deactivated) { 89 this.clearCache(); 90 this.layer.events.un({ 91 "beforefeaturesadded": this.cacheFeatures, 92 scope: this 93 }); 94 } 95 return deactivated; 96 }, 97 98 /** 99 * Method: cacheFeatures 100 * Cache features before they are added to the layer. 101 */ 102 cacheFeatures: function(event) { 103 if(!this.paging) { 104 this.clearCache(); 105 this.features = event.features; 106 this.pageNext(event); 107 } 108 }, 109 110 /** 111 * Method: clearCache 112 * Clear out the cached features. This destroys features, assuming 113 * nothing else has a reference. 114 */ 115 clearCache: function() { 116 if(this.features) { 117 for(var i=0; i<this.features.length; ++i) { 118 this.features[i].destroy(); 119 } 120 } 121 this.features = null; 122 this.num = null; 123 }, 124 125 /** 126 * Method: pageCount 127 * Get the total count of pages given the current cache of features. 128 * 129 * Returns: 130 * {Integer} The page count. 131 */ 132 pageCount: function() { 133 var numFeatures = this.features ? this.features.length : 0; 134 return Math.ceil(numFeatures / this.length); 135 }, 136 137 /** 138 * Method: pageNum 139 * Get the zero based page number. 140 * 141 * Returns: 142 * {Integer} The current page number being displayed. 143 */ 144 pageNum: function() { 145 return this.num; 146 }, 147 148 /** 149 * Method: pageLength 150 * Gets or sets page length. 151 * 152 * Parameters: 153 * newLength: {Integer} Optional length to be set. 154 * 155 * Returns: 156 * {Integer} The length of a page (number of features per page). 157 */ 158 pageLength: function(newLength) { 159 if(newLength && newLength > 0) { 160 this.length = newLength; 161 } 162 return this.length; 163 }, 164 165 /** 166 * Method: pageNext 167 * Display the next page of features. 168 * 169 * Returns: 170 * {Boolean} A new page was displayed. 171 */ 172 pageNext: function(event) { 173 var changed = false; 174 if(this.features) { 175 if(this.num === null) { 176 this.num = -1; 177 } 178 var start = (this.num + 1) * this.length; 179 changed = this.page(start, event); 180 } 181 return changed; 182 }, 183 184 /** 185 * Method: pagePrevious 186 * Display the previous page of features. 187 * 188 * Returns: 189 * {Boolean} A new page was displayed. 190 */ 191 pagePrevious: function() { 192 var changed = false; 193 if(this.features) { 194 if(this.num === null) { 195 this.num = this.pageCount(); 196 } 197 var start = (this.num - 1) * this.length; 198 changed = this.page(start); 199 } 200 return changed; 201 }, 202 203 /** 204 * Method: page 205 * Display the page starting at the given index from the cache. 206 * 207 * Returns: 208 * {Boolean} A new page was displayed. 209 */ 210 page: function(start, event) { 211 var changed = false; 212 if(this.features) { 213 if(start >= 0 && start < this.features.length) { 214 var num = Math.floor(start / this.length); 215 if(num != this.num) { 216 this.paging = true; 217 var features = this.features.slice(start, start + this.length); 218 this.layer.removeFeatures(this.layer.features); 219 this.num = num; 220 // modify the event if any 221 if(event && event.features) { 222 // this.was called by an event listener 223 event.features = features; 224 } else { 225 // this was called directly on the strategy 226 this.layer.addFeatures(features); 227 } 228 this.paging = false; 229 changed = true; 230 } 231 } 232 } 233 return changed; 234 }, 235 236 CLASS_NAME: "OpenLayers.Strategy.Paging" 237 }); -
lib/OpenLayers/Strategy/Cluster.js
old new 1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD 2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the 3 * full text of the license. */ 4 5 /** 6 * @requires OpenLayers/Strategy.js 7 */ 8 9 /** 10 * Class: OpenLayers.Strategy.Cluster 11 * Strategy for vector feature clustering. 12 * 13 * Inherits from: 14 * - <OpenLayers.Strategy> 15 */ 16 OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, { 17 18 /** 19 * Property: layer 20 * {<OpenLayers.Layer.Vector>} 21 */ 22 layer: null, 23 24 /** 25 * Property: distance 26 * {Integer} Pixel distance between features that should be considered a 27 * single cluster. Default is 20 pixels. 28 */ 29 distance: 20, 30 31 /** 32 * Property: features 33 * {Array(<OpenLayers.Feature.Vector>)} Cached features. 34 */ 35 features: null, 36 37 /** 38 * Property: clusters 39 * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters. 40 */ 41 clusters: null, 42 43 /** 44 * Property: clustering 45 * {Boolean} The strategy is currently clustering features. 46 */ 47 clustering: false, 48 49 /** 50 * Property: resolution 51 * {Float} The resolution (map units per pixel) of the current cluster set. 52 */ 53 resolution: null, 54 55 /** 56 * Constructor: OpenLayers.Strategy.Cluster 57 * Create a new clustering strategy. 58 * 59 * Parameters: 60 * options - {Object} Optional object whose properties will be set on the 61 * instance. 62 */ 63 initialize: function(options) { 64 OpenLayers.Strategy.prototype.initialize.apply(this, [options]); 65 }, 66 67 /** 68 * Method: activate 69 * Activate the strategy. Register any listeners, do appropriate setup. 70 * 71 * Returns: 72 * {Boolean} The strategy was successfully activated. 73 */ 74 activate: function() { 75 var activated = OpenLayers.Strategy.prototype.activate.call(this); 76 if(activated) { 77 this.layer.events.on({ 78 "beforefeaturesadded": this.cacheFeatures, 79 scope: this 80 }); 81 this.layer.map.events.on({"zoomend": this.cluster, scope: this}); 82 } 83 return activated; 84 }, 85 86 /** 87 * Method: deactivate 88 * Deactivate the strategy. Unregister any listeners, do appropriate 89 * tear-down. 90 * 91 * Returns: 92 * {Boolean} The strategy was successfully deactivated. 93 */ 94 deactivate: function() { 95 var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); 96 if(deactivated) { 97 this.clearCache(); 98 this.layer.events.un({ 99 "beforefeaturesadded": this.cacheFeatures, 100 scope: this 101 }); 102 this.layer.map.events.un({"zoomend": this.cluster, scope: this}); 103 } 104 return deactivated; 105 }, 106 107 /** 108 * Method: cacheFeatures 109 * Cache features before they are added to the layer. 110 * 111 * Returns: 112 * {Boolean} False to stop layer from being added to the layer. 113 */ 114 cacheFeatures: function(event) { 115 var propagate = true; 116 if(!this.clustering) { 117 this.clearCache(); 118 this.features = event.features; 119 this.cluster(); 120 propagate = false; 121 } 122 return propagate; 123 }, 124 125 /** 126 * Method: clearCache 127 * Clear out the cached features. This destroys features, assuming 128 * nothing else has a reference. 129 */ 130 clearCache: function() { 131 if(this.features) { 132 for(var i=0; i<this.features.length; ++i) { 133 this.features[i].destroy(); 134 } 135 } 136 this.features = null; 137 }, 138 139 /** 140 * Method: cluster 141 * Cluster features based on some threshold distance. 142 */ 143 cluster: function() { 144 if(this.features) { 145 var resolution = this.layer.getResolution(); 146 if(resolution != this.resolution || !this.clustersExist()) { 147 this.resolution = resolution; 148 var clusters = []; 149 var feature, clustered, cluster; 150 for(var i=0; i<this.features.length; ++i) { 151 feature = this.features[i]; 152 clustered = false; 153 for(var j=0; j<clusters.length; ++j) { 154 cluster = clusters[j]; 155 if(this.shouldCluster(cluster, feature)) { 156 this.addToCluster(cluster, feature); 157 clustered = true; 158 break; 159 } 160 } 161 if(!clustered) { 162 clusters.push(this.createCluster(this.features[i])); 163 } 164 } 165 this.layer.destroyFeatures(); 166 if(clusters.length > 0) { 167 this.clustering = true; 168 // A legitimate feature addition could occur during this 169 // addFeatures call. For clustering to behave well, features 170 // should be removed from a layer before requesting a new batch. 171 this.layer.addFeatures(clusters); 172 this.clustering = false; 173 } 174 this.clusters = clusters; 175 } 176 } 177 }, 178 179 /** 180 * Method: clustersExist 181 * Determine whether calculated clusters are already on the layer. 182 * 183 * Returns: 184 * {Boolean} The calculated clusters are already on the layer. 185 */ 186 clustersExist: function() { 187 var exist = false; 188 if(this.clusters && this.clusters.length > 0 && 189 this.clusters.length == this.layer.features.length) { 190 exist = true; 191 for(var i=0; i<this.clusters.length; ++i) { 192 if(this.clusters[i] != this.layer.features[i]) { 193 exist = false; 194 break; 195 } 196 } 197 } 198 return exist; 199 }, 200 201 /** 202 * Method: shouldCluster 203 * Determine whether to include a feature in a given cluster. 204 * 205 * Parameters: 206 * cluster - {<OpenLayers.Feature.Vector>} A cluster. 207 * feature - {<OpenLayers.Feature.Vector>} A feature. 208 * 209 * Returns: 210 * {Boolean} The feature should be included in the cluster. 211 */ 212 shouldCluster: function(cluster, feature) { 213 var cc = cluster.geometry.getBounds().getCenterLonLat(); 214 var fc = feature.geometry.getBounds().getCenterLonLat(); 215 var distance = ( 216 Math.sqrt( 217 Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2) 218 ) / this.resolution 219 ); 220 return (distance <= this.distance); 221 }, 222 223 /** 224 * Method: addToCluster 225 * Add a feature to a cluster. 226 * 227 * Parameters: 228 * cluster - {<OpenLayers.Feature.Vector>} A cluster. 229 * feature - {<OpenLayers.Feature.Vector>} A feature. 230 */ 231 addToCluster: function(cluster, feature) { 232 cluster.cluster.push(feature); 233 cluster.attributes.count += 1; 234 }, 235 236 /** 237 * Method: createCluster 238 * Given a feature, create a cluster. 239 * 240 * Parameters: 241 * feature - {<OpenLayers.Feature.Vector>} 242 * 243 * Returns: 244 * {<OpenLayers.Feature.Vector>} A cluster. 245 */ 246 createCluster: function(feature) { 247 var center = feature.geometry.getBounds().getCenterLonLat(); 248 var cluster = new OpenLayers.Feature.Vector( 249 new OpenLayers.Geometry.Point(center.lon, center.lat), 250 {count: 1} 251 ); 252 cluster.cluster = [feature]; 253 return cluster; 254 }, 255 256 CLASS_NAME: "OpenLayers.Strategy.Cluster" 257 }); -
lib/OpenLayers/Layer/Vector.js
old new 38 38 * Supported map event types (in addition to those from <OpenLayers.Layer>): 39 39 * - *beforefeatureadded* Triggered before a feature is added. Listeners 40 40 * will receive an object with a *feature* property referencing the 41 * feature to be added. 41 * feature to be added. To stop the feature from being added, a 42 * listener should return false. 43 * - *beforefeaturesadded* Triggered before an array of features is added. 44 * Listeners will receive an object with a *features* property 45 * referencing the feature to be added. To stop the features from 46 * being added, a listener should return false. 42 47 * - *featureadded* Triggered after a feature is added. The event 43 48 * object passed to listeners will have a *feature* property with a 44 49 * reference to the added feature. … … 72 77 * - *refresh* Triggered when something wants a strategy to ask the protocol 73 78 * for a new set of features. 74 79 */ 75 EVENT_TYPES: ["beforefeatureadded", "featureadded", "featuresadded", 80 EVENT_TYPES: ["beforefeatureadded", "beforefeaturesadded", 81 "featureadded", "featuresadded", 76 82 "beforefeatureremoved", "featureremoved", "featuresremoved", 77 83 "beforefeatureselected", "featureselected", "featureunselected", 78 84 "beforefeaturemodified", "featuremodified", "afterfeaturemodified", … … 443 449 } 444 450 445 451 var notify = !options || !options.silent; 452 if(notify) { 453 var event = {features: features}; 454 var ret = this.events.triggerEvent("beforefeaturesadded", event); 455 if(ret === false) { 456 return; 457 } 458 features = event.features; 459 } 460 446 461 447 462 for (var i=0, len=features.length; i<len; i++) { 448 463 if (i != (features.length - 1)) { … … 469 484 } 470 485 471 486 if (notify) { 472 this.events.triggerEvent("beforefeatureadded", { 473 feature: feature 474 }); 487 if(this.events.triggerEvent("beforefeatureadded", 488 {feature: feature}) === false) { 489 continue; 490 }; 475 491 this.preFeatureInsert(feature); 476 492 } 477 493 -
lib/OpenLayers.js
old new 185 185 "OpenLayers/Layer/Vector.js", 186 186 "OpenLayers/Strategy.js", 187 187 "OpenLayers/Strategy/Fixed.js", 188 "OpenLayers/Strategy/Cluster.js", 189 "OpenLayers/Strategy/Paging.js", 188 190 "OpenLayers/Strategy/BBOX.js", 189 191 "OpenLayers/Protocol.js", 190 192 "OpenLayers/Protocol/HTTP.js", -
examples/animator.js
old new 1 /* 2 Animator.js 1.1.9 3 4 This library is released under the BSD license: 5 6 Copyright (c) 2006, Bernard Sumption. All rights reserved. 7 8 Redistribution and use in source and binary forms, with or without 9 modification, are permitted provided that the following conditions are met: 10 11 Redistributions of source code must retain the above copyright notice, this 12 list of conditions and the following disclaimer. Redistributions in binary 13 form must reproduce the above copyright notice, this list of conditions and 14 the following disclaimer in the documentation and/or other materials 15 provided with the distribution. Neither the name BernieCode nor 16 the names of its contributors may be used to endorse or promote products 17 derived from this software without specific prior written permission. 18 19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 23 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 DAMAGE. 30 31 */ 32 33 34 // Applies a sequence of numbers between 0 and 1 to a number of subjects 35 // construct - see setOptions for parameters 36 function Animator(options) { 37 this.setOptions(options); 38 var _this = this; 39 this.timerDelegate = function(){_this.onTimerEvent()}; 40 this.subjects = []; 41 this.target = 0; 42 this.state = 0; 43 this.lastTime = null; 44 }; 45 Animator.prototype = { 46 // apply defaults 47 setOptions: function(options) { 48 this.options = Animator.applyDefaults({ 49 interval: 20, // time between animation frames 50 duration: 400, // length of animation 51 onComplete: function(){}, 52 onStep: function(){}, 53 transition: Animator.tx.easeInOut 54 }, options); 55 }, 56 // animate from the current state to provided value 57 seekTo: function(to) { 58 this.seekFromTo(this.state, to); 59 }, 60 // animate from the current state to provided value 61 seekFromTo: function(from, to) { 62 this.target = Math.max(0, Math.min(1, to)); 63 this.state = Math.max(0, Math.min(1, from)); 64 this.lastTime = new Date().getTime(); 65 if (!this.intervalId) { 66 this.intervalId = window.setInterval(this.timerDelegate, this.options.interval); 67 } 68 }, 69 // animate from the current state to provided value 70 jumpTo: function(to) { 71 this.target = this.state = Math.max(0, Math.min(1, to)); 72 this.propagate(); 73 }, 74 // seek to the opposite of the current target 75 toggle: function() { 76 this.seekTo(1 - this.target); 77 }, 78 // add a function or an object with a method setState(state) that will be called with a number 79 // between 0 and 1 on each frame of the animation 80 addSubject: function(subject) { 81 this.subjects[this.subjects.length] = subject; 82 return this; 83 }, 84 // remove all subjects 85 clearSubjects: function() { 86 this.subjects = []; 87 }, 88 // forward the current state to the animation subjects 89 propagate: function() { 90 var value = this.options.transition(this.state); 91 for (var i=0; i<this.subjects.length; i++) { 92 if (this.subjects[i].setState) { 93 this.subjects[i].setState(value); 94 } else { 95 this.subjects[i](value); 96 } 97 } 98 }, 99 // called once per frame to update the current state 100 onTimerEvent: function() { 101 var now = new Date().getTime(); 102 var timePassed = now - this.lastTime; 103 this.lastTime = now; 104 var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1); 105 if (Math.abs(movement) >= Math.abs(this.state - this.target)) { 106 this.state = this.target; 107 } else { 108 this.state += movement; 109 } 110 111 try { 112 this.propagate(); 113 } finally { 114 this.options.onStep.call(this); 115 if (this.target == this.state) { 116 window.clearInterval(this.intervalId); 117 this.intervalId = null; 118 this.options.onComplete.call(this); 119 } 120 } 121 }, 122 // shortcuts 123 play: function() {this.seekFromTo(0, 1)}, 124 reverse: function() {this.seekFromTo(1, 0)}, 125 // return a string describing this Animator, for debugging 126 inspect: function() { 127 var str = "#<Animator:\n"; 128 for (var i=0; i<this.subjects.length; i++) { 129 str += this.subjects[i].inspect(); 130 } 131 str += ">"; 132 return str; 133 } 134 } 135 // merge the properties of two objects 136 Animator.applyDefaults = function(defaults, prefs) { 137 prefs = prefs || {}; 138 var prop, result = {}; 139 for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop]; 140 return result; 141 } 142 // make an array from any object 143 Animator.makeArray = function(o) { 144 if (o == null) return []; 145 if (!o.length) return [o]; 146 var result = []; 147 for (var i=0; i<o.length; i++) result[i] = o[i]; 148 return result; 149 } 150 // convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!) 151 Animator.camelize = function(string) { 152 var oStringList = string.split('-'); 153 if (oStringList.length == 1) return oStringList[0]; 154 155 var camelizedString = string.indexOf('-') == 0 156 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 157 : oStringList[0]; 158 159 for (var i = 1, len = oStringList.length; i < len; i++) { 160 var s = oStringList[i]; 161 camelizedString += s.charAt(0).toUpperCase() + s.substring(1); 162 } 163 return camelizedString; 164 } 165 // syntactic sugar for creating CSSStyleSubjects 166 Animator.apply = function(el, style, options) { 167 if (style instanceof Array) { 168 return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1])); 169 } 170 return new Animator(options).addSubject(new CSSStyleSubject(el, style)); 171 } 172 // make a transition function that gradually accelerates. pass a=1 for smooth 173 // gravitational acceleration, higher values for an exaggerated effect 174 Animator.makeEaseIn = function(a) { 175 return function(state) { 176 return Math.pow(state, a*2); 177 } 178 } 179 // as makeEaseIn but for deceleration 180 Animator.makeEaseOut = function(a) { 181 return function(state) { 182 return 1 - Math.pow(1 - state, a*2); 183 } 184 } 185 // make a transition function that, like an object with momentum being attracted to a point, 186 // goes past the target then returns 187 Animator.makeElastic = function(bounces) { 188 return function(state) { 189 state = Animator.tx.easeInOut(state); 190 return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state; 191 } 192 } 193 // make an Attack Decay Sustain Release envelope that starts and finishes on the same level 194 // 195 Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) { 196 if (sustainLevel == null) sustainLevel = 0.5; 197 return function(state) { 198 if (state < attackEnd) { 199 return state / attackEnd; 200 } 201 if (state < decayEnd) { 202 return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel)); 203 } 204 if (state < sustainEnd) { 205 return sustainLevel; 206 } 207 return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd))); 208 } 209 } 210 // make a transition function that, like a ball falling to floor, reaches the target and/ 211 // bounces back again 212 Animator.makeBounce = function(bounces) { 213 var fn = Animator.makeElastic(bounces); 214 return function(state) { 215 state = fn(state); 216 return state <= 1 ? state : 2-state; 217 } 218 } 219 220 // pre-made transition functions to use with the 'transition' option 221 Animator.tx = { 222 easeInOut: function(pos){ 223 return ((-Math.cos(pos*Math.PI)/2) + 0.5); 224 }, 225 linear: function(x) { 226 return x; 227 }, 228 easeIn: Animator.makeEaseIn(1.5), 229 easeOut: Animator.makeEaseOut(1.5), 230 strongEaseIn: Animator.makeEaseIn(2.5), 231 strongEaseOut: Animator.makeEaseOut(2.5), 232 elastic: Animator.makeElastic(1), 233 veryElastic: Animator.makeElastic(3), 234 bouncy: Animator.makeBounce(1), 235 veryBouncy: Animator.makeBounce(3) 236 } 237 238 // animates a pixel-based style property between two integer values 239 function NumericalStyleSubject(els, property, from, to, units) { 240 this.els = Animator.makeArray(els); 241 if (property == 'opacity' && window.ActiveXObject) { 242 this.property = 'filter'; 243 } else { 244 this.property = Animator.camelize(property); 245 } 246 this.from = parseFloat(from); 247 this.to = parseFloat(to); 248 this.units = units != null ? units : 'px'; 249 } 250 NumericalStyleSubject.prototype = { 251 setState: function(state) { 252 var style = this.getStyle(state); 253 var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : ''; 254 var j=0; 255 for (var i=0; i<this.els.length; i++) { 256 try { 257 this.els[i].style[this.property] = style; 258 } catch (e) { 259 // ignore fontWeight - intermediate numerical values cause exeptions in firefox 260 if (this.property != 'fontWeight') throw e; 261 } 262 if (j++ > 20) return; 263 } 264 }, 265 getStyle: function(state) { 266 state = this.from + ((this.to - this.from) * state); 267 if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")"; 268 if (this.property == 'opacity') return state; 269 return Math.round(state) + this.units; 270 }, 271 inspect: function() { 272 return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n"; 273 } 274 } 275 276 // animates a colour based style property between two hex values 277 function ColorStyleSubject(els, property, from, to) { 278 this.els = Animator.makeArray(els); 279 this.property = Animator.camelize(property); 280 this.to = this.expandColor(to); 281 this.from = this.expandColor(from); 282 this.origFrom = from; 283 this.origTo = to; 284 } 285 286 ColorStyleSubject.prototype = { 287 // parse "#FFFF00" to [256, 256, 0] 288 expandColor: function(color) { 289 var hexColor, red, green, blue; 290 hexColor = ColorStyleSubject.parseColor(color); 291 if (hexColor) { 292 red = parseInt(hexColor.slice(1, 3), 16); 293 green = parseInt(hexColor.slice(3, 5), 16); 294 blue = parseInt(hexColor.slice(5, 7), 16); 295 return [red,green,blue] 296 } 297 if (window.DEBUG) { 298 alert("Invalid colour: '" + color + "'"); 299 } 300 }, 301 getValueForState: function(color, state) { 302 return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state)); 303 }, 304 setState: function(state) { 305 var color = '#' 306 + ColorStyleSubject.toColorPart(this.getValueForState(0, state)) 307 + ColorStyleSubject.toColorPart(this.getValueForState(1, state)) 308 + ColorStyleSubject.toColorPart(this.getValueForState(2, state)); 309 for (var i=0; i<this.els.length; i++) { 310 this.els[i].style[this.property] = color; 311 } 312 }, 313 inspect: function() { 314 return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n"; 315 } 316 } 317 318 // return a properly formatted 6-digit hex colour spec, or false 319 ColorStyleSubject.parseColor = function(string) { 320 var color = '#', match; 321 if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) { 322 var part; 323 for (var i=1; i<=3; i++) { 324 part = Math.max(0, Math.min(255, parseInt(match[i]))); 325 color += ColorStyleSubject.toColorPart(part); 326 } 327 return color; 328 } 329 if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) { 330 if(match[1].length == 3) { 331 for (var i=0; i<3; i++) { 332 color += match[1].charAt(i) + match[1].charAt(i); 333 } 334 return color; 335 } 336 return '#' + match[1]; 337 } 338 return false; 339 } 340 // convert a number to a 2 digit hex string 341 ColorStyleSubject.toColorPart = function(number) { 342 if (number > 255) number = 255; 343 var digits = number.toString(16); 344 if (number < 16) return '0' + digits; 345 return digits; 346 } 347 ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i; 348 ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; 349 350 // Animates discrete styles, i.e. ones that do not scale but have discrete values 351 // that can't be interpolated 352 function DiscreteStyleSubject(els, property, from, to, threshold) { 353 this.els = Animator.makeArray(els); 354 this.property = Animator.camelize(property); 355 this.from = from; 356 this.to = to; 357 this.threshold = threshold || 0.5; 358 } 359 360 DiscreteStyleSubject.prototype = { 361 setState: function(state) { 362 var j=0; 363 for (var i=0; i<this.els.length; i++) { 364 this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to; 365 } 366 }, 367 inspect: function() { 368 return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n"; 369 } 370 } 371 372 // animates between two styles defined using CSS. 373 // if style1 and style2 are present, animate between them, if only style1 374 // is present, animate between the element's current style and style1 375 function CSSStyleSubject(els, style1, style2) { 376 els = Animator.makeArray(els); 377 this.subjects = []; 378 if (els.length == 0) return; 379 var prop, toStyle, fromStyle; 380 if (style2) { 381 fromStyle = this.parseStyle(style1, els[0]); 382 toStyle = this.parseStyle(style2, els[0]); 383 } else { 384 toStyle = this.parseStyle(style1, els[0]); 385 fromStyle = {}; 386 for (prop in toStyle) { 387 fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop); 388 } 389 } 390 // remove unchanging properties 391 var prop; 392 for (prop in fromStyle) { 393 if (fromStyle[prop] == toStyle[prop]) { 394 delete fromStyle[prop]; 395 delete toStyle[prop]; 396 } 397 } 398 // discover the type (numerical or colour) of each style 399 var prop, units, match, type, from, to; 400 for (prop in fromStyle) { 401 var fromProp = String(fromStyle[prop]); 402 var toProp = String(toStyle[prop]); 403 if (toStyle[prop] == null) { 404 if (window.DEBUG) alert("No to style provided for '" + prop + '"'); 405 continue; 406 } 407 408 if (from = ColorStyleSubject.parseColor(fromProp)) { 409 to = ColorStyleSubject.parseColor(toProp); 410 type = ColorStyleSubject; 411 } else if (fromProp.match(CSSStyleSubject.numericalRe) 412 && toProp.match(CSSStyleSubject.numericalRe)) { 413 from = parseFloat(fromProp); 414 to = parseFloat(toProp); 415 type = NumericalStyleSubject; 416 match = CSSStyleSubject.numericalRe.exec(fromProp); 417 var reResult = CSSStyleSubject.numericalRe.exec(toProp); 418 if (match[1] != null) { 419 units = match[1]; 420 } else if (reResult[1] != null) { 421 units = reResult[1]; 422 } else { 423 units = reResult; 424 } 425 } else if (fromProp.match(CSSStyleSubject.discreteRe) 426 && toProp.match(CSSStyleSubject.discreteRe)) { 427 from = fromProp; 428 to = toProp; 429 type = DiscreteStyleSubject; 430 units = 0; // hack - how to get an animator option down to here 431 } else { 432 if (window.DEBUG) { 433 alert("Unrecognised format for value of " 434 + prop + ": '" + fromStyle[prop] + "'"); 435 } 436 continue; 437 } 438 this.subjects[this.subjects.length] = new type(els, prop, from, to, units); 439 } 440 } 441 442 CSSStyleSubject.prototype = { 443 // parses "width: 400px; color: #FFBB2E" to {width: "400px", color: "#FFBB2E"} 444 parseStyle: function(style, el) { 445 var rtn = {}; 446 // if style is a rule set 447 if (style.indexOf(":") != -1) { 448 var styles = style.split(";"); 449 for (var i=0; i<styles.length; i++) { 450 var parts = CSSStyleSubject.ruleRe.exec(styles[i]); 451 if (parts) { 452 rtn[parts[1]] = parts[2]; 453 } 454 } 455 } 456 // else assume style is a class name 457 else { 458 var prop, value, oldClass; 459 oldClass = el.className; 460 el.className = style; 461 for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) { 462 prop = CSSStyleSubject.cssProperties[i]; 463 value = CSSStyleSubject.getStyle(el, prop); 464 if (value != null) { 465 rtn[prop] = value; 466 } 467 } 468 el.className = oldClass; 469 } 470 return rtn; 471 472 }, 473 setState: function(state) { 474 for (var i=0; i<this.subjects.length; i++) { 475 this.subjects[i].setState(state); 476 } 477 }, 478 inspect: function() { 479 var str = ""; 480 for (var i=0; i<this.subjects.length; i++) { 481 str += this.subjects[i].inspect(); 482 } 483 return str; 484 } 485 } 486 // get the current value of a css property, 487 CSSStyleSubject.getStyle = function(el, property){ 488 var style; 489 if(document.defaultView && document.defaultView.getComputedStyle){ 490 style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property); 491 if (style) { 492 return style; 493 } 494 } 495 property = Animator.camelize(property); 496 if(el.currentStyle){ 497 style = el.currentStyle[property]; 498 } 499 return style || el.style[property] 500 } 501 502 503 CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/; 504 CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/; 505 CSSStyleSubject.discreteRe = /^\w+$/; 506 507 // required because the style object of elements isn't enumerable in Safari 508 /* 509 CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing', 510 'border-style','border-top','border-right','border-bottom','border-left','border-top-color', 511 'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width', 512 'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust', 513 'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top', 514 'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height', 515 'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding', 516 'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent', 517 'top','width','word-spacing','z-index','opacity','outline-offset'];*/ 518 519 520 CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y']; 521 522 523 // chains several Animator objects together 524 function AnimatorChain(animators, options) { 525 this.animators = animators; 526 this.setOptions(options); 527 for (var i=0; i<this.animators.length; i++) { 528 this.listenTo(this.animators[i]); 529 } 530 this.forwards = false; 531 this.current = 0; 532 } 533 534 AnimatorChain.prototype = { 535 // apply defaults 536 setOptions: function(options) { 537 this.options = Animator.applyDefaults({ 538 // by default, each call to AnimatorChain.play() calls jumpTo(0) of each animator 539 // before playing, which can cause flickering if you have multiple animators all 540 // targeting the same element. Set this to false to avoid this. 541 resetOnPlay: true 542 }, options); 543 }, 544 // play each animator in turn 545 play: function() { 546 this.forwards = true; 547 this.current = -1; 548 if (this.options.resetOnPlay) { 549 for (var i=0; i<this.animators.length; i++) { 550 this.animators[i].jumpTo(0); 551 } 552 } 553 this.advance(); 554 }, 555 // play all animators backwards 556 reverse: function() { 557 this.forwards = false; 558 this.current = this.animators.length; 559 if (this.options.resetOnPlay) { 560 for (var i=0; i<this.animators.length; i++) { 561 this.animators[i].jumpTo(1); 562 } 563 } 564 this.advance(); 565 }, 566 // if we have just play()'d, then call reverse(), and vice versa 567 toggle: function() { 568 if (this.forwards) { 569 this.seekTo(0); 570 } else { 571 this.seekTo(1); 572 } 573 }, 574 // internal: install an event listener on an animator's onComplete option 575 // to trigger the next animator 576 listenTo: function(animator) { 577 var oldOnComplete = animator.options.onComplete; 578 var _this = this; 579 animator.options.onComplete = function() { 580 if (oldOnComplete) oldOnComplete.call(animator); 581 _this.advance(); 582 } 583 }, 584 // play the next animator 585 advance: function() { 586 if (this.forwards) { 587 if (this.animators[this.current + 1] == null) return; 588 this.current++; 589 this.animators[this.current].play(); 590 } else { 591 if (this.animators[this.current - 1] == null) return; 592 this.current--; 593 this.animators[this.current].reverse(); 594 } 595 }, 596 // this function is provided for drop-in compatibility with Animator objects, 597 // but only accepts 0 and 1 as target values 598 seekTo: function(target) { 599 if (target <= 0) { 600 this.forwards = false; 601 this.animators[this.current].seekTo(0); 602 } else { 603 this.forwards = true; 604 this.animators[this.current].seekTo(1); 605 } 606 } 607 } 608 609 // an Accordion is a class that creates and controls a number of Animators. An array of elements is passed in, 610 // and for each element an Animator and a activator button is created. When an Animator's activator button is 611 // clicked, the Animator and all before it seek to 0, and all Animators after it seek to 1. This can be used to 612 // create the classic Accordion effect, hence the name. 613 // see setOptions for arguments 614 function Accordion(options) { 615 this.setOptions(options); 616 var selected = this.options.initialSection, current; 617 if (this.options.rememberance) { 618 current = document.location.hash.substring(1); 619 } 620 this.rememberanceTexts = []; 621 this.ans = []; 622 var _this = this; 623 for (var i=0; i<this.options.sections.length; i++) { 624 var el = this.options.sections[i]; 625 var an = new Animator(this.options.animatorOptions); 626 var from = this.options.from + (this.options.shift * i); 627 var to = this.options.to + (this.options.shift * i); 628 an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units)); 629 an.jumpTo(0); 630 var activator = this.options.getActivator(el); 631 activator.index = i; 632 activator.onclick = function(){_this.show(this.index)}; 633 this.ans[this.ans.length] = an; 634 this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, ""); 635 if (this.rememberanceTexts[i] === current) { 636 selected = i; 637 } 638 } 639 this.show(selected); 640 } 641 642 Accordion.prototype = { 643 // apply defaults 644 setOptions: function(options) { 645 this.options = Object.extend({ 646 // REQUIRED: an array of elements to use as the accordion sections 647 sections: null, 648 // a function that locates an activator button element given a section element. 649 // by default it takes a button id from the section's "activator" attibute 650 getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))}, 651 // shifts each animator's range, for example with options {from:0,to:100,shift:20} 652 // the animators' ranges will be 0-100, 20-120, 40-140 etc. 653 shift: 0, 654 // the first page to show 655 initialSection: 0, 656 // if set to true, document.location.hash will be used to preserve the open section across page reloads 657 rememberance: true, 658 // constructor arguments to the Animator objects 659 animatorOptions: {} 660 }, options || {}); 661 }, 662 show: function(section) { 663 for (var i=0; i<this.ans.length; i++) { 664 this.ans[i].seekTo(i > section ? 1 : 0); 665 } 666 if (this.options.rememberance) { 667 document.location.hash = this.rememberanceTexts[section]; 668 } 669 } 670 } -
examples/strategy-cluster.html
old new 1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title>OpenLayers Cluster Strategy Example</title> 4 <link rel="stylesheet" href="../theme/default/style.css" type="text/css" /> 5 <link rel="stylesheet" href="style.css" type="text/css" /> 6 <style type="text/css"> 7 #photos { 8 height: 100px; 9 width: 512px; 10 position: relative; 11 white-space: nowrap; 12 } 13 .shift { 14 height: 25px; 15 line-height: 25px; 16 background-color: #fefefe; 17 text-align: center; 18 position: absolute; 19 bottom: 10px; 20 font-size: 8px; 21 font-weight: bold; 22 color: #696969; 23 width: 25px; 24 } 25 #scroll-start { 26 left: 0px; 27 } 28 #scroll-end { 29 right: 0px; 30 } 31 #scroll { 32 left: 30px; 33 width: 452px; 34 height: 100px; 35 overflow: hidden; 36 position: absolute; 37 bottom: 0px; 38 } 39 #photos ul { 40 position: absolute; 41 bottom: 0px; 42 padding: 0; 43 margin: 0; 44 } 45 #photos ul.start { 46 left: 0px; 47 } 48 #photos ul.end { 49 right: 80px; 50 } 51 #photos ul li { 52 padding 10px; 53 margin: 0; 54 list-style: none; 55 display: inline; 56 } 57 img.thumb { 58 height: 30px; 59 } 60 img.big { 61 height: 90px; 62 } 63 </style> 64 <script src="../lib/OpenLayers.js"></script> 65 <script src="Jugl.js"></script> 66 <script src="animator.js"></script> 67 <script type="text/javascript"> 68 var map, template; 69 var Jugl = window["http://jugl.tschaub.net/trunk/lib/Jugl.js"]; 70 OpenLayers.ProxyHost = (window.location.host == "localhost") ? 71 "/cgi-bin/proxy.cgi?url=" : "proxy.cgi?url="; 72 73 function init() { 74 map = new OpenLayers.Map('map', { 75 restrictedExtent: new OpenLayers.Bounds(-180, -90, 180, 90) 76 }); 77 var base = new OpenLayers.Layer.WMS("OpenLayers WMS", 78 ["http://t3.labs.metacarta.com/wms-c/Basic.py", 79 "http://t2.labs.metacarta.com/wms-c/Basic.py", 80 "http://t1.labs.metacarta.com/wms-c/Basic.py"], 81 {layers: 'satellite'} 82 ); 83 84 var style = new OpenLayers.Style({ 85 pointRadius: "${radius}", 86 fillColor: "#ffcc66", 87 fillOpacity: 0.8, 88 strokeColor: "#cc6633", 89 strokeWidth: 2, 90 strokeOpacity: 0.8 91 }, { 92 context: { 93 radius: function(feature) { 94 return Math.min(feature.attributes.count, 7) + 3; 95 } 96 } 97 }); 98 99 var photos = new OpenLayers.Layer.Vector("Photos", { 100 strategies: [ 101 new OpenLayers.Strategy.Fixed(), 102 new OpenLayers.Strategy.Cluster() 103 ], 104 protocol: new OpenLayers.Protocol.HTTP({ 105 url: "http://labs.metacarta.com/flickrbrowse/flickr.py/flickr", 106 params: { 107 format: "WFS", 108 sort: "interestingness-desc", 109 service: "WFS", 110 request: "GetFeatures", 111 srs: "EPSG:4326", 112 maxfeatures: 150, 113 bbox: [-180, -90, 180, 90] 114 }, 115 format: new OpenLayers.Format.GML() 116 }), 117 styleMap: new OpenLayers.StyleMap({ 118 "default": style, 119 "select": { 120 fillColor: "#8aeeef", 121 strokeColor: "#32a8a9" 122 } 123 }) 124 }); 125 126 var select = new OpenLayers.Control.SelectFeature( 127 photos, {hover: true} 128 ); 129 map.addControl(select); 130 select.activate(); 131 photos.events.on({"featureselected": display}); 132 133 map.addLayers([base, photos]); 134 map.setCenter(new OpenLayers.LonLat(0, 0), 1); 135 136 // template setup 137 template = new Jugl.Template("template"); 138 139 } 140 141 function display(event) { 142 // clear previous photo list and create new one 143 $("photos").innerHTML = ""; 144 var node = template.process({ 145 context: {features: event.feature.cluster}, 146 clone: true, 147 parent: $("photos") 148 }); 149 // set up forward/rewind 150 var forward = Animator.apply($("list"), ["start", "end"], {duration: 1500}); 151 $("scroll-end").onmouseover = function() {forward.seekTo(1)}; 152 $("scroll-end").onmouseout = function() {forward.seekTo(forward.state)}; 153 $("scroll-start").onmouseover = function() {forward.seekTo(0)}; 154 $("scroll-start").onmouseout = function() {forward.seekTo(forward.state)}; 155 // set up photo zoom 156 for(var i=0; i<event.feature.cluster.length; ++i) { 157 listen($("link-" + i), Animator.apply($("photo-" + i), ["thumb", "big"])); 158 } 159 } 160 161 function listen(el, anim) { 162 el.onmouseover = function() {anim.seekTo(1)}; 163 el.onmouseout = function() {anim.seekTo(0)}; 164 } 165 166 </script> 167 </head> 168 <body onload="init()"> 169 <h1 id="title">Cluster Strategy Example</h1> 170 <p id="shortdesc"> 171 Uses a cluster strategy to render points representing clusters of features. 172 </p> 173 <div id="map" class="smallmap"></div> 174 <div id="docs"> 175 <p>The Cluster strategy lets you display points representing clusters 176 of features within some pixel distance.</p> 177 </div> 178 <div id="photos"></div> 179 <p>Hover over a cluster on the map to see the photos it includes.</p> 180 <div style="display: none;"> 181 <div id="template"> 182 <div class="shift" id="scroll-start"><<</div> 183 <div id="scroll"> 184 <ul id="list" class="start"> 185 <li jugl:repeat="feature features"> 186 <a jugl:attributes="href feature.attributes.img_url; 187 id 'link-' + repeat.feature.index" 188 target="_blank"> 189 <img jugl:attributes="src feature.attributes.img_url; 190 title feature.attributes.title; 191 id 'photo-' + repeat.feature.index" 192 class="thumb" /> 193 </a> 194 </li> 195 </ul> 196 </div> 197 <div class="shift" id="scroll-end">>></div> 198 </div> 199 </div> 200 </body> 201 </html> -
examples/strategy-paging.html
old new 1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title>OpenLayers Paging Strategy Example</title> 4 <link rel="stylesheet" href="../theme/default/style.css" type="text/css" /> 5 <link rel="stylesheet" href="style.css" type="text/css" /> 6 <script src="../lib/OpenLayers.js"></script> 7 <script type="text/javascript"> 8 var map, photos, paging; 9 OpenLayers.ProxyHost = (window.location.host == "localhost") ? 10 "/cgi-bin/proxy.cgi?url=" : "proxy.cgi?url="; 11 12 function init() { 13 map = new OpenLayers.Map('map', { 14 restrictedExtent: new OpenLayers.Bounds(-180, -90, 180, 90) 15 }); 16 var base = new OpenLayers.Layer.WMS("OpenLayers WMS", 17 ["http://t3.labs.metacarta.com/wms-c/Basic.py", 18 "http://t2.labs.metacarta.com/wms-c/Basic.py", 19 "http://t1.labs.metacarta.com/wms-c/Basic.py"], 20 {layers: 'satellite'} 21 ); 22 23 var style = new OpenLayers.Style({ 24 externalGraphic: "${img_url}", 25 pointRadius: 30 26 }); 27 28 paging = new OpenLayers.Strategy.Paging(); 29 30 photos = new OpenLayers.Layer.Vector("Photos", { 31 strategies: [new OpenLayers.Strategy.Fixed(), paging], 32 protocol: new OpenLayers.Protocol.HTTP({ 33 url: "http://labs.metacarta.com/flickrbrowse/flickr.py/flickr", 34 params: { 35 format: "WFS", 36 sort: "interestingness-desc", 37 service: "WFS", 38 request: "GetFeatures", 39 srs: "EPSG:4326", 40 maxfeatures: 100, 41 bbox: [-180, -90, 180, 90] 42 }, 43 format: new OpenLayers.Format.GML() 44 }), 45 styleMap: new OpenLayers.StyleMap(style) 46 }); 47 48 map.addLayers([base, photos]); 49 photos.events.on({"featuresadded": updateButtons}); 50 map.setCenter(new OpenLayers.LonLat(0, 0), 1); 51 } 52 53 function updateButtons() { 54 document.getElementById("prev").disabled = (paging.pageNum() < 1); 55 document.getElementById("next").disabled = (paging.pageNum() >= paging.pageCount() - 1); 56 document.getElementById("num").innerHTML = paging.pageNum() + 1; 57 document.getElementById("count").innerHTML = paging.pageCount(); 58 } 59 </script> 60 </head> 61 <body onload="init()"> 62 <h1 id="title">Paging Strategy Example</h1> 63 <p id="shortdesc"> 64 Uses a paging strategy to cache large batches of features and render a page at a time. 65 </p> 66 <div id="map" class="smallmap"></div> 67 Displaying page <span id="num">0</span> of <span id="count">...</span> 68 <button id="prev" disabled="disabled" onclick="paging.pagePrevious();">previous</button> 69 <button id="next" disabled="disabled" onclick="paging.pageNext();">next</button> 70 <br /><br /> 71 <div id="docs"> 72 <p>The Paging strategy lets you apply client side paging for protocols 73 that do not support paging on the server. In this case, the protocol requests a 74 batch of 100 features, the strategy caches those and supplies a single 75 page at a time to the layer.</p> 76 </div> 77 </body> 78 </html>
