/*
 * jQuery Tooltip plugin 1.3
 *
 * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
 * http://docs.jquery.com/Plugins/Tooltip
 *
 * Copyright (c) 2006 - 2008 Jörn Zaefferer
 *
 * $Id: jquery.tooltip.js 5741 2008-06-21 15:22:16Z joern.zaefferer $
 * 
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */;(function($){var helper={},current,title,tID,IE=$.browser.msie&&/MSIE\s(5\.5|6\.)/.test(navigator.userAgent),track=false;$.tooltip={blocked:false,defaults:{delay:200,fade:false,showURL:true,extraClass:"",top:15,left:15,id:"tooltip"},block:function(){$.tooltip.blocked=!$.tooltip.blocked;}};$.fn.extend({tooltip:function(settings){settings=$.extend({},$.tooltip.defaults,settings);createHelper(settings);return this.each(function(){$.data(this,"tooltip",settings);this.tOpacity=helper.parent.css("opacity");this.tooltipText=this.title;$(this).removeAttr("title");this.alt="";}).mouseover(save).mouseout(hide).click(hide);},fixPNG:IE?function(){return this.each(function(){var image=$(this).css('backgroundImage');if(image.match(/^url\(["']?(.*\.png)["']?\)$/i)){image=RegExp.$1;$(this).css({'backgroundImage':'none','filter':"progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='"+image+"')"}).each(function(){var position=$(this).css('position');if(position!='absolute'&&position!='relative')$(this).css('position','relative');});}});}:function(){return this;},unfixPNG:IE?function(){return this.each(function(){$(this).css({'filter':'',backgroundImage:''});});}:function(){return this;},hideWhenEmpty:function(){return this.each(function(){$(this)[$(this).html()?"show":"hide"]();});},url:function(){return this.attr('href')||this.attr('src');}});function createHelper(settings){if(helper.parent)return;helper.parent=$('<div id="'+settings.id+'"><h3></h3><div class="body"></div><div class="url"></div></div>').appendTo(document.body).hide();if($.fn.bgiframe)helper.parent.bgiframe();helper.title=$('h3',helper.parent);helper.body=$('div.body',helper.parent);helper.url=$('div.url',helper.parent);}function settings(element){return $.data(element,"tooltip");}function handle(event){if(settings(this).delay)tID=setTimeout(show,settings(this).delay);else
show();track=!!settings(this).track;$(document.body).bind('mousemove',update);update(event);}function save(){if($.tooltip.blocked||this==current||(!this.tooltipText&&!settings(this).bodyHandler))return;current=this;title=this.tooltipText;if(settings(this).bodyHandler){helper.title.hide();var bodyContent=settings(this).bodyHandler.call(this);if(bodyContent.nodeType||bodyContent.jquery){helper.body.empty().append(bodyContent)}else{helper.body.html(bodyContent);}helper.body.show();}else if(settings(this).showBody){var parts=title.split(settings(this).showBody);helper.title.html(parts.shift()).show();helper.body.empty();for(var i=0,part;(part=parts[i]);i++){if(i>0)helper.body.append("<br/>");helper.body.append(part);}helper.body.hideWhenEmpty();}else{helper.title.html(title).show();helper.body.hide();}if(settings(this).showURL&&$(this).url())helper.url.html($(this).url().replace('http://','')).show();else
helper.url.hide();helper.parent.addClass(settings(this).extraClass);if(settings(this).fixPNG)helper.parent.fixPNG();handle.apply(this,arguments);}function show(){tID=null;if((!IE||!$.fn.bgiframe)&&settings(current).fade){if(helper.parent.is(":animated"))helper.parent.stop().show().fadeTo(settings(current).fade,current.tOpacity);else
helper.parent.is(':visible')?helper.parent.fadeTo(settings(current).fade,current.tOpacity):helper.parent.fadeIn(settings(current).fade);}else{helper.parent.show();}update();}function update(event){if($.tooltip.blocked)return;if(event&&event.target.tagName=="OPTION"){return;}if(!track&&helper.parent.is(":visible")){$(document.body).unbind('mousemove',update)}if(current==null){$(document.body).unbind('mousemove',update);return;}helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");var left=helper.parent[0].offsetLeft;var top=helper.parent[0].offsetTop;if(event){left=event.pageX+settings(current).left;top=event.pageY+settings(current).top;var right='auto';if(settings(current).positionLeft){right=$(window).width()-left;left='auto';}helper.parent.css({left:left,right:right,top:top});}var v=viewport(),h=helper.parent[0];if(v.x+v.cx<h.offsetLeft+h.offsetWidth){left-=h.offsetWidth+20+settings(current).left;helper.parent.css({left:left+'px'}).addClass("viewport-right");}if(v.y+v.cy<h.offsetTop+h.offsetHeight){top-=h.offsetHeight+20+settings(current).top;helper.parent.css({top:top+'px'}).addClass("viewport-bottom");}}function viewport(){return{x:$(window).scrollLeft(),y:$(window).scrollTop(),cx:$(window).width(),cy:$(window).height()};}function hide(event){if($.tooltip.blocked)return;if(tID)clearTimeout(tID);current=null;var tsettings=settings(this);function complete(){helper.parent.removeClass(tsettings.extraClass).hide().css("opacity","");}if((!IE||!$.fn.bgiframe)&&tsettings.fade){if(helper.parent.is(':animated'))helper.parent.stop().fadeTo(tsettings.fade,0,complete);else
helper.parent.stop().fadeOut(tsettings.fade,complete);}else
complete();if(settings(this).fixPNG)helper.parent.unfixPNG();}})(jQuery);/*
	Watermark v3.1.3 (March 22, 2011) plugin for jQuery
	http://jquery-watermark.googlecode.com/
	Copyright (c) 2009-2011 Todd Northrop
	http://www.speednet.biz/
	Dual licensed under the MIT or GPL Version 2 licenses.
*/
(function(a,h,y){var w="function",v="password",j="maxLength",n="type",b="",c=true,u="placeholder",i=false,t="watermark",g=t,f="watermarkClass",q="watermarkFocus",l="watermarkSubmit",o="watermarkMaxLength",e="watermarkPassword",d="watermarkText",k=/\r/g,s="input:data("+g+"),textarea:data("+g+")",m="input:text,input:password,input[type=search],input:not([type]),textarea",p=["Page_ClientValidate"],r=i,x=u in document.createElement("input");a.watermark=a.watermark||{version:"3.1.3",runOnce:c,options:{className:t,useNative:c,hideBeforeUnload:c},hide:function(b){a(b).filter(s).each(function(){a.watermark._hide(a(this))})},_hide:function(a,r){var p=a[0],q=(p.value||b).replace(k,b),l=a.data(d)||b,m=a.data(o)||0,i=a.data(f);if(l.length&&q==l){p.value=b;if(a.data(e))if((a.attr(n)||b)==="text"){var g=a.data(e)||[],c=a.parent()||[];if(g.length&&c.length){c[0].removeChild(a[0]);c[0].appendChild(g[0]);a=g}}if(m){a.attr(j,m);a.removeData(o)}if(r){a.attr("autocomplete","off");h.setTimeout(function(){a.select()},1)}}i&&a.removeClass(i)},show:function(b){a(b).filter(s).each(function(){a.watermark._show(a(this))})},_show:function(g){var p=g[0],u=(p.value||b).replace(k,b),h=g.data(d)||b,s=g.attr(n)||b,t=g.data(f);if((u.length==0||u==h)&&!g.data(q)){r=c;if(g.data(e))if(s===v){var m=g.data(e)||[],l=g.parent()||[];if(m.length&&l.length){l[0].removeChild(g[0]);l[0].appendChild(m[0]);g=m;g.attr(j,h.length);p=g[0]}}if(s==="text"||s==="search"){var i=g.attr(j)||0;if(i>0&&h.length>i){g.data(o,i);g.attr(j,h.length)}}t&&g.addClass(t);p.value=h}else a.watermark._hide(g)},hideAll:function(){if(r){a.watermark.hide(m);r=i}},showAll:function(){a.watermark.show(m)}};a.fn.watermark=a.fn.watermark||function(p,o){var t="string";if(!this.length)return this;var s=i,r=typeof p===t;if(r)p=p.replace(k,b);if(typeof o==="object"){s=typeof o.className===t;o=a.extend({},a.watermark.options,o)}else if(typeof o===t){s=c;o=a.extend({},a.watermark.options,{className:o})}else o=a.watermark.options;if(typeof o.useNative!==w)o.useNative=o.useNative?function(){return c}:function(){return i};return this.each(function(){var B="dragleave",A="dragenter",z=this,i=a(z);if(!i.is(m))return;if(i.data(g)){if(r||s){a.watermark._hide(i);r&&i.data(d,p);s&&i.data(f,o.className)}}else{if(x&&o.useNative.call(z,i)&&(i.attr("tagName")||b)!=="TEXTAREA"){r&&i.attr(u,p);return}i.data(d,r?p:b);i.data(f,o.className);i.data(g,1);if((i.attr(n)||b)===v){var C=i.wrap("<span>").parent(),t=a(C.html().replace(/type=["']?password["']?/i,'type="text"'));t.data(d,i.data(d));t.data(f,i.data(f));t.data(g,1);t.attr(j,p.length);t.focus(function(){a.watermark._hide(t,c)}).bind(A,function(){a.watermark._hide(t)}).bind("dragend",function(){h.setTimeout(function(){t.blur()},1)});i.blur(function(){a.watermark._show(i)}).bind(B,function(){a.watermark._show(i)});t.data(e,i);i.data(e,t)}else i.focus(function(){i.data(q,1);a.watermark._hide(i,c)}).blur(function(){i.data(q,0);a.watermark._show(i)}).bind(A,function(){a.watermark._hide(i)}).bind(B,function(){a.watermark._show(i)}).bind("dragend",function(){h.setTimeout(function(){a.watermark._show(i)},1)}).bind("drop",function(e){var c=i[0],a=e.originalEvent.dataTransfer.getData("Text");if((c.value||b).replace(k,b).replace(a,b)===i.data(d))c.value=a;i.focus()});if(z.form){var w=z.form,y=a(w);if(!y.data(l)){y.submit(a.watermark.hideAll);if(w.submit){y.data(l,w.submit);w.submit=function(c,b){return function(){var d=b.data(l);a.watermark.hideAll();if(d.apply)d.apply(c,Array.prototype.slice.call(arguments));else d()}}(w,y)}else{y.data(l,1);w.submit=function(b){return function(){a.watermark.hideAll();delete b.submit;b.submit()}}(w)}}}}a.watermark._show(i)})};if(a.watermark.runOnce){a.watermark.runOnce=i;a.extend(a.expr[":"],{data:function(c,d,b){return!!a.data(c,b[3])}});(function(c){a.fn.val=function(){var e=this;if(!e.length)return arguments.length?e:y;if(!arguments.length)if(e.data(g)){var f=(e[0].value||b).replace(k,b);return f===(e.data(d)||b)?b:f}else return c.apply(e,arguments);else{c.apply(e,arguments);a.watermark.show(e);return e}}})(a.fn.val);p.length&&a(function(){for(var b,c,d=p.length-1;d>=0;d--){b=p[d];c=h[b];if(typeof c===w)h[b]=function(b){return function(){a.watermark.hideAll();return b.apply(null,Array.prototype.slice.call(arguments))}}(c)}});a(h).bind("beforeunload",function(){a.watermark.options.hideBeforeUnload&&a.watermark.hideAll()})}})(jQuery,window);/*
 * Metadata - jQuery plugin for parsing metadata from elements
 *
 * Copyright (c) 2006 John Resig, Yehuda Katz, Jï¿½Ã¶rn Zaefferer, Paul McLanahan
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.metadata.js 3640 2007-10-11 18:34:38Z pmclanahan $
 *
 */

/**
 * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
 * in the JSON will become a property of the element itself.
 *
 * There are four supported types of metadata storage:
 *
 *   attr:  Inside an attribute. The name parameter indicates *which* attribute.
 *          
 *   class: Inside the class attribute, wrapped in curly braces: { }
 *   
 *   elem:  Inside a child element (e.g. a script tag). The
 *          name parameter indicates *which* element.
 *   html5: Values are stored in data-* attributes.
 *          
 * The metadata for an element is loaded the first time the element is accessed via jQuery.
 *
 * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
 * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
 * 
 * @name $.metadata.setType
 *
 * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.metadata.setType("class")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from the class attribute
 * 
 * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.metadata.setType("attr", "data")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a "data" attribute
 * 
 * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
 * @before $.metadata.setType("elem", "script")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a nested script element
 * 
 * @example <p id="one" class="some_class" data-item_id="1" data-item_label="Label">This is a p</p>
 * @before $.metadata.setType("html5")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a series of data-* attributes
 *
 * @param String type The encoding type
 * @param String name The name of the attribute to be used to get metadata (optional)
 * @cat Plugins/Metadata
 * @descr Sets the type of encoding to be used when loading metadata for the first time
 * @type undefined
 * @see metadata()
 */

(function($) {

$.extend({
  metadata : {
    defaults : {
      type: 'class',
      name: 'metadata',
      cre: /({.*})/,
      single: 'metadata'
    },
    setType: function( type, name ){
      this.defaults.type = type;
      this.defaults.name = name;
    },
    get: function( elem, opts ){
      var settings = $.extend({},this.defaults,opts);
      // check for empty string in single property
      if ( !settings.single.length ) settings.single = 'metadata';
      
      var data = $.data(elem, settings.single);
      // returned cached data if it already exists
      if ( data ) return data;
      
      data = "{}";
      
      var getData = function(data) {
        if(typeof data != "string") return data;
        
        if( data.indexOf('{') < 0 ) {
          data = eval("(" + data + ")");
        }
      }
      
      var getObject = function(data) {
        if(typeof data != "string") return data;
        
        data = eval("(" + data + ")");
        return data;
      }
      
      if ( settings.type == "html5" ) {
        var object = {};
        $( elem.attributes ).each(function() {
          var name = this.nodeName;
          if(name.match(/^data-/)) name = name.replace(/^data-/, '');
          else return true;
          object[name] = getObject(this.nodeValue);
        });
      } else {
        if ( settings.type == "class" ) {
          var m = settings.cre.exec( elem.className );
          if ( m )
            data = m[1];
        } else if ( settings.type == "elem" ) {
          if( !elem.getElementsByTagName ) return;
          var e = elem.getElementsByTagName(settings.name);
          if ( e.length )
            data = $.trim(e[0].innerHTML);
        } else if ( elem.getAttribute != undefined ) {
          var attr = elem.getAttribute( settings.name );
          if ( attr )
            data = attr;
        }
        object = getObject(data.indexOf("{") < 0 ? "{" + data + "}" : data);
      }
      
      $.data( elem, settings.single, object );
      return object;
    }
  }
});

/**
 * Returns the metadata object for the first member of the jQuery object.
 *
 * @name metadata
 * @descr Returns element's metadata object
 * @param Object opts An object contianing settings to override the defaults
 * @type jQuery
 * @cat Plugins/Metadata
 */
$.fn.metadata = function( opts ){
  return $.metadata.get( this[0], opts );
};

})(jQuery);(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,cells[i]);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,node){var l=parsers.length;for(var i=1;i<l;i++){if(parsers[i].is($.trim(getElementText(table.config,node)),table,node)){return parsers[i];}}return parsers[0];}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=table.tBodies[0].rows[i],cols=[];cache.row.push($(c));for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c.cells[j]),table,c.cells[j]));}cols.push(i);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){if(!node)return"";var t="";if(config.textExtraction=="simple"){if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){t=node.childNodes[0].innerHTML;}else{t=node.innerHTML;}}else{if(typeof(config.textExtraction)=="function"){t=config.textExtraction(node);}else{t=$(node).text();}}return t;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){rows.push(r[n[i][checkCell]]);if(!table.config.appender){var o=r[n[i][checkCell]];var l=o.length;for(var j=0;j<l;j++){tableBody[0].appendChild(o[j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false,tableHeadersRows=[];for(var i=0;i<table.tHead.rows.length;i++){tableHeadersRows[i]=0;};$tableHeaders=$("thead th",table);$tableHeaders.each(function(index){this.count=0;this.column=index;this.order=formatSortingOrder(table.config.sortInitialOrder);if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(!this.sortDisabled){$(this).addClass(table.config.cssHeader);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){i=(v.toLowerCase()=="desc")?1:0;}else{i=(v==(0||1))?v:0;}return i;}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(getCachedSortType(table.config.parsers,c)=="text")?((order==0)?"sortText":"sortTextDesc"):((order==0)?"sortNumeric":"sortNumericDesc");var e="e"+i;dynamicExp+="var "+e+" = "+s+"(a["+c+"],b["+c+"]); ";dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function sortText(a,b){return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){var DECIMAL='\\'+config.decimal;var exp='/(^[+]?0('+DECIMAL+'0+)?$)|(^([-+]?[1-9][0-9]*)$)|(^([-+]?((0?|[1-9][0-9]*)'+DECIMAL+'(0*[1-9][0-9]*)))$)|(^[-+]?[1-9]+[0-9]*'+DECIMAL+'0+$)/';return RegExp(exp).test($.trim(s));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}$("tr:visible",table.tBodies[0]).filter(':even').removeClass(table.config.widgetZebra.css[1]).addClass(table.config.widgetZebra.css[0]).end().filter(':odd').removeClass(table.config.widgetZebra.css[0]).addClass(table.config.widgetZebra.css[1]);if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);/*! jQuery Validation Plugin - v1.11.0 - 2/4/2013
* https://github.com/jzaefferer/jquery-validation
* Copyright (c) 2013 JÃ¶rn Zaefferer; Licensed MIT */
(function (e) { e.extend(e.fn, { validate: function (t) { if (!this.length) { t && t.debug && window.console && console.warn("Nothing selected, can't validate, returning nothing."); return } var n = e.data(this[0], "validator"); return n ? n : (this.attr("novalidate", "novalidate"), n = new e.validator(t, this[0]), e.data(this[0], "validator", n), n.settings.onsubmit && (this.validateDelegate(":submit", "click", function (t) { n.settings.submitHandler && (n.submitButton = t.target), e(t.target).hasClass("cancel") && (n.cancelSubmit = !0) }), this.submit(function (t) { function r() { var r; return n.settings.submitHandler ? (n.submitButton && (r = e("<input type='hidden'/>").attr("name", n.submitButton.name).val(n.submitButton.value).appendTo(n.currentForm)), n.settings.submitHandler.call(n, n.currentForm, t), n.submitButton && r.remove(), !1) : !0 } return n.settings.debug && t.preventDefault(), n.cancelSubmit ? (n.cancelSubmit = !1, r()) : n.form() ? n.pendingRequest ? (n.formSubmitted = !0, !1) : r() : (n.focusInvalid(), !1) })), n) }, valid: function () { if (e(this[0]).is("form")) return this.validate().form(); var t = !0, n = e(this[0].form).validate(); return this.each(function () { t &= n.element(this) }), t }, removeAttrs: function (t) { var n = {}, r = this; return e.each(t.split(/\s/), function (e, t) { n[t] = r.attr(t), r.removeAttr(t) }), n }, rules: function (t, n) { var r = this[0]; if (t) { var i = e.data(r.form, "validator").settings, s = i.rules, o = e.validator.staticRules(r); switch (t) { case "add": e.extend(o, e.validator.normalizeRule(n)), s[r.name] = o, n.messages && (i.messages[r.name] = e.extend(i.messages[r.name], n.messages)); break; case "remove": if (!n) return delete s[r.name], o; var u = {}; return e.each(n.split(/\s/), function (e, t) { u[t] = o[t], delete o[t] }), u } } var a = e.validator.normalizeRules(e.extend({}, e.validator.classRules(r), e.validator.attributeRules(r), e.validator.dataRules(r), e.validator.staticRules(r)), r); if (a.required) { var f = a.required; delete a.required, a = e.extend({ required: f }, a) } return a } }), e.extend(e.expr[":"], { blank: function (t) { return !e.trim("" + t.value) }, filled: function (t) { return !!e.trim("" + t.value) }, unchecked: function (e) { return !e.checked } }), e.validator = function (t, n) { this.settings = e.extend(!0, {}, e.validator.defaults, t), this.currentForm = n, this.init() }, e.validator.format = function (t, n) { return arguments.length === 1 ? function () { var n = e.makeArray(arguments); return n.unshift(t), e.validator.format.apply(this, n) } : (arguments.length > 2 && n.constructor !== Array && (n = e.makeArray(arguments).slice(1)), n.constructor !== Array && (n = [n]), e.each(n, function (e, n) { t = t.replace(new RegExp("\\{" + e + "\\}", "g"), function () { return n }) }), t) }, e.extend(e.validator, { defaults: { messages: {}, groups: {}, rules: {}, errorClass: "error", validClass: "valid", errorElement: "label", focusInvalid: !0, errorContainer: e([]), errorLabelContainer: e([]), onsubmit: !0, ignore: ":hidden", ignoreTitle: !1, onfocusin: function (e, t) { this.lastActive = e, this.settings.focusCleanup && !this.blockFocusCleanup && (this.settings.unhighlight && this.settings.unhighlight.call(this, e, this.settings.errorClass, this.settings.validClass), this.addWrapper(this.errorsFor(e)).hide()) }, onfocusout: function (e, t) { !this.checkable(e) && (e.name in this.submitted || !this.optional(e)) && this.element(e) }, onkeyup: function (e, t) { if (t.which === 9 && this.elementValue(e) === "") return; (e.name in this.submitted || e === this.lastElement) && this.element(e) }, onclick: function (e, t) { e.name in this.submitted ? this.element(e) : e.parentNode.name in this.submitted && this.element(e.parentNode) }, highlight: function (t, n, r) { t.type === "radio" ? this.findByName(t.name).addClass(n).removeClass(r) : e(t).addClass(n).removeClass(r) }, unhighlight: function (t, n, r) { t.type === "radio" ? this.findByName(t.name).removeClass(n).addClass(r) : e(t).removeClass(n).addClass(r) } }, setDefaults: function (t) { e.extend(e.validator.defaults, t) }, messages: { required: "This field is required.", remote: "Please fix this field.", email: "Please enter a valid email address.", url: "Please enter a valid URL.", date: "Please enter a valid date.", dateISO: "Please enter a valid date (ISO).", number: "Please enter a valid number.", digits: "Please enter only digits.", creditcard: "Please enter a valid credit card number.", equalTo: "Please enter the same value again.", maxlength: e.validator.format("Please enter no more than {0} characters."), minlength: e.validator.format("Please enter at least {0} characters."), rangelength: e.validator.format("Please enter a value between {0} and {1} characters long."), range: e.validator.format("Please enter a value between {0} and {1}."), max: e.validator.format("Please enter a value less than or equal to {0}."), min: e.validator.format("Please enter a value greater than or equal to {0}.") }, autoCreateRanges: !1, prototype: { init: function () { function r(t) { var n = e.data(this[0].form, "validator"), r = "on" + t.type.replace(/^validate/, ""); n.settings[r] && n.settings[r].call(n, this[0], t) } this.labelContainer = e(this.settings.errorLabelContainer), this.errorContext = this.labelContainer.length && this.labelContainer || e(this.currentForm), this.containers = e(this.settings.errorContainer).add(this.settings.errorLabelContainer), this.submitted = {}, this.valueCache = {}, this.pendingRequest = 0, this.pending = {}, this.invalid = {}, this.reset(); var t = this.groups = {}; e.each(this.settings.groups, function (n, r) { typeof r == "string" && (r = r.split(/\s/)), e.each(r, function (e, r) { t[r] = n }) }); var n = this.settings.rules; e.each(n, function (t, r) { n[t] = e.validator.normalizeRule(r) }), e(this.currentForm).validateDelegate(":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'] ,[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'] ", "focusin focusout keyup", r).validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", r), this.settings.invalidHandler && e(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler) }, form: function () { return this.checkForm(), e.extend(this.submitted, this.errorMap), this.invalid = e.extend({}, this.errorMap), this.valid() || e(this.currentForm).triggerHandler("invalid-form", [this]), this.showErrors(), this.valid() }, checkForm: function () { this.prepareForm(); for (var e = 0, t = this.currentElements = this.elements() ; t[e]; e++) this.check(t[e]); return this.valid() }, element: function (t) { t = this.validationTargetFor(this.clean(t)), this.lastElement = t, this.prepareElement(t), this.currentElements = e(t); var n = this.check(t) !== !1; return n ? delete this.invalid[t.name] : this.invalid[t.name] = !0, this.numberOfInvalids() || (this.toHide = this.toHide.add(this.containers)), this.showErrors(), n }, showErrors: function (t) { if (t) { e.extend(this.errorMap, t), this.errorList = []; for (var n in t) this.errorList.push({ message: t[n], element: this.findByName(n)[0] }); this.successList = e.grep(this.successList, function (e) { return !(e.name in t) }) } this.settings.showErrors ? this.settings.showErrors.call(this, this.errorMap, this.errorList) : this.defaultShowErrors() }, resetForm: function () { e.fn.resetForm && e(this.currentForm).resetForm(), this.submitted = {}, this.lastElement = null, this.prepareForm(), this.hideErrors(), this.elements().removeClass(this.settings.errorClass).removeData("previousValue") }, numberOfInvalids: function () { return this.objectLength(this.invalid) }, objectLength: function (e) { var t = 0; for (var n in e) t++; return t }, hideErrors: function () { this.addWrapper(this.toHide).hide() }, valid: function () { return this.size() === 0 }, size: function () { return this.errorList.length }, focusInvalid: function () { if (this.settings.focusInvalid) try { e(this.findLastActive() || this.errorList.length && this.errorList[0].element || []).filter(":visible").focus().trigger("focusin") } catch (t) { } }, findLastActive: function () { var t = this.lastActive; return t && e.grep(this.errorList, function (e) { return e.element.name === t.name }).length === 1 && t }, elements: function () { var t = this, n = {}; return e(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, [disabled]").not(this.settings.ignore).filter(function () { return !this.name && t.settings.debug && window.console && console.error("%o has no name assigned", this), this.name in n || !t.objectLength(e(this).rules()) ? !1 : (n[this.name] = !0, !0) }) }, clean: function (t) { return e(t)[0] }, errors: function () { var t = this.settings.errorClass.replace(" ", "."); return e(this.settings.errorElement + "." + t, this.errorContext) }, reset: function () { this.successList = [], this.errorList = [], this.errorMap = {}, this.toShow = e([]), this.toHide = e([]), this.currentElements = e([]) }, prepareForm: function () { this.reset(), this.toHide = this.errors().add(this.containers) }, prepareElement: function (e) { this.reset(), this.toHide = this.errorsFor(e) }, elementValue: function (t) { var n = e(t).attr("type"), r = e(t).val(); return n === "radio" || n === "checkbox" ? e("input[name='" + e(t).attr("name") + "']:checked").val() : typeof r == "string" ? r.replace(/\r/g, "") : r }, check: function (t) { t = this.validationTargetFor(this.clean(t)); var n = e(t).rules(), r = !1, i = this.elementValue(t), s; for (var o in n) { var u = { method: o, parameters: n[o] }; try { s = e.validator.methods[o].call(this, i, t, u.parameters); if (s === "dependency-mismatch") { r = !0; continue } r = !1; if (s === "pending") { this.toHide = this.toHide.not(this.errorsFor(t)); return } if (!s) return this.formatAndAdd(t, u), !1 } catch (a) { throw this.settings.debug && window.console && console.log("Exception occured when checking element " + t.id + ", check the '" + u.method + "' method.", a), a } } if (r) return; return this.objectLength(n) && this.successList.push(t), !0 }, customDataMessage: function (t, n) { return e(t).data("msg-" + n.toLowerCase()) || t.attributes && e(t).attr("data-msg-" + n.toLowerCase()) }, customMessage: function (e, t) { var n = this.settings.messages[e]; return n && (n.constructor === String ? n : n[t]) }, findDefined: function () { for (var e = 0; e < arguments.length; e++) if (arguments[e] !== undefined) return arguments[e]; return undefined }, defaultMessage: function (t, n) { return this.findDefined(this.customMessage(t.name, n), this.customDataMessage(t, n), !this.settings.ignoreTitle && t.title || undefined, e.validator.messages[n], "<strong>Warning: No message defined for " + t.name + "</strong>") }, formatAndAdd: function (t, n) { var r = this.defaultMessage(t, n.method), i = /\$?\{(\d+)\}/g; typeof r == "function" ? r = r.call(this, n.parameters, t) : i.test(r) && (r = e.validator.format(r.replace(i, "{$1}"), n.parameters)), this.errorList.push({ message: r, element: t }), this.errorMap[t.name] = r, this.submitted[t.name] = r }, addWrapper: function (e) { return this.settings.wrapper && (e = e.add(e.parent(this.settings.wrapper))), e }, defaultShowErrors: function () { var e, t; for (e = 0; this.errorList[e]; e++) { var n = this.errorList[e]; this.settings.highlight && this.settings.highlight.call(this, n.element, this.settings.errorClass, this.settings.validClass), this.showLabel(n.element, n.message) } this.errorList.length && (this.toShow = this.toShow.add(this.containers)); if (this.settings.success) for (e = 0; this.successList[e]; e++) this.showLabel(this.successList[e]); if (this.settings.unhighlight) for (e = 0, t = this.validElements() ; t[e]; e++) this.settings.unhighlight.call(this, t[e], this.settings.errorClass, this.settings.validClass); this.toHide = this.toHide.not(this.toShow), this.hideErrors(), this.addWrapper(this.toShow).show() }, validElements: function () { return this.currentElements.not(this.invalidElements()) }, invalidElements: function () { return e(this.errorList).map(function () { return this.element }) }, showLabel: function (t, n) { var r = this.errorsFor(t); r.length ? (r.removeClass(this.settings.validClass).addClass(this.settings.errorClass), r.html(n)) : (r = e("<" + this.settings.errorElement + ">").attr("for", this.idOrName(t)).addClass(this.settings.errorClass).html(n || ""), this.settings.wrapper && (r = r.hide().show().wrap("<" + this.settings.wrapper + "/>").parent()), this.labelContainer.append(r).length || (this.settings.errorPlacement ? this.settings.errorPlacement(r, e(t)) : r.insertAfter(t))), !n && this.settings.success && (r.text(""), typeof this.settings.success == "string" ? r.addClass(this.settings.success) : this.settings.success(r, t)), this.toShow = this.toShow.add(r) }, errorsFor: function (t) { var n = this.idOrName(t); return this.errors().filter(function () { return e(this).attr("for") === n }) }, idOrName: function (e) { return this.groups[e.name] || (this.checkable(e) ? e.name : e.id || e.name) }, validationTargetFor: function (e) { return this.checkable(e) && (e = this.findByName(e.name).not(this.settings.ignore)[0]), e }, checkable: function (e) { return /radio|checkbox/i.test(e.type) }, findByName: function (t) { return e(this.currentForm).find("[name='" + t + "']") }, getLength: function (t, n) { switch (n.nodeName.toLowerCase()) { case "select": return e("option:selected", n).length; case "input": if (this.checkable(n)) return this.findByName(n.name).filter(":checked").length } return t.length }, depend: function (e, t) { return this.dependTypes[typeof e] ? this.dependTypes[typeof e](e, t) : !0 }, dependTypes: { "boolean": function (e, t) { return e }, string: function (t, n) { return !!e(t, n.form).length }, "function": function (e, t) { return e(t) } }, optional: function (t) { var n = this.elementValue(t); return !e.validator.methods.required.call(this, n, t) && "dependency-mismatch" }, startRequest: function (e) { this.pending[e.name] || (this.pendingRequest++, this.pending[e.name] = !0) }, stopRequest: function (t, n) { this.pendingRequest--, this.pendingRequest < 0 && (this.pendingRequest = 0), delete this.pending[t.name], n && this.pendingRequest === 0 && this.formSubmitted && this.form() ? (e(this.currentForm).submit(), this.formSubmitted = !1) : !n && this.pendingRequest === 0 && this.formSubmitted && (e(this.currentForm).triggerHandler("invalid-form", [this]), this.formSubmitted = !1) }, previousValue: function (t) { return e.data(t, "previousValue") || e.data(t, "previousValue", { old: null, valid: !0, message: this.defaultMessage(t, "remote") }) } }, classRuleSettings: { required: { required: !0 }, email: { email: !0 }, url: { url: !0 }, date: { date: !0 }, dateISO: { dateISO: !0 }, number: { number: !0 }, digits: { digits: !0 }, creditcard: { creditcard: !0 } }, addClassRules: function (t, n) { t.constructor === String ? this.classRuleSettings[t] = n : e.extend(this.classRuleSettings, t) }, classRules: function (t) { var n = {}, r = e(t).attr("class"); return r && e.each(r.split(" "), function () { this in e.validator.classRuleSettings && e.extend(n, e.validator.classRuleSettings[this]) }), n }, attributeRules: function (t) { var n = {}, r = e(t); for (var i in e.validator.methods) { var s; i === "required" ? (s = r.get(0).getAttribute(i), s === "" && (s = !0), s = !!s) : s = r.attr(i), s ? n[i] = s : r[0].getAttribute("type") === i && (n[i] = !0) } return n.maxlength && /-1|2147483647|524288/.test(n.maxlength) && delete n.maxlength, n }, dataRules: function (t) { var n, r, i = {}, s = e(t); for (n in e.validator.methods) r = s.data("rule-" + n.toLowerCase()), r !== undefined && (i[n] = r); return i }, staticRules: function (t) { var n = {}, r = e.data(t.form, "validator"); return r.settings.rules && (n = e.validator.normalizeRule(r.settings.rules[t.name]) || {}), n }, normalizeRules: function (t, n) { return e.each(t, function (r, i) { if (i === !1) { delete t[r]; return } if (i.param || i.depends) { var s = !0; switch (typeof i.depends) { case "string": s = !!e(i.depends, n.form).length; break; case "function": s = i.depends.call(n, n) } s ? t[r] = i.param !== undefined ? i.param : !0 : delete t[r] } }), e.each(t, function (r, i) { t[r] = e.isFunction(i) ? i(n) : i }), e.each(["minlength", "maxlength"], function () { t[this] && (t[this] = Number(t[this])) }), e.each(["rangelength"], function () { var n; t[this] && (e.isArray(t[this]) ? t[this] = [Number(t[this][0]), Number(t[this][1])] : typeof t[this] == "string" && (n = t[this].split(/[\s,]+/), t[this] = [Number(n[0]), Number(n[1])])) }), e.validator.autoCreateRanges && (t.min && t.max && (t.range = [t.min, t.max], delete t.min, delete t.max), t.minlength && t.maxlength && (t.rangelength = [t.minlength, t.maxlength], delete t.minlength, delete t.maxlength)), t }, normalizeRule: function (t) { if (typeof t == "string") { var n = {}; e.each(t.split(/\s/), function () { n[this] = !0 }), t = n } return t }, addMethod: function (t, n, r) { e.validator.methods[t] = n, e.validator.messages[t] = r !== undefined ? r : e.validator.messages[t], n.length < 3 && e.validator.addClassRules(t, e.validator.normalizeRule(t)) }, methods: { required: function (t, n, r) { if (!this.depend(r, n)) return "dependency-mismatch"; if (n.nodeName.toLowerCase() === "select") { var i = e(n).val(); return i && i.length > 0 } return this.checkable(n) ? this.getLength(t, n) > 0 : e.trim(t).length > 0 }, remote: function (t, n, r) { if (this.optional(n)) return "dependency-mismatch"; var i = this.previousValue(n); this.settings.messages[n.name] || (this.settings.messages[n.name] = {}), i.originalMessage = this.settings.messages[n.name].remote, this.settings.messages[n.name].remote = i.message, r = typeof r == "string" && { url: r } || r; if (i.old === t) return i.valid; i.old = t; var s = this; this.startRequest(n); var o = {}; return o[n.name] = t, e.ajax(e.extend(!0, { url: r, mode: "abort", port: "validate" + n.name, dataType: "json", data: o, success: function (r) { s.settings.messages[n.name].remote = i.originalMessage; var o = r === !0 || r === "true"; if (o) { var u = s.formSubmitted; s.prepareElement(n), s.formSubmitted = u, s.successList.push(n), delete s.invalid[n.name], s.showErrors() } else { var a = {}, f = r || s.defaultMessage(n, "remote"); a[n.name] = i.message = e.isFunction(f) ? f(t) : f, s.invalid[n.name] = !0, s.showErrors(a) } i.valid = o, s.stopRequest(n, o) } }, r)), "pending" }, minlength: function (t, n, r) { var i = e.isArray(t) ? t.length : this.getLength(e.trim(t), n); return this.optional(n) || i >= r }, maxlength: function (t, n, r) { var i = e.isArray(t) ? t.length : this.getLength(e.trim(t), n); return this.optional(n) || i <= r }, rangelength: function (t, n, r) { var i = e.isArray(t) ? t.length : this.getLength(e.trim(t), n); return this.optional(n) || i >= r[0] && i <= r[1] }, min: function (e, t, n) { return this.optional(t) || e >= n }, max: function (e, t, n) { return this.optional(t) || e <= n }, range: function (e, t, n) { return this.optional(t) || e >= n[0] && e <= n[1] }, email: function (e, t) { return this.optional(t) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(e) }, url: function (e, t) { return this.optional(t) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(e) }, date: function (e, t) { return this.optional(t) || !/Invalid|NaN/.test((new Date(e)).toString()) }, dateISO: function (e, t) { return this.optional(t) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(e) }, number: function (e, t) { return this.optional(t) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(e) }, digits: function (e, t) { return this.optional(t) || /^\d+$/.test(e) }, creditcard: function (e, t) { if (this.optional(t)) return "dependency-mismatch"; if (/[^0-9 \-]+/.test(e)) return !1; var n = 0, r = 0, i = !1; e = e.replace(/\D/g, ""); for (var s = e.length - 1; s >= 0; s--) { var o = e.charAt(s); r = parseInt(o, 10), i && (r *= 2) > 9 && (r -= 9), n += r, i = !i } return n % 10 === 0 }, equalTo: function (t, n, r) { var i = e(r); return this.settings.onfocusout && i.unbind(".validate-equalTo").bind("blur.validate-equalTo", function () { e(n).valid() }), t === i.val() } } }), e.format = e.validator.format })(jQuery), function (e) { var t = {}; if (e.ajaxPrefilter) e.ajaxPrefilter(function (e, n, r) { var i = e.port; e.mode === "abort" && (t[i] && t[i].abort(), t[i] = r) }); else { var n = e.ajax; e.ajax = function (r) { var i = ("mode" in r ? r : e.ajaxSettings).mode, s = ("port" in r ? r : e.ajaxSettings).port; return i === "abort" ? (t[s] && t[s].abort(), t[s] = n.apply(this, arguments)) : n.apply(this, arguments) } } }(jQuery), function (e) { e.extend(e.fn, { validateDelegate: function (t, n, r) { return this.bind(n, function (n) { var i = e(n.target); if (i.is(t)) return r.apply(i, arguments) }) } }) }(jQuery);/*
* jQuery dropdown: A simple dropdown plugin
*
* Inspired by Bootstrap: http://twitter.github.com/bootstrap/javascript.html#dropdowns
*
* Copyright 2011 Cory LaViska for A Beautiful Site, LLC. (http://abeautifulsite.net/)
*
* Dual licensed under the MIT or GPL Version 2 licenses
*
*/
if (jQuery) (function ($) {

  $.extend($.fn, {
    dropdown: function (method, data) {

      switch (method) {
        case 'hide':
          hideDropdowns();
          return $(this);
        case 'attach':
          return $(this).attr('data-dropdown', data);
        case 'detach':
          hideDropdowns();
          return $(this).removeAttr('data-dropdown');
        case 'disable':
          return $(this).addClass('dropdown-disabled');
        case 'enable':
          hideDropdowns();
          return $(this).removeClass('dropdown-disabled');
      }

    }
  });

  function showMenu(event) {

    var trigger = $(this),
dropdown = $($(this).attr('data-dropdown')),
isOpen = trigger.hasClass('dropdown-open'),
hOffset = parseInt($(this).attr('data-horizontal-offset') || 0),
vOffset = parseInt($(this).attr('data-vertical-offset') || 0);

    if (trigger !== event.target && $(event.target).hasClass('dropdown-ignore')) return;

    event.preventDefault();
    event.stopPropagation();

    hideDropdowns();

    if (isOpen || trigger.hasClass('dropdown-disabled')) return;

    // Disable window.resize handler for old IE (window.resize bug)
    $(window).off('resize.dropdown');

    dropdown
.css({
  left: dropdown.hasClass('anchor-right') ?
                trigger.position().left - (dropdown.outerWidth() - trigger.outerWidth()) + hOffset : trigger.position().left + hOffset,
  top: trigger.position().top + trigger.outerHeight() + vOffset
})
.show();

    trigger.addClass('dropdown-open');

    // Re-enable window.resize handler for old IE (window.resize bug)
    setTimeout(function () {
      $(window).on('resize.dropdown', hideDropdowns);
    }, 1);

  };

  function hideDropdowns(event) {

    var targetGroup = event ? $(event.target).parents().andSelf() : null;
    if (targetGroup && targetGroup.is('.dropdown-menu') && !targetGroup.is('A')) return;

    $('BODY')
.find('.dropdown-menu').hide().end()
.find('[data-dropdown]').removeClass('dropdown-open');

  };

  $(function () {
    $('BODY').on('click.dropdown', '[data-dropdown]', showMenu);
    $('HTML').on('click.dropdown', hideDropdowns);
  });

})(jQuery);
if(!this.JSON2){JSON2=function(){function f(n){return n<10?'0'+n:n;}
var escapeable=/["\\\x00-\x1f\x7f-\x9f]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){return escapeable.test(string)?'"'+string.replace(escapeable,function(a){var c=meta[a];if(typeof c==='string'){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
(c%16).toString(16);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
if(value.toUTCString)
{var xx='"\\/Date('+value.getTime()+')\\/"';return xx;}
gap+=indent;partial=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value,rep);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){v=str(k,value,rep);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
return{stringify:function(value,replacer,space){var i;gap='';indent='';if(space){if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}}
if(!replacer){rep=function(key,value){if(!Object.hasOwnProperty.call(this,key)){return undefined;}
return value;};}else if(typeof replacer==='function'||(typeof replacer==='object'&&typeof replacer.length==='number')){rep=replacer;}else{throw new Error('JSON.stringify');}
return str('',{'':value});},parse:function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
if(/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){var regEx=/(\"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*?\")|(\"\\*\/Date\(.*?\)\\*\/")/g;text=text.replace(regEx,this.regExDate);j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');},regExDate:function(str,p1,p2,offset,s)
{str=str.substring(1).replace('"','');var date=str;if(/\/Date(.*)\//.test(str)){str=str.match(/Date\((.*?)\)/)[1];date="new Date("+parseInt(str)+")";}
else{var matches=str.split(/[-,:,T,Z]/);matches[1]=(parseInt(matches[1],0)-1).toString();date="new Date(Date.UTC("+matches.join(",")+"))";}
return date;},quote:quote};}();}var _tmplCache = {};
window.parseTemplate = function(str, data) {
  /// <summary>
  /// Client side template parser that uses &lt;#= #&gt; and &lt;# code #&gt; expressions.
  /// and # # code blocks for template expansion.
  /// NOTE: chokes on single quotes in the document in some situations
  ///       use &amp;rsquo; for literals in text and avoid any single quote
  ///       attribute delimiters.
  /// </summary>    
  /// <param name="str" type="string">The text of the template to expand</param>    
  /// <param name="data" type="var">
  /// Any data that is to be merged. Pass an object and
  /// that object's properties are visible as variables.
  /// </param>    
  /// <returns type="string" />  
  var err = "";
  try {
    var func = _tmplCache[str];
    if (!func) {
      var strFunc =
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
          "with(obj){p.push('" +
          //                        str
      //                  .replace(/[\r\t\n]/g, " ")
      //                  .split("<#").join("\t")
      //                  .replace(/((^|#>)[^\t]*)'/g, "$1\r")
      //                  .replace(/\t=(.*?)#>/g, "',$1,'")
      //                  .split("\t").join("');")
      //                  .split("#>").join("p.push('")
      //                  .split("\r").join("\\'") + "');}return p.join('');";

          str.replace(/[\r\t\n]/g, " ")
            .replace(/'(?=[^#]*#>)/g, "\t")
            .split("'").join("\\'")
            .split("\t").join("'")
            .replace(/<#=(.+?)#>/g, "',$1,'")
            .split("<#").join("');")
            .split("#>").join("p.push('")
          + "');}return p.join('');";

      //alert(strFunc);
      func = new Function("obj", strFunc);
      _tmplCache[str] = func;
    }
    return func(data);
  } catch(e) {
    err = e.message;
    return "< # ERROR: " + err + " # >"
  }
  ;
}
$.fn.highlight = function () {
  return this.effect("highlight", {}, 5000);
}
/**
 * Insert content at caret position (converted to jquery function)
 * @link 
http://alexking.org/blog/2003/06/02/inserting-at-the-cursor-using-javascript
 */
$.fn.insertAtCaret = function (myValue) {
        return this.each(function(){
                //IE support
                if (document.selection) {
                        this.focus();
                        sel = document.selection.createRange();
                        sel.text = myValue;
                        this.focus();
                }
                //MOZILLA/NETSCAPE support
                else if (this.selectionStart || this.selectionStart == '0') {
                        var startPos = this.selectionStart;
                        var endPos = this.selectionEnd;
                        var scrollTop = this.scrollTop;
                        this.value = this.value.substring(0, startPos)
                                      + myValue
                              + this.value.substring(endPos, this.value.length);
                        this.focus();
                        this.selectionStart = startPos + myValue.length;
                        this.selectionEnd = startPos + myValue.length;
                        this.scrollTop = scrollTop;
                } else {
                        this.value += myValue;
                        this.focus();
                }
        });

};



                  /*
                  * jQuery pretty date plug-in 1.0.0
                  * 
                  * http://bassistance.de/jquery-plugins/jquery-plugin-prettydate/
                  * 
                  * Based on John Resig's prettyDate http://ejohn.org/blog/javascript-pretty-date
                  *
                  * Copyright (c) 2009 JÃ¶rn Zaefferer
                  *
                  * $Id: jquery.validate.js 6096 2009-01-12 14:12:04Z joern.zaefferer $
                  *
                  * Dual licensed under the MIT and GPL licenses:
                  *   http://www.opensource.org/licenses/mit-license.php
                  *   http://www.gnu.org/licenses/gpl.html
                  */

                  (function () {

                    $.prettyDate = {

                      template: function (source, params) {
                        if (arguments.length == 1)
                          return function () {
                            var args = $.makeArray(arguments);
                            args.unshift(source);
                            return $.prettyDate.template.apply(this, args);
                          };
                        if (arguments.length > 2 && params.constructor != Array) {
                          params = $.makeArray(arguments).slice(1);
                        }
                        if (params.constructor != Array) {
                          params = [params];
                        }
                        $.each(params, function (i, n) {
                          source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
                        });
                        return source;
                      },

                      now: function () {
                        return new Date();
                      },

                      // Takes an ISO time and returns a string representing how
                      // long ago the date represents.
                      format: function (time) {
                        var date = new Date((time || "").replace(/-/g, "/").replace(/[TZ]/g, " ")),
			diff = ($.prettyDate.now().getTime() - date.getTime()) / 1000,
			day_diff = Math.floor(diff / 86400);

                        if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31)
                          return;

                        var messages = $.prettyDate.messages;
                        return day_diff == 0 && (
				diff < 60 && messages.now ||
				diff < 120 && messages.minute ||
				diff < 3600 && messages.minutes(Math.floor(diff / 60)) ||
				diff < 7200 && messages.hour ||
				diff < 86400 && messages.hours(Math.floor(diff / 3600))) ||
			day_diff == 1 && messages.yesterday ||
			day_diff < 7 && messages.days(day_diff) ||
			day_diff < 31 && messages.weeks(Math.ceil(day_diff / 7));
                      }

                    };

                    $.prettyDate.messages = {
                      now: "zojuist",
                      minute: "1 minuut geleden",
                      minutes: $.prettyDate.template("{0} minuten geleden"),
                      hour: "1 uur geleden",
                      hours: $.prettyDate.template("{0} uren geleden"),
                      yesterday: "Gisteren",
                      days: $.prettyDate.template("{0} dagen geleden"),
                      weeks: $.prettyDate.template("{0} weken geleden")
                    };

                    $.fn.prettyDate = function (options) {
                      options = $.extend({
                        value: function () {
                          return $(this).attr("title");
                        },
                        interval: 10000
                      }, options);
                      var elements = this;
                      function format() {
                        elements.each(function () {
                          var date = $.prettyDate.format(options.value.apply(this));
                          if (date && $(this).text() != date)
                            $(this).text(date);
                        });
                      }
                      format();
                      if (options.interval)
                        setInterval(format, options.interval);
                      return this;
                    };

                  })();

(function($) {
    /*
     * Auto-growing textareas; technique ripped from Facebook
     */
    $.fn.autogrow = function(options) {
        
        this.filter('textarea').each(function() {
            
            var $this       = $(this),
                minHeight   = 75,
                lineHeight  = $this.css('lineHeight');
            
            var shadow = $('<div></div>').css({
                position:   'absolute',
                top:        -10000,
                left:       -10000,
                width:      $(this).width(),
                fontSize:   $this.css('fontSize'),
                fontFamily: $this.css('fontFamily'),
                lineHeight: $this.css('lineHeight'),
                resize:     'none'
            }).appendTo(document.body);
            
            var update = function() {
                var val = this.value.replace(/</g, '&lt;')
                                    .replace(/>/g, '&gt;')
                                    .replace(/&/g, '&amp;')
                                    .replace(/\n/g, '<br/>');
                
                shadow.html(val);
                $(this).css('height', Math.max(shadow.height() + 20, minHeight));
            }
            
            $(this).change(update).keyup(update).keydown(update);
            
            update.apply(this);
            
        });
        
        return this;
        
    }
    
})(jQuery);

;(function($) {
	$.fn.extend({
		addSelectOption: function(text, value, select) {
			return this.filter('select').each(function() {
            var option = new Option(text, value);
            var dropdownList = $(this)[0];
            if ($.browser.msie) {
                dropdownList.add(option);
            }
            else {
                dropdownList.add(option, null);
            }
            if (select)
              option.selected = true;
				});
		},
		hasSelectOption : function(value) {
			return this.find("option[value='" + value + "']").length > 0;
		},
		removeSelectOption : function(value) {
			return this.find("option[value='" + value + "']").remove();
		},
		clearOptions : function() {
			return this.filter('select').each(function() {
            var dropdownList = $(this)[0];
            while (dropdownList.options.length > 0) {
                dropdownList.options[0] = null;
            }
				});
		},
	  trackAnalytics : function() {
      return this.filter('a[track]').click(function() {
            var trackurl = $(this).attr('track');
            try {
              _gaq.push(['_trackPageview', '/out/' + trackurl]);
              //pageTracker._trackPageview('/out/' + trackurl); 
            }
            catch(ex) {
            }
				});
		  }
	  
	  });
})(jQuery);
    $.tablesorter.addParser({
        // set a unique id
        id: 'date-nl',
        is: function(s) {
            // return false so this parser is not auto detected
            return false;
        },
        format: function(s) {
            // format your data for normalization
            var dateSplit = s.split('-');

            if(3 !== dateSplit.length)
                    return 0;
            return new Date(dateSplit[2], dateSplit[1], dateSplit[0]);
        },
        // set type, either numeric or text
        type: 'date'
    });
    $.tablesorter.addParser({
        // set a unique id
        id: 'snelheid',
        is: function(s) {
            // return false so this parser is not auto detected
            return false;
        },
        format: function(s) {
            return s.replace(' km/u', '').replace(',', '.').replace('-', '0');
        },
        // set type, either numeric or text
        type: 'numeric'
    });
    $.tablesorter.addParser({
        // set a unique id
        id: 'procent',
        is: function(s) {
            // return false so this parser is not auto detected
            return false;
        },
        format: function(s) {
            return s.replace(' %', '').replace(',', '.').replace('-', '0');
        },
        // set type, either numeric or text
        type: 'numeric'
    });
    $.tablesorter.addParser({
        // set a unique id
        id: 'positie',
        is: function(s) {
            // return false so this parser is not auto detected
            return false;
        },
        format: function(s) {
            return s.substring(0, s.indexOf(' van '));
        },
        // set type, either numeric or text
        type: 'numeric'
    });
    $.tablesorter.addParser({
        // set a unique id
        id: 'afstand',
        is: function(s) {
            // return false so this parser is not auto detected
            return false;
        },
        format: function (s) {
          if (s.indexOf('km') !== -1) {
            var afstandInKm = parseFloat(s.replace('km', '').replace(',', '.').trim());
            return afstandInKm * 1000;
          }
          var afstandInM = parseFloat(s.replace('m', '').replace('.', '').replace('?', '-1').trim());
          return afstandInM;
        },
        // set type, either numeric or text
        type: 'numeric'
    });

/// <reference path="jquery/jquery.1.2.6.js" />

function ServiceProxy(serviceUrl) {
  var _I = this;
  this.serviceUrl = serviceUrl;
  // *** Call a wrapped object
  this.invoke = function(method, data, callback, error, bare) {
    // *** Convert input data into JSON - REQUIRES Json2.js
    var json = JSON2.stringify(data);
  
    // *** The service endpoint URL        
    var url = _I.serviceUrl + method;
    $.ajax({
      url: url,
      data: json,
      type: "POST",
      processData: false,
      contentType: "application/json",
      timeout: 10000,
      dataType: "text",  // not "json" we'll parse
      success:
                    function(res) {
                      if (!callback) return;

                      // *** Use json library so we can fix up MS AJAX dates
                      var result = JSON2.parse(res);

                      // *** Bare message IS result
                      if (bare)
                      { callback(result); return; }

                      // *** Wrapped message contains top level object node
                      // *** strip it off
                      for (var property in result) {
                        callback(result[property]);
                        break;
                      }
                    },
      error: function(xhr) {
        if (!error) return;
        if (xhr.responseText) {
          var err = JSON2.parse(xhr.responseText);
          if (err)
            error(err);
          else
            error({ Message: "Unknown server error." })
        }
        return;
      }
    });
  }
}


//Type.registerNamespace("Looptijden.Info");
looptijden = function() {
}
Looptijden = function() {
}
Looptijden.Info = function() {
}

Looptijden.Info.Ajax = function() {
  // ctor
}

Looptijden.Info.Ajax.prototype = {

  postMvcForm : function(actionUrl, form, success) {
    $.ajax({
      type: "POST",
      url: actionUrl,
      data: form.serialize(),
      dataType: "html",
      success: success
    });
  },
  getHtml : function(actionUrl, data, success) {
    $.ajax({
      type: "GET",
      url: actionUrl,
      data: data,
      dataType: "html",
      success: success
    });
  },
  getJsonData : function(actionUrl, data, success) {
    this.requestJsonData("GET", actionUrl, data, success);
  },
  postJsonData : function(actionUrl, data, success) {
    this.requestJsonData("POST", actionUrl, jQuery.param(data), success);
  },
  requestJsonData : function(method, actionUrl, data, success) {
    $.ajax({
      type: method,
      url: actionUrl,
      data: data,
      contentType: "application/json",
      dataType: "text",
      success:    function(res) {
                      if (!success) return;

                      // *** Use json library so we can fix up MS AJAX dates
                      var result = JSON2.parse(res);

                      // *** Bare message IS result
                      if (true)
                      { success(result); return; }

                      // *** Wrapped message contains top level object node
                      // *** strip it off
                      for (var property in result) {
                        success(result[property]);
                        break;
                      }
                    },
      error: function(xhr) {
        if (!xhr) return;
        if (xhr.responseText) {
          var err = JSON2.parse(xhr.responseText);
          if (err)
            error(err);
          else
            error({ Message: "Unknown server error." })
        }
        return;
      }
    });
  }
}


Looptijden.Info.Map = function() {
  // ctor
}

Looptijden.Info.Map.prototype = {

  initialize: function(mapElementId) {
    var map = new google.maps.Map2(document.getElementById(mapElementId));
    map.setCenter(new google.maps.LatLng(52, 5), 9);
    map.addControl(new GMapTypeControl());
    map.addControl(new GScaleControl());
    map.addControl(new GLargeMapControl());

    return map;
  },

  createMap: function(mapElement) {
    var map = new GMap2(mapElement);
    map.setCenter(new GLatLng(52, 5), 7);
    map.addControl(new GMapTypeControl());
    map.addControl(new GLargeMapControl());

    return map;
  },

  createMarker: function(latlng, title, html, draggable, iconUrl) {
    var markerIcon = new GIcon(G_DEFAULT_ICON);
    if (iconUrl) {
      markerIcon.shadow = null;
      markerIcon.image = iconUrl;
      markerIcon.iconSize = new GSize(10, 10);
      markerIcon.iconAnchor = new GPoint(5, 5);
    }
    return this.createMarkerWithIcon(latlng, title, html, draggable, markerIcon);
  },

  createMarkerWithIcon: function(latlng, title, html, draggable, markerIcon) {
    var marker = new GMarker(latlng, {icon: markerIcon, draggable: draggable, title: title});
    GEvent.addListener(marker, "click", function() {
      marker.openInfoWindowHtml(html);
    });
    return marker;
  }

}



//Looptijden.Info.Map.registerClass('Looptijden.Info.Map');


/*
* Date Format 1.2.2
* (c) 2007-2008 Steven Levithan <stevenlevithan.com>
* MIT license
* Includes enhancements by Scott Trenda <scott.trenda.net> and Kris Kowal <cixar.com/~kris.kowal/>
*
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*/
var dateFormat = function() {
  var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function(val, len) {
		  val = String(val);
		  len = len || 2;
		  while (val.length < len) val = "0" + val;
		  return val;
		};

  // Regexes and supporting functions are cached through closure
  return function(date, mask, utc) {
    var dF = dateFormat;

    // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
    if (arguments.length == 1 && (typeof date == "string" || date instanceof String) && !/\d/.test(date)) {
      mask = date;
      date = undefined;
    }

    // Passing date through Date applies Date.parse, if necessary
    date = date ? new Date(date) : new Date();
    if (isNaN(date)) throw new SyntaxError("invalid date");

    mask = String(dF.masks[mask] || mask || dF.masks["default"]);

    // Allow setting the utc argument via the mask
    if (mask.slice(0, 4) == "UTC:") {
      mask = mask.slice(4);
      utc = true;
    }

    var _ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
			  d: d,
			  dd: pad(d),
			  ddd: dF.i18n.dayNames[D],
			  dddd: dF.i18n.dayNames[D + 7],
			  m: m + 1,
			  mm: pad(m + 1),
			  mmm: dF.i18n.monthNames[m],
			  mmmm: dF.i18n.monthNames[m + 12],
			  yy: String(y).slice(2),
			  yyyy: y,
			  h: H % 12 || 12,
			  hh: pad(H % 12 || 12),
			  H: H,
			  HH: pad(H),
			  M: M,
			  MM: pad(M),
			  s: s,
			  ss: pad(s),
			  l: pad(L, 3),
			  L: pad(L > 99 ? Math.round(L / 10) : L),
			  t: H < 12 ? "a" : "p",
			  tt: H < 12 ? "am" : "pm",
			  T: H < 12 ? "A" : "P",
			  TT: H < 12 ? "AM" : "PM",
			  Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
			  o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
			  S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};

    return mask.replace(token, function($0) {
      return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
    });
  };
} ();

// Some common format strings
dateFormat.masks = {
  "default": "ddd mmm dd yyyy HH:MM:ss",
  shortDate: "m/d/yy",
  mediumDate: "mmm d, yyyy",
  longDate: "mmmm d, yyyy",
  fullDate: "dddd, mmmm d, yyyy",
  shortTime: "h:MM TT",
  mediumTime: "h:MM:ss TT",
  longTime: "h:MM:ss TT Z",
  isoDate: "yyyy-mm-dd",
  isoTime: "HH:MM:ss",
  isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
  isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// Internationalization strings
dateFormat.i18n = {
  dayNames: [
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	],
  monthNames: [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	]
};

// For convenience...
Date.prototype.format = function(mask, utc) {
  return dateFormat(this, mask, utc);
};

/*jQuery toolbar*/
$(function(){
  //all hover and click logic for buttons
  $(".fg-button:not(.ui-state-disabled)")
  .hover(
	  function(){ 
		  $(this).addClass("ui-state-hover"); 
	  },
	  function(){ 
		  $(this).removeClass("ui-state-hover"); 
	  }
  )
  .mousedown(function(){
		  $(this).parents('.fg-buttonset-single:first').find(".fg-button.ui-state-active").removeClass("ui-state-active");
		  if( $(this).is('.ui-state-active.fg-button-toggleable, .fg-buttonset-multi .ui-state-active') ){ $(this).removeClass("ui-state-active"); }
		  else { $(this).addClass("ui-state-active"); }	
  })
  .mouseup(function(){
	  if(! $(this).is('.fg-button-toggleable, .fg-buttonset-single .fg-button,  .fg-buttonset-multi .fg-button') ){
		  $(this).removeClass("ui-state-active");
	  }
  });
});

/* Make static maps zoomable by changing them into real maps */
$(function () {
  var mapWrap = new looptijden.mapwrap();

  $('.staticMap').click(function () {
    mapWrap.ensureAPILoaded('changeStaticMapIntoDynamic');
  });
});

function changeStaticMapIntoDynamic() {
  $('.staticMap').each(function () {
    var mapWrap = new looptijden.mapwrap();

    var elem = $(this).addClass('relative');
    if (elem.find('.map').length == 0) {
      var coords = elem.attr('data-route-polyline');
      var startLat = elem.attr('data-route-start-lat');
      var startLng = elem.attr('data-route-start-lng');
      var endLat = elem.attr('data-route-end-lat');
      var endLng = elem.attr('data-route-end-lng');
      var start = new google.maps.LatLng(startLat, startLng);
      var end = new google.maps.LatLng(endLat, endLng);

      var mapDiv = $('<div class="map">&nbsp;</div>').appendTo(elem);
      mapDiv.css('position', 'absolute').css('top', '0px').css('left', '0px').width(elem.width()).height(elem.height());
      //var mapWrap = new looptijden.mapwrap();
      mapWrap.initialize(mapDiv[0]);
      var polyline = mapWrap.decodeLine(coords);
      var path = mapWrap.addPolyline(polyline);
      mapWrap.zoomToPolyline(path);
      mapWrap.zoomIn(); // one zoom level more than Google suggests yields better results
      mapWrap.createKilometerMarkers(path);
      var startMarker = mapWrap.addMarker(start, 'Start locatie', '', false);
      startMarker.setIcon('/content/images/markers/dd-start.png');
      var endMarker = mapWrap.addMarker(end, 'Eind locatie', '', false);
    }
  });
}/*
* Looptijden.nl google maps v3 wrapping library
* 
* (c) 2009-2011 Koen Muilwijk, Looptijden.nl
*
* Gebruik van (delen van) deze code zonder expliciete toestemming is niet toegestaan.
*/
if(typeof(looptijden) == "undefined"){
  looptijden = function () { };
}

looptijden.mapwrap = function () {
  this.mapElement = null;         // div containing the map
  this.map = null;                // the google.maps.map
  this.labelDebug = null;         // optional debug label
  this.markers = [];              // all markers on the route
  this.clusterer = null;
  this.iconKm = null;
  this.iconKms = [];              // all km icons
  this.numberOfKmIcons = 43;      // number of km icons available (max available km marker)
  this.numberOf10KmIcons = 150;   // max available km marker with steps of 10 km
  this.kilometerMarkers = {};     // all km markers on the route
  this.infoWindow = null;         // the tooltip is shared to prevent multiple popups
}

looptijden.mapwrap.prototype = {
  initialize: function (mapElement, options) {
    var settings = jQuery.extend({
      mapViewHybrid: true,
      zoom: 7
    }, options);

    var mapOptions = {
      zoom: settings.zoom,
      mapTypeControl: true,
      mapTypeControlOptions: { style: google.maps.MapTypeControlStyle.DROPDOWN_MENU },
      navigationControl: true,
      navigationControlOptions: { style: google.maps.NavigationControlStyle.DEFAULT },
      mapTypeId: settings.mapViewHybrid ? google.maps.MapTypeId.HYBRID : google.maps.MapTypeId.ROADMAP
    };
    this.infoWindow = new google.maps.InfoWindow();

    if (mapElement != null) {
      this.map = new google.maps.Map(mapElement, mapOptions);
      try {
        this.map.setCenter(new google.maps.LatLng(52, 5.1));
      }
      catch (ex) { }
      this.mapElement = mapElement;
      this.mapElement.style.cursor = 'crosshair';
    }

    this.iconKm = new google.maps.MarkerImage('/content/images/markers/km.png', new google.maps.Size(20, 20), new google.maps.Point(0, 0), new google.maps.Point(10, 10));
    for (var i = 1; i <= this.numberOfKmIcons; i++) {
      this.iconKms['km' + i] = new google.maps.MarkerImage('/content/images/markers/km' + i + '.png', new google.maps.Size(20, 20), new google.maps.Point(0, 0), new google.maps.Point(10, 10));
      //this.iconKms.push(new google.maps.MarkerImage('/content/images/markers/km' + i + '.png', new google.maps.Size(20, 20), new google.maps.Point(0, 0), new google.maps.Point(10, 10)));
    }
    for (var i = 10; i <= this.numberOf10KmIcons; i += 10) {
      this.iconKms['km' + i] = new google.maps.MarkerImage('/content/images/markers/km' + i + '.png', new google.maps.Size(20, 20), new google.maps.Point(0, 0), new google.maps.Point(10, 10));
      //this.icon10Kms.push(new google.maps.MarkerImage('/content/images/markers/km' + i + '.png', new google.maps.Size(20, 20), new google.maps.Point(0, 0), new google.maps.Point(10, 10)));
    }
  },

  wrap: function (map, mapElement) {
    this.map = map;
    this.mapElement = mapElement;
  },

  clearMarkers: function () {
    for (var i = 0; i < this.markers.length; i++) {
      this.markers[i].setMap(null);
    }
    this.markers = [];
  },

  clearKilometerMarkers: function () {
    for (var i = 0; i < this.kilometerMarkers.length; i++) {
      this.kilometerMarkers[i].setMap(null);
    }
  },

  createKilometerMarkers: function (polypath) {
    this.clearKilometerMarkers();

    //if (this.map.getZoom() < 13)
    //      return; // don't do km's if zoom is too low
    var multiplier = 1;
    var points = this.getPointsAtDistance(polypath, 1000);
    if (points.length > 50) {
      multiplier = 10;
      points = this.getPointsAtDistance(polypath, 10000);
    }
    for (var i = 0; i < points.length; i++) {
      var icon = this.iconKm;
      var km = (i + 1) * multiplier;
      if (this.iconKms.hasOwnProperty('km' + km)) {
        icon = this.iconKms['km' + km];
      }
      var marker = new google.maps.Marker({
        position: points[i],
        map: this.map,
        flat: true,
        icon: icon
        //icon: i <= (this.numberOfKmIcons - 1) ? this.iconKms[i] : this.iconKm
      });
      this.kilometerMarkers[i] = marker;
    }
  },

  zoomTo: function (point, level) {
    this.map.setCenter(point);
    this.map.setZoom(level);
  },
  zoomToNL: function () {
    this.map.setCenter(new google.maps.LatLng(52, 5.1));
    this.map.setZoom(7);
  },
  zoomIn: function () {
    this.map.setZoom(this.map.getZoom() + 1);
  },
  zoomOut: function () {
    this.map.setZoom(this.map.getZoom() - 1);
  },
  // the smooth zoom function
  zoomSmoothly: function (max, cnt) {
    var self = this;
    if (cnt >= max) {
      return;
    } else {
      var z = google.maps.event.addListener(this.map, 'zoom_changed', function (event) {
        google.maps.event.removeListener(z);
        self.zoomSmoothly(max, cnt + 1);
      });
      setTimeout(function () { self.map.setZoom(cnt); }, 200); // 80ms is what I found to work well on my system -- it might not work well on all systems
    }
  },
  
  resize: function () {
    google.maps.event.trigger(this.map, 'resize');
  },

  gotoLocation: function (locatie) {
    var geocoder = new google.maps.Geocoder();
    var self = this;
    var options = { 'address': locatie, 'language': 'nl', 'region': 'nl' };
    geocoder.geocode(options, function (results, status) {
      if (status == google.maps.GeocoderStatus.OK) {
        self.map.setCenter(results[0].geometry.location);
        self.map.setZoom(12);
      }
      else {
        alert("De opgegeven locatie is niet gevonden. Probeer een plaatsnaam, straat of postcode in te vullen.");
      }
    });

  },

  showWedstrijdMarkers: function (data, options) {
    var empty = {};
    var defaults = { zoomToBounds: true, clusterMarkers: false, onMarkerClick: this._clickHandler };
    var settings = $.extend(empty, defaults, options);

    if (settings.clusterMarkers) {
      if (this.clusterer == null) {
        var clusterOptions = { gridSize: 30, maxZoom: 12 };
        this.clusterer = new MarkerClusterer(this.map, [], clusterOptions);
      }
      this.clusterer.clearMarkers();
    }

    this.clearMarkers();
    var image = new google.maps.MarkerImage('/content/images/markers/wedstrijd.gif',
        new google.maps.Size(19, 29),
        new google.maps.Point(0, 0),
        new google.maps.Point(8, 27)
      );

    var bounds = new google.maps.LatLngBounds();
    for (i = 0; i < data.length; i++) {
      var coord = data[i];
      var point = new google.maps.LatLng(coord.Latitude, coord.Longitude);
      bounds.extend(point);

      var html = "";
      for (j = 0; j < coord.DataLines.length; j++) {
        html = html + "- " + coord.DataLines[j] + "<br/>";
      }

      var prefix = "";
      if (coord.Datum !== null && !(coord.Datum.Day == 0 && coord.Datum.Month == 0 && coord.Datum.Year == 0)) {
        var datum = coord.Datum.Day + '-' + coord.Datum.Month + '-' + coord.Datum.Year;
        prefix = datum + ': ';
      }

      var hasCustomImage = coord.CustomMarkerImageName != null && coord.CustomMarkerImageName != "" && coord.CustomMarkerImageName != "null";

      var marker = new google.maps.Marker({
        position: point,
        icon: hasCustomImage ? "/content/images/markers/" + coord.CustomMarkerImageName : image,
        title: prefix + coord.Titel
      });
      if (!settings.clusterMarkers) {
        marker.setMap(this.map);
      }
      marker.set('parcoursId', coord.ParcoursId);
      marker.set('tijdId', coord.TijdId);
      this.markers.push(marker);

      google.maps.event.addListener(marker, 'click', (function (self, map, marker, tooltip, html) {
        return function () {
          settings.onMarkerClick(map, marker, tooltip, html);
        }
      })(this, this.map, marker, this.infoWindow, html));
    }

    if (settings.clusterMarkers) {
      this.clusterer.addMarkers(this.markers);
    }

    if (settings.zoomToBounds && !bounds.isEmpty()) {
      this.map.setOptions({ maxZoom: 15 });
      this.map.fitBounds(bounds);
      this.map.setOptions({ maxZoom: null });
    }
  },

  addMarker: function (position, title, infoText, draggable, icon) {
    var marker = new google.maps.Marker({
      position: position,
      title: title,
      draggable: draggable,
      icon: icon
    });
    marker.setMap(this.map);

    if (infoText != null && infoText.length > 0) {
      google.maps.event.addListener(marker, 'click', (function (self, map, marker, tooltip, html) {
        return function () {
          self._clickHandler(map, marker, tooltip, html);
        };
      })(this, this.map, marker, this.infoWindow, infoText));
    }

    return marker;
  },

  addAndZoomToPolypath: function (polygonCoordinates) {
    var path = this.addPolypath(polygonCoordinates);
    this.zoomToPolyline(path);
    this.createKilometerMarkers(path);
    return path;
  },
   
  addPolypath: function (polygonCoordinates) {
    var polyLine = [];
    for (var i = 0; i < polygonCoordinates.length; i++) {
      polyLine.push(new google.maps.LatLng(polygonCoordinates[i].Latitude, polygonCoordinates[i].Longitude));
    }
    return this.addPolyline(polyLine);
  },

  addPolyline: function (polyline) {
    var routePath = new google.maps.Polyline({
      path: polyline,
      strokeColor: "#FF0000",
      strokeOpacity: 0.8,
      strokeWeight: 2
    });
    routePath.setMap(this.map);
    return routePath;
  },

  zoomToPolyline: function (routePath) {
    var bounds = new google.maps.LatLngBounds();
    var coords = routePath.getPath().getArray();
    for (i = 0; i < coords.length; i++) {
      bounds.extend(coords[i]);
    }
    if (!bounds.isEmpty()) {
      this.map.fitBounds(bounds);
      if (this.map.getZoom() > 15) {
        this.map.setZoom(15);
      }
    }
  },

  _clickHandler: function (map, marker, infoWindow, html) {
    /*var infowindow = new google.maps.InfoWindow({
    content: html
    });*/
    infoWindow.close();
    infoWindow.setContent(html);
    infoWindow.open(map, marker);
  },

  ensureAPILoaded: function (functionToCall) {
    try {
      var bounds = new google.maps.LatLngBounds();
      eval(functionToCall + "()");
    }
    catch (ex) {
      this.loadGoogleAPI(functionToCall);
    }
  },

  loadGoogleAPI: function (functionToCall) {
    var script = document.createElement("script");
    script.src = "//maps.google.com/maps/api/js?sensor=false&key=AIzaSyARUJAZ-cFBxKPU5huArGq0DCGT-LPGSJs&h1=nl&language=nl&region=nl&callback=" + functionToCall;
    script.type = "text/javascript";
    document.body.appendChild(script);
  },

  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * Properties
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

  getMap: function () {
    return this.map;
  },

  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * Events
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * Utility function
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  debug: function (info) {
    if (this.labelDebug != null) {
      this.labelDebug.append(info);
    }
  },

  // decode an google encoded polyline
  decodeLine: function (encoded) {
    var len = encoded.length;
    var index = 0;
    var array = [];
    var lat = 0;
    var lng = 0;

    while (index < len) {
      var b;
      var shift = 0;
      var result = 0;
      do {
        b = encoded.charCodeAt(index++) - 63;
        result |= (b & 0x1f) << shift;
        shift += 5;
      } while (b >= 0x20);
      var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
      lat += dlat;

      shift = 0;
      result = 0;
      do {
        b = encoded.charCodeAt(index++) - 63;
        result |= (b & 0x1f) << shift;
        shift += 5;
      } while (b >= 0x20);
      var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
      lng += dlng;

      array.push(new google.maps.LatLng(lat * 1e-5, lng * 1e-5));
    }
    return array;
  },

  // === A method which returns an array of GLatLngs of points a given interval along the path ===
  // Based on epoly, rewritten for maps V3
  getClosestPoint: function (poly, position, forceSnap) {
    var point = null;
    var closest = 1000;
    var polyPoints = poly.getPath();
    for (var i = 1; i < polyPoints.getLength(); i++) {
      var p = polyPoints.getAt(i);
      var dist = this.distance(p, position);
      if (dist < closest) {
        point = p;
        closest = dist;
      }
    }
    if (closest < 50) {
      return point;
    }
    if (closest > 50 && forceSnap) {
      var dist = 0;
      var next = 0;
      for (var i = 1; i < polyPoints.getLength(); i++) {
        //olddist = dist;
        var p1 = polyPoints.getAt(i - 1);
        var p2 = polyPoints.getAt(i);
        var pointDistance = this.distance(p1, p2);
        for (var m = .1; m < 1; m += .1) {
          //  var m = (next - olddist) / (dist - olddist);
          //var p = new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
          var p = new google.maps.LatLng(
                        p2.lat() - (m * (p2.lat() - p1.lat())),
                        p2.lng() - (m * (p2.lng() - p1.lng())));
          var dist = this.distance(p, position);
          if (dist < closest) {
            point = p;
            closest = dist;
          }
        }
      }
      return point;
    }
    return position;
  },

  // === A method which returns an array of GLatLngs of points a given interval along the path ===
  // Based on epoly, rewritten for maps V3
  getDistanceFromStartToPoint: function (poly, position) {
    var lineDistance = 0;
    var polyPoints = poly.getPath();
    for (var i = 1; i < polyPoints.getLength(); i++) {
      //olddist = dist;
      var p1 = polyPoints.getAt(i - 1);
      var p2 = polyPoints.getAt(i);
      var pointDistance = this.distance(p1, p2);
      if (p1.lat() == position.lat() && p1.lng() == position.lng()) {
        return lineDistance;
      }
      for (var m = .1; m < 1; m += .1) {
        //  var m = (next - olddist) / (dist - olddist);
        //var p = new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
        var p = new google.maps.LatLng(
                        p2.lat() - (m * (p2.lat() - p1.lat())),
                        p2.lng() - (m * (p2.lng() - p1.lng())));
        var dist = this.distance(p1, p);
        if (p.lat() == position.lat() && p.lng() == position.lng()) {
          return lineDistance + dist;
        }
      }
      lineDistance += pointDistance;
    }
    return lineDistance;
  },
  // === A method which returns an array of GLatLngs of points a given interval along the path ===
  // Based on epoly, rewritten for maps V3
  getPointsAtDistance: function (poly, metres) {
    var next = metres;
    var points = [];
    // some awkward special cases
    if (metres <= 0) return points;
    var dist = 0;
    var olddist = 0;

    var polyPoints = poly.getPath();
    for (var i = 1; i < polyPoints.getLength(); i++) {
      olddist = dist;
      var p1 = polyPoints.getAt(i - 1);
      var p2 = polyPoints.getAt(i);
      dist += this.distance(p1, p2);
      while (dist > next) {
        var m = (next - olddist) / (dist - olddist);
        points.push(new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m));
        next += metres;
      }
    }
    return points;
  },
  // Calculates the total distance of the polygon
  getPolygonDistance: function (poly) {
    var dist = 0;
    var polyPoints = poly.getPath();
    for (var i = 1; i < polyPoints.getLength(); i++) {
      var p1 = polyPoints.getAt(i - 1);
      var p2 = polyPoints.getAt(i);
      dist += this.distance(p1, p2);
    }
    return dist;
  },
  
  addHexagon: function (center, radius, stretchRatioWidth, polyOptions) {
    var defaults = {
      fillColor: '#0F7ECC',
      strokeColor: '#FF9103',
      strokeWeight: 1,
      strokeOpacity: .5
    };
    
    var points = [];
    for (var vertex = 1; vertex <= 6; vertex++) {
      points.push(this.getHexagonVertex(center, radius, stretchRatioWidth, vertex));
    }

    var settings = $.extend({ path: points }, defaults, polyOptions);
    var poly = new google.maps.Polygon(settings);
    poly.setMap(this.map);
    return poly;
  },
  getHexagonVertex: function (center, radius, stretchRatioWidth, vertex) {
    if (vertex == 1) {
      return this.offsetWithBearing(center, radius, 0);
    }
    else if (vertex == 2) {
      return this.offsetWithBearing(center, radius - (radius * stretchRatioWidth), 60);
    }
    else if (vertex == 3) {
      return this.offsetWithBearing(center, radius - (radius * stretchRatioWidth), 120);
    }
    else if (vertex == 4) {
      return this.offsetWithBearing(center, radius, 180);
    }
    else if (vertex == 5) {
      return this.offsetWithBearing(center, radius - (radius * stretchRatioWidth), 240);
    }
    else if (vertex == 6) {
      return this.offsetWithBearing(center, radius - (radius * stretchRatioWidth), 300);
    }
    return null;
  },

  offsetWithBearing: function (point, dist, bearing) {
    var latConv = this.distance(point, new google.maps.LatLng(point.lat() + 0.001, point.lng())) * 1000;
    var lngConv = this.distance(point, new google.maps.LatLng(point.lat(), point.lng() + 0.001)) * 1000;
    var lat = dist * Math.cos(bearing * Math.PI / 180) / latConv;
    var lng = dist * Math.sin(bearing * Math.PI / 180) / lngConv;
    return new google.maps.LatLng(point.lat() + lat, point.lng() + lng);
  },

  rad: function (x) {
    return x * Math.PI / 180;
  },

  distance: function (p1, p2) {
    var R = 6371; // earth's mean radius in km
    var dLat = this.rad(p2.lat() - p1.lat());
    var dLong = this.rad(p2.lng() - p1.lng());

    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.rad(p1.lat())) * Math.cos(this.rad(p2.lat())) * Math.sin(dLong / 2) * Math.sin(dLong / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;

    return d * 1000;
  }

}