Created
November 21, 2012 13:48
-
-
Save bwyss/4124918 to your computer and use it in GitHub Desktop.
DF_demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
a.wax-fullscreen { | |
/* TODO: sprite-based fullscreen button */ | |
position: absolute; | |
top: 5px; | |
left: 5px; | |
z-index: 99999; | |
} | |
.zoomer, | |
.wax-legend, | |
.wax-tooltip { | |
display:block; | |
position:absolute; | |
border:1px solid #ccc; | |
background:#fff; | |
border-radius:3px; | |
-moz-border-radius:3px; | |
-webkit-border-radius:3px; | |
box-shadow:rgba(0,0,0,0.1) 0px 1px 3px; | |
-moz-box-shadow:rgba(0,0,0,0.1) 0px 1px 3px; | |
-webkit-box-shadow:rgba(0,0,0,0.1) 0px 1px 3px; | |
} | |
/* ### Zoomer, close buttons, share ### */ | |
.close, | |
.zoomer { | |
text-indent:-9999px; | |
background-image:url(map-controls.png); | |
background-position:60px 60px; | |
background-repeat:no-repeat; | |
overflow:hidden; | |
} | |
.close:active, | |
.zoomer:active { | |
border-color:#bbb; | |
background-color:#ddd; | |
box-shadow:inset rgba(0,0,0,0.1) 0px 1px 3px; | |
-moz-box-shadow:inset rgba(0,0,0,0.1) 0px 1px 3px; | |
-webkit-box-shadow:inset rgba(0,0,0,0.1) 0px 1px 3px; | |
} | |
.close { | |
top:4px; | |
right:4px; | |
width:18px; | |
height:18px; | |
background-position:-6px -6px; | |
} | |
.zoomer { | |
width:28px; | |
height:28px; | |
top:10px; | |
left:10px; | |
z-index: 2; | |
} | |
.zoomin { | |
background-position:-31px -1px; | |
left:39px; | |
border-radius:0px 3px 3px 0px; | |
-moz-border-radius:0px 3px 3px 0px; | |
-webkit-border-radius:0px 3px 3px 0px; | |
} | |
.zoomout { | |
background-position:-61px -1px; | |
border-radius:3px 0px 0px 3px; | |
-moz-border-radius:3px 0px 0px 3px; | |
-webkit-border-radius:3px 0px 0px 3px; | |
} | |
.wax-fullscreen-map { | |
position:fixed !important; | |
width:auto !important; | |
height:auto !important; | |
top:0; | |
left:0; | |
right:0; | |
bottom:0; | |
z-index:999999999999; | |
} | |
.wax-legends { | |
position:absolute; | |
left:10px; | |
bottom:10px; | |
z-index:999999; | |
} | |
.wax-legends .wax-legend { | |
padding:10px; | |
background:#333; | |
color:#fff; | |
} | |
.wax-tooltip { | |
z-index:999999; | |
position:absolute; | |
color:#fff; | |
padding:10px; | |
-webkit-user-select:auto; | |
right:10px; | |
top:10px; | |
max-width:300px; | |
opacity:.9; | |
-webkit-transition:opacity 150ms; | |
-moz-transition:opacity 150ms; | |
} | |
.wax-movetip { | |
position:absolute; | |
z-index:999999; | |
background:#333; | |
color:#fff; | |
padding:10px; | |
max-width:300px; | |
} | |
.wax-fade { opacity:0; } | |
.wax-tooltip .close { | |
display:block; | |
position:absolute; | |
top:0px; | |
right:0px; | |
} | |
.wax-mobile-body .wax-tooltip { | |
position:absolute; | |
top:50px; | |
} | |
.zoombox-box, | |
.boxselector-box { | |
margin:0; | |
padding:0; | |
border:1px dashed #888; | |
background: rgba(255,255,255,0.25); | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 0; | |
height: 0; | |
display: none; | |
} | |
.zoombox-box-container, | |
.boxselector-box-container, | |
.pointselector-box-container { | |
margin:0; | |
padding:0; | |
position:absolute; | |
background: url(blank.gif); | |
top:0; | |
left:0; | |
} | |
.wax-point-div { | |
width:10px; | |
height:10px; | |
margin-left:-5px; | |
margin-top:-5px; | |
background:#fff; | |
border:1px solid #333; | |
-webkit-border-radius:5px; | |
} | |
div.wax-attribution { | |
position:absolute; | |
background-color:rgba(255, 255, 255, 0.7); | |
color:#333; | |
font-size:11px; | |
line-height:20px; | |
z-index:99999; | |
text-align:center; | |
padding:0 5px; | |
bottom:0; | |
left:0; | |
} | |
.wax-attribution.wax-g { | |
left:65px; | |
bottom:4px; | |
background:transparent; | |
} | |
.wax-latlngtooltip { | |
position:absolute; | |
background:#caedf4; | |
padding:3px; | |
border:1px solid #75c1d0; | |
border-radius:3px; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
<link href="screen.css" rel="stylesheet" type="text/css" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<!-- Leaflet Stuff--> | |
<script src='wax/ext/modestmaps.min.js' type='text/javascript'></script> | |
<script src='wax/dist/wax.mm.js' type='text/javascript'></script> | |
<link href='wax/theme/controls.css' rel='stylesheet' type='text/css' /> | |
<!-- Odds & Ends--> | |
<script type="text/javascript" src="jquery.min.js"></script> | |
<script src="http://maps.googleapis.com/maps/api/js?key=AIzaSyDQCU3F1OHX_cljI31zr2TLe1pRBXkLE_8&sensor=false" type="text/javascript"></script> | |
<style type="text/css"> | |
body { margin-top: 1.0em; background-color: #444;} | |
#map { position:absolute; top:0; bottom:0; width:100%; } | |
</style> | |
<title>DF Demo</title> | |
</head> | |
<body> | |
<br/> | |
<div id='intro-map' class='map-demo'></div> | |
<script type="text/javascript"> | |
var url = 'http://a.tiles.mapbox.com/v3/bwyss.map-45tf06mr.jsonp'; | |
wax.tilejson(url, function(tilejson) { | |
var m = new MM.Map('intro-map', | |
new wax.mm.connector(tilejson), | |
new MM.Point(1400,600)); | |
m.setCenterZoom({ lat: 0, lon: 0 }, 2); | |
wax.mm.zoomer(m).appendTo(m.parent); | |
wax.mm.interaction() | |
.map(m) | |
.tilejson(tilejson) | |
.on(wax.tooltip().animate(true).parent(m.parent).events()); | |
}); | |
/* | |
//leaflet approach, not working when pan: | |
wax.tilejson('http://a.tiles.mapbox.com/v3/bwyss.map-45tf06mr.jsonp', function(tilejson) { | |
var map = new L.Map('intro-map') | |
.addLayer(new wax.leaf.connector(tilejson)) | |
.setView(new L.LatLng(20, 20), 3); | |
map.minZoom(4); | |
wax.leaf.legend(map, tilejson).appendTo(map._container); | |
wax.leaf.interaction() | |
.map(map) | |
.tilejson(tilejson) | |
.on(wax.tooltip().animate(true).parent(map._container).events()); | |
}); | |
*/ | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Modest Maps JS v1.0.0-alpha | |
* http://modestmaps.com/ | |
* | |
* Copyright (c) 2011 Stamen Design, All Rights Reserved. | |
* | |
* Open source under the BSD License. | |
* http://creativecommons.org/licenses/BSD/ | |
* | |
* Versioned using Semantic Versioning (v.major.minor.patch) | |
* See CHANGELOG and http://semver.org/ for more details. | |
* | |
*/ | |
var previousMM=MM;if(!com){var com={};if(!com.modestmaps){com.modestmaps={}}}var MM=com.modestmaps={noConflict:function(){MM=previousMM;return this}};(function(b){b.extend=function(e,c){for(var d in c.prototype){if(typeof e.prototype[d]=="undefined"){e.prototype[d]=c.prototype[d]}}return e};b.getFrame=function(){return function(c){(window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(d){window.setTimeout(function(){d(+new Date())},10)})(c)}}();b.transformProperty=(function(e){if(!this.document){return}var d=document.documentElement.style;for(var c=0;c<e.length;c++){if(e[c] in d){return e[c]}}return false})(["transformProperty","WebkitTransform","OTransform","MozTransform","msTransform"]);b.matrixString=function(c){if(c.scale*c.width%1){c.scale+=(1-c.scale*c.width%1)/c.width}if(b._browser.webkit3d){return"matrix3d("+[(c.scale||"1"),"0,0,0,0",(c.scale||"1"),"0,0","0,0,1,0",(c.x+(((c.width*c.scale)-c.width)/2)).toFixed(4),(c.y+(((c.height*c.scale)-c.height)/2)).toFixed(4),0,1].join(",")+")"}else{var d=(b.transformProperty=="MozTransform")?"px":"";return"matrix("+[(c.scale||"1"),0,0,(c.scale||"1"),(c.x+(((c.width*c.scale)-c.width)/2))+d,(c.y+(((c.height*c.scale)-c.height)/2))+d].join(",")+")"}};b._browser=(function(c){return{webkit:("WebKitCSSMatrix" in c),webkit3d:("WebKitCSSMatrix" in c)&&("m11" in new WebKitCSSMatrix())}})(this);b.moveElement=function(e,c){if(b.transformProperty){if(!c.scale){c.scale=1}if(!c.width){c.width=0}if(!c.height){c.height=0}var d=b.matrixString(c);if(e[b.transformProperty]!==d){e.style[b.transformProperty]=e[b.transformProperty]=d}}else{e.style.left=c.x+"px";e.style.top=c.y+"px";if(c.width&&c.height&&c.scale){e.style.width=Math.ceil(c.width*c.scale)+"px";e.style.height=Math.ceil(c.height*c.scale)+"px"}}};b.cancelEvent=function(c){c.cancelBubble=true;c.cancel=true;c.returnValue=false;if(c.stopPropagation){c.stopPropagation()}if(c.preventDefault){c.preventDefault()}return false};b.bind=function(e,f){var g=Array.prototype.slice;var d=Function.prototype.bind;if(e.bind===d&&d){return d.apply(e,g.call(arguments,1))}var c=g.call(arguments,2);return function(){return e.apply(f,c.concat(g.call(arguments)))}};b.addEvent=function(e,d,c){if(e.addEventListener){e.addEventListener(d,c,false);if(d=="mousewheel"){e.addEventListener("DOMMouseScroll",c,false)}}else{if(e.attachEvent){e["e"+d+c]=c;e[d+c]=function(){e["e"+d+c](window.event)};e.attachEvent("on"+d,e[d+c])}}};b.removeEvent=function(e,d,c){if(e.removeEventListener){e.removeEventListener(d,c,false);if(d=="mousewheel"){e.removeEventListener("DOMMouseScroll",c,false)}}else{if(e.detachEvent){e.detachEvent("on"+d,e[d+c]);e[d+c]=null}}};b.getStyle=function(d,c){if(d.currentStyle){return d.currentStyle[c]}else{if(window.getComputedStyle){return document.defaultView.getComputedStyle(d,null).getPropertyValue(c)}}};b.Point=function(c,d){this.x=parseFloat(c);this.y=parseFloat(d)};b.Point.prototype={x:0,y:0,toString:function(){return"("+this.x.toFixed(3)+", "+this.y.toFixed(3)+")"},copy:function(){return new b.Point(this.x,this.y)}};b.Point.distance=function(f,e){var d=(e.x-f.x);var c=(e.y-f.y);return Math.sqrt(d*d+c*c)};b.Point.interpolate=function(g,f,e){var d=g.x+(f.x-g.x)*e;var c=g.y+(f.y-g.y)*e;return new b.Point(d,c)};b.Coordinate=function(e,c,d){this.row=e;this.column=c;this.zoom=d};b.Coordinate.prototype={row:0,column:0,zoom:0,toString:function(){return"("+this.row.toFixed(3)+", "+this.column.toFixed(3)+" @"+this.zoom.toFixed(3)+")"},toKey:function(){return[this.zoom,this.row,this.column].join(",")},copy:function(){return new b.Coordinate(this.row,this.column,this.zoom)},container:function(){return new b.Coordinate(Math.floor(this.row),Math.floor(this.column),Math.floor(this.zoom))},zoomTo:function(c){var d=Math.pow(2,c-this.zoom);return new b.Coordinate(this.row*d,this.column*d,c)},zoomBy:function(d){var c=Math.pow(2,d);return new b.Coordinate(this.row*c,this.column*c,this.zoom+d)},up:function(c){if(c===undefined){c=1}return new b.Coordinate(this.row-c,this.column,this.zoom)},right:function(c){if(c===undefined){c=1}return new b.Coordinate(this.row,this.column+c,this.zoom)},down:function(c){if(c===undefined){c=1}return new b.Coordinate(this.row+c,this.column,this.zoom)},left:function(c){if(c===undefined){c=1}return new b.Coordinate(this.row,this.column-c,this.zoom)}};b.Location=function(c,d){this.lat=parseFloat(c);this.lon=parseFloat(d)};b.Location.prototype={lat:0,lon:0,toString:function(){return"("+this.lat.toFixed(3)+", "+this.lon.toFixed(3)+")"},copy:function(){return new b.Location(this.lat,this.lon)}};b.Location.distance=function(j,i,f){if(!f){f=6378000}var p=Math.PI/180,h=j.lat*p,o=j.lon*p,g=i.lat*p,n=i.lon*p,m=Math.cos(h)*Math.cos(o)*Math.cos(g)*Math.cos(n),l=Math.cos(h)*Math.sin(o)*Math.cos(g)*Math.sin(n),k=Math.sin(h)*Math.sin(g);return Math.acos(m+l+k)*f};b.Location.interpolate=function(j,h,n){if(j.lat===h.lat&&j.lon===h.lon){return new b.Location(j.lat,j.lon)}var t=Math.PI/180,l=j.lat*t,o=j.lon*t,k=h.lat*t,m=h.lon*t;var p=2*Math.asin(Math.sqrt(Math.pow(Math.sin((l-k)/2),2)+Math.cos(l)*Math.cos(k)*Math.pow(Math.sin((o-m)/2),2)));var u=Math.atan2(Math.sin(o-m)*Math.cos(k),Math.cos(l)*Math.sin(k)-Math.sin(l)*Math.cos(k)*Math.cos(o-m))/-(Math.PI/180);u=u<0?360+u:u;var g=Math.sin((1-n)*p)/Math.sin(p);var c=Math.sin(n*p)/Math.sin(p);var s=g*Math.cos(l)*Math.cos(o)+c*Math.cos(k)*Math.cos(m);var r=g*Math.cos(l)*Math.sin(o)+c*Math.cos(k)*Math.sin(m);var q=g*Math.sin(l)+c*Math.sin(k);var e=Math.atan2(q,Math.sqrt(Math.pow(s,2)+Math.pow(r,2)));var i=Math.atan2(r,s);return new b.Location(e/t,i/t)};b.MapExtent=function(h,d,g,f){if(arguments[0] instanceof b.Location){var e=arguments[0];h=e.lat;d=e.lon}if(arguments[1] instanceof b.Location){var c=arguments[1];g=c.lat;f=c.lon}if(isNaN(g)){g=h}if(isNaN(f)){f=d}this.north=Math.max(h,g);this.south=Math.min(h,g);this.east=Math.max(f,d);this.west=Math.min(f,d)};b.MapExtent.prototype={north:0,south:0,east:0,west:0,copy:function(){return new b.MapExtent(this.north,this.west,this.south,this.east)},toString:function(c){if(isNaN(c)){c=3}return[this.north.toFixed(c),this.west.toFixed(c),this.south.toFixed(c),this.east.toFixed(c)].join(", ")},northWest:function(){return new b.Location(this.north,this.west)},southEast:function(){return new b.Location(this.south,this.east)},northEast:function(){return new b.Location(this.north,this.east)},southWest:function(){return new b.Location(this.south,this.west)},center:function(){return new b.Location(this.south+(this.north-this.south)/2,this.east+(this.west-this.east)/2)},encloseLocation:function(c){if(c.lat>this.north){this.north=c.lat}if(c.lat<this.south){this.south=c.lat}if(c.lon>this.east){this.east=c.lon}if(c.lon<this.west){this.west=c.lon}},encloseLocations:function(d){var c=d.length;for(var e=0;e<c;e++){this.encloseLocation(d[e])}},setFromLocations:function(d){var c=d.length,f=d[0];this.north=this.south=f.lat;this.east=this.west=f.lon;for(var e=1;e<c;e++){this.encloseLocation(d[e])}},encloseExtent:function(c){if(c.north>this.north){this.north=c.north}if(c.south<this.south){this.south=c.south}if(c.east>this.east){this.east=c.east}if(c.west<this.west){this.west=c.west}},containsLocation:function(c){return c.lat>=this.south&&c.lat<=this.north&&c.lon>=this.west&&c.lon<=this.east},toArray:function(){return[this.northWest(),this.southEast()]}};b.MapExtent.fromString=function(d){var c=d.split(/\s*,\s*/);if(c.length!=4){throw"Invalid extent string (expecting 4 comma-separated numbers)"}return new b.MapExtent(parseFloat(c[0]),parseFloat(c[1]),parseFloat(c[2]),parseFloat(c[3]))};b.MapExtent.fromArray=function(c){var d=new b.MapExtent();d.setFromLocations(c);return d};b.Transformation=function(e,g,c,d,f,h){this.ax=e;this.bx=g;this.cx=c;this.ay=d;this.by=f;this.cy=h};b.Transformation.prototype={ax:0,bx:0,cx:0,ay:0,by:0,cy:0,transform:function(c){return new b.Point(this.ax*c.x+this.bx*c.y+this.cx,this.ay*c.x+this.by*c.y+this.cy)},untransform:function(c){return new b.Point((c.x*this.by-c.y*this.bx-this.cx*this.by+this.cy*this.bx)/(this.ax*this.by-this.ay*this.bx),(c.x*this.ay-c.y*this.ax-this.cx*this.ay+this.cy*this.ax)/(this.bx*this.ay-this.by*this.ax))}};b.deriveTransformation=function(m,l,g,f,c,p,i,h,e,d,o,n){var k=b.linearSolution(m,l,g,c,p,i,e,d,o);var j=b.linearSolution(m,l,f,c,p,h,e,d,n);return new b.Transformation(k[0],k[1],k[2],j[0],j[1],j[2])};b.linearSolution=function(f,o,i,e,n,h,d,m,g){f=parseFloat(f);o=parseFloat(o);i=parseFloat(i);e=parseFloat(e);n=parseFloat(n);h=parseFloat(h);d=parseFloat(d);m=parseFloat(m);g=parseFloat(g);var l=(((h-g)*(o-n))-((i-h)*(n-m)))/(((e-d)*(o-n))-((f-e)*(n-m)));var k=(((h-g)*(f-e))-((i-h)*(e-d)))/(((n-m)*(f-e))-((o-n)*(e-d)));var j=i-(f*l)-(o*k);return[l,k,j]};b.Projection=function(d,c){if(!c){c=new b.Transformation(1,0,0,0,1,0)}this.zoom=d;this.transformation=c};b.Projection.prototype={zoom:0,transformation:null,rawProject:function(c){throw"Abstract method not implemented by subclass."},rawUnproject:function(c){throw"Abstract method not implemented by subclass."},project:function(c){c=this.rawProject(c);if(this.transformation){c=this.transformation.transform(c)}return c},unproject:function(c){if(this.transformation){c=this.transformation.untransform(c)}c=this.rawUnproject(c);return c},locationCoordinate:function(d){var c=new b.Point(Math.PI*d.lon/180,Math.PI*d.lat/180);c=this.project(c);return new b.Coordinate(c.y,c.x,this.zoom)},coordinateLocation:function(d){d=d.zoomTo(this.zoom);var c=new b.Point(d.column,d.row);c=this.unproject(c);return new b.Location(180*c.y/Math.PI,180*c.x/Math.PI)}};b.LinearProjection=function(d,c){b.Projection.call(this,d,c)};b.LinearProjection.prototype={rawProject:function(c){return new b.Point(c.x,c.y)},rawUnproject:function(c){return new b.Point(c.x,c.y)}};b.extend(b.LinearProjection,b.Projection);b.MercatorProjection=function(d,c){b.Projection.call(this,d,c)};b.MercatorProjection.prototype={rawProject:function(c){return new b.Point(c.x,Math.log(Math.tan(0.25*Math.PI+0.5*c.y)))},rawUnproject:function(c){return new b.Point(c.x,2*Math.atan(Math.pow(Math.E,c.y))-0.5*Math.PI)}};b.extend(b.MercatorProjection,b.Projection);b.MapProvider=function(c){if(c){this.getTile=c}};b.MapProvider.prototype={tileLimits:[new b.Coordinate(0,0,0),new b.Coordinate(1,1,0).zoomTo(18)],getTileUrl:function(c){throw"Abstract method not implemented by subclass."},getTile:function(c){throw"Abstract method not implemented by subclass."},releaseTile:function(c){},setZoomRange:function(d,c){this.tileLimits[0]=this.tileLimits[0].zoomTo(d);this.tileLimits[1]=this.tileLimits[1].zoomTo(c)},sourceCoordinate:function(h){var c=this.tileLimits[0].zoomTo(h.zoom);var e=this.tileLimits[1].zoomTo(h.zoom);var d=e.row-c.row;if(h.row<0||h.row>=d){return null}var g=e.column-c.column;var f=h.column%g;while(f<0){f+=g}return new b.Coordinate(h.row,f,h.zoom)}};b.TemplatedMapProvider=function(e,c){var f=false;if(e.match(/{(Q|quadkey)}/)){f=true;e=e.replace("{subdomains}","{S}").replace("{zoom}","{Z}").replace("{quadkey}","{Q}")}var d=(c&&c.length&&e.indexOf("{S}")>=0);var g=function(k){var j=this.sourceCoordinate(k);if(!j){return null}var i=e;if(d){var h=parseInt(j.zoom+j.row+j.column,10)%c.length;i=i.replace("{S}",c[h])}if(f){return i.replace("{Z}",j.zoom.toFixed(0)).replace("{Q}",this.quadKey(j.row,j.column,j.zoom))}else{return i.replace("{Z}",j.zoom.toFixed(0)).replace("{X}",j.column.toFixed(0)).replace("{Y}",j.row.toFixed(0))}};b.MapProvider.call(this,g)};b.TemplatedMapProvider.prototype={quadKey:function(g,e,f){var d="";for(var c=1;c<=f;c++){d+=(((g>>f-c)&1)<<1)|((e>>f-c)&1)}return d||"0"},getTile:function(c){return this.getTileUrl(c)},releaseTile:function(){}};b.extend(b.TemplatedMapProvider,b.MapProvider);b.getMousePoint=function(g,f){var c=new b.Point(g.clientX,g.clientY);c.x+=document.body.scrollLeft+document.documentElement.scrollLeft;c.y+=document.body.scrollTop+document.documentElement.scrollTop;for(var d=f.parent;d;d=d.offsetParent){c.x-=d.offsetLeft;c.y-=d.offsetTop}return c};b.MouseWheelHandler=function(d,c){if(d){this.init(d,c)}else{if(arguments.length>1){this.precise=c?true:false}}};b.MouseWheelHandler.prototype={precise:false,init:function(d){this.map=d;this._mouseWheel=b.bind(this.mouseWheel,this);this._zoomDiv=document.body.appendChild(document.createElement("div"));this._zoomDiv.style.cssText="visibility:hidden;top:0;height:0;width:0;overflow-y:scroll";var c=this._zoomDiv.appendChild(document.createElement("div"));c.style.height="2000px";b.addEvent(d.parent,"mousewheel",this._mouseWheel)},remove:function(){b.removeEvent(this.map.parent,"mousewheel",this._mouseWheel);this._zoomDiv.parentNode.removeChild(this._zoomDiv)},mouseWheel:function(g){var h=0;this.prevTime=this.prevTime||new Date().getTime();try{this._zoomDiv.scrollTop=1000;this._zoomDiv.dispatchEvent(g);h=1000-this._zoomDiv.scrollTop}catch(d){h=g.wheelDelta||(-g.detail*5)}var f=new Date().getTime()-this.prevTime;if(Math.abs(h)>0&&(f>200)&&!this.precise){var c=b.getMousePoint(g,this.map);this.map.zoomByAbout(h>0?1:-1,c);this.prevTime=new Date().getTime()}else{if(this.precise){var c=b.getMousePoint(g,this.map);this.map.zoomByAbout(h*0.001,c)}}return b.cancelEvent(g)}};b.DoubleClickHandler=function(c){if(c!==undefined){this.init(c)}};b.DoubleClickHandler.prototype={init:function(c){this.map=c;this._doubleClick=b.bind(this.doubleClick,this);b.addEvent(c.parent,"dblclick",this._doubleClick)},remove:function(){b.removeEvent(this.map.parent,"dblclick",this._doubleClick)},doubleClick:function(d){var c=b.getMousePoint(d,this.map);this.map.zoomByAbout(d.shiftKey?-1:1,c);return b.cancelEvent(d)}};b.DragHandler=function(c){if(c!==undefined){this.init(c)}};b.DragHandler.prototype={init:function(c){this.map=c;this._mouseDown=b.bind(this.mouseDown,this);b.addEvent(c.parent,"mousedown",this._mouseDown)},remove:function(){b.removeEvent(this.map.parent,"mousedown",this._mouseDown)},mouseDown:function(c){b.addEvent(document,"mouseup",this._mouseUp=b.bind(this.mouseUp,this));b.addEvent(document,"mousemove",this._mouseMove=b.bind(this.mouseMove,this));this.prevMouse=new b.Point(c.clientX,c.clientY);this.map.parent.style.cursor="move";return b.cancelEvent(c)},mouseMove:function(c){if(this.prevMouse){this.map.panBy(c.clientX-this.prevMouse.x,c.clientY-this.prevMouse.y);this.prevMouse.x=c.clientX;this.prevMouse.y=c.clientY;this.prevMouse.t=+new Date()}return b.cancelEvent(c)},mouseUp:function(c){b.removeEvent(document,"mouseup",this._mouseUp);b.removeEvent(document,"mousemove",this._mouseMove);this.prevMouse=null;this.map.parent.style.cursor="";return b.cancelEvent(c)}};b.MouseHandler=function(c){if(c!==undefined){this.init(c)}};b.MouseHandler.prototype={init:function(c){this.map=c;this.handlers=[new b.DragHandler(c),new b.DoubleClickHandler(c),new b.MouseWheelHandler(c)]},remove:function(){for(var c=0;c<this.handlers.length;c++){this.handlers[c].remove()}}};var a=(function(){var c=window.documentMode;return("onhashchange" in window)&&(c===undefined||c>7)})();b.Hash=function(c){this.onMapMove=b.bind(this.onMapMove,this);this.onHashChange=b.bind(this.onHashChange,this);if(c){this.init(c)}};b.Hash.prototype={map:null,lastHash:null,parseHash:function(f){var c=f.split("/");if(c.length==3){var d=parseInt(c[0],10),e=parseFloat(c[1]),g=parseFloat(c[2]);if(isNaN(d)||isNaN(e)||isNaN(g)){return false}else{return{center:new b.Location(e,g),zoom:d}}}else{return false}},formatHash:function(f){var c=f.getCenter(),e=f.getZoom(),d=Math.max(0,Math.ceil(Math.log(e)/Math.LN2));return"#"+[e,c.lat.toFixed(d),c.lon.toFixed(d)].join("/")},init:function(c){this.map=c;this.map.addCallback("drawn",this.onMapMove);this.lastHash=null;this.onHashChange();if(!this.isListening){this.startListening()}},remove:function(){this.map=null;if(this.isListening){this.stopListening()}},onMapMove:function(d){if(this.movingMap||this.map.zoom===0){return false}var c=this.formatHash(d);if(this.lastHash!=c){location.replace(c);this.lastHash=c}},movingMap:false,update:function(){var e=location.hash;if(e===this.lastHash){return}var d=e.substr(1),c=this.parseHash(d);if(c){this.movingMap=true;this.map.setCenterZoom(c.center,c.zoom);this.movingMap=false}else{this.onMapMove(this.map)}},changeDefer:100,changeTimeout:null,onHashChange:function(){if(!this.changeTimeout){var c=this;this.changeTimeout=setTimeout(function(){c.update();c.changeTimeout=null},this.changeDefer)}},isListening:false,hashChangeInterval:null,startListening:function(){if(a){window.addEventListener("hashchange",this.onHashChange,false)}else{clearInterval(this.hashChangeInterval);this.hashChangeInterval=setInterval(this.onHashChange,50)}this.isListening=true},stopListening:function(){if(a){window.removeEventListener("hashchange",this.onHashChange)}else{clearInterval(this.hashChangeInterval)}this.isListening=false}};b.TouchHandler=function(d,c){if(d){this.init(d,c)}};b.TouchHandler.prototype={maxTapTime:250,maxTapDistance:30,maxDoubleTapDelay:350,locations:{},taps:[],wasPinching:false,lastPinchCenter:null,init:function(d,c){this.map=d;c=c||{};if(!this.isTouchable()){return false}this._touchStartMachine=b.bind(this.touchStartMachine,this);this._touchMoveMachine=b.bind(this.touchMoveMachine,this);this._touchEndMachine=b.bind(this.touchEndMachine,this);b.addEvent(d.parent,"touchstart",this._touchStartMachine);b.addEvent(d.parent,"touchmove",this._touchMoveMachine);b.addEvent(d.parent,"touchend",this._touchEndMachine);this.options={};this.options.snapToZoom=c.snapToZoom||true},isTouchable:function(){var c=document.createElement("div");c.setAttribute("ongesturestart","return;");return(typeof c.ongesturestart==="function")},remove:function(){if(!this.isTouchable()){return false}b.removeEvent(this.map.parent,"touchstart",this._touchStartMachine);b.removeEvent(this.map.parent,"touchmove",this._touchMoveMachine);b.removeEvent(this.map.parent,"touchend",this._touchEndMachine)},updateTouches:function(g){for(var f=0;f<g.touches.length;f+=1){var d=g.touches[f];if(d.identifier in this.locations){var c=this.locations[d.identifier];c.x=d.screenX;c.y=d.screenY;c.scale=g.scale}else{this.locations[d.identifier]={scale:g.scale,startPos:{x:d.screenX,y:d.screenY},x:d.screenX,y:d.screenY,time:new Date().getTime()}}}},sameTouch:function(c,d){return(c&&c.touch)&&(d.identifier==c.touch.identifier)},touchStartMachine:function(c){this.updateTouches(c);return b.cancelEvent(c)},touchMoveMachine:function(c){switch(c.touches.length){case 1:this.onPanning(c.touches[0]);break;case 2:this.onPinching(c);break}this.updateTouches(c);return b.cancelEvent(c)},touchEndMachine:function(m){var d=new Date().getTime();if(m.touches.length===0&&this.wasPinching){this.onPinched(this.lastPinchCenter)}for(var k=0;k<m.changedTouches.length;k+=1){var p=m.changedTouches[k],l=this.locations[p.identifier];if(!l||l.wasPinch){continue}var o={x:p.screenX,y:p.screenY},g=d-l.time,f=b.Point.distance(o,l.startPos);if(f>this.maxTapDistance){}else{if(g>this.maxTapTime){o.end=d;o.duration=g;this.onHold(o)}else{o.time=d;this.onTap(o)}}}var n={};for(var h=0;h<m.touches.length;h++){n[m.touches[h].identifier]=true}for(var c in this.locations){if(!(c in n)){delete n[c]}}return b.cancelEvent(m)},onHold:function(c){},onTap:function(c){if(this.taps.length&&(c.time-this.taps[0].time)<this.maxDoubleTapDelay){this.onDoubleTap(c);this.taps=[];return}this.taps=[c]},onDoubleTap:function(d){var f=this.map.getZoom(),g=Math.round(f)+1,c=g-f;var e=new b.Point(d.x,d.y);this.map.zoomByAbout(c,e)},onPanning:function(e){var d={x:e.screenX,y:e.screenY},c=this.locations[e.identifier];this.map.panBy(d.x-c.x,d.y-c.y)},onPinching:function(j){var i=j.touches[0],h=j.touches[1],l=new b.Point(i.screenX,i.screenY),k=new b.Point(h.screenX,h.screenY),f=this.locations[i.identifier],d=this.locations[h.identifier];f.wasPinch=true;d.wasPinch=true;var c=b.Point.interpolate(l,k,0.5);this.map.zoomByAbout(Math.log(j.scale)/Math.LN2-Math.log(f.scale)/Math.LN2,c);var g=b.Point.interpolate(f,d,0.5);this.map.panBy(c.x-g.x,c.y-g.y);this.wasPinching=true;this.lastPinchCenter=c},onPinched:function(c){if(this.options.snapToZoom){var d=this.map.getZoom(),e=Math.round(d);this.map.zoomByAbout(e-d,c)}this.wasPinching=false}};b.CallbackManager=function(c,e){this.owner=c;this.callbacks={};for(var d=0;d<e.length;d++){this.callbacks[e[d]]=[]}};b.CallbackManager.prototype={owner:null,callbacks:null,addCallback:function(c,d){if(typeof(d)=="function"&&this.callbacks[c]){this.callbacks[c].push(d)}},removeCallback:function(f,g){if(typeof(g)=="function"&&this.callbacks[f]){var d=this.callbacks[f],c=d.length;for(var e=0;e<c;e++){if(d[e]===g){d.splice(e,1);break}}}},dispatchCallback:function(f,d){if(this.callbacks[f]){for(var c=0;c<this.callbacks[f].length;c+=1){try{this.callbacks[f][c](this.owner,d)}catch(g){}}}}};b.RequestManager=function(){this.loadingBay=document.createDocumentFragment();this.requestsById={};this.openRequestCount=0;this.maxOpenRequests=4;this.requestQueue=[];this.callbackManager=new b.CallbackManager(this,["requestcomplete","requesterror"])};b.RequestManager.prototype={loadingBay:null,requestsById:null,requestQueue:null,openRequestCount:null,maxOpenRequests:null,callbackManager:null,addCallback:function(c,d){this.callbackManager.addCallback(c,d)},removeCallback:function(c,d){this.callbackManager.removeCallback(c,d)},dispatchCallback:function(d,c){this.callbackManager.dispatchCallback(d,c)},clear:function(){this.clearExcept({})},clearRequest:function(e){if(e in this.requestsById){delete this.requestsById[e]}for(var c=0;c<this.requestQueue.length;c++){var d=this.requestQueue[c];if(d&&d.id==e){this.requestQueue[c]=null}}},clearExcept:function(g){for(var f=0;f<this.requestQueue.length;f++){var h=this.requestQueue[f];if(h&&!(h.id in g)){this.requestQueue[f]=null}}var c=this.loadingBay.childNodes;for(var e=c.length-1;e>=0;e--){var d=c[e];if(!(d.id in g)){this.loadingBay.removeChild(d);this.openRequestCount--;d.src=d.coord=d.onload=d.onerror=null}}for(var l in this.requestsById){if(this.requestsById.hasOwnProperty(l)){if(!(l in g)){var k=this.requestsById[l];delete this.requestsById[l];if(k!==null){k=k.id=k.coord=k.url=null}}}}},hasRequest:function(c){return(c in this.requestsById)},requestTile:function(f,e,c){if(!(f in this.requestsById)){var d={id:f,coord:e.copy(),url:c};this.requestsById[f]=d;if(c){this.requestQueue.push(d)}}},getProcessQueue:function(){if(!this._processQueue){var c=this;this._processQueue=function(){c.processQueue()}}return this._processQueue},processQueue:function(e){if(e&&this.requestQueue.length>8){this.requestQueue.sort(e)}while(this.openRequestCount<this.maxOpenRequests&&this.requestQueue.length>0){var d=this.requestQueue.pop();if(d){this.openRequestCount++;var c=document.createElement("img");c.id=d.id;c.style.position="absolute";c.coord=d.coord;this.loadingBay.appendChild(c);c.onload=c.onerror=this.getLoadComplete();c.src=d.url;d=d.id=d.coord=d.url=null}}},_loadComplete:null,getLoadComplete:function(){if(!this._loadComplete){var c=this;this._loadComplete=function(f){f=f||window.event;var d=f.srcElement||f.target;d.onload=d.onerror=null;c.loadingBay.removeChild(d);c.openRequestCount--;delete c.requestsById[d.id];if(f.type==="load"&&(d.complete||(d.readyState&&d.readyState=="complete"))){c.dispatchCallback("requestcomplete",d)}else{c.dispatchCallback("requesterror",d.src);d.src=null}setTimeout(c.getProcessQueue(),0)}}return this._loadComplete}};b.Layer=function(d,c){this.parent=c||document.createElement("div");this.parent.style.cssText="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0";this.levels={};this.requestManager=new b.RequestManager();this.requestManager.addCallback("requestcomplete",this.getTileComplete());if(d){this.setProvider(d)}};b.Layer.prototype={map:null,parent:null,tiles:null,levels:null,requestManager:null,tileCacheSize:null,maxTileCacheSize:null,provider:null,recentTiles:null,recentTilesById:null,enablePyramidLoading:false,_tileComplete:null,getTileComplete:function(){if(!this._tileComplete){var c=this;this._tileComplete=function(e,f){c.tiles[f.id]=f;c.tileCacheSize++;var d={id:f.id,lastTouchedTime:new Date().getTime()};c.recentTilesById[f.id]=d;c.recentTiles.push(d);c.positionTile(f)}}return this._tileComplete},draw:function(){var o=Math.round(this.map.coordinate.zoom);var n=this.map.pointCoordinate(new b.Point(0,0)).zoomTo(o).container();var i=this.map.pointCoordinate(this.map.dimensions).zoomTo(o).container().right().down();var k={};var m=this.createOrGetLevel(n.zoom);var h=n.copy();for(h.column=n.column;h.column<=i.column;h.column++){for(h.row=n.row;h.row<=i.row;h.row++){var d=this.inventoryVisibleTile(m,h);while(d.length){k[d.pop()]=true}}}for(var f in this.levels){if(this.levels.hasOwnProperty(f)){var p=parseInt(f,10);if(p>=n.zoom-5&&p<n.zoom+2){continue}var e=this.levels[f];e.style.display="none";var g=this.tileElementsInLevel(e);while(g.length){this.provider.releaseTile(g[0].coord);this.requestManager.clearRequest(g[0].coord.toKey());e.removeChild(g[0]);g.shift()}}}var c=n.zoom-5;var l=n.zoom+2;for(var j=c;j<l;j++){this.adjustVisibleLevel(this.levels[j],j,k)}this.requestManager.clearExcept(k);this.requestManager.processQueue(this.getCenterDistanceCompare());this.checkCache()},inventoryVisibleTile:function(q,d){var i=d.toKey(),e=[i];if(i in this.tiles){var h=this.tiles[i];if(h.parentNode!=q){q.appendChild(h);if("reAddTile" in this.provider){this.provider.reAddTile(i,d,h)}}return e}if(!this.requestManager.hasRequest(i)){var p=this.provider.getTile(d);if(typeof p=="string"){this.addTileImage(i,d,p)}else{if(p){this.addTileElement(i,d,p)}}}var f=false;var n=d.zoom;for(var j=1;j<=n;j++){var c=d.zoomBy(-j).container();var o=c.toKey();if(this.enablePyramidLoading){e.push(o);var m=this.createOrGetLevel(c.zoom);if(o in this.tiles){var l=this.tiles[o];if(l.parentNode!=m){m.appendChild(l)}}else{if(!this.requestManager.hasRequest(o)){var g=this.provider.getTile(c);if(typeof g=="string"){this.addTileImage(o,c,g)}else{this.addTileElement(o,c,g)}}}}else{if(o in this.tiles){e.push(o);f=true;break}}}if(!f&&!this.enablePyramidLoading){var k=d.zoomBy(1);e.push(k.toKey());k.column+=1;e.push(k.toKey());k.row+=1;e.push(k.toKey());k.column-=1;e.push(k.toKey())}return e},tileElementsInLevel:function(e){var c=[];for(var d=e.firstChild;d;d=d.nextSibling){if(d.nodeType==1){c.push(d)}}return c},adjustVisibleLevel:function(d,m,f){var e=new Date().getTime();if(!d){return}var g=1;var l=this.map.coordinate.copy();if(d.childNodes.length>0){d.style.display="block";g=Math.pow(2,this.map.coordinate.zoom-m);l=l.zoomTo(m)}else{d.style.display="none"}var j=this.map.tileSize.x*g;var h=this.map.tileSize.y*g;var c=new b.Point(this.map.dimensions.x/2,this.map.dimensions.y/2);var k=this.tileElementsInLevel(d);while(k.length){var i=k.pop();if(!f[i.id]){this.provider.releaseTile(i.coord);this.requestManager.clearRequest(i.coord.toKey());d.removeChild(i)}else{b.moveElement(i,{x:Math.round(c.x+(i.coord.column-l.column)*j),y:Math.round(c.y+(i.coord.row-l.row)*h),scale:g,width:this.map.tileSize.x,height:this.map.tileSize.y});this.recentTilesById[i.id].lastTouchedTime=e}}},createOrGetLevel:function(c){if(c in this.levels){return this.levels[c]}var d=document.createElement("div");d.id=this.parent.id+"-zoom-"+c;d.style.cssText=this.parent.style.cssText;d.style.zIndex=c;this.parent.appendChild(d);this.levels[c]=d;return d},addTileImage:function(d,e,c){this.requestManager.requestTile(d,e,c)},addTileElement:function(e,f,d){d.id=e;d.coord=f.copy();this.tiles[e]=d;this.tileCacheSize++;var c={id:e,lastTouchedTime:new Date().getTime()};this.recentTilesById[e]=c;this.recentTiles.push(c);this.positionTile(d)},positionTile:function(g){var f=this.map.coordinate.zoomTo(g.coord.zoom);var h=Math.pow(2,this.map.coordinate.zoom-g.coord.zoom);g.style.cssText="position:absolute;-webkit-user-select: none;-webkit-user-drag: none;-moz-user-drag: none;";g.ondragstart=function(){return false};var d=((this.map.dimensions.x/2)+(g.coord.column-f.column)*this.map.tileSize.x*h);var c=((this.map.dimensions.y/2)+(g.coord.row-f.row)*this.map.tileSize.y*h);b.moveElement(g,{x:Math.round(d),y:Math.round(c),scale:h,width:this.map.tileSize.x,height:this.map.tileSize.y});var e=this.levels[g.coord.zoom];e.appendChild(g);g.className="map-tile-loaded";if(Math.round(this.map.coordinate.zoom)==g.coord.zoom){e.style.display="block"}this.requestRedraw()},_redrawTimer:undefined,requestRedraw:function(){if(!this._redrawTimer){this._redrawTimer=setTimeout(this.getRedraw(),1000)}},_redraw:null,getRedraw:function(){if(!this._redraw){var c=this;this._redraw=function(){c.draw();c._redrawTimer=0}}return this._redraw},checkCache:function(){var g=this.parent.getElementsByTagName("img").length;var e=Math.max(g,this.maxTileCacheSize);if(this.tileCacheSize>e){this.recentTiles.sort(function(i,h){return h.lastTouchedTime<i.lastTouchedTime?-1:h.lastTouchedTime>i.lastTouchedTime?1:0})}while(this.tileCacheSize>e){var d=this.recentTiles.pop();var c=new Date().getTime();delete this.recentTilesById[d.id];var f=this.tiles[d.id];if(f.parentNode){alert("Gah: trying to removing cached tile even though it's still in the DOM")}else{delete this.tiles[d.id];this.tileCacheSize--}}},setProvider:function(d){var e=(this.provider===null);if(!e){this.requestManager.clear();for(var c in this.levels){if(this.levels.hasOwnProperty(c)){var f=this.levels[c];while(f.firstChild){this.provider.releaseTile(f.firstChild.coord);f.removeChild(f.firstChild)}}}}this.tiles={};this.tileCacheSize=0;this.maxTileCacheSize=64;this.recentTilesById={};this.recentTiles=[];this.provider=d;if(!e){this.draw()}},getCenterDistanceCompare:function(){var c=this.map.coordinate.zoomTo(Math.round(this.map.coordinate.zoom));return function(f,e){if(f&&e){var h=f.coord;var g=e.coord;if(h.zoom==g.zoom){var d=Math.abs(c.row-h.row-0.5)+Math.abs(c.column-h.column-0.5);var i=Math.abs(c.row-g.row-0.5)+Math.abs(c.column-g.column-0.5);return d<i?1:d>i?-1:0}else{return h.zoom<g.zoom?1:h.zoom>g.zoom?-1:0}}return f?1:e?-1:0}},destroy:function(){this.requestManager.clear();this.requestManager.removeCallback("requestcomplete",this.getTileComplete());this.provider=null;if(this.parent.parentNode){this.parent.parentNode.removeChild(this.parent)}this.map=null}};b.Map=function(g,f,h,k){if(typeof g=="string"){g=document.getElementById(g);if(!g){throw"The ID provided to modest maps could not be found."}}this.parent=g;this.parent.style.padding="0";this.parent.style.overflow="hidden";var c=b.getStyle(this.parent,"position");if(c!="relative"&&c!="absolute"){this.parent.style.position="relative"}this.layers=[];if(!(f instanceof Array)){f=[f]}for(var e=0;e<f.length;e++){this.addLayer(f[e])}this.projection=new b.MercatorProjection(0,b.deriveTransformation(-Math.PI,Math.PI,0,0,Math.PI,Math.PI,1,0,-Math.PI,-Math.PI,0,1));this.tileSize=new b.Point(256,256);this.coordLimits=[new b.Coordinate(0,-Infinity,0),new b.Coordinate(1,Infinity,0).zoomTo(18)];this.coordinate=new b.Coordinate(0.5,0.5,0);if(!h){h=new b.Point(this.parent.offsetWidth,this.parent.offsetHeight);this.autoSize=true;b.addEvent(window,"resize",this.windowResize())}else{this.autoSize=false;this.parent.style.width=Math.round(h.x)+"px";this.parent.style.height=Math.round(h.y)+"px"}this.dimensions=h;this.callbackManager=new b.CallbackManager(this,["zoomed","panned","centered","extentset","resized","drawn"]);if(k===undefined){this.eventHandlers=[new b.MouseHandler(this),new b.TouchHandler(this)]}else{this.eventHandlers=k;if(k instanceof Array){for(var d=0;d<k.length;d++){k[d].init(this)}}}};b.Map.prototype={parent:null,dimensions:null,projection:null,coordinate:null,tileSize:null,coordLimits:null,layers:null,callbackManager:null,eventHandlers:null,autoSize:null,toString:function(){return"Map(#"+this.parent.id+")"},addCallback:function(c,d){this.callbackManager.addCallback(c,d);return this},removeCallback:function(c,d){this.callbackManager.removeCallback(c,d);return this},dispatchCallback:function(d,c){this.callbackManager.dispatchCallback(d,c);return this},windowResize:function(){if(!this._windowResize){var c=this;this._windowResize=function(d){c.dimensions=new b.Point(c.parent.offsetWidth,c.parent.offsetHeight);c.draw();c.dispatchCallback("resized",[c.dimensions])}}return this._windowResize},setZoomRange:function(d,c){this.coordLimits[0]=this.coordLimits[0].zoomTo(d);this.coordLimits[1]=this.coordLimits[1].zoomTo(c)},zoomBy:function(c){this.coordinate=this.enforceLimits(this.coordinate.zoomBy(c));b.getFrame(this.getRedraw());this.dispatchCallback("zoomed",c);return this},zoomIn:function(){return this.zoomBy(1)},zoomOut:function(){return this.zoomBy(-1)},setZoom:function(c){return this.zoomBy(c-this.coordinate.zoom)},zoomByAbout:function(d,c){var f=this.pointLocation(c);this.coordinate=this.enforceLimits(this.coordinate.zoomBy(d));var e=this.locationPoint(f);this.dispatchCallback("zoomed",d);return this.panBy(c.x-e.x,c.y-e.y)},panBy:function(d,c){this.coordinate.column-=d/this.tileSize.x;this.coordinate.row-=c/this.tileSize.y;this.coordinate=this.enforceLimits(this.coordinate);b.getFrame(this.getRedraw());this.dispatchCallback("panned",[d,c]);return this},panLeft:function(){return this.panBy(100,0)},panRight:function(){return this.panBy(-100,0)},panDown:function(){return this.panBy(0,-100)},panUp:function(){return this.panBy(0,100)},setCenter:function(c){return this.setCenterZoom(c,this.coordinate.zoom)},setCenterZoom:function(c,d){this.coordinate=this.projection.locationCoordinate(c).zoomTo(parseFloat(d)||0);b.getFrame(this.getRedraw());this.dispatchCallback("centered",[c,d]);return this},setExtent:function(q,r){if(q instanceof b.MapExtent){q=q.toArray()}var u,k;for(var l=0;l<q.length;l++){var m=this.projection.locationCoordinate(q[l]);if(u){u.row=Math.min(u.row,m.row);u.column=Math.min(u.column,m.column);u.zoom=Math.min(u.zoom,m.zoom);k.row=Math.max(k.row,m.row);k.column=Math.max(k.column,m.column);k.zoom=Math.max(k.zoom,m.zoom)}else{u=m.copy();k=m.copy()}}var j=this.dimensions.x+1;var h=this.dimensions.y+1;var n=(k.column-u.column)/(j/this.tileSize.x);var s=Math.log(n)/Math.log(2);var o=u.zoom-(r?s:Math.ceil(s));var p=(k.row-u.row)/(h/this.tileSize.y);var e=Math.log(p)/Math.log(2);var f=u.zoom-(r?e:Math.ceil(e));var c=Math.min(o,f);c=Math.min(c,this.coordLimits[1].zoom);c=Math.max(c,this.coordLimits[0].zoom);var d=(u.row+k.row)/2;var t=(u.column+k.column)/2;var g=u.zoom;this.coordinate=new b.Coordinate(d,t,g).zoomTo(c);this.draw();this.dispatchCallback("extentset",q);return this},setSize:function(c){this.dimensions=new b.Point(c.x,c.y);this.parent.style.width=Math.round(this.dimensions.x)+"px";this.parent.style.height=Math.round(this.dimensions.y)+"px";if(this.autoSize){b.removeEvent(window,"resize",this.windowResize());this.autoSize=false}this.draw();this.dispatchCallback("resized",this.dimensions);return this},coordinatePoint:function(d){if(d.zoom!=this.coordinate.zoom){d=d.zoomTo(this.coordinate.zoom)}var c=new b.Point(this.dimensions.x/2,this.dimensions.y/2);c.x+=this.tileSize.x*(d.column-this.coordinate.column);c.y+=this.tileSize.y*(d.row-this.coordinate.row);return c},pointCoordinate:function(c){var d=this.coordinate.copy();d.column+=(c.x-this.dimensions.x/2)/this.tileSize.x;d.row+=(c.y-this.dimensions.y/2)/this.tileSize.y;return d},locationCoordinate:function(c){return this.projection.locationCoordinate(c)},coordinateLocation:function(c){return this.projection.coordinateLocation(c)},locationPoint:function(c){return this.coordinatePoint(this.locationCoordinate(c))},pointLocation:function(c){return this.coordinateLocation(this.pointCoordinate(c))},getExtent:function(){var c=[];c.push(this.pointLocation(new b.Point(0,0)));c.push(this.pointLocation(this.dimensions));return c},extent:function(c,d){if(c){return this.setExtent(c,d)}else{return this.getExtent()}},getCenter:function(){return this.projection.coordinateLocation(this.coordinate)},center:function(c){if(c){return this.setCenter(c)}else{return this.getCenter()}},getZoom:function(){return this.coordinate.zoom},zoom:function(c){if(c!==undefined){return this.setZoom(c)}else{return this.getZoom()}},coerceLayer:function(c){if("draw" in c&&typeof c.draw=="function"){return c}else{if(typeof c=="string"){return new b.Layer(new b.TemplatedMapProvider(c))}else{return new b.Layer(c)}}},getLayers:function(){return this.layers.slice()},getLayerAt:function(c){return this.layers[c]},addLayer:function(c){c=this.coerceLayer(c);this.layers.push(c);this.parent.appendChild(c.parent);c.map=this;return this},removeLayer:function(d){for(var c=0;c<this.layers.length;c++){if(d==this.layers[c]){this.removeLayerAt(c);break}}return this},setLayerAt:function(c,d){if(c<0||c>=this.layers.length){throw new Error("invalid index in setLayerAt(): "+c)}d=this.coerceLayer(d);if(this.layers[c]!=d){if(c<this.layers.length){this.layers[c].destroy()}this.layers[c]=d;this.parent.appendChild(d.parent);d.map=this;b.getFrame(this.getRedraw())}return this},insertLayerAt:function(d,e){if(d<0||d>this.layers.length){throw new Error("invalid index in insertLayerAt(): "+d)}e=this.coerceLayer(e);if(d==this.layers.length){this.layers.push(e);this.parent.appendChild(e.parent)}else{var c=this.layers[d];this.parent.insertBefore(e.parent,c.parent);this.layers.splice(d,0,e)}e.map=this;b.getFrame(this.getRedraw());return this},removeLayerAt:function(d){if(d<0||d>=this.layers.length){throw new Error("invalid index in removeLayer(): "+d)}var c=this.layers[d];this.layers.splice(d,1);c.destroy();return this},swapLayersAt:function(d,c){if(d<0||d>=this.layers.length||c<0||c>=this.layers.length){throw new Error("invalid index in swapLayersAt(): "+index)}var g=this.layers[d],e=this.layers[c],f=document.createElement("div");this.parent.replaceChild(f,e.parent);this.parent.replaceChild(e.parent,g.parent);this.parent.replaceChild(g.parent,f);this.layers[d]=e;this.layers[c]=g;return this},enforceZoomLimits:function(f){var d=this.coordLimits;if(d){var e=d[0].zoom;var c=d[1].zoom;if(f.zoom<e){f=f.zoomTo(e)}else{if(f.zoom>c){f=f.zoomTo(c)}}}return f},enforcePanLimits:function(h){var d=this.coordLimits;if(d){h=h.copy();var f=d[0].zoomTo(h.zoom);var c=d[1].zoomTo(h.zoom);var e=this.pointCoordinate(new b.Point(0,0));var g=this.pointCoordinate(this.dimensions);if(c.row-f.row<g.row-e.row){h.row=(c.row+f.row)/2}else{if(e.row<f.row){h.row+=f.row-e.row}else{if(g.row>c.row){h.row-=g.row-c.row}}}if(c.column-f.column<g.column-e.column){h.column=(c.column+f.column)/2}else{if(e.column<f.column){h.column+=f.column-e.column}else{if(g.column>c.column){h.column-=g.column-c.column}}}}return h},enforceLimits:function(c){return this.enforcePanLimits(this.enforceZoomLimits(c))},draw:function(){this.coordinate=this.enforceLimits(this.coordinate);if(this.dimensions.x<=0||this.dimensions.y<=0){if(this.autoSize){var c=this.parent.offsetWidth,e=this.parent.offsetHeight;this.dimensions=new b.Point(c,e);if(c<=0||e<=0){return}}else{return}}for(var d=0;d<this.layers.length;d++){this.layers[d].draw()}this.dispatchCallback("drawn")},_redrawTimer:undefined,requestRedraw:function(){if(!this._redrawTimer){this._redrawTimer=setTimeout(this.getRedraw(),1000)}},_redraw:null,getRedraw:function(){if(!this._redraw){var c=this;this._redraw=function(){c.draw();c._redrawTimer=0}}return this._redraw},destroy:function(){for(var c=0;c<this.layers.length;c++){this.layers[c].destroy()}this.layers=[];this.projection=null;for(var d=0;d<this.eventHandlers.length;d++){this.eventHandlers[d].remove()}if(this.autoSize){b.removeEvent(window,"resize",this.windowResize())}}};if(typeof module!=="undefined"&&module.exports){module.exports={Point:b.Point,Projection:b.Projection,MercatorProjection:b.MercatorProjection,LinearProjection:b.LinearProjection,Transformation:b.Transformation,Location:b.Location,MapProvider:b.MapProvider,TemplatedMapProvider:b.TemplatedMapProvider,Coordinate:b.Coordinate,deriveTransformation:b.deriveTransformation}}})(MM); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* wax - 6.4.0 - v6.0.4-28-g4d63117 */ | |
!function (name, context, definition) { | |
if (typeof module !== 'undefined') module.exports = definition(name, context); | |
else if (typeof define === 'function' && typeof define.amd === 'object') define(definition); | |
else context[name] = definition(name, context); | |
}('bean', this, function (name, context) { | |
var win = window | |
, old = context[name] | |
, overOut = /over|out/ | |
, namespaceRegex = /[^\.]*(?=\..*)\.|.*/ | |
, nameRegex = /\..*/ | |
, addEvent = 'addEventListener' | |
, attachEvent = 'attachEvent' | |
, removeEvent = 'removeEventListener' | |
, detachEvent = 'detachEvent' | |
, doc = document || {} | |
, root = doc.documentElement || {} | |
, W3C_MODEL = root[addEvent] | |
, eventSupport = W3C_MODEL ? addEvent : attachEvent | |
, slice = Array.prototype.slice | |
, mouseTypeRegex = /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i | |
, mouseWheelTypeRegex = /mouse.*(wheel|scroll)/i | |
, textTypeRegex = /^text/i | |
, touchTypeRegex = /^touch|^gesture/i | |
, ONE = { one: 1 } // singleton for quick matching making add() do one() | |
, nativeEvents = (function (hash, events, i) { | |
for (i = 0; i < events.length; i++) | |
hash[events[i]] = 1 | |
return hash | |
})({}, ( | |
'click dblclick mouseup mousedown contextmenu ' + // mouse buttons | |
'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel | |
'mouseover mouseout mousemove selectstart selectend ' + // mouse movement | |
'keydown keypress keyup ' + // keyboard | |
'orientationchange ' + // mobile | |
'focus blur change reset select submit ' + // form elements | |
'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window | |
'error abort scroll ' + // misc | |
(W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event | |
// that doesn't actually exist, so make sure we only do these on newer browsers | |
'show ' + // mouse buttons | |
'input invalid ' + // form elements | |
'touchstart touchmove touchend touchcancel ' + // touch | |
'gesturestart gesturechange gestureend ' + // gesture | |
'message readystatechange pageshow pagehide popstate ' + // window | |
'hashchange offline online ' + // window | |
'afterprint beforeprint ' + // printing | |
'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd | |
'loadstart progress suspend emptied stalled loadmetadata ' + // media | |
'loadeddata canplay canplaythrough playing waiting seeking ' + // media | |
'seeked ended durationchange timeupdate play pause ratechange ' + // media | |
'volumechange cuechange ' + // media | |
'checking noupdate downloading cached updateready obsolete ' + // appcache | |
'' : '') | |
).split(' ') | |
) | |
, customEvents = (function () { | |
function isDescendant(parent, node) { | |
while ((node = node.parentNode) !== null) { | |
if (node === parent) return true | |
} | |
return false | |
} | |
function check(event) { | |
var related = event.relatedTarget | |
if (!related) return related === null | |
return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)) | |
} | |
return { | |
mouseenter: { base: 'mouseover', condition: check } | |
, mouseleave: { base: 'mouseout', condition: check } | |
, mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } | |
} | |
})() | |
, fixEvent = (function () { | |
var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') | |
, mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) | |
, mouseWheelProps = mouseProps.concat('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ axis'.split(' ')) // 'axis' is FF specific | |
, keyProps = commonProps.concat('char charCode key keyCode keyIdentifier keyLocation'.split(' ')) | |
, textProps = commonProps.concat(['data']) | |
, touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' ')) | |
, preventDefault = 'preventDefault' | |
, createPreventDefault = function (event) { | |
return function () { | |
if (event[preventDefault]) | |
event[preventDefault]() | |
else | |
event.returnValue = false | |
} | |
} | |
, stopPropagation = 'stopPropagation' | |
, createStopPropagation = function (event) { | |
return function () { | |
if (event[stopPropagation]) | |
event[stopPropagation]() | |
else | |
event.cancelBubble = true | |
} | |
} | |
, createStop = function (synEvent) { | |
return function () { | |
synEvent[preventDefault]() | |
synEvent[stopPropagation]() | |
synEvent.stopped = true | |
} | |
} | |
, copyProps = function (event, result, props) { | |
var i, p | |
for (i = props.length; i--;) { | |
p = props[i] | |
if (!(p in result) && p in event) result[p] = event[p] | |
} | |
} | |
return function (event, isNative) { | |
var result = { originalEvent: event, isNative: isNative } | |
if (!event) | |
return result | |
var props | |
, type = event.type | |
, target = event.target || event.srcElement | |
result[preventDefault] = createPreventDefault(event) | |
result[stopPropagation] = createStopPropagation(event) | |
result.stop = createStop(result) | |
result.target = target && target.nodeType === 3 ? target.parentNode : target | |
if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive | |
if (type.indexOf('key') !== -1) { | |
props = keyProps | |
result.keyCode = event.which || event.keyCode | |
} else if (mouseTypeRegex.test(type)) { | |
props = mouseProps | |
result.rightClick = event.which === 3 || event.button === 2 | |
result.pos = { x: 0, y: 0 } | |
if (event.pageX || event.pageY) { | |
result.clientX = event.pageX | |
result.clientY = event.pageY | |
} else if (event.clientX || event.clientY) { | |
result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft | |
result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop | |
} | |
if (overOut.test(type)) | |
result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element'] | |
} else if (touchTypeRegex.test(type)) { | |
props = touchProps | |
} else if (mouseWheelTypeRegex.test(type)) { | |
props = mouseWheelProps | |
} else if (textTypeRegex.test(type)) { | |
props = textProps | |
} | |
copyProps(event, result, props || commonProps) | |
} | |
return result | |
} | |
})() | |
// if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both | |
, targetElement = function (element, isNative) { | |
return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element | |
} | |
// we use one of these per listener, of any type | |
, RegEntry = (function () { | |
function entry(element, type, handler, original, namespaces) { | |
this.element = element | |
this.type = type | |
this.handler = handler | |
this.original = original | |
this.namespaces = namespaces | |
this.custom = customEvents[type] | |
this.isNative = nativeEvents[type] && element[eventSupport] | |
this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange' | |
this.customType = !W3C_MODEL && !this.isNative && type | |
this.target = targetElement(element, this.isNative) | |
this.eventSupport = this.target[eventSupport] | |
} | |
entry.prototype = { | |
// given a list of namespaces, is our entry in any of them? | |
inNamespaces: function (checkNamespaces) { | |
var i, j | |
if (!checkNamespaces) | |
return true | |
if (!this.namespaces) | |
return false | |
for (i = checkNamespaces.length; i--;) { | |
for (j = this.namespaces.length; j--;) { | |
if (checkNamespaces[i] === this.namespaces[j]) | |
return true | |
} | |
} | |
return false | |
} | |
// match by element, original fn (opt), handler fn (opt) | |
, matches: function (checkElement, checkOriginal, checkHandler) { | |
return this.element === checkElement && | |
(!checkOriginal || this.original === checkOriginal) && | |
(!checkHandler || this.handler === checkHandler) | |
} | |
} | |
return entry | |
})() | |
, registry = (function () { | |
// our map stores arrays by event type, just because it's better than storing | |
// everything in a single array. uses '$' as a prefix for the keys for safety | |
var map = {} | |
// generic functional search of our registry for matching listeners, | |
// `fn` returns false to break out of the loop | |
, forAll = function (element, type, original, handler, fn) { | |
if (!type || type === '*') { | |
// search the whole registry | |
for (var t in map) { | |
if (t.charAt(0) === '$') | |
forAll(element, t.substr(1), original, handler, fn) | |
} | |
} else { | |
var i = 0, l, list = map['$' + type], all = element === '*' | |
if (!list) | |
return | |
for (l = list.length; i < l; i++) { | |
if (all || list[i].matches(element, original, handler)) | |
if (!fn(list[i], list, i, type)) | |
return | |
} | |
} | |
} | |
, has = function (element, type, original) { | |
// we're not using forAll here simply because it's a bit slower and this | |
// needs to be fast | |
var i, list = map['$' + type] | |
if (list) { | |
for (i = list.length; i--;) { | |
if (list[i].matches(element, original, null)) | |
return true | |
} | |
} | |
return false | |
} | |
, get = function (element, type, original) { | |
var entries = [] | |
forAll(element, type, original, null, function (entry) { return entries.push(entry) }) | |
return entries | |
} | |
, put = function (entry) { | |
(map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) | |
return entry | |
} | |
, del = function (entry) { | |
forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { | |
list.splice(i, 1) | |
if (list.length === 0) | |
delete map['$' + entry.type] | |
return false | |
}) | |
} | |
// dump all entries, used for onunload | |
, entries = function () { | |
var t, entries = [] | |
for (t in map) { | |
if (t.charAt(0) === '$') | |
entries = entries.concat(map[t]) | |
} | |
return entries | |
} | |
return { has: has, get: get, put: put, del: del, entries: entries } | |
})() | |
// add and remove listeners to DOM elements | |
, listener = W3C_MODEL ? function (element, type, fn, add) { | |
element[add ? addEvent : removeEvent](type, fn, false) | |
} : function (element, type, fn, add, custom) { | |
if (custom && add && element['_on' + custom] === null) | |
element['_on' + custom] = 0 | |
element[add ? attachEvent : detachEvent]('on' + type, fn) | |
} | |
, nativeHandler = function (element, fn, args) { | |
return function (event) { | |
event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true) | |
return fn.apply(element, [event].concat(args)) | |
} | |
} | |
, customHandler = function (element, fn, type, condition, args, isNative) { | |
return function (event) { | |
if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { | |
if (event) | |
event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative) | |
fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) | |
} | |
} | |
} | |
, once = function (rm, element, type, fn, originalFn) { | |
// wrap the handler in a handler that does a remove as well | |
return function () { | |
rm(element, type, originalFn) | |
fn.apply(this, arguments) | |
} | |
} | |
, removeListener = function (element, orgType, handler, namespaces) { | |
var i, l, entry | |
, type = (orgType && orgType.replace(nameRegex, '')) | |
, handlers = registry.get(element, type, handler) | |
for (i = 0, l = handlers.length; i < l; i++) { | |
if (handlers[i].inNamespaces(namespaces)) { | |
if ((entry = handlers[i]).eventSupport) | |
listener(entry.target, entry.eventType, entry.handler, false, entry.type) | |
// TODO: this is problematic, we have a registry.get() and registry.del() that | |
// both do registry searches so we waste cycles doing this. Needs to be rolled into | |
// a single registry.forAll(fn) that removes while finding, but the catch is that | |
// we'll be splicing the arrays that we're iterating over. Needs extra tests to | |
// make sure we don't screw it up. @rvagg | |
registry.del(entry) | |
} | |
} | |
} | |
, addListener = function (element, orgType, fn, originalFn, args) { | |
var entry | |
, type = orgType.replace(nameRegex, '') | |
, namespaces = orgType.replace(namespaceRegex, '').split('.') | |
if (registry.has(element, type, fn)) | |
return element // no dupe | |
if (type === 'unload') | |
fn = once(removeListener, element, type, fn, originalFn) // self clean-up | |
if (customEvents[type]) { | |
if (customEvents[type].condition) | |
fn = customHandler(element, fn, type, customEvents[type].condition, true) | |
type = customEvents[type].base || type | |
} | |
entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)) | |
entry.handler = entry.isNative ? | |
nativeHandler(element, entry.handler, args) : | |
customHandler(element, entry.handler, type, false, args, false) | |
if (entry.eventSupport) | |
listener(entry.target, entry.eventType, entry.handler, true, entry.customType) | |
} | |
, del = function (selector, fn, $) { | |
return function (e) { | |
var target, i, array = typeof selector === 'string' ? $(selector, this) : selector | |
for (target = e.target; target && target !== this; target = target.parentNode) { | |
for (i = array.length; i--;) { | |
if (array[i] === target) { | |
return fn.apply(target, arguments) | |
} | |
} | |
} | |
} | |
} | |
, remove = function (element, typeSpec, fn) { | |
var k, m, type, namespaces, i | |
, rm = removeListener | |
, isString = typeSpec && typeof typeSpec === 'string' | |
if (isString && typeSpec.indexOf(' ') > 0) { | |
// remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') | |
typeSpec = typeSpec.split(' ') | |
for (i = typeSpec.length; i--;) | |
remove(element, typeSpec[i], fn) | |
return element | |
} | |
type = isString && typeSpec.replace(nameRegex, '') | |
if (type && customEvents[type]) | |
type = customEvents[type].type | |
if (!typeSpec || isString) { | |
// remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) | |
if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) | |
namespaces = namespaces.split('.') | |
rm(element, type, fn, namespaces) | |
} else if (typeof typeSpec === 'function') { | |
// remove(el, fn) | |
rm(element, null, typeSpec) | |
} else { | |
// remove(el, { t1: fn1, t2, fn2 }) | |
for (k in typeSpec) { | |
if (typeSpec.hasOwnProperty(k)) | |
remove(element, k, typeSpec[k]) | |
} | |
} | |
return element | |
} | |
, add = function (element, events, fn, delfn, $) { | |
var type, types, i, args | |
, originalFn = fn | |
, isDel = fn && typeof fn === 'string' | |
if (events && !fn && typeof events === 'object') { | |
for (type in events) { | |
if (events.hasOwnProperty(type)) | |
add.apply(this, [ element, type, events[type] ]) | |
} | |
} else { | |
args = arguments.length > 3 ? slice.call(arguments, 3) : [] | |
types = (isDel ? fn : events).split(' ') | |
isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)) | |
// special case for one() | |
this === ONE && (fn = once(remove, element, events, fn, originalFn)) | |
for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) | |
} | |
return element | |
} | |
, one = function () { | |
return add.apply(ONE, arguments) | |
} | |
, fireListener = W3C_MODEL ? function (isNative, type, element) { | |
var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') | |
evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) | |
element.dispatchEvent(evt) | |
} : function (isNative, type, element) { | |
element = targetElement(element, isNative) | |
// if not-native then we're using onpropertychange so we just increment a custom property | |
isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ | |
} | |
, fire = function (element, type, args) { | |
var i, j, l, names, handlers | |
, types = type.split(' ') | |
for (i = types.length; i--;) { | |
type = types[i].replace(nameRegex, '') | |
if (names = types[i].replace(namespaceRegex, '')) | |
names = names.split('.') | |
if (!names && !args && element[eventSupport]) { | |
fireListener(nativeEvents[type], type, element) | |
} else { | |
// non-native event, either because of a namespace, arguments or a non DOM element | |
// iterate over all listeners and manually 'fire' | |
handlers = registry.get(element, type) | |
args = [false].concat(args) | |
for (j = 0, l = handlers.length; j < l; j++) { | |
if (handlers[j].inNamespaces(names)) | |
handlers[j].handler.apply(element, args) | |
} | |
} | |
} | |
return element | |
} | |
, clone = function (element, from, type) { | |
var i = 0 | |
, handlers = registry.get(from, type) | |
, l = handlers.length | |
for (;i < l; i++) | |
handlers[i].original && add(element, handlers[i].type, handlers[i].original) | |
return element | |
} | |
, bean = { | |
add: add | |
, one: one | |
, remove: remove | |
, clone: clone | |
, fire: fire | |
, noConflict: function () { | |
context[name] = old | |
return this | |
} | |
} | |
if (win[attachEvent]) { | |
// for IE, clean up on unload to avoid leaks | |
var cleanup = function () { | |
var i, entries = registry.entries() | |
for (i in entries) { | |
if (entries[i].type && entries[i].type !== 'unload') | |
remove(entries[i].element, entries[i].type) | |
} | |
win[detachEvent]('onunload', cleanup) | |
win.CollectGarbage && win.CollectGarbage() | |
} | |
win[attachEvent]('onunload', cleanup) | |
} | |
return bean | |
}) | |
// Copyright Google Inc. | |
// Licensed under the Apache Licence Version 2.0 | |
// Autogenerated at Tue Oct 11 13:36:46 EDT 2011 | |
// @provides html4 | |
var html4 = {}; | |
html4.atype = { | |
NONE: 0, | |
URI: 1, | |
URI_FRAGMENT: 11, | |
SCRIPT: 2, | |
STYLE: 3, | |
ID: 4, | |
IDREF: 5, | |
IDREFS: 6, | |
GLOBAL_NAME: 7, | |
LOCAL_NAME: 8, | |
CLASSES: 9, | |
FRAME_TARGET: 10 | |
}; | |
html4.ATTRIBS = { | |
'*::class': 9, | |
'*::dir': 0, | |
'*::id': 4, | |
'*::lang': 0, | |
'*::onclick': 2, | |
'*::ondblclick': 2, | |
'*::onkeydown': 2, | |
'*::onkeypress': 2, | |
'*::onkeyup': 2, | |
'*::onload': 2, | |
'*::onmousedown': 2, | |
'*::onmousemove': 2, | |
'*::onmouseout': 2, | |
'*::onmouseover': 2, | |
'*::onmouseup': 2, | |
'*::style': 3, | |
'*::title': 0, | |
'a::accesskey': 0, | |
'a::coords': 0, | |
'a::href': 1, | |
'a::hreflang': 0, | |
'a::name': 7, | |
'a::onblur': 2, | |
'a::onfocus': 2, | |
'a::rel': 0, | |
'a::rev': 0, | |
'a::shape': 0, | |
'a::tabindex': 0, | |
'a::target': 10, | |
'a::type': 0, | |
'area::accesskey': 0, | |
'area::alt': 0, | |
'area::coords': 0, | |
'area::href': 1, | |
'area::nohref': 0, | |
'area::onblur': 2, | |
'area::onfocus': 2, | |
'area::shape': 0, | |
'area::tabindex': 0, | |
'area::target': 10, | |
'bdo::dir': 0, | |
'blockquote::cite': 1, | |
'br::clear': 0, | |
'button::accesskey': 0, | |
'button::disabled': 0, | |
'button::name': 8, | |
'button::onblur': 2, | |
'button::onfocus': 2, | |
'button::tabindex': 0, | |
'button::type': 0, | |
'button::value': 0, | |
'canvas::height': 0, | |
'canvas::width': 0, | |
'caption::align': 0, | |
'col::align': 0, | |
'col::char': 0, | |
'col::charoff': 0, | |
'col::span': 0, | |
'col::valign': 0, | |
'col::width': 0, | |
'colgroup::align': 0, | |
'colgroup::char': 0, | |
'colgroup::charoff': 0, | |
'colgroup::span': 0, | |
'colgroup::valign': 0, | |
'colgroup::width': 0, | |
'del::cite': 1, | |
'del::datetime': 0, | |
'dir::compact': 0, | |
'div::align': 0, | |
'dl::compact': 0, | |
'font::color': 0, | |
'font::face': 0, | |
'font::size': 0, | |
'form::accept': 0, | |
'form::action': 1, | |
'form::autocomplete': 0, | |
'form::enctype': 0, | |
'form::method': 0, | |
'form::name': 7, | |
'form::onreset': 2, | |
'form::onsubmit': 2, | |
'form::target': 10, | |
'h1::align': 0, | |
'h2::align': 0, | |
'h3::align': 0, | |
'h4::align': 0, | |
'h5::align': 0, | |
'h6::align': 0, | |
'hr::align': 0, | |
'hr::noshade': 0, | |
'hr::size': 0, | |
'hr::width': 0, | |
'iframe::align': 0, | |
'iframe::frameborder': 0, | |
'iframe::height': 0, | |
'iframe::marginheight': 0, | |
'iframe::marginwidth': 0, | |
'iframe::width': 0, | |
'img::align': 0, | |
'img::alt': 0, | |
'img::border': 0, | |
'img::height': 0, | |
'img::hspace': 0, | |
'img::ismap': 0, | |
'img::name': 7, | |
'img::src': 1, | |
'img::usemap': 11, | |
'img::vspace': 0, | |
'img::width': 0, | |
'input::accept': 0, | |
'input::accesskey': 0, | |
'input::align': 0, | |
'input::alt': 0, | |
'input::autocomplete': 0, | |
'input::checked': 0, | |
'input::disabled': 0, | |
'input::ismap': 0, | |
'input::maxlength': 0, | |
'input::name': 8, | |
'input::onblur': 2, | |
'input::onchange': 2, | |
'input::onfocus': 2, | |
'input::onselect': 2, | |
'input::readonly': 0, | |
'input::size': 0, | |
'input::src': 1, | |
'input::tabindex': 0, | |
'input::type': 0, | |
'input::usemap': 11, | |
'input::value': 0, | |
'ins::cite': 1, | |
'ins::datetime': 0, | |
'label::accesskey': 0, | |
'label::for': 5, | |
'label::onblur': 2, | |
'label::onfocus': 2, | |
'legend::accesskey': 0, | |
'legend::align': 0, | |
'li::type': 0, | |
'li::value': 0, | |
'map::name': 7, | |
'menu::compact': 0, | |
'ol::compact': 0, | |
'ol::start': 0, | |
'ol::type': 0, | |
'optgroup::disabled': 0, | |
'optgroup::label': 0, | |
'option::disabled': 0, | |
'option::label': 0, | |
'option::selected': 0, | |
'option::value': 0, | |
'p::align': 0, | |
'pre::width': 0, | |
'q::cite': 1, | |
'select::disabled': 0, | |
'select::multiple': 0, | |
'select::name': 8, | |
'select::onblur': 2, | |
'select::onchange': 2, | |
'select::onfocus': 2, | |
'select::size': 0, | |
'select::tabindex': 0, | |
'table::align': 0, | |
'table::bgcolor': 0, | |
'table::border': 0, | |
'table::cellpadding': 0, | |
'table::cellspacing': 0, | |
'table::frame': 0, | |
'table::rules': 0, | |
'table::summary': 0, | |
'table::width': 0, | |
'tbody::align': 0, | |
'tbody::char': 0, | |
'tbody::charoff': 0, | |
'tbody::valign': 0, | |
'td::abbr': 0, | |
'td::align': 0, | |
'td::axis': 0, | |
'td::bgcolor': 0, | |
'td::char': 0, | |
'td::charoff': 0, | |
'td::colspan': 0, | |
'td::headers': 6, | |
'td::height': 0, | |
'td::nowrap': 0, | |
'td::rowspan': 0, | |
'td::scope': 0, | |
'td::valign': 0, | |
'td::width': 0, | |
'textarea::accesskey': 0, | |
'textarea::cols': 0, | |
'textarea::disabled': 0, | |
'textarea::name': 8, | |
'textarea::onblur': 2, | |
'textarea::onchange': 2, | |
'textarea::onfocus': 2, | |
'textarea::onselect': 2, | |
'textarea::readonly': 0, | |
'textarea::rows': 0, | |
'textarea::tabindex': 0, | |
'tfoot::align': 0, | |
'tfoot::char': 0, | |
'tfoot::charoff': 0, | |
'tfoot::valign': 0, | |
'th::abbr': 0, | |
'th::align': 0, | |
'th::axis': 0, | |
'th::bgcolor': 0, | |
'th::char': 0, | |
'th::charoff': 0, | |
'th::colspan': 0, | |
'th::headers': 6, | |
'th::height': 0, | |
'th::nowrap': 0, | |
'th::rowspan': 0, | |
'th::scope': 0, | |
'th::valign': 0, | |
'th::width': 0, | |
'thead::align': 0, | |
'thead::char': 0, | |
'thead::charoff': 0, | |
'thead::valign': 0, | |
'tr::align': 0, | |
'tr::bgcolor': 0, | |
'tr::char': 0, | |
'tr::charoff': 0, | |
'tr::valign': 0, | |
'ul::compact': 0, | |
'ul::type': 0 | |
}; | |
html4.eflags = { | |
OPTIONAL_ENDTAG: 1, | |
EMPTY: 2, | |
CDATA: 4, | |
RCDATA: 8, | |
UNSAFE: 16, | |
FOLDABLE: 32, | |
SCRIPT: 64, | |
STYLE: 128 | |
}; | |
html4.ELEMENTS = { | |
'a': 0, | |
'abbr': 0, | |
'acronym': 0, | |
'address': 0, | |
'applet': 16, | |
'area': 2, | |
'b': 0, | |
'base': 18, | |
'basefont': 18, | |
'bdo': 0, | |
'big': 0, | |
'blockquote': 0, | |
'body': 49, | |
'br': 2, | |
'button': 0, | |
'canvas': 0, | |
'caption': 0, | |
'center': 0, | |
'cite': 0, | |
'code': 0, | |
'col': 2, | |
'colgroup': 1, | |
'dd': 1, | |
'del': 0, | |
'dfn': 0, | |
'dir': 0, | |
'div': 0, | |
'dl': 0, | |
'dt': 1, | |
'em': 0, | |
'fieldset': 0, | |
'font': 0, | |
'form': 0, | |
'frame': 18, | |
'frameset': 16, | |
'h1': 0, | |
'h2': 0, | |
'h3': 0, | |
'h4': 0, | |
'h5': 0, | |
'h6': 0, | |
'head': 49, | |
'hr': 2, | |
'html': 49, | |
'i': 0, | |
'iframe': 4, | |
'img': 2, | |
'input': 2, | |
'ins': 0, | |
'isindex': 18, | |
'kbd': 0, | |
'label': 0, | |
'legend': 0, | |
'li': 1, | |
'link': 18, | |
'map': 0, | |
'menu': 0, | |
'meta': 18, | |
'nobr': 0, | |
'noembed': 4, | |
'noframes': 20, | |
'noscript': 20, | |
'object': 16, | |
'ol': 0, | |
'optgroup': 0, | |
'option': 1, | |
'p': 1, | |
'param': 18, | |
'pre': 0, | |
'q': 0, | |
's': 0, | |
'samp': 0, | |
'script': 84, | |
'select': 0, | |
'small': 0, | |
'span': 0, | |
'strike': 0, | |
'strong': 0, | |
'style': 148, | |
'sub': 0, | |
'sup': 0, | |
'table': 0, | |
'tbody': 1, | |
'td': 1, | |
'textarea': 8, | |
'tfoot': 1, | |
'th': 1, | |
'thead': 1, | |
'title': 24, | |
'tr': 1, | |
'tt': 0, | |
'u': 0, | |
'ul': 0, | |
'var': 0 | |
}; | |
html4.ueffects = { | |
NOT_LOADED: 0, | |
SAME_DOCUMENT: 1, | |
NEW_DOCUMENT: 2 | |
}; | |
html4.URIEFFECTS = { | |
'a::href': 2, | |
'area::href': 2, | |
'blockquote::cite': 0, | |
'body::background': 1, | |
'del::cite': 0, | |
'form::action': 2, | |
'img::src': 1, | |
'input::src': 1, | |
'ins::cite': 0, | |
'q::cite': 0 | |
}; | |
html4.ltypes = { | |
UNSANDBOXED: 2, | |
SANDBOXED: 1, | |
DATA: 0 | |
}; | |
html4.LOADERTYPES = { | |
'a::href': 2, | |
'area::href': 2, | |
'blockquote::cite': 2, | |
'body::background': 1, | |
'del::cite': 2, | |
'form::action': 2, | |
'img::src': 1, | |
'input::src': 1, | |
'ins::cite': 2, | |
'q::cite': 2 | |
};; | |
// Copyright (C) 2006 Google Inc. | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
/** | |
* @fileoverview | |
* An HTML sanitizer that can satisfy a variety of security policies. | |
* | |
* <p> | |
* The HTML sanitizer is built around a SAX parser and HTML element and | |
* attributes schemas. | |
* | |
* @author [email protected] | |
* @requires html4 | |
* @overrides window | |
* @provides html, html_sanitize | |
*/ | |
/** | |
* @namespace | |
*/ | |
var html = (function (html4) { | |
var lcase; | |
// The below may not be true on browsers in the Turkish locale. | |
if ('script' === 'SCRIPT'.toLowerCase()) { | |
lcase = function (s) { return s.toLowerCase(); }; | |
} else { | |
/** | |
* {@updoc | |
* $ lcase('SCRIPT') | |
* # 'script' | |
* $ lcase('script') | |
* # 'script' | |
* } | |
*/ | |
lcase = function (s) { | |
return s.replace( | |
/[A-Z]/g, | |
function (ch) { | |
return String.fromCharCode(ch.charCodeAt(0) | 32); | |
}); | |
}; | |
} | |
var ENTITIES = { | |
lt : '<', | |
gt : '>', | |
amp : '&', | |
nbsp : '\240', | |
quot : '"', | |
apos : '\'' | |
}; | |
// Schemes on which to defer to uripolicy. Urls with other schemes are denied | |
var WHITELISTED_SCHEMES = /^(?:https?|mailto|data)$/i; | |
var decimalEscapeRe = /^#(\d+)$/; | |
var hexEscapeRe = /^#x([0-9A-Fa-f]+)$/; | |
/** | |
* Decodes an HTML entity. | |
* | |
* {@updoc | |
* $ lookupEntity('lt') | |
* # '<' | |
* $ lookupEntity('GT') | |
* # '>' | |
* $ lookupEntity('amp') | |
* # '&' | |
* $ lookupEntity('nbsp') | |
* # '\xA0' | |
* $ lookupEntity('apos') | |
* # "'" | |
* $ lookupEntity('quot') | |
* # '"' | |
* $ lookupEntity('#xa') | |
* # '\n' | |
* $ lookupEntity('#10') | |
* # '\n' | |
* $ lookupEntity('#x0a') | |
* # '\n' | |
* $ lookupEntity('#010') | |
* # '\n' | |
* $ lookupEntity('#x00A') | |
* # '\n' | |
* $ lookupEntity('Pi') // Known failure | |
* # '\u03A0' | |
* $ lookupEntity('pi') // Known failure | |
* # '\u03C0' | |
* } | |
* | |
* @param name the content between the '&' and the ';'. | |
* @return a single unicode code-point as a string. | |
*/ | |
function lookupEntity(name) { | |
name = lcase(name); // TODO: π is different from Π | |
if (ENTITIES.hasOwnProperty(name)) { return ENTITIES[name]; } | |
var m = name.match(decimalEscapeRe); | |
if (m) { | |
return String.fromCharCode(parseInt(m[1], 10)); | |
} else if (!!(m = name.match(hexEscapeRe))) { | |
return String.fromCharCode(parseInt(m[1], 16)); | |
} | |
return ''; | |
} | |
function decodeOneEntity(_, name) { | |
return lookupEntity(name); | |
} | |
var nulRe = /\0/g; | |
function stripNULs(s) { | |
return s.replace(nulRe, ''); | |
} | |
var entityRe = /&(#\d+|#x[0-9A-Fa-f]+|\w+);/g; | |
/** | |
* The plain text of a chunk of HTML CDATA which possibly containing. | |
* | |
* {@updoc | |
* $ unescapeEntities('') | |
* # '' | |
* $ unescapeEntities('hello World!') | |
* # 'hello World!' | |
* $ unescapeEntities('1 < 2 && 4 > 3 ') | |
* # '1 < 2 && 4 > 3\n' | |
* $ unescapeEntities('<< <- unfinished entity>') | |
* # '<< <- unfinished entity>' | |
* $ unescapeEntities('/foo?bar=baz©=true') // & often unescaped in URLS | |
* # '/foo?bar=baz©=true' | |
* $ unescapeEntities('pi=ππ, Pi=Π\u03A0') // FIXME: known failure | |
* # 'pi=\u03C0\u03c0, Pi=\u03A0\u03A0' | |
* } | |
* | |
* @param s a chunk of HTML CDATA. It must not start or end inside an HTML | |
* entity. | |
*/ | |
function unescapeEntities(s) { | |
return s.replace(entityRe, decodeOneEntity); | |
} | |
var ampRe = /&/g; | |
var looseAmpRe = /&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi; | |
var ltRe = /</g; | |
var gtRe = />/g; | |
var quotRe = /\"/g; | |
var eqRe = /\=/g; // Backslash required on JScript.net | |
/** | |
* Escapes HTML special characters in attribute values as HTML entities. | |
* | |
* {@updoc | |
* $ escapeAttrib('') | |
* # '' | |
* $ escapeAttrib('"<<&==&>>"') // Do not just escape the first occurrence. | |
* # '"<<&==&>>"' | |
* $ escapeAttrib('Hello <World>!') | |
* # 'Hello <World>!' | |
* } | |
*/ | |
function escapeAttrib(s) { | |
// Escaping '=' defangs many UTF-7 and SGML short-tag attacks. | |
return s.replace(ampRe, '&').replace(ltRe, '<').replace(gtRe, '>') | |
.replace(quotRe, '"').replace(eqRe, '='); | |
} | |
/** | |
* Escape entities in RCDATA that can be escaped without changing the meaning. | |
* {@updoc | |
* $ normalizeRCData('1 < 2 && 3 > 4 && 5 < 7&8') | |
* # '1 < 2 && 3 > 4 && 5 < 7&8' | |
* } | |
*/ | |
function normalizeRCData(rcdata) { | |
return rcdata | |
.replace(looseAmpRe, '&$1') | |
.replace(ltRe, '<') | |
.replace(gtRe, '>'); | |
} | |
// TODO(mikesamuel): validate sanitizer regexs against the HTML5 grammar at | |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html | |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html | |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html | |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html | |
/** token definitions. */ | |
var INSIDE_TAG_TOKEN = new RegExp( | |
// Don't capture space. | |
'^\\s*(?:' | |
// Capture an attribute name in group 1, and value in group 3. | |
// We capture the fact that there was an attribute in group 2, since | |
// interpreters are inconsistent in whether a group that matches nothing | |
// is null, undefined, or the empty string. | |
+ ('(?:' | |
+ '([a-z][a-z-]*)' // attribute name | |
+ ('(' // optionally followed | |
+ '\\s*=\\s*' | |
+ ('(' | |
// A double quoted string. | |
+ '\"[^\"]*\"' | |
// A single quoted string. | |
+ '|\'[^\']*\'' | |
// The positive lookahead is used to make sure that in | |
// <foo bar= baz=boo>, the value for bar is blank, not "baz=boo". | |
+ '|(?=[a-z][a-z-]*\\s*=)' | |
// An unquoted value that is not an attribute name. | |
// We know it is not an attribute name because the previous | |
// zero-width match would've eliminated that possibility. | |
+ '|[^>\"\'\\s]*' | |
+ ')' | |
) | |
+ ')' | |
) + '?' | |
+ ')' | |
) | |
// End of tag captured in group 3. | |
+ '|(\/?>)' | |
// Don't capture cruft | |
+ '|[\\s\\S][^a-z\\s>]*)', | |
'i'); | |
var OUTSIDE_TAG_TOKEN = new RegExp( | |
'^(?:' | |
// Entity captured in group 1. | |
+ '&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);' | |
// Comment, doctypes, and processing instructions not captured. | |
+ '|<\!--[\\s\\S]*?--\>|<!\\w[^>]*>|<\\?[^>*]*>' | |
// '/' captured in group 2 for close tags, and name captured in group 3. | |
+ '|<(\/)?([a-z][a-z0-9]*)' | |
// Text captured in group 4. | |
+ '|([^<&>]+)' | |
// Cruft captured in group 5. | |
+ '|([<&>]))', | |
'i'); | |
/** | |
* Given a SAX-like event handler, produce a function that feeds those | |
* events and a parameter to the event handler. | |
* | |
* The event handler has the form:{@code | |
* { | |
* // Name is an upper-case HTML tag name. Attribs is an array of | |
* // alternating upper-case attribute names, and attribute values. The | |
* // attribs array is reused by the parser. Param is the value passed to | |
* // the saxParser. | |
* startTag: function (name, attribs, param) { ... }, | |
* endTag: function (name, param) { ... }, | |
* pcdata: function (text, param) { ... }, | |
* rcdata: function (text, param) { ... }, | |
* cdata: function (text, param) { ... }, | |
* startDoc: function (param) { ... }, | |
* endDoc: function (param) { ... } | |
* }} | |
* | |
* @param {Object} handler a record containing event handlers. | |
* @return {Function} that takes a chunk of html and a parameter. | |
* The parameter is passed on to the handler methods. | |
*/ | |
function makeSaxParser(handler) { | |
return function parse(htmlText, param) { | |
htmlText = String(htmlText); | |
var htmlLower = null; | |
var inTag = false; // True iff we're currently processing a tag. | |
var attribs = []; // Accumulates attribute names and values. | |
var tagName = void 0; // The name of the tag currently being processed. | |
var eflags = void 0; // The element flags for the current tag. | |
var openTag = void 0; // True if the current tag is an open tag. | |
if (handler.startDoc) { handler.startDoc(param); } | |
while (htmlText) { | |
var m = htmlText.match(inTag ? INSIDE_TAG_TOKEN : OUTSIDE_TAG_TOKEN); | |
htmlText = htmlText.substring(m[0].length); | |
if (inTag) { | |
if (m[1]) { // attribute | |
// setAttribute with uppercase names doesn't work on IE6. | |
var attribName = lcase(m[1]); | |
var decodedValue; | |
if (m[2]) { | |
var encodedValue = m[3]; | |
switch (encodedValue.charCodeAt(0)) { // Strip quotes | |
case 34: case 39: | |
encodedValue = encodedValue.substring( | |
1, encodedValue.length - 1); | |
break; | |
} | |
decodedValue = unescapeEntities(stripNULs(encodedValue)); | |
} else { | |
// Use name as value for valueless attribs, so | |
// <input type=checkbox checked> | |
// gets attributes ['type', 'checkbox', 'checked', 'checked'] | |
decodedValue = attribName; | |
} | |
attribs.push(attribName, decodedValue); | |
} else if (m[4]) { | |
if (eflags !== void 0) { // False if not in whitelist. | |
if (openTag) { | |
if (handler.startTag) { | |
handler.startTag(tagName, attribs, param); | |
} | |
} else { | |
if (handler.endTag) { | |
handler.endTag(tagName, param); | |
} | |
} | |
} | |
if (openTag | |
&& (eflags & (html4.eflags.CDATA | html4.eflags.RCDATA))) { | |
if (htmlLower === null) { | |
htmlLower = lcase(htmlText); | |
} else { | |
htmlLower = htmlLower.substring( | |
htmlLower.length - htmlText.length); | |
} | |
var dataEnd = htmlLower.indexOf('</' + tagName); | |
if (dataEnd < 0) { dataEnd = htmlText.length; } | |
if (dataEnd) { | |
if (eflags & html4.eflags.CDATA) { | |
if (handler.cdata) { | |
handler.cdata(htmlText.substring(0, dataEnd), param); | |
} | |
} else if (handler.rcdata) { | |
handler.rcdata( | |
normalizeRCData(htmlText.substring(0, dataEnd)), param); | |
} | |
htmlText = htmlText.substring(dataEnd); | |
} | |
} | |
tagName = eflags = openTag = void 0; | |
attribs.length = 0; | |
inTag = false; | |
} | |
} else { | |
if (m[1]) { // Entity | |
if (handler.pcdata) { handler.pcdata(m[0], param); } | |
} else if (m[3]) { // Tag | |
openTag = !m[2]; | |
inTag = true; | |
tagName = lcase(m[3]); | |
eflags = html4.ELEMENTS.hasOwnProperty(tagName) | |
? html4.ELEMENTS[tagName] : void 0; | |
} else if (m[4]) { // Text | |
if (handler.pcdata) { handler.pcdata(m[4], param); } | |
} else if (m[5]) { // Cruft | |
if (handler.pcdata) { | |
var ch = m[5]; | |
handler.pcdata( | |
ch === '<' ? '<' : ch === '>' ? '>' : '&', | |
param); | |
} | |
} | |
} | |
} | |
if (handler.endDoc) { handler.endDoc(param); } | |
}; | |
} | |
/** | |
* Returns a function that strips unsafe tags and attributes from html. | |
* @param {Function} sanitizeAttributes | |
* maps from (tagName, attribs[]) to null or a sanitized attribute array. | |
* The attribs array can be arbitrarily modified, but the same array | |
* instance is reused, so should not be held. | |
* @return {Function} from html to sanitized html | |
*/ | |
function makeHtmlSanitizer(sanitizeAttributes) { | |
var stack; | |
var ignoring; | |
return makeSaxParser({ | |
startDoc: function (_) { | |
stack = []; | |
ignoring = false; | |
}, | |
startTag: function (tagName, attribs, out) { | |
if (ignoring) { return; } | |
if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; } | |
var eflags = html4.ELEMENTS[tagName]; | |
if (eflags & html4.eflags.FOLDABLE) { | |
return; | |
} else if (eflags & html4.eflags.UNSAFE) { | |
ignoring = !(eflags & html4.eflags.EMPTY); | |
return; | |
} | |
attribs = sanitizeAttributes(tagName, attribs); | |
// TODO(mikesamuel): relying on sanitizeAttributes not to | |
// insert unsafe attribute names. | |
if (attribs) { | |
if (!(eflags & html4.eflags.EMPTY)) { | |
stack.push(tagName); | |
} | |
out.push('<', tagName); | |
for (var i = 0, n = attribs.length; i < n; i += 2) { | |
var attribName = attribs[i], | |
value = attribs[i + 1]; | |
if (value !== null && value !== void 0) { | |
out.push(' ', attribName, '="', escapeAttrib(value), '"'); | |
} | |
} | |
out.push('>'); | |
} | |
}, | |
endTag: function (tagName, out) { | |
if (ignoring) { | |
ignoring = false; | |
return; | |
} | |
if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; } | |
var eflags = html4.ELEMENTS[tagName]; | |
if (!(eflags & (html4.eflags.UNSAFE | html4.eflags.EMPTY | |
| html4.eflags.FOLDABLE))) { | |
var index; | |
if (eflags & html4.eflags.OPTIONAL_ENDTAG) { | |
for (index = stack.length; --index >= 0;) { | |
var stackEl = stack[index]; | |
if (stackEl === tagName) { break; } | |
if (!(html4.ELEMENTS[stackEl] | |
& html4.eflags.OPTIONAL_ENDTAG)) { | |
// Don't pop non optional end tags looking for a match. | |
return; | |
} | |
} | |
} else { | |
for (index = stack.length; --index >= 0;) { | |
if (stack[index] === tagName) { break; } | |
} | |
} | |
if (index < 0) { return; } // Not opened. | |
for (var i = stack.length; --i > index;) { | |
var stackEl = stack[i]; | |
if (!(html4.ELEMENTS[stackEl] | |
& html4.eflags.OPTIONAL_ENDTAG)) { | |
out.push('</', stackEl, '>'); | |
} | |
} | |
stack.length = index; | |
out.push('</', tagName, '>'); | |
} | |
}, | |
pcdata: function (text, out) { | |
if (!ignoring) { out.push(text); } | |
}, | |
rcdata: function (text, out) { | |
if (!ignoring) { out.push(text); } | |
}, | |
cdata: function (text, out) { | |
if (!ignoring) { out.push(text); } | |
}, | |
endDoc: function (out) { | |
for (var i = stack.length; --i >= 0;) { | |
out.push('</', stack[i], '>'); | |
} | |
stack.length = 0; | |
} | |
}); | |
} | |
// From RFC3986 | |
var URI_SCHEME_RE = new RegExp( | |
"^" + | |
"(?:" + | |
"([^:\/?#]+)" + // scheme | |
":)?" | |
); | |
/** | |
* Strips unsafe tags and attributes from html. | |
* @param {string} htmlText to sanitize | |
* @param {Function} opt_uriPolicy -- a transform to apply to uri/url | |
* attribute values. If no opt_uriPolicy is provided, no uris | |
* are allowed ie. the default uriPolicy rewrites all uris to null | |
* @param {Function} opt_nmTokenPolicy : string -> string? -- a transform to | |
* apply to names, ids, and classes. If no opt_nmTokenPolicy is provided, | |
* all names, ids and classes are passed through ie. the default | |
* nmTokenPolicy is an identity transform | |
* @return {string} html | |
*/ | |
function sanitize(htmlText, opt_uriPolicy, opt_nmTokenPolicy) { | |
var out = []; | |
makeHtmlSanitizer( | |
function sanitizeAttribs(tagName, attribs) { | |
for (var i = 0; i < attribs.length; i += 2) { | |
var attribName = attribs[i]; | |
var value = attribs[i + 1]; | |
var atype = null, attribKey; | |
if ((attribKey = tagName + '::' + attribName, | |
html4.ATTRIBS.hasOwnProperty(attribKey)) | |
|| (attribKey = '*::' + attribName, | |
html4.ATTRIBS.hasOwnProperty(attribKey))) { | |
atype = html4.ATTRIBS[attribKey]; | |
} | |
if (atype !== null) { | |
switch (atype) { | |
case html4.atype.NONE: break; | |
case html4.atype.SCRIPT: | |
case html4.atype.STYLE: | |
value = null; | |
break; | |
case html4.atype.ID: | |
case html4.atype.IDREF: | |
case html4.atype.IDREFS: | |
case html4.atype.GLOBAL_NAME: | |
case html4.atype.LOCAL_NAME: | |
case html4.atype.CLASSES: | |
value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value; | |
break; | |
case html4.atype.URI: | |
var parsedUri = ('' + value).match(URI_SCHEME_RE); | |
if (!parsedUri) { | |
value = null; | |
} else if (!parsedUri[1] || | |
WHITELISTED_SCHEMES.test(parsedUri[1])) { | |
value = opt_uriPolicy && opt_uriPolicy(value); | |
} else { | |
value = null; | |
} | |
break; | |
case html4.atype.URI_FRAGMENT: | |
if (value && '#' === value.charAt(0)) { | |
value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value; | |
if (value) { value = '#' + value; } | |
} else { | |
value = null; | |
} | |
break; | |
default: | |
value = null; | |
break; | |
} | |
} else { | |
value = null; | |
} | |
attribs[i + 1] = value; | |
} | |
return attribs; | |
})(htmlText, out); | |
return out.join(''); | |
} | |
return { | |
escapeAttrib: escapeAttrib, | |
makeHtmlSanitizer: makeHtmlSanitizer, | |
makeSaxParser: makeSaxParser, | |
normalizeRCData: normalizeRCData, | |
sanitize: sanitize, | |
unescapeEntities: unescapeEntities | |
}; | |
})(html4); | |
var html_sanitize = html.sanitize; | |
// Exports for closure compiler. Note this file is also cajoled | |
// for domado and run in an environment without 'window' | |
if (typeof window !== 'undefined') { | |
window['html'] = html; | |
window['html_sanitize'] = html_sanitize; | |
} | |
// Loosen restrictions of Caja's | |
// html-sanitizer to allow for styling | |
html4.ATTRIBS['*::style'] = 0; | |
html4.ELEMENTS['style'] = 0; | |
html4.ATTRIBS['a::target'] = 0; | |
html4.ELEMENTS['video'] = 0; | |
html4.ATTRIBS['video::src'] = 0; | |
html4.ATTRIBS['video::poster'] = 0; | |
html4.ATTRIBS['video::controls'] = 0; | |
html4.ELEMENTS['audio'] = 0; | |
html4.ATTRIBS['audio::src'] = 0; | |
html4.ATTRIBS['video::autoplay'] = 0; | |
html4.ATTRIBS['video::controls'] = 0; | |
/*! | |
* mustache.js - Logic-less {{mustache}} templates with JavaScript | |
* http://github.com/janl/mustache.js | |
*/ | |
var Mustache = (typeof module !== "undefined" && module.exports) || {}; | |
(function (exports) { | |
exports.name = "mustache.js"; | |
exports.version = "0.5.0-dev"; | |
exports.tags = ["{{", "}}"]; | |
exports.parse = parse; | |
exports.compile = compile; | |
exports.render = render; | |
exports.clearCache = clearCache; | |
// This is here for backwards compatibility with 0.4.x. | |
exports.to_html = function (template, view, partials, send) { | |
var result = render(template, view, partials); | |
if (typeof send === "function") { | |
send(result); | |
} else { | |
return result; | |
} | |
}; | |
var _toString = Object.prototype.toString; | |
var _isArray = Array.isArray; | |
var _forEach = Array.prototype.forEach; | |
var _trim = String.prototype.trim; | |
var isArray; | |
if (_isArray) { | |
isArray = _isArray; | |
} else { | |
isArray = function (obj) { | |
return _toString.call(obj) === "[object Array]"; | |
}; | |
} | |
var forEach; | |
if (_forEach) { | |
forEach = function (obj, callback, scope) { | |
return _forEach.call(obj, callback, scope); | |
}; | |
} else { | |
forEach = function (obj, callback, scope) { | |
for (var i = 0, len = obj.length; i < len; ++i) { | |
callback.call(scope, obj[i], i, obj); | |
} | |
}; | |
} | |
var spaceRe = /^\s*$/; | |
function isWhitespace(string) { | |
return spaceRe.test(string); | |
} | |
var trim; | |
if (_trim) { | |
trim = function (string) { | |
return string == null ? "" : _trim.call(string); | |
}; | |
} else { | |
var trimLeft, trimRight; | |
if (isWhitespace("\xA0")) { | |
trimLeft = /^\s+/; | |
trimRight = /\s+$/; | |
} else { | |
// IE doesn't match non-breaking spaces with \s, thanks jQuery. | |
trimLeft = /^[\s\xA0]+/; | |
trimRight = /[\s\xA0]+$/; | |
} | |
trim = function (string) { | |
return string == null ? "" : | |
String(string).replace(trimLeft, "").replace(trimRight, ""); | |
}; | |
} | |
var escapeMap = { | |
"&": "&", | |
"<": "<", | |
">": ">", | |
'"': '"', | |
"'": ''', | |
"/": '/' | |
}; | |
function escapeHTML(string) { | |
return String(string).replace(/[&<>"'\/]/g, function (s) { | |
return escapeMap[s] || s; | |
}); | |
} | |
/** | |
* Adds the `template`, `line`, and `file` properties to the given error | |
* object and alters the message to provide more useful debugging information. | |
*/ | |
function debug(e, template, line, file) { | |
file = file || "<template>"; | |
var lines = template.split("\n"), | |
start = Math.max(line - 3, 0), | |
end = Math.min(lines.length, line + 3), | |
context = lines.slice(start, end); | |
var c; | |
for (var i = 0, len = context.length; i < len; ++i) { | |
c = i + start + 1; | |
context[i] = (c === line ? " >> " : " ") + context[i]; | |
} | |
e.template = template; | |
e.line = line; | |
e.file = file; | |
e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n"); | |
return e; | |
} | |
/** | |
* Looks up the value of the given `name` in the given context `stack`. | |
*/ | |
function lookup(name, stack, defaultValue) { | |
if (name === ".") { | |
return stack[stack.length - 1]; | |
} | |
var names = name.split("."); | |
var lastIndex = names.length - 1; | |
var target = names[lastIndex]; | |
var value, context, i = stack.length, j, localStack; | |
while (i) { | |
localStack = stack.slice(0); | |
context = stack[--i]; | |
j = 0; | |
while (j < lastIndex) { | |
context = context[names[j++]]; | |
if (context == null) { | |
break; | |
} | |
localStack.push(context); | |
} | |
if (context && typeof context === "object" && target in context) { | |
value = context[target]; | |
break; | |
} | |
} | |
// If the value is a function, call it in the current context. | |
if (typeof value === "function") { | |
value = value.call(localStack[localStack.length - 1]); | |
} | |
if (value == null) { | |
return defaultValue; | |
} | |
return value; | |
} | |
function renderSection(name, stack, callback, inverted) { | |
var buffer = ""; | |
var value = lookup(name, stack); | |
if (inverted) { | |
// From the spec: inverted sections may render text once based on the | |
// inverse value of the key. That is, they will be rendered if the key | |
// doesn't exist, is false, or is an empty list. | |
if (value == null || value === false || (isArray(value) && value.length === 0)) { | |
buffer += callback(); | |
} | |
} else if (isArray(value)) { | |
forEach(value, function (value) { | |
stack.push(value); | |
buffer += callback(); | |
stack.pop(); | |
}); | |
} else if (typeof value === "object") { | |
stack.push(value); | |
buffer += callback(); | |
stack.pop(); | |
} else if (typeof value === "function") { | |
var scope = stack[stack.length - 1]; | |
var scopedRender = function (template) { | |
return render(template, scope); | |
}; | |
buffer += value.call(scope, callback(), scopedRender) || ""; | |
} else if (value) { | |
buffer += callback(); | |
} | |
return buffer; | |
} | |
/** | |
* Parses the given `template` and returns the source of a function that, | |
* with the proper arguments, will render the template. Recognized options | |
* include the following: | |
* | |
* - file The name of the file the template comes from (displayed in | |
* error messages) | |
* - tags An array of open and close tags the `template` uses. Defaults | |
* to the value of Mustache.tags | |
* - debug Set `true` to log the body of the generated function to the | |
* console | |
* - space Set `true` to preserve whitespace from lines that otherwise | |
* contain only a {{tag}}. Defaults to `false` | |
*/ | |
function parse(template, options) { | |
options = options || {}; | |
var tags = options.tags || exports.tags, | |
openTag = tags[0], | |
closeTag = tags[tags.length - 1]; | |
var code = [ | |
'var buffer = "";', // output buffer | |
"\nvar line = 1;", // keep track of source line number | |
"\ntry {", | |
'\nbuffer += "' | |
]; | |
var spaces = [], // indices of whitespace in code on the current line | |
hasTag = false, // is there a {{tag}} on the current line? | |
nonSpace = false; // is there a non-space char on the current line? | |
// Strips all space characters from the code array for the current line | |
// if there was a {{tag}} on it and otherwise only spaces. | |
var stripSpace = function () { | |
if (hasTag && !nonSpace && !options.space) { | |
while (spaces.length) { | |
code.splice(spaces.pop(), 1); | |
} | |
} else { | |
spaces = []; | |
} | |
hasTag = false; | |
nonSpace = false; | |
}; | |
var sectionStack = [], updateLine, nextOpenTag, nextCloseTag; | |
var setTags = function (source) { | |
tags = trim(source).split(/\s+/); | |
nextOpenTag = tags[0]; | |
nextCloseTag = tags[tags.length - 1]; | |
}; | |
var includePartial = function (source) { | |
code.push( | |
'";', | |
updateLine, | |
'\nvar partial = partials["' + trim(source) + '"];', | |
'\nif (partial) {', | |
'\n buffer += render(partial,stack[stack.length - 1],partials);', | |
'\n}', | |
'\nbuffer += "' | |
); | |
}; | |
var openSection = function (source, inverted) { | |
var name = trim(source); | |
if (name === "") { | |
throw debug(new Error("Section name may not be empty"), template, line, options.file); | |
} | |
sectionStack.push({name: name, inverted: inverted}); | |
code.push( | |
'";', | |
updateLine, | |
'\nvar name = "' + name + '";', | |
'\nvar callback = (function () {', | |
'\n return function () {', | |
'\n var buffer = "";', | |
'\nbuffer += "' | |
); | |
}; | |
var openInvertedSection = function (source) { | |
openSection(source, true); | |
}; | |
var closeSection = function (source) { | |
var name = trim(source); | |
var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name; | |
if (!openName || name != openName) { | |
throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file); | |
} | |
var section = sectionStack.pop(); | |
code.push( | |
'";', | |
'\n return buffer;', | |
'\n };', | |
'\n})();' | |
); | |
if (section.inverted) { | |
code.push("\nbuffer += renderSection(name,stack,callback,true);"); | |
} else { | |
code.push("\nbuffer += renderSection(name,stack,callback);"); | |
} | |
code.push('\nbuffer += "'); | |
}; | |
var sendPlain = function (source) { | |
code.push( | |
'";', | |
updateLine, | |
'\nbuffer += lookup("' + trim(source) + '",stack,"");', | |
'\nbuffer += "' | |
); | |
}; | |
var sendEscaped = function (source) { | |
code.push( | |
'";', | |
updateLine, | |
'\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));', | |
'\nbuffer += "' | |
); | |
}; | |
var line = 1, c, callback; | |
for (var i = 0, len = template.length; i < len; ++i) { | |
if (template.slice(i, i + openTag.length) === openTag) { | |
i += openTag.length; | |
c = template.substr(i, 1); | |
updateLine = '\nline = ' + line + ';'; | |
nextOpenTag = openTag; | |
nextCloseTag = closeTag; | |
hasTag = true; | |
switch (c) { | |
case "!": // comment | |
i++; | |
callback = null; | |
break; | |
case "=": // change open/close tags, e.g. {{=<% %>=}} | |
i++; | |
closeTag = "=" + closeTag; | |
callback = setTags; | |
break; | |
case ">": // include partial | |
i++; | |
callback = includePartial; | |
break; | |
case "#": // start section | |
i++; | |
callback = openSection; | |
break; | |
case "^": // start inverted section | |
i++; | |
callback = openInvertedSection; | |
break; | |
case "/": // end section | |
i++; | |
callback = closeSection; | |
break; | |
case "{": // plain variable | |
closeTag = "}" + closeTag; | |
// fall through | |
case "&": // plain variable | |
i++; | |
nonSpace = true; | |
callback = sendPlain; | |
break; | |
default: // escaped variable | |
nonSpace = true; | |
callback = sendEscaped; | |
} | |
var end = template.indexOf(closeTag, i); | |
if (end === -1) { | |
throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file); | |
} | |
var source = template.substring(i, end); | |
if (callback) { | |
callback(source); | |
} | |
// Maintain line count for \n in source. | |
var n = 0; | |
while (~(n = source.indexOf("\n", n))) { | |
line++; | |
n++; | |
} | |
i = end + closeTag.length - 1; | |
openTag = nextOpenTag; | |
closeTag = nextCloseTag; | |
} else { | |
c = template.substr(i, 1); | |
switch (c) { | |
case '"': | |
case "\\": | |
nonSpace = true; | |
code.push("\\" + c); | |
break; | |
case "\r": | |
// Ignore carriage returns. | |
break; | |
case "\n": | |
spaces.push(code.length); | |
code.push("\\n"); | |
stripSpace(); // Check for whitespace on the current line. | |
line++; | |
break; | |
default: | |
if (isWhitespace(c)) { | |
spaces.push(code.length); | |
} else { | |
nonSpace = true; | |
} | |
code.push(c); | |
} | |
} | |
} | |
if (sectionStack.length != 0) { | |
throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file); | |
} | |
// Clean up any whitespace from a closing {{tag}} that was at the end | |
// of the template without a trailing \n. | |
stripSpace(); | |
code.push( | |
'";', | |
"\nreturn buffer;", | |
"\n} catch (e) { throw {error: e, line: line}; }" | |
); | |
// Ignore `buffer += "";` statements. | |
var body = code.join("").replace(/buffer \+= "";\n/g, ""); | |
if (options.debug) { | |
if (typeof console != "undefined" && console.log) { | |
console.log(body); | |
} else if (typeof print === "function") { | |
print(body); | |
} | |
} | |
return body; | |
} | |
/** | |
* Used by `compile` to generate a reusable function for the given `template`. | |
*/ | |
function _compile(template, options) { | |
var args = "view,partials,stack,lookup,escapeHTML,renderSection,render"; | |
var body = parse(template, options); | |
var fn = new Function(args, body); | |
// This anonymous function wraps the generated function so we can do | |
// argument coercion, setup some variables, and handle any errors | |
// encountered while executing it. | |
return function (view, partials) { | |
partials = partials || {}; | |
var stack = [view]; // context stack | |
try { | |
return fn(view, partials, stack, lookup, escapeHTML, renderSection, render); | |
} catch (e) { | |
throw debug(e.error, template, e.line, options.file); | |
} | |
}; | |
} | |
// Cache of pre-compiled templates. | |
var _cache = {}; | |
/** | |
* Clear the cache of compiled templates. | |
*/ | |
function clearCache() { | |
_cache = {}; | |
} | |
/** | |
* Compiles the given `template` into a reusable function using the given | |
* `options`. In addition to the options accepted by Mustache.parse, | |
* recognized options include the following: | |
* | |
* - cache Set `false` to bypass any pre-compiled version of the given | |
* template. Otherwise, a given `template` string will be cached | |
* the first time it is parsed | |
*/ | |
function compile(template, options) { | |
options = options || {}; | |
// Use a pre-compiled version from the cache if we have one. | |
if (options.cache !== false) { | |
if (!_cache[template]) { | |
_cache[template] = _compile(template, options); | |
} | |
return _cache[template]; | |
} | |
return _compile(template, options); | |
} | |
/** | |
* High-level function that renders the given `template` using the given | |
* `view` and `partials`. If you need to use any of the template options (see | |
* `compile` above), you must compile in a separate step, and then call that | |
* compiled function. | |
*/ | |
function render(template, view, partials) { | |
return compile(template)(view, partials); | |
} | |
})(Mustache); | |
/*! | |
* Reqwest! A general purpose XHR connection manager | |
* (c) Dustin Diaz 2011 | |
* https://github.com/ded/reqwest | |
* license MIT | |
*/ | |
!function(a,b){typeof module!="undefined"?module.exports=b():typeof define=="function"&&define.amd?define(a,b):this[a]=b()}("reqwest",function(){function handleReadyState(a,b,c){return function(){a&&a[readyState]==4&&(twoHundo.test(a.status)?b(a):c(a))}}function setHeaders(a,b){var c=b.headers||{},d;c.Accept=c.Accept||defaultHeaders.accept[b.type]||defaultHeaders.accept["*"],!b.crossOrigin&&!c[requestedWith]&&(c[requestedWith]=defaultHeaders.requestedWith),c[contentType]||(c[contentType]=b.contentType||defaultHeaders.contentType);for(d in c)c.hasOwnProperty(d)&&a.setRequestHeader(d,c[d])}function generalCallback(a){lastValue=a}function urlappend(a,b){return a+(/\?/.test(a)?"&":"?")+b}function handleJsonp(a,b,c,d){var e=uniqid++,f=a.jsonpCallback||"callback",g=a.jsonpCallbackName||"reqwest_"+e,h=new RegExp("((^|\\?|&)"+f+")=([^&]+)"),i=d.match(h),j=doc.createElement("script"),k=0;i?i[3]==="?"?d=d.replace(h,"$1="+g):g=i[3]:d=urlappend(d,f+"="+g),win[g]=generalCallback,j.type="text/javascript",j.src=d,j.async=!0,typeof j.onreadystatechange!="undefined"&&(j.event="onclick",j.htmlFor=j.id="_reqwest_"+e),j.onload=j.onreadystatechange=function(){if(j[readyState]&&j[readyState]!=="complete"&&j[readyState]!=="loaded"||k)return!1;j.onload=j.onreadystatechange=null,j.onclick&&j.onclick(),a.success&&a.success(lastValue),lastValue=undefined,head.removeChild(j),k=1},head.appendChild(j)}function getRequest(a,b,c){var d=(a.method||"GET").toUpperCase(),e=typeof a=="string"?a:a.url,f=a.processData!==!1&&a.data&&typeof a.data!="string"?reqwest.toQueryString(a.data):a.data||null,g;return(a.type=="jsonp"||d=="GET")&&f&&(e=urlappend(e,f),f=null),a.type=="jsonp"?handleJsonp(a,b,c,e):(g=xhr(),g.open(d,e,!0),setHeaders(g,a),g.onreadystatechange=handleReadyState(g,b,c),a.before&&a.before(g),g.send(f),g)}function Reqwest(a,b){this.o=a,this.fn=b,init.apply(this,arguments)}function setType(a){var b=a.match(/\.(json|jsonp|html|xml)(\?|$)/);return b?b[1]:"js"}function init(o,fn){function complete(a){o.timeout&&clearTimeout(self.timeout),self.timeout=null,o.complete&&o.complete(a)}function success(resp){var r=resp.responseText;if(r)switch(type){case"json":try{resp=win.JSON?win.JSON.parse(r):eval("("+r+")")}catch(err){return error(resp,"Could not parse JSON in response",err)}break;case"js":resp=eval(r);break;case"html":resp=r}fn(resp),o.success&&o.success(resp),complete(resp)}function error(a,b,c){o.error&&o.error(a,b,c),complete(a)}this.url=typeof o=="string"?o:o.url,this.timeout=null;var type=o.type||setType(this.url),self=this;fn=fn||function(){},o.timeout&&(this.timeout=setTimeout(function(){self.abort()},o.timeout)),this.request=getRequest(o,success,error)}function reqwest(a,b){return new Reqwest(a,b)}function normalize(a){return a?a.replace(/\r?\n/g,"\r\n"):""}function serial(a,b){var c=a.name,d=a.tagName.toLowerCase(),e=function(a){a&&!a.disabled&&b(c,normalize(a.attributes.value&&a.attributes.value.specified?a.value:a.text))};if(a.disabled||!c)return;switch(d){case"input":if(!/reset|button|image|file/i.test(a.type)){var f=/checkbox/i.test(a.type),g=/radio/i.test(a.type),h=a.value;(!f&&!g||a.checked)&&b(c,normalize(f&&h===""?"on":h))}break;case"textarea":b(c,normalize(a.value));break;case"select":if(a.type.toLowerCase()==="select-one")e(a.selectedIndex>=0?a.options[a.selectedIndex]:null);else for(var i=0;a.length&&i<a.length;i++)a.options[i].selected&&e(a.options[i])}}function eachFormElement(){var a=this,b,c,d,e=function(b,c){for(var e=0;e<c.length;e++){var f=b[byTag](c[e]);for(d=0;d<f.length;d++)serial(f[d],a)}};for(c=0;c<arguments.length;c++)b=arguments[c],/input|select|textarea/i.test(b.tagName)&&serial(b,a),e(b,["input","select","textarea"])}function serializeQueryString(){return reqwest.toQueryString(reqwest.serializeArray.apply(null,arguments))}function serializeHash(){var a={};return eachFormElement.apply(function(b,c){b in a?(a[b]&&!isArray(a[b])&&(a[b]=[a[b]]),a[b].push(c)):a[b]=c},arguments),a}var win=window,doc=document,twoHundo=/^20\d$/,byTag="getElementsByTagName",readyState="readyState",contentType="Content-Type",requestedWith="X-Requested-With",head=doc[byTag]("head")[0],uniqid=0,lastValue,xmlHttpRequest="XMLHttpRequest",isArray=typeof Array.isArray=="function"?Array.isArray:function(a){return a instanceof Array},defaultHeaders={contentType:"application/x-www-form-urlencoded",accept:{"*":"text/javascript, text/html, application/xml, text/xml, */*",xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript",js:"application/javascript, text/javascript"},requestedWith:xmlHttpRequest},xhr=win[xmlHttpRequest]?function(){return new XMLHttpRequest}:function(){return new ActiveXObject("Microsoft.XMLHTTP")};return Reqwest.prototype={abort:function(){this.request.abort()},retry:function(){init.call(this,this.o,this.fn)}},reqwest.serializeArray=function(){var a=[];return eachFormElement.apply(function(b,c){a.push({name:b,value:c})},arguments),a},reqwest.serialize=function(){if(arguments.length===0)return"";var a,b,c=Array.prototype.slice.call(arguments,0);return a=c.pop(),a&&a.nodeType&&c.push(a)&&(a=null),a&&(a=a.type),a=="map"?b=serializeHash:a=="array"?b=reqwest.serializeArray:b=serializeQueryString,b.apply(null,c)},reqwest.toQueryString=function(a){var b="",c,d=encodeURIComponent,e=function(a,c){b+=d(a)+"="+d(c)+"&"};if(isArray(a))for(c=0;a&&c<a.length;c++)e(a[c].name,a[c].value);else for(var f in a){if(!Object.hasOwnProperty.call(a,f))continue;var g=a[f];if(isArray(g))for(c=0;c<g.length;c++)e(f,g[c]);else e(f,a[f])}return b.replace(/&$/,"").replace(/%20/g,"+")},reqwest.compat=function(a,b){return a&&(a.type&&(a.method=a.type)&&delete a.type,a.dataType&&(a.type=a.dataType),a.jsonpCallback&&(a.jsonpCallbackName=a.jsonpCallback)&&delete a.jsonpCallback,a.jsonp&&(a.jsonpCallback=a.jsonp)),new Reqwest(a,b)},reqwest});wax = wax || {}; | |
// Attribution | |
// ----------- | |
wax.attribution = function() { | |
var container, | |
a = {}; | |
function urlX(url) { | |
// Data URIs are subject to a bug in Firefox | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=255107 | |
// which let them be a vector. But WebKit does 'the right thing' | |
// or at least 'something' about this situation, so we'll tolerate | |
// them. | |
if (/^(https?:\/\/|data:image)/.test(url)) { | |
return url; | |
} | |
} | |
function idX(id) { | |
return id; | |
} | |
a.content = function(x) { | |
if (typeof x === 'undefined') return container.innerHTML; | |
container.innerHTML = html_sanitize(x, urlX, idX); | |
return this; | |
}; | |
a.element = function() { | |
return container; | |
}; | |
a.init = function() { | |
container = document.createElement('div'); | |
container.className = 'wax-attribution'; | |
return this; | |
}; | |
return a.init(); | |
}; | |
wax = wax || {}; | |
// Attribution | |
// ----------- | |
wax.bwdetect = function(options, callback) { | |
var detector = {}, | |
threshold = options.threshold || 400, | |
// test image: 30.29KB | |
testImage = 'http://a.tiles.mapbox.com/mapbox/1.0.0/blue-marble-topo-bathy-jul/0/0/0.png?preventcache=' + (+new Date()), | |
// High-bandwidth assumed | |
// 1: high bandwidth (.png, .jpg) | |
// 0: low bandwidth (.png128, .jpg70) | |
bw = 1, | |
// Alternative versions | |
auto = options.auto === undefined ? true : options.auto; | |
function bwTest() { | |
wax.bw = -1; | |
var im = new Image(); | |
im.src = testImage; | |
var first = true; | |
var timeout = setTimeout(function() { | |
if (first && wax.bw == -1) { | |
detector.bw(0); | |
first = false; | |
} | |
}, threshold); | |
im.onload = function() { | |
if (first && wax.bw == -1) { | |
clearTimeout(timeout); | |
detector.bw(1); | |
first = false; | |
} | |
}; | |
} | |
detector.bw = function(x) { | |
if (!arguments.length) return bw; | |
var oldBw = bw; | |
if (wax.bwlisteners && wax.bwlisteners.length) (function () { | |
listeners = wax.bwlisteners; | |
wax.bwlisteners = []; | |
for (i = 0; i < listeners; i++) { | |
listeners[i](x); | |
} | |
})(); | |
wax.bw = x; | |
if (bw != (bw = x)) callback(x); | |
}; | |
detector.add = function() { | |
if (auto) bwTest(); | |
return this; | |
}; | |
if (wax.bw == -1) { | |
wax.bwlisteners = wax.bwlisteners || []; | |
wax.bwlisteners.push(detector.bw); | |
} else if (wax.bw !== undefined) { | |
detector.bw(wax.bw); | |
} else { | |
detector.add(); | |
} | |
return detector; | |
}; | |
// Formatter | |
// --------- | |
// | |
// This code is no longer the recommended code path for Wax - | |
// see `template.js`, a safe implementation of Mustache templates. | |
wax.formatter = function(x) { | |
var formatter = {}, | |
f; | |
// Prevent against just any input being used. | |
if (x && typeof x === 'string') { | |
try { | |
// Ugly, dangerous use of eval. | |
eval('f = ' + x); | |
} catch (e) { | |
if (console) console.log(e); | |
} | |
} else if (x && typeof x === 'function') { | |
f = x; | |
} else { | |
f = function() {}; | |
} | |
function urlX(url) { | |
if (/^(https?:\/\/|data:image)/.test(url)) { | |
return url; | |
} | |
} | |
function idX(id) { | |
return id; | |
} | |
// Wrap the given formatter function in order to | |
// catch exceptions that it may throw. | |
formatter.format = function(options, data) { | |
try { | |
return html_sanitize(f(options, data), urlX, idX); | |
} catch (e) { | |
if (console) console.log(e); | |
} | |
}; | |
return formatter; | |
}; | |
// GridInstance | |
// ------------ | |
// GridInstances are queryable, fully-formed | |
// objects for acquiring features from events. | |
// | |
// This code ignores format of 1.1-1.2 | |
wax.gi = function(grid_tile, options) { | |
options = options || {}; | |
// resolution is the grid-elements-per-pixel ratio of gridded data. | |
// The size of a tile element. For now we expect tiles to be squares. | |
var instance = {}, | |
resolution = options.resolution || 4, | |
tileSize = options.tileSize || 256; | |
// Resolve the UTF-8 encoding stored in grids to simple | |
// number values. | |
// See the [utfgrid spec](https://github.com/mapbox/utfgrid-spec) | |
// for details. | |
function resolveCode(key) { | |
if (key >= 93) key--; | |
if (key >= 35) key--; | |
key -= 32; | |
return key; | |
} | |
instance.grid_tile = function() { | |
return grid_tile; | |
}; | |
instance.getKey = function(x, y) { | |
if (!(grid_tile && grid_tile.grid)) return; | |
if ((y < 0) || (x < 0)) return; | |
if ((Math.floor(y) >= tileSize) || | |
(Math.floor(x) >= tileSize)) return; | |
// Find the key in the grid. The above calls should ensure that | |
// the grid's array is large enough to make this work. | |
return resolveCode(grid_tile.grid[ | |
Math.floor((y) / resolution) | |
].charCodeAt( | |
Math.floor((x) / resolution) | |
)); | |
}; | |
// Lower-level than tileFeature - has nothing to do | |
// with the DOM. Takes a px offset from 0, 0 of a grid. | |
instance.gridFeature = function(x, y) { | |
// Find the key in the grid. The above calls should ensure that | |
// the grid's array is large enough to make this work. | |
var key = this.getKey(x, y), | |
keys = grid_tile.keys; | |
if (keys && | |
keys[key] && | |
grid_tile.data[keys[key]]) { | |
return grid_tile.data[keys[key]]; | |
} | |
}; | |
// Get a feature: | |
// * `x` and `y`: the screen coordinates of an event | |
// * `tile_element`: a DOM element of a tile, from which we can get an offset. | |
instance.tileFeature = function(x, y, tile_element) { | |
if (!grid_tile) return; | |
// IE problem here - though recoverable, for whatever reason | |
var offset = wax.u.offset(tile_element); | |
feature = this.gridFeature(x - offset.left, y - offset.top); | |
return feature; | |
}; | |
return instance; | |
}; | |
// GridManager | |
// ----------- | |
// Generally one GridManager will be used per map. | |
// | |
// It takes one options object, which current accepts a single option: | |
// `resolution` determines the number of pixels per grid element in the grid. | |
// The default is 4. | |
wax.gm = function() { | |
var resolution = 4, | |
grid_tiles = {}, | |
manager = {}, | |
tilejson, | |
formatter; | |
var gridUrl = function(url) { | |
return url.replace(/(\.png|\.jpg|\.jpeg)(\d*)/, '.grid.json'); | |
}; | |
function templatedGridUrl(template) { | |
if (typeof template === 'string') template = [template]; | |
return function templatedGridFinder(url) { | |
if (!url) return; | |
var rx = new RegExp('/(\\d+)\\/(\\d+)\\/(\\d+)\\.[\\w\\._]+'); | |
var xyz = rx.exec(url); | |
if (!xyz) return; | |
return template[parseInt(xyz[2], 10) % template.length] | |
.replace(/\{z\}/g, xyz[1]) | |
.replace(/\{x\}/g, xyz[2]) | |
.replace(/\{y\}/g, xyz[3]); | |
}; | |
} | |
manager.formatter = function(x) { | |
if (!arguments.length) return formatter; | |
formatter = wax.formatter(x); | |
return manager; | |
}; | |
manager.template = function(x) { | |
if (!arguments.length) return formatter; | |
formatter = wax.template(x); | |
return manager; | |
}; | |
manager.gridUrl = function(x) { | |
if (!arguments.length) return gridUrl; | |
gridUrl = typeof x === 'function' ? | |
x : templatedGridUrl(x); | |
return manager; | |
}; | |
manager.getGrid = function(url, callback) { | |
var gurl = gridUrl(url); | |
if (!formatter || !gurl) return callback(null, null); | |
wax.request.get(gurl, function(err, t) { | |
if (err) return callback(err, null); | |
callback(null, wax.gi(t, { | |
formatter: formatter, | |
resolution: resolution | |
})); | |
}); | |
return manager; | |
}; | |
manager.tilejson = function(x) { | |
if (!arguments.length) return tilejson; | |
// prefer templates over formatters | |
if (x.template) { | |
manager.template(x.template); | |
} else if (x.formatter) { | |
manager.formatter(x.formatter); | |
} else { | |
formatter = undefined; | |
} | |
if (x.grids) manager.gridUrl(x.grids); | |
if (x.resolution) resolution = x.resolution; | |
tilejson = x; | |
return manager; | |
}; | |
return manager; | |
}; | |
wax = wax || {}; | |
// Hash | |
// ---- | |
wax.hash = function(options) { | |
options = options || {}; | |
function getState() { | |
return location.hash.substring(1); | |
} | |
function pushState(state) { | |
var l = window.location; | |
l.replace(l.toString().replace((l.hash || /$/), '#' + state)); | |
} | |
var s0, // old hash | |
hash = {}, | |
lat = 90 - 1e-8; // allowable latitude range | |
function parseHash(s) { | |
var args = s.split('/'); | |
for (var i = 0; i < args.length; i++) { | |
args[i] = Number(args[i]); | |
if (isNaN(args[i])) return true; | |
} | |
if (args.length < 3) { | |
// replace bogus hash | |
return true; | |
} else if (args.length == 3) { | |
options.setCenterZoom(args); | |
} | |
} | |
function move() { | |
var s1 = options.getCenterZoom(); | |
if (s0 !== s1) { | |
s0 = s1; | |
// don't recenter the map! | |
pushState(s0); | |
} | |
} | |
function stateChange(state) { | |
// ignore spurious hashchange events | |
if (state === s0) return; | |
if (parseHash(s0 = state)) { | |
// replace bogus hash | |
move(); | |
} | |
} | |
var _move = wax.u.throttle(move, 500); | |
hash.add = function() { | |
stateChange(getState()); | |
options.bindChange(_move); | |
return this; | |
}; | |
hash.remove = function() { | |
options.unbindChange(_move); | |
return this; | |
}; | |
return hash.add(); | |
}; | |
wax = wax || {}; | |
wax.interaction = function() { | |
var gm = wax.gm(), | |
interaction = {}, | |
_downLock = false, | |
_clickTimeout = false, | |
// Active feature | |
// Down event | |
_d, | |
// Touch tolerance | |
tol = 4, | |
grid, | |
attach, | |
detach, | |
parent, | |
map, | |
tileGrid; | |
var defaultEvents = { | |
mousemove: onMove, | |
touchstart: onDown, | |
mousedown: onDown | |
}; | |
var touchEnds = { | |
touchend: onUp, | |
touchmove: onUp, | |
touchcancel: touchCancel | |
}; | |
// Abstract getTile method. Depends on a tilegrid with | |
// grid[ [x, y, tile] ] structure. | |
function getTile(e) { | |
var g = grid(); | |
for (var i = 0; i < g.length; i++) { | |
if ((g[i][0] < e.y) && | |
((g[i][0] + 256) > e.y) && | |
(g[i][1] < e.x) && | |
((g[i][1] + 256) > e.x)) return g[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.u.eventoffset(e); | |
interaction.screen_feature(pos, function(feature) { | |
if (feature) { | |
bean.fire(interaction, 'on', { | |
parent: parent(), | |
data: feature, | |
formatter: gm.formatter().format, | |
e: e | |
}); | |
} else { | |
bean.fire(interaction, 'off'); | |
} | |
}); | |
} | |
// A handler for 'down' events - which means `mousedown` and `touchstart` | |
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.u.eventoffset(e); | |
if (e.type === 'mousedown') { | |
bean.add(document.body, 'click', onUp); | |
// Only track single-touches. Double-touches will not affect this | |
// control | |
} else if (e.type === 'touchstart' && e.touches.length === 1) { | |
// Don't make the user click close if they hit another tooltip | |
bean.fire(interaction, 'off'); | |
// Touch moves invalidate touches | |
bean.add(parent(), touchEnds); | |
} | |
} | |
function touchCancel() { | |
bean.remove(parent(), touchEnds); | |
_downLock = false; | |
} | |
function onUp(e) { | |
var evt = {}, | |
pos = wax.u.eventoffset(e); | |
_downLock = false; | |
// TODO: refine | |
for (var key in e) { | |
evt[key] = e[key]; | |
} | |
bean.remove(document.body, 'mouseup', onUp); | |
bean.remove(parent(), touchEnds); | |
if (e.type === 'touchend') { | |
// If this was a touch and it survived, there's no need to avoid a double-tap | |
// but also wax.u.eventoffset will have failed, since this touch | |
// event doesn't have coordinates | |
interaction.click(e, _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() { | |
_clickTimeout = null; | |
interaction.click(evt, pos); | |
}, 300); | |
} | |
return onUp; | |
} | |
// Handle a click event. Takes a second | |
interaction.click = function(e, pos) { | |
interaction.screen_feature(pos, function(feature) { | |
if (feature) bean.fire(interaction, 'on', { | |
parent: parent(), | |
data: feature, | |
formatter: gm.formatter().format, | |
e: e | |
}); | |
}); | |
}; | |
interaction.screen_feature = function(pos, callback) { | |
var tile = getTile(pos); | |
if (!tile) callback(null); | |
gm.getGrid(tile.src, function(err, g) { | |
if (err || !g) return callback(null); | |
var feature = g.tileFeature(pos.x, pos.y, tile); | |
callback(feature); | |
}); | |
}; | |
// set an attach function that should be | |
// called when maps are set | |
interaction.attach = function(x) { | |
if (!arguments.length) return attach; | |
attach = x; | |
return interaction; | |
}; | |
interaction.detach = function(x) { | |
if (!arguments.length) return detach; | |
detach = x; | |
return interaction; | |
}; | |
// Attach listeners to the map | |
interaction.map = function(x) { | |
if (!arguments.length) return map; | |
map = x; | |
if (attach) attach(map); | |
bean.add(parent(), defaultEvents); | |
bean.add(parent(), 'touchstart', onDown); | |
return interaction; | |
}; | |
// set a grid getter for this control | |
interaction.grid = function(x) { | |
if (!arguments.length) return grid; | |
grid = x; | |
return interaction; | |
}; | |
// detach this and its events from the map cleanly | |
interaction.remove = function(x) { | |
if (detach) detach(map); | |
bean.remove(parent(), defaultEvents); | |
bean.fire(interaction, 'remove'); | |
return interaction; | |
}; | |
// get or set a tilejson chunk of json | |
interaction.tilejson = function(x) { | |
if (!arguments.length) return gm.tilejson(); | |
gm.tilejson(x); | |
return interaction; | |
}; | |
// return the formatter, which has an exposed .format | |
// function | |
interaction.formatter = function() { | |
return gm.formatter(); | |
}; | |
// ev can be 'on', 'off', fn is the handler | |
interaction.on = function(ev, fn) { | |
bean.add(interaction, ev, fn); | |
return interaction; | |
}; | |
// ev can be 'on', 'off', fn is the handler | |
interaction.off = function(ev, fn) { | |
bean.remove(interaction, ev, fn); | |
return interaction; | |
}; | |
// Return or set the gridmanager implementation | |
interaction.gridmanager = function(x) { | |
if (!arguments.length) return gm; | |
gm = x; | |
return interaction; | |
}; | |
// parent should be a function that returns | |
// the parent element of the map | |
interaction.parent = function(x) { | |
parent = x; | |
return interaction; | |
}; | |
return interaction; | |
}; | |
// Wax Legend | |
// ---------- | |
// Wax header | |
var wax = wax || {}; | |
wax.legend = function() { | |
var element, | |
legend = {}, | |
container; | |
function urlX(url) { | |
// Data URIs are subject to a bug in Firefox | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=255107 | |
// which let them be a vector. But WebKit does 'the right thing' | |
// or at least 'something' about this situation, so we'll tolerate | |
// them. | |
if (/^(https?:\/\/|data:image)/.test(url)) { | |
return url; | |
} | |
} | |
function idX(id) { | |
return id; | |
} | |
legend.element = function() { | |
return container; | |
}; | |
legend.content = function(content) { | |
if (!arguments.length) return element.innerHTML; | |
if (content) { | |
element.innerHTML = html_sanitize(content, urlX, idX); | |
element.style.display = 'block'; | |
} else { | |
element.innerHTML = ''; | |
element.style.display = 'none'; | |
} | |
return legend; | |
}; | |
legend.add = function() { | |
container = document.createElement('div'); | |
container.className = 'wax-legends'; | |
element = container.appendChild(document.createElement('div')); | |
element.className = 'wax-legend'; | |
element.style.display = 'none'; | |
return legend; | |
}; | |
return legend.add(); | |
}; | |
var wax = wax || {}; | |
wax.location = function() { | |
var t = {}; | |
function on(o) { | |
console.log(o); | |
if ((o.e.type === 'mousemove' || !o.e.type)) { | |
return; | |
} else { | |
var loc = o.formatter({ format: 'location' }, o.data); | |
if (loc) { | |
window.location.href = loc; | |
} | |
} | |
} | |
t.events = function() { | |
return { | |
on: on | |
}; | |
}; | |
return t; | |
}; | |
var wax = wax || {}; | |
wax.movetip = {}; | |
wax.movetip = function() { | |
var popped = false, | |
t = {}, | |
_tooltipOffset, | |
_contextOffset, | |
tooltip, | |
parent; | |
function moveTooltip(e) { | |
var eo = wax.u.eventoffset(e); | |
// faux-positioning | |
if ((_tooltipOffset.height + eo.y) > | |
(_contextOffset.top + _contextOffset.height) && | |
(_contextOffset.height > _tooltipOffset.height)) { | |
eo.y -= _tooltipOffset.height; | |
tooltip.className += ' flip-y'; | |
} | |
// faux-positioning | |
if ((_tooltipOffset.width + eo.x) > | |
(_contextOffset.left + _contextOffset.width)) { | |
eo.x -= _tooltipOffset.width; | |
tooltip.className += ' flip-x'; | |
} | |
tooltip.style.left = eo.x + 'px'; | |
tooltip.style.top = eo.y + 'px'; | |
} | |
// Get the active tooltip for a layer or create a new one if no tooltip exists. | |
// Hide any tooltips on layers underneath this one. | |
function getTooltip(feature) { | |
var tooltip = document.createElement('div'); | |
tooltip.className = 'wax-tooltip wax-tooltip-0'; | |
tooltip.innerHTML = feature; | |
return tooltip; | |
} | |
// Hide a given tooltip. | |
function hide() { | |
if (tooltip) { | |
tooltip.parentNode.removeChild(tooltip); | |
tooltip = null; | |
} | |
} | |
function on(o) { | |
var content; | |
if (popped) return; | |
if ((o.e.type === 'mousemove' || !o.e.type)) { | |
content = o.formatter({ format: 'teaser' }, o.data); | |
if (!content) return; | |
hide(); | |
parent.style.cursor = 'pointer'; | |
tooltip = document.body.appendChild(getTooltip(content)); | |
} else { | |
content = o.formatter({ format: 'teaser' }, o.data); | |
if (!content) return; | |
hide(); | |
var tt = document.body.appendChild(getTooltip(content)); | |
tt.className += ' wax-popup'; | |
var close = tt.appendChild(document.createElement('a')); | |
close.href = '#close'; | |
close.className = 'close'; | |
close.innerHTML = 'Close'; | |
popped = true; | |
tooltip = tt; | |
_tooltipOffset = wax.u.offset(tooltip); | |
_contextOffset = wax.u.offset(parent); | |
moveTooltip(o.e); | |
bean.add(close, 'click touchend', function closeClick(e) { | |
e.stop(); | |
hide(); | |
popped = false; | |
}); | |
} | |
if (tooltip) { | |
_tooltipOffset = wax.u.offset(tooltip); | |
_contextOffset = wax.u.offset(parent); | |
moveTooltip(o.e); | |
} | |
} | |
function off() { | |
parent.style.cursor = 'default'; | |
if (!popped) hide(); | |
} | |
t.parent = function(x) { | |
if (!arguments.length) return parent; | |
parent = x; | |
return t; | |
}; | |
t.events = function() { | |
return { | |
on: on, | |
off: off | |
}; | |
}; | |
return t; | |
}; | |
// Wax GridUtil | |
// ------------ | |
// Wax header | |
var wax = wax || {}; | |
// Request | |
// ------- | |
// Request data cache. `callback(data)` where `data` is the response data. | |
wax.request = { | |
cache: {}, | |
locks: {}, | |
promises: {}, | |
get: function(url, callback) { | |
// Cache hit. | |
if (this.cache[url]) { | |
return callback(this.cache[url][0], this.cache[url][1]); | |
// Cache miss. | |
} else { | |
this.promises[url] = this.promises[url] || []; | |
this.promises[url].push(callback); | |
// Lock hit. | |
if (this.locks[url]) return; | |
// Request. | |
var that = this; | |
this.locks[url] = true; | |
reqwest({ | |
url: url + (~url.indexOf('?') ? '&' : '?') + 'callback=grid', | |
type: 'jsonp', | |
jsonpCallback: 'callback', | |
success: function(data) { | |
that.locks[url] = false; | |
that.cache[url] = [null, data]; | |
for (var i = 0; i < that.promises[url].length; i++) { | |
that.promises[url][i](that.cache[url][0], that.cache[url][1]); | |
} | |
}, | |
error: function(err) { | |
that.locks[url] = false; | |
that.cache[url] = [err, null]; | |
for (var i = 0; i < that.promises[url].length; i++) { | |
that.promises[url][i](that.cache[url][0], that.cache[url][1]); | |
} | |
} | |
}); | |
} | |
} | |
}; | |
// Templating | |
// --------- | |
wax.template = function(x) { | |
var template = {}; | |
function urlX(url) { | |
// Data URIs are subject to a bug in Firefox | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=255107 | |
// which let them be a vector. But WebKit does 'the right thing' | |
// or at least 'something' about this situation, so we'll tolerate | |
// them. | |
if (/^(https?:\/\/|data:image)/.test(url)) { | |
return url; | |
} | |
} | |
function idX(id) { | |
return id; | |
} | |
// Clone the data object such that the '__[format]__' key is only | |
// set for this instance of templating. | |
template.format = function(options, data) { | |
var clone = {}; | |
for (var key in data) { | |
clone[key] = data[key]; | |
} | |
if (options.format) { | |
clone['__' + options.format + '__'] = true; | |
} | |
return html_sanitize(Mustache.to_html(x, clone), urlX, idX); | |
}; | |
return template; | |
}; | |
if (!wax) var wax = {}; | |
// A wrapper for reqwest jsonp to easily load TileJSON from a URL. | |
wax.tilejson = function(url, callback) { | |
reqwest({ | |
url: url + (~url.indexOf('?') ? '&' : '?') + 'callback=grid', | |
type: 'jsonp', | |
jsonpCallback: 'callback', | |
success: callback, | |
error: callback | |
}); | |
}; | |
var wax = wax || {}; | |
wax.tooltip = {}; | |
wax.tooltip = function() { | |
var popped = false, | |
animate = false, | |
t = {}, | |
tooltips = [], | |
_currentContent, | |
transitionEvent, | |
parent; | |
if (document.body.style['-webkit-transition'] !== undefined) { | |
transitionEvent = 'webkitTransitionEnd'; | |
} else if (document.body.style.MozTransition !== undefined) { | |
transitionEvent = 'transitionend'; | |
} | |
// Get the active tooltip for a layer or create a new one if no tooltip exists. | |
// Hide any tooltips on layers underneath this one. | |
function getTooltip(feature) { | |
var tooltip = document.createElement('div'); | |
tooltip.className = 'wax-tooltip wax-tooltip-0'; | |
tooltip.innerHTML = feature; | |
return tooltip; | |
} | |
function remove() { | |
if (this.parentNode) this.parentNode.removeChild(this); | |
} | |
// Hide a given tooltip. | |
function hide() { | |
var _ct; | |
while (_ct = tooltips.pop()) { | |
if (animate && transitionEvent) { | |
// This code assumes that transform-supporting browsers | |
// also support proper events. IE9 does both. | |
bean.add(_ct, transitionEvent, remove); | |
_ct.className += ' wax-fade'; | |
} else { | |
if (_ct.parentNode) _ct.parentNode.removeChild(_ct); | |
} | |
} | |
} | |
function on(o) { | |
var content; | |
if (o.e.type === 'mousemove' || !o.e.type) { | |
if (!popped) { | |
content = o.content || o.formatter({ format: 'teaser' }, o.data); | |
if (!content || content == _currentContent) return; | |
hide(); | |
parent.style.cursor = 'pointer'; | |
tooltips.push(parent.appendChild(getTooltip(content))); | |
_currentContent = content; | |
} | |
} else { | |
content = o.content || o.formatter({ format: 'full' }, o.data); | |
if (!content) { | |
if (o.e.type && o.e.type.match(/touch/)) { | |
// fallback possible | |
content = o.content || o.formatter({ format: 'teaser' }, o.data); | |
} | |
// but if that fails, return just the same. | |
if (!content) return; | |
} | |
hide(); | |
parent.style.cursor = 'pointer'; | |
var tt = parent.appendChild(getTooltip(content)); | |
tt.className += ' wax-popup'; | |
var close = tt.appendChild(document.createElement('a')); | |
close.href = '#close'; | |
close.className = 'close'; | |
close.innerHTML = 'Close'; | |
popped = true; | |
tooltips.push(tt); | |
bean.add(close, 'touchstart mousedown', function(e) { | |
e.stop(); | |
}); | |
bean.add(close, 'click touchend', function closeClick(e) { | |
e.stop(); | |
hide(); | |
popped = false; | |
}); | |
} | |
} | |
function off() { | |
parent.style.cursor = 'default'; | |
_currentContent = null; | |
if (!popped) hide(); | |
} | |
t.parent = function(x) { | |
if (!arguments.length) return parent; | |
parent = x; | |
return t; | |
}; | |
t.animate = function(x) { | |
if (!arguments.length) return animate; | |
animate = x; | |
return t; | |
}; | |
t.events = function() { | |
return { | |
on: on, | |
off: off | |
}; | |
}; | |
return t; | |
}; | |
var wax = wax || {}; | |
// Utils are extracted from other libraries or | |
// written from scratch to plug holes in browser compatibility. | |
wax.u = { | |
// From Bonzo | |
offset: function(el) { | |
// TODO: window margins | |
// | |
// Okay, so fall back to styles if offsetWidth and height are botched | |
// by Firefox. | |
var width = el.offsetWidth || parseInt(el.style.width, 10), | |
height = el.offsetHeight || parseInt(el.style.height, 10), | |
doc_body = document.body, | |
top = 0, | |
left = 0; | |
var calculateOffset = function(el) { | |
if (el === doc_body || el === document.documentElement) return; | |
top += el.offsetTop; | |
left += el.offsetLeft; | |
var style = el.style.transform || | |
el.style.WebkitTransform || | |
el.style.OTransform || | |
el.style.MozTransform || | |
el.style.msTransform; | |
if (style) { | |
if (match = style.match(/translate\((.+)px, (.+)px\)/)) { | |
top += parseInt(match[2], 10); | |
left += parseInt(match[1], 10); | |
} else if (match = style.match(/translate3d\((.+)px, (.+)px, (.+)px\)/)) { | |
top += parseInt(match[2], 10); | |
left += parseInt(match[1], 10); | |
} else if (match = style.match(/matrix3d\(([\-\d,\s]+)\)/)) { | |
var pts = match[1].split(','); | |
top += parseInt(pts[13], 10); | |
left += parseInt(pts[12], 10); | |
} else if (match = style.match(/matrix\(.+, .+, .+, .+, (.+), (.+)\)/)) { | |
top += parseInt(match[2], 10); | |
left += parseInt(match[1], 10); | |
} | |
} | |
}; | |
calculateOffset(el); | |
try { | |
while (el = el.offsetParent) { calculateOffset(el); } | |
} catch(e) { | |
// Hello, internet explorer. | |
} | |
// Offsets from the body | |
top += doc_body.offsetTop; | |
left += doc_body.offsetLeft; | |
// Offsets from the HTML element | |
top += doc_body.parentNode.offsetTop; | |
left += doc_body.parentNode.offsetLeft; | |
// Firefox and other weirdos. Similar technique to jQuery's | |
// `doesNotIncludeMarginInBodyOffset`. | |
var htmlComputed = document.defaultView ? | |
window.getComputedStyle(doc_body.parentNode, null) : | |
doc_body.parentNode.currentStyle; | |
if (doc_body.parentNode.offsetTop !== | |
parseInt(htmlComputed.marginTop, 10) && | |
!isNaN(parseInt(htmlComputed.marginTop, 10))) { | |
top += parseInt(htmlComputed.marginTop, 10); | |
left += parseInt(htmlComputed.marginLeft, 10); | |
} | |
return { | |
top: top, | |
left: left, | |
height: height, | |
width: width | |
}; | |
}, | |
'$': function(x) { | |
return (typeof x === 'string') ? | |
document.getElementById(x) : | |
x; | |
}, | |
// IE doesn't have indexOf | |
indexOf: function(array, item) { | |
var nativeIndexOf = Array.prototype.indexOf; | |
if (array === null) return -1; | |
var i, l; | |
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); | |
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; | |
return -1; | |
}, | |
// From quirksmode: normalize the offset of an event from the top-left | |
// of the page. | |
eventoffset: function(e) { | |
var posx = 0; | |
var posy = 0; | |
if (!e) { e = window.event; } | |
if (e.pageX || e.pageY) { | |
// Good browsers | |
return { | |
x: e.pageX, | |
y: e.pageY | |
}; | |
} else if (e.clientX || e.clientY) { | |
// Internet Explorer | |
var doc = document.documentElement, body = document.body; | |
var htmlComputed = document.body.parentNode.currentStyle; | |
var topMargin = parseInt(htmlComputed.marginTop, 10) || 0; | |
var leftMargin = parseInt(htmlComputed.marginLeft, 10) || 0; | |
return { | |
x: e.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - | |
(doc && doc.clientLeft || body && body.clientLeft || 0) + leftMargin, | |
y: e.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - | |
(doc && doc.clientTop || body && body.clientTop || 0) + topMargin | |
}; | |
} else if (e.touches && e.touches.length === 1) { | |
// Touch browsers | |
return { | |
x: e.touches[0].pageX, | |
y: e.touches[0].pageY | |
}; | |
} | |
}, | |
// Ripped from underscore.js | |
// Internal function used to implement `_.throttle` and `_.debounce`. | |
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. | |
throttle: function(func, wait) { | |
return this.limit(func, wait, false); | |
} | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// Attribution | |
// ----------- | |
// Attribution wrapper for Modest Maps. | |
wax.mm.attribution = function(map, tilejson) { | |
tilejson = tilejson || {}; | |
var a, // internal attribution control | |
attribution = {}; | |
attribution.element = function() { | |
return a.element(); | |
}; | |
attribution.appendTo = function(elem) { | |
wax.u.$(elem).appendChild(a.element()); | |
return this; | |
}; | |
attribution.init = function() { | |
a = wax.attribution(); | |
a.content(tilejson.attribution); | |
a.element().className = 'wax-attribution wax-mm'; | |
return this; | |
}; | |
return attribution.init(); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// Box Selector | |
// ------------ | |
wax.mm.boxselector = function(map, tilejson, opts) { | |
var corner = null, | |
nearCorner = null, | |
callback = ((typeof opts === 'function') ? | |
opts : | |
opts.callback), | |
boxDiv, | |
style, | |
borderWidth = 0, | |
horizontal = false, // Whether the resize is horizontal | |
vertical = false, | |
edge = 5, // Distance from border sensitive to resizing | |
addEvent = MM.addEvent, | |
removeEvent = MM.removeEvent, | |
box, | |
boxselector = {}; | |
function getMousePoint(e) { | |
// start with just the mouse (x, y) | |
var point = new MM.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; | |
} | |
function mouseDown(e) { | |
if (!e.shiftKey) return; | |
corner = nearCorner = getMousePoint(e); | |
horizontal = vertical = true; | |
style.left = corner.x + 'px'; | |
style.top = corner.y + 'px'; | |
style.width = style.height = 0; | |
addEvent(document, 'mousemove', mouseMove); | |
addEvent(document, 'mouseup', mouseUp); | |
map.parent.style.cursor = 'crosshair'; | |
return MM.cancelEvent(e); | |
} | |
// Resize existing box | |
function mouseDownResize(e) { | |
var point = getMousePoint(e), | |
TL = { | |
x: parseInt(boxDiv.offsetLeft, 10), | |
y: parseInt(boxDiv.offsetTop, 10) | |
}, | |
BR = { | |
x: TL.x + parseInt(boxDiv.offsetWidth, 10), | |
y: TL.y + parseInt(boxDiv.offsetHeight, 10) | |
}; | |
// Determine whether resize is horizontal, vertical or both | |
horizontal = point.x - TL.x <= edge || BR.x - point.x <= edge; | |
vertical = point.y - TL.y <= edge || BR.y - point.y <= edge; | |
if (vertical || horizontal) { | |
corner = { | |
x: (point.x - TL.x < BR.x - point.x) ? BR.x : TL.x, | |
y: (point.y - TL.y < BR.y - point.y) ? BR.y : TL.y | |
}; | |
nearCorner = { | |
x: (point.x - TL.x < BR.x - point.x) ? TL.x : BR.x, | |
y: (point.y - TL.y < BR.y - point.y) ? TL.y : BR.y | |
}; | |
addEvent(document, 'mousemove', mouseMove); | |
addEvent(document, 'mouseup', mouseUp); | |
return MM.cancelEvent(e); | |
} | |
} | |
function mouseMove(e) { | |
var point = getMousePoint(e); | |
style.display = 'block'; | |
if (horizontal) { | |
style.left = (point.x < corner.x ? point.x : corner.x) + 'px'; | |
style.width = Math.abs(point.x - corner.x) - 2 * borderWidth + 'px'; | |
} | |
if (vertical) { | |
style.top = (point.y < corner.y ? point.y : corner.y) + 'px'; | |
style.height = Math.abs(point.y - corner.y) - 2 * borderWidth + 'px'; | |
} | |
changeCursor(point, map.parent); | |
return MM.cancelEvent(e); | |
} | |
function mouseUp(e) { | |
var point = getMousePoint(e), | |
l1 = map.pointLocation( new MM.Point( | |
horizontal ? point.x : nearCorner.x, | |
vertical? point.y : nearCorner.y | |
)); | |
l2 = map.pointLocation(corner); | |
// Format coordinates like mm.map.getExtent(). | |
boxselector.extent([ | |
new MM.Location( | |
Math.max(l1.lat, l2.lat), | |
Math.min(l1.lon, l2.lon)), | |
new MM.Location( | |
Math.min(l1.lat, l2.lat), | |
Math.max(l1.lon, l2.lon)) | |
]); | |
removeEvent(document, 'mousemove', mouseMove); | |
removeEvent(document, 'mouseup', mouseUp); | |
map.parent.style.cursor = 'auto'; | |
} | |
function mouseMoveCursor(e) { | |
changeCursor(getMousePoint(e), boxDiv); | |
} | |
// Set resize cursor if mouse is on edge | |
function changeCursor(point, elem) { | |
var TL = { | |
x: parseInt(boxDiv.offsetLeft, 10), | |
y: parseInt(boxDiv.offsetTop, 10) | |
}, | |
BR = { | |
x: TL.x + parseInt(boxDiv.offsetWidth, 10), | |
y: TL.y + parseInt(boxDiv.offsetHeight, 10) | |
}; | |
// Build cursor style string | |
var prefix = ''; | |
if (point.y - TL.y <= edge) prefix = 'n'; | |
else if (BR.y - point.y <= edge) prefix = 's'; | |
if (point.x - TL.x <= edge) prefix += 'w'; | |
else if (BR.x - point.x <= edge) prefix += 'e'; | |
if (prefix !== '') prefix += '-resize'; | |
elem.style.cursor = prefix; | |
} | |
function drawbox(map, e) { | |
if (!boxDiv || !box) return; | |
var br = map.locationPoint(box[1]), | |
tl = map.locationPoint(box[0]), | |
style = boxDiv.style; | |
style.display = 'block'; | |
style.height = 'auto'; | |
style.width = 'auto'; | |
style.left = Math.max(0, tl.x) + 'px'; | |
style.top = Math.max(0, tl.y) + 'px'; | |
style.right = Math.max(0, map.dimensions.x - br.x) + 'px'; | |
style.bottom = Math.max(0, map.dimensions.y - br.y) + 'px'; | |
} | |
boxselector.extent = function(x, silent) { | |
if (!x) return box; | |
box = [ | |
new MM.Location( | |
Math.max(x[0].lat, x[1].lat), | |
Math.min(x[0].lon, x[1].lon)), | |
new MM.Location( | |
Math.min(x[0].lat, x[1].lat), | |
Math.max(x[0].lon, x[1].lon)) | |
]; | |
drawbox(map); | |
if (!silent) callback(box); | |
}; | |
boxselector.add = function(map) { | |
boxDiv = boxDiv || document.createElement('div'); | |
boxDiv.id = map.parent.id + '-boxselector-box'; | |
boxDiv.className = 'boxselector-box'; | |
map.parent.appendChild(boxDiv); | |
style = boxDiv.style; | |
borderWidth = parseInt(window.getComputedStyle(boxDiv).borderWidth, 10); | |
addEvent(map.parent, 'mousedown', mouseDown); | |
addEvent(boxDiv, 'mousedown', mouseDownResize); | |
addEvent(map.parent, 'mousemove', mouseMoveCursor); | |
map.addCallback('drawn', drawbox); | |
return this; | |
}; | |
boxselector.remove = function() { | |
map.parent.removeChild(boxDiv); | |
removeEvent(map.parent, 'mousedown', mouseDown); | |
removeEvent(boxDiv, 'mousedown', mouseDownResize); | |
removeEvent(map.parent, 'mousemove', mouseMoveCursor); | |
map.removeCallback('drawn', drawbox); | |
}; | |
return boxselector.add(map); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
wax._ = {}; | |
// Bandwidth Detection | |
// ------------------ | |
wax.mm.bwdetect = function(map, options) { | |
options = options || {}; | |
var lowpng = options.png || '.png128', | |
lowjpg = options.jpg || '.jpg70', | |
bw = false; | |
wax._.bw_png = lowpng; | |
wax._.bw_jpg = lowjpg; | |
return wax.bwdetect(options, function(x) { | |
wax._.bw = !x; | |
for (var i = 0; i < map.layers.length; i++) { | |
if (map.getLayerAt(i).provider instanceof wax.mm.connector) { | |
map.getLayerAt(i).setProvider(map.getLayerAt(i).provider); | |
} | |
} | |
}); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// Fullscreen | |
// ---------- | |
// A simple fullscreen control for Modest Maps | |
// 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. | |
wax.mm.fullscreen = function(map) { | |
// true: fullscreen | |
// false: minimized | |
var fullscreened = false, | |
fullscreen = {}, | |
a, | |
body = document.body, | |
smallSize; | |
function click(e) { | |
if (e) e.stop(); | |
if (fullscreened) { | |
fullscreen.original(); | |
} else { | |
fullscreen.full(); | |
} | |
} | |
function ss(w, h) { | |
map.dimensions = new MM.Point(w, h); | |
map.parent.style.width = Math.round(map.dimensions.x) + 'px'; | |
map.parent.style.height = Math.round(map.dimensions.y) + 'px'; | |
map.dispatchCallback('resized', map.dimensions); | |
} | |
// 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. | |
fullscreen.add = function(map) { | |
a = document.createElement('a'); | |
a.className = 'wax-fullscreen'; | |
a.href = '#fullscreen'; | |
a.innerHTML = 'fullscreen'; | |
bean.add(a, 'click', click); | |
return this; | |
}; | |
fullscreen.full = function() { | |
if (fullscreened) { return; } else { fullscreened = true; } | |
smallSize = [map.parent.offsetWidth, map.parent.offsetHeight]; | |
map.parent.className += ' wax-fullscreen-map'; | |
body.className += ' wax-fullscreen-view'; | |
ss(map.parent.offsetWidth, map.parent.offsetHeight); | |
}; | |
fullscreen.original = function() { | |
if (!fullscreened) { return; } else { fullscreened = false; } | |
map.parent.className = map.parent.className.replace(' wax-fullscreen-map', ''); | |
body.className = body.className.replace(' wax-fullscreen-view', ''); | |
ss(smallSize[0], smallSize[1]); | |
}; | |
fullscreen.appendTo = function(elem) { | |
wax.u.$(elem).appendChild(a); | |
return this; | |
}; | |
return fullscreen.add(map); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
wax.mm.hash = function(map) { | |
return wax.hash({ | |
getCenterZoom: 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('/'); | |
}, | |
setCenterZoom: function setCenterZoom(args) { | |
map.setCenterZoom( | |
new MM.Location(args[1], args[2]), | |
args[0]); | |
}, | |
bindChange: function(fn) { | |
map.addCallback('drawn', fn); | |
}, | |
unbindChange: function(fn) { | |
map.removeCallback('drawn', fn); | |
} | |
}); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
wax.mm.interaction = function() { | |
var dirty = false, | |
_grid, | |
map, | |
clearingEvents = ['zoomed', 'panned', 'centered', | |
'extentset', 'resized', 'drawn']; | |
function grid() { | |
var zoomLayer = map.getLayerAt(0) | |
.levels[Math.round(map.getZoom())]; | |
if (!dirty && _grid !== undefined && _grid.length) { | |
return _grid; | |
} else { | |
_grid = (function(t) { | |
var o = []; | |
for (var key in t) { | |
if (t[key].parentNode === zoomLayer) { | |
var offset = wax.u.offset(t[key]); | |
o.push([ | |
offset.top, | |
offset.left, | |
t[key] | |
]); | |
} | |
} | |
return o; | |
})(map.getLayerAt(0).tiles); | |
return _grid; | |
} | |
} | |
function setdirty() { dirty = true; } | |
function attach(x) { | |
if (!arguments.length) return map; | |
map = x; | |
for (var i = 0; i < clearingEvents.length; i++) { | |
map.addCallback(clearingEvents[i], setdirty); | |
} | |
} | |
function detach(x) { | |
for (var i = 0; i < clearingEvents.length; i++) { | |
map.removeCallback(clearingEvents[i], setdirty); | |
} | |
} | |
return wax.interaction() | |
.attach(attach) | |
.detach(detach) | |
.parent(function() { | |
return map.parent; | |
}) | |
.grid(grid); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// LatLng | |
// ------ | |
// Show the current cursor position in | |
// lat/long | |
wax.mm.latlngtooltip = function(map) { | |
var tt, // tooltip | |
_down = false, | |
latlng = {}; | |
function getMousePoint(e) { | |
// start with just the mouse (x, y) | |
var point = new MM.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; | |
} | |
function onDown(e) { | |
console.log('here'); | |
_down = true; | |
} | |
function onUp(e) { | |
_down = false; | |
} | |
function onMove(e) { | |
if (!e.shiftKey || _down) { | |
if (tt.parentNode === map.parent) { | |
map.parent.removeChild(tt); | |
} | |
return; | |
} | |
var pt = getMousePoint(e), | |
ll = map.pointLocation(pt), | |
fmt = ll.lat.toFixed(2) + ', ' + ll.lon.toFixed(2); | |
tt.innerHTML = fmt; | |
pt.scale = pt.width = pt.height = 1; | |
pt.x += 10; | |
MM.moveElement(tt, pt); | |
map.parent.appendChild(tt); | |
} | |
latlng.add = function() { | |
MM.addEvent(map.parent, 'mousemove', onMove); | |
MM.addEvent(map.parent, 'mousedown', onDown); | |
MM.addEvent(map.parent, 'mouseup', onUp); | |
tt = document.createElement('div'); | |
tt.className = 'wax-latlngtooltip'; | |
return this; | |
}; | |
latlng.remove = function() { | |
MM.removeEvent(map.parent, 'mousemove', onMove); | |
MM.removeEvent(map.parent, 'mousedown', onDown); | |
MM.removeEvent(map.parent, 'mouseup', onUp); | |
return this; | |
}; | |
return latlng.add(); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// Legend Control | |
// -------------- | |
// The Modest Maps version of this control is a very, very | |
// light wrapper around the `/lib` code for legends. | |
wax.mm.legend = function(map, tilejson) { | |
tilejson = tilejson || {}; | |
var l, // parent legend | |
legend = {}; | |
legend.add = function() { | |
l = wax.legend() | |
.content(tilejson.legend || ''); | |
return this; | |
}; | |
legend.content = function(x) { | |
if (x) l.content(x.legend || ''); | |
}; | |
legend.element = function() { | |
return l.element(); | |
}; | |
legend.appendTo = function(elem) { | |
wax.u.$(elem).appendChild(l.element()); | |
return this; | |
}; | |
return legend.add(); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// 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, tilejson, opts) { | |
var mouseDownPoint = null, | |
mouseUpPoint = null, | |
tolerance = 5, | |
overlayDiv, | |
pointselector = {}, | |
locations = []; | |
var callback = (typeof opts === 'function') ? | |
opts : | |
opts.callback; | |
// Create a `com.modestmaps.Point` from a screen event, like a click. | |
function makePoint(e) { | |
var coords = wax.u.eventoffset(e); | |
var point = new MM.Point(coords.x, coords.y); | |
// correct for scrolled document | |
// 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 | |
for (var node = map.parent; node; node = node.offsetParent) { | |
point.x -= node.offsetLeft; | |
point.y -= node.offsetTop; | |
} | |
return point; | |
} | |
// 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; | |
} | |
// Attach this control to a map by registering callbacks | |
// and adding the overlay | |
// Redraw the points when the map is moved, so that they stay in the | |
// correct geographic locations. | |
function drawPoints() { | |
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 | |
bean.add(locations[i].pointDiv, 'mouseup', | |
(function selectPointWrap(e) { | |
var l = locations[i]; | |
return function(e) { | |
MM.removeEvent(map.parent, 'mouseup', mouseUp); | |
pointselector.deleteLocation(l, e); | |
}; | |
})()); | |
map.parent.appendChild(locations[i].pointDiv); | |
} | |
locations[i].pointDiv.style.left = point.x + 'px'; | |
locations[i].pointDiv.style.top = point.y + 'px'; | |
} | |
} | |
function mouseDown(e) { | |
mouseDownPoint = makePoint(e); | |
bean.add(map.parent, 'mouseup', mouseUp); | |
} | |
// Remove the awful circular reference from locations. | |
// TODO: This function should be made unnecessary by not having it. | |
function mouseUp(e) { | |
if (!mouseDownPoint) return; | |
mouseUpPoint = makePoint(e); | |
if (MM.Point.distance(mouseDownPoint, mouseUpPoint) < tolerance) { | |
pointselector.addLocation(map.pointLocation(mouseDownPoint)); | |
callback(cleanLocations(locations)); | |
} | |
mouseDownPoint = null; | |
} | |
// 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. | |
pointselector.addLocation = function(location) { | |
locations.push(location); | |
drawPoints(); | |
callback(cleanLocations(locations)); | |
}; | |
pointselector.locations = function(x) { | |
return locations; | |
}; | |
pointselector.add = function(map) { | |
bean.add(map.parent, 'mousedown', mouseDown); | |
map.addCallback('drawn', drawPoints); | |
return this; | |
}; | |
pointselector.remove = function(map) { | |
bean.remove(map.parent, 'mousedown', mouseDown); | |
map.removeCallback('drawn', drawPoints); | |
for (var i = locations.length - 1; i > -1; i--) { | |
pointselector.deleteLocation(locations[i]); | |
} | |
return this; | |
}; | |
pointselector.deleteLocation = function(location, e) { | |
if (!e || confirm('Delete this point?')) { | |
location.pointDiv.parentNode.removeChild(location.pointDiv); | |
locations.splice(wax.u.indexOf(locations, location), 1); | |
callback(cleanLocations(locations)); | |
} | |
}; | |
return pointselector.add(map); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// ZoomBox | |
// ------- | |
// An OL-style ZoomBox control, from the Modest Maps example. | |
wax.mm.zoombox = function(map) { | |
// TODO: respond to resize | |
var zoombox = {}, | |
drawing = false, | |
box, | |
mouseDownPoint = null; | |
function getMousePoint(e) { | |
// start with just the mouse (x, y) | |
var point = new MM.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; | |
} | |
function mouseUp(e) { | |
if (!drawing) return; | |
drawing = false; | |
var point = getMousePoint(e); | |
var l1 = map.pointLocation(point), | |
l2 = map.pointLocation(mouseDownPoint); | |
map.setExtent([l1, l2]); | |
box.style.display = 'none'; | |
MM.removeEvent(map.parent, 'mousemove', mouseMove); | |
MM.removeEvent(map.parent, 'mouseup', mouseUp); | |
map.parent.style.cursor = 'auto'; | |
} | |
function mouseDown(e) { | |
if (!(e.shiftKey && !this.drawing)) return; | |
drawing = true; | |
mouseDownPoint = getMousePoint(e); | |
box.style.left = mouseDownPoint.x + 'px'; | |
box.style.top = mouseDownPoint.y + 'px'; | |
MM.addEvent(map.parent, 'mousemove', mouseMove); | |
MM.addEvent(map.parent, 'mouseup', mouseUp); | |
map.parent.style.cursor = 'crosshair'; | |
return MM.cancelEvent(e); | |
} | |
function mouseMove(e) { | |
if (!drawing) return; | |
var point = 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 MM.cancelEvent(e); | |
} | |
zoombox.add = function(map) { | |
// Use a flag to determine whether the zoombox is currently being | |
// drawn. Necessary only for IE because `mousedown` is triggered | |
// twice. | |
box = box || document.createElement('div'); | |
box.id = map.parent.id + '-zoombox-box'; | |
box.className = 'zoombox-box'; | |
map.parent.appendChild(box); | |
MM.addEvent(map.parent, 'mousedown', mouseDown); | |
return this; | |
}; | |
zoombox.remove = function() { | |
map.parent.removeChild(box); | |
MM.removeEvent(map.parent, 'mousedown', mouseDown); | |
}; | |
return zoombox.add(map); | |
}; | |
wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// 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. | |
wax.mm.zoomer = function(map) { | |
var zoomin = document.createElement('a'); | |
zoomin.innerHTML = '+'; | |
zoomin.href = '#'; | |
zoomin.className = 'zoomer zoomin'; | |
bean.add(zoomin, 'mousedown dblclick', function(e) { | |
e.stop(); | |
}); | |
bean.add(zoomin, 'click', function(e) { | |
e.stop(); | |
map.zoomIn(); | |
}, false); | |
var zoomout = document.createElement('a'); | |
zoomout.innerHTML = '-'; | |
zoomout.href = '#'; | |
zoomout.className = 'zoomer zoomout'; | |
bean.add(zoomout, 'mousedown dblclick', function(e) { | |
e.stop(); | |
}); | |
bean.add(zoomout, 'click', function(e) { | |
e.stop(); | |
map.zoomOut(); | |
}); | |
var zoomer = { | |
add: function(map) { | |
map.addCallback('drawn', function(map, e) { | |
if (map.coordinate.zoom === map.coordLimits[0].zoom) { | |
zoomout.className = 'zoomer zoomout zoomdisabled'; | |
} else if (map.coordinate.zoom === map.coordLimits[1].zoom) { | |
zoomin.className = 'zoomer zoomin zoomdisabled'; | |
} else { | |
zoomin.className = 'zoomer zoomin'; | |
zoomout.className = 'zoomer zoomout'; | |
} | |
}); | |
return this; | |
}, | |
appendTo: function(elem) { | |
wax.u.$(elem).appendChild(zoomin); | |
wax.u.$(elem).appendChild(zoomout); | |
return this; | |
} | |
}; | |
return zoomer.add(map); | |
}; | |
var wax = wax || {}; | |
wax.mm = wax.mm || {}; | |
// A layer connector for Modest Maps conformant to TileJSON | |
// https://github.com/mapbox/tilejson | |
wax.mm._provider = function(options) { | |
this.options = { | |
tiles: options.tiles, | |
scheme: options.scheme || 'xyz', | |
minzoom: options.minzoom || 0, | |
maxzoom: options.maxzoom || 22, | |
bounds: options.bounds || [-180, -90, 180, 90] | |
}; | |
}; | |
wax.mm._provider.prototype = { | |
outerLimits: function() { | |
return [ | |
this.locationCoordinate( | |
new MM.Location( | |
this.options.bounds[0], | |
this.options.bounds[1])).zoomTo(this.options.minzoom), | |
this.locationCoordinate( | |
new MM.Location( | |
this.options.bounds[2], | |
this.options.bounds[3])).zoomTo(this.options.maxzoom) | |
]; | |
}, | |
getTile: function(c) { | |
if (!(coord = this.sourceCoordinate(c))) return null; | |
if (coord.zoom < this.options.minzoom || coord.zoom > this.options.maxzoom) return null; | |
coord.row = (this.options.scheme === 'tms') ? | |
Math.pow(2, coord.zoom) - coord.row - 1 : | |
coord.row; | |
var u = this.options.tiles[parseInt(Math.pow(2, coord.zoom) * coord.row + coord.column, 10) % | |
this.options.tiles.length] | |
.replace('{z}', coord.zoom.toFixed(0)) | |
.replace('{x}', coord.column.toFixed(0)) | |
.replace('{y}', coord.row.toFixed(0)); | |
if (wax._ && wax._.bw) { | |
u = u.replace('.png', wax._.bw_png) | |
.replace('.jpg', wax._.bw_jpg); | |
} | |
return u; | |
} | |
}; | |
if (MM) { | |
MM.extend(wax.mm._provider, MM.MapProvider); | |
} | |
wax.mm.connector = function(options) { | |
var x = new wax.mm._provider(options); | |
return new MM.Layer(x); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment