diff --git a/css/img/close.gif b/css/img/close.gif new file mode 100644 index 0000000000..a8958de9b4 Binary files /dev/null and b/css/img/close.gif differ diff --git a/css/ol.css b/css/ol.css index cf034845b8..cf46a42de2 100644 --- a/css/ol.css +++ b/css/ol.css @@ -1,2 +1,120 @@ .ol-viewport { width:100%; height:100%; position:relative; left:0; top:0; } -.ol-renderer-webgl-canvas { width:100%; height:100%; } \ No newline at end of file +.ol-renderer-webgl-canvas { width:100%;height:100%; } + +/** + * arrow implementation from http://cssarrowplease.com/ for ol-popup + */ + +.ol-popup { + position: absolute; + background: #88b7d5; + border: 4px solid #c2e1f5; +} + +/** + * FIXME + */ +.ol-popup-close { + background: url("img/close.gif") no-repeat; + cursor: pointer; + position: absolute; + width: 17px; + height: 17px; + right: 0; +} + +.ol-popup-top {} +.ol-popup-top:after, .ol-popup-top:before { + bottom: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.ol-popup-top:after { + border-bottom-color: #88b7d5; + border-width: 30px; + left: 50%; + margin-left: -30px; +} + +.ol-popup-top:before { + border-bottom-color: #c2e1f5; + border-width: 36px; + left: 50%; + margin-left: -36px; +} + +.ol-popup-bottom {} +.ol-popup-bottom:after, .ol-popup-bottom:before { + top: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.ol-popup-bottom:after { + border-top-color: #88b7d5; + border-width: 30px; + left: 50%; + margin-left: -30px; +} +.ol-popup-bottom:before { + border-top-color: #c2e1f5; + border-width: 36px; + left: 50%; + margin-left: -36px; +} +.ol-popup-right {} +.ol-popup-right:after, .ol-popup-right:before { + left: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.ol-popup-right:after { + border-left-color: #88b7d5; + border-width: 30px; + top: 50%; + margin-top: -30px; +} +.ol-popup-right:before { + border-left-color: #c2e1f5; + border-width: 36px; + top: 50%; + margin-top: -36px; +} +.ol-popup-left {} +.ol-popup-left:after, .ol-popup-left:before { + right: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.ol-popup-left:after { + border-right-color: #88b7d5; + border-width: 30px; + top: 50%; + margin-top: -30px; +} +.ol-popup-left:before { + border-right-color: #c2e1f5; + border-width: 36px; + top: 50%; + margin-top: -36px; +} + diff --git a/src/api/popup.js b/src/api/popup.js new file mode 100644 index 0000000000..703f48a07e --- /dev/null +++ b/src/api/popup.js @@ -0,0 +1,130 @@ +goog.provide('ol.popup'); + +goog.require('ol.Popup'); +goog.require('ol.map'); + + +/** + * @typedef {ol.Popup|Object} popup + */ +ol.PopupLike; + + + +/** + * @export + * @param {ol.PopupLike} opt_arg popup object literal. + * @return {ol.Popup} the popup. + */ +ol.popup = function(opt_arg){ + + if (opt_arg instanceof ol.Popup) { + return opt_arg; + } + + /** @type {ol.Map} */ + var map; + + /** @type {ol.Loc|ol.Feature|undefined} */ + var anchor; + + /** @type {string|undefined} */ + var placement; + + /** @type {string|undefined} */ + var content; + + /** @type {string|undefined} */ + var template; + + if (arguments.length == 1 && goog.isDef(opt_arg)) { + if (goog.isObject(opt_arg)) { + map = opt_arg['map']; + anchor = opt_arg['anchor']; + placement = opt_arg['placement']; + content = opt_arg['content']; + template = opt_arg['template']; + } + } + + var popup = new ol.Popup(map, anchor); + + if (goog.isDef(anchor)) { + popup.setAnchor(anchor); + } + if (goog.isDef(placement)) { + popup.setPlacement(placement); + } + if (goog.isDef(content)) { + popup.setContent(content); + } + if (goog.isDef(template)) { + popup.setTemplate(template); + } + + return popup; + +}; + + +/** + * @export + * @param {ol.Loc|ol.Feature=} opt_arg a feature or a location. + * @return {ol.Popup|ol.Feature|ol.Loc|undefined} Result. + */ +ol.Popup.prototype.anchor = function(opt_arg){ + if (arguments.length == 1 && goog.isDef(opt_arg)) { + this.setAnchor(opt_arg); + return this; + } + else { + return this.getAnchor(); + } +}; + + +/** + * @export + * @param {ol.Map=} opt_arg the map . + * @return {ol.Popup|ol.Map|undefined} the map or the popup. + */ +ol.Popup.prototype.map = function(opt_arg){ + if (arguments.length == 1 && goog.isDef(opt_arg)) { + this.setMap(opt_arg); + return this; + } + else { + return this.getMap(); + } +}; + +/** + * @export + * @param {string=} opt_arg the content for the map (HTML makrkup) + * @return {ol.Popup|string|undefined} the content or the popup. + */ +ol.Popup.prototype.content = function(opt_arg){ + if (arguments.length == 1 && goog.isDef(opt_arg)) { + this.setContent(opt_arg); + return this; + } + else { + return this.getContent(); + } +}; + +/** + * @export + * @param {string=} opt_arg the template to be used to generate the content + * @return {ol.Popup|string|undefined} the template or the popup. + */ +ol.Popup.prototype.template = function(opt_arg){ + if (arguments.length == 1 && goog.isDef(opt_arg)) { + this.setTemplate(opt_arg); + return this; + } + else { + return this.getTemplate(); + } +}; + diff --git a/src/ol.js b/src/ol.js index b8f91ab859..163edd4dcb 100644 --- a/src/ol.js +++ b/src/ol.js @@ -12,6 +12,7 @@ goog.require("ol.feature"); goog.require("ol.projection"); goog.require("ol.layer.xyz"); goog.require("ol.layer.osm"); +goog.require("ol.popup"); goog.require("ol.Tile"); goog.require("ol.TileSet"); goog.require("ol.TileCache"); diff --git a/src/ol/Popup.js b/src/ol/Popup.js new file mode 100644 index 0000000000..4aa843e8c7 --- /dev/null +++ b/src/ol/Popup.js @@ -0,0 +1,281 @@ +goog.provide('ol.Popup'); + +goog.require('ol.Map'); +goog.require('ol.Loc'); +goog.require('ol.Feature'); +//goog.require('goog.dom'); +//goog.require('goog.style'); + + +/** + * @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_ = 32; //FIXME: set this from CSS dynamically somehow? + +}; + +/** + * @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_) && + (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 { + return '
'; + } + } +}; + + +/** + * @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_); + + 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_); + + //position the element + if (this.anchor_ instanceof ol.Feature) { + this.pos_ = this.anchor_.getGeometry().getCentroid(); + } else { + this.pos_ = this.anchor_; + } + var popupPosPx = this.map_.getViewportPosition(this.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.x -= popupSize.width / 2.0; + + if (this.placement_ == "bottom") { + popupPosPx.y -= popupSize.height + this.arrowOffset_; + } else { + popupPosPx.y += this.arrowOffset_; + } + break; + case 'left': + case 'right': + popupPosPx.y -= popupSize.height / 2.0; + + if (this.placement_ == "right") { + popupPosPx.x -= popupSize.width + this.arrowOffset_; + } else { + popupPosPx.x += this.arrowOffset_; + } + break; + }; + this.moveTo_(popupPosPx); + +}; + +/** + * @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.x, px.y); + } +}; + +/** + * 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]; + } +}; diff --git a/src/ol/geom/Geometry.js b/src/ol/geom/Geometry.js index b24e76d1ed..9378df6ebc 100644 --- a/src/ol/geom/Geometry.js +++ b/src/ol/geom/Geometry.js @@ -32,3 +32,12 @@ ol.geom.Geometry.prototype.setBounds = function(bounds) { this.bounds_ = bounds; return this; }; + +/** + * @returns ol.Loc + */ +ol.geom.Geometry.prototype.getCentroid = function() { + //FIXME: stub only to get popups working + return new ol.Loc(-76,45); +}; + diff --git a/test/api.html b/test/api.html index 7a70332a77..3cb67c3177 100644 --- a/test/api.html +++ b/test/api.html @@ -58,6 +58,7 @@ + +