]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Popup.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Popup.js
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 /**
7  * Class: OpenLayers.Popup
8  * A popup is a small div that can opened and closed on the map.
9  * Typically opened in response to clicking on a marker.  
10  * See <OpenLayers.Marker>.  Popup's don't require their own
11  * layer and are added the the map using the <OpenLayers.Map.addPopup>
12  * method.
13  *
14  * Example:
15  * (code)
16  * popup = new OpenLayers.Popup("chicken", 
17  *                    new OpenLayers.LonLat(5,40),
18  *                    new OpenLayers.Size(200,200),
19  *                    "example popup",
20  *                    true);
21  *       
22  * map.addPopup(popup);
23  * (end)
24  */
25 OpenLayers.Popup = OpenLayers.Class({
26
27     /** 
28      * Property: events  
29      * {<OpenLayers.Events>} custom event manager 
30      */
31     events: null,
32     
33     /** Property: id
34      * {String} the unique identifier assigned to this popup.
35      */
36     id: "",
37
38     /** 
39      * Property: lonlat 
40      * {<OpenLayers.LonLat>} the position of this popup on the map
41      */
42     lonlat: null,
43
44     /** 
45      * Property: div 
46      * {DOMElement} the div that contains this popup.
47      */
48     div: null,
49
50     /** 
51      * Property: contentSize 
52      * {<OpenLayers.Size>} the width and height of the content.
53      */
54     contentSize: null,    
55
56     /** 
57      * Property: size 
58      * {<OpenLayers.Size>} the width and height of the popup.
59      */
60     size: null,    
61
62     /** 
63      * Property: contentHTML 
64      * {String} An HTML string for this popup to display.
65      */
66     contentHTML: null,
67     
68     /** 
69      * Property: backgroundColor 
70      * {String} the background color used by the popup.
71      */
72     backgroundColor: "",
73     
74     /** 
75      * Property: opacity 
76      * {float} the opacity of this popup (between 0.0 and 1.0)
77      */
78     opacity: "",
79
80     /** 
81      * Property: border 
82      * {String} the border size of the popup.  (eg 2px)
83      */
84     border: "",
85     
86     /** 
87      * Property: contentDiv 
88      * {DOMElement} a reference to the element that holds the content of
89      *              the div.
90      */
91     contentDiv: null,
92     
93     /** 
94      * Property: groupDiv 
95      * {DOMElement} First and only child of 'div'. The group Div contains the
96      *     'contentDiv' and the 'closeDiv'.
97      */
98     groupDiv: null,
99
100     /** 
101      * Property: closeDiv
102      * {DOMElement} the optional closer image
103      */
104     closeDiv: null,
105
106     /** 
107      * APIProperty: autoSize
108      * {Boolean} Resize the popup to auto-fit the contents.
109      *     Default is false.
110      */
111     autoSize: false,
112
113     /**
114      * APIProperty: minSize
115      * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
116      */
117     minSize: null,
118
119     /**
120      * APIProperty: maxSize
121      * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
122      */
123     maxSize: null,
124
125     /** 
126      * Property: displayClass
127      * {String} The CSS class of the popup.
128      */
129     displayClass: "olPopup",
130
131     /** 
132      * Property: contentDisplayClass
133      * {String} The CSS class of the popup content div.
134      */
135     contentDisplayClass: "olPopupContent",
136
137     /** 
138      * Property: padding 
139      * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal 
140      *     padding of the content div inside the popup. This was originally
141      *     confused with the css padding as specified in style.css's 
142      *     'olPopupContent' class. We would like to get rid of this altogether,
143      *     except that it does come in handy for the framed and anchoredbubble
144      *     popups, who need to maintain yet another barrier between their 
145      *     content and the outer border of the popup itself. 
146      * 
147      *     Note that in order to not break API, we must continue to support 
148      *     this property being set as an integer. Really, though, we'd like to 
149      *     have this specified as a Bounds object so that user can specify
150      *     distinct left, top, right, bottom paddings. With the 3.0 release
151      *     we can make this only a bounds.
152      */
153     padding: 0,
154
155     /** 
156      * Property: disableFirefoxOverflowHack
157      * {Boolean} The hack for overflow in Firefox causes all elements 
158      *     to be re-drawn, which causes Flash elements to be 
159      *     re-initialized, which is troublesome.
160      *     With this property the hack can be disabled.
161      */
162     disableFirefoxOverflowHack: false,
163
164     /**
165      * Method: fixPadding
166      * To be removed in 3.0, this function merely helps us to deal with the 
167      *     case where the user may have set an integer value for padding, 
168      *     instead of an <OpenLayers.Bounds> object.
169      */
170     fixPadding: function() {
171         if (typeof this.padding == "number") {
172             this.padding = new OpenLayers.Bounds(
173                 this.padding, this.padding, this.padding, this.padding
174             );
175         }
176     },
177
178     /**
179      * APIProperty: panMapIfOutOfView
180      * {Boolean} When drawn, pan map such that the entire popup is visible in
181      *     the current viewport (if necessary).
182      *     Default is false.
183      */
184     panMapIfOutOfView: false,
185     
186     /**
187      * APIProperty: keepInMap 
188      * {Boolean} If panMapIfOutOfView is false, and this property is true, 
189      *     contrain the popup such that it always fits in the available map
190      *     space. By default, this is not set on the base class. If you are
191      *     creating popups that are near map edges and not allowing pannning,
192      *     and especially if you have a popup which has a
193      *     fixedRelativePosition, setting this to false may be a smart thing to
194      *     do. Subclasses may want to override this setting.
195      *   
196      *     Default is false.
197      */
198     keepInMap: false,
199
200     /**
201      * APIProperty: closeOnMove
202      * {Boolean} When map pans, close the popup.
203      *     Default is false.
204      */
205     closeOnMove: false,
206     
207     /** 
208      * Property: map 
209      * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
210      */
211     map: null,
212
213     /** 
214     * Constructor: OpenLayers.Popup
215     * Create a popup.
216     * 
217     * Parameters: 
218     * id - {String} a unqiue identifier for this popup.  If null is passed
219     *               an identifier will be automatically generated. 
220     * lonlat - {<OpenLayers.LonLat>}  The position on the map the popup will
221     *                                 be shown.
222     * contentSize - {<OpenLayers.Size>} The size of the content.
223     * contentHTML - {String}          An HTML string to display inside the   
224     *                                 popup.
225     * closeBox - {Boolean}            Whether to display a close box inside
226     *                                 the popup.
227     * closeBoxCallback - {Function}   Function to be called on closeBox click.
228     */
229     initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
230         if (id == null) {
231             id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
232         }
233
234         this.id = id;
235         this.lonlat = lonlat;
236
237         this.contentSize = (contentSize != null) ? contentSize 
238                                   : new OpenLayers.Size(
239                                                    OpenLayers.Popup.WIDTH,
240                                                    OpenLayers.Popup.HEIGHT);
241         if (contentHTML != null) { 
242              this.contentHTML = contentHTML;
243         }
244         this.backgroundColor = OpenLayers.Popup.COLOR;
245         this.opacity = OpenLayers.Popup.OPACITY;
246         this.border = OpenLayers.Popup.BORDER;
247
248         this.div = OpenLayers.Util.createDiv(this.id, null, null, 
249                                              null, null, null, "hidden");
250         this.div.className = this.displayClass;
251         
252         var groupDivId = this.id + "_GroupDiv";
253         this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null, 
254                                                     null, "relative", null,
255                                                     "hidden");
256
257         var id = this.div.id + "_contentDiv";
258         this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(), 
259                                                     null, "relative");
260         this.contentDiv.className = this.contentDisplayClass;
261         this.groupDiv.appendChild(this.contentDiv);
262         this.div.appendChild(this.groupDiv);
263
264         if (closeBox) {
265             this.addCloseBox(closeBoxCallback);
266         } 
267
268         this.registerEvents();
269     },
270
271     /** 
272      * Method: destroy
273      * nullify references to prevent circular references and memory leaks
274      */
275     destroy: function() {
276
277         this.id = null;
278         this.lonlat = null;
279         this.size = null;
280         this.contentHTML = null;
281         
282         this.backgroundColor = null;
283         this.opacity = null;
284         this.border = null;
285         
286         if (this.closeOnMove && this.map) {
287             this.map.events.unregister("movestart", this, this.hide);
288         }
289
290         this.events.destroy();
291         this.events = null;
292         
293         if (this.closeDiv) {
294             OpenLayers.Event.stopObservingElement(this.closeDiv); 
295             this.groupDiv.removeChild(this.closeDiv);
296         }
297         this.closeDiv = null;
298         
299         this.div.removeChild(this.groupDiv);
300         this.groupDiv = null;
301
302         if (this.map != null) {
303             this.map.removePopup(this);
304         }
305         this.map = null;
306         this.div = null;
307         
308         this.autoSize = null;
309         this.minSize = null;
310         this.maxSize = null;
311         this.padding = null;
312         this.panMapIfOutOfView = null;
313     },
314
315     /** 
316     * Method: draw
317     * Constructs the elements that make up the popup.
318     *
319     * Parameters:
320     * px - {<OpenLayers.Pixel>} the position the popup in pixels.
321     * 
322     * Returns:
323     * {DOMElement} Reference to a div that contains the drawn popup
324     */
325     draw: function(px) {
326         if (px == null) {
327             if ((this.lonlat != null) && (this.map != null)) {
328                 px = this.map.getLayerPxFromLonLat(this.lonlat);
329             }
330         }
331
332         // this assumes that this.map already exists, which is okay because 
333         // this.draw is only called once the popup has been added to the map.
334         if (this.closeOnMove) {
335             this.map.events.register("movestart", this, this.hide);
336         }
337         
338         //listen to movestart, moveend to disable overflow (FF bug)
339         if (!this.disableFirefoxOverflowHack && OpenLayers.Util.getBrowserName() == 'firefox') {
340             this.map.events.register("movestart", this, function() {
341                 var style = document.defaultView.getComputedStyle(
342                     this.contentDiv, null
343                 );
344                 var currentOverflow = style.getPropertyValue("overflow");
345                 if (currentOverflow != "hidden") {
346                     this.contentDiv._oldOverflow = currentOverflow;
347                     this.contentDiv.style.overflow = "hidden";
348                 }
349             });
350             this.map.events.register("moveend", this, function() {
351                 var oldOverflow = this.contentDiv._oldOverflow;
352                 if (oldOverflow) {
353                     this.contentDiv.style.overflow = oldOverflow;
354                     this.contentDiv._oldOverflow = null;
355                 }
356             });
357         }
358
359         this.moveTo(px);
360         if (!this.autoSize && !this.size) {
361             this.setSize(this.contentSize);
362         }
363         this.setBackgroundColor();
364         this.setOpacity();
365         this.setBorder();
366         this.setContentHTML();
367         
368         if (this.panMapIfOutOfView) {
369             this.panIntoView();
370         }    
371
372         return this.div;
373     },
374
375     /** 
376      * Method: updatePosition
377      * if the popup has a lonlat and its map members set, 
378      * then have it move itself to its proper position
379      */
380     updatePosition: function() {
381         if ((this.lonlat) && (this.map)) {
382             var px = this.map.getLayerPxFromLonLat(this.lonlat);
383             if (px) {
384                 this.moveTo(px);           
385             }    
386         }
387     },
388
389     /**
390      * Method: moveTo
391      * 
392      * Parameters:
393      * px - {<OpenLayers.Pixel>} the top and left position of the popup div. 
394      */
395     moveTo: function(px) {
396         if ((px != null) && (this.div != null)) {
397             this.div.style.left = px.x + "px";
398             this.div.style.top = px.y + "px";
399         }
400     },
401
402     /**
403      * Method: visible
404      *
405      * Returns:      
406      * {Boolean} Boolean indicating whether or not the popup is visible
407      */
408     visible: function() {
409         return OpenLayers.Element.visible(this.div);
410     },
411
412     /**
413      * Method: toggle
414      * Toggles visibility of the popup.
415      */
416     toggle: function() {
417         if (this.visible()) {
418             this.hide();
419         } else {
420             this.show();
421         }
422     },
423
424     /**
425      * Method: show
426      * Makes the popup visible.
427      */
428     show: function() {
429         OpenLayers.Element.show(this.div);
430
431         if (this.panMapIfOutOfView) {
432             this.panIntoView();
433         }    
434     },
435
436     /**
437      * Method: hide
438      * Makes the popup invisible.
439      */
440     hide: function() {
441         OpenLayers.Element.hide(this.div);
442     },
443
444     /**
445      * Method: setSize
446      * Used to adjust the size of the popup. 
447      *
448      * Parameters:
449      * contentSize - {<OpenLayers.Size>} the new size for the popup's 
450      *     contents div (in pixels).
451      */
452     setSize:function(contentSize) { 
453         this.size = contentSize.clone(); 
454         
455         // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
456         //  must add that to the desired "size". 
457         var contentDivPadding = this.getContentDivPadding();
458         var wPadding = contentDivPadding.left + contentDivPadding.right;
459         var hPadding = contentDivPadding.top + contentDivPadding.bottom;
460
461         // take into account the popup's 'padding' property
462         this.fixPadding();
463         wPadding += this.padding.left + this.padding.right;
464         hPadding += this.padding.top + this.padding.bottom;
465
466         // make extra space for the close div
467         if (this.closeDiv) {
468             var closeDivWidth = parseInt(this.closeDiv.style.width);
469             wPadding += closeDivWidth + contentDivPadding.right;
470         }
471
472         //increase size of the main popup div to take into account the 
473         // users's desired padding and close div.        
474         this.size.w += wPadding;
475         this.size.h += hPadding;
476
477         //now if our browser is IE, we need to actually make the contents 
478         // div itself bigger to take its own padding into effect. this makes 
479         // me want to shoot someone, but so it goes.
480         if (OpenLayers.Util.getBrowserName() == "msie") {
481             this.contentSize.w += 
482                 contentDivPadding.left + contentDivPadding.right;
483             this.contentSize.h += 
484                 contentDivPadding.bottom + contentDivPadding.top;
485         }
486
487         if (this.div != null) {
488             this.div.style.width = this.size.w + "px";
489             this.div.style.height = this.size.h + "px";
490         }
491         if (this.contentDiv != null){
492             this.contentDiv.style.width = contentSize.w + "px";
493             this.contentDiv.style.height = contentSize.h + "px";
494         }
495     },  
496
497     /**
498      * APIMethod: updateSize
499      * Auto size the popup so that it precisely fits its contents (as 
500      *     determined by this.contentDiv.innerHTML). Popup size will, of
501      *     course, be limited by the available space on the current map
502      */
503     updateSize: function() {
504         
505         // determine actual render dimensions of the contents by putting its
506         // contents into a fake contentDiv (for the CSS) and then measuring it
507         var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" + 
508             this.contentDiv.innerHTML + 
509             "</div>";
510  
511         var containerElement = (this.map) ? this.map.layerContainerDiv
512                                                                           : document.body;
513         var realSize = OpenLayers.Util.getRenderedDimensions(
514             preparedHTML, null, {
515                 displayClass: this.displayClass,
516                 containerElement: containerElement
517             }
518         );
519
520         // is the "real" size of the div is safe to display in our map?
521         var safeSize = this.getSafeContentSize(realSize);
522
523         var newSize = null;
524         if (safeSize.equals(realSize)) {
525             //real size of content is small enough to fit on the map, 
526             // so we use real size.
527             newSize = realSize;
528
529         } else {
530
531             //make a new OL.Size object with the clipped dimensions 
532             // set or null if not clipped.
533             var fixedSize = new OpenLayers.Size();
534             fixedSize.w = (safeSize.w < realSize.w) ? safeSize.w : null;
535             fixedSize.h = (safeSize.h < realSize.h) ? safeSize.h : null;
536         
537             if (fixedSize.w && fixedSize.h) {
538                 //content is too big in both directions, so we will use 
539                 // max popup size (safeSize), knowing well that it will 
540                 // overflow both ways.                
541                 newSize = safeSize;
542             } else {
543                 //content is clipped in only one direction, so we need to 
544                 // run getRenderedDimensions() again with a fixed dimension
545                 var clippedSize = OpenLayers.Util.getRenderedDimensions(
546                     preparedHTML, fixedSize, {
547                         displayClass: this.contentDisplayClass,
548                         containerElement: containerElement
549                     }
550                 );
551                 
552                 //if the clipped size is still the same as the safeSize, 
553                 // that means that our content must be fixed in the 
554                 // offending direction. If overflow is 'auto', this means 
555                 // we are going to have a scrollbar for sure, so we must 
556                 // adjust for that.
557                 //
558                 var currentOverflow = OpenLayers.Element.getStyle(
559                     this.contentDiv, "overflow"
560                 );
561                 if ( (currentOverflow != "hidden") && 
562                      (clippedSize.equals(safeSize)) ) {
563                     var scrollBar = OpenLayers.Util.getScrollbarWidth();
564                     if (fixedSize.w) {
565                         clippedSize.h += scrollBar;
566                     } else {
567                         clippedSize.w += scrollBar;
568                     }
569                 }
570                 
571                 newSize = this.getSafeContentSize(clippedSize);
572             }
573         }                        
574         this.setSize(newSize);     
575     },    
576
577     /**
578      * Method: setBackgroundColor
579      * Sets the background color of the popup.
580      *
581      * Parameters:
582      * color - {String} the background color.  eg "#FFBBBB"
583      */
584     setBackgroundColor:function(color) { 
585         if (color != undefined) {
586             this.backgroundColor = color; 
587         }
588         
589         if (this.div != null) {
590             this.div.style.backgroundColor = this.backgroundColor;
591         }
592     },  
593     
594     /**
595      * Method: setOpacity
596      * Sets the opacity of the popup.
597      * 
598      * Parameters:
599      * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
600      */
601     setOpacity:function(opacity) { 
602         if (opacity != undefined) {
603             this.opacity = opacity; 
604         }
605         
606         if (this.div != null) {
607             // for Mozilla and Safari
608             this.div.style.opacity = this.opacity;
609
610             // for IE
611             this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
612         }
613     },  
614     
615     /**
616      * Method: setBorder
617      * Sets the border style of the popup.
618      *
619      * Parameters:
620      * border - {String} The border style value. eg 2px 
621      */
622     setBorder:function(border) { 
623         if (border != undefined) {
624             this.border = border;
625         }
626         
627         if (this.div != null) {
628             this.div.style.border = this.border;
629         }
630     },      
631     
632     /**
633      * Method: setContentHTML
634      * Allows the user to set the HTML content of the popup.
635      *
636      * Parameters:
637      * contentHTML - {String} HTML for the div.
638      */
639     setContentHTML:function(contentHTML) {
640
641         if (contentHTML != null) {
642             this.contentHTML = contentHTML;
643         }
644        
645         if ((this.contentDiv != null) && 
646             (this.contentHTML != null) &&
647             (this.contentHTML != this.contentDiv.innerHTML)) {
648        
649             this.contentDiv.innerHTML = this.contentHTML;
650        
651             if (this.autoSize) {
652                 
653                 //if popup has images, listen for when they finish
654                 // loading and resize accordingly
655                 this.registerImageListeners();
656
657                 //auto size the popup to its current contents
658                 this.updateSize();
659             }
660         }    
661
662     },
663     
664     /**
665      * Method: registerImageListeners
666      * Called when an image contained by the popup loaded. this function
667      *     updates the popup size, then unregisters the image load listener.
668      */   
669     registerImageListeners: function() { 
670
671         // As the images load, this function will call updateSize() to 
672         // resize the popup to fit the content div (which presumably is now
673         // bigger than when the image was not loaded).
674         // 
675         // If the 'panMapIfOutOfView' property is set, we will pan the newly
676         // resized popup back into view.
677         // 
678         // Note that this function, when called, will have 'popup' and 
679         // 'img' properties in the context.
680         //
681         var onImgLoad = function() {
682             
683             this.popup.updateSize();
684      
685             if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
686                 this.popup.panIntoView();
687             }
688
689             OpenLayers.Event.stopObserving(
690                 this.img, "load", this.img._onImageLoad
691             );
692     
693         };
694
695         //cycle through the images and if their size is 0x0, that means that 
696         // they haven't been loaded yet, so we attach the listener, which 
697         // will fire when the images finish loading and will resize the 
698         // popup accordingly to its new size.
699         var images = this.contentDiv.getElementsByTagName("img");
700         for (var i = 0, len = images.length; i < len; i++) {
701             var img = images[i];
702             if (img.width == 0 || img.height == 0) {
703
704                 var context = {
705                     'popup': this,
706                     'img': img
707                 };
708
709                 //expando this function to the image itself before registering
710                 // it. This way we can easily and properly unregister it.
711                 img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
712
713                 OpenLayers.Event.observe(img, 'load', img._onImgLoad);
714             }    
715         } 
716     },
717
718     /**
719      * APIMethod: getSafeContentSize
720      * 
721      * Parameters:
722      * size - {<OpenLayers.Size>} Desired size to make the popup.
723      * 
724      * Returns:
725      * {<OpenLayers.Size>} A size to make the popup which is neither smaller
726      *     than the specified minimum size, nor bigger than the maximum 
727      *     size (which is calculated relative to the size of the viewport).
728      */
729     getSafeContentSize: function(size) {
730
731         var safeContentSize = size.clone();
732
733         // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
734         //  must add that to the desired "size". 
735         var contentDivPadding = this.getContentDivPadding();
736         var wPadding = contentDivPadding.left + contentDivPadding.right;
737         var hPadding = contentDivPadding.top + contentDivPadding.bottom;
738
739         // take into account the popup's 'padding' property
740         this.fixPadding();
741         wPadding += this.padding.left + this.padding.right;
742         hPadding += this.padding.top + this.padding.bottom;
743
744         if (this.closeDiv) {
745             var closeDivWidth = parseInt(this.closeDiv.style.width);
746             wPadding += closeDivWidth + contentDivPadding.right;
747         }
748
749         // prevent the popup from being smaller than a specified minimal size
750         if (this.minSize) {
751             safeContentSize.w = Math.max(safeContentSize.w, 
752                 (this.minSize.w - wPadding));
753             safeContentSize.h = Math.max(safeContentSize.h, 
754                 (this.minSize.h - hPadding));
755         }
756
757         // prevent the popup from being bigger than a specified maximum size
758         if (this.maxSize) {
759             safeContentSize.w = Math.min(safeContentSize.w, 
760                 (this.maxSize.w - wPadding));
761             safeContentSize.h = Math.min(safeContentSize.h, 
762                 (this.maxSize.h - hPadding));
763         }
764         
765         //make sure the desired size to set doesn't result in a popup that 
766         // is bigger than the map's viewport.
767         //
768         if (this.map && this.map.size) {
769             
770             var extraX = 0, extraY = 0;
771             if (this.keepInMap && !this.panMapIfOutOfView) {
772                 var px = this.map.getPixelFromLonLat(this.lonlat);
773                 switch (this.relativePosition) {
774                     case "tr":
775                         extraX = px.x;
776                         extraY = this.map.size.h - px.y;
777                         break;
778                     case "tl":
779                         extraX = this.map.size.w - px.x;
780                         extraY = this.map.size.h - px.y;
781                         break;
782                     case "bl":
783                         extraX = this.map.size.w - px.x;
784                         extraY = px.y;
785                         break;
786                     case "br":
787                         extraX = px.x;
788                         extraY = px.y;
789                         break;
790                     default:    
791                         extraX = px.x;
792                         extraY = this.map.size.h - px.y;
793                         break;
794                 }
795             }    
796           
797             var maxY = this.map.size.h - 
798                 this.map.paddingForPopups.top - 
799                 this.map.paddingForPopups.bottom - 
800                 hPadding - extraY;
801             
802             var maxX = this.map.size.w - 
803                 this.map.paddingForPopups.left - 
804                 this.map.paddingForPopups.right - 
805                 wPadding - extraX;
806             
807             safeContentSize.w = Math.min(safeContentSize.w, maxX);
808             safeContentSize.h = Math.min(safeContentSize.h, maxY);
809         }
810         
811         return safeContentSize;
812     },
813     
814     /**
815      * Method: getContentDivPadding
816      * Glorious, oh glorious hack in order to determine the css 'padding' of 
817      *     the contentDiv. IE/Opera return null here unless we actually add the 
818      *     popup's main 'div' element (which contains contentDiv) to the DOM. 
819      *     So we make it invisible and then add it to the document temporarily. 
820      *
821      *     Once we've taken the padding readings we need, we then remove it 
822      *     from the DOM (it will actually get added to the DOM in 
823      *     Map.js's addPopup)
824      *
825      * Returns:
826      * {<OpenLayers.Bounds>}
827      */
828     getContentDivPadding: function() {
829
830         //use cached value if we have it
831         var contentDivPadding = this._contentDivPadding;
832         if (!contentDivPadding) {
833
834                 if (this.div.parentNode == null) {
835                         //make the div invisible and add it to the page        
836                     this.div.style.display = "none";
837                     document.body.appendChild(this.div);
838                 }
839                     
840             //read the padding settings from css, put them in an OL.Bounds        
841             contentDivPadding = new OpenLayers.Bounds(
842                 OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
843                 OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
844                 OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
845                 OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
846             );
847     
848             //cache the value
849             this._contentDivPadding = contentDivPadding;
850
851             if (this.div.parentNode == document.body) {
852                     //remove the div from the page and make it visible again
853                     document.body.removeChild(this.div);
854                     this.div.style.display = "";
855             }
856         }
857         return contentDivPadding;
858     },
859
860     /**
861      * Method: addCloseBox
862      * 
863      * Parameters:
864      * callback - {Function} The callback to be called when the close button
865      *     is clicked.
866      */
867     addCloseBox: function(callback) {
868
869         this.closeDiv = OpenLayers.Util.createDiv(
870             this.id + "_close", null, new OpenLayers.Size(17, 17)
871         );
872         this.closeDiv.className = "olPopupCloseBox"; 
873         
874         // use the content div's css padding to determine if we should
875         //  padd the close div
876         var contentDivPadding = this.getContentDivPadding();
877          
878         this.closeDiv.style.right = contentDivPadding.right + "px";
879         this.closeDiv.style.top = contentDivPadding.top + "px";
880         this.groupDiv.appendChild(this.closeDiv);
881
882         var closePopup = callback || function(e) {
883             this.hide();
884             OpenLayers.Event.stop(e);
885         };
886         OpenLayers.Event.observe(this.closeDiv, "click", 
887                 OpenLayers.Function.bindAsEventListener(closePopup, this));
888     },
889
890     /**
891      * Method: panIntoView
892      * Pans the map such that the popup is totaly viewable (if necessary)
893      */
894     panIntoView: function() {
895         
896         var mapSize = this.map.getSize();
897     
898         //start with the top left corner of the popup, in px, 
899         // relative to the viewport
900         var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
901             parseInt(this.div.style.left),
902             parseInt(this.div.style.top)
903         ));
904         var newTL = origTL.clone();
905     
906         //new left (compare to margins, using this.size to calculate right)
907         if (origTL.x < this.map.paddingForPopups.left) {
908             newTL.x = this.map.paddingForPopups.left;
909         } else 
910         if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
911             newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
912         }
913         
914         //new top (compare to margins, using this.size to calculate bottom)
915         if (origTL.y < this.map.paddingForPopups.top) {
916             newTL.y = this.map.paddingForPopups.top;
917         } else 
918         if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
919             newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
920         }
921         
922         var dx = origTL.x - newTL.x;
923         var dy = origTL.y - newTL.y;
924         
925         this.map.pan(dx, dy);
926     },
927
928     /** 
929      * Method: registerEvents
930      * Registers events on the popup.
931      *
932      * Do this in a separate function so that subclasses can 
933      *   choose to override it if they wish to deal differently
934      *   with mouse events
935      * 
936      *   Note in the following handler functions that some special
937      *    care is needed to deal correctly with mousing and popups. 
938      *   
939      *   Because the user might select the zoom-rectangle option and
940      *    then drag it over a popup, we need a safe way to allow the
941      *    mousemove and mouseup events to pass through the popup when
942      *    they are initiated from outside.
943      * 
944      *   Otherwise, we want to essentially kill the event propagation
945      *    for all other events, though we have to do so carefully, 
946      *    without disabling basic html functionality, like clicking on 
947      *    hyperlinks or drag-selecting text.
948      */
949      registerEvents:function() {
950         this.events = new OpenLayers.Events(this, this.div, null, true);
951
952         this.events.on({
953             "mousedown": this.onmousedown,
954             "mousemove": this.onmousemove,
955             "mouseup": this.onmouseup,
956             "click": this.onclick,
957             "mouseout": this.onmouseout,
958             "dblclick": this.ondblclick,
959             scope: this
960         });
961         
962      },
963
964     /** 
965      * Method: onmousedown 
966      * When mouse goes down within the popup, make a note of
967      *   it locally, and then do not propagate the mousedown 
968      *   (but do so safely so that user can select text inside)
969      * 
970      * Parameters:
971      * evt - {Event} 
972      */
973     onmousedown: function (evt) {
974         this.mousedown = true;
975         OpenLayers.Event.stop(evt, true);
976     },
977
978     /** 
979      * Method: onmousemove
980      * If the drag was started within the popup, then 
981      *   do not propagate the mousemove (but do so safely
982      *   so that user can select text inside)
983      * 
984      * Parameters:
985      * evt - {Event} 
986      */
987     onmousemove: function (evt) {
988         if (this.mousedown) {
989             OpenLayers.Event.stop(evt, true);
990         }
991     },
992
993     /** 
994      * Method: onmouseup
995      * When mouse comes up within the popup, after going down 
996      *   in it, reset the flag, and then (once again) do not 
997      *   propagate the event, but do so safely so that user can 
998      *   select text inside
999      * 
1000      * Parameters:
1001      * evt - {Event} 
1002      */
1003     onmouseup: function (evt) {
1004         if (this.mousedown) {
1005             this.mousedown = false;
1006             OpenLayers.Event.stop(evt, true);
1007         }
1008     },
1009
1010     /**
1011      * Method: onclick
1012      * Ignore clicks, but allowing default browser handling
1013      * 
1014      * Parameters:
1015      * evt - {Event} 
1016      */
1017     onclick: function (evt) {
1018         OpenLayers.Event.stop(evt, true);
1019     },
1020
1021     /** 
1022      * Method: onmouseout
1023      * When mouse goes out of the popup set the flag to false so that
1024      *   if they let go and then drag back in, we won't be confused.
1025      * 
1026      * Parameters:
1027      * evt - {Event} 
1028      */
1029     onmouseout: function (evt) {
1030         this.mousedown = false;
1031     },
1032     
1033     /** 
1034      * Method: ondblclick
1035      * Ignore double-clicks, but allowing default browser handling
1036      * 
1037      * Parameters:
1038      * evt - {Event} 
1039      */
1040     ondblclick: function (evt) {
1041         OpenLayers.Event.stop(evt, true);
1042     },
1043
1044     CLASS_NAME: "OpenLayers.Popup"
1045 });
1046
1047 OpenLayers.Popup.WIDTH = 200;
1048 OpenLayers.Popup.HEIGHT = 200;
1049 OpenLayers.Popup.COLOR = "white";
1050 OpenLayers.Popup.OPACITY = 1;
1051 OpenLayers.Popup.BORDER = "0px";