pour googlemap in the mix
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Fri, 5 Apr 2013 08:14:41 +0000 (10:14 +0200)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Fri, 5 Apr 2013 08:14:41 +0000 (10:14 +0200)
fix prelude so we can refer to full url's in {js,css}_files

13 files changed:
plugins/googlemap/__init__.py [new file with mode: 0644]
plugins/googlemap/googlemap.css [new file with mode: 0644]
plugins/googlemap/googlemap.html [new file with mode: 0644]
plugins/googlemap/googlemap.js [new file with mode: 0644]
plugins/googlemap/googlemap.py [new file with mode: 0644]
plugins/googlemap/markerclusterer.js [new file with mode: 0644]
plugins/googlemap/markerclusterer_compiled.js [new file with mode: 0644]
plugins/googlemap/markerclusterer_packed.js [new file with mode: 0644]
trash/dashboard.py
trash/sliceview.py
unfold/plugin.py
unfold/prelude.py
unfold/templates/prelude.html

diff --git a/plugins/googlemap/__init__.py b/plugins/googlemap/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugins/googlemap/googlemap.css b/plugins/googlemap/googlemap.css
new file mode 100644 (file)
index 0000000..6a82854
--- /dev/null
@@ -0,0 +1,15 @@
+#map-container {
+  padding: 6px;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #ccc #ccc #999 #ccc;
+  -webkit-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
+  -moz-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
+  box-shadow: rgba(64, 64, 64, 0.1) 0 2px 5px;
+  /*width: 800px;*/
+}
+
+#map {
+  /*width: 800px;*/
+  height: 500px;
+}
diff --git a/plugins/googlemap/googlemap.html b/plugins/googlemap/googlemap.html
new file mode 100644 (file)
index 0000000..d0bb5d6
--- /dev/null
@@ -0,0 +1,2 @@
+{# keep the original skeleton but this probably won't support several instances in the same page #}
+<div id='map-container'><div id='map'></div></div>
diff --git a/plugins/googlemap/googlemap.js b/plugins/googlemap/googlemap.js
new file mode 100644 (file)
index 0000000..2ab99da
--- /dev/null
@@ -0,0 +1,278 @@
+/**
+ * MySlice GoogleMap plugin
+ * URL: http://trac.myslice.info
+ * Description: display a query result in a googlemap
+ * Author: The MySlice Team
+ * Copyright (c) 2012 UPMC Sorbonne Universite - INRIA
+ * License: GPLv3
+ */
+
+
+(function( jQuery ){
+
+  var methods = {
+     init : function( options ) {
+
+       return this.each(function(){
+         
+         var $this = jQuery(this),
+             data = $this.data('GoogleMap'), GoogleMap = jQuery('<div />', {text : $this.attr('title')});
+         
+         // If the plugin hasn't been initialized yet
+         if ( ! data ) {
+         
+           /* Plugin initialization */
+
+            //google.load('maps', '3', { other_params: 'sensor=false' });
+            //google.setOnLoadCallback(initialize);
+
+            $this.data('map', null);
+            $this.data('markerCluster', null);
+            $this.data('markers', []);
+
+            var myLatlng = new google.maps.LatLng(34.397, 150.644);
+            var myOptions = {
+              zoom: 2,
+              center: myLatlng,
+              mapTypeId: google.maps.MapTypeId.ROADMAP
+            }
+      
+            var map = new google.maps.Map(document.getElementById("map"), myOptions);
+            $this.data('map', map);
+
+            /* End of plugin initialization */
+
+            jQuery(this).data('GoogleMap', {
+                               plugin_uuid: options.plugin_uuid,
+                               query_uuid: options.query_uuid,
+                target : $this,
+                current_resources: Array(),
+                GoogleMap : GoogleMap
+            });
+
+            /* Subscribe to query updates */
+            jQuery.subscribe('/results/' + options.query_uuid + '/changed', {instance: $this}, update_map);
+            jQuery.subscribe('/update-set/' + options.query_uuid, {instance: $this}, on_resource_changed);
+            jQuery.subscribe('/query/' + options.query_uuid + '/changed', {instance: $this}, query_changed);
+            
+            //data = jQuery(this).data();
+            
+            // TODO: Change the status of a node based on the actions in GoogleMap plugin or in other plugins (e.g. DataTables)
+            // Can we publish a value in results row['sliver'] ???
+            // Today, the value is attached or undefined
+            // But can we think about a added/removed status ???
+            // This plugin would update the map based on the results published
+
+         }
+       });
+     },
+    destroy : function( ) {
+
+        return this.each(function(){
+            var $this = jQuery(this), data = $this.data('GoogleMap');
+                       jQuery(window).unbind('GoogleMap');
+                       data.GoogleMap.remove();
+                       $this.removeData('GoogleMap');
+               })
+
+    },
+/*
+    reposition : function( ) { // ... },
+    update : function( ) { // ... },
+    hide : function( ) { // ... },
+*/
+    show : function( content ) {
+        google.maps.event.trigger(map, 'resize');
+    }
+  };
+
+    jQuery.fn.GoogleMap = function( method ) {
+               /* Method calling logic */
+               if ( methods[method] ) {
+                       return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+               } else if ( typeof method === 'object' || ! method ) {
+                       return methods.init.apply( this, arguments );
+               } else {
+                       jQuery.error( 'Method ' +  method + ' does not exist on jQuery.GoogleMap' );
+               }    
+
+    };
+
+    /* Private methods */
+        function query_changed(e,query){
+            var data = e.data.instance.data();
+            /* Compare current and advertised query to get added and removed fields */
+            previous_query=data.current_query;
+            /* Save the query as the current query */
+            data.current_query=query;
+            
+            var rows=[];
+            if(typeof(data.results)!="undefined" && data.results.length>0){
+                jQuery.each(data.results, function(i, row){
+                    jQuery.each(query.filter, function (idx, filter){
+                        if(get_value(row[filter[0]])==filter[2]){
+                            rows.push(row);
+                        }
+                    });
+                });
+                data.markerCluster=[];
+                data.markers=[];
+                var myLatlng = new google.maps.LatLng(34.397, 150.644);
+                var myOptions = {
+                    zoom: 2,
+                    center: myLatlng,
+                    mapTypeId: google.maps.MapTypeId.ROADMAP
+                }
+                map = new google.maps.Map(jQuery('#map')[0],myOptions);
+                data.map=map;
+                //map.clearMarkers();
+                update_map(e, rows);
+            }
+        }
+       function update_map(e, rows) {            
+            var data = e.data.instance.data();
+            var instance_ = e.data.instance;
+           //$plugindiv.closest('.need-spin').spin(false);
+           instance_.closest('.need-spin').spin(false);
+
+
+            if (!rows) {
+                alert('error');
+                return;
+            }
+
+            if(rows.length==0) {
+                rows=data.results;
+            }
+
+            if(typeof(data.results)=="undefined" || data.results==null){
+                data.results=rows;
+            }
+            var map = data.map;
+            var markerCluster = data.markerCluster;            
+            var markers = data.markers;
+            var coords = new Array();
+            var infowindow = new google.maps.InfoWindow();
+            /*
+            if(typeof(markers)!="undefined" && markers.length>0){
+                map.clearMarkers();
+            }*/
+
+            data.current_resources = Array();
+
+            jQuery.each(data.results, function(i, result){
+                // get the coordinates
+                var latitude=get_value(result['latitude']);
+                var longitude=get_value(result['longitude']);
+                var hash = latitude + longitude;
+
+                // check to see if we've seen this hash before
+                if(coords[hash] == null) {
+                    // get coordinate object
+                    var myLatlng = new google.maps.LatLng(latitude, longitude);
+                    // store an indicator that we've seen this point before
+                    coords[hash] = 1;
+                } else {
+                    // add some randomness to this point 1500 = 100 meters, 15000 = 10 meters
+                    var lat = latitude + (Math.random() -.5) / 1500; 
+                    var lng = longitude + (Math.random() -.5) / 1500; 
+
+                    // get the coordinate object
+                    var myLatlng = new google.maps.LatLng(lat, lng);
+                }
+                // If the node is attached to the slice, action will be Remove; else action will be add to slice
+                if (typeof(result['sliver']) != 'undefined') {
+                    data.current_resources.push(result['urn']);
+                    action="del";
+                    action_class="ui-icon-circle-minus";
+                    action_message="Remove from slice";
+                }else{
+                    action="add";
+                    action_class="ui-icon-circle-plus";
+                    action_message="Add to slice";
+                }
+                // XXX not working
+                if (!(result['latitude'])) {
+                    return true;
+                }
+
+                //jQuery(".map-button").click(button_click);
+                if(jQuery.inArray(result,rows)>-1){
+                    var marker = new google.maps.Marker({
+                        position: myLatlng,
+                        title: get_value(result['ip']),
+                        // This should be done by the rendering
+                        content: '<p>Agent: ' + get_value(result['ip']) + ' (' + get_value(result['urn']) + ')<br/>Platform: ' + get_value(result['platform'])+'</p>' +
+                                '<div class="map-button" id="'+action+'/'+get_value(result['urn'])+'" style="cursor:pointer;">'+
+                                '<span class="ui-icon '+action_class+'" style="clear:both;float:left;"></span>'+action_message+
+                                '</div>'
+                    }); 
+
+                    google.maps.event.addListener(marker, 'click', function() {
+                            infowindow.content = this.content;
+                            infowindow.open(map, this);
+                            // onload of the infowindow on the map, bind a click on a button
+                            google.maps.event.addListener(infowindow, 'domready', function() {
+                                jQuery('.map-button').unbind('click');
+                                jQuery(".map-button").click({instance: instance_}, button_click);
+                            });
+                    });
+                    markers.push(marker);
+                }
+            });
+            markerCluster = new MarkerClusterer(map, markers, {zoomOnClick: false});
+            google.maps.event.addListener(markerCluster, "clusterclick", function (cluster) {
+                var markers = cluster.getMarkers();
+                var bounds  = new google.maps.LatLngBounds();
+              /* 
+              * date: 24/05/2012
+              * author: lbaron
+              * Firefox JS Error - replaced $.each by JQuery.each
+              */                  
+                jQuery.each(markers, function(i, marker){
+                    bounds.extend(marker.getPosition()); 
+                    });
+                //map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
+                map.fitBounds(bounds);
+            });
+            data.markerCluster=markerCluster;
+        }
+        function button_click(e){
+            var data = e.data.instance.data().GoogleMap;
+            var op_value=this.id.split("/");
+            if(op_value.length>0){
+                jQuery.publish('selected', op_value[0]+'/'+op_value[1]);
+                var value = op_value[1];
+
+                if (op_value[0] == 'add') {
+                    data.current_resources.push(value);
+                } else {
+                    tmp = jQuery.grep(data.current_resources, function(x) { return x != value; });
+                    data.current_resources = tmp;
+                }
+
+                /* inform slice that our selected resources have changed */
+                jQuery.publish('/update-set/' + data.query_uuid, [data.current_resources, true, e.data.instance]);
+            }
+        }
+
+    function on_resource_changed(e, resources, instance)
+    {
+        /* TODO OPENLAB : this query determines which checkboxes must be checked */
+        if (instance == e.data.instance)
+            return;
+        data = e.data.instance.data().GoogleMap;
+
+        previous_resources = data.current_resources;
+        data.current_resources = resources;
+
+        /* TODO We uncheck all checkboxes ... */
+        //jQuery('datatables-checkbox-' + data.options.plugin_uuid).attr('checked', false);
+        /* ... and check the ones specified in the resource list */
+        //jQuery.each(data.current_resources, function(index, urn) {
+        //    jQuery('#datatables-checkbox-' + data.options.plugin_uuid + "-" + urn).attr('checked', true)
+        //});
+        
+    }
+
+})( jQuery );
diff --git a/plugins/googlemap/googlemap.py b/plugins/googlemap/googlemap.py
new file mode 100644 (file)
index 0000000..ac2c798
--- /dev/null
@@ -0,0 +1,34 @@
+from unfold.plugin import Plugin
+
+class GoogleMap (Plugin):
+
+    # set checkboxes if a final column with checkboxes is desired
+    # pass columns as the initial set of columns
+    #   if None then this is taken from the query's fields
+    def __init__ (self, query, **settings):
+        Plugin.__init__ (self, **settings)
+        self.query=query
+
+    def template_file (self):
+        return "googlemap.html"
+
+    def template_env (self, request):
+        env={}
+        return env
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [ "https://maps.googleapis.com/maps/api/js?sensor=false", 
+                           "/js/googlemap.js",
+                           "/js/markerclusterer.js",
+                            "js/manifold.js", "js/manifold-query.js", 
+                            "js/spin.presets.js", "js/spin.min.js", "js/jquery.spin.js", 
+                            "js/unfold-helper.js",
+                           ],
+            'css_files' : [ "css/googlemap.css",
+                            ],
+            }
+        return reqs
+
+    # the list of things passed to the js plugin
+    def json_settings_list (self): return ['plugin_uuid','query_uuid']
diff --git a/plugins/googlemap/markerclusterer.js b/plugins/googlemap/markerclusterer.js
new file mode 100644 (file)
index 0000000..8ac6d8f
--- /dev/null
@@ -0,0 +1,1137 @@
+// ==ClosureCompiler==
+// @compilation_level ADVANCED_OPTIMIZATIONS
+// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
+// ==/ClosureCompiler==
+
+/**
+ * @name MarkerClusterer for Google Maps v3
+ * @version version 1.0
+ * @author Luke Mahe
+ * @fileoverview
+ * The library creates and manages per-zoom-level clusters for large amounts of
+ * markers.
+ * <br/>
+ * This is a v3 implementation of the
+ * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
+ * >v2 MarkerClusterer</a>.
+ */
+
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * A Marker Clusterer that clusters markers.
+ *
+ * @param {google.maps.Map} map The Google map to attach to.
+ * @param {Array.<google.maps.Marker>} opt_markers Optional markers to add to
+ *   the cluster.
+ * @param {Object} opt_options support the following options:
+ *     'gridSize': (number) The grid size of a cluster in pixels.
+ *     'maxZoom': (number) The maximum zoom level that a marker can be part of a
+ *                cluster.
+ *     'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
+ *                    cluster is to zoom into it.
+ *     'averageCenter': (boolean) Wether the center of each cluster should be
+ *                      the average of all markers in the cluster.
+ *     'styles': (object) An object that has style properties:
+ *       'url': (string) The image url.
+ *       'height': (number) The image height.
+ *       'width': (number) The image width.
+ *       'anchor': (Array) The anchor position of the label text.
+ *       'textColor': (string) The text color.
+ *       'textSize': (number) The text size.
+ * @constructor
+ * @extends google.maps.OverlayView
+ */
+function MarkerClusterer(map, opt_markers, opt_options) {
+  // MarkerClusterer implements google.maps.OverlayView interface. We use the
+  // extend function to extend MarkerClusterer with google.maps.OverlayView
+  // because it might not always be available when the code is defined so we
+  // look for it at the last possible moment. If it doesn't exist now then
+  // there is no point going ahead :)
+  this.extend(MarkerClusterer, google.maps.OverlayView);
+  this.map_ = map;
+
+  /**
+   * @type {Array.<google.maps.Marker>}
+   * @private
+   */
+  this.markers_ = [];
+
+  /**
+   *  @type {Array.<Cluster>}
+   */
+  this.clusters_ = [];
+
+  this.sizes = [53, 56, 66, 78, 90];
+
+  /**
+   * @private
+   */
+  this.styles_ = [];
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.ready_ = false;
+
+  var options = opt_options || {};
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.gridSize_ = options['gridSize'] || 60;
+
+  /**
+   * @type {?number}
+   * @private
+   */
+  this.maxZoom_ = options['maxZoom'] || null;
+
+  this.styles_ = options['styles'] || [];
+
+  /**
+   * @type {string}
+   * @private
+   */
+  this.imagePath_ = options['imagePath'] ||
+      this.MARKER_CLUSTER_IMAGE_PATH_;
+
+  /**
+   * @type {string}
+   * @private
+   */
+  this.imageExtension_ = options['imageExtension'] ||
+      this.MARKER_CLUSTER_IMAGE_EXTENSION_;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.zoomOnClick_ = true;
+
+  if (options['zoomOnClick'] != undefined) {
+    this.zoomOnClick_ = options['zoomOnClick'];
+  }
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.averageCenter_ = false;
+
+  if (options['averageCenter'] != undefined) {
+    this.averageCenter_ = options['averageCenter'];
+  }
+
+  this.setupStyles_();
+
+  this.setMap(map);
+
+  /**
+   * @type {number}
+   * @private
+   */
+  this.prevZoom_ = this.map_.getZoom();
+
+  // Add the map event listeners
+  var that = this;
+  google.maps.event.addListener(this.map_, 'zoom_changed', function() {
+    var maxZoom = that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom;
+    var zoom = that.map_.getZoom();
+    if (zoom < 0 || zoom > maxZoom) {
+      return;
+    }
+
+    if (that.prevZoom_ != zoom) {
+      that.prevZoom_ = that.map_.getZoom();
+      that.resetViewport();
+    }
+  });
+
+  google.maps.event.addListener(this.map_, 'idle', function() {
+    that.redraw();
+  });
+
+  // Finally, add the markers
+  if (opt_markers && opt_markers.length) {
+    this.addMarkers(opt_markers, false);
+  }
+}
+
+
+/**
+ * The marker cluster image path.
+ *
+ * @type {string}
+ * @private
+ */
+MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ =
+    'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' +
+    'images/m';
+
+
+/**
+ * The marker cluster image path.
+ *
+ * @type {string}
+ * @private
+ */
+MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
+
+
+/**
+ * Extends a objects prototype by anothers.
+ *
+ * @param {Object} obj1 The object to be extended.
+ * @param {Object} obj2 The object to extend with.
+ * @return {Object} The new extended object.
+ * @ignore
+ */
+MarkerClusterer.prototype.extend = function(obj1, obj2) {
+  return (function(object) {
+    for (property in object.prototype) {
+      this.prototype[property] = object.prototype[property];
+    }
+    return this;
+  }).apply(obj1, [obj2]);
+};
+
+
+/**
+ * Implementaion of the interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.onAdd = function() {
+  this.setReady_(true);
+};
+
+
+/**
+ * Implementation of the interface.
+ * @ignore
+ */
+MarkerClusterer.prototype.draw = function() {};
+
+
+/**
+ * Sets up the styles object.
+ *
+ * @private
+ */
+MarkerClusterer.prototype.setupStyles_ = function() {
+  if (this.styles_.length) {
+    return;
+  }
+
+  for (var i = 0, size; size = this.sizes[i]; i++) {
+    this.styles_.push({
+      url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
+      height: size,
+      width: size
+    });
+  }
+};
+
+
+/**
+ *  Sets the styles.
+ *
+ *  @param {Object} styles The style to set.
+ */
+MarkerClusterer.prototype.setStyles = function(styles) {
+  this.styles_ = styles;
+};
+
+
+/**
+ *  Gets the styles.
+ *
+ *  @return {Object} The styles object.
+ */
+MarkerClusterer.prototype.getStyles = function() {
+  return this.styles_;
+};
+
+
+/**
+ * Whether zoom on click is set.
+ *
+ * @return {boolean} True if zoomOnClick_ is set.
+ */
+MarkerClusterer.prototype.isZoomOnClick = function() {
+  return this.zoomOnClick_;
+};
+
+/**
+ * Whether average center is set.
+ *
+ * @return {boolean} True if averageCenter_ is set.
+ */
+MarkerClusterer.prototype.isAverageCenter = function() {
+  return this.averageCenter_;
+};
+
+
+/**
+ *  Returns the array of markers in the clusterer.
+ *
+ *  @return {Array.<google.maps.Marker>} The markers.
+ */
+MarkerClusterer.prototype.getMarkers = function() {
+  return this.markers_;
+};
+
+
+/**
+ *  Returns the array of markers in the clusterer.
+ *
+ *  @return {Array.<google.maps.Marker>} The number of markers.
+ */
+MarkerClusterer.prototype.getTotalMarkers = function() {
+  return this.markers_;
+};
+
+
+/**
+ *  Sets the max zoom for the clusterer.
+ *
+ *  @param {number} maxZoom The max zoom level.
+ */
+MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
+  this.maxZoom_ = maxZoom;
+};
+
+
+/**
+ *  Gets the max zoom for the clusterer.
+ *
+ *  @return {number} The max zoom level.
+ */
+MarkerClusterer.prototype.getMaxZoom = function() {
+  return this.maxZoom_ || this.map_.mapTypes[this.map_.getMapTypeId()].maxZoom;
+};
+
+
+/**
+ *  The function for calculating the cluster icon image.
+ *
+ *  @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
+ *  @param {number} numStyles The number of styles available.
+ *  @return {Object} A object properties: 'text' (string) and 'index' (number).
+ *  @private
+ */
+MarkerClusterer.prototype.calculator_ = function(markers, numStyles) {
+  var index = 0;
+  var count = markers.length;
+  var dv = count;
+  while (dv !== 0) {
+    dv = parseInt(dv / 10, 10);
+    index++;
+  }
+
+  index = Math.min(index, numStyles);
+  return {
+    text: count,
+    index: index
+  };
+};
+
+
+/**
+ * Set the calculator function.
+ *
+ * @param {function(Array, number)} calculator The function to set as the
+ *     calculator. The function should return a object properties:
+ *     'text' (string) and 'index' (number).
+ *
+ */
+MarkerClusterer.prototype.setCalculator = function(calculator) {
+  this.calculator_ = calculator;
+};
+
+
+/**
+ * Get the calculator function.
+ *
+ * @return {function(Array, number)} the calculator function.
+ */
+MarkerClusterer.prototype.getCalculator = function() {
+  return this.calculator_;
+};
+
+
+/**
+ * Add an array of markers to the clusterer.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to add.
+ * @param {boolean} opt_nodraw Whether to redraw the clusters.
+ */
+MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
+  for (var i = 0, marker; marker = markers[i]; i++) {
+    this.pushMarkerTo_(marker);
+  }
+  if (!opt_nodraw) {
+    this.redraw();
+  }
+};
+
+
+/**
+ * Pushes a marker to the clusterer.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @private
+ */
+MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
+  marker.setVisible(false);
+  marker.setMap(null);
+  marker.isAdded = false;
+  if (marker['draggable']) {
+    // If the marker is draggable add a listener so we update the clusters on
+    // the drag end.
+    var that = this;
+    google.maps.event.addListener(marker, 'dragend', function() {
+      marker.isAdded = false;
+      that.resetViewport();
+      that.redraw();
+    });
+  }
+  this.markers_.push(marker);
+};
+
+
+/**
+ * Adds a marker to the clusterer and redraws if needed.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @param {boolean} opt_nodraw Whether to redraw the clusters.
+ */
+MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
+  this.pushMarkerTo_(marker);
+  if (!opt_nodraw) {
+    this.redraw();
+  }
+};
+
+
+/**
+ * Remove a marker from the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to remove.
+ * @return {boolean} True if the marker was removed.
+ */
+MarkerClusterer.prototype.removeMarker = function(marker) {
+  var index = -1;
+  if (this.markers_.indexOf) {
+    index = this.markers_.indexOf(marker);
+  } else {
+    for (var i = 0, m; m = this.markers_[i]; i++) {
+      if (m == marker) {
+        index = i;
+        continue;
+      }
+    }
+  }
+
+  if (index == -1) {
+    // Marker is not in our list of markers.
+    return false;
+  }
+
+  this.markers_.splice(index, 1);
+  marker.setVisible(false);
+  marker.setMap(null);
+
+  this.resetViewport();
+  this.redraw();
+  return true;
+};
+
+
+/**
+ * Sets the clusterer's ready state.
+ *
+ * @param {boolean} ready The state.
+ * @private
+ */
+MarkerClusterer.prototype.setReady_ = function(ready) {
+  if (!this.ready_) {
+    this.ready_ = ready;
+    this.createClusters_();
+  }
+};
+
+
+/**
+ * Returns the number of clusters in the clusterer.
+ *
+ * @return {number} The number of clusters.
+ */
+MarkerClusterer.prototype.getTotalClusters = function() {
+  return this.clusters_.length;
+};
+
+
+/**
+ * Returns the google map that the clusterer is associated with.
+ *
+ * @return {google.maps.Map} The map.
+ */
+MarkerClusterer.prototype.getMap = function() {
+  return this.map_;
+};
+
+
+/**
+ * Sets the google map that the clusterer is associated with.
+ *
+ * @param {google.maps.Map} map The map.
+ */
+MarkerClusterer.prototype.setMap = function(map) {
+  this.map_ = map;
+};
+
+
+/**
+ * Returns the size of the grid.
+ *
+ * @return {number} The grid size.
+ */
+MarkerClusterer.prototype.getGridSize = function() {
+  return this.gridSize_;
+};
+
+
+/**
+ * Returns the size of the grid.
+ *
+ * @param {number} size The grid size.
+ */
+MarkerClusterer.prototype.setGridSize = function(size) {
+  this.gridSize_ = size;
+};
+
+
+/**
+ * Extends a bounds object by the grid size.
+ *
+ * @param {google.maps.LatLngBounds} bounds The bounds to extend.
+ * @return {google.maps.LatLngBounds} The extended bounds.
+ */
+MarkerClusterer.prototype.getExtendedBounds = function(bounds) {
+  var projection = this.getProjection();
+
+  // Turn the bounds into latlng.
+  var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
+      bounds.getNorthEast().lng());
+  var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
+      bounds.getSouthWest().lng());
+
+  // Convert the points to pixels and the extend out by the grid size.
+  var trPix = projection.fromLatLngToDivPixel(tr);
+  trPix.x += this.gridSize_;
+  trPix.y -= this.gridSize_;
+
+  var blPix = projection.fromLatLngToDivPixel(bl);
+  blPix.x -= this.gridSize_;
+  blPix.y += this.gridSize_;
+
+  // Convert the pixel points back to LatLng
+  var ne = projection.fromDivPixelToLatLng(trPix);
+  var sw = projection.fromDivPixelToLatLng(blPix);
+
+  // Extend the bounds to contain the new bounds.
+  bounds.extend(ne);
+  bounds.extend(sw);
+
+  return bounds;
+};
+
+
+/**
+ * Determins if a marker is contained in a bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @param {google.maps.LatLngBounds} bounds The bounds to check against.
+ * @return {boolean} True if the marker is in the bounds.
+ * @private
+ */
+MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
+  return bounds.contains(marker.getPosition());
+};
+
+
+/**
+ * Clears all clusters and markers from the clusterer.
+ */
+MarkerClusterer.prototype.clearMarkers = function() {
+  this.resetViewport();
+
+  // Set the markers a empty array.
+  this.markers_ = [];
+};
+
+
+/**
+ * Clears all existing clusters and recreates them.
+ */
+MarkerClusterer.prototype.resetViewport = function() {
+  // Remove all the clusters
+  for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
+    cluster.remove();
+  }
+
+  // Reset the markers to not be added and to be invisible.
+  for (var i = 0, marker; marker = this.markers_[i]; i++) {
+    marker.isAdded = false;
+    marker.setMap(null);
+    marker.setVisible(false);
+  }
+
+  this.clusters_ = [];
+};
+
+
+/**
+ * Redraws the clusters.
+ */
+MarkerClusterer.prototype.redraw = function() {
+  this.createClusters_();
+};
+
+
+/**
+ * Creates the clusters.
+ *
+ * @private
+ */
+MarkerClusterer.prototype.createClusters_ = function() {
+  if (!this.ready_) {
+    return;
+  }
+
+  // Get our current map view bounds.
+  // Create a new bounds object so we don't affect the map.
+  var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
+      this.map_.getBounds().getNorthEast());
+  var bounds = this.getExtendedBounds(mapBounds);
+
+  for (var i = 0, marker; marker = this.markers_[i]; i++) {
+    var added = false;
+    if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
+      for (var j = 0, cluster; cluster = this.clusters_[j]; j++) {
+        if (!added && cluster.getCenter() &&
+            cluster.isMarkerInClusterBounds(marker)) {
+          added = true;
+          cluster.addMarker(marker);
+          break;
+        }
+      }
+
+      if (!added) {
+        // Create a new cluster.
+        var cluster = new Cluster(this);
+        cluster.addMarker(marker);
+        this.clusters_.push(cluster);
+      }
+    }
+  }
+};
+
+
+/**
+ * A cluster that contains markers.
+ *
+ * @param {MarkerClusterer} markerClusterer The markerclusterer that this
+ *     cluster is associated with.
+ * @constructor
+ * @ignore
+ */
+function Cluster(markerClusterer) {
+  this.markerClusterer_ = markerClusterer;
+  this.map_ = markerClusterer.getMap();
+  this.gridSize_ = markerClusterer.getGridSize();
+  this.averageCenter_ = markerClusterer.isAverageCenter();
+  this.center_ = null;
+  this.markers_ = [];
+  this.bounds_ = null;
+  this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
+      markerClusterer.getGridSize());
+}
+
+/**
+ * Determins if a marker is already added to the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker is already added.
+ */
+Cluster.prototype.isMarkerAlreadyAdded = function(marker) {
+  if (this.markers_.indexOf) {
+    return this.markers_.indexOf(marker) != -1;
+  } else {
+    for (var i = 0, m; m = this.markers_[i]; i++) {
+      if (m == marker) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
+
+
+/**
+ * Add a marker the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @return {boolean} True if the marker was added.
+ */
+Cluster.prototype.addMarker = function(marker) {
+  if (this.isMarkerAlreadyAdded(marker)) {
+    return false;
+  }
+
+  if (!this.center_) {
+    this.center_ = marker.getPosition();
+    this.calculateBounds_();
+  } else {
+    if (this.averageCenter_) {
+      var lat = (this.center_.lat() + marker.getPosition().lat()) / 2;
+      var lng = (this.center_.lng() + marker.getPosition().lng()) / 2;
+      this.center_ = new google.maps.LatLng(lat, lng);
+      this.calculateBounds_();
+    }
+  }
+
+
+  if (this.markers_.length == 0) {
+    // Only 1 marker in this cluster so show the marker.
+    marker.setMap(this.map_);
+    marker.setVisible(true);
+  } else if (this.markers_.length == 1) {
+    // Hide the 1 marker that was showing.
+    this.markers_[0].setMap(null);
+    this.markers_[0].setVisible(false);
+  }
+
+  marker.isAdded = true;
+  this.markers_.push(marker);
+
+  this.updateIcon();
+  return true;
+};
+
+
+/**
+ * Returns the marker clusterer that the cluster is associated with.
+ *
+ * @return {MarkerClusterer} The associated marker clusterer.
+ */
+Cluster.prototype.getMarkerClusterer = function() {
+  return this.markerClusterer_;
+};
+
+
+/**
+ * Returns the bounds of the cluster.
+ *
+ * @return {google.maps.LatLngBounds} the cluster bounds.
+ */
+Cluster.prototype.getBounds = function() {
+  this.calculateBounds_();
+  return this.bounds_;
+};
+
+
+/**
+ * Removes the cluster
+ */
+Cluster.prototype.remove = function() {
+  this.clusterIcon_.remove();
+  this.markers_.length = 0;
+  delete this.markers_;
+};
+
+
+/**
+ * Returns the center of the cluster.
+ *
+ * @return {number} The cluster center.
+ */
+Cluster.prototype.getSize = function() {
+  return this.markers_.length;
+};
+
+
+/**
+ * Returns the center of the cluster.
+ *
+ * @return {Array.<google.maps.Marker>} The cluster center.
+ */
+Cluster.prototype.getMarkers = function() {
+  return this.markers_;
+};
+
+
+/**
+ * Returns the center of the cluster.
+ *
+ * @return {google.maps.LatLng} The cluster center.
+ */
+Cluster.prototype.getCenter = function() {
+  return this.center_;
+};
+
+
+/**
+ * Calculated the bounds of the cluster with the grid.
+ *
+ * @private
+ */
+Cluster.prototype.calculateBounds_ = function() {
+  var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+  this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
+};
+
+
+/**
+ * Determines if a marker lies in the clusters bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker lies in the bounds.
+ */
+Cluster.prototype.isMarkerInClusterBounds = function(marker) {
+  return this.bounds_.contains(marker.getPosition());
+};
+
+
+/**
+ * Returns the map that the cluster is associated with.
+ *
+ * @return {google.maps.Map} The map.
+ */
+Cluster.prototype.getMap = function() {
+  return this.map_;
+};
+
+
+/**
+ * Updates the cluster icon
+ */
+Cluster.prototype.updateIcon = function() {
+  var zoom = this.map_.getZoom();
+  var mz = this.markerClusterer_.getMaxZoom();
+
+  if (zoom > mz) {
+    // The zoom is greater than our max zoom so show all the markers in cluster.
+    for (var i = 0, marker; marker = this.markers_[i]; i++) {
+      marker.setMap(this.map_);
+      marker.setVisible(true);
+    }
+    return;
+  }
+
+  if (this.markers_.length < 2) {
+    // We have 0 or 1 markers so hide the icon.
+    this.clusterIcon_.hide();
+    return;
+  }
+
+  var numStyles = this.markerClusterer_.getStyles().length;
+  var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
+  this.clusterIcon_.setCenter(this.center_);
+  this.clusterIcon_.setSums(sums);
+  this.clusterIcon_.show();
+};
+
+
+/**
+ * A cluster icon
+ *
+ * @param {Cluster} cluster The cluster to be associated with.
+ * @param {Object} styles An object that has style properties:
+ *     'url': (string) The image url.
+ *     'height': (number) The image height.
+ *     'width': (number) The image width.
+ *     'anchor': (Array) The anchor position of the label text.
+ *     'textColor': (string) The text color.
+ *     'textSize': (number) The text size.
+ * @param {number} opt_padding Optional padding to apply to the cluster icon.
+ * @constructor
+ * @extends google.maps.OverlayView
+ * @ignore
+ */
+function ClusterIcon(cluster, styles, opt_padding) {
+  cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
+
+  this.styles_ = styles;
+  this.padding_ = opt_padding || 0;
+  this.cluster_ = cluster;
+  this.center_ = null;
+  this.map_ = cluster.getMap();
+  this.div_ = null;
+  this.sums_ = null;
+  this.visible_ = false;
+
+  this.setMap(this.map_);
+}
+
+
+/**
+ * Triggers the clusterclick event and zoom's if the option is set.
+ */
+ClusterIcon.prototype.triggerClusterClick = function() {
+  var markerClusterer = this.cluster_.getMarkerClusterer();
+
+  // Trigger the clusterclick event.
+  google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
+
+  if (markerClusterer.isZoomOnClick()) {
+    // Center the map on this cluster.
+    this.map_.panTo(this.cluster_.getCenter());
+
+    // Zoom into the cluster.
+    this.map_.fitBounds(this.cluster_.getBounds());
+  }
+};
+
+
+/**
+ * Adding the cluster icon to the dom.
+ * @ignore
+ */
+ClusterIcon.prototype.onAdd = function() {
+  this.div_ = document.createElement('DIV');
+  if (this.visible_) {
+    var pos = this.getPosFromLatLng_(this.center_);
+    this.div_.style.cssText = this.createCss(pos);
+    this.div_.innerHTML = this.sums_.text;
+  }
+
+  var panes = this.getPanes();
+  panes.overlayImage.appendChild(this.div_);
+
+  var that = this;
+  google.maps.event.addDomListener(this.div_, 'click', function() {
+    that.triggerClusterClick();
+  });
+};
+
+
+/**
+ * Returns the position to place the div dending on the latlng.
+ *
+ * @param {google.maps.LatLng} latlng The position in latlng.
+ * @return {google.maps.Point} The position in pixels.
+ * @private
+ */
+ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
+  var pos = this.getProjection().fromLatLngToDivPixel(latlng);
+  pos.x -= parseInt(this.width_ / 2, 10);
+  pos.y -= parseInt(this.height_ / 2, 10);
+  return pos;
+};
+
+
+/**
+ * Draw the icon.
+ * @ignore
+ */
+ClusterIcon.prototype.draw = function() {
+  if (this.visible_) {
+    var pos = this.getPosFromLatLng_(this.center_);
+    this.div_.style.top = pos.y + 'px';
+    this.div_.style.left = pos.x + 'px';
+  }
+};
+
+
+/**
+ * Hide the icon.
+ */
+ClusterIcon.prototype.hide = function() {
+  if (this.div_) {
+    this.div_.style.display = 'none';
+  }
+  this.visible_ = false;
+};
+
+
+/**
+ * Position and show the icon.
+ */
+ClusterIcon.prototype.show = function() {
+  if (this.div_) {
+    var pos = this.getPosFromLatLng_(this.center_);
+    this.div_.style.cssText = this.createCss(pos);
+    this.div_.style.display = '';
+  }
+  this.visible_ = true;
+};
+
+
+/**
+ * Remove the icon from the map
+ */
+ClusterIcon.prototype.remove = function() {
+  this.setMap(null);
+};
+
+
+/**
+ * Implementation of the onRemove interface.
+ * @ignore
+ */
+ClusterIcon.prototype.onRemove = function() {
+  if (this.div_ && this.div_.parentNode) {
+    this.hide();
+    this.div_.parentNode.removeChild(this.div_);
+    this.div_ = null;
+  }
+};
+
+
+/**
+ * Set the sums of the icon.
+ *
+ * @param {Object} sums The sums containing:
+ *   'text': (string) The text to display in the icon.
+ *   'index': (number) The style index of the icon.
+ */
+ClusterIcon.prototype.setSums = function(sums) {
+  this.sums_ = sums;
+  this.text_ = sums.text;
+  this.index_ = sums.index;
+  if (this.div_) {
+    this.div_.innerHTML = sums.text;
+  }
+
+  this.useStyle();
+};
+
+
+/**
+ * Sets the icon to the the styles.
+ */
+ClusterIcon.prototype.useStyle = function() {
+  var index = Math.max(0, this.sums_.index - 1);
+  index = Math.min(this.styles_.length - 1, index);
+  var style = this.styles_[index];
+  this.url_ = style['url'];
+  this.height_ = style['height'];
+  this.width_ = style['width'];
+  this.textColor_ = style['textColor'];
+  this.anchor = style['anchor'];
+  this.textSize_ = style['textSize'];
+};
+
+
+/**
+ * Sets the center of the icon.
+ *
+ * @param {google.maps.LatLng} center The latlng to set as the center.
+ */
+ClusterIcon.prototype.setCenter = function(center) {
+  this.center_ = center;
+};
+
+
+/**
+ * Create the css text based on the position of the icon.
+ *
+ * @param {google.maps.Point} pos The position.
+ * @return {string} The css style text.
+ */
+ClusterIcon.prototype.createCss = function(pos) {
+  var style = [];
+  if (document.all) {
+    style.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
+        'sizingMethod=scale,src="' + this.url_ + '");');
+  } else {
+    style.push('background:url(' + this.url_ + ');');
+  }
+
+  if (typeof this.anchor_ === 'object') {
+    if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
+        this.anchor_[0] < this.height_) {
+      style.push('height:' + (this.height_ - this.anchor_[0]) +
+          'px; padding-top:' + this.anchor_[0] + 'px;');
+    } else {
+      style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
+          'px;');
+    }
+    if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
+        this.anchor_[1] < this.width_) {
+      style.push('width:' + (this.width_ - this.anchor_[1]) +
+          'px; padding-left:' + this.anchor_[1] + 'px;');
+    } else {
+      style.push('width:' + this.width_ + 'px; text-align:center;');
+    }
+  } else {
+    style.push('height:' + this.height_ + 'px; line-height:' +
+        this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
+  }
+
+  var txtColor = this.textColor_ ? this.textColor_ : 'black';
+  var txtSize = this.textSize_ ? this.textSize_ : 11;
+
+  style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
+      pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
+      txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
+  return style.join('');
+};
+
+
+// Export Symbols for Closure
+// If you are not going to compile with closure then you can remove the
+// code below.
+window['MarkerClusterer'] = MarkerClusterer;
+MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker;
+MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers;
+MarkerClusterer.prototype['clearMarkers'] =
+    MarkerClusterer.prototype.clearMarkers;
+MarkerClusterer.prototype['getCalculator'] =
+    MarkerClusterer.prototype.getCalculator;
+MarkerClusterer.prototype['getGridSize'] =
+    MarkerClusterer.prototype.getGridSize;
+MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap;
+MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers;
+MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom;
+MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles;
+MarkerClusterer.prototype['getTotalClusters'] =
+    MarkerClusterer.prototype.getTotalClusters;
+MarkerClusterer.prototype['getTotalMarkers'] =
+    MarkerClusterer.prototype.getTotalMarkers;
+MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw;
+MarkerClusterer.prototype['removeMarker'] =
+    MarkerClusterer.prototype.removeMarker;
+MarkerClusterer.prototype['resetViewport'] =
+    MarkerClusterer.prototype.resetViewport;
+MarkerClusterer.prototype['setCalculator'] =
+    MarkerClusterer.prototype.setCalculator;
+MarkerClusterer.prototype['setGridSize'] =
+    MarkerClusterer.prototype.setGridSize;
+MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd;
+MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw;
+
+Cluster.prototype['getCenter'] = Cluster.prototype.getCenter;
+Cluster.prototype['getSize'] = Cluster.prototype.getSize;
+Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers;
+
+ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd;
+ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw;
+ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;
diff --git a/plugins/googlemap/markerclusterer_compiled.js b/plugins/googlemap/markerclusterer_compiled.js
new file mode 100644 (file)
index 0000000..ad74d1b
--- /dev/null
@@ -0,0 +1 @@
+function d(a){return function(b){this[a]=b}}function e(a){return function(){return this[a]}}var h; function i(a,b,c){this.extend(i,google.maps.OverlayView);this.b=a;this.a=[];this.l=[];this.Y=[53,56,66,78,90];this.h=[];this.z=false;c=c||{};this.f=c.gridSize||60;this.U=c.maxZoom||null;this.h=c.styles||[];this.T=c.imagePath||this.M;this.S=c.imageExtension||this.L;this.K=true;if(c.zoomOnClick!=undefined)this.K=c.zoomOnClick;this.p=false;if(c.averageCenter!=undefined)this.p=c.averageCenter;j(this);this.setMap(a);this.G=this.b.getZoom();var f=this;google.maps.event.addListener(this.b,"zoom_changed", function(){var g=f.b.mapTypes[f.b.getMapTypeId()].maxZoom,l=f.b.getZoom();if(!(l<0||l>g))if(f.G!=l){f.G=f.b.getZoom();f.m()}});google.maps.event.addListener(this.b,"idle",function(){f.j()});b&&b.length&&this.B(b,false)}h=i.prototype;h.M="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";h.L="png";h.extend=function(a,b){return function(c){for(property in c.prototype)this.prototype[property]=c.prototype[property];return this}.apply(a,[b])}; h.onAdd=function(){if(!this.z){this.z=true;m(this)}};h.R=function(){};h.draw=function(){};function j(a){if(!a.h.length)for(var b=0,c;c=a.Y[b];b++)a.h.push({url:a.T+(b+1)+"."+a.S,height:c,width:c})}h.w=e("h");h.v=e("a");h.Q=e("a");h.F=function(){return this.U||this.b.mapTypes[this.b.getMapTypeId()].maxZoom};h.C=function(a,b){for(var c=0,f=a.length,g=f;g!==0;){g=parseInt(g/10,10);c++}c=Math.min(c,b);return{text:f,index:c}};h.W=d("C");h.D=e("C"); h.B=function(a,b){for(var c=0,f;f=a[c];c++)n(this,f);b||this.j()};function n(a,b){b.setVisible(false);b.setMap(null);b.r=false;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.r=false;a.m();a.j()});a.a.push(b)}h.o=function(a,b){n(this,a);b||this.j()};h.V=function(a){var b=-1;if(this.a.indexOf)b=this.a.indexOf(a);else for(var c=0,f;f=this.a[c];c++)if(f==a)b=c;if(b==-1)return false;this.a.splice(b,1);a.setVisible(false);a.setMap(null);this.m();this.j();return true};h.P=function(){return this.l.length}; h.getMap=e("b");h.setMap=d("b");h.u=e("f");h.X=d("f");function o(a,b){var c=a.getProjection(),f=new google.maps.LatLng(b.getNorthEast().lat(),b.getNorthEast().lng()),g=new google.maps.LatLng(b.getSouthWest().lat(),b.getSouthWest().lng());f=c.fromLatLngToDivPixel(f);f.x+=a.f;f.y-=a.f;g=c.fromLatLngToDivPixel(g);g.x-=a.f;g.y+=a.f;f=c.fromDivPixelToLatLng(f);c=c.fromDivPixelToLatLng(g);b.extend(f);b.extend(c);return b}h.N=function(){this.m();this.a=[]}; h.m=function(){for(var a=0,b;b=this.l[a];a++)b.remove();for(a=0;b=this.a[a];a++){b.r=false;b.setMap(null);b.setVisible(false)}this.l=[]};h.j=function(){m(this)};function m(a){if(a.z)for(var b=o(a,new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast())),c=0,f;f=a.a[c];c++){var g=false;if(!f.r&&b.contains(f.getPosition())){for(var l=0,k;k=a.l[l];l++)if(!g&&k.getCenter()&&k.t.contains(f.getPosition())){g=true;k.o(f);break}if(!g){k=new p(a);k.o(f);a.l.push(k)}}}} function p(a){this.i=a;this.b=a.getMap();this.f=a.u();this.p=a.p;this.d=null;this.a=[];this.t=null;this.k=new q(this,a.w(),a.u())}h=p.prototype; h.o=function(a){var b;a:if(this.a.indexOf)b=this.a.indexOf(a)!=-1;else{b=0;for(var c;c=this.a[b];b++)if(c==a){b=true;break a}b=false}if(b)return false;if(this.d){if(this.p){b=(this.d.lat()+a.getPosition().lat())/2;c=(this.d.lng()+a.getPosition().lng())/2;this.d=new google.maps.LatLng(b,c);r(this)}}else{this.d=a.getPosition();r(this)}if(this.a.length==0){a.setMap(this.b);a.setVisible(true)}else if(this.a.length==1){this.a[0].setMap(null);this.a[0].setVisible(false)}a.r=true;this.a.push(a);if(this.b.getZoom()> this.i.F())for(a=0;b=this.a[a];a++){b.setMap(this.b);b.setVisible(true)}else if(this.a.length<2)s(this.k);else{a=this.i.w().length;b=this.i.D()(this.a,a);this.k.setCenter(this.d);a=this.k;a.A=b;a.aa=b.text;a.Z=b.index;if(a.c)a.c.innerHTML=b.text;b=Math.max(0,a.A.index-1);b=Math.min(a.h.length-1,b);b=a.h[b];a.J=b.url;a.g=b.height;a.n=b.width;a.H=b.textColor;a.anchor=b.anchor;a.I=b.textSize;this.k.show()}return true};h.getBounds=function(){r(this);return this.t}; h.remove=function(){this.k.remove();this.a.length=0;delete this.a};h.O=function(){return this.a.length};h.v=e("a");h.getCenter=e("d");function r(a){a.t=o(a.i,new google.maps.LatLngBounds(a.d,a.d))}h.getMap=e("b");function q(a,b,c){a.i.extend(q,google.maps.OverlayView);this.h=b;this.$=c||0;this.q=a;this.d=null;this.b=a.getMap();this.A=this.c=null;this.s=false;this.setMap(this.b)}h=q.prototype; h.onAdd=function(){this.c=document.createElement("DIV");if(this.s){this.c.style.cssText=t(this,u(this,this.d));this.c.innerHTML=this.A.text}this.getPanes().overlayImage.appendChild(this.c);var a=this;google.maps.event.addDomListener(this.c,"click",function(){var b=a.q.i;google.maps.event.trigger(b,"clusterclick",a.q);if(b.K){a.b.panTo(a.q.getCenter());a.b.fitBounds(a.q.getBounds())}})}; function u(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.n/2,10);c.y-=parseInt(a.g/2,10);return c}h.draw=function(){if(this.s){var a=u(this,this.d);this.c.style.top=a.y+"px";this.c.style.left=a.x+"px"}};function s(a){if(a.c)a.c.style.display="none";a.s=false}h.show=function(){if(this.c){this.c.style.cssText=t(this,u(this,this.d));this.c.style.display=""}this.s=true};h.remove=function(){this.setMap(null)}; h.onRemove=function(){if(this.c&&this.c.parentNode){s(this);this.c.parentNode.removeChild(this.c);this.c=null}};h.setCenter=d("d"); function t(a,b){var c=[];document.all?c.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+a.J+'");'):c.push("background:url("+a.J+");");if(typeof a.e==="object"){typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]<a.g?c.push("height:"+(a.g-a.e[0])+"px; padding-top:"+a.e[0]+"px;"):c.push("height:"+a.g+"px; line-height:"+a.g+"px;");typeof a.e[1]==="number"&&a.e[1]>0&&a.e[1]<a.n?c.push("width:"+(a.n-a.e[1])+"px; padding-left:"+a.e[1]+"px;"):c.push("width:"+a.n+"px; text-align:center;")}else c.push("height:"+ a.g+"px; line-height:"+a.g+"px; width:"+a.n+"px; text-align:center;");c.push("cursor:pointer; top:"+b.y+"px; left:"+b.x+"px; color:"+(a.H?a.H:"black")+"; position:absolute; font-size:"+(a.I?a.I:11)+"px; font-family:Arial,sans-serif; font-weight:bold");return c.join("")}window.MarkerClusterer=i;i.prototype.addMarker=i.prototype.o;i.prototype.addMarkers=i.prototype.B;i.prototype.clearMarkers=i.prototype.N;i.prototype.getCalculator=i.prototype.D;i.prototype.getGridSize=i.prototype.u; i.prototype.getMap=i.prototype.getMap;i.prototype.getMarkers=i.prototype.v;i.prototype.getMaxZoom=i.prototype.F;i.prototype.getStyles=i.prototype.w;i.prototype.getTotalClusters=i.prototype.P;i.prototype.getTotalMarkers=i.prototype.Q;i.prototype.redraw=i.prototype.j;i.prototype.removeMarker=i.prototype.V;i.prototype.resetViewport=i.prototype.m;i.prototype.setCalculator=i.prototype.W;i.prototype.setGridSize=i.prototype.X;i.prototype.onAdd=i.prototype.onAdd;i.prototype.draw=i.prototype.draw; i.prototype.idle=i.prototype.R;p.prototype.getCenter=p.prototype.getCenter;p.prototype.getSize=p.prototype.O;p.prototype.getMarkers=p.prototype.v;q.prototype.onAdd=q.prototype.onAdd;q.prototype.draw=q.prototype.draw;q.prototype.onRemove=q.prototype.onRemove;
diff --git a/plugins/googlemap/markerclusterer_packed.js b/plugins/googlemap/markerclusterer_packed.js
new file mode 100644 (file)
index 0000000..cbcac71
--- /dev/null
@@ -0,0 +1 @@
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 5(c,d,e){3.Z(5,u.v.23);3.l=c;3.k=[];3.N=[];3.24=[2H,2I,2J,2K,2L];3.F=[];3.1d=r;7 f=e||{};3.G=f[\'2M\']||2N;3.1s=f[\'1t\']||w;3.F=f[\'2O\']||[];3.25=f[\'2P\']||3.26;3.27=f[\'2Q\']||3.28;3.1u=C;8(f[\'29\']!=2a){3.1u=f[\'29\']}3.12=r;8(f[\'2b\']!=2a){3.12=f[\'2b\']}3.2c();3.D(c);3.1v=3.l.1e();7 g=3;u.v.13.1w(3.l,\'2R\',6(){7 a=g.l.2d[g.l.2e()].1t;7 b=g.l.1e();8(b<0||b>a){9}8(g.1v!=b){g.1v=g.l.1e();g.K()}});u.v.13.1w(3.l,\'1f\',6(){g.H()});8(d&&d.A){3.1g(d,r)}}5.4.26=\'2S://u-v-2T-2U-2V.2W.2X/2Y/2Z/30/\'+\'31/m\';5.4.28=\'32\';5.4.Z=6(b,c){9(6(a){E(1x 33 a.4){3.4[1x]=a.4[1x]}9 3}).34(b,[c])};5.4.O=6(){3.2f(C)};5.4.1f=6(){};5.4.P=6(){};5.4.2c=6(){8(3.F.A){9}E(7 i=0,14;14=3.24[i];i++){3.F.z({1y:3.25+(i+1)+\'.\'+3.27,L:14,15:14})}};5.4.35=6(a){3.F=a};5.4.16=6(){9 3.F};5.4.2g=6(){9 3.1u};5.4.2h=6(){9 3.12};5.4.Q=6(){9 3.k};5.4.1z=6(){9 3.k};5.4.36=6(a){3.1s=a};5.4.1h=6(){9 3.1s||3.l.2d[3.l.2e()].1t};5.4.1A=6(a,b){7 c=0;7 d=a.A;7 e=d;37(e!==0){e=1B(e/10,10);c++}c=1C.2i(c,b);9{R:d,1D:c}};5.4.1E=6(a){3.1A=a};5.4.1i=6(){9 3.1A};5.4.1g=6(a,b){E(7 i=0,q;q=a[i];i++){3.1F(q)}8(!b){3.H()}};5.4.1F=6(a){a.S(r);a.D(w);a.17=r;8(a[\'38\']){7 b=3;u.v.13.1w(a,\'39\',6(){a.17=r;b.K();b.H()})}3.k.z(a)};5.4.T=6(a,b){3.1F(a);8(!b){3.H()}};5.4.1G=6(a){7 b=-1;8(3.k.1j){b=3.k.1j(a)}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){b=i;3a}}}8(b==-1){9 r}3.k.3b(b,1);a.S(r);a.D(w);3.K();3.H();9 C};5.4.2f=6(a){8(!3.1d){3.1d=a;3.1H()}};5.4.1I=6(){9 3.N.A};5.4.U=6(){9 3.l};5.4.D=6(a){3.l=a};5.4.18=6(){9 3.G};5.4.1J=6(a){3.G=a};5.4.1K=6(a){7 b=3.2j();7 c=M u.v.1L(a.1M().1k(),a.1M().1l());7 d=M u.v.1L(a.1N().1k(),a.1N().1l());7 e=b.1O(c);e.x+=3.G;e.y-=3.G;7 f=b.1O(d);f.x-=3.G;f.y+=3.G;7 g=b.2k(e);7 h=b.2k(f);a.Z(g);a.Z(h);9 a};5.4.2l=6(a,b){9 b.2m(a.19())};5.4.1P=6(){3.K();3.k=[]};5.4.K=6(){E(7 i=0,1Q;1Q=3.N[i];i++){1Q.1m()}E(7 i=0,q;q=3.k[i];i++){q.17=r;q.D(w);q.S(r)}3.N=[]};5.4.H=6(){3.1H()};5.4.1H=6(){8(!3.1d){9}7 a=M u.v.2n(3.l.1n().1N(),3.l.1n().1M());7 b=3.1K(a);E(7 i=0,q;q=3.k[i];i++){7 c=r;8(!q.17&&3.2l(q,b)){E(7 j=0,d;d=3.N[j];j++){8(!c&&d.1a()&&d.2o(q)){c=C;d.T(q);3c}}8(!c){7 d=M o(3);d.T(q);3.N.z(d)}}}};6 o(a){3.V=a;3.l=a.U();3.G=a.18();3.12=a.2h();3.s=w;3.k=[];3.1o=w;3.W=M n(3,a.16(),a.18())}o.4.2p=6(a){8(3.k.1j){9 3.k.1j(a)!=-1}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){9 C}}}9 r};o.4.T=6(a){8(3.2p(a)){9 r}8(!3.s){3.s=a.19();3.1p()}I{8(3.12){7 b=(3.s.1k()+a.19().1k())/2;7 c=(3.s.1l()+a.19().1l())/2;3.s=M u.v.1L(b,c);3.1p()}}8(3.k.A==0){a.D(3.l);a.S(C)}I 8(3.k.A==1){3.k[0].D(w);3.k[0].S(r)}a.17=C;3.k.z(a);3.2q();9 C};o.4.1R=6(){9 3.V};o.4.1n=6(){3.1p();9 3.1o};o.4.1m=6(){3.W.1m();3.k.A=0;3d 3.k};o.4.1S=6(){9 3.k.A};o.4.Q=6(){9 3.k};o.4.1a=6(){9 3.s};o.4.1p=6(){7 a=M u.v.2n(3.s,3.s);3.1o=3.V.1K(a)};o.4.2o=6(a){9 3.1o.2m(a.19())};o.4.U=6(){9 3.l};o.4.2q=6(){7 a=3.l.1e();7 b=3.V.1h();8(a>b){E(7 i=0,q;q=3.k[i];i++){q.D(3.l);q.S(C)}9}8(3.k.A<2){3.W.1T();9}7 c=3.V.16().A;7 d=3.V.1i()(3.k,c);3.W.2r(3.s);3.W.2s(d);3.W.2t()};6 n(a,b,c){a.1R().Z(n,u.v.23);3.F=b;3.3e=c||0;3.1b=a;3.s=w;3.l=a.U();3.p=w;3.1q=w;3.1c=r;3.D(3.l)}n.4.2u=6(){7 a=3.1b.1R();u.v.13.3f(a,\'3g\',3.1b);8(a.2g()){3.l.3h(3.1b.1a());3.l.3i(3.1b.1n())}};n.4.O=6(){3.p=2v.3j(\'3k\');8(3.1c){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.2x=3.1q.R}7 b=3.3l();b.3m.3n(3.p);7 c=3;u.v.13.3o(3.p,\'3p\',6(){c.2u()})};n.4.1r=6(a){7 b=3.2j().1O(a);b.x-=1B(3.Y/2,10);b.y-=1B(3.J/2,10);9 b};n.4.P=6(){8(3.1c){7 a=3.1r(3.s);3.p.X.1V=a.y+\'t\';3.p.X.1W=a.x+\'t\'}};n.4.1T=6(){8(3.p){3.p.X.2y=\'3q\'}3.1c=r};n.4.2t=6(){8(3.p){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.X.2y=\'\'}3.1c=C};n.4.1m=6(){3.D(w)};n.4.1X=6(){8(3.p&&3.p.2z){3.1T();3.p.2z.3r(3.p);3.p=w}};n.4.2s=6(a){3.1q=a;3.3s=a.R;3.3t=a.1D;8(3.p){3.p.2x=a.R}3.2A()};n.4.2A=6(){7 a=1C.3u(0,3.1q.1D-1);a=1C.2i(3.F.A-1,a);7 b=3.F[a];3.1Y=b[\'1y\'];3.J=b[\'L\'];3.Y=b[\'15\'];3.1Z=b[\'3v\'];3.2B=b[\'2B\'];3.20=b[\'3w\']};n.4.2r=6(a){3.s=a};n.4.1U=6(a){7 b=[];8(2v.3x){b.z(\'3y:3z:3A.3B.3C(\'+\'3D=3E,3F="\'+3.1Y+\'");\')}I{b.z(\'3G:1y(\'+3.1Y+\');\')}8(21 3.B===\'3H\'){8(21 3.B[0]===\'2C\'&&3.B[0]>0&&3.B[0]<3.J){b.z(\'L:\'+(3.J-3.B[0])+\'t; 2D-1V:\'+3.B[0]+\'t;\')}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t;\')}8(21 3.B[1]===\'2C\'&&3.B[1]>0&&3.B[1]<3.Y){b.z(\'15:\'+(3.Y-3.B[1])+\'t; 2D-1W:\'+3.B[1]+\'t;\')}I{b.z(\'15:\'+3.Y+\'t; R-2F:2G;\')}}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t; 15:\'+3.Y+\'t; R-2F:2G;\')}7 c=3.1Z?3.1Z:\'3I\';7 d=3.20?3.20:11;b.z(\'3J:3K; 1V:\'+a.y+\'t; 1W:\'+a.x+\'t; 3L:\'+c+\'; 3M:3N; 22-14:\'+d+\'t; 22-3O:3P,3Q-3R; 22-3S:3T\');9 b.3U(\'\')};3V[\'5\']=5;5.4[\'T\']=5.4.T;5.4[\'1g\']=5.4.1g;5.4[\'1P\']=5.4.1P;5.4[\'1i\']=5.4.1i;5.4[\'18\']=5.4.18;5.4[\'U\']=5.4.U;5.4[\'Q\']=5.4.Q;5.4[\'1h\']=5.4.1h;5.4[\'16\']=5.4.16;5.4[\'1I\']=5.4.1I;5.4[\'1z\']=5.4.1z;5.4[\'H\']=5.4.H;5.4[\'1G\']=5.4.1G;5.4[\'K\']=5.4.K;5.4[\'1E\']=5.4.1E;5.4[\'1J\']=5.4.1J;5.4[\'O\']=5.4.O;5.4[\'P\']=5.4.P;5.4[\'1f\']=5.4.1f;o.4[\'1a\']=o.4.1a;o.4[\'1S\']=o.4.1S;o.4[\'Q\']=o.4.Q;n.4[\'O\']=n.4.O;n.4[\'P\']=n.4.P;n.4[\'1X\']=n.4.1X;',62,244,'|||this|prototype|MarkerClusterer|function|var|if|return|||||||||||markers_|map_||ClusterIcon|Cluster|div_|marker|false|center_|px|google|maps|null|||push|length|anchor_|true|setMap|for|styles_|gridSize_|redraw|else|height_|resetViewport|height|new|clusters_|onAdd|draw|getMarkers|text|setVisible|addMarker|getMap|markerClusterer_|clusterIcon_|style|width_|extend|||averageCenter_|event|size|width|getStyles|isAdded|getGridSize|getPosition|getCenter|cluster_|visible_|ready_|getZoom|idle|addMarkers|getMaxZoom|getCalculator|indexOf|lat|lng|remove|getBounds|bounds_|calculateBounds_|sums_|getPosFromLatLng_|maxZoom_|maxZoom|zoomOnClick_|prevZoom_|addListener|property|url|getTotalMarkers|calculator_|parseInt|Math|index|setCalculator|pushMarkerTo_|removeMarker|createClusters_|getTotalClusters|setGridSize|getExtendedBounds|LatLng|getNorthEast|getSouthWest|fromLatLngToDivPixel|clearMarkers|cluster|getMarkerClusterer|getSize|hide|createCss|top|left|onRemove|url_|textColor_|textSize_|typeof|font|OverlayView|sizes|imagePath_|MARKER_CLUSTER_IMAGE_PATH_|imageExtension_|MARKER_CLUSTER_IMAGE_EXTENSION_|zoomOnClick|undefined|averageCenter|setupStyles_|mapTypes|getMapTypeId|setReady_|isZoomOnClick|isAverageCenter|min|getProjection|fromDivPixelToLatLng|isMarkerInBounds_|contains|LatLngBounds|isMarkerInClusterBounds|isMarkerAlreadyAdded|updateIcon|setCenter|setSums|show|triggerClusterClick|document|cssText|innerHTML|display|parentNode|useStyle|anchor|number|padding|line|align|center|53|56|66|78|90|gridSize|60|styles|imagePath|imageExtension|zoom_changed|http|utility|library|v3|googlecode|com|svn|trunk|markerclusterer|images|png|in|apply|setStyles|setMaxZoom|while|draggable|dragend|continue|splice|break|delete|padding_|trigger|clusterclick|panTo|fitBounds|createElement|DIV|getPanes|overlayImage|appendChild|addDomListener|click|none|removeChild|text_|index_|max|textColor|textSize|all|filter|progid|DXImageTransform|Microsoft|AlphaImageLoader|sizingMethod|scale|src|background|object|black|cursor|pointer|color|position|absolute|family|Arial|sans|serif|weight|bold|join|window'.split('|'),0,{}))
index f1ce698..6f97a0a 100644 (file)
@@ -43,7 +43,7 @@ def dashboard_view (request):
         sons=[ 
             QueryCode (
                 page=page,
-                title="Vizualize your query (no syntax highlight for now)",
+                title="Vizualize your query",
                 query=slices_query,
                 toggled=False,
                 ),
index 9139454..f62208b 100644 (file)
@@ -11,6 +11,7 @@ from manifold.manifoldquery import ManifoldQuery
 from plugins.stack.stack import Stack
 from plugins.tabs.tabs import Tabs
 from plugins.hazelnut.hazelnut import Hazelnut 
+from plugins.googlemap.googlemap import GoogleMap 
 from plugins.lists.slicelist import SliceList
 from plugins.querycode.querycode import QueryCode
 from plugins.quickfilter.quickfilter import QuickFilter
@@ -43,12 +44,14 @@ def slice_view (request, slicename=tmp_default_slice):
                 page=page,
                 title="2 tabs : w/ and w/o checkboxes",
                 domid='thetabs',
-                active_domid='checkboxes',
+                # active_domid='checkboxes',
+                active_domid='gmap',
                 sons=[
                     Hazelnut ( 
                         page=page,
                         title='a sample and simple hazelnut',
                         domid='simple',
+                        # tab's sons preferably turn this off
                         togglable=False,
                         # this is the query at the core of the slice list
                         query=main_query,
@@ -57,10 +60,11 @@ def slice_view (request, slicename=tmp_default_slice):
                         page=page,
                         title='with checkboxes',
                         domid='checkboxes',
+                        # tab's sons preferably turn this off
                         togglable=False,
-                        checkboxes=True,
                         # this is the query at the core of the slice list
                         query=main_query,
+                        checkboxes=True,
                         datatables_options = { 
                             # for now we turn off sorting on the checkboxes columns this way
                             # this of course should be automatic in hazelnut
@@ -69,6 +73,14 @@ def slice_view (request, slicename=tmp_default_slice):
                             'bLengthChange' : True,
                             },
                         ),
+                    GoogleMap (
+                        page=page,
+                        title='geographic view',
+                        domid='gmap',
+                        # tab's sons preferably turn this off
+                        togglable=False,
+                        query=main_query,
+                        ),
                     ]),
               Hazelnut ( 
                 page=page,
index 9189e71..b7ad65b 100644 (file)
@@ -17,7 +17,7 @@ from unfold.prelude import Prelude
 
 DEBUG= False
 #DEBUG= [ 'SimpleList' ]
-DEBUG=True
+#DEBUG=True
 
 # decorator to deflect calls on Plugin to its Prelude through self.page.prelude
 def to_prelude (method):
index f576ed3..8c90a19 100644 (file)
@@ -48,25 +48,32 @@ class Prelude:
     #    env['js_chunks']= '\n'.join(self.js_chunks)
     #    env['css_chunks']='\n'.join(self.css_chunks)
     #    return env
-    # together with this in layout-unfold2.html
+    # together with this in prelude.html
     # {% for js_file in js_files %} {% insert_str prelude js_file %} {% endfor %}
     # {% for css_file in css_files %} {% insert_str prelude css_file %} {% endfor %}
     # somehow however this would not work too well, 
     # probably insert_above is not powerful enough to handle that
     # 
-    # so a much simpler and safer approach is for use to compute the html header directly
+    # so a much simpler and safer approach is for us to compute the html header directly
+    # this requires  to filter on full urls
+    #
+    @staticmethod
+    def full_url (input):
+        if input.startswith("http://") or input.startswith("https://"):
+            return input
+        else:
+            from myslice.settings import STATIC_URL
+            return "%s%s"%(STATIC_URL,input)
+        
     def prelude_env (self): 
         env={}
-        env['js_files']=  self.js_files
-        env['css_files']= self.css_files
+        env['js_urls'] = [ Prelude.full_url (js_file) for js_file in self.js_files ]
+        env['css_urls'] = [ Prelude.full_url (css_file) for css_file in self.css_files ]
         env['js_chunks']= self.js_chunks
         env['css_chunks']=self.css_chunks
         if debug:
             print "prelude has %d js_files, %d css files, %d js chunks and %d css_chunks"%\
                 (len(self.js_files),len(self.css_files),len(self.js_chunks),len(self.css_chunks),)
-        # not sure how this should be done more cleanly
-        from myslice.settings import STATIC_URL
-        env ['STATIC_URL'] = STATIC_URL
         # render this with prelude.html and put the result in header_prelude
         header_prelude = render_to_string ('prelude.html',env)
         return { 'header_prelude' : header_prelude }
index 37e0e59..898624e 100644 (file)
@@ -1,6 +1,6 @@
-{% for js_file in js_files %} <script type='text/javascript' src='{{ STATIC_URL }}{{ js_file|safe }}'></script> 
+{% for js_url in js_urls %} <script type='text/javascript' src='{{ js_url|safe }}'></script> 
 {% endfor %}
-{% for css_file in css_files %} <link rel='stylesheet' type='text/css' href='{{ STATIC_URL }}{{ css_file|safe }}' /> 
+{% for css_url in css_urls %} <link rel='stylesheet' type='text/css' href='{{ css_url|safe }}' /> 
 {% endfor %}
 <script type="text/javascript"> {% for js_chunk in js_chunks %} {{ js_chunk|safe }} {% endfor %} </script>
 <style type="text/css"> {% for css_chunk in css_chunks %} {{ css_chunk|safe }} {% endfor %} </style>