interaction.js | |
---|---|
wax = wax || {};
wax.mm = wax.mm || {}; | |
A chaining-style control that adds interaction to a modestmaps.Map object. Takes an options object with the following keys:
| wax.mm.interaction = function(map, tilejson, options) {
options = options || {};
tilejson = tilejson || {};
var MM = com.modestmaps,
waxGM = wax.GridManager(tilejson),
callbacks = options.callbacks || new wax.tooltip(),
clickAction = options.clickAction || ['full'],
clickHandler = options.clickHandler || function(url) {
window.location = url;
},
interaction = {},
_downLock = false,
_clickTimeout = false,
touchable = ('ontouchstart' in document.documentElement), |
Active feature | _af, |
Down event | _d, |
Touch tolerance | tol = 4,
tileGrid; |
Search through | function getTileGrid() { |
TODO: don't build for tiles outside of viewport Touch interaction leads to intermediate | var zoomLayer = map.createOrGetLayer(Math.round(map.getZoom())); |
Calculate a tile grid and cache it, by using the | return tileGrid || (tileGrid =
(function(t) {
var o = [];
for (var key in t) {
if (t[key].parentNode === zoomLayer) {
var offset = wax.util.offset(t[key]);
o.push([offset.top, offset.left, t[key]]);
}
}
return o;
})(map.tiles));
} |
When the map moves, the tile grid is no longer valid. | function clearTileGrid(map, e) {
tileGrid = null;
}
function getTile(e) {
for (var i = 0, grid = getTileGrid(); i < grid.length; i++) {
if ((grid[i][0] < e.y) &&
((grid[i][0] + 256) > e.y) &&
(grid[i][1] < e.x) &&
((grid[i][1] + 256) > e.x)) return grid[i][2];
}
return false;
} |
Clear the double-click timeout to prevent double-clicks from triggering popups. | function killTimeout() {
if (_clickTimeout) {
window.clearTimeout(_clickTimeout);
_clickTimeout = null;
return true;
} else {
return false;
}
}
function onMove(e) { |
If the user is actually dragging the map, exit early to avoid performance hits. | if (_downLock) return;
var pos = wax.util.eventoffset(e),
tile = getTile(pos),
feature;
tile && waxGM.getGrid(tile.src, function(err, g) {
if (err || !g) return;
if (feature = g.getFeature(pos.x, pos.y, tile, {
format: 'teaser'
})) {
if (feature && _af !== feature) {
_af = feature;
callbacks.out(map.parent);
callbacks.over(feature, map.parent, 0, e);
} else if (!feature) {
_af = null;
callbacks.out(map.parent);
}
} else {
_af = null;
callbacks.out(map.parent);
}
});
} |
A handler for 'down' events - which means | function onDown(e) { |
Ignore double-clicks by ignoring clicks within 300ms of each other. | if (killTimeout()) { return; } |
Prevent interaction offset calculations happening while the user is dragging the map. Store this event so that we can compare it to the up event | _downLock = true;
_d = wax.util.eventoffset(e);
if (e.type === 'mousedown') {
MM.addEvent(map.parent, 'mouseup', onUp); |
Only track single-touches. Double-touches will not affect this control | } else if (e.type === 'touchstart' && e.touches.length === 1) { |
turn this into touch-mode. Fallback to teaser and full. | this.clickAction = ['full', 'teaser']; |
Don't make the user click close if they hit another tooltip | if (callbacks._currentTooltip) {
callbacks.hideTooltip(callbacks._currentTooltip);
} |
Touch moves invalidate touches | MM.addEvent(map.parent, 'touchend', onUp);
MM.addEvent(map.parent, 'touchmove', touchCancel);
}
}
function touchCancel() {
MM.removeEvent(map.parent, 'touchend', onUp);
MM.removeEvent(map.parent, 'touchmove', onUp);
_downLock = false;
}
function onUp(e) {
var pos = wax.util.eventoffset(e);
MM.removeEvent(map.parent, 'mouseup', onUp);
if (map.parent.ontouchend) {
MM.removeEvent(map.parent, 'touchend', onUp);
MM.removeEvent(map.parent, 'touchmove', _touchCancel);
}
_downLock = false;
if (e.type === 'touchend') { |
If this was a touch and it survived, there's no need to avoid a double-tap | click(_d);
} else if (Math.round(pos.y / tol) === Math.round(_d.y / tol) &&
Math.round(pos.x / tol) === Math.round(_d.x / tol)) { |
Contain the event data in a closure. | _clickTimeout = window.setTimeout((function(pos) {
return function(e) {
click(e, pos);
};
})(pos));
}
return onUp;
} |
Handle a click event. Takes a second | function click(e, pos) {
var tile = getTile(pos),
feature;
tile && waxGM.getGrid(tile.src, function(err, g) {
for (var i = 0; g && i < clickAction.length; i++) {
if (feature = g.getFeature(pos.x, pos.y, tile, {
format: clickAction[i]
})) {
switch (clickAction[i]) {
case 'full': |
clickAction can be teaser in touch interaction | case 'teaser':
return callbacks.click(feature, map.parent, 0, e);
case 'location':
return clickHandler(feature);
}
}
}
});
} |
Attach listeners to the map | interaction.add = function() {
var l = ['zoomed', 'panned', 'centered',
'extentset', 'resized', 'drawn'];
for (var i = 0; i < l.length; i++) {
map.addCallback(l[i], clearTileGrid);
}
MM.addEvent(map.parent, 'mousemove', onMove);
MM.addEvent(map.parent, 'mousedown', onDown);
if (touchable) {
MM.addEvent(map.parent, 'touchstart', onDown);
}
return this;
}; |
Ensure chainability | return interaction.add(map);
};
|