/*! jQuery v2.2.3 | (c) jQuery Foundation | jquery.org/license */ var asl_jQuery = jQuery; /*! JsRender v0.9.75 (Beta): http://jsviews.com/#jsrender */ /*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */ !function(e,t){var n=t.jQuery;"object"==typeof exports?module.exports=n?e(t,n):function(n){if(n&&!n.fn)throw"Provide jQuery or null";return e(t,n)}:"function"==typeof define&&define.amd?define(function(){return e(t)}):e(t,!1)}(function(e,t){"use strict";function n(e,t){return function(){var n,r=this,i=r.base;return r.base=e,n=t.apply(r,arguments),r.base=i,n}}function r(e,t){return W(t)&&(t=n(e?e._d?e:n(o,e):o,t),t._d=1),t}function i(e,t){for(var n in t.props)Ce.test(n)&&(e[n]=r(e[n],t.props[n]))}function a(e){return e}function o(){return""}function s(e){try{throw console.log("JsRender dbg breakpoint: "+e),"dbg breakpoint"}catch(t){}return this.base?this.baseApply(arguments):e}function d(e){this.name=(t.link?"JsViews":"JsRender")+" Error",this.message=e||this.name}function l(e,t){var n;for(n in t)e[n]=t[n];return e}function u(e,t,n){return e?(ie.delimiters=[e,t,pe=n?n.charAt(0):pe],se=e.charAt(0),de=e.charAt(1),le=t.charAt(0),ue=t.charAt(1),e="\\"+se+"(\\"+pe+")?\\"+de,t="\\"+le+"\\"+ue,P="(?:(\\w+(?=[\\/\\s\\"+le+"]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"+le+"]|\\"+le+"(?!\\"+ue+"))*?)",re.rTag="(?:"+P+")",P=new RegExp("(?:"+e+P+"(\\/)?|\\"+se+"(\\"+pe+")?\\"+de+"(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))"+t,"g"),Z=new RegExp("<.*>|([^\\\\]|^)[{}]|"+e+".*"+t),oe):ie.delimiters}function p(e,t){t||e===!0||(t=e,e=void 0);var n,r,i,a,o=this,s=!t||"root"===t;if(e){if(a=t&&o.type===t&&o,!a)if(n=o.views,o._.useKey){for(r in n)if(a=t?n[r].get(e,t):n[r])break}else for(r=0,i=n.length;!a&&i>r;r++)a=t?n[r].get(e,t):n[r]}else if(s)for(;o.parent;)a=o,o=o.parent;else for(;o&&!a;)a=o.type===t?o:void 0,o=o.parent;return a}function c(){var e=this.get("item");return e?e.index:void 0}function f(){return this.index}function g(t){var n,r=this,i=r.linkCtx,a=(r.ctx||{})[t];return void 0===a&&i&&i.ctx&&(a=i.ctx[t]),void 0===a&&(a=te[t]),a&&W(a)&&!a._wrp&&(n=function(){return a.apply(this&&this!==e?this:r,arguments)},n._wrp=r,l(n,a)),n||a}function v(e){return e&&(e.fn?e:this.getRsc("templates",e)||Y(e))}function m(e,t,n,r){var a,o,s="number"==typeof n&&t.tmpl.bnds[n-1],d=t.linkCtx;return void 0!==r?n=r={props:{},args:[r]}:s&&(n=s(t.data,t,re)),o=n.args[0],(e||s)&&(a=d&&d.tag,a||(a=l(new re._tg,{_:{inline:!d,bnd:s,unlinked:!0},tagName:":",cvt:e,flow:!0,tagCtx:n}),d&&(d.tag=a,a.linkCtx=d),n.ctx=O(n.ctx,(d?d.view:t).ctx)),a._er=r&&o,i(a,n),n.view=t,a.ctx=n.ctx||{},n.ctx=void 0,o=a.cvtArgs(a.convert||"true"!==e&&e)[0],o=s&&t._.onRender?t._.onRender(o,t,a):o),void 0!=o?o:""}function h(e){var t=this,n=t.tagCtx,r=n.view,i=n.args;return e=t.convert||e,e=e&&(""+e===e?r.getRsc("converters",e)||N("Unknown converter: '"+e+"'"):e),i=i.length||n.index?e?i.slice():i:[r.data],e&&(e.depends&&(t.depends=re.getDeps(t.depends,t,e.depends,e)),i[0]=e.apply(t,i)),i}function w(e,t){for(var n,r,i=this;void 0===n&&i;)r=i.tmpl&&i.tmpl[e],n=r&&r[t],i=i.parent;return n||G[e][t]}function x(e,t,n,r,a,o){t=t||z;var s,d,l,u,p,c,f,g,v,m,h,w,x,b,_,y,k,j,C,T="",A=t.linkCtx||0,R=t.ctx,$=n||t.tmpl,M="number"==typeof r&&t.tmpl.bnds[r-1];for("tag"===e._is?(s=e,e=s.tagName,r=s.tagCtxs,l=s.template):(d=t.getRsc("tags",e)||N("Unknown tag: {{"+e+"}} "),l=d.template),void 0!==o?(T+=o,r=o=[{props:{},args:[]}]):M&&(r=M(t.data,t,re)),g=r.length,f=0;g>f;f++)m=r[f],(!A||!A.tag||f&&!A.tag._.inline||s._er)&&((w=$.tmpls&&m.tmpl)&&(w=m.content=$.tmpls[w-1]),m.index=f,m.tmpl=w,m.render=V,m.view=t,m.ctx=O(m.ctx,R)),(n=m.props.tmpl)&&(m.tmpl=t.getTmpl(n)),s||(s=new d._ctr,x=!!s.init,s.parent=c=R&&R.tag,s.tagCtxs=r,C=s.dataMap,A&&(s._.inline=!1,A.tag=s,s.linkCtx=A),(s._.bnd=M||A.fn)?s._.arrVws={}:s.dataBoundOnly&&N("{^{"+e+"}} tag must be data-bound")),r=s.tagCtxs,C=s.dataMap,m.tag=s,C&&r&&(m.map=r[f].map),s.flow||(h=m.ctx=m.ctx||{},u=s.parents=h.parentTags=R&&O(h.parentTags,R.parentTags)||{},c&&(u[c.tagName]=c),u[s.tagName]=h.tag=s);if(!(s._er=o)){for(i(s,r[0]),s.rendering={},f=0;g>f;f++)m=s.tagCtx=r[f],k=m.props,y=s.cvtArgs(),(b=k.dataMap||C)&&(y.length||k.dataMap)&&(_=m.map,(!_||_.src!==y[0]||a)&&(_&&_.src&&_.unmap(),_=m.map=b.map(y[0],k,void 0,!s._.bnd)),y=[_.tgt]),s.ctx=m.ctx,f||(x&&(j=s.template,s.init(m,A,s.ctx),x=void 0),A&&(A.attr=s.attr=A.attr||s.attr),p=s.attr,s._.noVws=p&&p!==Ve),v=void 0,s.render&&(v=s.render.apply(s,y)),y.length||(y=[t]),void 0===v&&(v=m.render(y[0],!0)||(a?void 0:"")),T=T?T+(v||""):v;s.rendering=void 0}return s.tagCtx=r[0],s.ctx=s.tagCtx.ctx,s._.noVws&&s._.inline&&(T="text"===p?ee.html(T):""),M&&t._.onRender?t._.onRender(T,t,s):T}function b(e,t,n,r,i,a,o,s){var d,l,u,p=this,f="array"===t;p.content=s,p.views=f?[]:{},p.parent=n,p.type=t||"top",p.data=r,p.tmpl=i,u=p._={key:0,useKey:f?0:1,id:""+Ae++,onRender:o,bnds:{}},p.linked=!!o,n?(d=n.views,l=n._,l.useKey?(d[u.key="_"+l.useKey++]=p,p.index=Ee,p.getIndex=c):d.length===(u.key=p.index=a)?d.push(p):d.splice(a,0,p),p.ctx=e||n.ctx):p.ctx=e}function _(e){var t,n,r,i,a,o,s;for(t in Ue)if(a=Ue[t],(o=a.compile)&&(n=e[t+"s"]))for(r in n)i=n[r]=o(r,n[r],e,0),i._is=t,i&&(s=re.onStore[t])&&s(r,i,o)}function y(e,t,n){function i(){var t=this;t._={inline:!0,unlinked:!0},t.tagName=e}var a,o,s,d=new re._tg;if(W(t)?t={depends:t.depends,render:t}:""+t===t&&(t={template:t}),o=t.baseTag){t.flow=!!t.flow,t.baseTag=o=""+o===o?n&&n.tags[o]||ne[o]:o,d=l(d,o);for(s in t)d[s]=r(o[s],t[s])}else d=l(d,t);return void 0!==(a=d.template)&&(d.template=""+a===a?Y[a]||Y(a):a),d.init!==!1&&((i.prototype=d).constructor=d._ctr=i),n&&(d._parentTmpl=n),d}function k(e){return this.base.apply(this,e)}function j(e,n,r,i){function a(n){var a,s;if(""+n===n||n.nodeType>0&&(o=n)){if(!o)if(/^\.\/[^\\:*?"<>]*$/.test(n))(s=Y[e=e||n])?n=s:o=document.getElementById(n);else if(t.fn&&!Z.test(n))try{o=t(document).find(n)[0]}catch(d){}o&&(i?n=o.innerHTML:(a=o.getAttribute(Me),a?a!==Ne?(n=Y[a],delete Y[a]):t.fn&&(n=t.data(o)[Ne]):(e=e||(t.fn?Ne:n),n=j(e,o.innerHTML,r,i)),n.tmplName=e=e||a,e!==Ne&&(Y[e]=n),o.setAttribute(Me,e),t.fn&&t.data(o,Ne,n))),o=void 0}else n.fn||(n=void 0);return n}var o,s,d=n=n||"";return 0===i&&(i=void 0,d=a(d)),i=i||(n.markup?n:{}),i.tmplName=e,r&&(i._parentTmpl=r),!d&&n.markup&&(d=a(n.markup))&&d.fn&&(d=d.markup),void 0!==d?(d.fn||n.fn?d.fn&&(s=d):(n=T(d,i),S(d.replace(xe,"\\$&"),n)),s||(_(i),s=l(function(){return n.render.apply(n,arguments)},n)),e&&!r&&e!==Ne&&(Se[e]=s),s):void 0}function C(e){function t(t,n){this.tgt=e.getTgt(t,n)}return W(e)&&(e={getTgt:e}),e.baseMap&&(e=l(l({},e.baseMap),e)),e.map=function(e,n){return new t(e,n)},e}function T(e,n){var r,i=ae._wm||{},a=l({tmpls:[],links:{},bnds:[],_is:"template",render:V},n);return a.markup=e,n.htmlTag||(r=ye.exec(e),a.htmlTag=r?r[1].toLowerCase():""),r=i[a.htmlTag],r&&r!==i.div&&(a.markup=t.trim(a.markup)),a}function A(e,t){function n(i,a,o){var s,d,l,u;if(i&&typeof i===$e&&!i.nodeType&&!i.markup&&!i.getTgt){for(l in i)n(l,i[l],a);return G}return void 0===a&&(a=i,i=void 0),i&&""+i!==i&&(o=a,a=i,i=void 0),u=o?o[r]=o[r]||{}:n,d=t.compile,null===a?i&&delete u[i]:(a=d?d.call(u,i,a,o,0):a,i&&(u[i]=a)),d&&a&&(a._is=e),a&&(s=re.onStore[e])&&s(i,a,d),a}var r=e+"s";G[r]=n}function R(e){oe[e]=function(t){return arguments.length?(ie[e]=t,oe):ie[e]}}function V(e,t,n,r,i,a){var o,s,d,l,u,p,c,f,g=r,v="";if(t===!0?(n=t,t=void 0):typeof t!==$e&&(t=void 0),(d=this.tag)?(u=this,g=g||u.view,l=g.getTmpl(d.template||u.tmpl),arguments.length||(e=g)):l=this,l){if(!g&&e&&"view"===e._is&&(g=e),g&&e===g&&(e=g.data),p=!g,fe=fe||p,g||((t=t||{}).root=e),!fe||ae.useViews||l.useViews||g&&g!==z)v=$(l,e,t,n,g,i,a,d);else{if(g?(c=g.data,f=g.index,g.index=Ee):(g=z,g.data=e,g.ctx=t),X(e)&&!n)for(o=0,s=e.length;s>o;o++)g.index=o,g.data=e[o],v+=l.fn(e[o],g,re);else g.data=e,v+=l.fn(e,g,re);g.data=c,g.index=f}p&&(fe=void 0)}return v}function $(e,t,n,r,i,a,o,s){function d(e){_=l({},n),_[x]=e}var u,p,c,f,g,v,m,h,w,x,_,y,k="";if(s&&(w=s.tagName,y=s.tagCtx,n=n?O(n,s.ctx):s.ctx,e===i.content?m=e!==i.ctx._wrp?i.ctx._wrp:void 0:e!==y.content?e===s.template?(m=y.tmpl,n._wrp=y.content):m=y.content||i.content:m=i.content,y.props.link===!1&&(n=n||{},n.link=!1),(x=y.props.itemVar)&&("~"!==x.charAt(0)&&E("Use itemVar='~myItem'"),x=x.slice(1))),i&&(o=o||i._.onRender,n=O(n,i.ctx)),a===!0&&(v=!0,a=0),o&&(n&&n.link===!1||s&&s._.noVws)&&(o=void 0),h=o,o===!0&&(h=void 0,o=i._.onRender),n=e.helpers?O(e.helpers,n):n,_=n,X(t)&&!r)for(c=v?i:void 0!==a&&i||new b(n,"array",i,t,e,a,o),i&&i._.useKey&&(c._.bnd=!s||s._.bnd&&s),x&&(c.it=x),x=c.it,u=0,p=t.length;p>u;u++)x&&d(t[u]),f=new b(_,"item",c,t[u],e,(a||0)+u,o,m),g=e.fn(t[u],f,re),k+=c._.onRender?c._.onRender(g,f):g;else x&&d(t),c=v?i:new b(_,w||"data",i,t,e,a,o,m),s&&!s.flow&&(c.tag=s),k+=e.fn(t,c,re);return h?h(k,c):k}function M(e,t,n){var r=void 0!==n?W(n)?n.call(t.data,e,t):n||"":"{Error: "+e.message+"}";return ie.onError&&void 0!==(n=ie.onError.call(t.data,e,n&&r,t))&&(r=n),t&&!t.linkCtx?ee.html(r):r}function N(e){throw new re.Err(e)}function E(e){N("Syntax error\n"+e)}function S(e,t,n,r,i){function a(t){t-=f,t&&v.push(e.substr(f,t).replace(he,"\\n"))}function o(t,n){t&&(t+="}}",E((n?"{{"+n+"}} block has {{/"+t+" without {{"+t:"Unmatched or missing {{/"+t)+", in template:\n"+e))}function s(s,d,c,h,w,x,b,_,y,k,j,C){(b&&d||y&&!c||_&&":"===_.slice(-1)||k)&&E(s),x&&(w=":",h=Ve),y=y||n&&!i;var T=(d||n)&&[[]],A="",R="",V="",$="",M="",N="",S="",F="",U=!y&&!w;c=c||(_=_||"#data",w),a(C),f=C+s.length,b?p&&v.push(["*","\n"+_.replace(/^:/,"ret+= ").replace(we,"$1")+";\n"]):c?("else"===c&&(_e.test(_)&&E('for "{{else if expr}}" use "{{else expr}}"'),T=m[7]&&[[]],m[8]=e.substring(m[8],C),m=g.pop(),v=m[2],U=!0),_&&J(_.replace(he," "),T,t).replace(be,function(e,t,n,r,i,a,o,s){return r="'"+i+"':",o?(R+=a+",",$+="'"+s+"',"):n?(V+=r+a+",",N+=r+"'"+s+"',"):t?S+=a:("trigger"===i&&(F+=a),A+=r+a+",",M+=r+"'"+s+"',",u=u||Ce.test(i)),""}).slice(0,-1),T&&T[0]&&T.pop(),l=[c,h||!!r||u||"",U&&[],I($||(":"===c?"'#data',":""),M,N),I(R||(":"===c?"data,":""),A,V),S,F,T||0],v.push(l),U&&(g.push(m),m=l,m[8]=f)):j&&(o(j!==m[0]&&"else"!==m[0]&&j,m[0]),m[8]=e.substring(m[8],C),m=g.pop()),o(!m&&j),v=m[2]}var d,l,u,p=ie.allowCode||t&&t.allowCode||oe.allowCode===!0,c=[],f=0,g=[],v=c,m=[,,c];return p&&(t.allowCode=p),n&&(void 0!==r&&(e=e.slice(0,-r.length-2)+ue),e=se+e+ue),o(g[0]&&g[0][2].pop()[0]),e.replace(P,s),a(e.length),(f=c[c.length-1])&&o(""+f!==f&&+f[8]===f[8]&&f[0]),n?(d=K(c,e,n),F(d,[c[0][7]])):d=K(c,t),d}function F(e,t){var n,r,i=0,a=t.length;for(e.deps=[];a>i;i++){r=t[i];for(n in r)"_jsvto"!==n&&r[n].length&&(e.deps=e.deps.concat(r[n]))}e.paths=r}function I(e,t,n){return[e.slice(0,-1),t.slice(0,-1),n.slice(0,-1)]}function U(e,t){return"\n "+(t?t+":{":"")+"args:["+e[0]+"]"+(e[1]||!t?",\n props:{"+e[1]+"}":"")+(e[2]?",\n ctx:{"+e[2]+"}":"")}function J(e,t,n){function r(r,h,w,x,b,_,y,k,j,C,T,A,R,V,$,M,N,F,I,U){function J(e,n,r,o,s,d,p,c){var f="."===r;if(r&&(b=b.slice(n.length),/^\.?constructor$/.test(c||b)&&E(e),f||(e=(o?'view.hlp("'+o+'")':s?"view":"data")+(c?(d?"."+d:o?"":s?"":"."+r)+(p||""):(c=o?"":s?d||"":r,"")),e+=c?"."+c:"",e=n+("view.data"===e.slice(0,9)?e.slice(5):e)),l)){if(q="linkTo"===i?a=t._jsvto=t._jsvto||[]:u.bd,B=f&&q[q.length-1]){if(B._jsv){for(;B.sb;)B=B.sb;B.bnd&&(b="^"+b.slice(1)),B.sb=b,B.bnd=B.bnd||"^"===b.charAt(0)}}else q.push(b);m[g]=I+(f?1:0)}return e}x=l&&x,x&&!k&&(b=x+b),_=_||"",w=w||h||A,b=b||j,C=C||N||"";var K,O,q,B,L;if(!y||d||s){if(l&&M&&!d&&!s&&(!i||o||a)&&(K=m[g-1],U.length-1>I-(K||0))){if(K=U.slice(K,I+r.length),O!==!0)if(q=a||p[g-1].bd,B=q[q.length-1],B&&B.prm){for(;B.sb&&B.sb.prm;)B=B.sb;L=B.sb={path:B.sb,bnd:B.bnd}}else q.push(L={path:q.pop()});M=de+":"+K+" onerror=''"+le,O=f[M],O||(f[M]=!0,f[M]=O=S(M,n,!0)),O!==!0&&L&&(L._jsv=O,L.prm=u.bd,L.bnd=L.bnd||L.path&&L.path.indexOf("^")>=0)}return d?(d=!R,d?r:A+'"'):s?(s=!V,s?r:A+'"'):(w?(m[g]=I++,u=p[++g]={bd:[]},w):"")+(F?g?"":(c=U.slice(c,I),(i?(i=o=a=!1,"\b"):"\b,")+c+(c=I+r.length,l&&t.push(u.bd=[]),"\b")):k?(g&&E(e),l&&t.pop(),i=b,o=x,c=I+r.length,x&&(l=u.bd=t[i]=[]),b+":"):b?b.split("^").join(".").replace(ve,J)+(C?(u=p[++g]={bd:[]},v[g]=!0,C):_):_?_:$?(v[g]=!1,u=p[--g],$+(C?(u=p[++g],v[g]=!0,C):"")):T?(v[g]||E(e),","):h?"":(d=R,s=V,'"'))}E(e)}var i,a,o,s,d,l=t&&t[0],u={bd:l},p={0:u},c=0,f=n?n.links:l&&(l.links=l.links||{}),g=0,v={},m={},h=(e+(n?" ":"")).replace(me,r);return!g&&h||E(e)}function K(e,t,n){var r,i,a,o,s,d,l,u,p,c,f,g,v,m,h,w,x,b,_,y,k,j,C,A,R,V,$,M,N,S,I=0,J=ae.useViews||t.useViews||t.tags||t.templates||t.helpers||t.converters,O="",q={},B=e.length;for(""+t===t?(b=n?'data-link="'+t.replace(he," ").slice(1,-1)+'"':t,t=0):(b=t.tmplName||"unnamed",t.allowCode&&(q.allowCode=!0),t.debug&&(q.debug=!0),f=t.bnds,x=t.tmpls),r=0;B>r;r++)if(i=e[r],""+i===i)O+='\n+"'+i+'"';else if(a=i[0],"*"===a)O+=";\n"+i[1]+"\nret=ret";else{if(o=i[1],k=!n&&i[2],s=U(i[3],"params")+"},"+U(v=i[4]),M=i[5],S=i[6],j=i[8]&&i[8].replace(we,"$1"),(R="else"===a)?g&&g.push(i[7]):(I=0,f&&(g=i[7])&&(g=[g],I=f.push(1))),J=J||v[1]||v[2]||g||/view.(?!index)/.test(v[0]),(V=":"===a)?(o&&(a=o===Ve?">":o+a),S=i[6]||ie.trigger):(k&&(_=T(j,q),_.tmplName=b+"/"+a,_.useViews=_.useViews||J,K(k,_),J=_.useViews,x.push(_)),R||(y=a,J=J||a&&(!ne[a]||!ne[a].flow),A=O,O=""),C=e[r+1],C=C&&"else"===C[0]),N=M?";\ntry{\nret+=":"\n+",m="",h="",V&&(g||S||o&&o!==Ve)){if($="return {"+s+"};",w='c("'+o+'",view,',$=new Function("data,view,j,u"," // "+b+" "+I+" "+a+"\n"+$),$._er=M,m=w+I+",",h=")",$._tag=a,n)return $;F($,g),c=!0}if(O+=V?(n?(M?"\ntry{\n":"")+"return ":N)+(c?(c=void 0,J=p=!0,w+(g?(f[I-1]=$,I):"{"+s+"}")+")"):">"===a?(l=!0,"h("+v[0]+")"):(u=!0,"((v="+v[0]+')!=null?v:"")')):(d=!0,"\n{view:view,tmpl:"+(k?x.length:"0")+","+s+"},"),y&&!C){if(O="["+O.slice(0,-1)+"]",w='t("'+y+'",view,this,',n||g){if(O=new Function("data,view,j,u"," // "+b+" "+I+" "+y+"\nreturn "+O+";"),O._er=M,O._tag=y,g&&F(f[I-1]=O,g),n)return O;m=w+I+",undefined,",h=")"}O=A+N+w+(I||O)+")",g=0,y=0}M&&(J=!0,O+=";\n}catch(e){ret"+(n?"urn ":"+=")+m+"j._err(e,view,"+M+")"+h+";}\n"+(n?"":"ret=ret"))}O="// "+b+"\nvar v"+(d?",t=j._tag":"")+(p?",c=j._cnvt":"")+(l?",h=j._html":"")+(n?";\n":',ret=""\n')+(q.debug?"debugger;":"")+O+(n?"\n":";\nreturn ret;"),ie.debugMode!==!1&&(O="try {\n"+O+"\n}catch(e){\nreturn j._err(e, view);\n}");try{O=new Function("data,view,j,u",O)}catch(L){E("Compiled template code:\n\n"+O+'\n: "'+L.message+'"')}return t&&(t.fn=O,t.useViews=!!J),O}function O(e,t){return e&&e!==t?t?l(l({},t),e):e:t&&l({},t)}function q(e){return Re[e]||(Re[e]="&#"+e.charCodeAt(0)+";")}function B(e){var t,n,r=[];if(typeof e===$e)for(t in e)n=e[t],n&&n.toJSON&&!n.toJSON()||W(n)||r.push({key:t,prop:n});return r}function L(e,n,r){var i=this.jquery&&(this[0]||N('Unknown template: "'+this.selector+'"')),a=i.getAttribute(Me);return V.call(a?t.data(i)[Ne]:Y(i),e,n,r)}function Q(e){return void 0!=e?je.test(e)&&(""+e).replace(Te,q)||e:""}var H=t===!1;t=t&&t.fn?t:e.jQuery;var D,P,Z,z,G,W,X,Y,ee,te,ne,re,ie,ae,oe,se,de,le,ue,pe,ce,fe,ge="v0.9.75",ve=/^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,me=/(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,he=/[ \t]*(\r\n|\n|\r)/g,we=/\\(['"])/g,xe=/['"\\]/g,be=/(?:\x08|^)(onerror:)?(?:(~?)(([\w$_\.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,_e=/^if\s/,ye=/<(\w+)[>\s]/,ke=/[\x00`><"'&]/g,je=/[\x00`><\"'&]/,Ce=/^on[A-Z]|^convert(Back)?$/,Te=ke,Ae=0,Re={"&":"&","<":"<",">":">","\x00":"�","'":"'",'"':""","`":"`"},Ve="html",$e="object",Me="data-jsv-tmpl",Ne="jsvTmpl",Ee="For #index in nested block use #getIndex().",Se={},Fe=e.jsrender,Ie=Fe&&t&&!t.render,Ue={template:{compile:j},tag:{compile:y},helper:{},converter:{}};if(G={jsviews:ge,sub:{View:b,Err:d,tmplFn:S,parse:J,extend:l,extendCtx:O,syntaxErr:E,onStore:{},addSetting:R,settings:{allowCode:!1},advSet:o,_ths:i,_tg:function(){},_cnvt:m,_tag:x,_er:N,_err:M,_html:Q},settings:{delimiters:u,advanced:function(e){return e?(l(ae,e),re.advSet(),oe):ae}},map:C},(d.prototype=new Error).constructor=d,c.depends=function(){return[this.get("item"),"index"]},f.depends="index",b.prototype={get:p,getIndex:f,getRsc:w,getTmpl:v,hlp:g,_is:"view"},!(Fe||t&&t.render)){for(D in Ue)A(D,Ue[D]);Y=G.templates,ee=G.converters,te=G.helpers,ne=G.tags,re=G.sub,oe=G.settings,re._tg.prototype={baseApply:k,cvtArgs:h},z=re.topView=new b,t?(t.fn.render=L,t.observable&&(l(re,t.views.sub),G.map=t.views.map)):(t={},H&&(e.jsrender=t),t.renderFile=t.__express=t.compile=function(){throw"Node.js: use npm jsrender, or jsrender-node.js"},t.isFunction=function(e){return"function"==typeof e},t.isArray=Array.isArray||function(e){return"[object Array]"==={}.toString.call(e)},re._jq=function(e){e!==t&&(l(e,t),t=e,t.fn.render=L,delete t.jsrender)},t.jsrender=ge),ie=re.settings,ie.allowCode=!1,W=t.isFunction,X=t.isArray,t.render=Se,t.views=G,t.templates=Y=G.templates;for(ce in ie)R(ce);(oe.debugMode=function(e){return void 0===e?ie.debugMode:(ie.debugMode=e,ie.onError=e+""===e?new Function("","return '"+e+"';"):W(e)?e:void 0,oe)})(!1),ae=ie.advanced={useViews:!1,_jsv:!1},ne({"if":{render:function(e){var t=this,n=t.tagCtx,r=t.rendering.done||!e&&(arguments.length||!n.index)?"":(t.rendering.done=!0,t.selected=n.index,n.render(n.view,!0));return r},flow:!0},"for":{render:function(e){var t,n=!arguments.length,r=this,i=r.tagCtx,a="",o=0;return r.rendering.done||(t=n?i.view.data:e,void 0!==t&&(a+=i.render(t,n),o+=X(t)?t.length:1),(r.rendering.done=o)&&(r.selected=i.index)),a},flow:!0},props:{baseTag:"for",dataMap:C(B),flow:!0},include:{flow:!0},"*":{render:a,flow:!0},":*":{render:a,flow:!0},dbg:te.dbg=ee.dbg=s}),ee({html:Q,attr:Q,url:function(e){return void 0!=e?encodeURI(""+e):null===e?e:""}}),oe.delimiters("{{","}}","^")}return Ie&&Fe.views.sub._jq(t),t||Fe},window); //# sourceMappingURL=jsrender.min.js.map /*! Underscore.js 1.6.0 | http://underscorejs.org | (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors | Underscore may be freely distributed under the MIT license. */ (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this); var asl_underscore = _.noConflict(); // ==ClosureCompiler== // @compilation_level ADVANCED_OPTIMIZATIONS // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3_3.js // ==/ClosureCompiler== /** * @name MarkerClusterer for Google Maps v3 * @version version 1.0 * @author Luke Mahe * @fileoverview * The library creates and manages per-zoom-level clusters for large amounts of * markers. *
* This is a v3 implementation of the * v2 MarkerClusterer. */ /** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A Marker Clusterer that clusters markers. * * @param {google.maps.Map} map The Google map to attach to. * @param {Array.=} opt_markers Optional markers to add to * the cluster. * @param {Object=} opt_options support the following options: * 'gridSize': (number) The grid size of a cluster in pixels. * 'maxZoom': (number) The maximum zoom level that a marker can be part of a * cluster. * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a * cluster is to zoom into it. * 'averageCenter': (boolean) Wether the center of each cluster should be * the average of all markers in the cluster. * 'minimumClusterSize': (number) The minimum number of markers to be in a * cluster before the markers are hidden and a count * is shown. * 'styles': (object) An object that has style properties: * 'url': (string) The image url. * 'height': (number) The image height. * 'width': (number) The image width. * 'anchor': (Array) The anchor position of the label text. * 'textColor': (string) The text color. * 'textSize': (number) The text size. * 'backgroundPosition': (string) The position of the backgound x, y. * 'iconAnchor': (Array) The anchor position of the icon x, y. * @constructor * @extends google.maps.OverlayView */ function MarkerClusterer(map, opt_markers, opt_options) { // MarkerClusterer implements google.maps.OverlayView interface. We use the // extend function to extend MarkerClusterer with google.maps.OverlayView // because it might not always be available when the code is defined so we // look for it at the last possible moment. If it doesn't exist now then // there is no point going ahead :) this.extend(MarkerClusterer, google.maps.OverlayView); this.map_ = map; /** * @type {Array.} * @private */ this.markers_ = []; /** * @type {Array.} */ this.clusters_ = []; this.sizes = [53, 56, 66, 78, 90]; /** * @private */ this.styles_ = []; /** * @type {boolean} * @private */ this.ready_ = false; var options = opt_options || {}; /** * @type {number} * @private */ this.gridSize_ = options['gridSize'] || 60; /** * @private */ this.minClusterSize_ = options['minimumClusterSize'] || 2; /** * @type {?number} * @private */ this.maxZoom_ = options['maxZoom'] || null; this.styles_ = options['styles'] || []; /** * @type {string} * @private */ this.imagePath_ = options['imagePath'] || this.MARKER_CLUSTER_IMAGE_PATH_; /** * @type {string} * @private */ this.imageExtension_ = options['imageExtension'] || this.MARKER_CLUSTER_IMAGE_EXTENSION_; /** * @type {boolean} * @private */ this.zoomOnClick_ = true; if (options['zoomOnClick'] != undefined) { this.zoomOnClick_ = options['zoomOnClick']; } /** * @type {boolean} * @private */ this.averageCenter_ = false; if (options['averageCenter'] != undefined) { this.averageCenter_ = options['averageCenter']; } this.setupStyles_(); this.setMap(map); /** * @type {number} * @private */ this.prevZoom_ = this.map_.getZoom(); // Add the map event listeners var that = this; google.maps.event.addListener(this.map_, 'zoom_changed', function() { var zoom = that.map_.getZoom(); if (that.prevZoom_ != zoom) { that.prevZoom_ = zoom; that.resetViewport(); } }); google.maps.event.addListener(this.map_, 'idle', function() { that.redraw(); }); // Finally, add the markers if (opt_markers && opt_markers.length) { this.addMarkers(opt_markers, false); } } /** * The marker cluster image path. * * @type {string} * @private */ MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = '../images/m'; /** * The marker cluster image path. * * @type {string} * @private */ MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; /** * Extends a objects prototype by anothers. * * @param {Object} obj1 The object to be extended. * @param {Object} obj2 The object to extend with. * @return {Object} The new extended object. * @ignore */ MarkerClusterer.prototype.extend = function(obj1, obj2) { return (function(object) { for (var property in object.prototype) { this.prototype[property] = object.prototype[property]; } return this; }).apply(obj1, [obj2]); }; /** * Implementaion of the interface method. * @ignore */ MarkerClusterer.prototype.onAdd = function() { this.setReady_(true); }; /** * Implementaion of the interface method. * @ignore */ MarkerClusterer.prototype.draw = function() {}; /** * Sets up the styles object. * * @private */ MarkerClusterer.prototype.setupStyles_ = function() { if (this.styles_.length) { return; } for (var i = 0, size; size = this.sizes[i]; i++) { this.styles_.push({ url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, height: size, width: size }); } }; /** * Fit the map to the bounds of the markers in the clusterer. */ MarkerClusterer.prototype.fitMapToMarkers = function() { var markers = this.getMarkers(); var bounds = new google.maps.LatLngBounds(); for (var i = 0, marker; marker = markers[i]; i++) { bounds.extend(marker.getPosition()); } this.map_.fitBounds(bounds); }; /** * Sets the styles. * * @param {Object} styles The style to set. */ MarkerClusterer.prototype.setStyles = function(styles) { this.styles_ = styles; }; /** * Gets the styles. * * @return {Object} The styles object. */ MarkerClusterer.prototype.getStyles = function() { return this.styles_; }; /** * Whether zoom on click is set. * * @return {boolean} True if zoomOnClick_ is set. */ MarkerClusterer.prototype.isZoomOnClick = function() { return this.zoomOnClick_; }; /** * Whether average center is set. * * @return {boolean} True if averageCenter_ is set. */ MarkerClusterer.prototype.isAverageCenter = function() { return this.averageCenter_; }; /** * Returns the array of markers in the clusterer. * * @return {Array.} The markers. */ MarkerClusterer.prototype.getMarkers = function() { return this.markers_; }; /** * Returns the number of markers in the clusterer * * @return {Number} The number of markers. */ MarkerClusterer.prototype.getTotalMarkers = function() { return this.markers_.length; }; /** * Sets the max zoom for the clusterer. * * @param {number} maxZoom The max zoom level. */ MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { this.maxZoom_ = maxZoom; }; /** * Gets the max zoom for the clusterer. * * @return {number} The max zoom level. */ MarkerClusterer.prototype.getMaxZoom = function() { return this.maxZoom_; }; /** * The function for calculating the cluster icon image. * * @param {Array.} markers The markers in the clusterer. * @param {number} numStyles The number of styles available. * @return {Object} A object properties: 'text' (string) and 'index' (number). * @private */ MarkerClusterer.prototype.calculator_ = function(markers, numStyles) { var index = 0; var count = markers.length; var dv = count; while (dv !== 0) { dv = parseInt(dv / 10, 10); index++; } index = Math.min(index, numStyles); return { text: count, index: index }; }; /** * Set the calculator function. * * @param {function(Array, number)} calculator The function to set as the * calculator. The function should return a object properties: * 'text' (string) and 'index' (number). * */ MarkerClusterer.prototype.setCalculator = function(calculator) { this.calculator_ = calculator; }; /** * Get the calculator function. * * @return {function(Array, number)} the calculator function. */ MarkerClusterer.prototype.getCalculator = function() { return this.calculator_; }; /** * Add an array of markers to the clusterer. * * @param {Array.} markers The markers to add. * @param {boolean=} opt_nodraw Whether to redraw the clusters. */ MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { for (var i = 0, marker; marker = markers[i]; i++) { this.pushMarkerTo_(marker); } if (!opt_nodraw) { this.redraw(); } }; /** * Pushes a marker to the clusterer. * * @param {google.maps.Marker} marker The marker to add. * @private */ MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { marker.isAdded = false; if (marker['draggable']) { // If the marker is draggable add a listener so we update the clusters on // the drag end. var that = this; google.maps.event.addListener(marker, 'dragend', function() { marker.isAdded = false; that.repaint(); }); } this.markers_.push(marker); }; /** * Adds a marker to the clusterer and redraws if needed. * * @param {google.maps.Marker} marker The marker to add. * @param {boolean=} opt_nodraw Whether to redraw the clusters. */ MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { this.pushMarkerTo_(marker); if (!opt_nodraw) { this.redraw(); } }; /** * Removes a marker and returns true if removed, false if not * * @param {google.maps.Marker} marker The marker to remove * @return {boolean} Whether the marker was removed or not * @private */ MarkerClusterer.prototype.removeMarker_ = function(marker) { var index = -1; if (this.markers_.indexOf) { index = this.markers_.indexOf(marker); } else { for (var i = 0, m; m = this.markers_[i]; i++) { if (m == marker) { index = i; break; } } } if (index == -1) { // Marker is not in our list of markers. return false; } marker.setMap(null); this.markers_.splice(index, 1); return true; }; /** * Remove a marker from the cluster. * * @param {google.maps.Marker} marker The marker to remove. * @param {boolean=} opt_nodraw Optional boolean to force no redraw. * @return {boolean} True if the marker was removed. */ MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) { var removed = this.removeMarker_(marker); if (!opt_nodraw && removed) { this.resetViewport(); this.redraw(); return true; } else { return false; } }; /** * Removes an array of markers from the cluster. * * @param {Array.} markers The markers to remove. * @param {boolean=} opt_nodraw Optional boolean to force no redraw. */ MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) { var removed = false; for (var i = 0, marker; marker = markers[i]; i++) { var r = this.removeMarker_(marker); removed = removed || r; } if (!opt_nodraw && removed) { this.resetViewport(); this.redraw(); return true; } }; /** * Sets the clusterer's ready state. * * @param {boolean} ready The state. * @private */ MarkerClusterer.prototype.setReady_ = function(ready) { if (!this.ready_) { this.ready_ = ready; this.createClusters_(); } }; /** * Returns the number of clusters in the clusterer. * * @return {number} The number of clusters. */ MarkerClusterer.prototype.getTotalClusters = function() { return this.clusters_.length; }; /** * Returns the google map that the clusterer is associated with. * * @return {google.maps.Map} The map. */ MarkerClusterer.prototype.getMap = function() { return this.map_; }; /** * Sets the google map that the clusterer is associated with. * * @param {google.maps.Map} map The map. */ MarkerClusterer.prototype.setMap = function(map) { this.map_ = map; }; /** * Returns the size of the grid. * * @return {number} The grid size. */ MarkerClusterer.prototype.getGridSize = function() { return this.gridSize_; }; /** * Sets the size of the grid. * * @param {number} size The grid size. */ MarkerClusterer.prototype.setGridSize = function(size) { this.gridSize_ = size; }; /** * Returns the min cluster size. * * @return {number} The grid size. */ MarkerClusterer.prototype.getMinClusterSize = function() { return this.minClusterSize_; }; /** * Sets the min cluster size. * * @param {number} size The grid size. */ MarkerClusterer.prototype.setMinClusterSize = function(size) { this.minClusterSize_ = size; }; /** * Extends a bounds object by the grid size. * * @param {google.maps.LatLngBounds} bounds The bounds to extend. * @return {google.maps.LatLngBounds} The extended bounds. */ MarkerClusterer.prototype.getExtendedBounds = function(bounds) { var projection = this.getProjection(); // Turn the bounds into latlng. var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getNorthEast().lng()); var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getSouthWest().lng()); // Convert the points to pixels and the extend out by the grid size. var trPix = projection.fromLatLngToDivPixel(tr); trPix.x += this.gridSize_; trPix.y -= this.gridSize_; var blPix = projection.fromLatLngToDivPixel(bl); blPix.x -= this.gridSize_; blPix.y += this.gridSize_; // Convert the pixel points back to LatLng var ne = projection.fromDivPixelToLatLng(trPix); var sw = projection.fromDivPixelToLatLng(blPix); // Extend the bounds to contain the new bounds. bounds.extend(ne); bounds.extend(sw); return bounds; }; /** * Determins if a marker is contained in a bounds. * * @param {google.maps.Marker} marker The marker to check. * @param {google.maps.LatLngBounds} bounds The bounds to check against. * @return {boolean} True if the marker is in the bounds. * @private */ MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { return bounds.contains(marker.getPosition()); }; /** * Clears all clusters and markers from the clusterer. */ MarkerClusterer.prototype.clearMarkers = function() { this.resetViewport(true); // Set the markers a empty array. this.markers_ = []; }; /** * Clears all existing clusters and recreates them. * @param {boolean} opt_hide To also hide the marker. */ MarkerClusterer.prototype.resetViewport = function(opt_hide) { // Remove all the clusters for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { cluster.remove(); } // Reset the markers to not be added and to be invisible. for (var i = 0, marker; marker = this.markers_[i]; i++) { marker.isAdded = false; if (opt_hide) { marker.setMap(null); } } this.clusters_ = []; }; /** * */ MarkerClusterer.prototype.repaint = function() { var oldClusters = this.clusters_.slice(); this.clusters_.length = 0; this.resetViewport(); this.redraw(); // Remove the old clusters. // Do it in a timeout so the other clusters have been drawn first. window.setTimeout(function() { for (var i = 0, cluster; cluster = oldClusters[i]; i++) { cluster.remove(); } }, 0); }; /** * Redraws the clusters. */ MarkerClusterer.prototype.redraw = function() { this.createClusters_(); }; /** * Calculates the distance between two latlng locations in km. * @see http://www.movable-type.co.uk/scripts/latlong.html * * @param {google.maps.LatLng} p1 The first lat lng point. * @param {google.maps.LatLng} p2 The second lat lng point. * @return {number} The distance between the two points in km. * @private */ MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { if (!p1 || !p2) { return 0; } var R = 6371; // Radius of the Earth in km var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); var d = R * c; return d; }; /** * Add a marker to a cluster, or creates a new cluster. * * @param {google.maps.Marker} marker The marker to add. * @private */ MarkerClusterer.prototype.addToClosestCluster_ = function(marker) { var distance = 40000; // Some large number var clusterToAddTo = null; var pos = marker.getPosition(); for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { var center = cluster.getCenter(); if (center) { var d = this.distanceBetweenPoints_(center, marker.getPosition()); if (d < distance) { distance = d; clusterToAddTo = cluster; } } } if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { clusterToAddTo.addMarker(marker); } else { var cluster = new Cluster(this); cluster.addMarker(marker); this.clusters_.push(cluster); } }; /** * Creates the clusters. * * @private */ MarkerClusterer.prototype.createClusters_ = function() { if (!this.ready_) { return; } // Get our current map view bounds. // Create a new bounds object so we don't affect the map. var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), this.map_.getBounds().getNorthEast()); var bounds = this.getExtendedBounds(mapBounds); for (var i = 0, marker; marker = this.markers_[i]; i++) { if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { this.addToClosestCluster_(marker); } } }; /** * A cluster that contains markers. * * @param {MarkerClusterer} markerClusterer The markerclusterer that this * cluster is associated with. * @constructor * @ignore */ function Cluster(markerClusterer) { this.markerClusterer_ = markerClusterer; this.map_ = markerClusterer.getMap(); this.gridSize_ = markerClusterer.getGridSize(); this.minClusterSize_ = markerClusterer.getMinClusterSize(); this.averageCenter_ = markerClusterer.isAverageCenter(); this.center_ = null; this.markers_ = []; this.bounds_ = null; this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), markerClusterer.getGridSize()); } /** * Determins if a marker is already added to the cluster. * * @param {google.maps.Marker} marker The marker to check. * @return {boolean} True if the marker is already added. */ Cluster.prototype.isMarkerAlreadyAdded = function(marker) { if (this.markers_.indexOf) { return this.markers_.indexOf(marker) != -1; } else { for (var i = 0, m; m = this.markers_[i]; i++) { if (m == marker) { return true; } } } return false; }; /** * Add a marker the cluster. * * @param {google.maps.Marker} marker The marker to add. * @return {boolean} True if the marker was added. */ Cluster.prototype.addMarker = function(marker) { if (this.isMarkerAlreadyAdded(marker)) { return false; } if (!this.center_) { this.center_ = marker.getPosition(); this.calculateBounds_(); } else { if (this.averageCenter_) { var l = this.markers_.length + 1; var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l; var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l; this.center_ = new google.maps.LatLng(lat, lng); this.calculateBounds_(); } } marker.isAdded = true; this.markers_.push(marker); var len = this.markers_.length; if (len < this.minClusterSize_ && marker.getMap() != this.map_) { // Min cluster size not reached so show the marker. marker.setMap(this.map_); } if (len == this.minClusterSize_) { // Hide the markers that were showing. for (var i = 0; i < len; i++) { this.markers_[i].setMap(null); } } if (len >= this.minClusterSize_) { marker.setMap(null); } this.updateIcon(); return true; }; /** * Returns the marker clusterer that the cluster is associated with. * * @return {MarkerClusterer} The associated marker clusterer. */ Cluster.prototype.getMarkerClusterer = function() { return this.markerClusterer_; }; /** * Returns the bounds of the cluster. * * @return {google.maps.LatLngBounds} the cluster bounds. */ Cluster.prototype.getBounds = function() { var bounds = new google.maps.LatLngBounds(this.center_, this.center_); var markers = this.getMarkers(); for (var i = 0, marker; marker = markers[i]; i++) { bounds.extend(marker.getPosition()); } return bounds; }; /** * Removes the cluster */ Cluster.prototype.remove = function() { this.clusterIcon_.remove(); this.markers_.length = 0; delete this.markers_; }; /** * Returns the center of the cluster. * * @return {number} The cluster center. */ Cluster.prototype.getSize = function() { return this.markers_.length; }; /** * Returns the center of the cluster. * * @return {Array.} The cluster center. */ Cluster.prototype.getMarkers = function() { return this.markers_; }; /** * Returns the center of the cluster. * * @return {google.maps.LatLng} The cluster center. */ Cluster.prototype.getCenter = function() { return this.center_; }; /** * Calculated the extended bounds of the cluster with the grid. * * @private */ Cluster.prototype.calculateBounds_ = function() { var bounds = new google.maps.LatLngBounds(this.center_, this.center_); this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); }; /** * Determines if a marker lies in the clusters bounds. * * @param {google.maps.Marker} marker The marker to check. * @return {boolean} True if the marker lies in the bounds. */ Cluster.prototype.isMarkerInClusterBounds = function(marker) { return this.bounds_.contains(marker.getPosition()); }; /** * Returns the map that the cluster is associated with. * * @return {google.maps.Map} The map. */ Cluster.prototype.getMap = function() { return this.map_; }; /** * Updates the cluster icon */ Cluster.prototype.updateIcon = function() { var zoom = this.map_.getZoom(); var mz = this.markerClusterer_.getMaxZoom(); if (mz && zoom > mz) { // The zoom is greater than our max zoom so show all the markers in cluster. for (var i = 0, marker; marker = this.markers_[i]; i++) { marker.setMap(this.map_); } return; } if (this.markers_.length < this.minClusterSize_) { // Min cluster size not yet reached. this.clusterIcon_.hide(); return; } var numStyles = this.markerClusterer_.getStyles().length; var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); this.clusterIcon_.setCenter(this.center_); this.clusterIcon_.setSums(sums); this.clusterIcon_.show(); }; /** * A cluster icon * * @param {Cluster} cluster The cluster to be associated with. * @param {Object} styles An object that has style properties: * 'url': (string) The image url. * 'height': (number) The image height. * 'width': (number) The image width. * 'anchor': (Array) The anchor position of the label text. * 'textColor': (string) The text color. * 'textSize': (number) The text size. * 'backgroundPosition: (string) The background postition x, y. * @param {number=} opt_padding Optional padding to apply to the cluster icon. * @constructor * @extends google.maps.OverlayView * @ignore */ function ClusterIcon(cluster, styles, opt_padding) { cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); this.styles_ = styles; this.padding_ = opt_padding || 0; this.cluster_ = cluster; this.center_ = null; this.map_ = cluster.getMap(); this.div_ = null; this.sums_ = null; this.visible_ = false; this.setMap(this.map_); } /** * Triggers the clusterclick event and zoom's if the option is set. * * @param {google.maps.MouseEvent} event The event to propagate */ ClusterIcon.prototype.triggerClusterClick = function(event) { var markerClusterer = this.cluster_.getMarkerClusterer(); // Trigger the clusterclick event. google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_, event); if (markerClusterer.isZoomOnClick()) { // Zoom into the cluster. this.map_.fitBounds(this.cluster_.getBounds()); } }; /** * Adding the cluster icon to the dom. * @ignore */ ClusterIcon.prototype.onAdd = function() { this.div_ = document.createElement('DIV'); if (this.visible_) { var pos = this.getPosFromLatLng_(this.center_); this.div_.style.cssText = this.createCss(pos); this.div_.innerHTML = this.sums_.text; } var panes = this.getPanes(); panes.overlayMouseTarget.appendChild(this.div_); var that = this; google.maps.event.addDomListener(this.div_, 'click', function(event) { that.triggerClusterClick(event); }); }; /** * Returns the position to place the div dending on the latlng. * * @param {google.maps.LatLng} latlng The position in latlng. * @return {google.maps.Point} The position in pixels. * @private */ ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { var pos = this.getProjection().fromLatLngToDivPixel(latlng); if (typeof this.iconAnchor_ === 'object' && this.iconAnchor_.length === 2) { pos.x -= this.iconAnchor_[0]; pos.y -= this.iconAnchor_[1]; } else { pos.x -= parseInt(this.width_ / 2, 10); pos.y -= parseInt(this.height_ / 2, 10); } return pos; }; /** * Draw the icon. * @ignore */ ClusterIcon.prototype.draw = function() { if (this.visible_) { var pos = this.getPosFromLatLng_(this.center_); this.div_.style.top = pos.y + 'px'; this.div_.style.left = pos.x + 'px'; } }; /** * Hide the icon. */ ClusterIcon.prototype.hide = function() { if (this.div_) { this.div_.style.display = 'none'; } this.visible_ = false; }; /** * Position and show the icon. */ ClusterIcon.prototype.show = function() { if (this.div_) { var pos = this.getPosFromLatLng_(this.center_); this.div_.style.cssText = this.createCss(pos); this.div_.style.display = ''; } this.visible_ = true; }; /** * Remove the icon from the map */ ClusterIcon.prototype.remove = function() { this.setMap(null); }; /** * Implementation of the onRemove interface. * @ignore */ ClusterIcon.prototype.onRemove = function() { if (this.div_ && this.div_.parentNode) { this.hide(); this.div_.parentNode.removeChild(this.div_); this.div_ = null; } }; /** * Set the sums of the icon. * * @param {Object} sums The sums containing: * 'text': (string) The text to display in the icon. * 'index': (number) The style index of the icon. */ ClusterIcon.prototype.setSums = function(sums) { this.sums_ = sums; this.text_ = sums.text; this.index_ = sums.index; if (this.div_) { this.div_.innerHTML = sums.text; } this.useStyle(); }; /** * Sets the icon to the the styles. */ ClusterIcon.prototype.useStyle = function() { var index = Math.max(0, this.sums_.index - 1); index = Math.min(this.styles_.length - 1, index); var style = this.styles_[index]; this.url_ = style['url']; this.height_ = style['height']; this.width_ = style['width']; this.textColor_ = style['textColor']; this.anchor_ = style['anchor']; this.textSize_ = style['textSize']; this.backgroundPosition_ = style['backgroundPosition']; this.iconAnchor_ = style['iconAnchor']; }; /** * Sets the center of the icon. * * @param {google.maps.LatLng} center The latlng to set as the center. */ ClusterIcon.prototype.setCenter = function(center) { this.center_ = center; }; /** * Create the css text based on the position of the icon. * * @param {google.maps.Point} pos The position. * @return {string} The css style text. */ ClusterIcon.prototype.createCss = function(pos) { var style = []; style.push('background-image:url(' + this.url_ + ');'); var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; style.push('background-position:' + backgroundPosition + ';'); if (typeof this.anchor_ === 'object') { if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) { style.push('height:' + (this.height_ - this.anchor_[0]) + 'px; padding-top:' + this.anchor_[0] + 'px;'); } else if (typeof this.anchor_[0] === 'number' && this.anchor_[0] < 0 && -this.anchor_[0] < this.height_) { style.push('height:' + this.height_ + 'px; line-height:' + (this.height_ + this.anchor_[0]) + 'px;'); } else { style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 'px;'); } if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) { style.push('width:' + (this.width_ - this.anchor_[1]) + 'px; padding-left:' + this.anchor_[1] + 'px;'); } else { style.push('width:' + this.width_ + 'px; text-align:center;'); } } else { style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); } var txtColor = this.textColor_ ? this.textColor_ : 'black'; var txtSize = this.textSize_ ? this.textSize_ : 11; style.push('cursor:pointer; top:' + pos.y + 'px; left:' + pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); return style.join(''); }; // Export Symbols for Closure // If you are not going to compile with closure then you can remove the // code below. window['MarkerClusterer'] = MarkerClusterer; MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; MarkerClusterer.prototype['clearMarkers'] = MarkerClusterer.prototype.clearMarkers; MarkerClusterer.prototype['fitMapToMarkers'] = MarkerClusterer.prototype.fitMapToMarkers; MarkerClusterer.prototype['getCalculator'] = MarkerClusterer.prototype.getCalculator; MarkerClusterer.prototype['getGridSize'] = MarkerClusterer.prototype.getGridSize; MarkerClusterer.prototype['getExtendedBounds'] = MarkerClusterer.prototype.getExtendedBounds; MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; MarkerClusterer.prototype['getTotalClusters'] = MarkerClusterer.prototype.getTotalClusters; MarkerClusterer.prototype['getTotalMarkers'] = MarkerClusterer.prototype.getTotalMarkers; MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; MarkerClusterer.prototype['removeMarker'] = MarkerClusterer.prototype.removeMarker; MarkerClusterer.prototype['removeMarkers'] = MarkerClusterer.prototype.removeMarkers; MarkerClusterer.prototype['resetViewport'] = MarkerClusterer.prototype.resetViewport; MarkerClusterer.prototype['repaint'] = MarkerClusterer.prototype.repaint; MarkerClusterer.prototype['setCalculator'] = MarkerClusterer.prototype.setCalculator; MarkerClusterer.prototype['setGridSize'] = MarkerClusterer.prototype.setGridSize; MarkerClusterer.prototype['setMaxZoom'] = MarkerClusterer.prototype.setMaxZoom; MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; Cluster.prototype['getSize'] = Cluster.prototype.getSize; Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; /** * Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect) * * Apache License, Version 2.0: * Copyright (c) 2012 - 2015 David Stutz * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * BSD 3-Clause License: * Copyright (c) 2012 - 2015 David Stutz * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of David Stutz nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ !function ($) { "use strict";// jshint ;_; if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { ko.bindingHandlers.multiselect = { after: ['options', 'value', 'selectedOptions', 'enable', 'disable'], init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { var $element = $(element); var config = ko.toJS(valueAccessor()); $element.multiselect(config); if (allBindings.has('options')) { var options = allBindings.get('options'); if (ko.isObservable(options)) { ko.computed({ read: function() { options(); setTimeout(function() { var ms = $element.data('multiselect'); if (ms) ms.updateOriginalOptions();//Not sure how beneficial this is. $element.multiselect('rebuild'); }, 1); }, disposeWhenNodeIsRemoved: element }); } } //value and selectedOptions are two-way, so these will be triggered even by our own actions. //It needs some way to tell if they are triggered because of us or because of outside change. //It doesn't loop but it's a waste of processing. if (allBindings.has('value')) { var value = allBindings.get('value'); if (ko.isObservable(value)) { ko.computed({ read: function() { value(); setTimeout(function() { $element.multiselect('refresh'); }, 1); }, disposeWhenNodeIsRemoved: element }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); } } //Switched from arrayChange subscription to general subscription using 'refresh'. //Not sure performance is any better using 'select' and 'deselect'. if (allBindings.has('selectedOptions')) { var selectedOptions = allBindings.get('selectedOptions'); if (ko.isObservable(selectedOptions)) { ko.computed({ read: function() { selectedOptions(); setTimeout(function() { $element.multiselect('refresh'); }, 1); }, disposeWhenNodeIsRemoved: element }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); } } var setEnabled = function (enable) { setTimeout(function () { if (enable) $element.multiselect('enable'); else $element.multiselect('disable'); }); }; if (allBindings.has('enable')) { var enable = allBindings.get('enable'); if (ko.isObservable(enable)) { ko.computed({ read: function () { setEnabled(enable()); }, disposeWhenNodeIsRemoved: element }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); } else { setEnabled(enable); } } if (allBindings.has('disable')) { var disable = allBindings.get('disable'); if (ko.isObservable(disable)) { ko.computed({ read: function () { setEnabled(!disable()); }, disposeWhenNodeIsRemoved: element }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); } else { setEnabled(!disable); } } ko.utils.domNodeDisposal.addDisposeCallback(element, function() { $element.multiselect('destroy'); }); }, update: function(element, valueAccessor, allBindings, viewModel, bindingContext) { var $element = $(element); var config = ko.toJS(valueAccessor()); $element.multiselect('setOptions', config); $element.multiselect('rebuild'); } }; } function forEach(array, callback) { for (var index = 0; index < array.length; ++index) { callback(array[index], index); } } /** * Constructor to create a new multiselect using the given select. * * @param {jQuery} select * @param {Object} options * @returns {Multiselect} */ function Multiselect(select, options) { this.$select = $(select); // Placeholder via data attributes if (this.$select.attr("data-placeholder")) { options.nonSelectedText = this.$select.data("placeholder"); } this.options = this.mergeOptions($.extend({}, options, this.$select.data())); // Initialization. // We have to clone to create a new reference. this.originalOptions = this.$select.clone()[0].options; this.query = ''; this.searchTimeout = null; this.lastToggledInput = null; this.options.multiple = this.$select.attr('multiple') === "multiple"; this.options.onChange = $.proxy(this.options.onChange, this); this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this); this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this); this.options.onInitialized = $.proxy(this.options.onInitialized, this); // Build select all if enabled. this.buildContainer(); this.buildButton(); this.buildDropdown(); this.buildSelectAll(); this.buildDropdownOptions(); this.buildFilter(); this.updateButtonText(); this.updateSelectAll(true); if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { this.disable(); } this.$select.hide().after(this.$container); this.options.onInitialized(this.$select, this.$container); } Multiselect.prototype = { defaults: { /** * Default text function will either print 'None selected' in case no * option is selected or a list of the selected options up to a length * of 3 selected options. * * @param {jQuery} options * @param {jQuery} select * @returns {String} */ buttonText: function(options, select) { if (this.disabledText.length > 0 && (this.disableIfEmpty || select.prop('disabled')) && options.length == 0) { return this.disabledText; } else if (options.length === 0) { return this.nonSelectedText; } else if (this.allSelectedText && options.length === $('option', $(select)).length && $('option', $(select)).length !== 1 && this.multiple) { if (this.selectAllNumber) { return this.allSelectedText + ' (' + options.length + ')'; } else { return this.allSelectedText; } } else if (options.length > this.numberDisplayed) { return options.length + ' ' + this.nSelectedText; } else { var selected = ''; var delimiter = this.delimiterText; options.each(function() { var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); selected += label + delimiter; }); return selected.substr(0, selected.length - 2); } }, /** * Updates the title of the button similar to the buttonText function. * * @param {jQuery} options * @param {jQuery} select * @returns {@exp;selected@call;substr} */ buttonTitle: function(options, select) { if (options.length === 0) { return this.nonSelectedText; } else { var selected = ''; var delimiter = this.delimiterText; options.each(function () { var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); selected += label + delimiter; }); return selected.substr(0, selected.length - 2); } }, /** * Create a label. * * @param {jQuery} element * @returns {String} */ optionLabel: function(element){ return $(element).attr('label') || $(element).text(); }, /** * Create a class. * * @param {jQuery} element * @returns {String} */ optionClass: function(element) { return $(element).attr('class') || ''; }, /** * Triggered on change of the multiselect. * * Not triggered when selecting/deselecting options manually. * * @param {jQuery} option * @param {Boolean} checked */ onChange : function(option, checked) { }, /** * Triggered when the dropdown is shown. * * @param {jQuery} event */ onDropdownShow: function(event) { }, /** * Triggered when the dropdown is hidden. * * @param {jQuery} event */ onDropdownHide: function(event) { }, /** * Triggered after the dropdown is shown. * * @param {jQuery} event */ onDropdownShown: function(event) { }, /** * Triggered after the dropdown is hidden. * * @param {jQuery} event */ onDropdownHidden: function(event) { }, /** * Triggered on select all. */ onSelectAll: function(checked) { }, /** * Triggered after initializing. * * @param {jQuery} $select * @param {jQuery} $container */ onInitialized: function($select, $container) { }, enableHTML: false, buttonClass: 'btn btn-default', inheritClass: false, buttonWidth: 'auto', buttonContainer: '
', dropRight: false, dropUp: false, selectedClass: 'active', // Maximum height of the dropdown menu. // If maximum height is exceeded a scrollbar will be displayed. maxHeight: false, checkboxName: false, includeSelectAllOption: false, includeSelectAllIfMoreThan: 0, selectAllText: ' Select all', selectAllValue: 'multiselect-all', selectAllName: false, selectAllNumber: true, selectAllJustVisible: true, enableFiltering: false, enableCaseInsensitiveFiltering: false, enableFullValueFiltering: false, enableClickableOptGroups: false, enableCollapsibelOptGroups: false, filterPlaceholder: 'Search', // possible options: 'text', 'value', 'both' filterBehavior: 'text', includeFilterClearBtn: true, preventInputChangeEvent: false, nonSelectedText: 'None selected', nSelectedText: 'selected', allSelectedText: 'All selected', numberDisplayed: 3, disableIfEmpty: false, disabledText: '', delimiterText: ', ', templates: { button: '', ul: '', filter: '
  • ', filterClearBtn: '', li: '
  • ', divider: '
  • ', liGroup: '
  • ' } }, constructor: Multiselect, /** * Builds the container of the multiselect. */ buildContainer: function() { this.$container = $(this.options.buttonContainer); this.$container.on('show.bs.dropdown', this.options.onDropdownShow); this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); }, /** * Builds the button of the multiselect. */ buildButton: function() { this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); if (this.$select.attr('class') && this.options.inheritClass) { this.$button.addClass(this.$select.attr('class')); } // Adopt active state. if (this.$select.prop('disabled')) { this.disable(); } else { this.enable(); } // Manually add button width if set. if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { this.$button.css({ 'width' : this.options.buttonWidth, 'overflow' : 'hidden', 'text-overflow' : 'ellipsis' }); this.$container.css({ 'width': this.options.buttonWidth }); } // Keep the tab index from the select. var tabindex = this.$select.attr('tabindex'); if (tabindex) { this.$button.attr('tabindex', tabindex); } this.$container.prepend(this.$button); }, /** * Builds the ul representing the dropdown menu. */ buildDropdown: function() { // Build ul. this.$ul = $(this.options.templates.ul); if (this.options.dropRight) { this.$ul.addClass('pull-right'); } // Set max height of dropdown menu to activate auto scrollbar. if (this.options.maxHeight) { // TODO: Add a class for this option to move the css declarations. this.$ul.css({ 'max-height': this.options.maxHeight + 'px', 'overflow-y': 'auto', 'overflow-x': 'hidden' }); } if (this.options.dropUp) { var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0)); var moveCalc = height + 34; this.$ul.css({ 'max-height': height + 'px', 'overflow-y': 'auto', 'overflow-x': 'hidden', 'margin-top': "-" + moveCalc + 'px' }); } this.$container.append(this.$ul); }, /** * Build the dropdown options and binds all nessecary events. * * Uses createDivider and createOptionValue to create the necessary options. */ buildDropdownOptions: function() { this.$select.children().each($.proxy(function(index, element) { var $element = $(element); // Support optgroups and options without a group simultaneously. var tag = $element.prop('tagName') .toLowerCase(); if ($element.prop('value') === this.options.selectAllValue) { return; } if (tag === 'optgroup') { this.createOptgroup(element); } else if (tag === 'option') { if ($element.data('role') === 'divider') { this.createDivider(); } else { this.createOptionValue(element); } } // Other illegal tags will be ignored. }, this)); // Bind the change event on the dropdown elements. $('li input', this.$ul).on('change', $.proxy(function(event) { var $target = $(event.target); var checked = $target.prop('checked') || false; var isSelectAllOption = $target.val() === this.options.selectAllValue; // Apply or unapply the configured selected class. if (this.options.selectedClass) { if (checked) { $target.closest('li') .addClass(this.options.selectedClass); } else { $target.closest('li') .removeClass(this.options.selectedClass); } } // Get the corresponding option. var value = $target.val(); var $option = this.getOptionByValue(value); var $optionsNotThis = $('option', this.$select).not($option); var $checkboxesNotThis = $('input', this.$container).not($target); if (isSelectAllOption) { if (checked) { this.selectAll(this.options.selectAllJustVisible); } else { this.deselectAll(this.options.selectAllJustVisible); } } else { if (checked) { $option.prop('selected', true); if (this.options.multiple) { // Simply select additional option. $option.prop('selected', true); } else { // Unselect all other options and corresponding checkboxes. if (this.options.selectedClass) { $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass); } $($checkboxesNotThis).prop('checked', false); $optionsNotThis.prop('selected', false); // It's a single selection, so close. this.$button.click(); } if (this.options.selectedClass === "active") { $optionsNotThis.closest("a").css("outline", ""); } } else { // Unselect option. $option.prop('selected', false); } // To prevent select all from firing onChange: #575 this.options.onChange($option, checked); } this.$select.change(); this.updateButtonText(); this.updateSelectAll(); if(this.options.preventInputChangeEvent) { return false; } }, this)); $('li a', this.$ul).on('mousedown', function(e) { if (e.shiftKey) { // Prevent selecting text by Shift+click return false; } }); $('li a', this.$ul).on('touchstart click', $.proxy(function(event) { event.stopPropagation(); var $target = $(event.target); if (event.shiftKey && this.options.multiple) { if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431) event.preventDefault(); $target = $target.find("input"); $target.prop("checked", !$target.prop("checked")); } var checked = $target.prop('checked') || false; if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range var from = $target.closest("li").index(); var to = this.lastToggledInput.closest("li").index(); if (from > to) { // Swap the indices var tmp = to; to = from; from = tmp; } // Make sure we grab all elements since slice excludes the last index ++to; // Change the checkboxes and underlying options var range = this.$ul.find("li").slice(from, to).find("input"); range.prop('checked', checked); if (this.options.selectedClass) { range.closest('li') .toggleClass(this.options.selectedClass, checked); } for (var i = 0, j = range.length; i < j; i++) { var $checkbox = $(range[i]); var $option = this.getOptionByValue($checkbox.val()); $option.prop('selected', checked); } } // Trigger the select "change" event $target.trigger("change"); } // Remembers last clicked option if($target.is("input") && !$target.closest("li").is(".multiselect-item")){ this.lastToggledInput = $target; } $target.blur(); }, this)); // Keyboard support. this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) { if ($('input[type="text"]', this.$container).is(':focus')) { return; } if (event.keyCode === 9 && this.$container.hasClass('open')) { this.$button.click(); } else { var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible"); if (!$items.length) { return; } var index = $items.index($items.filter(':focus')); // Navigation up. if (event.keyCode === 38 && index > 0) { index--; } // Navigate down. else if (event.keyCode === 40 && index < $items.length - 1) { index++; } else if (!~index) { index = 0; } var $current = $items.eq(index); $current.focus(); if (event.keyCode === 32 || event.keyCode === 13) { var $checkbox = $current.find('input'); $checkbox.prop("checked", !$checkbox.prop("checked")); $checkbox.change(); } event.stopPropagation(); event.preventDefault(); } }, this)); if(this.options.enableClickableOptGroups && this.options.multiple) { $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) { event.stopPropagation(); var group = $(event.target).parent(); // Search all option in optgroup var $options = group.nextUntil('li.multiselect-group'); var $visibleOptions = $options.filter(":visible:not(.disabled)"); // check or uncheck items var allChecked = true; var optionInputs = $visibleOptions.find('input'); var values = []; optionInputs.each(function() { allChecked = allChecked && $(this).prop('checked'); values.push($(this).val()); }); if (!allChecked) { this.select(values, false); } else { this.deselect(values, false); } this.options.onChange(optionInputs, !allChecked); }, this)); } if (this.options.enableCollapsibleOptGroups && this.options.multiple) { $("li.multiselect-group input", this.$ul).off(); $("li.multiselect-group", this.$ul).siblings().not("li.multiselect-group, li.multiselect-all", this.$ul).each( function () { $(this).toggleClass('hidden', true); }); $("li.multiselect-group", this.$ul).on("click", $.proxy(function(group) { group.stopPropagation(); }, this)); $("li.multiselect-group > a > b", this.$ul).on("click", $.proxy(function(t) { t.stopPropagation(); var n = $(t.target).closest('li'); var r = n.nextUntil("li.multiselect-group"); var i = true; r.each(function() { i = i && $(this).hasClass('hidden'); }); r.toggleClass('hidden', !i); }, this)); console.log($("li.multiselect-group > a > input", this.$ul)); $("li.multiselect-group > a > input", this.$ul).on("change", $.proxy(function(t) { t.stopPropagation(); var n = $(t.target).closest('li'); var r = n.nextUntil("li.multiselect-group", ':not(.disabled)'); var s = r.find("input"); var i = true; s.each(function() { i = i && $(this).prop("checked"); }); s.prop("checked", !i).trigger("change"); }, this)); // Set the initial selection state of the groups. $('li.multiselect-group', this.$ul).each(function() { var r = $(this).nextUntil("li.multiselect-group", ':not(.disabled)'); var s = r.find("input"); var i = true; s.each(function() { i = i && $(this).prop("checked"); }); $(this).find('input').prop("checked", i); }); // Update the group checkbox based on new selections among the // corresponding children. $("li input", this.$ul).on("change", $.proxy(function(t) { t.stopPropagation(); var n = $(t.target).closest('li'); var r1 = n.prevUntil("li.multiselect-group", ':not(.disabled)'); var r2 = n.nextUntil("li.multiselect-group", ':not(.disabled)'); var s1 = r1.find("input"); var s2 = r2.find("input"); var i = $(t.target).prop('checked'); s1.each(function() { i = i && $(this).prop("checked"); }); s2.each(function() { i = i && $(this).prop("checked"); }); n.prevAll('.multiselect-group').find('input').prop('checked', i); }, this)); $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea'); $("li.multiselect-group > a, li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px'); $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px'); } }, /** * Create an option using the given select option. * * @param {jQuery} element */ createOptionValue: function(element) { var $element = $(element); if ($element.is(':selected')) { $element.prop('selected', true); } // Support the label attribute on options. var label = this.options.optionLabel(element); var classes = this.options.optionClass(element); var value = $element.val(); var inputType = this.options.multiple ? "checkbox" : "radio"; var $li = $(this.options.templates.li); var $label = $('label', $li); $label.addClass(inputType); $li.addClass(classes); if (this.options.enableHTML) { $label.html(" " + label); } else { $label.text(" " + label); } var $checkbox = $('').attr('type', inputType); if (this.options.checkboxName) { $checkbox.attr('name', this.options.checkboxName); } $label.prepend($checkbox); var selected = $element.prop('selected') || false; $checkbox.val(value); if (value === this.options.selectAllValue) { $li.addClass("multiselect-item multiselect-all"); $checkbox.parent().parent() .addClass('multiselect-all'); } $label.attr('title', $element.attr('title')); this.$ul.append($li); if ($element.is(':disabled')) { $checkbox.attr('disabled', 'disabled') .prop('disabled', true) .closest('a') .attr("tabindex", "-1") .closest('li') .addClass('disabled'); } $checkbox.prop('checked', selected); if (selected && this.options.selectedClass) { $checkbox.closest('li') .addClass(this.options.selectedClass); } }, /** * Creates a divider using the given select option. * * @param {jQuery} element */ createDivider: function(element) { var $divider = $(this.options.templates.divider); this.$ul.append($divider); }, /** * Creates an optgroup. * * @param {jQuery} group */ createOptgroup: function(group) { if (this.options.enableCollapsibleOptGroups && this.options.multiple) { var label = $(group).attr("label"); var value = $(group).attr("value"); var r = $('
  • ' + label + '
  • '); if (this.options.enableClickableOptGroups) { r.addClass("multiselect-group-clickable") } this.$ul.append(r); if ($(group).is(":disabled")) { r.addClass("disabled") } $("option", group).each($.proxy(function($, group) { this.createOptionValue(group) }, this)) } else { var groupName = $(group).prop('label'); // Add a header for the group. var $li = $(this.options.templates.liGroup); if (this.options.enableHTML) { $('label', $li).html(groupName); } else { $('label', $li).text(groupName); } if (this.options.enableClickableOptGroups) { $li.addClass('multiselect-group-clickable'); } this.$ul.append($li); if ($(group).is(':disabled')) { $li.addClass('disabled'); } // Add the options of the group. $('option', group).each($.proxy(function(index, element) { this.createOptionValue(element); }, this)); } }, /** * Build the select all. * * Checks if a select all has already been created. */ buildSelectAll: function() { if (typeof this.options.selectAllValue === 'number') { this.options.selectAllValue = this.options.selectAllValue.toString(); } var alreadyHasSelectAll = this.hasSelectAll(); if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { // Check whether to add a divider after the select all. if (this.options.includeSelectAllDivider) { this.$ul.prepend($(this.options.templates.divider)); } var $li = $(this.options.templates.li); $('label', $li).addClass("checkbox"); if (this.options.enableHTML) { $('label', $li).html(" " + this.options.selectAllText); } else { $('label', $li).text(" " + this.options.selectAllText); } if (this.options.selectAllName) { $('label', $li).prepend(''); } else { $('label', $li).prepend(''); } var $checkbox = $('input', $li); $checkbox.val(this.options.selectAllValue); $li.addClass("multiselect-item multiselect-all"); $checkbox.parent().parent() .addClass('multiselect-all'); this.$ul.prepend($li); $checkbox.prop('checked', false); } }, /** * Builds the filter. */ buildFilter: function() { // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); if (this.$select.find('option').length >= enableFilterLength) { this.$filter = $(this.options.templates.filter); $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); // Adds optional filter clear button if(this.options.includeFilterClearBtn){ var clearBtn = $(this.options.templates.filterClearBtn); clearBtn.on('click', $.proxy(function(event){ clearTimeout(this.searchTimeout); this.$filter.find('.multiselect-search').val(''); $('li', this.$ul).show().removeClass("filter-hidden"); this.updateSelectAll(); }, this)); this.$filter.find('.input-group').append(clearBtn); } this.$ul.prepend(this.$filter); this.$filter.val(this.query).on('click', function(event) { event.stopPropagation(); }) .on('input keydown', $.proxy(function(event) { // Cancel enter key default behaviour if (event.which === 13) { event.preventDefault(); } // This is useful to catch "keydown" events after the browser has updated the control. clearTimeout(this.searchTimeout); this.searchTimeout = this.asyncFunction($.proxy(function() { if (this.query !== event.target.value) { this.query = event.target.value; var currentGroup, currentGroupVisible; $.each($('li', this.$ul), $.proxy(function(index, element) { var value = $('input', element).length > 0 ? $('input', element).val() : ""; var text = $('label', element).text(); var filterCandidate = ''; if ((this.options.filterBehavior === 'text')) { filterCandidate = text; } else if ((this.options.filterBehavior === 'value')) { filterCandidate = value; } else if (this.options.filterBehavior === 'both') { filterCandidate = text + '\n' + value; } if (value !== this.options.selectAllValue && text) { // By default lets assume that element is not // interesting for this search. var showElement = false; if (this.options.enableCaseInsensitiveFiltering) { filterCandidate = filterCandidate.toLowerCase(); this.query = this.query.toLowerCase(); } if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') { var valueToMatch = filterCandidate.trim().substring(0, this.query.length); if (this.query.indexOf(valueToMatch) > -1) { showElement = true; } } else if (filterCandidate.indexOf(this.query) > -1) { showElement = true; } console.log(!showElement); // Toggle current element (group or group item) according to showElement boolean. $(element).toggle(showElement).toggleClass('filter-hidden', !showElement); // Differentiate groups and group items. if ($(element).hasClass('multiselect-group')) { // Remember group status. currentGroup = element; currentGroupVisible = showElement; } else { // Show group name when at least one of its items is visible. if (showElement) { $(currentGroup).show().removeClass('filter-hidden'); } // Show all group items when group name satisfies filter. if (!showElement && currentGroupVisible) { $(element).show().removeClass('filter-hidden'); } } } }, this)); } this.updateSelectAll(); }, this), 300, this); }, this)); } } }, /** * Unbinds the whole plugin. */ destroy: function() { this.$container.remove(); this.$select.show(); this.$select.data('multiselect', null); }, /** * Refreshs the multiselect based on the selected options of the select. */ refresh: function () { var inputs = $.map($('li input', this.$ul), $); $('option', this.$select).each($.proxy(function (index, element) { var $elem = $(element); var value = $elem.val(); var $input; for (var i = inputs.length; 0 < i--; /**/) { if (value !== ($input = inputs[i]).val()) continue; // wrong li if ($elem.is(':selected')) { $input.prop('checked', true); if (this.options.selectedClass) { $input.closest('li') .addClass(this.options.selectedClass); } } else { $input.prop('checked', false); if (this.options.selectedClass) { $input.closest('li') .removeClass(this.options.selectedClass); } } if ($elem.is(":disabled")) { $input.attr('disabled', 'disabled') .prop('disabled', true) .closest('li') .addClass('disabled'); } else { $input.prop('disabled', false) .closest('li') .removeClass('disabled'); } break; // assumes unique values } }, this)); this.updateButtonText(); this.updateSelectAll(); }, /** * Select all options of the given values. * * If triggerOnChange is set to true, the on change event is triggered if * and only if one value is passed. * * @param {Array} selectValues * @param {Boolean} triggerOnChange */ select: function(selectValues, triggerOnChange) { if(!$.isArray(selectValues)) { selectValues = [selectValues]; } for (var i = 0; i < selectValues.length; i++) { var value = selectValues[i]; if (value === null || value === undefined) { continue; } var $option = this.getOptionByValue(value); var $checkbox = this.getInputByValue(value); if($option === undefined || $checkbox === undefined) { continue; } if (!this.options.multiple) { this.deselectAll(false); } if (this.options.selectedClass) { $checkbox.closest('li') .addClass(this.options.selectedClass); } $checkbox.prop('checked', true); $option.prop('selected', true); if (triggerOnChange) { this.options.onChange($option, true); } } this.updateButtonText(); this.updateSelectAll(); }, /** * Clears all selected items. */ clearSelection: function () { this.deselectAll(false); this.updateButtonText(); this.updateSelectAll(); }, /** * Deselects all options of the given values. * * If triggerOnChange is set to true, the on change event is triggered, if * and only if one value is passed. * * @param {Array} deselectValues * @param {Boolean} triggerOnChange */ deselect: function(deselectValues, triggerOnChange) { if(!$.isArray(deselectValues)) { deselectValues = [deselectValues]; } for (var i = 0; i < deselectValues.length; i++) { var value = deselectValues[i]; if (value === null || value === undefined) { continue; } var $option = this.getOptionByValue(value); var $checkbox = this.getInputByValue(value); if($option === undefined || $checkbox === undefined) { continue; } if (this.options.selectedClass) { $checkbox.closest('li') .removeClass(this.options.selectedClass); } $checkbox.prop('checked', false); $option.prop('selected', false); if (triggerOnChange) { this.options.onChange($option, false); } } this.updateButtonText(); this.updateSelectAll(); }, /** * Selects all enabled & visible options. * * If justVisible is true or not specified, only visible options are selected. * * @param {Boolean} justVisible * @param {Boolean} triggerOnSelectAll */ selectAll: function (justVisible, triggerOnSelectAll) { justVisible = (this.options.enableCollapsibleOptGroups && this.options.multiple) ? false : justVisible; var justVisible = typeof justVisible === 'undefined' ? true : justVisible; var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul); var visibleCheckboxes = allCheckboxes.filter(":visible"); var allCheckboxesCount = allCheckboxes.length; var visibleCheckboxesCount = visibleCheckboxes.length; if(justVisible) { visibleCheckboxes.prop('checked', true); $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass); } else { allCheckboxes.prop('checked', true); $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass); } if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) { $("option:not([data-role='divider']):enabled", this.$select).prop('selected', true); } else { var values = visibleCheckboxes.map(function() { return $(this).val(); }).get(); $("option:enabled", this.$select).filter(function(index) { return $.inArray($(this).val(), values) !== -1; }).prop('selected', true); } if (triggerOnSelectAll) { this.options.onSelectAll(); } }, /** * Deselects all options. * * If justVisible is true or not specified, only visible options are deselected. * * @param {Boolean} justVisible */ deselectAll: function (justVisible) { justVisible = (this.options.enableCollapsibleOptGroups && this.options.multiple) ? false : justVisible; justVisible = typeof justVisible === 'undefined' ? true : justVisible; if(justVisible) { var visibleCheckboxes = $("li input[type='checkbox']:not(:disabled)", this.$ul).filter(":visible"); visibleCheckboxes.prop('checked', false); var values = visibleCheckboxes.map(function() { return $(this).val(); }).get(); $("option:enabled", this.$select).filter(function(index) { return $.inArray($(this).val(), values) !== -1; }).prop('selected', false); if (this.options.selectedClass) { $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass); } } else { $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false); $("option:enabled", this.$select).prop('selected', false); if (this.options.selectedClass) { $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass); } } }, /** * Rebuild the plugin. * * Rebuilds the dropdown, the filter and the select all option. */ rebuild: function() { this.$ul.html(''); // Important to distinguish between radios and checkboxes. this.options.multiple = this.$select.attr('multiple') === "multiple"; this.buildSelectAll(); this.buildDropdownOptions(); this.buildFilter(); this.updateButtonText(); this.updateSelectAll(true); if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { this.disable(); } else { this.enable(); } if (this.options.dropRight) { this.$ul.addClass('pull-right'); } }, /** * The provided data will be used to build the dropdown. */ dataprovider: function(dataprovider) { var groupCounter = 0; var $select = this.$select.empty(); $.each(dataprovider, function (index, option) { var $tag; if ($.isArray(option.children)) { // create optiongroup tag groupCounter++; $tag = $('').attr({ label: option.label || 'Group ' + groupCounter, disabled: !!option.disabled }); forEach(option.children, function(subOption) { // add children option tags $tag.append($('