Comparing version 1.4.2 to 2.0.0
@@ -46,12 +46,8 @@ // Wax for Google Maps API v3 | ||
var key = zoom + '/' + coord.x + '/' + coord.y; | ||
this.cache[key] = this.cache[key] || $('<div></div>') | ||
.addClass('interactive-div-' + zoom) | ||
.width(256).height(256) | ||
.data('gTileKey', key) | ||
.append( | ||
$('<img />') | ||
.width(256).height(256) | ||
.attr('src', this.getTileUrl(coord, zoom)) | ||
.error(function() { $(this).hide() }) | ||
)[0]; | ||
if (!this.cache[key]) { | ||
var img = this.cache[key] = new Image(256, 256); | ||
this.cache[key].src = this.getTileUrl(coord, zoom); | ||
this.cache[key].setAttribute('gTileKey', key); | ||
this.cache[key].onerror = function() { img.style.display = 'none'; }; | ||
} | ||
return this.cache[key]; | ||
@@ -64,5 +60,5 @@ }; | ||
wax.g.MapType.prototype.releaseTile = function(tile) { | ||
var key = $(tile).data('gTileKey'); | ||
var key = tile.getAttribute('gTileKey'); | ||
this.cache[key] && delete this.cache[key]; | ||
$(tile).remove(); | ||
tile.parentNode && tile.parentNode.removeChild(tile); | ||
}; | ||
@@ -69,0 +65,0 @@ |
@@ -1,8 +0,3 @@ | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
@@ -21,4 +16,3 @@ // A layer connector for Modest Maps | ||
// * `zoomrange`: like [0, 10] (default [0, 18]) | ||
// | ||
com.modestmaps.WaxProvider = function(options) { | ||
wax.mm.provider = function(options) { | ||
this.layerName = options.layerName; | ||
@@ -32,3 +26,3 @@ this.baseUrls = (typeof(options.baseUrl) == 'string') ? | ||
com.modestmaps.WaxProvider.prototype = { | ||
wax.mm.provider.prototype = { | ||
outerLimits: function() { | ||
@@ -59,2 +53,2 @@ return [ | ||
com.modestmaps.extend(com.modestmaps.WaxProvider, com.modestmaps.MapProvider); | ||
com.modestmaps.extend(wax.mm.provider, com.modestmaps.MapProvider); |
@@ -1,155 +0,152 @@ | ||
// Wax for Google Maps API v3 | ||
// -------------------------- | ||
// Wax header | ||
var wax = wax || {}; | ||
wax = wax || {}; | ||
wax.g = wax.g || {}; | ||
// Controls constructor. | ||
wax.g.Controls = function(map) { | ||
this.map = map; | ||
// A control that adds interaction to a google Map object. | ||
// | ||
// Takes an options object with the following keys: | ||
// | ||
// * `callbacks` (optional): an `out`, `over`, and `click` callback. | ||
// If not given, the `wax.tooltip` library will be expected. | ||
// * `clickAction` (optional): **full** or **location**: default is | ||
// **full**. | ||
wax.g.interaction = function(map, options) { | ||
options = options || {}; | ||
// Our GridManager (from `gridutil.js`). This will keep the | ||
// cache of grid information and provide friendly utility methods | ||
// that return `GridTile` objects instead of raw data. | ||
var interaction = { | ||
modifyingEvents: ['dragstart', 'dragend', 'drag', 'zoom_changed', | ||
'resize', 'center_changed', 'bounds_changed'], | ||
// Find the map div reference. Munging of the google maps codebase makes | ||
// the key to this reference unpredictable, hence we iterate to find. | ||
this.mapDiv = false; | ||
for (var key in map) { | ||
// IE safe check for whether object is a DOM element. | ||
if (map[key] && map[key].nodeType > 0) { | ||
this.mapDiv = map[key]; | ||
break; | ||
} | ||
} | ||
}; | ||
waxGM: new wax.GridManager(), | ||
// Since Google Maps obscures mouseover events, grids need to calculated | ||
// in order to simulate them, and eventually do multi-layer interaction. | ||
wax.g.Controls.prototype.calculateGrid = function() { | ||
if (this.map.interaction_grid) return; | ||
// Get all 'marked' tiles, added by the `wax.g.MapType` layer. | ||
var interactive_tiles = $('div.interactive-div-' + this.map.getZoom() + ' img', this.mapDiv); | ||
var start_offset = $(this.mapDiv).offset(); | ||
// Return an array of objects which have the **relative** offset of | ||
// each tile, with a reference to the tile object in `tile`, since the API | ||
// returns evt coordinates as relative to the map object. | ||
var tiles = $(interactive_tiles).map(function(t) { | ||
var e_offset = $(interactive_tiles[t]).offset(); | ||
return { | ||
xy: { | ||
left: e_offset.left - start_offset.left, | ||
top: e_offset.top - start_offset.top | ||
}, | ||
tile: interactive_tiles[t] | ||
}; | ||
}); | ||
return tiles; | ||
}; | ||
// This requires wax.Tooltip or similar | ||
callbacks: options.callbacks || new wax.tooltip(), | ||
wax.g.Controls.prototype.interaction = function(options) { | ||
options = options || {}; | ||
var that = this; | ||
var gm = new wax.GridManager(); | ||
var f = null; | ||
clickAction: options.clickAction || 'full', | ||
// This requires wax.Tooltip or similar | ||
var callbacks = options.callbacks || { | ||
out: wax.tooltip.unselect, | ||
over: wax.tooltip.select, | ||
click: wax.tooltip.click | ||
}; | ||
// Attach listeners to the map | ||
add: function() { | ||
for (var i = 0; i < this.modifyingEvents.length; i++) { | ||
google.maps.event.addListener( | ||
map, | ||
this.modifyingEvents[i], | ||
wax.util.bind(this.clearTileGrid, this) | ||
); | ||
} | ||
google.maps.event.addListener(map, 'mousemove', this.onMove()); | ||
google.maps.event.addListener(map, 'click', this.click()); | ||
return this; | ||
}, | ||
var inTile = function(sevt, xy) { | ||
if ((xy.top < sevt.y) && | ||
((xy.top + 256) > sevt.y) && | ||
(xy.left < sevt.x) && | ||
((xy.left + 256) > sevt.x)) { | ||
return true; | ||
} | ||
}; | ||
// Search through `.tiles` and determine the position, | ||
// from the top-left of the **document**, and cache that data | ||
// so that `mousemove` events don't always recalculate. | ||
getTileGrid: function() { | ||
// Get all 'marked' tiles, added by the `wax.g.MapType` layer. | ||
// Return an array of objects which have the **relative** offset of | ||
// each tile, with a reference to the tile object in `tile`, since the API | ||
// returns evt coordinates as relative to the map object. | ||
if (!this._getTileGrid) { | ||
this._getTileGrid = []; | ||
var zoom = map.getZoom(); | ||
var mapOffset = wax.util.offset(map.getDiv()); | ||
for (var i in map.mapTypes) { | ||
if (!map.mapTypes[i].interactive) continue; | ||
var find = $.proxy(function(map, evt) { | ||
var found = false; | ||
var interaction_grid = this.calculateGrid(); | ||
for (var i = 0; i < interaction_grid.length && !found; i++) { | ||
if (inTile(evt.pixel, interaction_grid[i].xy)) { | ||
var found = interaction_grid[i]; | ||
var mapType = map.mapTypes[i]; | ||
for (var key in mapType.cache) { | ||
if (key.split('/')[0] != zoom) continue; | ||
var tileOffset = wax.util.offset(mapType.cache[key]); | ||
this._getTileGrid.push([ | ||
tileOffset.top - mapOffset.top, | ||
tileOffset.left - mapOffset.left, | ||
mapType.cache[key] | ||
]); | ||
} | ||
} | ||
} | ||
} | ||
return found; | ||
}, this); | ||
return this._getTileGrid; | ||
}, | ||
google.maps.event.addListener(this.map, 'mousemove', function(evt) { | ||
var opt = { format: 'teaser' }; | ||
var found = find(this.map, evt); | ||
if (!found) return; | ||
gm.getGrid($(found.tile).attr('src'), function(g) { | ||
if (!g) return; | ||
var feature = g.getFeature( | ||
evt.pixel.x + $(that.mapDiv).offset().left, | ||
evt.pixel.y + $(that.mapDiv).offset().top, | ||
found.tile, | ||
opt | ||
); | ||
if (feature !== f) { | ||
callbacks.out(feature, $(that.mapDiv), 0); | ||
callbacks.over(feature, $(that.mapDiv), 0); | ||
f = feature; | ||
} | ||
}); | ||
}); | ||
clearTileGrid: function(map, e) { | ||
this._getTileGrid = null; | ||
}, | ||
google.maps.event.addListener(this.map, 'click', function(evt) { | ||
var opt = { | ||
format: options.clickAction || 'full' | ||
}; | ||
var found = find(this.map, evt); | ||
if (!found) return; | ||
gm.getGrid($(found.tile).attr('src'), function(g) { | ||
if (!g) return; | ||
var feature = g.getFeature( | ||
evt.pixel.x + $(that.mapDiv).offset().left, | ||
evt.pixel.y + $(that.mapDiv).offset().top, | ||
found.tile, | ||
opt | ||
); | ||
if (feature) { | ||
if (opt.format == 'full') { | ||
callbacks.click(feature, $(that.mapDiv), 0); | ||
} else { | ||
window.location = feature; | ||
getTile: function(evt) { | ||
var tile; | ||
var grid = this.getTileGrid(); | ||
for (var i = 0; i < grid.length; i++) { | ||
if ((grid[i][0] < evt.pixel.y) && | ||
((grid[i][0] + 256) > evt.pixel.y) && | ||
(grid[i][1] < evt.pixel.x) && | ||
((grid[i][1] + 256) > evt.pixel.x)) { | ||
tile = grid[i][2]; | ||
break; | ||
} | ||
} | ||
}); | ||
}); | ||
return tile || false; | ||
}, | ||
// Ensure chainability | ||
return this; | ||
}; | ||
onMove: function(evt) { | ||
if (!this._onMove) this._onMove = wax.util.bind(function(evt) { | ||
var tile = this.getTile(evt); | ||
if (tile) { | ||
this.waxGM.getGrid(tile.src, wax.util.bind(function(err, g) { | ||
if (err || !g) return; | ||
var feature = g.getFeature( | ||
evt.pixel.x + wax.util.offset(map.getDiv()).left, | ||
evt.pixel.y + wax.util.offset(map.getDiv()).top, | ||
tile, | ||
{ format: 'teaser' } | ||
); | ||
// Support only a single layer. | ||
// Thus a layer index of **0** is given to the tooltip library | ||
if (feature && this.feature !== feature) { | ||
this.feature = feature; | ||
this.callbacks.out(map.getDiv()); | ||
this.callbacks.over(feature, map.getDiv(), 0, evt); | ||
} else if (!feature) { | ||
this.feature = null; | ||
this.callbacks.out(map.getDiv()); | ||
} | ||
}, this)); | ||
} | ||
}, this); | ||
return this._onMove; | ||
}, | ||
wax.g.Controls.prototype.legend = function() { | ||
var legend = new wax.Legend($(this.mapDiv)), | ||
url = null; | ||
click: function(evt) { | ||
if (!this._onClick) this._onClick = wax.util.bind(function(evt) { | ||
var tile = this.getTile(evt); | ||
if (tile) { | ||
this.waxGM.getGrid(tile.src, wax.util.bind(function(err, g) { | ||
if (err || !g) return; | ||
var feature = g.getFeature( | ||
evt.pixel.x + wax.util.offset(map.getDiv()).left, | ||
evt.pixel.y + wax.util.offset(map.getDiv()).top, | ||
tile, | ||
{ format: this.clickAction } | ||
); | ||
if (feature) { | ||
switch (this.clickAction) { | ||
case 'full': | ||
this.callbacks.click(feature, map.getDiv(), 0, evt); | ||
break; | ||
case 'location': | ||
window.location = feature; | ||
break; | ||
} | ||
} | ||
}, this)); | ||
} | ||
}, this); | ||
return this._onClick; | ||
} | ||
}; | ||
// Ideally we would use the 'tilesloaded' event here. This doesn't seem to | ||
// work so we use the much less appropriate 'idle' event. | ||
google.maps.event.addListener(this.map, 'idle', $.proxy(function() { | ||
if (url) return; | ||
var img = $('div.interactive-div-' + this.map.getZoom() + ' img:first', | ||
this.mapDiv); | ||
img && (url = img.attr('src')) && legend.render([url]); | ||
}, this)); | ||
// Ensure chainability | ||
return this; | ||
// Return the interaction control such that the caller may manipulate it | ||
// e.g. remove it. | ||
return interaction.add(map); | ||
}; | ||
wax.g.Controls.prototype.embedder = function(script_id) { | ||
$(this.mapDiv).prepend($('<input type="text" class="embed-src" />') | ||
.css({ | ||
'z-index': '9999999999', | ||
'position': 'relative' | ||
}) | ||
.val("<div id='" + script_id + "'>" + $('#' + script_id).html() + '</div>')); | ||
// Ensure chainability | ||
return this; | ||
}; |
@@ -7,25 +7,2 @@ // Wax GridUtil | ||
// Nondrag | ||
// ------- | ||
// A simple abstraction from the `mousemove` handler that doesn't | ||
// trigger mousemove events while dragging. | ||
(function($) { | ||
$.fn.extend({ | ||
nondrag: function(callback) { | ||
$(this).bind('mousedown mouseup mousemove', function(evt) { | ||
var down = false; | ||
if (evt.type === 'mouseup') { | ||
down = false; | ||
} else if (down || evt.type === 'mousedown') { | ||
down = true; | ||
// Don't trigger the callback if this is a drag. | ||
return; | ||
} | ||
callback(evt); | ||
}); | ||
return this; | ||
} | ||
}); | ||
})(jQuery); | ||
// Request | ||
@@ -41,3 +18,3 @@ // ------- | ||
if (this.cache[url]) { | ||
return callback(this.cache[url]); | ||
return callback(this.cache[url][0], this.cache[url][1]); | ||
// Cache miss. | ||
@@ -52,19 +29,18 @@ } else { | ||
this.locks[url] = true; | ||
$.jsonp({ | ||
url: url, | ||
context: this, | ||
callback: 'grid', | ||
callbackParameter: 'callback', | ||
reqwest({ | ||
url: url + '?callback=grid', | ||
type: 'jsonp', | ||
jsonpCallback: 'callback', | ||
success: function(data) { | ||
that.locks[url] = false; | ||
that.cache[url] = data; | ||
that.cache[url] = [null, data]; | ||
for (var i = 0; i < that.promises[url].length; i++) { | ||
that.promises[url][i](that.cache[url]); | ||
that.promises[url][i](that.cache[url][0], that.cache[url][1]); | ||
} | ||
}, | ||
error: function() { | ||
error: function(err) { | ||
that.locks[url] = false; | ||
that.cache[url] = null; | ||
that.cache[url] = [err, null]; | ||
for (var i = 0; i < that.promises[url].length; i++) { | ||
that.promises[url][i](that.cache[url]); | ||
that.promises[url][i](that.cache[url][0], that.cache[url][1]); | ||
} | ||
@@ -84,2 +60,3 @@ } | ||
this.formatter = formatter; | ||
// tileRes is the grid-elements-per-pixel ratio of gridded data. | ||
this.tileRes = 4; | ||
@@ -100,32 +77,27 @@ }; | ||
wax.GridInstance.prototype.getFeature = function(x, y, tile_element, options) { | ||
if (!(this.grid_tile && this.grid_tile.grid)) return; | ||
var tileX, tileY; | ||
if (tile_element.left && tile_element.top) { | ||
tileX = tile_element.left; | ||
tileY = tile_element.top; | ||
} else { | ||
var $tile_element = $(tile_element); | ||
// IE problem here - though recoverable, for whatever reason | ||
tileX = $tile_element.offset().left; | ||
tileY = $tile_element.offset().top; | ||
} | ||
if (Math.floor((y - tileY) / this.tileRes) > 256 || | ||
Math.floor((x - tileX) / this.tileRes) > 256) return; | ||
if (!(this.grid_tile && this.grid_tile.grid)) return; | ||
var key = this.grid_tile.grid[ | ||
Math.floor((y - tileY) / this.tileRes) | ||
].charCodeAt( | ||
Math.floor((x - tileX) / this.tileRes) | ||
); | ||
// IE problem here - though recoverable, for whatever reason | ||
var offset = wax.util.offset(tile_element); | ||
var tileX = offset.left; | ||
var tileY = offset.top; | ||
key = this.resolveCode(key); | ||
if (y - tileY < 0) return; | ||
if (x - tileX < 0) return; | ||
if (Math.floor((y - tileY) / this.tileRes) > 256) return; | ||
if (Math.floor((x - tileX) / this.tileRes) > 256) return; | ||
// If this layers formatter hasn't been loaded yet, | ||
// download and load it now. | ||
if (this.grid_tile.keys[key]) { | ||
return this.formatter.format( | ||
options, | ||
this.grid_tile.data[this.grid_tile.keys[key]] | ||
var key = this.grid_tile.grid[ | ||
Math.floor((y - tileY) / this.tileRes) | ||
].charCodeAt( | ||
Math.floor((x - tileX) / this.tileRes) | ||
); | ||
} | ||
key = this.resolveCode(key); | ||
// If this layers formatter hasn't been loaded yet, | ||
// download and load it now. | ||
if (this.grid_tile.keys[key] && this.grid_tile.data[this.grid_tile.keys[key]]) { | ||
return this.formatter.format(options, this.grid_tile.data[this.grid_tile.keys[key]]); | ||
} | ||
}; | ||
@@ -148,7 +120,8 @@ | ||
var that = this; | ||
that.getFormatter(that.formatterUrl(url), function(f) { | ||
if (!f) return callback(false); | ||
that.getFormatter(that.formatterUrl(url), function(err, f) { | ||
if (err || !f) return callback(err, null); | ||
wax.request.get(that.tileDataUrl(url), function(t) { | ||
callback(new wax.GridInstance(t, f)); | ||
wax.request.get(that.tileDataUrl(url), function(err, t) { | ||
if (err) return callback(err, null); | ||
callback(null, new wax.GridInstance(t, f)); | ||
}); | ||
@@ -158,12 +131,2 @@ }); | ||
// Create a cross-browser event object | ||
wax.GridManager.prototype.makeEvent = function(evt) { | ||
return { | ||
target: evt.target || evt.srcElement, | ||
pX: evt.pageX || evt.clientX, | ||
pY: evt.pageY || evt.clientY, | ||
evt: evt | ||
}; | ||
}; | ||
// Simplistically derive the URL of the grid data endpoint from a tile URL | ||
@@ -181,17 +144,17 @@ wax.GridManager.prototype.tileDataUrl = function(url) { | ||
wax.GridManager.prototype.getFormatter = function(url, callback) { | ||
var that = this; | ||
// Formatter is cached. | ||
if (typeof this.formatters[url] !== 'undefined') { | ||
callback(this.formatters[url]); | ||
return; | ||
} else { | ||
wax.request.get(url, function(data) { | ||
if (data && data.formatter) { | ||
that.formatters[url] = new wax.Formatter(data); | ||
} else { | ||
that.formatters[url] = false; | ||
} | ||
callback(that.formatters[url]); | ||
}); | ||
} | ||
var that = this; | ||
// Formatter is cached. | ||
if (typeof this.formatters[url] !== 'undefined') { | ||
callback(null, this.formatters[url]); | ||
return; | ||
} else { | ||
wax.request.get(url, function(err, data) { | ||
if (data && data.formatter) { | ||
that.formatters[url] = new wax.Formatter(data); | ||
} else { | ||
that.formatters[url] = false; | ||
} | ||
callback(err, that.formatters[url]); | ||
}); | ||
} | ||
}; | ||
@@ -198,0 +161,0 @@ |
@@ -8,27 +8,34 @@ // Wax Legend | ||
wax.Legend = function(context, container) { | ||
this.legends = {}; | ||
this.context = context; | ||
this.container = container || $('<div class="wax-legends"></div>'); | ||
this.legends = {}; | ||
$(this.context).append(this.container); | ||
this.container = container; | ||
if (!this.container) { | ||
this.container = document.createElement('div'); | ||
this.container.className = 'wax-legends'; | ||
} | ||
this.context.appendChild(this.container); | ||
}; | ||
wax.Legend.prototype.render = function(urls) { | ||
$('.wax-legend', this.container).hide(); | ||
var render = $.proxy(function(url, content) { | ||
var url; | ||
for (url in this.legends) { | ||
this.legends[url].style.display = 'none'; | ||
} | ||
var render = wax.util.bind(function(url, content) { | ||
if (!content) { | ||
this.legends[url] = false; | ||
} else if (this.legends[url]) { | ||
this.legends[url].show(); | ||
this.legends[url].style.display = 'block'; | ||
} else { | ||
this.legends[url] = $("<div class='wax-legend'></div>").append(content); | ||
this.container.append(this.legends[url]); | ||
this.legends[url] = document.createElement('div'); | ||
this.legends[url].className = 'wax-legend'; | ||
this.legends[url].innerHTML = content; | ||
this.container.appendChild(this.legends[url]); | ||
} | ||
}, this); | ||
var renderLegend = function(data) { | ||
if (data && data.legend) render(url, data.legend); | ||
}; | ||
for (var i = 0; i < urls.length; i++) { | ||
var url = this.legendUrl(urls[i]); | ||
wax.request.get(url, renderLegend); | ||
url = this.legendUrl(urls[i]); | ||
wax.request.get(url, function(err, data) { | ||
if (data && data.legend) render(url, data.legend); | ||
}); | ||
} | ||
@@ -35,0 +42,0 @@ }; |
@@ -1,30 +0,45 @@ | ||
// Wax GridUtil | ||
// ------------ | ||
// Wax header | ||
var wax = wax || {}; | ||
wax.tooltip = {}; | ||
wax.tooltip = function(options) { | ||
this._currentTooltip = undefined; | ||
options = options || {}; | ||
if (options.animationOut) this.animationOut = options.animationOut; | ||
if (options.animationIn) this.animationIn = options.animationIn; | ||
}; | ||
// Helper function to determine whether a given element is a wax popup. | ||
wax.tooltip.prototype.isPopup = function(el) { | ||
return el && el.className.indexOf('wax-popup') !== -1; | ||
}; | ||
// Get the active tooltip for a layer or create a new one if no tooltip exists. | ||
// Hide any tooltips on layers underneath this one. | ||
wax.tooltip.getToolTip = function(feature, context, index, evt) { | ||
var tooltip = $(context).children('div.wax-tooltip-' + | ||
index + | ||
':not(.removed)'); | ||
if (tooltip.size() === 0) { | ||
tooltip = $("<div class='wax-tooltip wax-tooltip-" + | ||
index + | ||
"'>" + | ||
'</div>').html(feature); | ||
if (!$(context).triggerHandler('addedtooltip', [tooltip, context, evt])) { | ||
$(context).append(tooltip); | ||
} | ||
wax.tooltip.prototype.getTooltip = function(feature, context, index, evt) { | ||
tooltip = document.createElement('div'); | ||
tooltip.className = 'wax-tooltip wax-tooltip-' + index; | ||
tooltip.innerHTML = feature; | ||
context.appendChild(tooltip); | ||
return tooltip; | ||
}; | ||
// Hide a given tooltip. | ||
wax.tooltip.prototype.hideTooltip = function(el) { | ||
if (!el) return; | ||
var event; | ||
var remove = function() { | ||
this.parentNode.removeChild(this); | ||
}; | ||
if (el.style['-webkit-transition'] !== undefined && this.animationOut) { | ||
event = 'webkitTransitionEnd'; | ||
} else if (el.style.MozTransition !== undefined && this.animationOut) { | ||
event = 'transitionend'; | ||
} | ||
for (var i = (index - 1); i > 0; i--) { | ||
var fallback = $('div.wax-tooltip-' + i + ':not(.removed)'); | ||
if (fallback.size() > 0) { | ||
fallback.addClass('hidden').hide(); | ||
} | ||
if (event) { | ||
el.addEventListener(event, remove, false); | ||
el.addEventListener('transitionend', remove, false); | ||
el.className += ' ' + this.animationOut; | ||
} else { | ||
el.parentNode.removeChild(el); | ||
} | ||
return tooltip; | ||
}; | ||
@@ -34,47 +49,57 @@ | ||
// shown until this popup is closed or another popup is opened. | ||
wax.tooltip.click = function(feature, context, index) { | ||
var tooltip = wax.tooltip.getToolTip(feature, context, index); | ||
var close = $('<a href="#close" class="close">Close</a>'); | ||
close.click(function() { | ||
tooltip | ||
.addClass('removed') | ||
.fadeOut('fast', function() { $(this).remove(); }); | ||
wax.tooltip.prototype.click = function(feature, context, index) { | ||
// Hide any current tooltips. | ||
this.unselect(context); | ||
var tooltip = this.getTooltip(feature, context, index); | ||
var close = document.createElement('a'); | ||
close.href = '#close'; | ||
close.className = 'close'; | ||
close.innerHTML = 'Close'; | ||
var closeClick = wax.util.bind(function(ev) { | ||
this.hideTooltip(tooltip); | ||
this._currentTooltip = undefined; | ||
ev.returnValue = false; // Prevents hash change. | ||
if (ev.stopPropagation) ev.stopPropagation(); | ||
if (ev.preventDefault) ev.preventDefault(); | ||
return false; | ||
}); | ||
tooltip | ||
.addClass('wax-popup') | ||
.html(feature) | ||
.append(close); | ||
}, this); | ||
// IE compatibility. | ||
if (close.addEventListener) { | ||
close.addEventListener('click', closeClick, false); | ||
} else if (close.attachEvent) { | ||
close.attachEvent('onclick', closeClick); | ||
} | ||
tooltip.className += ' wax-popup'; | ||
tooltip.innerHTML = feature; | ||
tooltip.appendChild(close); | ||
this._currentTooltip = tooltip; | ||
}; | ||
// Show a tooltip. | ||
wax.tooltip.select = function(feature, context, layer_id, evt) { | ||
wax.tooltip.prototype.select = function(feature, context, layer_id, evt) { | ||
if (!feature) return; | ||
if (this.isPopup(this._currentTooltip)) return; | ||
wax.tooltip.getToolTip(feature, context, layer_id, evt); | ||
$(context).css('cursor', 'pointer'); | ||
$('div', context).css('cursor', 'pointer'); | ||
this._currentTooltip = this.getTooltip(feature, context, layer_id, evt); | ||
context.style.cursor = 'pointer'; | ||
}; | ||
// Hide all tooltips on this layer and show the first hidden tooltip on the | ||
// highest layer underneath if found. | ||
wax.tooltip.unselect = function(feature, context, layer_id, evt) { | ||
$(context) | ||
.css('cursor', 'default'); | ||
if (layer_id) { | ||
$('div.wax-tooltip-' + layer_id + ':not(.wax-popup)') | ||
.remove(); | ||
} else { | ||
$('div.wax-tooltip:not(.wax-popup)') | ||
.remove(); | ||
wax.tooltip.prototype.unselect = function(context) { | ||
if (this.isPopup(this._currentTooltip)) return; | ||
context.style.cursor = 'default'; | ||
if (this._currentTooltip) { | ||
this.hideTooltip(this._currentTooltip); | ||
this._currentTooltip = undefined; | ||
} | ||
}; | ||
// TODO: remove | ||
$('div', context).css('cursor', 'default'); | ||
$('div.wax-tooltip:first') | ||
.removeClass('hidden') | ||
.show(); | ||
$(context).triggerHandler('removedtooltip', [feature, context, evt]); | ||
}; | ||
wax.tooltip.prototype.out = wax.tooltip.prototype.unselect; | ||
wax.tooltip.prototype.over = wax.tooltip.prototype.select; | ||
wax.tooltip.prototype.click = wax.tooltip.prototype.click; |
@@ -1,27 +0,8 @@ | ||
// Wax: Box Selector | ||
// ----------------- | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
com.modestmaps.Map.prototype.boxselector = function(opts) { | ||
var boxDiv = document.createElement('div'); | ||
boxDiv.id = this.parent.id + '-boxselector'; | ||
boxDiv.className = 'boxselector-box-container'; | ||
boxDiv.style.width = this.dimensions.x + 'px'; | ||
boxDiv.style.height = this.dimensions.y + 'px'; | ||
this.parent.appendChild(boxDiv); | ||
var box = document.createElement('div'); | ||
box.id = this.parent.id + '-boxselector-box'; | ||
box.className = 'boxselector-box'; | ||
boxDiv.appendChild(box); | ||
// Box Selector | ||
// ------------ | ||
wax.mm.boxselector = function(map, opts) { | ||
var mouseDownPoint = null; | ||
var map = this; | ||
@@ -32,103 +13,113 @@ var callback = (typeof opts === 'function') ? | ||
var boxselector = this.boxselector; | ||
this.boxselector.getMousePoint = function(e) { | ||
// start with just the mouse (x, y) | ||
var point = new com.modestmaps.Point(e.clientX, e.clientY); | ||
// correct for scrolled document | ||
point.x += document.body.scrollLeft + document.documentElement.scrollLeft; | ||
point.y += document.body.scrollTop + document.documentElement.scrollTop; | ||
var boxselector = { | ||
add: function(map) { | ||
this.boxDiv = document.createElement('div'); | ||
this.boxDiv.id = map.parent.id + '-boxselector-box'; | ||
this.boxDiv.className = 'boxselector-box'; | ||
map.parent.appendChild(this.boxDiv); | ||
// correct for nested offsets in DOM | ||
for (var node = map.parent; node; node = node.offsetParent) { | ||
point.x -= node.offsetLeft; | ||
point.y -= node.offsetTop; | ||
} | ||
return point; | ||
}; | ||
com.modestmaps.addEvent(map.parent, 'mousedown', this.mouseDown()); | ||
map.addCallback('drawn', this.drawbox()); | ||
}, | ||
remove: function() { | ||
map.parent.removeChild(this.boxDiv); | ||
map.removeCallback('mousedown', this.drawbox()); | ||
}, | ||
getMousePoint: function(e) { | ||
// start with just the mouse (x, y) | ||
var point = new com.modestmaps.Point(e.clientX, e.clientY); | ||
// correct for scrolled document | ||
point.x += document.body.scrollLeft + document.documentElement.scrollLeft; | ||
point.y += document.body.scrollTop + document.documentElement.scrollTop; | ||
this.boxselector.mouseDown = function(e) { | ||
if (e.shiftKey) { | ||
mouseDownPoint = boxselector.getMousePoint(e); | ||
// correct for nested offsets in DOM | ||
for (var node = map.parent; node; node = node.offsetParent) { | ||
point.x -= node.offsetLeft; | ||
point.y -= node.offsetTop; | ||
} | ||
return point; | ||
}, | ||
mouseDown: function() { | ||
if (!this._mouseDown) this._mouseDown = wax.util.bind(function(e) { | ||
if (e.shiftKey) { | ||
mouseDownPoint = this.getMousePoint(e); | ||
box.style.left = mouseDownPoint.x + 'px'; | ||
box.style.top = mouseDownPoint.y + 'px'; | ||
box.style.height = 'auto'; | ||
box.style.width = 'auto'; | ||
this.boxDiv.style.left = mouseDownPoint.x + 'px'; | ||
this.boxDiv.style.top = mouseDownPoint.y + 'px'; | ||
com.modestmaps.addEvent(map.parent, 'mousemove', boxselector.mouseMove); | ||
com.modestmaps.addEvent(map.parent, 'mouseup', boxselector.mouseUp); | ||
com.modestmaps.addEvent(map.parent, 'mousemove', this.mouseMove()); | ||
com.modestmaps.addEvent(map.parent, 'mouseup', this.mouseUp()); | ||
map.parent.style.cursor = 'crosshair'; | ||
return com.modestmaps.cancelEvent(e); | ||
} | ||
}; | ||
map.parent.style.cursor = 'crosshair'; | ||
return com.modestmaps.cancelEvent(e); | ||
} | ||
}, this); | ||
return this._mouseDown; | ||
}, | ||
mouseMove: function(e) { | ||
if (!this._mouseMove) this._mouseMove = wax.util.bind(function(e) { | ||
var point = this.getMousePoint(e); | ||
this.boxDiv.style.display = 'block'; | ||
if (point.x < mouseDownPoint.x) { | ||
this.boxDiv.style.left = point.x + 'px'; | ||
} else { | ||
this.boxDiv.style.left = mouseDownPoint.x + 'px'; | ||
} | ||
this.boxDiv.style.width = Math.abs(point.x - mouseDownPoint.x) + 'px'; | ||
if (point.y < mouseDownPoint.y) { | ||
this.boxDiv.style.top = point.y + 'px'; | ||
} else { | ||
this.boxDiv.style.top = mouseDownPoint.y + 'px'; | ||
} | ||
this.boxDiv.style.height = Math.abs(point.y - mouseDownPoint.y) + 'px'; | ||
return com.modestmaps.cancelEvent(e); | ||
}, this); | ||
return this._mouseMove; | ||
}, | ||
mouseUp: function() { | ||
if (!this._mouseUp) this._mouseUp = wax.util.bind(function(e) { | ||
var point = boxselector.getMousePoint(e); | ||
this.boxselector.mouseMove = function(e) { | ||
var point = boxselector.getMousePoint(e); | ||
box.style.display = 'block'; | ||
if (point.x < mouseDownPoint.x) { | ||
box.style.left = point.x + 'px'; | ||
box.style.right = (map.dimensions.x - mouseDownPoint.x) + 'px'; | ||
} else { | ||
box.style.left = mouseDownPoint.x + 'px'; | ||
box.style.right = (map.dimensions.x - point.x) + 'px'; | ||
} | ||
if (point.y < mouseDownPoint.y) { | ||
box.style.top = point.y + 'px'; | ||
} else { | ||
box.style.bottom = (map.dimensions.y - point.y) + 'px'; | ||
} | ||
return com.modestmaps.cancelEvent(e); | ||
}; | ||
var l1 = map.pointLocation(point), | ||
l2 = map.pointLocation(mouseDownPoint); | ||
this.boxselector.mouseUp = function(e) { | ||
var point = boxselector.getMousePoint(e); | ||
// Format coordinates like mm.map.getExtent(). | ||
var extent = [ | ||
new com.modestmaps.Location( | ||
Math.max(l1.lat, l2.lat), | ||
Math.min(l1.lon, l2.lon)), | ||
new com.modestmaps.Location( | ||
Math.min(l1.lat, l2.lat), | ||
Math.max(l1.lon, l2.lon)) | ||
]; | ||
var l1 = map.pointLocation(point), | ||
l2 = map.pointLocation(mouseDownPoint); | ||
this.box = [l1, l2]; | ||
callback(extent); | ||
// Format coordinates like mm.map.getExtent(). | ||
var extent = []; | ||
extent.push(new com.modestmaps.Location( | ||
Math.max(l1.lat, l2.lat), | ||
Math.min(l1.lon, l2.lon))); | ||
extent.push(new com.modestmaps.Location( | ||
Math.min(l1.lat, l2.lat), | ||
Math.max(l1.lon, l2.lon))); | ||
com.modestmaps.removeEvent(map.parent, 'mousemove', this.mouseMove()); | ||
com.modestmaps.removeEvent(map.parent, 'mouseup', this.mouseUp()); | ||
boxselector.box = [l1, l2]; | ||
callback(extent); | ||
com.modestmaps.removeEvent(map.parent, 'mousemove', boxselector.mouseMove); | ||
com.modestmaps.removeEvent(map.parent, 'mouseup', boxselector.mouseUp); | ||
map.parent.style.cursor = 'auto'; | ||
return com.modestmaps.cancelEvent(e); | ||
}; | ||
com.modestmaps.addEvent(boxDiv, 'mousedown', boxselector.mouseDown); | ||
var drawbox = function(map, e) { | ||
if (map.boxselector.box) { | ||
box.style.display = 'block'; | ||
box.style.height = 'auto'; | ||
box.style.width = 'auto'; | ||
var br = map.locationPoint(map.boxselector.box[0]); | ||
var tl = map.locationPoint(map.boxselector.box[1]); | ||
box.style.left = Math.max(0, tl.x) + 'px'; | ||
box.style.top = Math.max(0, tl.y) + 'px'; | ||
box.style.right = Math.max(0, map.dimensions.x - br.x) + 'px'; | ||
box.style.bottom = Math.max(0, map.dimensions.y - br.y) + 'px'; | ||
map.parent.style.cursor = 'auto'; | ||
}, this); | ||
return this._mouseUp; | ||
}, | ||
drawbox: function() { | ||
if (!this._drawbox) this._drawbox = wax.util.bind(function(map, e) { | ||
if (this.boxDiv) { | ||
this.boxDiv.style.display = 'block'; | ||
this.boxDiv.style.height = 'auto'; | ||
this.boxDiv.style.width = 'auto'; | ||
var br = map.locationPoint(this.box[0]); | ||
var tl = map.locationPoint(this.box[1]); | ||
this.boxDiv.style.left = Math.max(0, tl.x) + 'px'; | ||
this.boxDiv.style.top = Math.max(0, tl.y) + 'px'; | ||
this.boxDiv.style.right = Math.max(0, map.dimensions.x - br.x) + 'px'; | ||
this.boxDiv.style.bottom = Math.max(0, map.dimensions.y - br.y) + 'px'; | ||
} | ||
}, this); | ||
return this._drawbox; | ||
} | ||
}; | ||
this.addCallback('drawn', drawbox); | ||
this.boxselector.remove = function() { | ||
boxDiv.parentNode.removeChild(boxDiv); | ||
map.removeCallback('mousedown', drawbox); | ||
}; | ||
return this; | ||
return boxselector.add(map); | ||
}; |
@@ -1,48 +0,53 @@ | ||
// Wax: Fullscreen | ||
// ----------------- | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// Fullscreen | ||
// ---------- | ||
// A simple fullscreen control for Modest Maps | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
// Add zoom links, which can be styled as buttons, to a `modestmaps.Map` | ||
// control. This function can be used chaining-style with other | ||
// chaining-style controls. | ||
com.modestmaps.Map.prototype.fullscreen = function() { | ||
// Modest Maps demands an absolute height & width, and doesn't auto-correct | ||
// for changes, so here we save the original size of the element and | ||
// restore to that size on exit from fullscreen. | ||
$('<a class="wax-fullscreen" href="#fullscreen">fullscreen</a>') | ||
.toggle( | ||
$.proxy(this.maximize, this), | ||
$.proxy(this.minimize, this) | ||
) | ||
.appendTo(this.parent); | ||
return this; | ||
}; | ||
wax.mm.fullscreen = function(map, opts) { | ||
com.modestmaps.Map.prototype.maximize = function(e) { | ||
if (e) { | ||
e.preventDefault(); | ||
} | ||
this.smallSize = [$(this.parent).width(), $(this.parent).height()]; | ||
$(this.parent).addClass('wax-fullscreen-map'); | ||
this.setSize( | ||
$(this.parent).outerWidth(), | ||
$(this.parent).outerHeight()); | ||
}; | ||
var fullscreen = { | ||
state: 1, // minimized | ||
com.modestmaps.Map.prototype.minimize = function(e) { | ||
if (e) { | ||
e.preventDefault(); | ||
} | ||
$(this.parent).removeClass('wax-fullscreen-map'); | ||
this.setSize( | ||
this.smallSize[0], | ||
this.smallSize[1]); | ||
// Modest Maps demands an absolute height & width, and doesn't auto-correct | ||
// for changes, so here we save the original size of the element and | ||
// restore to that size on exit from fullscreen. | ||
add: function(map) { | ||
this.a = document.createElement('a'); | ||
this.a.className = 'wax-fullscreen'; | ||
this.a.href = '#fullscreen'; | ||
this.a.innerHTML = 'fullscreen'; | ||
map.parent.appendChild(this.a); | ||
com.modestmaps.addEvent(this.a, 'click', this.click(map)); | ||
return this; | ||
}, | ||
click: function(map) { | ||
if (this._click) return this._click; | ||
else this._click = wax.util.bind(function(e) { | ||
if (e) com.modestmaps.cancelEvent(e); | ||
if (this.state) { | ||
this.smallSize = [map.parent.offsetWidth, map.parent.offsetHeight]; | ||
map.parent.className += ' wax-fullscreen-map'; | ||
map.setSize( | ||
map.parent.offsetWidth, | ||
map.parent.offsetHeight); | ||
} else { | ||
map.parent.className = map.parent.className.replace('wax-fullscreen-map', ''); | ||
map.setSize( | ||
this.smallSize[0], | ||
this.smallSize[1]); | ||
} | ||
this.state = !this.state; | ||
}, this); | ||
return this._click; | ||
} | ||
}; | ||
return fullscreen.add(map); | ||
}; |
@@ -1,33 +0,10 @@ | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// Ripped from underscore.js | ||
// Internal function used to implement `_.throttle` and `_.debounce`. | ||
var limit = function(func, wait, debounce) { | ||
var timeout; | ||
return function() { | ||
var context = this, args = arguments; | ||
var throttler = function() { | ||
timeout = null; | ||
func.apply(context, args); | ||
}; | ||
if (debounce) clearTimeout(timeout); | ||
if (debounce || !timeout) timeout = setTimeout(throttler, wait); | ||
}; | ||
}; | ||
// Returns a function, that, when invoked, will only be triggered at most once | ||
// during a given window of time. | ||
var throttle = function(func, wait) { | ||
return limit(func, wait, false); | ||
}; | ||
var locationHash = { | ||
// A basic manager dealing only in hashchange and `location.hash`. | ||
// This **will interfere** with anchors, so a HTML5 pushState | ||
// implementation will be preferred. | ||
wax.mm.locationHash = { | ||
stateChange: function(callback) { | ||
window.addEventListener('hashchange', function() { | ||
com.modestmaps.addEvent(window, 'hashchange', function() { | ||
callback(location.hash); | ||
@@ -44,53 +21,90 @@ }, false); | ||
com.modestmaps.Map.prototype.hash = function(options) { | ||
var s0, // cached location.hash | ||
lat = 90 - 1e-8, // allowable latitude range | ||
map; | ||
// Hash | ||
// ---- | ||
wax.mm.hash = function(map, options) { | ||
// cached location.hash | ||
var s0, | ||
// allowable latitude range | ||
lat = 90 - 1e-8; | ||
var hash = { | ||
map: this, | ||
parser: function(s) { | ||
var args = s.split('/').map(Number); | ||
if (args.length < 3 || args.some(isNaN)) { | ||
return true; // replace bogus hash | ||
} else if (args.length == 3) { | ||
this.map.setCenterZoom(new com.modestmaps.Location(args[1], args[2]), args[0]); | ||
} | ||
}, | ||
formatter: function() { | ||
var center = this.map.getCenter(), | ||
zoom = this.map.getZoom(), | ||
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); | ||
return '#' + [zoom.toFixed(2), | ||
center.lat.toFixed(precision), | ||
center.lon.toFixed(precision)].join('/'); | ||
}, | ||
move: function() { | ||
var s1 = hash.formatter(); | ||
if (s0 !== s1) { | ||
s0 = s1; | ||
options.manager.pushState(s0); // don't recenter the map! | ||
} | ||
}, | ||
stateChange: function(state) { | ||
if (state === s0) return; // ignore spurious hashchange events | ||
if (hash.parser((s0 = state).substring(1))) { | ||
hash.move(); // replace bogus hash | ||
} | ||
}, | ||
// If a state isn't present when you initially load the map, the map should | ||
// still get a center and zoom level. | ||
initialize: function() { | ||
if (options.defaultCenter) this.map.setCenter(options.defaultCenter); | ||
if (options.defaultZoom) this.map.setZoom(options.defaultZoom); | ||
} | ||
}; | ||
// Ripped from underscore.js | ||
// Internal function used to implement `_.throttle` and `_.debounce`. | ||
var limit = function(func, wait, debounce) { | ||
var timeout; | ||
return function() { | ||
var context = this, args = arguments; | ||
var throttler = function() { | ||
timeout = null; | ||
func.apply(context, args); | ||
}; | ||
if (debounce) clearTimeout(timeout); | ||
if (debounce || !timeout) timeout = setTimeout(throttler, wait); | ||
}; | ||
}; | ||
options.manager.getState() ? | ||
hash.stateChange(options.manager.getState()) : | ||
hash.initialize() && hash.move(); | ||
this.addCallback('drawn', throttle(hash.move, 500)); | ||
options.manager.stateChange(hash.stateChange); | ||
// Returns a function, that, when invoked, will only be triggered at most once | ||
// during a given window of time. | ||
var throttle = function(func, wait) { | ||
return limit(func, wait, false); | ||
}; | ||
return this; | ||
var hash = { | ||
map: this, | ||
parser: function(s) { | ||
var args = s.split('/'); | ||
for (var i = 0; i < args.length; i++) { | ||
if (isNaN(args[i])) return true; | ||
args[i] = Number(args); | ||
} | ||
if (args.length < 3) { | ||
// replace bogus hash | ||
return true; | ||
} else if (args.length == 3) { | ||
map.setCenterZoom(new com.modestmaps.Location(args[1], args[2]), args[0]); | ||
} | ||
}, | ||
add: function(map) { | ||
if (options.manager.getState()) { | ||
hash.stateChange(options.manager.getState()); | ||
} else { | ||
hash.initialize(); | ||
hash.move(); | ||
} | ||
map.addCallback('drawn', throttle(hash.move, 500)); | ||
options.manager.stateChange(hash.stateChange); | ||
}, | ||
// Currently misnamed. Get the hash string that will go in the URL, | ||
// pulling from the map object | ||
formatter: function() { | ||
var center = map.getCenter(), | ||
zoom = map.getZoom(), | ||
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); | ||
return '#' + [zoom.toFixed(2), | ||
center.lat.toFixed(precision), | ||
center.lon.toFixed(precision)].join('/'); | ||
}, | ||
move: function() { | ||
var s1 = hash.formatter(); | ||
if (s0 !== s1) { | ||
s0 = s1; | ||
// don't recenter the map! | ||
options.manager.pushState(s0); | ||
} | ||
}, | ||
stateChange: function(state) { | ||
// ignore spurious hashchange events | ||
if (state === s0) return; | ||
if (hash.parser((s0 = state).substring(1))) { | ||
// replace bogus hash | ||
hash.move(); | ||
} | ||
}, | ||
// If a state isn't present when you initially load the map, the map should | ||
// still get a center and zoom level. | ||
initialize: function() { | ||
if (options.defaultCenter) map.setCenter(options.defaultCenter); | ||
if (options.defaultZoom) map.setZoom(options.defaultZoom); | ||
} | ||
}; | ||
return hash.add(map); | ||
}; |
@@ -1,10 +0,3 @@ | ||
// Requires jQuery | ||
// | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
@@ -20,165 +13,200 @@ // A chaining-style control that adds | ||
// **full**. | ||
com.modestmaps.Map.prototype.interaction = function(options) { | ||
// * `clickHandler` (optional): if not given, `clickAction: 'location'` will | ||
// assign a location to your window with `window.location = 'location'`. | ||
// To make location-getting work with other systems, like those based on | ||
// pushState or Backbone, you can provide a custom function of the form | ||
// | ||
// | ||
// `clickHandler: function(url) { ... go to url ... }` | ||
wax.mm.interaction = function(map, options) { | ||
var MM = com.modestmaps; | ||
options = options || {}; | ||
// Our GridManager (from `gridutil.js`). This will keep the | ||
// cache of grid information and provide friendly utility methods | ||
// that return `GridTile` objects instead of raw data. | ||
this.waxGM = new wax.GridManager(); | ||
// This requires wax.Tooltip or similar | ||
this.callbacks = options.callbacks || { | ||
out: wax.tooltip.unselect, | ||
over: wax.tooltip.select, | ||
click: wax.tooltip.click | ||
}; | ||
var interaction = { | ||
modifyingEvents: ['zoomed', 'panned', 'centered', | ||
'extentset', 'resized', 'drawn'], | ||
this.clickAction = options.clickAction || 'full'; | ||
// Our GridManager (from `gridutil.js`). This will keep the | ||
// cache of grid information and provide friendly utility methods | ||
// that return `GridTile` objects instead of raw data. | ||
waxGM: new wax.GridManager(), | ||
// Search through `.tiles` and determine the position, | ||
// from the top-left of the **document**, and cache that data | ||
// so that `mousemove` events don't always recalculate. | ||
this.waxGetTileGrid = function() { | ||
// TODO: don't build for tiles outside of viewport | ||
var zoom = this.getZoom(); | ||
// Calculate a tile grid and cache it, by using the `.tiles` | ||
// element on this map. | ||
return this._waxGetTileGrid || (this._waxGetTileGrid = | ||
(function(t) { | ||
var o = []; | ||
$.each(t, function(key, img) { | ||
if (key.split(',')[0] == zoom) { | ||
var $img = $(img); | ||
var offset = $img.offset(); | ||
o.push([offset.top, offset.left, $img]); | ||
} | ||
}); | ||
return o; | ||
})(this.tiles)); | ||
}; | ||
// This requires wax.Tooltip or similar | ||
callbacks: options.callbacks || new wax.tooltip(), | ||
this.waxClearTimeout = function() { | ||
if (this.clickTimeout) { | ||
window.clearTimeout(this.clickTimeout); | ||
this.clickTimeout = null; | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; | ||
clickAction: options.clickAction || 'full', | ||
// Click handler | ||
// ------------- | ||
// | ||
// The extra logic here is all to avoid the inconsistencies | ||
// of browsers in handling double and single clicks on the same | ||
// element. After dealing with particulars, delegates to waxHandleClick | ||
$(this.parent).mousedown($.proxy(function(evt) { | ||
// Ignore double-clicks by ignoring clicks within 300ms of | ||
// each other. | ||
if (this.waxClearTimeout()) { | ||
return; | ||
} | ||
// Store this event so that we can compare it to the | ||
// up event | ||
var tol = 4; // tolerance | ||
this.downEvent = evt; | ||
$(this.parent).one('mouseup', $.proxy(function(evt) { | ||
// Don't register clicks that are likely the boundaries | ||
// of dragging the map | ||
if (Math.round(evt.pageY / tol) === Math.round(this.downEvent.pageY / tol) && | ||
Math.round(evt.pageX / tol) === Math.round(this.downEvent.pageX / tol)) { | ||
this.clickTimeout = window.setTimeout( | ||
$.proxy(function() { | ||
this.waxHandleClick(evt); | ||
}, this), | ||
300 | ||
clickHandler: options.clickHandler || function(url) { | ||
window.location = url; | ||
}, | ||
// Attach listeners to the map | ||
add: function() { | ||
for (var i = 0; i < this.modifyingEvents.length; i++) { | ||
map.addCallback( | ||
this.modifyingEvents[i], | ||
wax.util.bind(this.clearTileGrid, this) | ||
); | ||
} | ||
}, this)); | ||
}, this)); | ||
MM.addEvent(map.parent, 'mousemove', this.onMove()); | ||
MM.addEvent(map.parent, 'mousedown', this.onDown()); | ||
MM.addEvent(map.parent, 'touchstart', this.onDown()); | ||
return this; | ||
}, | ||
this.waxHandleClick = function(evt) { | ||
var $tile = this.waxGetTile(evt); | ||
if ($tile) { | ||
this.waxGM.getGrid($tile.attr('src'), $.proxy(function(g) { | ||
if (g) { | ||
var feature = g.getFeature(evt.pageX, evt.pageY, $tile, { | ||
format: this.clickAction | ||
}); | ||
if (feature) { | ||
switch (this.clickAction) { | ||
case 'full': | ||
this.callbacks.click(feature, this.parent, 0, evt); | ||
break; | ||
case 'location': | ||
window.location = feature; | ||
break; | ||
// Search through `.tiles` and determine the position, | ||
// from the top-left of the **document**, and cache that data | ||
// so that `mousemove` events don't always recalculate. | ||
getTileGrid: function() { | ||
// TODO: don't build for tiles outside of viewport | ||
var zoom = map.getZoom(); | ||
// Calculate a tile grid and cache it, by using the `.tiles` | ||
// element on this map. | ||
return this._getTileGrid || (this._getTileGrid = | ||
(function(t) { | ||
var o = []; | ||
for (var key in t) { | ||
if (key.split(',')[0] == zoom) { | ||
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. | ||
clearTileGrid: function(map, e) { | ||
this._getTileGrid = null; | ||
}, | ||
getTile: function(evt) { | ||
var tile; | ||
var grid = this.getTileGrid(); | ||
for (var i = 0; i < grid.length; i++) { | ||
if ((grid[i][0] < evt.y) && | ||
((grid[i][0] + 256) > evt.y) && | ||
(grid[i][1] < evt.x) && | ||
((grid[i][1] + 256) > evt.x)) { | ||
tile = grid[i][2]; | ||
break; | ||
} | ||
}, this)); | ||
} | ||
}; | ||
} | ||
return tile || false; | ||
}, | ||
this.waxGetTile = function(evt) { | ||
var $tile; | ||
var grid = this.waxGetTileGrid(); | ||
for (var i = 0; i < grid.length; i++) { | ||
if ((grid[i][0] < evt.pageY) && | ||
((grid[i][0] + 256) > evt.pageY) && | ||
(grid[i][1] < evt.pageX) && | ||
((grid[i][1] + 256) > evt.pageX)) { | ||
$tile = grid[i][2]; | ||
break; | ||
// Clear the double-click timeout to prevent double-clicks from | ||
// triggering popups. | ||
clearTimeout: function() { | ||
if (this.clickTimeout) { | ||
window.clearTimeout(this.clickTimeout); | ||
this.clickTimeout = null; | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
return $tile || false; | ||
}; | ||
}, | ||
// On `mousemove` events that **don't** have the mouse button | ||
// down - so that the map isn't being dragged. | ||
$(this.parent).nondrag($.proxy(function(evt) { | ||
var $tile = this.waxGetTile(evt); | ||
if ($tile) { | ||
this.waxGM.getGrid($tile.attr('src'), $.proxy(function(g) { | ||
if (g) { | ||
var feature = g.getFeature(evt.pageX, evt.pageY, $tile, { | ||
format: 'teaser' | ||
}); | ||
// This and other Modest Maps controls only support a single layer. | ||
// Thus a layer index of **0** is given to the tooltip library | ||
if (feature) { | ||
if (feature && this.feature !== feature) { | ||
this.feature = feature; | ||
this.callbacks.out(feature, this.parent, 0, evt); | ||
this.callbacks.over(feature, this.parent, 0, evt); | ||
} else if (!feature) { | ||
this.feature = null; | ||
this.callbacks.out(feature, this.parent, 0, evt); | ||
onMove: function(evt) { | ||
if (!this._onMove) this._onMove = wax.util.bind(function(evt) { | ||
var pos = wax.util.eventoffset(evt); | ||
var tile = this.getTile(pos); | ||
if (tile) { | ||
this.waxGM.getGrid(tile.src, wax.util.bind(function(err, g) { | ||
if (err) return; | ||
if (g) { | ||
var feature = g.getFeature(pos.x, pos.y, tile, { | ||
format: 'teaser' | ||
}); | ||
// This and other Modest Maps controls only support a single layer. | ||
// Thus a layer index of **0** is given to the tooltip library | ||
if (feature) { | ||
if (feature && this.feature !== feature) { | ||
this.feature = feature; | ||
this.callbacks.out(map.parent); | ||
this.callbacks.over(feature, map.parent, 0, evt); | ||
} else if (!feature) { | ||
this.feature = null; | ||
this.callbacks.out(map.parent); | ||
} | ||
} else { | ||
this.feature = null; | ||
this.callbacks.out(map.parent); | ||
} | ||
} | ||
} else { | ||
this.feature = null; | ||
this.callbacks.out({}, this.parent, 0, evt); | ||
} | ||
}, this)); | ||
} | ||
}, this)); | ||
} | ||
}, this); | ||
return this._onMove; | ||
}, | ||
}, this)); | ||
// A handler for 'down' events - which means `mousedown` and `touchstart` | ||
onDown: function(evt) { | ||
if (!this._onDown) this._onDown = wax.util.bind(function(evt) { | ||
// Ignore double-clicks by ignoring clicks within 300ms of | ||
// each other. | ||
if (this.clearTimeout()) { | ||
return; | ||
} | ||
// Store this event so that we can compare it to the | ||
// up event | ||
if (evt.type === 'mousedown') { | ||
this.downEvent = wax.util.eventoffset(evt); | ||
MM.addEvent(map.parent, 'mouseup', this.onUp()); | ||
} else if (evt.type === 'touchstart' && evt.touches.length === 1) { | ||
console.log('good touch'); | ||
MM.addEvent(map.parent, 'touchend', this.onUp()); | ||
} | ||
}, this); | ||
return this._onDown; | ||
}, | ||
// When the map is moved, the calculated tile grid is no longer | ||
// accurate, so it must be reset. | ||
var modifying_events = ['zoomed', 'panned', 'centered', | ||
'extentset', 'resized', 'drawn']; | ||
onUp: function() { | ||
if (!this._onUp) this._onUp = wax.util.bind(function(evt) { | ||
MM.removeEvent(map.parent, 'mouseup', this.onUp()); | ||
// Don't register clicks that are likely the boundaries | ||
// of dragging the map | ||
// The tolerance between the place where the mouse goes down | ||
// and where where it comes up is set at 4px. | ||
var tol = 4; | ||
var pos = wax.util.eventoffset(evt); | ||
if (Math.round(pos.y / tol) === Math.round(this.downEvent.y / tol) && | ||
Math.round(pos.x / tol) === Math.round(this.downEvent.x / tol)) { | ||
// Contain the event data in a closure. | ||
this.clickTimeout = window.setTimeout( | ||
wax.util.bind(function() { this.click()(pos); }, this), 300); | ||
} | ||
}, this); | ||
return this._onUp; | ||
}, | ||
var clearMap = function(map, e) { | ||
map._waxGetTileGrid = null; | ||
click: function(evt) { | ||
if (!this._onClick) this._onClick = wax.util.bind(function(pos) { | ||
var tile = this.getTile(pos); | ||
if (tile) { | ||
this.waxGM.getGrid(tile.src, wax.util.bind(function(err, g) { | ||
if (g) { | ||
var feature = g.getFeature(pos.x, pos.y, tile, { | ||
format: this.clickAction | ||
}); | ||
if (feature) { | ||
switch (this.clickAction) { | ||
case 'full': | ||
this.callbacks.click(feature, map.parent, 0, evt); | ||
break; | ||
case 'location': | ||
this.clickHandler(feature); | ||
break; | ||
} | ||
} | ||
} | ||
}, this)); | ||
} | ||
}, this); | ||
return this._onClick; | ||
} | ||
}; | ||
for (var i = 0; i < modifying_events.length; i++) { | ||
this.addCallback(modifying_events[i], clearMap); | ||
} | ||
// Ensure chainability | ||
return this; | ||
return interaction.add(map); | ||
}; |
@@ -1,29 +0,23 @@ | ||
// Wax: Legend Control | ||
// ------------------- | ||
// Requires: | ||
// | ||
// * modestmaps | ||
// * wax.Legend | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
// Legend Control | ||
// -------------- | ||
// The Modest Maps version of this control is a very, very | ||
// light wrapper around the `/lib` code for legends. | ||
com.modestmaps.Map.prototype.legend = function(options) { | ||
wax.mm.legend = function(map, options) { | ||
options = options || {}; | ||
this.legend = new wax.Legend(this.parent, options.container); | ||
this.legend.render([ | ||
this.provider.getTileUrl({ | ||
zoom: 0, | ||
column: 0, | ||
row: 0 | ||
}) | ||
]); | ||
return this; | ||
var legend = { | ||
add: function() { | ||
this.legend = new wax.Legend(map.parent, options.container); | ||
this.legend.render([ | ||
map.provider.getTileUrl({ | ||
zoom: 0, | ||
column: 0, | ||
row: 0 | ||
}) | ||
]); | ||
} | ||
}; | ||
return legend.add(map); | ||
}; |
@@ -1,17 +0,19 @@ | ||
// Wax: Point Selector | ||
// ----------------- | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
com.modestmaps.Map.prototype.pointselector = function(opts) { | ||
// Point Selector | ||
// -------------- | ||
// | ||
// This takes an object of options: | ||
// | ||
// * `callback`: a function called with an array of `com.modestmaps.Location` | ||
// objects when the map is edited | ||
// | ||
// It also exposes a public API function: `addLocation`, which adds a point | ||
// to the map as if added by the user. | ||
wax.mm.pointselector = function(map, opts) { | ||
var mouseDownPoint = null, | ||
mouseUpPoint = null, | ||
map = this, | ||
tolerance = 5, | ||
overlayDiv, | ||
MM = com.modestmaps, | ||
@@ -24,16 +26,18 @@ locations = []; | ||
var overlayDiv = document.createElement('div'); | ||
overlayDiv.id = this.parent.id + '-boxselector'; | ||
overlayDiv.className = 'pointselector-box-container'; | ||
overlayDiv.style.width = this.dimensions.x + 'px'; | ||
overlayDiv.style.height = this.dimensions.y + 'px'; | ||
this.parent.appendChild(overlayDiv); | ||
// Create a `com.modestmaps.Point` from a screen event, like a click. | ||
var makePoint = function(e) { | ||
var point = new MM.Point(e.clientX, e.clientY); | ||
var coords = wax.util.eventoffset(e); | ||
var point = new MM.Point(coords.x, coords.y); | ||
// correct for scrolled document | ||
point.x += document.body.scrollLeft + document.documentElement.scrollLeft; | ||
point.y += document.body.scrollTop + document.documentElement.scrollTop; | ||
// and for the document | ||
var body = { | ||
x: parseFloat(MM.getStyle(document.documentElement, 'margin-left')), | ||
y: parseFloat(MM.getStyle(document.documentElement, 'margin-top')) | ||
}; | ||
if (!isNaN(body.x)) point.x -= body.x; | ||
if (!isNaN(body.y)) point.y -= body.y; | ||
// TODO: use wax.util.offset | ||
// correct for nested offsets in DOM | ||
@@ -47,54 +51,91 @@ for (var node = map.parent; node; node = node.offsetParent) { | ||
// Currently locations in this control contain circular references to elements. | ||
// These can't be JSON encoded, so here's a utility to clean the data that's | ||
// spit back. | ||
function cleanLocations(locations) { | ||
var o = []; | ||
for (var i = 0; i < locations.length; i++) { | ||
o.push(new MM.Location(locations[i].lat, locations[i].lon)); | ||
} | ||
return o; | ||
} | ||
var pointselector = { | ||
// Attach this control to a map by registering callbacks | ||
// and adding the overlay | ||
add: function(map) { | ||
MM.addEvent(map.parent, 'mousedown', this.mouseDown()); | ||
map.addCallback('drawn', pointselector.drawPoints()); | ||
return this; | ||
}, | ||
deletePoint: function(location, e) { | ||
if (confirm('Delete this point?')) { | ||
// TODO: indexOf not supported in IE | ||
location.pointDiv.parentNode.removeChild(location.pointDiv); | ||
locations.splice(locations.indexOf(location), 1); | ||
callback(locations); | ||
locations.splice(wax.util.indexOf(locations, location), 1); | ||
callback(cleanLocations(locations)); | ||
} | ||
}, | ||
// Redraw the points when the map is moved, so that they stay in the | ||
// correct geographic locations. | ||
drawPoints: function() { | ||
var offset = new MM.Point(0, 0); | ||
for (var i = 0; i < locations.length; i++) { | ||
var point = map.locationPoint(locations[i]); | ||
if (!locations[i].pointDiv) { | ||
locations[i].pointDiv = document.createElement('div'); | ||
locations[i].pointDiv.className = 'wax-point-div'; | ||
locations[i].pointDiv.style.position = 'absolute'; | ||
locations[i].pointDiv.style.display = 'block'; | ||
locations[i].pointDiv.location = locations[i]; | ||
// Create this closure once per point | ||
MM.addEvent(locations[i].pointDiv, 'mouseup', (function selectPointWrap(e) { | ||
var l = locations[i]; | ||
return function(e) { | ||
MM.cancelEvent(e); | ||
pointselector.deletePoint(l, e); | ||
}; | ||
})()); | ||
overlayDiv.appendChild(locations[i].pointDiv); | ||
if (!this._drawPoints) this._drawPoints = wax.util.bind(function() { | ||
var offset = new MM.Point(0, 0); | ||
for (var i = 0; i < locations.length; i++) { | ||
var point = map.locationPoint(locations[i]); | ||
if (!locations[i].pointDiv) { | ||
locations[i].pointDiv = document.createElement('div'); | ||
locations[i].pointDiv.className = 'wax-point-div'; | ||
locations[i].pointDiv.style.position = 'absolute'; | ||
locations[i].pointDiv.style.display = 'block'; | ||
// TODO: avoid circular reference | ||
locations[i].pointDiv.location = locations[i]; | ||
// Create this closure once per point | ||
MM.addEvent(locations[i].pointDiv, 'mouseup', | ||
(function selectPointWrap(e) { | ||
var l = locations[i]; | ||
return function(e) { | ||
MM.removeEvent(map.parent, 'mouseup', pointselector.mouseUp()); | ||
pointselector.deletePoint(l, e); | ||
}; | ||
})()); | ||
map.parent.appendChild(locations[i].pointDiv); | ||
} | ||
locations[i].pointDiv.style.left = point.x + 'px'; | ||
locations[i].pointDiv.style.top = point.y + 'px'; | ||
} | ||
locations[i].pointDiv.style.left = point.x + 'px'; | ||
locations[i].pointDiv.style.top = point.y + 'px'; | ||
} | ||
}, this); | ||
return this._drawPoints; | ||
}, | ||
mouseDown: function(e) { | ||
mouseDownPoint = makePoint(e); | ||
MM.addEvent(map.parent, 'mouseup', pointselector.mouseUp); | ||
mouseDown: function() { | ||
if (!this._mouseDown) this._mouseDown = wax.util.bind(function(e) { | ||
mouseDownPoint = makePoint(e); | ||
MM.addEvent(map.parent, 'mouseup', this.mouseUp()); | ||
}, this); | ||
return this._mouseDown; | ||
}, | ||
mouseUp: function(e) { | ||
if (!mouseDownPoint) return; | ||
mouseUpPoint = makePoint(e); | ||
if (MM.Point.distance(mouseDownPoint, mouseUpPoint) < tolerance) { | ||
locations.push(map.pointLocation(mouseDownPoint)); | ||
callback(locations); | ||
pointselector.drawPoints(); | ||
} | ||
mouseDownPoint = null; | ||
MM.removeEvent(map.parent, 'mouseup', pointselector.mouseUp); | ||
// API for programmatically adding points to the map - this | ||
// calls the callback for ever point added, so it can be symmetrical. | ||
// Useful for initializing the map when it's a part of a form. | ||
addLocation: function(location) { | ||
locations.push(location); | ||
pointselector.drawPoints()(); | ||
callback(cleanLocations(locations)); | ||
}, | ||
// Remove the awful circular reference from locations. | ||
// TODO: This function should be made unnecessary by not having it. | ||
mouseUp: function() { | ||
if (!this._mouseUp) this._mouseUp = wax.util.bind(function(e) { | ||
if (!mouseDownPoint) return; | ||
mouseUpPoint = makePoint(e); | ||
if (MM.Point.distance(mouseDownPoint, mouseUpPoint) < tolerance) { | ||
this.addLocation(map.pointLocation(mouseDownPoint)); | ||
callback(cleanLocations(locations)); | ||
} | ||
mouseDownPoint = null; | ||
}, this); | ||
return this._mouseUp; | ||
} | ||
}; | ||
MM.addEvent(overlayDiv, 'mousedown', pointselector.mouseDown); | ||
map.addCallback('drawn', pointselector.drawPoints); | ||
return this; | ||
return pointselector.add(map); | ||
}; |
@@ -1,100 +0,94 @@ | ||
// Wax: ZoomBox | ||
// ----------------- | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// ZoomBox | ||
// ------- | ||
// An OL-style ZoomBox control, from the Modest Maps example. | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
com.modestmaps.Map.prototype.zoombox = function(opts) { | ||
var boxDiv = document.createElement('div'); | ||
boxDiv.id = this.parent.id + '-zoombox'; | ||
boxDiv.className = 'zoombox-box-container'; | ||
boxDiv.style.width = this.dimensions.x + 'px'; | ||
boxDiv.style.height = this.dimensions.y + 'px'; | ||
this.parent.appendChild(boxDiv); | ||
var box = document.createElement('div'); | ||
box.id = this.parent.id + '-zoombox-box'; | ||
box.className = 'zoombox-box'; | ||
boxDiv.appendChild(box); | ||
wax.mm.zoombox = function(map, opts) { | ||
// TODO: respond to resize | ||
var mouseDownPoint = null; | ||
var map = this; | ||
var zoombox = this.zoombox; | ||
var zoombox = { | ||
add: function(map) { | ||
this.box = document.createElement('div'); | ||
this.box.id = map.parent.id + '-zoombox-box'; | ||
this.box.className = 'zoombox-box'; | ||
map.parent.appendChild(this.box); | ||
com.modestmaps.addEvent(map.parent, 'mousedown', this.mouseDown()); | ||
}, | ||
remove: function() { | ||
map.parent.removeChild(this.box); | ||
map.removeCallback('mousedown', this.mouseDown); | ||
}, | ||
getMousePoint: function(e) { | ||
// start with just the mouse (x, y) | ||
var point = new com.modestmaps.Point(e.clientX, e.clientY); | ||
// correct for scrolled document | ||
point.x += document.body.scrollLeft + document.documentElement.scrollLeft; | ||
point.y += document.body.scrollTop + document.documentElement.scrollTop; | ||
this.zoombox.remove = function() { | ||
boxDiv.parentNode.removeChild(boxDiv); | ||
map.removeCallback('mousedown', zoombox.mouseDown); | ||
}; | ||
this.zoombox.getMousePoint = function(e) { | ||
// start with just the mouse (x, y) | ||
var point = new com.modestmaps.Point(e.clientX, e.clientY); | ||
// correct for scrolled document | ||
point.x += document.body.scrollLeft + document.documentElement.scrollLeft; | ||
point.y += document.body.scrollTop + document.documentElement.scrollTop; | ||
// correct for nested offsets in DOM | ||
for (var node = map.parent; node; node = node.offsetParent) { | ||
point.x -= node.offsetLeft; | ||
point.y -= node.offsetTop; | ||
} | ||
return point; | ||
}, | ||
mouseDown: function() { | ||
if (!this._mouseDown) this._mouseDown = wax.util.bind(function(e) { | ||
if (e.shiftKey) { | ||
mouseDownPoint = this.getMousePoint(e); | ||
// correct for nested offsets in DOM | ||
for (var node = map.parent; node; node = node.offsetParent) { | ||
point.x -= node.offsetLeft; | ||
point.y -= node.offsetTop; | ||
} | ||
return point; | ||
}; | ||
this.zoombox.mouseDown = function(e) { | ||
if (e.shiftKey) { | ||
mouseDownPoint = zoombox.getMousePoint(e); | ||
this.box.style.left = mouseDownPoint.x + 'px'; | ||
this.box.style.top = mouseDownPoint.y + 'px'; | ||
box.style.left = mouseDownPoint.x + 'px'; | ||
box.style.top = mouseDownPoint.y + 'px'; | ||
com.modestmaps.addEvent(map.parent, 'mousemove', this.mouseMove()); | ||
com.modestmaps.addEvent(map.parent, 'mouseup', this.mouseUp()); | ||
com.modestmaps.addEvent(map.parent, 'mousemove', zoombox.mouseMove); | ||
com.modestmaps.addEvent(map.parent, 'mouseup', zoombox.mouseUp); | ||
map.parent.style.cursor = 'crosshair'; | ||
return com.modestmaps.cancelEvent(e); | ||
} | ||
}, this); | ||
return this._mouseDown; | ||
}, | ||
mouseMove: function(e) { | ||
if (!this._mouseMove) this._mouseMove = wax.util.bind(function(e) { | ||
var point = this.getMousePoint(e); | ||
this.box.style.display = 'block'; | ||
if (point.x < mouseDownPoint.x) { | ||
this.box.style.left = point.x + 'px'; | ||
} else { | ||
this.box.style.left = mouseDownPoint.x + 'px'; | ||
} | ||
this.box.style.width = Math.abs(point.x - mouseDownPoint.x) + 'px'; | ||
if (point.y < mouseDownPoint.y) { | ||
this.box.style.top = point.y + 'px'; | ||
} else { | ||
this.box.style.top = mouseDownPoint.y + 'px'; | ||
} | ||
this.box.style.height = Math.abs(point.y - mouseDownPoint.y) + 'px'; | ||
return com.modestmaps.cancelEvent(e); | ||
}, this); | ||
return this._mouseMove; | ||
}, | ||
mouseUp: function(e) { | ||
if (!this._mouseUp) this._mouseUp = wax.util.bind(function(e) { | ||
var point = this.getMousePoint(e); | ||
map.parent.style.cursor = 'crosshair'; | ||
return com.modestmaps.cancelEvent(e); | ||
} | ||
}; | ||
this.zoombox.mouseMove = function(e) { | ||
var point = zoombox.getMousePoint(e); | ||
box.style.display = 'block'; | ||
if (point.x < mouseDownPoint.x) { | ||
box.style.left = point.x + 'px'; | ||
} else { | ||
box.style.left = mouseDownPoint.x + 'px'; | ||
} | ||
box.style.width = Math.abs(point.x - mouseDownPoint.x) + 'px'; | ||
if (point.y < mouseDownPoint.y) { | ||
box.style.top = point.y + 'px'; | ||
} else { | ||
box.style.top = mouseDownPoint.y + 'px'; | ||
} | ||
box.style.height = Math.abs(point.y - mouseDownPoint.y) + 'px'; | ||
return com.modestmaps.cancelEvent(e); | ||
}; | ||
this.zoombox.mouseUp = function(e) { | ||
var point = zoombox.getMousePoint(e); | ||
var l1 = map.pointLocation(point), | ||
l2 = map.pointLocation(mouseDownPoint); | ||
var l1 = map.pointLocation(point), | ||
l2 = map.pointLocation(mouseDownPoint); | ||
map.setExtent([l1, l2]); | ||
map.setExtent([l1, l2]); | ||
this.box.style.display = 'none'; | ||
com.modestmaps.removeEvent(map.parent, 'mousemove', this.mouseMove()); | ||
com.modestmaps.removeEvent(map.parent, 'mouseup', this.mouseUp()); | ||
box.style.display = 'none'; | ||
com.modestmaps.removeEvent(map.parent, 'mousemove', zoombox.mouseMove); | ||
com.modestmaps.removeEvent(map.parent, 'mouseup', zoombox.mouseUp); | ||
map.parent.style.cursor = 'auto'; | ||
return com.modestmaps.cancelEvent(e); | ||
map.parent.style.cursor = 'auto'; | ||
}, this); | ||
return this._mouseUp; | ||
} | ||
}; | ||
com.modestmaps.addEvent(boxDiv, 'mousedown', zoombox.mouseDown); | ||
return this; | ||
return zoombox.add(map); | ||
}; |
@@ -1,39 +0,52 @@ | ||
// Wax: Zoom Control | ||
// ----------------- | ||
wax = wax || {}; | ||
wax.mm = wax.mm || {}; | ||
// namespacing! | ||
if (!com) { | ||
var com = { }; | ||
if (!com.modestmaps) { | ||
com.modestmaps = { }; | ||
} | ||
} | ||
// Zoomer | ||
// ------ | ||
// Add zoom links, which can be styled as buttons, to a `modestmaps.Map` | ||
// control. This function can be used chaining-style with other | ||
// chaining-style controls. | ||
com.modestmaps.Map.prototype.zoomer = function() { | ||
var zoomin = $('<a class="zoomer zoomin" href="#zoomin">+</a>') | ||
.click($.proxy(function(e) { | ||
e.preventDefault(); | ||
this.zoomIn(); | ||
}, this)) | ||
.appendTo(this.parent); | ||
var zoomout = $('<a class="zoomer zoomout" href="#zoomout">-</a>') | ||
.click($.proxy(function(e) { | ||
e.preventDefault(); | ||
this.zoomOut(); | ||
}, this)) | ||
.appendTo(this.parent); | ||
this.addCallback('drawn', function(map, e) { | ||
if (map.coordinate.zoom === map.provider.outerLimits()[0].zoom) { | ||
zoomout.addClass('zoomdisabled'); | ||
} else if (map.coordinate.zoom === map.provider.outerLimits()[1].zoom) { | ||
zoomin.addClass('zoomdisabled'); | ||
} else { | ||
zoomin.removeClass('zoomdisabled'); | ||
zoomout.removeClass('zoomdisabled'); | ||
wax.mm.zoomer = function(map) { | ||
var zoomin = document.createElement('a'); | ||
zoomin.innerHTML = '+'; | ||
zoomin.href = '#'; | ||
zoomin.className = 'zoomer zoomin'; | ||
com.modestmaps.addEvent(zoomin, 'mousedown', function(e) { | ||
com.modestmaps.cancelEvent(e); | ||
}); | ||
com.modestmaps.addEvent(zoomin, 'click', function(e) { | ||
com.modestmaps.cancelEvent(e); | ||
map.zoomIn(); | ||
}, false); | ||
map.parent.appendChild(zoomin); | ||
var zoomout = document.createElement('a'); | ||
zoomout.innerHTML = '-'; | ||
zoomout.href = '#'; | ||
zoomout.className = 'zoomer zoomout'; | ||
com.modestmaps.addEvent(zoomout, 'mousedown', function(e) { | ||
com.modestmaps.cancelEvent(e); | ||
}); | ||
com.modestmaps.addEvent(zoomout, 'click', function(e) { | ||
com.modestmaps.cancelEvent(e); | ||
map.zoomOut(); | ||
}, false); | ||
map.parent.appendChild(zoomout); | ||
var zoomer = { | ||
add: function(map) { | ||
map.addCallback('drawn', function(map, e) { | ||
if (map.coordinate.zoom === map.provider.outerLimits()[0].zoom) { | ||
zoomout.className = 'zoomer zoomout zoomdisabled'; | ||
} else if (map.coordinate.zoom === map.provider.outerLimits()[1].zoom) { | ||
zoomin.className = 'zoomer zoomin zoomdisabled'; | ||
} else { | ||
zoomin.className = 'zoomer zoomin'; | ||
zoomout.className = 'zoomer zoomout'; | ||
} | ||
}); | ||
return this; | ||
} | ||
}); | ||
return this; | ||
}; | ||
return zoomer.add(map); | ||
}; |
@@ -5,2 +5,10 @@ // Wax header | ||
var addEv = function(element, name, observer) { | ||
if (element.addEventListener) { | ||
element.addEventListener(name, observer, false); | ||
} else if (element.attachEvent) { | ||
element.attachEvent('on' + name, observer); | ||
} | ||
}; | ||
// An interaction toolkit for tiles that implement the | ||
@@ -21,12 +29,8 @@ // [MBTiles UTFGrid spec](https://github.com/mapbox/mbtiles-spec) | ||
this.callbacks = { | ||
out: wax.tooltip.unselect, | ||
over: wax.tooltip.select, | ||
click: wax.tooltip.click | ||
}; | ||
this.callbacks = this.options.callbacks || new wax.tooltip(); | ||
}, | ||
setMap: function(map) { | ||
$(map.viewPortDiv).bind('mousemove', $.proxy(this.getInfoForHover, this)); | ||
$(map.viewPortDiv).bind('mouseout', $.proxy(this.resetLayers, this)); | ||
addEv(map.viewPortDiv, 'mousemove', wax.util.bind(this.getInfoForHover, this)); | ||
addEv(map.viewPortDiv, 'mouseout', wax.util.bind(this.resetLayers, this)); | ||
this.clickHandler = new OpenLayers.Handler.Click( | ||
@@ -63,3 +67,8 @@ this, { | ||
for (var y = 0; y < layers[j].grid[x].length; y++) { | ||
var divpos = $(layers[j].grid[x][y].imgDiv).offset(); | ||
// Ah, the OpenLayers junkpile. Change everything in | ||
// 0.x.0 releases? Sure! | ||
if (layers[j].grid[x][y].imgDiv) { | ||
layers[j].grid[x][y].frame == layers[j].grid[x][y].imgDiv; | ||
} | ||
var divpos = wax.util.offset(layers[j].grid[x][y].frame); | ||
if (divpos && | ||
@@ -71,3 +80,3 @@ ((divpos.top < sevt.pageY) && | ||
tiles.push(layers[j].grid[x][y]); | ||
continue layerfound; | ||
continue layerfound; | ||
} | ||
@@ -83,10 +92,12 @@ } | ||
if (this._viableLayers) return this._viableLayers; | ||
return this._viableLayers = $(this.map.layers).filter( | ||
function(i) { | ||
// TODO: make better indication of whether | ||
// this is an interactive layer | ||
return (this.map.layers[i].visibility === true) && | ||
(this.map.layers[i].CLASS_NAME === 'OpenLayers.Layer.TMS'); | ||
this._viableLayers = []; | ||
for (var i in this.map.layers) { | ||
// TODO: make better indication of whether | ||
// this is an interactive layer | ||
if ((this.map.layers[i].visibility === true) && | ||
(this.map.layers[i].CLASS_NAME === 'OpenLayers.Layer.TMS')) { | ||
this._viableLayers.push(this.map.layers[i]); | ||
} | ||
); | ||
} | ||
return this._viableLayers; | ||
}, | ||
@@ -96,3 +107,3 @@ | ||
this._viableLayers = null; | ||
this.callbacks['out'](); | ||
this.callbacks.out(this.map.viewPortDiv); | ||
}, | ||
@@ -110,5 +121,5 @@ | ||
for (var t = 0; t < tiles.length; t++) { | ||
this.gm.getGrid(tiles[t].url, function(g) { | ||
this.gm.getGrid(tiles[t].url, function(err, g) { | ||
if (!g) return; | ||
var feature = g.getFeature(evt.pageX, evt.pageY, tiles[t].imgDiv, { | ||
var feature = g.getFeature(evt.pageX, evt.pageY, tiles[t].frame, { | ||
format: that.clickAction | ||
@@ -144,5 +155,6 @@ }); | ||
// is currently being requested. | ||
this.gm.getGrid(tiles[t].url, function(g) { | ||
this.gm.getGrid(tiles[t].url, function(err, g) { | ||
if (g && tiles[t]) { | ||
var feature = g.getFeature(evt.pageX, evt.pageY, tiles[t].imgDiv, options); | ||
var feature = g.getFeature(evt.pageX, evt.pageY, tiles[t].frame, options); | ||
if (feature) { | ||
@@ -152,7 +164,7 @@ if (!tiles[t]) return; | ||
that.feature[t] = feature; | ||
that.callbacks.out(feature, tiles[t].layer.map.div, t, evt); | ||
that.callbacks.out(tiles[t].layer.map.div); | ||
that.callbacks.over(feature, tiles[t].layer.map.div, t, evt); | ||
} else if (!feature) { | ||
that.feature[t] = null; | ||
that.callbacks.out(feature, tiles[t].layer.map.div, t, evt); | ||
that.callbacks.out(tiles[t].layer.map.div); | ||
} | ||
@@ -163,7 +175,3 @@ } else { | ||
that.feature[t] = null; | ||
if (tiles[t]) { | ||
that.callbacks.out({}, tiles[t].layer.map.div, t, evt); | ||
} else { | ||
that.callbacks.out({}, false, t); | ||
} | ||
that.callbacks.out(tiles[t].layer.map.div); | ||
} | ||
@@ -170,0 +178,0 @@ } |
@@ -23,7 +23,2 @@ // Instantiate objects based on a JSON "record". The record must be a statement | ||
// | ||
// Requirements: | ||
// | ||
// - Underscore.js | ||
// - jQuery @TODO: only used for $.browser check. Could probably be removed. | ||
// | ||
// Usage: | ||
@@ -41,5 +36,55 @@ // | ||
var wax = wax || {}; | ||
// TODO: replace with non-global-modifier | ||
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce | ||
if (!Array.prototype.reduce) { | ||
Array.prototype.reduce = function(fun /*, initialValue */) { | ||
"use strict"; | ||
if (this === void 0 || this === null) | ||
throw new TypeError(); | ||
var t = Object(this); | ||
var len = t.length >>> 0; | ||
if (typeof fun !== "function") | ||
throw new TypeError(); | ||
// no value to return if no initial value and an empty array | ||
if (len == 0 && arguments.length == 1) | ||
throw new TypeError(); | ||
var k = 0; | ||
var accumulator; | ||
if (arguments.length >= 2) { | ||
accumulator = arguments[1]; | ||
} else { | ||
do { | ||
if (k in t) { | ||
accumulator = t[k++]; | ||
break; | ||
} | ||
// if array contains no values, no initial value to return | ||
if (++k >= len) | ||
throw new TypeError(); | ||
} | ||
while (true); | ||
} | ||
while (k < len) { | ||
if (k in t) | ||
accumulator = fun.call(undefined, accumulator, t[k], k, t); | ||
k++; | ||
} | ||
return accumulator; | ||
}; | ||
} | ||
wax.Record = function(obj, context) { | ||
var getFunction = function(head, cur) { | ||
var ret = _.reduce(head.split('.'), function(part, segment) { | ||
// TODO: strip out reduce | ||
var ret = head.split('.').reduce(function(part, segment) { | ||
return [part[1] || part[0], part[1] ? part[1][segment] : part[0][segment]]; | ||
@@ -89,3 +134,3 @@ }, [cur || window, null]); | ||
var isKeyword = function(string) { | ||
return _.isString(string) && (_.indexOf([ | ||
return wax.util.isString(string) && (wax.util.indexOf([ | ||
'@new', | ||
@@ -100,3 +145,3 @@ '@call', | ||
var altersContext = function(string) { | ||
return _.isString(string) && (_.indexOf([ | ||
return wax.util.isString(string) && (wax.util.indexOf([ | ||
'@new', | ||
@@ -108,3 +153,3 @@ '@call', | ||
var getStatement = function(obj) { | ||
if (_.isArray(obj) && obj[0] && isKeyword(obj[0])) { | ||
if (wax.util.isArray(obj) && obj[0] && isKeyword(obj[0])) { | ||
return { | ||
@@ -148,3 +193,3 @@ verb: obj[0].split(' ')[0], | ||
} else if (obj !== null && typeof obj === 'object') { | ||
var keys = _.keys(obj); | ||
var keys = wax.util.keys(obj); | ||
for (i = 0; i < keys.length; i++) { | ||
@@ -151,0 +196,0 @@ var key = keys[i]; |
// Application bootstrap. | ||
$(function() { | ||
$.domReady(function() { | ||
// Convert any markdown sections to HTML. | ||
var nav = $('.navigation ul'); | ||
$('.md').each(function() { | ||
var html = document.createElement('div'); | ||
html.innerHTML = (new Showdown.converter()).makeHtml(this.innerHTML); | ||
$(html).html((new Showdown.converter()).makeHtml($(this).html())); | ||
html.className = this.className; | ||
html.id = this.id; | ||
// TODO: free from shackles of jQuery | ||
$(this).hide().after(html); | ||
$('h1, h2, h3, h4, h5, h6', html).each(function() { | ||
this.id = this.innerText.replace(/[\s\W]+/g, '-').toLowerCase(); | ||
$(this).after(html); | ||
$(this).remove(); | ||
}); | ||
$('h1, h2, h3, h4, h5, h6').each(function(elem, i, wrapped) { | ||
this.setAttribute('id', $(this).text().replace(/[\s\W]+/g, '-').toLowerCase()); | ||
var para = document.createElement('a'); | ||
para.innerHTML = '¶' | ||
para.className = 'para' | ||
para.href = '#' + this.id; | ||
var para = document.createElement('a'), | ||
api = document.createElement('a'), | ||
sectionLi = document.createElement('li'), | ||
sectionA = document.createElement('a'); | ||
this.appendChild(para); | ||
}); | ||
para.innerHTML = '¶'; | ||
para.setAttribute('title', 'permalink'); | ||
para.className = 'para'; | ||
para.href = '#' + this.id; | ||
sectionA.href = '#' + this.id; | ||
$(sectionA).text($(this).text()); | ||
sectionLi.className = this.nodeName; | ||
sectionLi.appendChild(sectionA); | ||
nav.append(sectionLi); | ||
if (this.nodeName === 'H3') { | ||
api.innerHTML = 'api'; | ||
api.setAttribute('title', 'API Documentation'); | ||
api.className = 'api'; | ||
api.href = '../docs/' + this.id + '.html'; | ||
$(this).append(api); | ||
} | ||
$(this).append(para); | ||
}); | ||
$('.run').each(function() { | ||
eval(this.innerText); | ||
eval($(this).text()); | ||
}); | ||
sh_highlightDocument(); | ||
}); |
{ | ||
"author": "Development Seed <info@developmentseed.org>", | ||
"name": "wax", | ||
"author": [ | ||
"Tom MacWright <macwright@gmail.com>" | ||
], | ||
"version": "2.0.0", | ||
"description": "Tools for improving web maps.", | ||
"author": { | ||
"name": "MapBox", | ||
"url": "http://mapbox.com/", | ||
"email": "info@mapbox.com" | ||
}, | ||
"contributors": [ | ||
"Tom MacWright <macwright@gmail.com>", | ||
"Young Hahn <young@developmentseed.org>", | ||
@@ -14,10 +18,7 @@ "Will White <will@developmentseed.org>" | ||
}], | ||
"version": "1.4.2", | ||
"files": [ | ||
"build" | ||
], | ||
"devDependencies": { | ||
"jshint": "0.2.x", | ||
"uglify-js": "1.0.x", | ||
"docco": "0.3.x" | ||
} | ||
} |
160
README.md
# Wax | ||
Tools for improving web maps. The centerpiece of the code is a client | ||
implementation of the [MBTiles interaction specification](https://github.com/mapbox/mbtiles-spec). | ||
Tools for improving web maps. The centerpiece of the code is a client implementation of the [MBTiles interaction specification](https://github.com/mapbox/mbtiles-spec). | ||
## Controls | ||
For full documentation of supported mapping APIs and how to use Wax see http://mapbox.github.com/wax. | ||
* `wax.tooltip` | ||
* `wax.legend` | ||
## Building Wax | ||
#### OpenLayers | ||
For end users, a minified library is already provided in `dist/`. | ||
* `wax.ol.Interaction` | ||
* `wax.ol.Legend` | ||
* `wax.ol.Embedder` | ||
* `wax.ol.Switcher` | ||
#### Google Maps API v3 | ||
* `wax.g.Controls` | ||
* `wax.g.MapType` | ||
* `wax.g.mapBoxLogo` | ||
#### Modest Maps | ||
* `.interaction()` | ||
* `.zoomer()` | ||
* `.legend()` | ||
* `.fullscreen()` | ||
* `.zoombox()` | ||
* `.hash()` | ||
#### Lib | ||
* `jquery.jsonp-2.1.4.js`, [from jquery-jsonp](http://code.google.com/p/jquery-jsonp/) | ||
#### Records | ||
The main usage of mapping frameworks through Wax is via records. Records are pure JSON objects that have a 1:1 representation with function Javascript code, but, unlike imperative code, can be stored and manipulated as configuration. Records are tested with [polymaps](http://polymaps.org), [openlayers](http://openlayers.org/), [Modest Maps](https://github.com/stamen/modestmaps-js) and Google Maps API v3, but the system (`/lib/record.js`) is generalized beyond mapping tools of any sort, to exist as a basic Javascript AST interpreter. | ||
Currently records support several control techniques: | ||
* `@new` instantiates objects | ||
* `@chain` runs functions, changing the value of `this` with each run | ||
* `@inject` runs a function in a `@chain` without changing the reference to `this` | ||
* `@call` runs a function from the global scope changing the value of `this` | ||
* `@literal` allows an object attribute to be referenced | ||
* `@group` runs a set of record statements (e.g. using the keywords above) in order | ||
These techniques (with arbitrary levels of nesting), are sufficient to construct maps in each mapping framework. | ||
## Requirements | ||
* [jQuery](http://jquery.com/) - tested with 1.5 | ||
* (docs only) [docco](https://github.com/jashkenas/docco) | ||
* (build only) [UglifyJS](https://github.com/mishoo/UglifyJS/) | ||
## Usage Examples | ||
Samples of usage can be found in examples/. These depend on localizing copies of each API code. | ||
To set up the examples first run: | ||
make ext | ||
Then check out the example html files. | ||
## Building library | ||
For wax users, a minified library is already provided in build/. | ||
But for developers you can rebuild a minified library by running: | ||
make build | ||
npm install --dev | ||
make | ||
* Requires [UglifyJS](https://github.com/mishoo/UglifyJS/) | ||
Install mainline UglifyJS: | ||
npm install https://github.com/mishoo/UglifyJS/tarball/master | ||
Make the combined & minified OpenLayers & Google Maps libraries: | ||
rm -r build | ||
make build | ||
## Building docs | ||
Wax uses docco for documention. Install it like: | ||
Wax uses docco for documention. Install docco and build docs by running: | ||
npm install docco | ||
Make the docs: | ||
npm install --dev | ||
make doc | ||
@@ -100,71 +25,6 @@ | ||
Wax includes two libraries in `/lib` which are included in builds | ||
Wax currently includes one external: | ||
* [underscore.js](http://documentcloud.github.com/underscore/) (MIT) | ||
* [jquery-jsonp](http://code.google.com/p/jquery-jsonp/) (MIT) | ||
* [reqwest](https://github.com/ded/reqwest) (MIT) | ||
## Changelog | ||
#### 1.4.2 | ||
* Beta pointselector control. | ||
* Make zoombox removable. | ||
#### 1.4.1 | ||
* Tweaks to `boxselect` including removability. | ||
#### 1.4.0 | ||
* Added `.boxselect(function())` | ||
#### 1.3.0 | ||
* Added `.zoombox()` and `hash()` controls for Modest Maps. | ||
#### 1.2.1 | ||
* Bug fixes for OpenLayers | ||
#### 1.2.0 | ||
* Functions on the Google Maps `Controls` object are now lowercase. | ||
* Changed `WaxProvider`'s signature: now takes an object of settings and supports multiple domains, filetypes and zoom restrictions. | ||
* Changed `wax.g.MapType`'s signature: now accepts an object of settings in the same form as `WaxProvider` | ||
* Modest Maps `.interaction()` now supports clicks, with the same `clickAction` setting as the OpenLayers version. | ||
* Added large manual for usage. | ||
* Fixed Modest Maps `.fullscreen()` sizing. | ||
* Removed `/examples` directory: examples will be in manuals. | ||
* Performance optimization of interaction code: no calculations are performed during drag events. | ||
#### 1.1.0 | ||
* connector/mm: Added [Modest Maps](https://github.com/stamen/modestmaps-js) connector. | ||
* control/mm: Added `.legend()`, `.interaction()`, `.fullscreen()`, and `.zoomer()` controls for Modest Maps. | ||
* control/lib: Added `addedTooltip` event to `tooltip.js` to allow for external styling code. | ||
#### 1.0.4 | ||
* connector/g: Hide error tiles and wrap on dateline. | ||
* connector/g: Performance improvements. | ||
* control/legend: Fix rerender bug. | ||
* control/tooltip: `addedtooltip` event for binding/extending tooltip behavior. Subject to change. | ||
#### 1.0.3 | ||
* Embedder functionality for Google Maps and OpenLayers. | ||
#### 1.0.2 | ||
* Bug fixes for Firefox 3. | ||
#### 1.0.1 | ||
* `make ext` added for downloading and installing external libraries needed to use examples. | ||
* Bug fixes for legend, IE compatibility. | ||
#### 1.0.0 | ||
* Initial release. | ||
## Authors | ||
@@ -171,0 +31,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
1650985
63
14115
1
3
34