initial version of popups

This commit is contained in:
Mike Adair
2012-06-22 08:13:34 -04:00
parent a8642e9af5
commit 368bb28671
10 changed files with 918 additions and 1 deletions

BIN
css/img/close.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,2 +1,120 @@
.ol-viewport { width:100%; height:100%; position:relative; left:0; top:0; }
.ol-renderer-webgl-canvas { width:100%; height:100%; }
.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;
}

130
src/api/popup.js Normal file
View File

@@ -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();
}
};

View File

@@ -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");

281
src/ol/Popup.js Normal file
View File

@@ -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 '<br>';
}
}
};
/**
* @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];
}
};

View File

@@ -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);
};

View File

@@ -58,6 +58,7 @@
<script type="text/javascript" src="spec/api/layer/xyz.test.js"></script>
<script type="text/javascript" src="spec/api/layer/osm.test.js"></script>
<script type="text/javascript" src="spec/api/feature.test.js"></script>
<script type="text/javascript" src="spec/api/popup.test.js"></script>
<script type="text/javascript">

View File

@@ -90,6 +90,7 @@
<script type="text/javascript" src="spec/ol/Feature.test.js"></script>
<script type="text/javascript" src="spec/ol/renderer/WebGL.test.js"></script>
<script type="text/javascript" src="spec/ol/renderer/TileLayerRenderer.test.js"></script>
<script type="text/javascript" src="spec/ol/Popup.test.js"></script>
<script type="text/javascript">

280
test/spec/api/popup.test.js Normal file
View File

@@ -0,0 +1,280 @@
describe("ol.popup", function() {
it("should be able to add it to a map", function() {
var map = ol.map();
var popup = ol.popup({
map: map
});
expect(popup).toBeA(ol.Popup);
expect(popup.map()).toBeA(ol.Map);
});
it("should be able to place it at a specific location", function() {
var map = ol.map();
var popup = ol.popup({
map: map,
anchor: ol.loc([10,20])
});
expect(popup).toBeA(ol.Popup);
expect(popup.anchor()).toBeA(ol.Loc);
expect(popup.anchor().x()).toBe(10);
expect(popup.anchor().y()).toBe(20);
});
it("should be able to anchor it with a feature", function() {
var map = ol.map();
var feat = ol.feature();
var point = ol.geom.point([21, 4]);
feat.geometry(point);
var popup = ol.popup({
map: map,
anchor: feat
});
expect(popup).toBeA(ol.Popup);
expect(popup.anchor()).toBeA(ol.Feature);
expect(popup.anchor().geometry().x()).toBe(21);
expect(popup.anchor().geometry().y()).toBe(4);
});
it("should be able to anchor it with a feature", function() {
var map = ol.map();
var feat = ol.feature();
var point = ol.geom.point([21, 4]);
feat.geometry(point);
var popup = ol.popup({
map: map
}).anchor(feat);
expect(popup).toBeA(ol.Popup);
expect(popup.anchor()).toBeA(ol.Feature);
expect(popup.anchor().geometry().x()).toBe(21);
expect(popup.anchor().geometry().y()).toBe(4);
});
it("should be able to associate it with a feature", function() {
var map = ol.map();
var feat = ol.feature();
var point = ol.geom.point([21, 4]);
feat.geometry(point);
var popup = ol.popup({
map: map
});
popup.anchor(feat);
expect(popup).toBeA(ol.Popup);
expect(popup.anchor()).toBeA(ol.Feature);
expect(popup.anchor().geometry().x()).toBe(21);
expect(popup.anchor().geometry().y()).toBe(4);
});
/*
* not yet implemented
it("should be able to set the placement automatically", function() {
var map = ol.map();
var popup = ol.popup({
map: map,
anchor: ol.loc([10,20]),
placement: 'auto'
});
expect(popup).toBeA(ol.Popup);
//expect?
});
*/
it("should be able to set the placement top of the location", function() {
var map = ol.map();
var popup = ol.popup({
map: map,
anchor: ol.loc([10,20]),
placement: 'top'
});
expect(popup).toBeA(ol.Popup);
//expect?
});
it("should be able to set the placement right of the location", function() {
var map = ol.map();
var popup = ol.popup({
map: map,
anchor: ol.loc([10,20]),
placement: 'right'
});
expect(popup).toBeA(ol.Popup);
//expect?
});
/*
it("should be able to open and close a popup", function() {
var map = ol.map();
var popup = ol.popup({
map: map,
anchor: ol.loc([10,20]),
content: 'test'
});
expect(popup).toBeA(ol.Popup);
popup.open();
var elems = goog.dom.getElementsByClass('ol-popup');
expect(elems.length).toBe(1);
expect(elems[0].innerHTML.indexOf('test')).toBe(0);
popup.close();
//expect(popup.container()).toBeNull();
});
it("should be able to open and close a popup with a feature argument", function() {
var point = ol.geom.point([21, 4]);
var feat = ol.feature().geometry(point).set('name','foo');
var map = ol.map();
var popup = ol.popup({
map: map,
template: '<p>{{name}}</p>'
});
expect(popup).toBeA(ol.Popup);
popup.open(feat);
expect(popup.anchor()).toBeA(ol.Feature);
expect(popup.anchor().geometry().x()).toBe(21);
expect(popup.anchor().geometry().y()).toBe(4);
//expect?
popup.close();
//expect?
});
it("should be able to open with a new feature and the popup updates", function() {
var point = ol.geom.point([21, 4]);
var feat = ol.feature().geometry(point).set('name','foo');
var map = ol.map();
var popup = ol.popup({
map: map,
template: '<p>{{name}}</p>'
});
expect(popup).toBeA(ol.Popup);
popup.open(feat);
expect(popup.anchor()).toBeA(ol.Feature);
expect(popup.anchor().geometry().x()).toBe(21);
expect(popup.anchor().geometry().y()).toBe(4);
//expect?
var feat2 = ol.feature().geometry(ol.geom.point([-67,-80])).set('name','bar');
popup.open(feat2)
expect(popup.anchor().geometry().x()).toBe(-67);
expect(popup.anchor().geometry().y()).toBe(-80);
popup.close();
//expect?
});
it("should be able to open and close a popup with a loc argument", function() {
var map = ol.map();
var popup = ol.popup({
map: map,
content: 'test'
});
expect(popup).toBeA(ol.Popup);
popup.open(ol.loc([15,3]));
expect(popup.anchor()).toBeA(ol.Loc);
expect(popup.anchor().x()).toBe(15);
expect(popup.anchor().y()).toBe(3);
popup.close();
//expect?
});
it("should be able to set content in the popup", function() {
var point = ol.geom.point([21, 4]);
var feat = ol.feature().geometry(point);
var map = ol.map();
var popup = ol.popup({
map: map,
content: '<p>hello popup! #ol3</p>'
});
expect(popup).toBeA(ol.Popup);
popup.open();
//expect?
popup.content('<p>content changed! #ol3</p>');
//expect?
popup.close();
//expect?
});
it("should be able to set content based on a template and feature attributes", function() {
var point = ol.geom.point([21, 4]);
var feat = ol.feature().geometry(point).set('name', 'foo');
var map = ol.map();
var popup = ol.popup({
map: map,
template: '<p>hello popup template! #ol3 feature name: {{name}}</p>'
});
expect(popup).toBeA(ol.Popup);
popup.open(feat);
//expect?
popup.template();
popup.close();
//expect?
});
it("should be able to use a user provided container", function() {
var point = ol.geom.point([21, 4]);
var feat = ol.feature().geometry(point).set('name', 'foo');
var map = ol.map();
var popup = ol.popup({
map: map,
template: '<p>hello popup template! #ol3 feature name: {{name}}</p>'
});
expect(popup).toBeA(ol.Popup);
popup.open(feat);
//expect?
popup.template();
popup.close();
//expect?
});
*/
});

