Files
openlayers/src/ol/Popup.js

344 lines
9.1 KiB
JavaScript

goog.provide('ol.Popup');
goog.require('ol.Map');
goog.require('ol.Loc');
goog.require('ol.Feature');
/**
* @export
* @constructor
* @param {ol.Map} map the map on which the popup is placed.
* @param {ol.Loc|ol.Feature=} opt_anchor the anchor object for the popup.
* @param {string=} opt_placement the placement of the arrow on the popup.
* @param {boolean=} opt_close include a close button on the popup
*/
ol.Popup = function(map, opt_anchor, opt_placement, opt_close) {
/**
* @private
* @type {ol.Map}
*/
this.map_ = map;
/**
* @private
* @type {ol.Loc|ol.Feature|undefined}
*/
this.anchor_ = opt_anchor;
/**
* can be 'top','bottom','right','left','auto'
* TODO: 'auto' not yet implemented
* @private
* @type {!string}
*/
this.placement_ = goog.isDefAndNotNull(opt_placement)?opt_placement:'top';
/**
* include a close button on the popup - defaults to true.
* @private
* @type {boolean|undefined}
*/
this.closeButton_ = goog.isDefAndNotNull(opt_close) ? opt_close : true;
/**
* @private
* @type {string|undefined}
*/
this.content_ = undefined;
/**
* @private
* @type {string|undefined}
*/
this.template_ = undefined;
/**
* @private
* @type {Element}
*/
this.container_ = null;
/**
* @private
* @type {number}
*/
this.arrowOffset_ = 30; //FIXME: set this from CSS dynamically somehow?
/**
* if the CSS sets either width or height assume the app is specifying the
* size of the popup, if not auto size the popup.
* @private
* @type {boolean}
*/
this.autoSize_ = true;
};
/**
* @const
*/
ol.Popup.CLASS_NAME = 'ol-popup';
/**
* @return {ol.Map} Projection.
*/
ol.Popup.prototype.getMap = function() {
return this.map_;
};
/**
* @param {ol.Map} map the map object to hold this popup.
*/
ol.Popup.prototype.setMap = function(map) {
this.map_ = map;
};
/**
* @return {ol.Feature|ol.Loc|undefined} the anchor .
*/
ol.Popup.prototype.getAnchor = function() {
return this.anchor_;
};
/**
* @param {ol.Feature|ol.Loc} anchor the anchor location to place this popup.
*/
ol.Popup.prototype.setAnchor = function(anchor) {
this.anchor_ = anchor;
};
/**
* @return {string|undefined} the placement value relative to the anchor.
*/
ol.Popup.prototype.getPlacement = function() {
return this.placement_;
};
/**
* @param {string} placement where to place this popup relative to the anchor.
*/
ol.Popup.prototype.setPlacement = function(placement) {
if (!goog.isNull(this.container_)) {
goog.dom.classes.remove(this.container_,
ol.Popup.CLASS_NAME+'-'+this.placement_);
goog.dom.classes.add(this.container_,ol.Popup.CLASS_NAME+'-'+placement);
}
this.placement_ = placement;
};
/**
* @return {string|undefined} static content to be displayed in the popup (HTML)
*/
ol.Popup.prototype.getContent = function() {
return this.content_;
};
/**
* @param {string} content the content to be displayed this popup.
*/
ol.Popup.prototype.setContent = function(content) {
this.content_ = content;
};
/**
* @private
* @returns {string} generates the content
*/
ol.Popup.prototype.generateContent_ = function() {
//set the content
if ( goog.isDefAndNotNull(this.content_) ) {
return this.content_;
} else {
if ( goog.isDefAndNotNull(this.template_) &&
goog.isDefAndNotNull(this.anchor_) &&
(this.anchor_ instanceof ol.Feature)) {
//set content from feature attributes on the template
//TODO: this.setContent(template.apply(this.anchor_.getAttributes()));
return this.template_; //stub to return something
} else {
ol.error('ol.Popup unabale to generate any content');
return '<p>no content</p>';
}
}
};
/**
* @return {string|undefined} the anchor .
*/
ol.Popup.prototype.getTemplate = function() {
return this.template_;
};
/**
* @param {string} template the map object to hold this popup.
*/
ol.Popup.prototype.setTemplate = function(template) {
this.template_ = template;
};
/**
* Open the popup.
* @export
* @param {ol.Feature|ol.Loc} opt_arg feature or location for the anchor
*/
ol.Popup.prototype.open = function(opt_arg) {
if (goog.isDef(opt_arg)) {
this.setAnchor(opt_arg);
}
//create popup container if it's not created already
if (goog.isNull(this.container_)) {
this.container_ = goog.dom.createElement('div');
goog.dom.classes.add(this.container_,
ol.Popup.CLASS_NAME, ol.Popup.CLASS_NAME+'-'+this.placement_);
//see if the style class sets width or height
if (goog.style.getStyle(this.container_, 'width').length>0 ||
goog.style.getStyle(this.container_, 'height').length>0 ) {
this.autoSize_ = false;
}
if (this.closeButton_) {
var closeButton = goog.dom.createElement('div');
goog.dom.appendChild(this.container_, closeButton);
goog.dom.classes.add(closeButton, ol.Popup.CLASS_NAME+'-close');
}
this.map_.getEvents().register('click', this.clickHandler, this);
goog.dom.appendChild(this.map_.getMapOverlay(), this.container_);
}
this.childContent_=goog.dom.htmlToDocumentFragment(this.generateContent_());
goog.dom.appendChild(this.container_, this.childContent_);
if (this.autoSize_) {
this.registerImageListeners();
}
this.setAnchorOffset_();
};
ol.Popup.prototype.setAnchorOffset_ = function() {
if (goog.isNull(this.container_.parentNode)) {
//this means the popup has already been closed, nothing to do here
//which might happen while waiting for images to load
return;
}
if (!goog.isDefAndNotNull(this.anchor_)) {
//must have an anchor when trying to set the position
ol.error("ol.Popup must have an anchor to set the position");
return;
}
//position the element
if (this.anchor_ instanceof ol.Feature) {
this.pos_ = this.anchor_.getGeometry().getCentroid();
} else {
this.pos_ = new ol.geom.Point(this.anchor_.getX(), this.anchor_.getY());
}
var pos = /** @type {ol.Loc} */ (this.pos_);
var popupPosPx = this.map_.getPixelForLoc(pos);
var popupSize = goog.style.getSize(this.container_);
switch(this.placement_) {
default:
case 'auto':
//TODO: switch based on map quadrant
break;
case 'top':
case 'bottom':
popupPosPx[0] -= popupSize.width / 2.0;
if (this.placement_ == "bottom") {
popupPosPx[1] -= popupSize.height + this.arrowOffset_;
} else {
popupPosPx[1] += this.arrowOffset_;
}
break;
case 'left':
case 'right':
popupPosPx[1] -= popupSize.height / 2.0;
if (this.placement_ == "right") {
popupPosPx[0] -= popupSize.width + this.arrowOffset_;
} else {
popupPosPx[0] += this.arrowOffset_;
}
break;
}
this.moveTo_(popupPosPx);
};
/**
* registerImageListeners
* Called when an image contained by the popup loaded. this function
* updates the popup size, then unregisters the image load listener.
*/
ol.Popup.prototype.registerImageListeners = function() {
// As the images load, this function will call setAnchorOffset_() to
// resize the popup to fit the content div (which presumably is now
// bigger than when the image was not loaded).
//
//cycle through the images and if their size is 0x0, that means that
// they haven't been loaded yet, so we attach the listener, which
// will fire when the images finish loading and will resize the
// popup accordingly to its new size.
var images = this.container_.getElementsByTagName("img");
for (var i = 0, len = images.length; i < len; i++) {
var img = images[i];
if (img.width == 0 || img.height == 0) {
goog.events.listenOnce(img, 'load',
goog.bind(this.setAnchorOffset_, this));
}
}
};
/**
* @param px - {goog.} the top and left position of the popup div.
*/
ol.Popup.prototype.moveTo_ = function(px) {
if (goog.isDefAndNotNull(px)) {
goog.style.setPosition(this.container_, px[0], px[1]);
}
};
/**
* Click handler
* @param {Event} evt the event generated by a click
*/
ol.Popup.prototype.clickHandler = function(evt) {
var target = /** @type {Node} */ evt.target;
if (goog.dom.classes.has(target,ol.Popup.CLASS_NAME+'-close')) {
this.close();
}
};
/**
* Clean up.
* @export
*/
ol.Popup.prototype.close = function() {
goog.dom.removeChildren(this.container_);
goog.dom.removeNode(this.container_);
};
/**
* Clean up.
* @export
*/
ol.Popup.prototype.destroy = function() {
for (var key in this) {
delete this[key];
}
};