Skip to content

Commit

Permalink
Add bindPopup API to the Marker class (#3056)
Browse files Browse the repository at this point in the history
* add bindPopup and other related methods to the Marker class

* add DOM tests for Marker

* add unbindPopup method to Marker with tests, add bindPopup to debug/markers.html

* refactor to conform to setter/getter conventions

* fix markers.html debug page
  • Loading branch information
mollymerp authored and lucaswoj committed Aug 25, 2016
1 parent 647f3c2 commit 9309bb2
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 14 deletions.
6 changes: 4 additions & 2 deletions debug/markers.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,22 @@

var lng = w + (e - w) * Math.random();
var lat = s + (n - s) * Math.random();
var popup = new mapboxgl.Popup().setText('hello hi');

var marker = new mapboxgl.Marker()
.setLngLat([lng, lat])
.setPopup(popup)
.addTo(map);

marker.getElement().onclick = function(e) {
alert('Hello world');
e.stopPropagation();
alert('clicked me');
};
}

for (var i = 0; i < 100; i++) addRandomMarker();

map.addControl(new mapboxgl.Navigation());

</script>

</body>
Expand Down
87 changes: 81 additions & 6 deletions js/ui/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
module.exports = Marker;

var DOM = require('../util/dom');
var util = require('../util/util');
var LngLat = require('../geo/lng_lat');
var Point = require('point-geometry');
var Popup = require('./popup');