View File

@@ -0,0 +1,96 @@
describe("ol.Popup", function() {
it("should be able to add it to a map", function() {
var map = new ol.Map();
var popup = new ol.Popup(map);
expect(popup).toBeA(ol.Popup);
expect(popup.getMap()).toBeA(ol.Map);
});
it("should be able to place it at a specific location", function() {
var map = new ol.Map();
var popup = new ol.Popup(map, new ol.Loc(10,20));
expect(popup).toBeA(ol.Popup);
expect(popup.getAnchor()).toBeA(ol.Loc);
expect(popup.getAnchor().x()).toBe(10);
expect(popup.getAnchor().y()).toBe(20);
});
it("should be able to anchor it with a feature", function() {
var feat = new ol.Feature();
feat.setGeometry(new ol.geom.Point(21, 4));
var map = new ol.Map();
var popup = new ol.Popup(map, feat);
expect(popup).toBeA(ol.Popup);
var anchor = popup.getAnchor();
expect(anchor).toBeA(ol.Feature);
var geom = anchor.getGeometry();
expect(geom.getX()).toBe(21);
expect(geom.getY()).toBe(4);
});
/*
it("should be able to set the placement top of the location", function() {
var map = new ol.Map();
var popup = new ol.Popup(map, new ol.Loc(10,20),'top');
expect(popup).toBeA(ol.Popup);
popup.open();
var elems = goog.dom.getElementsByClass('ol-popup-top');
expect(elems.length).toBe(1);
elems = goog.dom.getElementsByClass('ol-popup-close');
expect(elems.length).toBe(1);
popup.close();
});
it("should be able to change the placement", function() {
var map = new ol.Map();
var popup = new ol.Popup(map, new ol.Loc(10,20),'top',false);
expect(popup).toBeA(ol.Popup);
popup.open();
var elems = goog.dom.getElementsByClass('ol-popup-top');
expect(elems.length).toBe(1);
elems = goog.dom.getElementsByClass('ol-popup-close');
expect(elems.length).toBe(0);
popup.setPlacement('right');
elems = goog.dom.getElementsByClass('ol-popup-top');
expect(elems.length).toBe(0);
elems = goog.dom.getElementsByClass('ol-popup-right');
expect(elems.length).toBe(1);
popup.close();
});
it("should be able to use a user provided container", function() {
var point = ol.geom.point([21, 4]);
var feat = ol.feature().geometry(point).set('name', 'foo');
var map = ol.map();
var popup = ol.popup({
map: map,
template: '<p>hello popup template! #ol3 feature name: {{name}}</p>'
});
expect(popup).toBeA(ol.Popup);
popup.open(feat);
//expect?
pop.template();
popup.close();
expect(goog.dom.getElementsByClass('ol-popup')).toBeNull();
});
*/
});