torque/lib/wax.g.js
Andrew W Hill 1d377496e0 all add
2012-05-16 16:33:56 -07:00

2766 lines
84 KiB
JavaScript

/* wax - 4.1.3 - 1.0.4-477-gba3b176 */
/*!
* Reqwest! A general purpose XHR connection manager
* copyright Dustin Diaz 2011
* https://github.com/ded/reqwest
* license MIT
*/
!function(context,win){function serial(a){var b=a.name;if(a.disabled||!b)return"";b=enc(b);switch(a.tagName.toLowerCase()){case"input":switch(a.type){case"reset":case"button":case"image":case"file":return"";case"checkbox":case"radio":return a.checked?b+"="+(a.value?enc(a.value):!0)+"&":"";default:return b+"="+(a.value?enc(a.value):"")+"&"}break;case"textarea":return b+"="+enc(a.value)+"&";case"select":return b+"="+enc(a.options[a.selectedIndex].value)+"&"}return""}function enc(a){return encodeURIComponent(a)}function reqwest(a,b){return new Reqwest(a,b)}function init(o,fn){function error(a){o.error&&o.error(a),complete(a)}function success(resp){o.timeout&&clearTimeout(self.timeout)&&(self.timeout=null);var r=resp.responseText;if(r)switch(type){case"json":resp=win.JSON?win.JSON.parse(r):eval("("+r+")");break;case"js":resp=eval(r);break;case"html":resp=r}fn(resp),o.success&&o.success(resp),complete(resp)}function complete(a){o.complete&&o.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(),error()},o.timeout)),this.request=getRequest(o,success,error)}function setType(a){if(/\.json$/.test(a))return"json";if(/\.jsonp$/.test(a))return"jsonp";if(/\.js$/.test(a))return"js";if(/\.html?$/.test(a))return"html";if(/\.xml$/.test(a))return"xml";return"js"}function Reqwest(a,b){this.o=a,this.fn=b,init.apply(this,arguments)}function getRequest(a,b,c){if(a.type!="jsonp"){var f=xhr();f.open(a.method||"GET",typeof a=="string"?a:a.url,!0),setHeaders(f,a),f.onreadystatechange=handleReadyState(f,b,c),a.before&&a.before(f),f.send(a.data||null);return f}var d=doc.createElement("script"),e=0;win[getCallbackName(a)]=generalCallback,d.type="text/javascript",d.src=a.url,d.async=!0,d.onload=d.onreadystatechange=function(){if(d[readyState]&&d[readyState]!=="complete"&&d[readyState]!=="loaded"||e)return!1;d.onload=d.onreadystatechange=null,a.success&&a.success(lastValue),lastValue=undefined,head.removeChild(d),e=1},head.appendChild(d)}function generalCallback(a){lastValue=a}function getCallbackName(a){var b=a.jsonpCallback||"callback";if(a.url.slice(-(b.length+2))==b+"=?"){var c="reqwest_"+uniqid++;a.url=a.url.substr(0,a.url.length-1)+c;return c}var d=new RegExp(b+"=([\\w]+)");return a.url.match(d)[1]}function setHeaders(a,b){var c=b.headers||{};c.Accept=c.Accept||"text/javascript, text/html, application/xml, text/xml, */*",b.crossOrigin||(c["X-Requested-With"]=c["X-Requested-With"]||"XMLHttpRequest"),c[contentType]=c[contentType]||"application/x-www-form-urlencoded";for(var d in c)c.hasOwnProperty(d)&&a.setRequestHeader(d,c[d],!1)}function handleReadyState(a,b,c){return function(){a&&a[readyState]==4&&(twoHundo.test(a.status)?b(a):c(a))}}var twoHundo=/^20\d$/,doc=document,byTag="getElementsByTagName",readyState="readyState",contentType="Content-Type",head=doc[byTag]("head")[0],uniqid=0,lastValue,xhr="XMLHttpRequest"in win?function(){return new XMLHttpRequest}:function(){return new ActiveXObject("Microsoft.XMLHTTP")};Reqwest.prototype={abort:function(){this.request.abort()},retry:function(){init.call(this,this.o,this.fn)}},reqwest.serialize=function(a){var b=[a[byTag]("input"),a[byTag]("select"),a[byTag]("textarea")],c=[],d,e;for(d=0,l=b.length;d<l;++d)for(e=0,l2=b[d].length;e<l2;++e)c.push(serial(b[d][e]));return c.join("").replace(/&$/,"")},reqwest.serializeArray=function(a){for(var b=this.serialize(a).split("&"),c=0,d=b.length,e=[],f;c<d;c++)b[c]&&(f=b[c].split("="))&&e.push({name:f[0],value:f[1]});return e};var old=context.reqwest;reqwest.noConflict=function(){context.reqwest=old;return this},typeof module!="undefined"?module.exports=reqwest:context.reqwest=reqwest}(this,window)// 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 mikesamuel@gmail.com
* @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: &pi; is different from &Pi;
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 &lt; 2 &amp;&AMP; 4 &gt; 3&#10;')
* # '1 < 2 && 4 > 3\n'
* $ unescapeEntities('&lt;&lt <- unfinished entity&gt;')
* # '<&lt <- unfinished entity>'
* $ unescapeEntities('/foo?bar=baz&copy=true') // & often unescaped in URLS
* # '/foo?bar=baz&copy=true'
* $ unescapeEntities('pi=&pi;&#x3c0;, 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.
* # '&#34;&lt;&lt;&amp;&#61;&#61;&amp;&gt;&gt;&#34;'
* $ escapeAttrib('Hello <World>!')
* # 'Hello &lt;World&gt;!'
* }
*/
function escapeAttrib(s) {
// Escaping '=' defangs many UTF-7 and SGML short-tag attacks.
return s.replace(ampRe, '&amp;').replace(ltRe, '&lt;').replace(gtRe, '&gt;')
.replace(quotRe, '&#34;').replace(eqRe, '&#61;');
}
/**
* Escape entities in RCDATA that can be escaped without changing the meaning.
* {@updoc
* $ normalizeRCData('1 < 2 &&amp; 3 > 4 &amp;& 5 &lt; 7&8')
* # '1 &lt; 2 &amp;&amp; 3 &gt; 4 &amp;&amp; 5 &lt; 7&amp;8'
* }
*/
function normalizeRCData(rcdata) {
return rcdata
.replace(looseAmpRe, '&amp;$1')
.replace(ltRe, '&lt;')
.replace(gtRe, '&gt;');
}
// 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 === '<' ? '&lt;' : ch === '>' ? '&gt;' : '&amp;',
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;
/*
mustache.js — Logic-less templates in JavaScript
See http://mustache.github.com/ for more info.
*/
var Mustache = function() {
var regexCache = {};
var Renderer = function() {};
Renderer.prototype = {
otag: "{{",
ctag: "}}",
pragmas: {},
buffer: [],
pragmas_implemented: {
"IMPLICIT-ITERATOR": true
},
context: {},
render: function(template, context, partials, in_recursion) {
// reset buffer & set context
if(!in_recursion) {
this.context = context;
this.buffer = []; // TODO: make this non-lazy
}
// fail fast
if(!this.includes("", template)) {
if(in_recursion) {
return template;
} else {
this.send(template);
return;
}
}
// get the pragmas together
template = this.render_pragmas(template);
// render the template
var html = this.render_section(template, context, partials);
// render_section did not find any sections, we still need to render the tags
if (html === false) {
html = this.render_tags(template, context, partials, in_recursion);
}
if (in_recursion) {
return html;
} else {
this.sendLines(html);
}
},
/*
Sends parsed lines
*/
send: function(line) {
if(line !== "") {
this.buffer.push(line);
}
},
sendLines: function(text) {
if (text) {
var lines = text.split("\n");
for (var i = 0; i < lines.length; i++) {
this.send(lines[i]);
}
}
},
/*
Looks for %PRAGMAS
*/
render_pragmas: function(template) {
// no pragmas
if(!this.includes("%", template)) {
return template;
}
var that = this;
var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) {
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
});
return template.replace(regex, function(match, pragma, options) {
if(!that.pragmas_implemented[pragma]) {
throw({message:
"This implementation of mustache doesn't understand the '" +
pragma + "' pragma"});
}
that.pragmas[pragma] = {};
if(options) {
var opts = options.split("=");
that.pragmas[pragma][opts[0]] = opts[1];
}
return "";
// ignore unknown pragmas silently
});
},
/*
Tries to find a partial in the curent scope and render it
*/
render_partial: function(name, context, partials) {
name = this.trim(name);
if(!partials || partials[name] === undefined) {
throw({message: "unknown_partial '" + name + "'"});
}
if(typeof(context[name]) != "object") {
return this.render(partials[name], context, partials, true);
}
return this.render(partials[name], context[name], partials, true);
},
/*
Renders inverted (^) and normal (#) sections
*/
render_section: function(template, context, partials) {
if(!this.includes("#", template) && !this.includes("^", template)) {
// did not render anything, there were no sections
return false;
}
var that = this;
var regex = this.getCachedRegex("render_section", function(otag, ctag) {
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
return new RegExp(
"^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
otag + // {{
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
ctag + // }}
"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
otag + // {{
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
ctag + // }}
"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
"g");
});
// for each {{#foo}}{{/foo}} section do...
return template.replace(regex, function(match, before, type, name, content, after) {
// before contains only tags, no sections
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
// after may contain both sections and tags, so use full rendering function
renderedAfter = after ? that.render(after, context, partials, true) : "",
// will be computed below
renderedContent,
value = that.find(name, context);
if (type === "^") { // inverted section
if (!value || that.is_array(value) && value.length === 0) {
// false or empty list, render it
renderedContent = that.render(content, context, partials, true);
} else {
renderedContent = "";
}
} else if (type === "#") { // normal section
if (that.is_array(value)) { // Enumerable, Let's loop!
renderedContent = that.map(value, function(row) {
return that.render(content, that.create_context(row), partials, true);
}).join("");
} else if (that.is_object(value)) { // Object, Use it as subcontext!
renderedContent = that.render(content, that.create_context(value),
partials, true);
} else if (typeof value === "function") {
// higher order section
renderedContent = value.call(context, content, function(text) {
return that.render(text, context, partials, true);
});
} else if (value) { // boolean section
renderedContent = that.render(content, context, partials, true);
} else {
renderedContent = "";
}
}
return renderedBefore + renderedContent + renderedAfter;
});
},
/*
Replace {{foo}} and friends with values from our view
*/
render_tags: function(template, context, partials, in_recursion) {
// tit for tat
var that = this;
var new_regex = function() {
return that.getCachedRegex("render_tags", function(otag, ctag) {
return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g");
});
};
var regex = new_regex();
var tag_replace_callback = function(match, operator, name) {
switch(operator) {
case "!": // ignore comments
return "";
case "=": // set new delimiters, rebuild the replace regexp
that.set_delimiters(name);
regex = new_regex();
return "";
case ">": // render partial
return that.render_partial(name, context, partials);
case "{": // the triple mustache is unescaped
return that.find(name, context);
default: // escape the value
return that.escape(that.find(name, context));
}
};
var lines = template.split("\n");
for(var i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
if(!in_recursion) {
this.send(lines[i]);
}
}
if(in_recursion) {
return lines.join("\n");
}
},
set_delimiters: function(delimiters) {
var dels = delimiters.split(" ");
this.otag = this.escape_regex(dels[0]);
this.ctag = this.escape_regex(dels[1]);
},
escape_regex: function(text) {
// thank you Simon Willison
if(!arguments.callee.sRE) {
var specials = [
'/', '.', '*', '+', '?', '|',
'(', ')', '[', ']', '{', '}', '\\'
];
arguments.callee.sRE = new RegExp(
'(\\' + specials.join('|\\') + ')', 'g'
);
}
return text.replace(arguments.callee.sRE, '\\$1');
},
/*
find `name` in current `context`. That is find me a value
from the view object
*/
find: function(name, context) {
name = this.trim(name);
// Checks whether a value is thruthy or false or 0
function is_kinda_truthy(bool) {
return bool === false || bool === 0 || bool;
}
var value;
if(is_kinda_truthy(context[name])) {
value = context[name];
} else if(is_kinda_truthy(this.context[name])) {
value = this.context[name];
}
if(typeof value === "function") {
return value.apply(context);
}
if(value !== undefined) {
return value;
}
// silently ignore unkown variables
return "";
},
// Utility methods
/* includes tag */
includes: function(needle, haystack) {
return haystack.indexOf(this.otag + needle) != -1;
},
/*
Does away with nasty characters
*/
escape: function(s) {
s = String(s === null ? "" : s);
return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
switch(s) {
case "&": return "&amp;";
case '"': return '&quot;';
case "'": return '&#39;';
case "<": return "&lt;";
case ">": return "&gt;";
default: return s;
}
});
},
// by @langalex, support for arrays of strings
create_context: function(_context) {
if(this.is_object(_context)) {
return _context;
} else {
var iterator = ".";
if(this.pragmas["IMPLICIT-ITERATOR"]) {
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
}
},
is_object: function(a) {
return a && typeof a == "object";
},
is_array: function(a) {
return Object.prototype.toString.call(a) === '[object Array]';
},
/*
Gets rid of leading and trailing whitespace
*/
trim: function(s) {
return s.replace(/^\s*|\s*$/g, "");
},
/*
Why, why, why? Because IE. Cry, cry cry.
*/
map: function(array, fn) {
if (typeof array.map == "function") {
return array.map(fn);
} else {
var r = [];
var l = array.length;
for(var i = 0; i < l; i++) {
r.push(fn(array[i]));
}
return r;
}
},
getCachedRegex: function(name, generator) {
var byOtag = regexCache[this.otag];
if (!byOtag) {
byOtag = regexCache[this.otag] = {};
}
var byCtag = byOtag[this.ctag];
if (!byCtag) {
byCtag = byOtag[this.ctag] = {};
}
var regex = byCtag[name];
if (!regex) {
regex = byCtag[name] = generator(this.otag, this.ctag);
}
return regex;
}
};
return({
name: "mustache.js",
version: "0.4.0-dev",
/*
Turns a template and view into HTML
*/
to_html: function(template, view, partials, send_fun) {
var renderer = new Renderer();
if(send_fun) {
renderer.send = send_fun;
}
renderer.render(template, view || {}, partials);
if(!send_fun) {
return renderer.buffer.join("\n");
}
}
});
}();
;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.set = function(content) {
if (typeof content === 'undefined') return;
container.innerHTML = html_sanitize(content, 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.GridInstance = function(grid_tile, formatter, 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 section of the mbtiles spec](https://github.com/mapbox/mbtiles-spec/blob/master/1.1/utfgrid.md)
// 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.
// * `options` options to give to the formatter: minimally having a `format`
// member, being `full`, `teaser`, or something else.
instance.tileFeature = function(x, y, tile_element, options) {
if (!grid_tile) return;
// IE problem here - though recoverable, for whatever reason
var offset = wax.util.offset(tile_element);
feature = this.gridFeature(x - offset.left, y - offset.top);
if (feature) return formatter.format(options, 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.GridManager = function(options) {
options = options || {};
var resolution = options.resolution || 4,
version = options.version || '1.1',
grid_tiles = {},
manager = {},
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 xyz = /(\d+)\/(\d+)\/(\d+)\.[\w\._]+/g.exec(url);
if (!xyz) return;
return template[parseInt(xyz[2], 10) % template.length]
.replace('{z}', xyz[1])
.replace('{x}', xyz[2])
.replace('{y}', 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.GridInstance(t, formatter, {
resolution: resolution || 4
}));
});
return manager;
};
manager.add = function(options) {
if (options.template) {
manager.template(options.template);
} else if (options.formatter) {
manager.formatter(options.formatter);
}
if (options.grids) {
manager.gridUrl(options.grids);
}
return this;
};
return manager.add(options);
};
wax = wax || {};
// Hash
// ----
wax.hash = function(options) {
options = options || {};
function getState() {
return location.hash.substring(1);
}
function pushState(state) {
location.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.util.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 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 this;
};
legend.add = function() {
container = document.createElement('div');
container.className = 'wax-legends';
element = document.createElement('div');
element.className = 'wax-legend';
element.style.display = 'none';
container.appendChild(element);
return this;
};
return legend.add();
};
// Like underscore's bind, except it runs a function
// with no arguments off of an object.
//
// var map = ...;
// w(map).melt(myFunction);
//
// is equivalent to
//
// var map = ...;
// myFunction(map);
//
var w = function(self) {
self.melt = function(func, obj) {
return func.apply(obj, [self, obj]);
};
return self;
};
var wax = wax || {};
wax.movetip = {};
wax.movetip = function(options) {
options = options || {};
var t = {},
_currentTooltip = undefined,
_context = undefined,
_animationOut = options.animationOut,
_animationIn = options.animationIn;
// Helper function to determine whether a given element is a wax popup.
function isPopup (el) {
return el && el.className.indexOf('wax-popup') !== -1;
}
function getTooltip(feature, context) {
var tooltip = document.createElement('div');
tooltip.className = 'wax-movetip';
tooltip.style.cssText = 'position:absolute;';
tooltip.innerHTML = feature;
context.appendChild(tooltip);
_context = context;
_tooltipOffset = wax.util.offset(tooltip);
_contextOffset = wax.util.offset(_context);
return tooltip;
}
function moveTooltip(e) {
if (!_currentTooltip) return;
var eo = wax.util.eventoffset(e);
_currentTooltip.className = 'wax-movetip';
// faux-positioning
if ((_tooltipOffset.height + eo.y) >
(_contextOffset.top + _contextOffset.height) &&
(_contextOffset.height > _tooltipOffset.height)) {
eo.y -= _tooltipOffset.height;
_currentTooltip.className += ' flip-y';
}
// faux-positioning
if ((_tooltipOffset.width + eo.x) >
(_contextOffset.left + _contextOffset.width)) {
eo.x -= _tooltipOffset.width;
_currentTooltip.className += ' flip-x';
}
_currentTooltip.style.left = eo.x + 'px';
_currentTooltip.style.top = eo.y + 'px';
}
// Hide a given tooltip.
function hideTooltip(el) {
if (!el) return;
var event,
remove = function() {
if (this.parentNode) this.parentNode.removeChild(this);
};
if (el.style['-webkit-transition'] !== undefined && _animationOut) {
event = 'webkitTransitionEnd';
} else if (el.style.MozTransition !== undefined && _animationOut) {
event = 'transitionend';
}
if (event) {
// This code assumes that transform-supporting browsers
// also support proper events. IE9 does both.
el.addEventListener(event, remove, false);
el.addEventListener('transitionend', remove, false);
el.className += ' ' + _animationOut;
} else {
if (el.parentNode) el.parentNode.removeChild(el);
}
}
// Expand a tooltip to be a "popup". Suspends all other tooltips from being
// shown until this popup is closed or another popup is opened.
function click(feature, context) {
// Hide any current tooltips.
if (_currentTooltip) {
hideTooltip(_currentTooltip);
_currentTooltip = undefined;
}
var tooltip = getTooltip(feature, context);
tooltip.className += ' wax-popup';
tooltip.innerHTML = feature;
var close = document.createElement('a');
close.href = '#close';
close.className = 'close';
close.innerHTML = 'Close';
tooltip.appendChild(close);
var closeClick = function(ev) {
hideTooltip(tooltip);
_currentTooltip = undefined;
ev.returnValue = false; // Prevents hash change.
if (ev.stopPropagation) ev.stopPropagation();
if (ev.preventDefault) ev.preventDefault();
return false;
};
// IE compatibility.
if (close.addEventListener) {
close.addEventListener('click', closeClick, false);
} else if (close.attachEvent) {
close.attachEvent('onclick', closeClick);
}
_currentTooltip = tooltip;
}
t.over = function(feature, context, e) {
if (!feature) return;
context.style.cursor = 'pointer';
if (isPopup(_currentTooltip)) {
return;
} else {
_currentTooltip = getTooltip(feature, context);
moveTooltip(e);
if (context.addEventListener) {
context.addEventListener('mousemove', moveTooltip);
}
}
};
// Hide all tooltips on this layer and show the first hidden tooltip on the
// highest layer underneath if found.
t.out = function(context) {
context.style.cursor = 'default';
if (isPopup(_currentTooltip)) {
return;
} else if (_currentTooltip) {
hideTooltip(_currentTooltip);
if (context.removeEventListener) {
context.removeEventListener('mousemove', moveTooltip);
}
_currentTooltip = undefined;
}
};
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(options) {
this._currentTooltip = undefined;
options = options || {};
if (options.animationOut) this.animationOut = options.animationOut;
if (options.animationIn) this.animationIn = options.animationIn;
};
// Helper function to determine whether a given element is a wax popup.
wax.tooltip.prototype.isPopup = function(el) {
return el && el.className.indexOf('wax-popup') !== -1;
};
// Get the active tooltip for a layer or create a new one if no tooltip exists.
// Hide any tooltips on layers underneath this one.
wax.tooltip.prototype.getTooltip = function(feature, context) {
var tooltip = document.createElement('div');
tooltip.className = 'wax-tooltip wax-tooltip-0';
tooltip.innerHTML = feature;
context.appendChild(tooltip);
return tooltip;
};
// Hide a given tooltip.
wax.tooltip.prototype.hideTooltip = function(el) {
if (!el) return;
var event,
remove = function() {
if (this.parentNode) this.parentNode.removeChild(this);
};
if (el.style['-webkit-transition'] !== undefined && this.animationOut) {
event = 'webkitTransitionEnd';
} else if (el.style.MozTransition !== undefined && this.animationOut) {
event = 'transitionend';
}
if (event) {
// This code assumes that transform-supporting browsers
// also support proper events. IE9 does both.
el.addEventListener(event, remove, false);
el.addEventListener('transitionend', remove, false);
el.className += ' ' + this.animationOut;
} else {
if (el.parentNode) el.parentNode.removeChild(el);
}
};
// Expand a tooltip to be a "popup". Suspends all other tooltips from being
// shown until this popup is closed or another popup is opened.
wax.tooltip.prototype.click = function(feature, context) {
// Hide any current tooltips.
if (this._currentTooltip) {
this.hideTooltip(this._currentTooltip);
this._currentTooltip = undefined;
}
var tooltip = this.getTooltip(feature, context);
tooltip.className += ' wax-popup';
tooltip.innerHTML = feature;
var close = document.createElement('a');
close.href = '#close';
close.className = 'close';
close.innerHTML = 'Close';
tooltip.appendChild(close);
var closeClick = wax.util.bind(function(ev) {
this.hideTooltip(tooltip);
this._currentTooltip = undefined;
ev.returnValue = false; // Prevents hash change.
if (ev.stopPropagation) ev.stopPropagation();
if (ev.preventDefault) ev.preventDefault();
return false;
}, this);
// IE compatibility.
if (close.addEventListener) {
close.addEventListener('click', closeClick, false);
} else if (close.attachEvent) {
close.attachEvent('onclick', closeClick);
}
this._currentTooltip = tooltip;
};
// Show a tooltip.
wax.tooltip.prototype.over = function(feature, context) {
if (!feature) return;
context.style.cursor = 'pointer';
if (this.isPopup(this._currentTooltip)) {
return;
} else {
this._currentTooltip = this.getTooltip(feature, context);
}
};
// Hide all tooltips on this layer and show the first hidden tooltip on the
// highest layer underneath if found.
wax.tooltip.prototype.out = function(context) {
context.style.cursor = 'default';
if (this.isPopup(this._currentTooltip)) {
return;
} else if (this._currentTooltip) {
this.hideTooltip(this._currentTooltip);
this._currentTooltip = undefined;
}
};
var wax = wax || {};
wax.util = wax.util || {};
// Utils are extracted from other libraries or
// written from scratch to plug holes in browser compatibility.
wax.util = {
// 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;
},
// From underscore, minus funcbind for now.
// Returns a version of a function that always has the second parameter,
// `obj`, as `this`.
bind: function(func, obj) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
return func.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
};
},
// From underscore
isString: function(obj) {
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
},
// 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;
},
// is this object an array?
isArray: Array.isArray || function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
},
// From underscore: reimplement the ECMA5 `Object.keys()` method
keys: Object.keys || function(obj) {
var ho = Object.prototype.hasOwnProperty;
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (ho.call(obj, key)) keys[keys.length] = key;
return keys;
},
// 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) var 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);
},
// parseUri 1.2.2
// Steven Levithan <stevenlevithan.com>
parseUri: function(str) {
var o = {
strictMode: false,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
},
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
while (i--) uri[o.key[i]] = m[i] || "";
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) uri[o.q.name][$1] = $2;
});
return uri;
},
// appends callback onto urls regardless of existing query params
addUrlData: function(url, data) {
url += (this.parseUri(url).query) ? '&' : '?';
return url += data;
}
};
wax = wax || {};
wax.g = wax.g || {};
// Attribution
// -----------
// Attribution wrapper for Google Maps.
wax.g.attribution = function(map, tilejson) {
tilejson = tilejson || {};
var a, // internal attribution control
attribution = {};
attribution.element = function() {
return a.element();
};
attribution.appendTo = function(elem) {
wax.util.$(elem).appendChild(a.element());
return this;
};
attribution.init = function() {
a = wax.attribution();
a.set(tilejson.attribution);
a.element().className = 'wax-attribution wax-g';
return this;
};
return attribution.init();
};
wax = wax || {};
wax.g = wax.g || {};
// Bandwidth Detection
// ------------------
wax.g.bwdetect = function(map, options) {
options = options || {};
var lowpng = options.png || '.png128',
lowjpg = options.jpg || '.jpg70';
// Create a low-bandwidth map type.
if (!map.mapTypes['mb-low']) {
var mb = map.mapTypes.mb;
var tilejson = {
tiles: [],
scheme: mb.options.scheme,
blankImage: mb.options.blankImage,
minzoom: mb.minZoom,
maxzoom: mb.maxZoom,
name: mb.name,
description: mb.description
};
for (var i = 0; i < mb.options.tiles.length; i++) {
tilejson.tiles.push(mb.options.tiles[i]
.replace('.png', lowpng)
.replace('.jpg', lowjpg));
}
m.mapTypes.set('mb-low', new wax.g.connector(tilejson));
}
return wax.bwdetect(options, function(bw) {
map.setMapTypeId(bw ? 'mb' : 'mb-low');
});
};
wax = wax || {};
wax.g = wax.g || {};
wax.g.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.lng().toFixed(precision)
].join('/');
},
setCenterZoom: function setCenterZoom(args) {
map.setCenter(new google.maps.LatLng(args[1], args[2]));
map.setZoom(args[0]);
},
bindChange: function(fn) {
google.maps.event.addListener(map, 'idle', fn);
},
unbindChange: function(fn) {
google.maps.event.removeListener(map, 'idle', fn);
}
});
};
wax = wax || {};
wax.g = wax.g || {};
// A control that adds interaction to a google Map object.
//
// Takes an options object with the following keys:
//
// * `callbacks` (optional): an `out`, `over`, and `click` callback.
// If not given, the `wax.tooltip` library will be expected.
// * `clickAction` (optional): **full** or **location**: default is
// **full**.
wax.g.interaction = function(map, tilejson, options) {
tilejson = tilejson || {};
options = options || {};
// Our GridManager (from `gridutil.js`). This will keep the
// cache of grid information and provide friendly utility methods
// that return `GridTile` objects instead of raw data.
var interaction = {
waxGM: new wax.GridManager(tilejson),
// This requires wax.Tooltip or similar
callbacks: options.callbacks || new wax.tooltip(),
clickAction: options.clickAction || 'full',
eventHandlers:{},
// Attach listeners to the map
add: function() {
this.eventHandlers.tileloaded = google.maps.event.addListener(map, 'tileloaded',
wax.util.bind(this.clearTileGrid, this));
this.eventHandlers.idle = google.maps.event.addListener(map, 'idle',
wax.util.bind(this.clearTileGrid, this));
this.eventHandlers.mousemove = google.maps.event.addListener(map, 'mousemove',
this.onMove());
this.eventHandlers.click = google.maps.event.addListener(map, 'click',
this.click());
return this;
},
// Remove interaction events from the map.
remove: function() {
google.maps.event.removeListener(this.eventHandlers.tileloaded);
google.maps.event.removeListener(this.eventHandlers.idle);
google.maps.event.removeListener(this.eventHandlers.mousemove);
google.maps.event.removeListener(this.eventHandlers.click);
return this;
},
// Search through `.tiles` and determine the position,
// from the top-left of the **document**, and cache that data
// so that `mousemove` events don't always recalculate.
getTileGrid: function() {
// Get all 'marked' tiles, added by the `wax.g.MapType` layer.
// Return an array of objects which have the **relative** offset of
// each tile, with a reference to the tile object in `tile`, since the API
// returns evt coordinates as relative to the map object.
if (!this._getTileGrid) {
this._getTileGrid = [];
var zoom = map.getZoom();
var mapOffset = wax.util.offset(map.getDiv());
var get = wax.util.bind(function(mapType) {
if (!mapType.interactive) return;
for (var key in mapType.cache) {
if (key.split('/')[0] != zoom) continue;
var tileOffset = wax.util.offset(mapType.cache[key]);
this._getTileGrid.push([
tileOffset.top - mapOffset.top,
tileOffset.left - mapOffset.left,
mapType.cache[key]
]);
}
}, this);
// Iterate over base mapTypes and overlayMapTypes.
for (var i in map.mapTypes) get(map.mapTypes[i]);
map.overlayMapTypes.forEach(get);
}
return this._getTileGrid;
},
clearTileGrid: function(map, e) {
this._getTileGrid = null;
},
getTile: function(evt) {
var tile;
var grid = this.getTileGrid();
for (var i = 0; i < grid.length; i++) {
if ((grid[i][0] < evt.pixel.y) &&
((grid[i][0] + 256) > evt.pixel.y) &&
(grid[i][1] < evt.pixel.x) &&
((grid[i][1] + 256) > evt.pixel.x)) {
tile = grid[i][2];
break;
}
}
return tile || false;
},
onMove: function(evt) {
if (!this._onMove) this._onMove = wax.util.bind(function(evt) {
var tile = this.getTile(evt);
if (tile) {
this.waxGM.getGrid(tile.src, wax.util.bind(function(err, g) {
if (err || !g) return;
var feature = g.tileFeature(
evt.pixel.x + wax.util.offset(map.getDiv()).left,
evt.pixel.y + wax.util.offset(map.getDiv()).top,
tile,
{ format: 'teaser' }
);
// Support only a single layer.
// Thus a layer index of **0** is given to the tooltip library
if (feature && this.feature !== feature) {
this.feature = feature;
this.callbacks.out(map.getDiv());
this.callbacks.over(feature, map.getDiv(), 0, evt);
} else if (!feature) {
this.feature = null;
this.callbacks.out(map.getDiv());
}
}, this));
}
}, this);
return this._onMove;
},
click: function(evt) {
if (!this._onClick) this._onClick = wax.util.bind(function(evt) {
// Feature previously found? Don't continue with the event...
// @vizzuality change!
if (window.event && window.event.cancelBubble) {
return false;
}
var tile = this.getTile(evt);
if (tile) {
this.waxGM.getGrid(tile.src, wax.util.bind(function(err, g) {
if (err || !g) return;
var feature = g.tileFeature(
evt.pixel.x + wax.util.offset(map.getDiv()).left,
evt.pixel.y + wax.util.offset(map.getDiv()).top,
tile,
{ format: this.clickAction }
);
if (feature) {
// Stop propagation of the click event to avoid fire more wax clicks!!!
// @vizzuality change!
// For Firefox browser that doesn't recognize window.event
if (!window.event) {
window.event = {};
setTimeout(function(){
window.event = null;
},150);
}
window.event.cancelBubble = true;
switch (this.clickAction) {
case 'full':
this.callbacks.click(feature, map.getDiv(), 0, evt);
break;
case 'location':
window.location = feature;
break;
}
}
}, this));
}
}, this);
return this._onClick;
}
};
// Return the interaction control such that the caller may manipulate it
// e.g. remove it.
return interaction.add(map);
};
wax = wax || {};
wax.g = wax.g || {};
// Legend Control
// --------------
// Adds legends to a google Map object.
wax.g.legend = function(map, tilejson) {
tilejson = tilejson || {};
var l, // parent legend
legend = {};
legend.add = function() {
l = wax.legend()
.content(tilejson.legend || '');
return this;
};
legend.element = function() {
return l.element();
};
legend.appendTo = function(elem) {
wax.util.$(elem).appendChild(l.element());
return this;
};
return legend.add();
};
// Wax for Google Maps API v3
// --------------------------
// Wax header
var wax = wax || {};
wax.g = wax.g || {};
// Wax Google Maps MapType: takes an object of options in the form
//
// {
// name: '',
// filetype: '.png',
// layerName: 'world-light',
// alt: '',
// zoomRange: [0, 18],
// baseUrl: 'a url',
// }
wax.g.connector = function(options) {
options = options || {};
this.options = {
tiles: options.tiles,
scheme: options.scheme || 'xyz',
blankImage: options.blankImage
};
this.minZoom = options.minzoom || 0;
this.maxZoom = options.maxzoom || 22;
this.name = options.name || '';
this.description = options.description || '';
// non-configurable options
this.interactive = true;
this.tileSize = new google.maps.Size(256, 256);
// DOM element cache
this.cache = {};
};
// Get a tile element from a coordinate, zoom level, and an ownerDocument.
wax.g.connector.prototype.getTile = function(coord, zoom, ownerDocument) {
var key = zoom + '/' + coord.x + '/' + coord.y;
if (!this.cache[key]) {
var img = this.cache[key] = new Image(256, 256);
this.cache[key].src = this.getTileUrl(coord, zoom);
this.cache[key].setAttribute('gTileKey', key);
this.cache[key].onerror = function() { img.style.display = 'none'; };
}
return this.cache[key];
};
// Remove a tile that has fallen out of the map's viewport.
//
// TODO: expire cache data in the gridmanager.
wax.g.connector.prototype.releaseTile = function(tile) {
var key = tile.getAttribute('gTileKey');
this.cache[key] && delete this.cache[key];
tile.parentNode && tile.parentNode.removeChild(tile);
};
// Get a tile url, based on x, y coordinates and a z value.
wax.g.connector.prototype.getTileUrl = function(coord, z) {
// Y coordinate is flipped in Mapbox, compared to Google
var mod = Math.pow(2, z),
y = (this.options.scheme === 'tms') ?
(mod - 1) - coord.y :
coord.y,
x = (coord.x % mod);
x = (x < 0) ? (coord.x % mod) + mod : x;
if (y < 0) return this.options.blankImage;
return this.options.tiles
[parseInt(x + y, 10) %
this.options.tiles.length]
.replace('{z}', z)
.replace('{x}', x)
.replace('{y}', y);
};