/**
* Creates a marker component
Expand Down Expand Up @@ -36,7 +38,7 @@ Marker.prototype = {
* @param {Map} map
* @returns {Marker} `this`
*/
addTo: function(map) {
addTo: function (map) {
this.remove();
this._map = map;
map.getCanvasContainer().appendChild(this._el);
Expand All @@ -52,21 +54,22 @@ Marker.prototype = {
* marker.remove();
* @returns {Marker} `this`
*/
remove: function() {
remove: function () {
if (this._map) {
this._map.off('move', this._update);
this._map = null;
}
var parent = this._el.parentNode;
if (parent) parent.removeChild(this._el);
if (this._popup) this._closePopup();
return this;
},

/**
* Get the marker's geographical location
* @returns {LngLat}
*/
getLngLat: function() {
getLngLat: function () {
return this._lngLat;
},

Expand All @@ -75,19 +78,91 @@ Marker.prototype = {
* @param {LngLat} lnglat
* @returns {Marker} `this`
*/
setLngLat: function(lnglat) {
setLngLat: function (lnglat) {
this._lngLat = LngLat.convert(lnglat);
if (this._popup) this._popup.setLngLat(this._lngLat);
this._update();
return this;
},

getElement: function() {
getElement: function () {
return this._el;
},

_update: function() {
/**
* Binds a Popup to the Marker
* @param {Popup=} popup an instance of the `Popup` class. If undefined or null, any popup
* set on this `Marker` instance is unset
* @returns {Marker} `this`
*/

setPopup: function (popup) {
if (popup == null) {
this._closePopup();
delete this._popupHandlersAdded;
delete this._popup;
} else if (popup instanceof Popup) {
this._popup = popup;
} else {
util.warnOnce('Marker.setPopup only accepts an instance of the Popup class as an argument. If no argument is provided, the popup is unset from this Marker instance');
}

if (this._popup && this._lngLat) this._popup.setLngLat(this._lngLat);

if (!this._popupHandlersAdded) {
this.getElement().addEventListener('click', this._openPopup.bind(this));
this._popupHandlersAdded = true;
}
return this;
},

/**
* Returns the Popup instance that is bound to the Marker
* @returns {Popup} popup
*/
getPopup: function () {
return this._popup;
},

/**
* Opens or closes the bound popup, depending on the current state
* @returns {Marker} `this`
*/
togglePopup: function () {
if (this._popup) {
if (this._popup._map) {
this._closePopup();
} else {
this._openPopup();
}
}
},

_openPopup: function (e) {
// prevent event from bubbling up to the map canvas
e.stopPropagation();

if (!this._popup || !this._map) return;

if (!this._popup._map) {
this._popup.addTo(this._map);
}

return this;
},

_closePopup: function () {
if (this._popup) {
this._popup.remove();
}

return this;
},

_update: function () {
if (!this._map) return;
var pos = this._map.project(this._lngLat)._add(this._offset);
DOM.setTransform(this._el, 'translate(' + pos.x + 'px,' + pos.y + 'px)');
}
};

9 changes: 6 additions & 3 deletions js/util/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

exports.create = function (tagName, className, container) {
return {
offsetWidth: container.offsetWidth,
offsetHeight: container.offsetHeight,
offsetWidth: container ? container.offsetWidth : null,
offsetHeight: container ? container.offsetHeight : null,
remove: function () {},
addEventListener: function() {},
classList: {
add: function () {}
}
},
appendChild: function () {}
};
};

exports.setTransform = function() {};
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"handlebars": "4.0.5",
"highlight.js": "9.3.0",
"istanbul": "^0.4.2",
"jsdom": "^9.4.2",
"jsdom-global": "^2.1.0",
"json-loader": "^0.5.4",
"lodash": "^4.13.1",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#3e36b193a0c442a3fd863119f101afa6db97b32d",
Expand Down Expand Up @@ -93,15 +95,12 @@
"build-token": "browserify debug/access-token.js --debug --transform envify > debug/access-token-generated.js",
"watch-bench": "node bench/download-data.js && watchify bench/index.js --plugin [minifyify --no-map] --transform [babelify --presets react] --transform unassertify --transform envify --outfile bench/index-generated.js --verbose",
"start-server": "st --no-cache --localhost --port 9966 --index index.html .",

"start": "run-p build-token watch-dev watch-bench start-server",
"start-debug": "run-p build-token watch-dev start-server",
"start-bench": "run-p build-token watch-bench start-server",

"build-docs": "documentation build --github --format html --config documentation.yml --theme ./docs/_theme --output docs/api/",
"build": "npm run build-docs # invoked by publisher when publishing docs on the mb-pages branch",
"start-docs": "npm run build-min && npm run build-docs && jekyll serve --watch",

"lint": "eslint --ignore-path .gitignore js test bench docs/_posts/examples/*.html",
"open-changed-examples": "git diff --name-only mb-pages HEAD -- docs/_posts/examples/*.html | awk '{print \"http://127.0.0.1:4000/mapbox-gl-js/example/\" substr($0,33,length($0)-37)}' | xargs open",
"test-suite": "node test/render.test.js && node test/query.test.js",
Expand Down
90 changes: 90 additions & 0 deletions test/js/ui/marker.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict';

var test = require('tap').test;
var jsdomGlobal = require('jsdom-global');

var extend = require('../../../js/util/util').extend;
var Map = require('../../../js/ui/map');
var Marker = require('../../../js/ui/marker');
var Popup = require('../../../js/ui/popup');

function prepareDOM() {
var cleanup = jsdomGlobal();
var mapdiv = document.createElement('div');
mapdiv.id = "map";
document.body.appendChild(mapdiv);
return cleanup;
}

function createMap(options, callback) {
var map = new Map(extend({
container: 'map',
attributionControl: false,
trackResize: true,
style: {
"version": 8,
"sources": {},
"layers": []
}
}, options));

if (callback) map.on('load', function () {
callback(null, map);
});

return map;
}



test('Marker', function (t) {
t.test('constructor', function (t) {
var cleanup = prepareDOM();
var el = document.createElement('div');
var marker = new Marker(el);
t.ok(marker.getElement(), 'marker element is created');
cleanup();
t.end();
});

t.test('marker is added to map', function (t) {
var cleanup = prepareDOM();
var map = createMap();
var el = document.createElement('div');
map.on('load', function () {
var marker = new Marker(el).setLngLat([-77.01866, 38.888]);
t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance');
t.ok(marker._map, 'marker instance is bound to map instance');
cleanup();
t.end();
});
});

t.test('popups can be bound to marker instance', function (t) {
var cleanup = prepareDOM();
var map = createMap();
var el = document.createElement('div');
var popup = new Popup();
var marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map);
marker.setPopup(popup);
t.ok(marker.getPopup() instanceof Popup, 'popup created with Popup instance');
cleanup();
t.end();
});

t.test('popups can be unbound from a marker instance', function (t) {
var cleanup = prepareDOM();
var map = createMap();
var el = document.createElement('div');
var marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map);
marker.setPopup(new Popup());
t.ok(marker.getPopup() instanceof Popup);
t.ok(marker.setPopup() instanceof Marker, 'passing no argument to Marker.setPopup() is valid');
t.ok(!marker.getPopup(), 'Calling setPopup with no argument successfully removes Popup instance from Marker instance');
t.end();
cleanup();
});

t.end();
});

0 comments on commit 9309bb2

Please sign in to comment.